From b58e78066615d7f65b11a008625dce94ab9f149d Mon Sep 17 00:00:00 2001 From: Ferran Recio <ferran@moodle.com> Date: Mon, 28 Jun 2021 13:32:00 +0200 Subject: [PATCH] MDL-71995 core: reactive debug tools --- lang/en/admin.php | 1 + lang/en/debug.php | 11 + lib/amd/build/local/reactive/debug.min.js | 2 + lib/amd/build/local/reactive/debug.min.js.map | 1 + .../build/local/reactive/debugpanel.min.js | 2 + .../local/reactive/debugpanel.min.js.map | 1 + lib/amd/build/local/reactive/reactive.min.js | 2 +- .../build/local/reactive/reactive.min.js.map | 2 +- .../build/local/reactive/statemanager.min.js | 2 +- .../local/reactive/statemanager.min.js.map | 2 +- lib/amd/build/reactive.min.js | 2 +- lib/amd/build/reactive.min.js.map | 2 +- lib/amd/src/local/reactive/debug.js | 372 ++++++++++++ lib/amd/src/local/reactive/debugpanel.js | 563 ++++++++++++++++++ lib/amd/src/local/reactive/reactive.js | 11 + lib/amd/src/local/reactive/statemanager.js | 11 + lib/amd/src/reactive.js | 11 +- lib/outputrenderers.php | 4 + .../reactive/debuginstancepanel.mustache | 86 +++ .../local/reactive/debugpanel.mustache | 45 ++ 20 files changed, 1126 insertions(+), 7 deletions(-) create mode 100644 lib/amd/build/local/reactive/debug.min.js create mode 100644 lib/amd/build/local/reactive/debug.min.js.map create mode 100644 lib/amd/build/local/reactive/debugpanel.min.js create mode 100644 lib/amd/build/local/reactive/debugpanel.min.js.map create mode 100644 lib/amd/src/local/reactive/debug.js create mode 100644 lib/amd/src/local/reactive/debugpanel.js create mode 100644 lib/templates/local/reactive/debuginstancepanel.mustache create mode 100644 lib/templates/local/reactive/debugpanel.mustache diff --git a/lang/en/admin.php b/lang/en/admin.php index 90243b03e31..1cf92cdf7dc 100644 --- a/lang/en/admin.php +++ b/lang/en/admin.php @@ -140,6 +140,7 @@ $string['cliupgradefinished'] = 'Command line upgrade from {$a->oldversion} to { $string['cliupgradenoneed'] = 'No upgrade needed for the installed version {$a}. Thanks for coming anyway!'; $string['cliupgradepending'] = 'An upgrade is pending'; $string['cliyesnoprompt'] = 'type y (means yes) or n (means no)'; +$string['close'] = 'Close'; $string['commentsperpage'] = 'Comments displayed per page'; $string['commonactivitysettings'] = 'Common activity settings'; $string['commonfiltersettings'] = 'Common filter settings'; diff --git a/lang/en/debug.php b/lang/en/debug.php index 3e974935889..ef151f0e165 100644 --- a/lang/en/debug.php +++ b/lang/en/debug.php @@ -52,6 +52,17 @@ $string['notables'] = 'No tables!'; $string['outputbuffer'] = 'Output buffer'; $string['phpvaroff'] = 'The PHP server variable \'{$a->name}\' should be Off - {$a->link}'; $string['phpvaron'] = 'The PHP server variable \'{$a->name}\' is not turned On - {$a->link}'; +$string['reactive_instances'] = 'Reactive instances:'; +$string['reactive_noinstances'] = 'this page has no reactive instances'; +$string['reactive_pin'] = 'Pin'; +$string['reactive_unpin'] = 'Unpin'; +$string['reactive_highlightoff'] = 'Highlight OFF'; +$string['reactive_highlighton'] = 'Highlight ON'; +$string['reactive_readmodeon'] = 'Read mode ON'; +$string['reactive_readmodeoff'] = 'Read mode OFF'; +$string['reactive_resetpanel'] = 'Reset panel'; +$string['reactive_statedata'] = 'State data'; +$string['reactive_saveingwarning'] = 'Edit the state can cause inexpected results'; $string['sessionmissing'] = '{$a} object missing from session'; $string['sqlrelyonobsoletetable'] = 'This SQL relies on obsolete table(s): {$a}! Your code must be fixed by a developer.'; $string['stacktrace'] = 'Stack trace'; diff --git a/lib/amd/build/local/reactive/debug.min.js b/lib/amd/build/local/reactive/debug.min.js new file mode 100644 index 00000000000..e7d0e697b66 --- /dev/null +++ b/lib/amd/build/local/reactive/debug.min.js @@ -0,0 +1,2 @@ +define ("core/local/reactive/debug",["exports","core/local/reactive/reactive","core/log"],function(a,b,c){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.initDebug=void 0;b=d(b);c=d(c);function d(a){return a&&a.__esModule?a:{default:a}}function e(a){return i(a)||h(a)||g(a)||f()}function f(){throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}function g(a,b){if(!a)return;if("string"==typeof a)return j(a,b);var c=Object.prototype.toString.call(a).slice(8,-1);if("Object"===c&&a.constructor)c=a.constructor.name;if("Map"===c||"Set"===c)return Array.from(c);if("Arguments"===c||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(c))return j(a,b)}function h(a){if("undefined"!=typeof Symbol&&Symbol.iterator in Object(a))return Array.from(a)}function i(a){if(Array.isArray(a))return j(a)}function j(a,b){if(null==b||b>a.length)b=a.length;for(var c=0,d=Array(b);c<b;c++){d[c]=a[c]}return d}function k(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 l(a){return function(){var b=this,c=arguments;return new Promise(function(d,e){var h=a.apply(b,c);function f(a){k(h,d,e,f,g,"next",a)}function g(a){k(h,d,e,f,g,"throw",a)}f(void 0)})}}function m(a){"@babel/helpers - typeof";if("function"==typeof Symbol&&"symbol"==typeof Symbol.iterator){m=function(a){return typeof a}}else{m=function(a){return a&&"function"==typeof Symbol&&a.constructor===Symbol&&a!==Symbol.prototype?"symbol":typeof a}}return m(a)}function n(a,b){if(!(a instanceof b)){throw new TypeError("Cannot call a class as a function")}}function o(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 p(a,b,c){if(b)o(a.prototype,b);if(c)o(a,c);return a}function q(a,b,c){if("undefined"!=typeof Reflect&&Reflect.get){q=Reflect.get}else{q=function(a,b,c){var d=r(a,b);if(!d)return;var e=Object.getOwnPropertyDescriptor(d,b);if(e.get){return e.get.call(c)}return e.value}}return q(a,b,c||a)}function r(a,b){while(!Object.prototype.hasOwnProperty.call(a,b)){a=y(a);if(null===a)break}return a}function s(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)t(a,b)}function t(a,b){t=Object.setPrototypeOf||function(a,b){a.__proto__=b;return a};return t(a,b)}function u(a){return function(){var b=y(a),c;if(x()){var d=y(this).constructor;c=Reflect.construct(b,arguments,d)}else{c=b.apply(this,arguments)}return v(this,c)}}function v(a,b){if(b&&("object"===m(b)||"function"==typeof b)){return b}return w(a)}function w(a){if(void 0===a){throw new ReferenceError("this hasn't been initialised - super() hasn't been called")}return a}function x(){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 y(a){y=Object.setPrototypeOf?Object.getPrototypeOf:function(a){return a.__proto__||Object.getPrototypeOf(a)};return y(a)}var A={},B={},C=function(a){s(b,a);var d=u(b);function b(){n(this,b);return d.apply(this,arguments)}p(b,[{key:"setInitialState",value:function setInitialState(a){q(y(b.prototype),"setInitialState",this).call(this,a);c.default.debug("Debug module \"M.reactive\" loaded.")}},{key:"registerNewInstance",value:function registerNewInstance(a){var b,d=this,e=null!==(b=a.name)&&void 0!==b?b:"instance".concat(this.state.reactives.length);e=e.replace(/\W/g,"");c.default.debug("Registering new reactive instance \"M.reactive.".concat(e,"\""));A[e]=a;B[e]=new E(A[e]);this.dispatch("putInstance",e,a);var f=function(){d.dispatch("putInstance",e,a)};a.target.addEventListener("readmode:on",f);a.target.addEventListener("readmode:off",f);a.target.addEventListener("registerComponent:success",f);a.target.addEventListener("transaction:end",f);var g=function(a){var b=a.detail,c=null===b||void 0===b?void 0:b.changes;d.dispatch("lastTransaction",e,c)};a.target.addEventListener("transaction:start",g)}},{key:"debug",value:function debug(a){return B[a]}},{key:"list",get:function get(){return JSON.parse(JSON.stringify(this.state.reactives))}}]);return b}(b.default),D=function(){function a(){n(this,a)}p(a,[{key:"putInstance",value:function putInstance(a,b,c){var d=a.state;a.setReadOnly(!1);if(d.reactives.has(b)){d.reactives.get(b).countcomponents=c.components.length;d.reactives.get(b).readOnly=c.stateManager.readonly;d.reactives.get(b).modified=new Date().getTime()}else{d.reactives.add({id:b,countcomponents:c.components.length,readOnly:c.stateManager.readonly,lastChanges:[],modified:new Date().getTime()})}a.setReadOnly(!0)}},{key:"lastTransaction",value:function lastTransaction(a,b,c){if(!c||0===c.length){return}var d=a.state,e=["transaction:start"];c.forEach(function(a){e.push(a.eventName)});e.push("transaction:end");a.setReadOnly(!1);d.reactives.get(b).lastChanges=e;a.setReadOnly(!0)}}]);return a}(),E=function(){function a(b){n(this,a);this.instance=b;if(b._reactiveDebugData===void 0){b._reactiveDebugData={highlighted:!1}}}p(a,[{key:"dispatch",value:function(){var a=l(regeneratorRuntime.mark(function a(){var b,c=arguments;return regeneratorRuntime.wrap(function(a){while(1){switch(a.prev=a.next){case 0:(b=this.instance).dispatch.apply(b,c);case 1:case"end":return a.stop();}}},a,this)}));return function dispatch(){return a.apply(this,arguments)}}()},{key:"processUpdates",value:function processUpdates(a){this.instance.stateManager.processUpdates(a)}},{key:"readOnly",set:function set(a){this.instance.stateManager.setReadOnly(a)},get:function get(){return this.instance.stateManager.readonly}},{key:"state",get:function get(){return this.instance.state}},{key:"highlight",set:function set(a){this.instance._reactiveDebugData.highlighted=a;this.instance.components.forEach(function(b){var c=b.element,d=a?"thick solid #0000FF":"";c.style.border=d})},get:function get(){return this.instance._reactiveDebugData.highlighted}},{key:"components",get:function get(){return e(this.instance.components)}},{key:"changes",get:function get(){var a=[];this.instance.stateManager.eventsToPublish.forEach(function(b){a.push(b.eventName)});return a}},{key:"elements",get:function get(){var a=[];this.instance.components.forEach(function(b){var c=b.element;a.push(c)});return a}},{key:"stateData",get:function get(){return JSON.parse(JSON.stringify(this.state))}}]);return a}(),F="core_reactive_debug:stateChanged";function z(a,b){if(b===void 0){b=document}b.dispatchEvent(new CustomEvent(F,{bubbles:!0,detail:a}))}a.initDebug=function initDebug(){var a=new C({name:"CoreReactiveDebug",eventName:F,eventDispatch:z,mutations:new D,state:{reactives:[]}});B.registerNewInstance=a.registerNewInstance.bind(a);return{debug:a,debuggers:B}}}); +//# sourceMappingURL=debug.min.js.map diff --git a/lib/amd/build/local/reactive/debug.min.js.map b/lib/amd/build/local/reactive/debug.min.js.map new file mode 100644 index 00000000000..e6855c5e1f7 --- /dev/null +++ b/lib/amd/build/local/reactive/debug.min.js.map @@ -0,0 +1 @@ +{"version":3,"sources":["../../../src/local/reactive/debug.js"],"names":["reactiveInstances","reactiveDebuggers","Debug","stateData","log","debug","instance","name","state","reactives","length","replace","DebugInstance","dispatch","refreshMethod","target","addEventListener","storeTransaction","detail","changes","JSON","parse","stringify","Reactive","Mutations","stateManager","setReadOnly","has","get","countcomponents","components","readOnly","readonly","modified","Date","getTime","add","id","lastChanges","forEach","change","push","eventName","_reactiveDebugData","highlighted","updates","processUpdates","value","element","border","style","result","eventsToPublish","stateChangedEventName","dispatchStateChangedEvent","document","dispatchEvent","CustomEvent","bubbles","initDebug","eventDispatch","mutations","registerNewInstance","bind","debuggers"],"mappings":"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","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 * Reactive module debug tools.\n *\n * @module core/reactive/local/reactive/debug\n * @copyright 2021 Ferran Recio <ferran@moodle.com>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport Reactive from 'core/local/reactive/reactive';\nimport log from 'core/log';\n\n// The list of reactives instances.\nconst reactiveInstances = {};\n\n// The reactive debugging objects.\nconst reactiveDebuggers = {};\n\n/**\n * Reactive module debug tools.\n *\n * If debug is enabled, this reactive module will spy all the reactive instances and keep a record\n * of the changes and components they have.\n *\n * It is important to note that the Debug class is also a Reactive module. The debug instance keeps\n * the reactive instances data as its own state. This way it is possible to implement development tools\n * that whatches this data.\n *\n * @class core/reactive/local/reactive/debug/Debug\n * @copyright 2021 Ferran Recio <ferran@moodle.com>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\nclass Debug extends Reactive {\n\n /**\n * Set the initial state.\n *\n * @param {object} stateData the initial state data.\n */\n setInitialState(stateData) {\n super.setInitialState(stateData);\n log.debug(`Debug module \"M.reactive\" loaded.`);\n }\n\n /**\n * List the currents page reactives instances.\n */\n get list() {\n return JSON.parse(JSON.stringify(this.state.reactives));\n }\n\n /**\n * Register a new Reactive instance.\n *\n * This method is called every time a \"new Reactive\" is executed.\n *\n * @param {Reactive} instance the reactive instance\n */\n registerNewInstance(instance) {\n\n // Generate a valid variable name for that instance.\n let name = instance.name ?? `instance${this.state.reactives.length}`;\n name = name.replace(/\\W/g, '');\n\n log.debug(`Registering new reactive instance \"M.reactive.${name}\"`);\n\n reactiveInstances[name] = instance;\n reactiveDebuggers[name] = new DebugInstance(reactiveInstances[name]);\n // Register also in the state.\n this.dispatch('putInstance', name, instance);\n // Add debug watchers to instance.\n const refreshMethod = () => {\n this.dispatch('putInstance', name, instance);\n };\n instance.target.addEventListener('readmode:on', refreshMethod);\n instance.target.addEventListener('readmode:off', refreshMethod);\n instance.target.addEventListener('registerComponent:success', refreshMethod);\n instance.target.addEventListener('transaction:end', refreshMethod);\n // We store the last transaction into the state.\n const storeTransaction = ({detail}) => {\n const changes = detail?.changes;\n this.dispatch('lastTransaction', name, changes);\n };\n instance.target.addEventListener('transaction:start', storeTransaction);\n }\n\n /**\n * Returns a debugging object for a specific Reactive instance.\n *\n * A debugging object is a class that wraps a Reactive instance to quick access some of the\n * reactive methods using the browser JS console.\n *\n * @param {string} name the Reactive instance name\n * @returns {DebugInstance} a debug object wrapping the Reactive instance\n */\n debug(name) {\n return reactiveDebuggers[name];\n }\n}\n\n/**\n * The debug state mutations class.\n *\n * @class core/reactive/local/reactive/debug/Mutations\n */\nclass Mutations {\n\n /**\n * Insert or update a new instance into the debug state.\n *\n * @param {StateManager} stateManager the debug state manager\n * @param {string} name the instance name\n * @param {Reactive} instance the reactive instance\n */\n putInstance(stateManager, name, instance) {\n const state = stateManager.state;\n\n stateManager.setReadOnly(false);\n\n if (state.reactives.has(name)) {\n state.reactives.get(name).countcomponents = instance.components.length;\n state.reactives.get(name).readOnly = instance.stateManager.readonly;\n state.reactives.get(name).modified = new Date().getTime();\n } else {\n state.reactives.add({\n id: name,\n countcomponents: instance.components.length,\n readOnly: instance.stateManager.readonly,\n lastChanges: [],\n modified: new Date().getTime(),\n });\n }\n stateManager.setReadOnly(true);\n }\n\n /**\n * Update the lastChanges attribute with a list of changes\n *\n * @param {StateManager} stateManager the debug reactive state\n * @param {string} name tje instance name\n * @param {array} changes the list of changes\n */\n lastTransaction(stateManager, name, changes) {\n if (!changes || changes.length === 0) {\n return;\n }\n\n const state = stateManager.state;\n const lastChanges = ['transaction:start'];\n\n changes.forEach(change => {\n lastChanges.push(change.eventName);\n });\n\n lastChanges.push('transaction:end');\n\n stateManager.setReadOnly(false);\n\n state.reactives.get(name).lastChanges = lastChanges;\n\n stateManager.setReadOnly(true);\n }\n}\n\n/**\n * Class used to debug a specific instance and manipulate the state from the JS console.\n *\n * @class core/reactive/local/reactive/debug/DebugInstance\n * @copyright 2021 Ferran Recio <ferran@moodle.com>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\nclass DebugInstance {\n\n /**\n * Constructor.\n *\n * @param {Reactive} instance the reactive instance\n */\n constructor(instance) {\n this.instance = instance;\n // Add some debug data directly into the instance. This way we avoid having attributes\n // that will confuse the console aoutocomplete.\n if (instance._reactiveDebugData === undefined) {\n instance._reactiveDebugData = {\n highlighted: false,\n };\n }\n }\n\n /**\n * Set the read only mode.\n *\n * Quick access to the instance setReadOnly method.\n *\n * @param {bool} value: the new read only value\n */\n set readOnly(value) {\n this.instance.stateManager.setReadOnly(value);\n }\n\n /**\n * Get the read only value\n *\n * @return {bool}\n */\n get readOnly() {\n return this.instance.stateManager.readonly;\n }\n\n /**\n * Return the current state object.\n *\n * @return {object}\n */\n get state() {\n return this.instance.state;\n }\n\n /**\n * Tooggle the reactive HTML element highlight registered in this reactive instance.\n *\n * @param {bool} value the highlight value\n */\n set highlight(value) {\n this.instance._reactiveDebugData.highlighted = value;\n this.instance.components.forEach(({element}) => {\n const border = (value) ? `thick solid #0000FF` : '';\n element.style.border = border;\n });\n }\n\n /**\n * Get the current highligh value.\n *\n * @return {bool}\n */\n get highlight() {\n return this.instance._reactiveDebugData.highlighted;\n }\n\n /**\n * List all the components registered in this instance.\n *\n * @return {array}\n */\n get components() {\n return [...this.instance.components];\n }\n\n /**\n * List all the state changes evenet pending to dispatch.\n *\n * @return {array}\n */\n get changes() {\n const result = [];\n this.instance.stateManager.eventsToPublish.forEach(\n (element) => {\n result.push(element.eventName);\n }\n );\n return result;\n }\n\n /**\n * Dispatch a change in the state.\n *\n * Usually reactive modules throw an error directly to the components when something\n * goes wrong. However, course editor can directly display a notification.\n *\n * @method dispatch\n * @param {string} actionName the action name (usually the mutation name)\n * @param {*} param any number of params the mutation needs.\n */\n async dispatch(...args) {\n this.instance.dispatch(...args);\n }\n\n /**\n * Return all the HTML elements registered in the instance components.\n *\n * @return {array}\n */\n get elements() {\n const result = [];\n this.instance.components.forEach(({element}) => {\n result.push(element);\n });\n return result;\n }\n\n /**\n * Return a plain copy of the state data.\n *\n * @return {object}\n */\n get stateData() {\n return JSON.parse(JSON.stringify(this.state));\n }\n\n /**\n * Process an update state array.\n *\n * @param {array} updates an array of update state messages\n */\n processUpdates(updates) {\n this.instance.stateManager.processUpdates(updates);\n }\n}\n\nconst stateChangedEventName = 'core_reactive_debug:stateChanged';\n\n/**\n * Internal 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 */\nfunction dispatchStateChangedEvent(detail, target) {\n if (target === undefined) {\n target = document;\n }\n target.dispatchEvent(\n new CustomEvent(\n stateChangedEventName,\n {\n bubbles: true,\n detail: detail,\n }\n )\n );\n}\n\n/**\n * The main init method to initialize the reactive debug.\n * @returns {object}\n */\nexport const initDebug = () => {\n const debug = new Debug({\n name: 'CoreReactiveDebug',\n eventName: stateChangedEventName,\n eventDispatch: dispatchStateChangedEvent,\n mutations: new Mutations(),\n state: {\n reactives: [],\n },\n });\n\n // The reactiveDebuggers will be used as a way of access the debug instances but also to register every new\n // instance. To ensure this will update the reactive debug state we add the registerNewInstance method to it.\n reactiveDebuggers.registerNewInstance = debug.registerNewInstance.bind(debug);\n\n return {\n debug,\n debuggers: reactiveDebuggers,\n };\n};\n"],"file":"debug.min.js"} \ No newline at end of file diff --git a/lib/amd/build/local/reactive/debugpanel.min.js b/lib/amd/build/local/reactive/debugpanel.min.js new file mode 100644 index 00000000000..9b34561cc80 --- /dev/null +++ b/lib/amd/build/local/reactive/debugpanel.min.js @@ -0,0 +1,2 @@ +define ("core/local/reactive/debugpanel",["exports","core/reactive","core/log","core/utils"],function(a,b,c,d){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.initsubpanel=a.init=void 0;c=function(a){return a&&a.__esModule?a:{default:a}}(c);function e(a,b){return k(a)||j(a,b)||g(a,b)||f()}function f(){throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}function g(a,b){if(!a)return;if("string"==typeof a)return h(a,b);var c=Object.prototype.toString.call(a).slice(8,-1);if("Object"===c&&a.constructor)c=a.constructor.name;if("Map"===c||"Set"===c)return Array.from(c);if("Arguments"===c||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(c))return h(a,b)}function h(a,b){if(null==b||b>a.length)b=a.length;for(var c=0,d=Array(b);c<b;c++){d[c]=a[c]}return d}function j(a,b){if("undefined"==typeof Symbol||!(Symbol.iterator in Object(a)))return;var c=[],d=!0,e=!1,f=void 0;try{for(var g=a[Symbol.iterator](),h;!(d=(h=g.next()).done);d=!0){c.push(h.value);if(b&&c.length===b)break}}catch(a){e=!0;f=a}finally{try{if(!d&&null!=g["return"])g["return"]()}finally{if(e)throw f}}return c}function k(a){if(Array.isArray(a))return a}function l(a){"@babel/helpers - typeof";if("function"==typeof Symbol&&"symbol"==typeof Symbol.iterator){l=function(a){return typeof a}}else{l=function(a){return a&&"function"==typeof Symbol&&a.constructor===Symbol&&a!==Symbol.prototype?"symbol":typeof a}}return l(a)}function m(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 n(a){for(var b=1,c;b<arguments.length;b++){c=null!=arguments[b]?arguments[b]:{};if(b%2){m(Object(c),!0).forEach(function(b){o(a,b,c[b])})}else if(Object.getOwnPropertyDescriptors){Object.defineProperties(a,Object.getOwnPropertyDescriptors(c))}else{m(Object(c)).forEach(function(b){Object.defineProperty(a,b,Object.getOwnPropertyDescriptor(c,b))})}}return a}function o(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 p(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 q(a){return function(){var b=this,c=arguments;return new Promise(function(d,e){var h=a.apply(b,c);function f(a){p(h,d,e,f,g,"next",a)}function g(a){p(h,d,e,f,g,"throw",a)}f(void 0)})}}function r(a,b){if(!(a instanceof b)){throw new TypeError("Cannot call a class as a function")}}function s(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 t(a,b,c){if(b)s(a.prototype,b);if(c)s(a,c);return a}function u(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)v(a,b)}function v(a,b){v=Object.setPrototypeOf||function(a,b){a.__proto__=b;return a};return v(a,b)}function w(a){return function(){var b=A(a),c;if(z()){var d=A(this).constructor;c=Reflect.construct(b,arguments,d)}else{c=b.apply(this,arguments)}return x(this,c)}}function x(a,b){if(b&&("object"===l(b)||"function"==typeof b)){return b}return y(a)}function y(a){if(void 0===a){throw new ReferenceError("this hasn't been initialised - super() hasn't been called")}return a}function z(){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 A(a){A=Object.setPrototypeOf?Object.getPrototypeOf:function(a){return a.__proto__||Object.getPrototypeOf(a)};return A(a)}a.init=function init(a,c){var d=document.getElementById(a);if(b.debug===void 0){d.remove();return}new B({element:d,reactive:b.debug,selectors:c})};a.initsubpanel=function initsubpanel(a,c){var d=document.getElementById(a);if(b.debug===void 0){d.remove();return}new C({element:d,reactive:b.debug,selectors:c})};var B=function(a){u(b,a);var d=w(b);function b(){r(this,b);return d.apply(this,arguments)}t(b,[{key:"create",value:function create(){this.name="GlobalDebugPanel";this.selectors={LOADERS:"[data-for='loaders']",SUBPANEL:"[data-for='subpanel']",LOG:"[data-for='log']"}}},{key:"stateReady",value:function stateReady(a){var b=this;if(0<a.reactives.size){this.getElement(this.selectors.LOADERS).innerHTML=""}a.reactives.forEach(function(a){b._createLoader(a)});this.getElement(this.selectors.SUBPANEL).innerHTML=""}},{key:"_createLoader",value:function _createLoader(a){var b=this,c=this.getElement(this.selectors.LOADERS),d=document.createElement("button");d.innerHTML=a.id;d.dataset.id=a.id;c.appendChild(d);this.addEventListener(d,"click",function(){return b._openPanel(d,a)})}},{key:"_openPanel",value:function(){var a=q(regeneratorRuntime.mark(function a(b,d){var e,f;return regeneratorRuntime.wrap(function(a){while(1){switch(a.prev=a.next){case 0:a.prev=0;e=this.getElement(this.selectors.SUBPANEL);f=n({},d);a.next=5;return this.renderComponent(e,"core/local/reactive/debuginstancepanel",f);case 5:a.next=11;break;case 7:a.prev=7;a.t0=a["catch"](0);c.default.error("Cannot load reactive debug subpanel");throw a.t0;case 11:case"end":return a.stop();}}},a,this,[[0,7]])}));return function _openPanel(){return a.apply(this,arguments)}}()}]);return b}(b.BaseComponent),C=function(a){u(c,a);var f=w(c);function c(){r(this,c);return f.apply(this,arguments)}t(c,[{key:"create",value:function create(){this.name="DebugInstanceSubpanel";this.selectors={NAME:"[data-for='name']",CLOSE:"[data-for='close']",READMODE:"[data-for='readmode']",HIGHLIGHT:"[data-for='highlight']",LOG:"[data-for='log']",STATE:"[data-for='state']",CLEAN:"[data-for='clean']",PIN:"[data-for='pin']",SAVE:"[data-for='save']",INVALID:"[data-for='invalid']"};this.id=this.element.dataset.id;this.controller=M.reactive[this.id];this.draggable=!1;this.relativeDrag=!0;this.strings={savewarning:""}}},{key:"stateReady",value:function stateReady(){var a=this,c,e;this.dragdrop=new b.DragDrop(this);this.addEventListener(this.getElement(this.selectors.CLOSE),"click",this.remove);if(this.controller.highlight){this._toggleButtonText(this.getElement(this.selectors.HIGHLIGHT))}this.addEventListener(this.getElement(this.selectors.HIGHLIGHT),"click",function(){a.controller.highlight=!a.controller.highlight;a._toggleButtonText(a.getElement(a.selectors.HIGHLIGHT))});this.addEventListener(this.getElement(this.selectors.READMODE),"click",this._toggleEditMode);this.addEventListener(this.getElement(this.selectors.CLEAN),"click",this._cleanAreas);this.addEventListener(this.getElement(this.selectors.PIN),"click",this._togglePin);this.getElement(this.selectors.SAVE).disabled=!0;this.addEventListener(this.getElement(this.selectors.STATE),"keyup",(0,d.debounce)(this._checkJSON,500));this.addEventListener(this.getElement(this.selectors.SAVE),"click",this._saveState);this.strings.savewarning=null!==(c=null===(e=this.getElement(this.selectors.INVALID))||void 0===e?void 0:e.innerHTML)&&void 0!==c?c:"";this._refreshState()}},{key:"destroy",value:function destroy(){if(this.dragdrop!==void 0){this.dragdrop.unregister()}}},{key:"getWatchers",value:function getWatchers(){return[{watch:"reactives[".concat(this.id,"].lastChanges:updated"),handler:this._refreshLog},{watch:"reactives[".concat(this.id,"].modified:updated"),handler:this._refreshState},{watch:"reactives[".concat(this.id,"].readOnly:updated"),handler:this._refreshReadOnly}]}},{key:"_refreshLog",value:function _refreshLog(a){var b,c=a.element,d=null!==(b=null===c||void 0===c?void 0:c.lastChanges)&&void 0!==b?b:[],e=d.join("\n"),f=this.getElement(this.selectors.LOG);f.value+="\n\n= Transaction =\n ".concat(e);f.scrollTop=f.scrollHeight}},{key:"_cleanAreas",value:function _cleanAreas(){var a=this.getElement(this.selectors.LOG);a.value="";this._refreshState()}},{key:"_refreshState",value:function _refreshState(){var a=this.getElement(this.selectors.STATE);a.value=JSON.stringify(this.controller.state,null,4)}},{key:"_refreshReadOnly",value:function _refreshReadOnly(){var a=this.getElement(this.selectors.READMODE);if(a.dataset.readonly===void 0){a.dataset.readonly=a.innerHTML}if(this.controller.readOnly){a.innerHTML=a.dataset.readonly}else{a.innerHTML=a.dataset.alt}}},{key:"_toggleEditMode",value:function _toggleEditMode(){this.controller.readOnly=!this.controller.readOnly}},{key:"_checkJSON",value:function _checkJSON(){var a=this.getElement(this.selectors.INVALID),b=this.getElement(this.selectors.SAVE),c=this.getElement(this.selectors.STATE).value,d=this.controller.stateData;if(c==JSON.stringify(this.controller.state,null,4)){a.style.color="";a.innerHTML="";b.disabled=!0;return}try{var e=JSON.parse(c),f=this._generateStateUpdates(d,e);a.style.color="";a.innerHTML=this.strings.savewarning;b.disabled=!1;return f}catch(c){var g;a.style.color="red";a.innerHTML=null!==(g=c.message)&&void 0!==g?g:"Invalid JSON sctructure";b.disabled=!0}}},{key:"_saveState",value:function _saveState(){var a=this._checkJSON();if(!a){return}this.controller.processUpdates(a)}},{key:"_generateStateUpdates",value:function _generateStateUpdates(a,b){for(var c=[],d={},f=function(){var a=e(h[g],2),b=a[0],f=a[1];if(Array.isArray(f)){d[b]={};f.forEach(function(a){if(a.id===void 0){throw Error("Array ".concat(b," element without id attribute"))}c.push({name:b,action:"override",fields:a});var e=(a.id+"").valueOf();d[b][e]=!0})}else{c.push({name:b,action:"override",fields:f})}},g=0,h=Object.entries(b);g<h.length;g++){f()}for(var i=function(){var a=e(k[j],2),f=a[0],g=a[1],h=!1;if(b[f]===void 0){h=!0}if(Array.isArray(g)){if(!h&&d[f]===void 0){throw Error("Array ".concat(f," cannot change to object."))}g.forEach(function(a){var b=(a.id+"").valueOf(),e=h;if(!e&&d[f][b]===void 0){e=!0}if(e){c.push({name:f,action:"delete",fields:a})}})}else{if(!h&&d[f]!==void 0){throw Error("Object ".concat(f," cannot change to array."))}if(h){c.push({name:f,action:"delete",fields:g})}}},j=0,k=Object.entries(a);j<k.length;j++){i()}return c}},{key:"getDraggableData",value:function getDraggableData(){return this.draggable}},{key:"dragEnd",value:function dragEnd(a,b){this.element.style.top="".concat(b.newFixedTop,"px");this.element.style.left="".concat(b.newFixedLeft,"px")}},{key:"_togglePin",value:function _togglePin(){this.draggable=!this.draggable;this.dragdrop.setDraggable(this.draggable);if(this.draggable){this._unpin()}else{this._pin()}}},{key:"_unpin",value:function _unpin(){var a=window.innerHeight/2,b=window.innerWidth/2,c={position:"fixed",resize:"both",overflow:"auto",height:"400px",width:"400px",top:"".concat(a-200,"px"),left:"".concat(b-200,"px")};Object.assign(this.element.style,c);this.getElement(this.selectors.STATE).style.height="50px";this.getElement(this.selectors.LOG).style.height="50px";this._toggleButtonText(this.getElement(this.selectors.PIN))}},{key:"_pin",value:function _pin(){var a=this;["position","resize","overflow","top","left","height","width"].forEach(function(b){return a.element.style.removeProperty(b)});this._toggleButtonText(this.getElement(this.selectors.PIN))}},{key:"_toggleButtonText",value:function _toggleButtonText(a){var b=[a.dataset.alt,a.innerHTML];a.innerHTML=b[0];a.dataset.alt=b[1]}}]);return c}(b.BaseComponent)}); +//# sourceMappingURL=debugpanel.min.js.map diff --git a/lib/amd/build/local/reactive/debugpanel.min.js.map b/lib/amd/build/local/reactive/debugpanel.min.js.map new file mode 100644 index 00000000000..ad4de543e8d --- /dev/null +++ b/lib/amd/build/local/reactive/debugpanel.min.js.map @@ -0,0 +1 @@ +{"version":3,"sources":["../../../src/local/reactive/debugpanel.js"],"names":["init","target","selectors","element","document","getElementById","debug","remove","GlobalDebugPanel","reactive","initsubpanel","DebugInstanceSubpanel","name","LOADERS","SUBPANEL","LOG","state","reactives","size","getElement","innerHTML","forEach","instance","_createLoader","loaders","btn","createElement","id","dataset","appendChild","addEventListener","_openPanel","data","renderComponent","log","error","BaseComponent","NAME","CLOSE","READMODE","HIGHLIGHT","STATE","CLEAN","PIN","SAVE","INVALID","controller","M","draggable","relativeDrag","strings","savewarning","dragdrop","DragDrop","highlight","_toggleButtonText","_toggleEditMode","_cleanAreas","_togglePin","disabled","_checkJSON","_saveState","_refreshState","unregister","watch","handler","_refreshLog","_refreshReadOnly","list","lastChanges","logContent","join","value","scrollTop","scrollHeight","JSON","stringify","readonly","readOnly","alt","invalid","save","edited","currentStateData","stateData","style","color","newState","parse","result","_generateStateUpdates","message","updates","processUpdates","newStateData","ids","key","newValue","Array","isArray","Error","push","action","fields","index","valueOf","Object","entries","oldValue","deleteField","deleteEntry","dropdata","event","top","newFixedTop","left","newFixedLeft","setDraggable","_unpin","_pin","pageCenterY","window","innerHeight","pageCenterX","innerWidth","position","resize","overflow","height","width","assign","prop","removeProperty"],"mappings":"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","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 * Reactive module debug panel.\n *\n * This module contains all the UI components for the reactive debug tools.\n * Those tools are only available if the debug is enables and could be used\n * from the footer.\n *\n * @module core/local/reactive/debugpanel\n * @copyright 2021 Ferran Recio <ferran@moodle.com>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport {BaseComponent, DragDrop, debug} from 'core/reactive';\nimport log from 'core/log';\nimport {debounce} from 'core/utils';\n\n/**\n * Init the main reactive panel.\n *\n * @param {element|string} target the DOM main element or its ID\n * @param {object} selectors optional css selector overrides\n */\nexport const init = (target, selectors) => {\n const element = document.getElementById(target);\n // Check if the debug reactive module is available.\n if (debug === undefined) {\n element.remove();\n return;\n }\n // Create the main component.\n new GlobalDebugPanel({\n element,\n reactive: debug,\n selectors,\n });\n};\n\n/**\n * Init an instance reactive subpanel.\n *\n * @param {element|string} target the DOM main element or its ID\n * @param {object} selectors optional css selector overrides\n */\nexport const initsubpanel = (target, selectors) => {\n const element = document.getElementById(target);\n // Check if the debug reactive module is available.\n if (debug === undefined) {\n element.remove();\n return;\n }\n // Create the main component.\n new DebugInstanceSubpanel({\n element,\n reactive: debug,\n selectors,\n });\n};\n\n/**\n * Component for the main reactive dev panel.\n *\n * This component shows the list of reactive instances and handle the buttons\n * to open a specific instance panel.\n */\nclass GlobalDebugPanel extends BaseComponent {\n\n /**\n * Constructor hook.\n */\n create() {\n // Optional component name for debugging.\n this.name = 'GlobalDebugPanel';\n // Default query selectors.\n this.selectors = {\n LOADERS: `[data-for='loaders']`,\n SUBPANEL: `[data-for='subpanel']`,\n LOG: `[data-for='log']`,\n };\n }\n\n /**\n * Initial state ready method.\n *\n * @param {object} state the initial state\n */\n stateReady(state) {\n if (state.reactives.size > 0) {\n this.getElement(this.selectors.LOADERS).innerHTML = '';\n }\n // Generate loading buttons.\n state.reactives.forEach(\n instance => {\n this._createLoader(instance);\n }\n );\n // Remove loading wheel.\n this.getElement(this.selectors.SUBPANEL).innerHTML = '';\n }\n\n /**\n * Create a debug panel button for a specific reactive instance.\n *\n * @param {object} instance hte instance data\n */\n _createLoader(instance) {\n const loaders = this.getElement(this.selectors.LOADERS);\n const btn = document.createElement(\"button\");\n btn.innerHTML = instance.id;\n btn.dataset.id = instance.id;\n loaders.appendChild(btn);\n // Add click event.\n this.addEventListener(btn, 'click', () => this._openPanel(btn, instance));\n }\n\n /**\n * Open a debug panel.\n *\n * @param {Element} btn the button element\n * @param {object} instance the instance data\n */\n async _openPanel(btn, instance) {\n try {\n const target = this.getElement(this.selectors.SUBPANEL);\n const data = {...instance};\n await this.renderComponent(target, 'core/local/reactive/debuginstancepanel', data);\n } catch (error) {\n log.error('Cannot load reactive debug subpanel');\n throw error;\n }\n }\n}\n\n/**\n * Component for the main reactive dev panel.\n *\n * This component shows the list of reactive instances and handle the buttons\n * to open a specific instance panel.\n */\nclass DebugInstanceSubpanel extends BaseComponent {\n\n /**\n * Constructor hook.\n */\n create() {\n // Optional component name for debugging.\n this.name = 'DebugInstanceSubpanel';\n // Default query selectors.\n this.selectors = {\n NAME: `[data-for='name']`,\n CLOSE: `[data-for='close']`,\n READMODE: `[data-for='readmode']`,\n HIGHLIGHT: `[data-for='highlight']`,\n LOG: `[data-for='log']`,\n STATE: `[data-for='state']`,\n CLEAN: `[data-for='clean']`,\n PIN: `[data-for='pin']`,\n SAVE: `[data-for='save']`,\n INVALID: `[data-for='invalid']`,\n };\n this.id = this.element.dataset.id;\n this.controller = M.reactive[this.id];\n\n // The component is created always pinned.\n this.draggable = false;\n // We want the element to be dragged like modal.\n this.relativeDrag = true;\n // Save warning (will be loaded when state is ready.\n this.strings = {\n savewarning: '',\n };\n }\n\n /**\n * Initial state ready method.\n *\n */\n stateReady() {\n // Enable drag and drop.\n this.dragdrop = new DragDrop(this);\n\n // Close button.\n this.addEventListener(\n this.getElement(this.selectors.CLOSE),\n 'click',\n this.remove\n );\n // Highlight button.\n if (this.controller.highlight) {\n this._toggleButtonText(this.getElement(this.selectors.HIGHLIGHT));\n }\n this.addEventListener(\n this.getElement(this.selectors.HIGHLIGHT),\n 'click',\n () => {\n this.controller.highlight = !this.controller.highlight;\n this._toggleButtonText(this.getElement(this.selectors.HIGHLIGHT));\n }\n );\n // Edit mode button.\n this.addEventListener(\n this.getElement(this.selectors.READMODE),\n 'click',\n this._toggleEditMode\n );\n // Clean log and state.\n this.addEventListener(\n this.getElement(this.selectors.CLEAN),\n 'click',\n this._cleanAreas\n );\n // Unpin panel butotn.\n this.addEventListener(\n this.getElement(this.selectors.PIN),\n 'click',\n this._togglePin\n );\n // Save button, state format error message and state textarea.\n this.getElement(this.selectors.SAVE).disabled = true;\n\n this.addEventListener(\n this.getElement(this.selectors.STATE),\n 'keyup',\n debounce(this._checkJSON, 500)\n );\n\n this.addEventListener(\n this.getElement(this.selectors.SAVE),\n 'click',\n this._saveState\n );\n // Save the default save warning message.\n this.strings.savewarning = this.getElement(this.selectors.INVALID)?.innerHTML ?? '';\n // Add current state.\n this._refreshState();\n }\n\n /**\n * Remove all subcomponents dependencies.\n */\n destroy() {\n if (this.dragdrop !== undefined) {\n this.dragdrop.unregister();\n }\n }\n\n /**\n * Component watchers.\n *\n * @returns {Array} of watchers\n */\n getWatchers() {\n return [\n {watch: `reactives[${this.id}].lastChanges:updated`, handler: this._refreshLog},\n {watch: `reactives[${this.id}].modified:updated`, handler: this._refreshState},\n {watch: `reactives[${this.id}].readOnly:updated`, handler: this._refreshReadOnly},\n ];\n }\n\n /**\n * Wtacher method to refresh the log panel.\n *\n * @param {object} detail of the change\n */\n _refreshLog({element}) {\n const list = element?.lastChanges ?? [];\n\n const logContent = list.join(\"\\n\");\n // Append last log.\n const target = this.getElement(this.selectors.LOG);\n target.value += `\\n\\n= Transaction =\\n ${logContent}`;\n target.scrollTop = target.scrollHeight;\n }\n\n /**\n * Listener method to clean the log area.\n */\n _cleanAreas() {\n let target = this.getElement(this.selectors.LOG);\n target.value = '';\n\n this._refreshState();\n }\n\n /**\n * Watcher to refresh the state information.\n */\n _refreshState() {\n const target = this.getElement(this.selectors.STATE);\n target.value = JSON.stringify(this.controller.state, null, 4);\n }\n\n /**\n * Watcher to update the read only information.\n */\n _refreshReadOnly() {\n // Toggle the read mode button.\n const target = this.getElement(this.selectors.READMODE);\n if (target.dataset.readonly === undefined) {\n target.dataset.readonly = target.innerHTML;\n }\n if (this.controller.readOnly) {\n target.innerHTML = target.dataset.readonly;\n } else {\n target.innerHTML = target.dataset.alt;\n }\n }\n\n /**\n * Listener to toggle the edit mode of the component.\n */\n _toggleEditMode() {\n this.controller.readOnly = !this.controller.readOnly;\n }\n\n /**\n * Check that the edited state JSON is valid.\n *\n * Not all valid JSON are suitable for transforming the state. For example,\n * the first level attributes cannot change the type.\n *\n * @return {undefined|array} Array of state updates.\n */\n _checkJSON() {\n const invalid = this.getElement(this.selectors.INVALID);\n const save = this.getElement(this.selectors.SAVE);\n\n const edited = this.getElement(this.selectors.STATE).value;\n\n const currentStateData = this.controller.stateData;\n\n // Check if the json is tha same as state.\n if (edited == JSON.stringify(this.controller.state, null, 4)) {\n invalid.style.color = '';\n invalid.innerHTML = '';\n save.disabled = true;\n return undefined;\n }\n\n // Check if the json format is valid.\n try {\n const newState = JSON.parse(edited);\n // Check the first level did not change types.\n const result = this._generateStateUpdates(currentStateData, newState);\n // Enable save button.\n invalid.style.color = '';\n invalid.innerHTML = this.strings.savewarning;\n save.disabled = false;\n return result;\n } catch (error) {\n invalid.style.color = 'red';\n invalid.innerHTML = error.message ?? 'Invalid JSON sctructure';\n save.disabled = true;\n return undefined;\n }\n }\n\n /**\n * Listener to save the current edited state into the real state.\n */\n _saveState() {\n const updates = this._checkJSON();\n if (!updates) {\n return;\n }\n // Sent the updates to the state manager.\n this.controller.processUpdates(updates);\n }\n\n /**\n * Check that the edited state JSON is valid.\n *\n * Not all valid JSON are suitable for transforming the state. For example,\n * the first level attributes cannot change the type. This method do a two\n * steps comparison between the current state data and the new state data.\n *\n * A reactive state cannot be overridden like any other variable. To keep\n * the watchers updated is necessary to transform the current state into\n * the new one. As a result, this method generates all the necessary state\n * updates to convert the state into the new state.\n *\n * @param {object} currentStateData\n * @param {object} newStateData\n * @return {array} Array of state updates.\n * @throws {Error} is the structure is not compatible\n */\n _generateStateUpdates(currentStateData, newStateData) {\n\n const updates = [];\n\n const ids = {};\n\n // Step 1: Add all overrides newStateData.\n for (const [key, newValue] of Object.entries(newStateData)) {\n // Check is it is new.\n if (Array.isArray(newValue)) {\n ids[key] = {};\n newValue.forEach(element => {\n if (element.id === undefined) {\n throw Error(`Array ${key} element without id attribute`);\n }\n updates.push({\n name: key,\n action: 'override',\n fields: element,\n });\n const index = String(element.id).valueOf();\n ids[key][index] = true;\n });\n } else {\n updates.push({\n name: key,\n action: 'override',\n fields: newValue,\n });\n }\n }\n // Step 2: delete unnecesary data from currentStateData.\n for (const [key, oldValue] of Object.entries(currentStateData)) {\n let deleteField = false;\n // Check if the attribute is still there.\n if (newStateData[key] === undefined) {\n deleteField = true;\n }\n if (Array.isArray(oldValue)) {\n if (!deleteField && ids[key] === undefined) {\n throw Error(`Array ${key} cannot change to object.`);\n }\n oldValue.forEach(element => {\n const index = String(element.id).valueOf();\n let deleteEntry = deleteField;\n // Check if the id is there.\n if (!deleteEntry && ids[key][index] === undefined) {\n deleteEntry = true;\n }\n if (deleteEntry) {\n updates.push({\n name: key,\n action: 'delete',\n fields: element,\n });\n }\n });\n } else {\n if (!deleteField && ids[key] !== undefined) {\n throw Error(`Object ${key} cannot change to array.`);\n }\n if (deleteField) {\n updates.push({\n name: key,\n action: 'delete',\n fields: oldValue,\n });\n }\n }\n }\n // Delete all elements without action.\n return updates;\n }\n\n // Drag and drop methods.\n\n /**\n * Get the draggable data of this component.\n *\n * @returns {Object} exported course module drop data\n */\n getDraggableData() {\n return this.draggable;\n }\n\n /**\n * The element drop end hook.\n *\n * @param {Object} dropdata the dropdata\n * @param {Event} event the dropdata\n */\n dragEnd(dropdata, event) {\n this.element.style.top = `${event.newFixedTop}px`;\n this.element.style.left = `${event.newFixedLeft}px`;\n }\n\n /**\n * Pin and unpin the panel.\n */\n _togglePin() {\n this.draggable = !this.draggable;\n this.dragdrop.setDraggable(this.draggable);\n if (this.draggable) {\n this._unpin();\n } else {\n this._pin();\n }\n }\n\n /**\n * Unpin the panel form the footer.\n */\n _unpin() {\n // Find the initial spot.\n const pageCenterY = window.innerHeight / 2;\n const pageCenterX = window.innerWidth / 2;\n // Put the element in the middle of the screen\n const style = {\n position: 'fixed',\n resize: 'both',\n overflow: 'auto',\n height: '400px',\n width: '400px',\n top: `${pageCenterY - 200}px`,\n left: `${pageCenterX - 200}px`,\n };\n Object.assign(this.element.style, style);\n // Small also the text areas.\n this.getElement(this.selectors.STATE).style.height = '50px';\n this.getElement(this.selectors.LOG).style.height = '50px';\n\n this._toggleButtonText(this.getElement(this.selectors.PIN));\n }\n\n /**\n * Pin the panel into the footer.\n */\n _pin() {\n const props = [\n 'position',\n 'resize',\n 'overflow',\n 'top',\n 'left',\n 'height',\n 'width',\n ];\n props.forEach(\n prop => this.element.style.removeProperty(prop)\n );\n this._toggleButtonText(this.getElement(this.selectors.PIN));\n }\n\n /**\n * Toogle the button text with the data-alt value.\n *\n * @param {Element} element the button element\n */\n _toggleButtonText(element) {\n [element.innerHTML, element.dataset.alt] = [element.dataset.alt, element.innerHTML];\n }\n\n}\n"],"file":"debugpanel.min.js"} \ No newline at end of file diff --git a/lib/amd/build/local/reactive/reactive.min.js b/lib/amd/build/local/reactive/reactive.min.js index 03886840701..1d8b80b51e2 100644 --- a/lib/amd/build/local/reactive/reactive.min.js +++ b/lib/amd/build/local/reactive/reactive.min.js @@ -1,2 +1,2 @@ -define ("core/local/reactive/reactive",["exports","core/log","core/local/reactive/statemanager","core/pending"],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,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){return n(a)||m(a,b)||k(a,b)||j()}function j(){throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}function k(a,b){if(!a)return;if("string"==typeof a)return l(a,b);var c=Object.prototype.toString.call(a).slice(8,-1);if("Object"===c&&a.constructor)c=a.constructor.name;if("Map"===c||"Set"===c)return Array.from(c);if("Arguments"===c||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(c))return l(a,b)}function l(a,b){if(null==b||b>a.length)b=a.length;for(var c=0,d=Array(b);c<b;c++){d[c]=a[c]}return d}function m(a,b){if("undefined"==typeof Symbol||!(Symbol.iterator in Object(a)))return;var c=[],d=!0,e=!1,f=void 0;try{for(var g=a[Symbol.iterator](),h;!(d=(h=g.next()).done);d=!0){c.push(h.value);if(b&&c.length===b)break}}catch(a){e=!0;f=a}finally{try{if(!d&&null!=g["return"])g["return"]()}finally{if(e)throw f}}return c}function n(a){if(Array.isArray(a))return a}function o(a,b){if(!(a instanceof b)){throw new TypeError("Cannot call a class as a function")}}function p(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 q(a,b,c){if(b)p(a.prototype,b);if(c)p(a,c);return a}var r=0,s=function(){function a(b){var e,f;o(this,a);if(b.eventName===void 0||b.eventDispatch===void 0){throw new Error("Reactivity event required")}if(b.name!==void 0){this.name=b.name}this.target=null!==(e=b.target)&&void 0!==e?e:document.createTextNode(null);this.eventName=b.eventName;this.eventDispatch=b.eventDispatch;this.stateManager=new c.default(this.eventDispatch,this.target);this.watchers=new Map([]);this.components=new Set([]);this.mutations=null!==(f=b.mutations)&&void 0!==f?f:{};this.target.addEventListener(this.eventName,this.callWatchersHandler.bind(this));this.pendingState=new d.default("core/reactive:registerInstance".concat(r++));if(b.state!==void 0){this.setInitialState(b.state)}}q(a,[{key:"callWatchersHandler",value:function callWatchersHandler(a){this.target.dispatchEvent(new CustomEvent(a.detail.action,{bubbles:!1,detail:a.detail}))}},{key:"setInitialState",value:function setInitialState(a){this.pendingState.resolve();this.stateManager.setInitialState(a)}},{key:"addMutations",value:function addMutations(a){if(a.init!==void 0){a.init(this.stateManager)}for(var b=0,c=Object.entries(a);b<c.length;b++){var d=h(c[b],2),e=d[0],f=d[1];this.mutations[e]=f.bind(a)}}},{key:"setMutations",value:function setMutations(a){this.mutations=a;if(a.init!==void 0){a.init(this.stateManager)}}},{key:"getInitialStatePromise",value:function getInitialStatePromise(){return this.stateManager.getInitialPromise()}},{key:"registerComponent",value:function registerComponent(a){var c,e=this,f=null!==(c=a.name)&&void 0!==c?c:"Unkown component",g=function(){},h=g;if(a.dispatchRegistrationSuccess!==void 0){g=a.dispatchRegistrationSuccess.bind(a)}if(a.dispatchRegistrationFail!==void 0){h=a.dispatchRegistrationFail.bind(a)}if(this.components.has(a)){g();return a}var i=new d.default("core/reactive:registerComponent".concat(r++)),j=[],k=[];if(a.getWatchers!==void 0){k=a.getWatchers()}k.forEach(function(b){var c=b.watch,d=b.handler;if(c===void 0){h();throw new Error("Missing watch attribute in ".concat(f," watcher"))}if(d===void 0){h();throw new Error("Missing handler for watcher ".concat(c," in ").concat(f))}var g=function(b){d.apply(a,[b.detail])};j.push({target:e.target,watch:c,listener:g});e.target.addEventListener(c,g)});if(a.stateReady!==void 0){this.getInitialStatePromise().then(function(b){a.stateReady(b);i.resolve();return!0}).catch(function(a){i.resolve();b.default.error("Initial state in ".concat(f," rejected due to: ").concat(a));b.default.error(a)})}this.watchers.set(a,j);this.components.add(a);g();return a}},{key:"unregisterComponent",value:function unregisterComponent(a){if(!this.components.has(a)){return a}this.components.delete(a);var b=this.watchers.get(a);if(b===void 0){return a}b.forEach(function(a){var b=a.target,c=a.watch,d=a.listener;b.removeEventListener(c,d)});this.watchers.delete(a);return a}},{key:"dispatch",value:function(){var a=g(regeneratorRuntime.mark(function a(b){var c,e,f,g,h,i=arguments;return regeneratorRuntime.wrap(function(a){while(1){switch(a.prev=a.next){case 0:if(!("string"!=typeof b)){a.next=2;break}throw new Error("Dispatch action name must be a string");case 2:if(!("_"===b.charAt(0))){a.next=4;break}throw new Error("Illegal Private ".concat(b," mutation method dispatch"));case 4:if(!(this.mutations[b]===void 0)){a.next=6;break}throw new Error("Unkown ".concat(b," mutation"));case 6:c=new d.default("core/reactive:".concat(b).concat(r++));e=this.mutations[b];a.prev=8;for(f=i.length,g=Array(1<f?f-1:0),h=1;h<f;h++){g[h-1]=i[h]}a.next=12;return e.apply(this.mutations,[this.stateManager].concat(g));case 12:c.resolve();a.next=20;break;case 15:a.prev=15;a.t0=a["catch"](8);this.stateManager.setReadOnly(!0);c.resolve();throw a.t0;case 20:case"end":return a.stop();}}},a,this,[[8,15]])}));return function dispatch(){return a.apply(this,arguments)}}()},{key:"state",get:function get(){return this.stateManager.state}}]);return a}();a.default=s;return a.default}); +define ("core/local/reactive/reactive",["exports","core/log","core/local/reactive/statemanager","core/pending"],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,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){return n(a)||m(a,b)||k(a,b)||j()}function j(){throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}function k(a,b){if(!a)return;if("string"==typeof a)return l(a,b);var c=Object.prototype.toString.call(a).slice(8,-1);if("Object"===c&&a.constructor)c=a.constructor.name;if("Map"===c||"Set"===c)return Array.from(c);if("Arguments"===c||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(c))return l(a,b)}function l(a,b){if(null==b||b>a.length)b=a.length;for(var c=0,d=Array(b);c<b;c++){d[c]=a[c]}return d}function m(a,b){if("undefined"==typeof Symbol||!(Symbol.iterator in Object(a)))return;var c=[],d=!0,e=!1,f=void 0;try{for(var g=a[Symbol.iterator](),h;!(d=(h=g.next()).done);d=!0){c.push(h.value);if(b&&c.length===b)break}}catch(a){e=!0;f=a}finally{try{if(!d&&null!=g["return"])g["return"]()}finally{if(e)throw f}}return c}function n(a){if(Array.isArray(a))return a}function o(a,b){if(!(a instanceof b)){throw new TypeError("Cannot call a class as a function")}}function p(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 q(a,b,c){if(b)p(a.prototype,b);if(c)p(a,c);return a}var r=0,s=function(){function a(b){var e,f;o(this,a);if(b.eventName===void 0||b.eventDispatch===void 0){throw new Error("Reactivity event required")}if(b.name!==void 0){this.name=b.name}this.target=null!==(e=b.target)&&void 0!==e?e:document.createTextNode(null);this.eventName=b.eventName;this.eventDispatch=b.eventDispatch;this.stateManager=new c.default(this.eventDispatch,this.target);this.watchers=new Map([]);this.components=new Set([]);this.mutations=null!==(f=b.mutations)&&void 0!==f?f:{};this.target.addEventListener(this.eventName,this.callWatchersHandler.bind(this));this.pendingState=new d.default("core/reactive:registerInstance".concat(r++));if(b.state!==void 0){this.setInitialState(b.state)}if(M.reactive!==void 0){M.reactive.registerNewInstance(this)}}q(a,[{key:"callWatchersHandler",value:function callWatchersHandler(a){this.target.dispatchEvent(new CustomEvent(a.detail.action,{bubbles:!1,detail:a.detail}))}},{key:"setInitialState",value:function setInitialState(a){this.pendingState.resolve();this.stateManager.setInitialState(a)}},{key:"addMutations",value:function addMutations(a){if(a.init!==void 0){a.init(this.stateManager)}for(var b=0,c=Object.entries(a);b<c.length;b++){var d=h(c[b],2),e=d[0],f=d[1];this.mutations[e]=f.bind(a)}}},{key:"setMutations",value:function setMutations(a){this.mutations=a;if(a.init!==void 0){a.init(this.stateManager)}}},{key:"getInitialStatePromise",value:function getInitialStatePromise(){return this.stateManager.getInitialPromise()}},{key:"registerComponent",value:function registerComponent(a){var c,e=this,f=null!==(c=a.name)&&void 0!==c?c:"Unkown component",g=function(){},h=g;if(a.dispatchRegistrationSuccess!==void 0){g=a.dispatchRegistrationSuccess.bind(a)}if(a.dispatchRegistrationFail!==void 0){h=a.dispatchRegistrationFail.bind(a)}if(this.components.has(a)){g();return a}var i=new d.default("core/reactive:registerComponent".concat(r++)),j=[],k=[];if(a.getWatchers!==void 0){k=a.getWatchers()}k.forEach(function(b){var c=b.watch,d=b.handler;if(c===void 0){h();throw new Error("Missing watch attribute in ".concat(f," watcher"))}if(d===void 0){h();throw new Error("Missing handler for watcher ".concat(c," in ").concat(f))}var g=function(b){d.apply(a,[b.detail])};j.push({target:e.target,watch:c,listener:g});e.target.addEventListener(c,g)});if(a.stateReady!==void 0){this.getInitialStatePromise().then(function(b){a.stateReady(b);i.resolve();return!0}).catch(function(a){i.resolve();b.default.error("Initial state in ".concat(f," rejected due to: ").concat(a));b.default.error(a)})}this.watchers.set(a,j);this.components.add(a);this.target.dispatchEvent(new CustomEvent("registerComponent:success",{bubbles:!1,detail:{component:a}}));g();return a}},{key:"unregisterComponent",value:function unregisterComponent(a){if(!this.components.has(a)){return a}this.components.delete(a);var b=this.watchers.get(a);if(b===void 0){return a}b.forEach(function(a){var b=a.target,c=a.watch,d=a.listener;b.removeEventListener(c,d)});this.watchers.delete(a);return a}},{key:"dispatch",value:function(){var a=g(regeneratorRuntime.mark(function a(b){var c,e,f,g,h,i=arguments;return regeneratorRuntime.wrap(function(a){while(1){switch(a.prev=a.next){case 0:if(!("string"!=typeof b)){a.next=2;break}throw new Error("Dispatch action name must be a string");case 2:if(!("_"===b.charAt(0))){a.next=4;break}throw new Error("Illegal Private ".concat(b," mutation method dispatch"));case 4:if(!(this.mutations[b]===void 0)){a.next=6;break}throw new Error("Unkown ".concat(b," mutation"));case 6:c=new d.default("core/reactive:".concat(b).concat(r++));e=this.mutations[b];a.prev=8;for(f=i.length,g=Array(1<f?f-1:0),h=1;h<f;h++){g[h-1]=i[h]}a.next=12;return e.apply(this.mutations,[this.stateManager].concat(g));case 12:c.resolve();a.next=20;break;case 15:a.prev=15;a.t0=a["catch"](8);this.stateManager.setReadOnly(!0);c.resolve();throw a.t0;case 20:case"end":return a.stop();}}},a,this,[[8,15]])}));return function dispatch(){return a.apply(this,arguments)}}()},{key:"state",get:function get(){return this.stateManager.state}}]);return a}();a.default=s;return a.default}); //# sourceMappingURL=reactive.min.js.map diff --git a/lib/amd/build/local/reactive/reactive.min.js.map b/lib/amd/build/local/reactive/reactive.min.js.map index 78a5702f06f..e0fd086cd3a 100644 --- a/lib/amd/build/local/reactive/reactive.min.js.map +++ b/lib/amd/build/local/reactive/reactive.min.js.map @@ -1 +1 @@ -{"version":3,"sources":["../../../src/local/reactive/reactive.js"],"names":["pendingCount","description","eventName","eventDispatch","Error","name","target","document","createTextNode","stateManager","StateManager","watchers","Map","components","Set","mutations","addEventListener","callWatchersHandler","bind","pendingState","Pending","state","setInitialState","event","dispatchEvent","CustomEvent","detail","action","bubbles","stateData","resolve","newFunctions","init","Object","entries","mutation","mutationFunction","manager","getInitialPromise","component","componentName","dispatchSuccess","dispatchFail","dispatchRegistrationSuccess","dispatchRegistrationFail","has","pendingPromise","listeners","handlers","getWatchers","forEach","watch","handler","listener","apply","push","stateReady","getInitialStatePromise","then","catch","reason","log","error","set","add","delete","get","removeEventListener","actionName","charAt","params","setReadOnly"],"mappings":"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","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 * A generic single state reactive module.\n *\n * @module core/reactive/local/reactive/reactive\n * @class core/reactive/local/reactive/reactive\n * @copyright 2021 Ferran Recio <ferran@moodle.com>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport log from 'core/log';\nimport StateManager from 'core/local/reactive/statemanager';\nimport Pending from 'core/pending';\n\n// Count the number of pending operations done to ensure we have a unique id for each one.\nlet pendingCount = 0;\n\n/**\n * Set up general reactive class to create a single state application with components.\n *\n * The reactive class is used for registering new UI components and manage the access to the state values\n * and mutations.\n *\n * When a new reactive instance is created, it will contain an empty state and and empty mutations\n * lists. When the state data is ready, the initial state can be loaded using the \"setInitialState\"\n * method. This will protect the state from writing and will trigger all the components \"stateReady\"\n * methods.\n *\n * State can only be altered by mutations. To replace all the mutations with a specific class,\n * use \"setMutations\" method. If you need to just add some new mutation methods, use \"addMutations\".\n *\n * To register new components into a reactive instance, use \"registerComponent\".\n *\n * Inside a component, use \"dispatch\" to invoke a mutation on the state (components can only access\n * the state in read only mode).\n */\nexport default class {\n\n /**\n * The component descriptor data structure.\n *\n * @typedef {object} description\n * @property {string} eventName the custom event name used for state changed events\n * @property {Function} eventDispatch the state update event dispatch function\n * @property {Element} [target] the target of the event dispatch. If not passed a fake element will be created\n * @property {Object} [mutations] an object with state mutations functions\n * @property {Object} [state] an object to initialize the state.\n */\n\n /**\n * Create a basic reactive manager.\n *\n * Note that if your state is not async loaded, you can pass directly on creation by using the\n * description.state attribute. However, this will initialize the state, this means\n * setInitialState will throw an exception because the state is already defined.\n *\n * @param {description} description reactive manager description.\n */\n constructor(description) {\n\n if (description.eventName === undefined || description.eventDispatch === undefined) {\n throw new Error(`Reactivity event required`);\n }\n\n if (description.name !== undefined) {\n this.name = description.name;\n }\n\n // Each reactive instance has its own element anchor to propagate state changes internally.\n // By default the module will create a fake DOM element to target custom events but\n // if all reactive components is constrait to a single element, this can be passed as\n // target in the description.\n this.target = description.target ?? document.createTextNode(null);\n\n this.eventName = description.eventName;\n this.eventDispatch = description.eventDispatch;\n\n // State manager is responsible for dispatch state change events when a mutation happens.\n this.stateManager = new StateManager(this.eventDispatch, this.target);\n\n // An internal registry of watchers and components.\n this.watchers = new Map([]);\n this.components = new Set([]);\n\n // Mutations can be overridden later using setMutations method.\n this.mutations = description.mutations ?? {};\n\n // Register the event to alert watchers when specific state change happens.\n this.target.addEventListener(this.eventName, this.callWatchersHandler.bind(this));\n\n // Add a pending operation waiting for the initial state.\n this.pendingState = new Pending(`core/reactive:registerInstance${pendingCount++}`);\n\n // Set initial state if we already have it.\n if (description.state !== undefined) {\n this.setInitialState(description.state);\n }\n }\n\n /**\n * State changed listener.\n *\n * This function take any state change and send it to the proper watchers.\n *\n * To prevent internal state changes from colliding with other reactive instances, only the\n * general \"state changed\" is triggered at document level. All the internal changes are\n * triggered at private target level without bubbling. This way any reactive instance can alert\n * only its own watchers.\n *\n * @param {CustomEvent} event\n */\n callWatchersHandler(event) {\n // Execute any registered component watchers.\n this.target.dispatchEvent(new CustomEvent(event.detail.action, {\n bubbles: false,\n detail: event.detail,\n }));\n }\n\n /**\n * Set the initial state.\n *\n * @param {object} stateData the initial state data.\n */\n setInitialState(stateData) {\n this.pendingState.resolve();\n this.stateManager.setInitialState(stateData);\n }\n\n /**\n * Add individual functions to the mutations.\n *\n * Note new mutations will be added to the existing ones. To replace the full mutation\n * object with a new one, use setMutations method.\n *\n * @method addMutations\n * @param {Object} newFunctions an object with new mutation functions.\n */\n addMutations(newFunctions) {\n // Mutations can provide an init method to do some setup in the statemanager.\n if (newFunctions.init !== undefined) {\n newFunctions.init(this.stateManager);\n }\n // Save all mutations.\n for (const [mutation, mutationFunction] of Object.entries(newFunctions)) {\n this.mutations[mutation] = mutationFunction.bind(newFunctions);\n }\n }\n\n /**\n * Replace the current mutations with a new object.\n *\n * This method is designed to override the full mutations class, for example by extending\n * the original one. To add some individual mutations, use addMutations instead.\n *\n * @param {object} manager the new mutations intance\n */\n setMutations(manager) {\n this.mutations = manager;\n // Mutations can provide an init method to do some setup in the statemanager.\n if (manager.init !== undefined) {\n manager.init(this.stateManager);\n }\n }\n\n /**\n * Return the current state.\n *\n * @return {object}\n */\n get state() {\n return this.stateManager.state;\n }\n\n /**\n * Return the initial state promise.\n *\n * Typically, components do not require to use this promise because registerComponent\n * will trigger their stateReady method automatically. But it could be useful for complex\n * components that require to combine state, template and string loadings.\n *\n * @method getState\n * @return {Promise}\n */\n getInitialStatePromise() {\n return this.stateManager.getInitialPromise();\n }\n\n /**\n * Register a new component.\n *\n * Component can provide some optional functions to the reactive module:\n * - getWatchers: returns an array of watchers\n * - stateReady: a method to call when the initial state is loaded\n *\n * It can also provide some optional attributes:\n * - name: the component name (default value: \"Unkown component\") to customize debug messages.\n *\n * The method will also use dispatchRegistrationSuccess and dispatchRegistrationFail. Those\n * are BaseComponent methods to inform parent components of the registration status.\n * Components should not override those methods.\n *\n * @method registerComponent\n * @param {object} component the new component\n * @property {string} [component.name] the component name to display in warnings and errors.\n * @property {Function} [component.dispatchRegistrationSuccess] method to notify registration success\n * @property {Function} [component.dispatchRegistrationFail] method to notify registration fail\n * @property {Function} [component.getWatchers] getter of the component watchers\n * @property {Function} [component.stateReady] method to call when the state is ready\n * @return {object} the registered component\n */\n registerComponent(component) {\n\n // Component name is an optional attribute to customize debug messages.\n const componentName = component.name ?? 'Unkown component';\n\n // Components can provide special methods to communicate registration to parent components.\n let dispatchSuccess = () => {\n return;\n };\n let dispatchFail = dispatchSuccess;\n if (component.dispatchRegistrationSuccess !== undefined) {\n dispatchSuccess = component.dispatchRegistrationSuccess.bind(component);\n }\n if (component.dispatchRegistrationFail !== undefined) {\n dispatchFail = component.dispatchRegistrationFail.bind(component);\n }\n\n // Components can be registered only one time.\n if (this.components.has(component)) {\n dispatchSuccess();\n return component;\n }\n\n // Components are fully registered only when the state ready promise is resolved.\n const pendingPromise = new Pending(`core/reactive:registerComponent${pendingCount++}`);\n\n // Keep track of the event listeners.\n let listeners = [];\n\n // Register watchers.\n let handlers = [];\n if (component.getWatchers !== undefined) {\n handlers = component.getWatchers();\n }\n handlers.forEach(({watch, handler}) => {\n\n if (watch === undefined) {\n dispatchFail();\n throw new Error(`Missing watch attribute in ${componentName} watcher`);\n }\n if (handler === undefined) {\n dispatchFail();\n throw new Error(`Missing handler for watcher ${watch} in ${componentName}`);\n }\n\n const listener = (event) => {\n handler.apply(component, [event.detail]);\n };\n\n // Save the listener information in case the component must be unregistered later.\n listeners.push({target: this.target, watch, listener});\n\n // The state manager triggers a general \"state changed\" event at a document level. However,\n // for the internal watchers, each component can listen to specific state changed custom events\n // in the target element. This way we can use the native event loop without colliding with other\n // reactive instances.\n this.target.addEventListener(watch, listener);\n });\n\n // Register state ready function. There's the possibility a component is registered after the initial state\n // is loaded. For those cases we have a state promise to handle this specific state change.\n if (component.stateReady !== undefined) {\n this.getInitialStatePromise()\n .then(state => {\n component.stateReady(state);\n pendingPromise.resolve();\n return true;\n })\n .catch(reason => {\n pendingPromise.resolve();\n log.error(`Initial state in ${componentName} rejected due to: ${reason}`);\n log.error(reason);\n });\n }\n\n // Save unregister data.\n this.watchers.set(component, listeners);\n this.components.add(component);\n\n dispatchSuccess();\n return component;\n }\n\n /**\n * Unregister a component and its watchers.\n *\n * @param {object} component the object instance to unregister\n * @returns {object} the deleted component\n */\n unregisterComponent(component) {\n if (!this.components.has(component)) {\n return component;\n }\n\n this.components.delete(component);\n\n // Remove event listeners.\n const listeners = this.watchers.get(component);\n if (listeners === undefined) {\n return component;\n }\n\n listeners.forEach(({target, watch, listener}) => {\n target.removeEventListener(watch, listener);\n });\n\n this.watchers.delete(component);\n\n return component;\n }\n\n /**\n * Dispatch a change in the state.\n *\n * This method is the only way for components to alter the state. Watchers will receive a\n * read only state to prevent illegal changes. If some user action require a state change, the\n * component should dispatch a mutation to trigger all the necessary logic to alter the state.\n *\n * @method dispatch\n * @param {string} actionName the action name (usually the mutation name)\n * @param {*} param any number of params the mutation needs.\n */\n async dispatch(actionName, ...params) {\n if (typeof actionName !== 'string') {\n throw new Error(`Dispatch action name must be a string`);\n }\n // JS does not have private methods yet. However, we prevent any component from calling\n // a method starting with \"_\" because the most accepted convention for private methods.\n if (actionName.charAt(0) === '_') {\n throw new Error(`Illegal Private ${actionName} mutation method dispatch`);\n }\n if (this.mutations[actionName] === undefined) {\n throw new Error(`Unkown ${actionName} mutation`);\n }\n\n const pendingPromise = new Pending(`core/reactive:${actionName}${pendingCount++}`);\n\n const mutationFunction = this.mutations[actionName];\n try {\n await mutationFunction.apply(this.mutations, [this.stateManager, ...params]);\n pendingPromise.resolve();\n } catch (error) {\n // Ensure the state is locked.\n this.stateManager.setReadOnly(true);\n pendingPromise.resolve();\n throw error;\n }\n }\n}\n"],"file":"reactive.min.js"} \ No newline at end of file +{"version":3,"sources":["../../../src/local/reactive/reactive.js"],"names":["pendingCount","description","eventName","eventDispatch","Error","name","target","document","createTextNode","stateManager","StateManager","watchers","Map","components","Set","mutations","addEventListener","callWatchersHandler","bind","pendingState","Pending","state","setInitialState","M","reactive","registerNewInstance","event","dispatchEvent","CustomEvent","detail","action","bubbles","stateData","resolve","newFunctions","init","Object","entries","mutation","mutationFunction","manager","getInitialPromise","component","componentName","dispatchSuccess","dispatchFail","dispatchRegistrationSuccess","dispatchRegistrationFail","has","pendingPromise","listeners","handlers","getWatchers","forEach","watch","handler","listener","apply","push","stateReady","getInitialStatePromise","then","catch","reason","log","error","set","add","delete","get","removeEventListener","actionName","charAt","params","setReadOnly"],"mappings":"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","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 * A generic single state reactive module.\n *\n * @module core/reactive/local/reactive/reactive\n * @class core/reactive/local/reactive/reactive\n * @copyright 2021 Ferran Recio <ferran@moodle.com>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport log from 'core/log';\nimport StateManager from 'core/local/reactive/statemanager';\nimport Pending from 'core/pending';\n\n// Count the number of pending operations done to ensure we have a unique id for each one.\nlet pendingCount = 0;\n\n/**\n * Set up general reactive class to create a single state application with components.\n *\n * The reactive class is used for registering new UI components and manage the access to the state values\n * and mutations.\n *\n * When a new reactive instance is created, it will contain an empty state and and empty mutations\n * lists. When the state data is ready, the initial state can be loaded using the \"setInitialState\"\n * method. This will protect the state from writing and will trigger all the components \"stateReady\"\n * methods.\n *\n * State can only be altered by mutations. To replace all the mutations with a specific class,\n * use \"setMutations\" method. If you need to just add some new mutation methods, use \"addMutations\".\n *\n * To register new components into a reactive instance, use \"registerComponent\".\n *\n * Inside a component, use \"dispatch\" to invoke a mutation on the state (components can only access\n * the state in read only mode).\n */\nexport default class {\n\n /**\n * The component descriptor data structure.\n *\n * @typedef {object} description\n * @property {string} eventName the custom event name used for state changed events\n * @property {Function} eventDispatch the state update event dispatch function\n * @property {Element} [target] the target of the event dispatch. If not passed a fake element will be created\n * @property {Object} [mutations] an object with state mutations functions\n * @property {Object} [state] an object to initialize the state.\n */\n\n /**\n * Create a basic reactive manager.\n *\n * Note that if your state is not async loaded, you can pass directly on creation by using the\n * description.state attribute. However, this will initialize the state, this means\n * setInitialState will throw an exception because the state is already defined.\n *\n * @param {description} description reactive manager description.\n */\n constructor(description) {\n\n if (description.eventName === undefined || description.eventDispatch === undefined) {\n throw new Error(`Reactivity event required`);\n }\n\n if (description.name !== undefined) {\n this.name = description.name;\n }\n\n // Each reactive instance has its own element anchor to propagate state changes internally.\n // By default the module will create a fake DOM element to target custom events but\n // if all reactive components is constrait to a single element, this can be passed as\n // target in the description.\n this.target = description.target ?? document.createTextNode(null);\n\n this.eventName = description.eventName;\n this.eventDispatch = description.eventDispatch;\n\n // State manager is responsible for dispatch state change events when a mutation happens.\n this.stateManager = new StateManager(this.eventDispatch, this.target);\n\n // An internal registry of watchers and components.\n this.watchers = new Map([]);\n this.components = new Set([]);\n\n // Mutations can be overridden later using setMutations method.\n this.mutations = description.mutations ?? {};\n\n // Register the event to alert watchers when specific state change happens.\n this.target.addEventListener(this.eventName, this.callWatchersHandler.bind(this));\n\n // Add a pending operation waiting for the initial state.\n this.pendingState = new Pending(`core/reactive:registerInstance${pendingCount++}`);\n\n // Set initial state if we already have it.\n if (description.state !== undefined) {\n this.setInitialState(description.state);\n }\n\n // Check if we have a debug instance to register the instance.\n if (M.reactive !== undefined) {\n M.reactive.registerNewInstance(this);\n }\n }\n\n /**\n * State changed listener.\n *\n * This function take any state change and send it to the proper watchers.\n *\n * To prevent internal state changes from colliding with other reactive instances, only the\n * general \"state changed\" is triggered at document level. All the internal changes are\n * triggered at private target level without bubbling. This way any reactive instance can alert\n * only its own watchers.\n *\n * @param {CustomEvent} event\n */\n callWatchersHandler(event) {\n // Execute any registered component watchers.\n this.target.dispatchEvent(new CustomEvent(event.detail.action, {\n bubbles: false,\n detail: event.detail,\n }));\n }\n\n /**\n * Set the initial state.\n *\n * @param {object} stateData the initial state data.\n */\n setInitialState(stateData) {\n this.pendingState.resolve();\n this.stateManager.setInitialState(stateData);\n }\n\n /**\n * Add individual functions to the mutations.\n *\n * Note new mutations will be added to the existing ones. To replace the full mutation\n * object with a new one, use setMutations method.\n *\n * @method addMutations\n * @param {Object} newFunctions an object with new mutation functions.\n */\n addMutations(newFunctions) {\n // Mutations can provide an init method to do some setup in the statemanager.\n if (newFunctions.init !== undefined) {\n newFunctions.init(this.stateManager);\n }\n // Save all mutations.\n for (const [mutation, mutationFunction] of Object.entries(newFunctions)) {\n this.mutations[mutation] = mutationFunction.bind(newFunctions);\n }\n }\n\n /**\n * Replace the current mutations with a new object.\n *\n * This method is designed to override the full mutations class, for example by extending\n * the original one. To add some individual mutations, use addMutations instead.\n *\n * @param {object} manager the new mutations intance\n */\n setMutations(manager) {\n this.mutations = manager;\n // Mutations can provide an init method to do some setup in the statemanager.\n if (manager.init !== undefined) {\n manager.init(this.stateManager);\n }\n }\n\n /**\n * Return the current state.\n *\n * @return {object}\n */\n get state() {\n return this.stateManager.state;\n }\n\n /**\n * Return the initial state promise.\n *\n * Typically, components do not require to use this promise because registerComponent\n * will trigger their stateReady method automatically. But it could be useful for complex\n * components that require to combine state, template and string loadings.\n *\n * @method getState\n * @return {Promise}\n */\n getInitialStatePromise() {\n return this.stateManager.getInitialPromise();\n }\n\n /**\n * Register a new component.\n *\n * Component can provide some optional functions to the reactive module:\n * - getWatchers: returns an array of watchers\n * - stateReady: a method to call when the initial state is loaded\n *\n * It can also provide some optional attributes:\n * - name: the component name (default value: \"Unkown component\") to customize debug messages.\n *\n * The method will also use dispatchRegistrationSuccess and dispatchRegistrationFail. Those\n * are BaseComponent methods to inform parent components of the registration status.\n * Components should not override those methods.\n *\n * @method registerComponent\n * @param {object} component the new component\n * @property {string} [component.name] the component name to display in warnings and errors.\n * @property {Function} [component.dispatchRegistrationSuccess] method to notify registration success\n * @property {Function} [component.dispatchRegistrationFail] method to notify registration fail\n * @property {Function} [component.getWatchers] getter of the component watchers\n * @property {Function} [component.stateReady] method to call when the state is ready\n * @return {object} the registered component\n */\n registerComponent(component) {\n\n // Component name is an optional attribute to customize debug messages.\n const componentName = component.name ?? 'Unkown component';\n\n // Components can provide special methods to communicate registration to parent components.\n let dispatchSuccess = () => {\n return;\n };\n let dispatchFail = dispatchSuccess;\n if (component.dispatchRegistrationSuccess !== undefined) {\n dispatchSuccess = component.dispatchRegistrationSuccess.bind(component);\n }\n if (component.dispatchRegistrationFail !== undefined) {\n dispatchFail = component.dispatchRegistrationFail.bind(component);\n }\n\n // Components can be registered only one time.\n if (this.components.has(component)) {\n dispatchSuccess();\n return component;\n }\n\n // Components are fully registered only when the state ready promise is resolved.\n const pendingPromise = new Pending(`core/reactive:registerComponent${pendingCount++}`);\n\n // Keep track of the event listeners.\n let listeners = [];\n\n // Register watchers.\n let handlers = [];\n if (component.getWatchers !== undefined) {\n handlers = component.getWatchers();\n }\n handlers.forEach(({watch, handler}) => {\n\n if (watch === undefined) {\n dispatchFail();\n throw new Error(`Missing watch attribute in ${componentName} watcher`);\n }\n if (handler === undefined) {\n dispatchFail();\n throw new Error(`Missing handler for watcher ${watch} in ${componentName}`);\n }\n\n const listener = (event) => {\n handler.apply(component, [event.detail]);\n };\n\n // Save the listener information in case the component must be unregistered later.\n listeners.push({target: this.target, watch, listener});\n\n // The state manager triggers a general \"state changed\" event at a document level. However,\n // for the internal watchers, each component can listen to specific state changed custom events\n // in the target element. This way we can use the native event loop without colliding with other\n // reactive instances.\n this.target.addEventListener(watch, listener);\n });\n\n // Register state ready function. There's the possibility a component is registered after the initial state\n // is loaded. For those cases we have a state promise to handle this specific state change.\n if (component.stateReady !== undefined) {\n this.getInitialStatePromise()\n .then(state => {\n component.stateReady(state);\n pendingPromise.resolve();\n return true;\n })\n .catch(reason => {\n pendingPromise.resolve();\n log.error(`Initial state in ${componentName} rejected due to: ${reason}`);\n log.error(reason);\n });\n }\n\n // Save unregister data.\n this.watchers.set(component, listeners);\n this.components.add(component);\n\n // Dispatch an event to communicate the registration to the debug module.\n this.target.dispatchEvent(new CustomEvent('registerComponent:success', {\n bubbles: false,\n detail: {component},\n }));\n\n dispatchSuccess();\n return component;\n }\n\n /**\n * Unregister a component and its watchers.\n *\n * @param {object} component the object instance to unregister\n * @returns {object} the deleted component\n */\n unregisterComponent(component) {\n if (!this.components.has(component)) {\n return component;\n }\n\n this.components.delete(component);\n\n // Remove event listeners.\n const listeners = this.watchers.get(component);\n if (listeners === undefined) {\n return component;\n }\n\n listeners.forEach(({target, watch, listener}) => {\n target.removeEventListener(watch, listener);\n });\n\n this.watchers.delete(component);\n\n return component;\n }\n\n /**\n * Dispatch a change in the state.\n *\n * This method is the only way for components to alter the state. Watchers will receive a\n * read only state to prevent illegal changes. If some user action require a state change, the\n * component should dispatch a mutation to trigger all the necessary logic to alter the state.\n *\n * @method dispatch\n * @param {string} actionName the action name (usually the mutation name)\n * @param {*} param any number of params the mutation needs.\n */\n async dispatch(actionName, ...params) {\n if (typeof actionName !== 'string') {\n throw new Error(`Dispatch action name must be a string`);\n }\n // JS does not have private methods yet. However, we prevent any component from calling\n // a method starting with \"_\" because the most accepted convention for private methods.\n if (actionName.charAt(0) === '_') {\n throw new Error(`Illegal Private ${actionName} mutation method dispatch`);\n }\n if (this.mutations[actionName] === undefined) {\n throw new Error(`Unkown ${actionName} mutation`);\n }\n\n const pendingPromise = new Pending(`core/reactive:${actionName}${pendingCount++}`);\n\n const mutationFunction = this.mutations[actionName];\n try {\n await mutationFunction.apply(this.mutations, [this.stateManager, ...params]);\n pendingPromise.resolve();\n } catch (error) {\n // Ensure the state is locked.\n this.stateManager.setReadOnly(true);\n pendingPromise.resolve();\n throw error;\n }\n }\n}\n"],"file":"reactive.min.js"} \ No newline at end of file diff --git a/lib/amd/build/local/reactive/statemanager.min.js b/lib/amd/build/local/reactive/statemanager.min.js index 8b0cd8e54bc..b1b6afca721 100644 --- a/lib/amd/build/local/reactive/statemanager.min.js +++ b/lib/amd/build/local/reactive/statemanager.min.js @@ -1,2 +1,2 @@ -define ("core/local/reactive/statemanager",["exports"],function(a){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.default=void 0;function b(a){"@babel/helpers - typeof";if("function"==typeof Symbol&&"symbol"==typeof Symbol.iterator){b=function(a){return typeof a}}else{b=function(a){return a&&"function"==typeof Symbol&&a.constructor===Symbol&&a!==Symbol.prototype?"symbol":typeof a}}return b(a)}function c(a,b,e){if("undefined"!=typeof Reflect&&Reflect.get){c=Reflect.get}else{c=function(a,b,c){var e=d(a,b);if(!e)return;var f=Object.getOwnPropertyDescriptor(e,b);if(f.get){return f.get.call(c)}return f.value}}return c(a,b,e||a)}function d(a,b){while(!Object.prototype.hasOwnProperty.call(a,b)){a=n(a);if(null===a)break}return a}function e(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 f(a){return function(){var b=n(a),c;if(k()){var d=n(this).constructor;c=Reflect.construct(b,arguments,d)}else{c=b.apply(this,arguments)}return g(this,c)}}function g(a,c){if(c&&("object"===b(c)||"function"==typeof c)){return c}return h(a)}function h(a){if(void 0===a){throw new ReferenceError("this hasn't been initialised - super() hasn't been called")}return a}function i(a){var b="function"==typeof Map?new Map:void 0;i=function(a){if(null===a||!l(a))return a;if("function"!=typeof a){throw new TypeError("Super expression must either be null or a function")}if("undefined"!=typeof b){if(b.has(a))return b.get(a);b.set(a,c)}function c(){return j(a,arguments,n(this).constructor)}c.prototype=Object.create(a.prototype,{constructor:{value:c,enumerable:!1,writable:!0,configurable:!0}});return m(c,a)};return i(a)}function j(){if(k()){j=Reflect.construct}else{j=function(b,c,d){var e=[null];e.push.apply(e,c);var a=Function.bind.apply(b,e),f=new a;if(d)m(f,d.prototype);return f}}return j.apply(null,arguments)}function k(){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 l(a){return-1!==Function.toString.call(a).indexOf("[native code]")}function m(a,b){m=Object.setPrototypeOf||function(a,b){a.__proto__=b;return a};return m(a,b)}function n(a){n=Object.setPrototypeOf?Object.getPrototypeOf:function(a){return a.__proto__||Object.getPrototypeOf(a)};return n(a)}function o(a,b){return t(a)||s(a,b)||q(a,b)||p()}function p(){throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}function q(a,b){if(!a)return;if("string"==typeof a)return r(a,b);var c=Object.prototype.toString.call(a).slice(8,-1);if("Object"===c&&a.constructor)c=a.constructor.name;if("Map"===c||"Set"===c)return Array.from(c);if("Arguments"===c||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(c))return r(a,b)}function r(a,b){if(null==b||b>a.length)b=a.length;for(var c=0,d=Array(b);c<b;c++){d[c]=a[c]}return d}function s(a,b){if("undefined"==typeof Symbol||!(Symbol.iterator in Object(a)))return;var c=[],d=!0,e=!1,f=void 0;try{for(var g=a[Symbol.iterator](),h;!(d=(h=g.next()).done);d=!0){c.push(h.value);if(b&&c.length===b)break}}catch(a){e=!0;f=a}finally{try{if(!d&&null!=g["return"])g["return"]()}finally{if(e)throw f}}return c}function t(a){if(Array.isArray(a))return a}function u(a,b){if(!(a instanceof b)){throw new TypeError("Cannot call a class as a function")}}function v(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 w(a,b,c){if(b)v(a.prototype,b);if(c)v(a,c);return a}var x=function(){function a(b,c){var d=this;u(this,a);this.dispatchEvent=b;this.target=null!==c&&void 0!==c?c:document;this.readonly=!1;this.eventsToPublish=[];this.updateTypes={create:this.defaultCreate.bind(this),update:this.defaultUpdate.bind(this),delete:this.defaultDelete.bind(this),put:this.defaultPut.bind(this),override:this.defaultOverride.bind(this),prepareFields:this.defaultPrepareFields.bind(this)};this.initialPromise=new Promise(function(a){d.target.addEventListener("state:loaded",function initialStateDone(b){a(b.detail.state)})})}w(a,[{key:"setInitialState",value:function setInitialState(a){if(this.state!==void 0){throw Error("Initial state can only be initialized ones")}for(var b=new Proxy({},new y("state",this,!0)),c=0,d=Object.entries(a);c<d.length;c++){var e=o(d[c],2),f=e[0],g=e[1];b[f]=g}this.state=b;this.readonly=!0;this.dispatchEvent({action:"state:loaded",state:this.state},this.target)}},{key:"getInitialPromise",value:function getInitialPromise(){return this.initialPromise}},{key:"setReadOnly",value:function setReadOnly(a){this.readonly=a;if(this.readonly){this._publishEvents()}}},{key:"addUpdateTypes",value:function addUpdateTypes(a){for(var b=0,c=Object.entries(a);b<c.length;b++){var d=o(c[b],2),e=d[0],f=d[1];if("function"==typeof f){this.updateTypes[e]=f.bind(a)}}}},{key:"processUpdates",value:function processUpdates(a,b){var c=this;if(!Array.isArray(a)){throw Error("State updates must be an array")}this.setReadOnly(!1);a.forEach(function(a){if(a.name===void 0){throw Error("Missing state update name")}c.processUpdate(a.name,a.action,a.fields,b)});this.setReadOnly(!0)}},{key:"processUpdate",value:function processUpdate(a,b,c,d){var e,f,g;if(!c){throw Error("Missing state update fields")}if(d===void 0){d={}}b=null!==(e=b)&&void 0!==e?e:"update";var h=null!==(f=d[b])&&void 0!==f?f:this.updateTypes[b];if(h===void 0){throw Error("Unkown update action ".concat(b))}var i=null!==(g=d.prepareFields)&&void 0!==g?g:this.updateTypes.prepareFields;h(this,a,i(this,a,c))}},{key:"defaultPrepareFields",value:function defaultPrepareFields(a,b,c){return c}},{key:"defaultCreate",value:function defaultCreate(a,b,c){var d=a.state;if(d[b]instanceof z){d[b].add(c);return}d[b]=c}},{key:"defaultDelete",value:function defaultDelete(a,b,c){var d=a.get(b,c.id);if(!d){throw Error("Inexistent ".concat(b," ").concat(c.id))}var e=a.state;if(e[b]instanceof z){e[b].delete(c.id);return}delete e[b]}},{key:"defaultUpdate",value:function defaultUpdate(a,b,c){var d=a.get(b,c.id);if(!d){throw Error("Inexistent ".concat(b," ").concat(c.id))}for(var e=0,f=Object.entries(c);e<f.length;e++){var g=o(f[e],2),h=g[0],i=g[1];d[h]=i}}},{key:"defaultPut",value:function defaultPut(a,b,c){var d=a.get(b,c.id);if(d){for(var e=0,f=Object.entries(c);e<f.length;e++){var g=o(f[e],2),h=g[0],i=g[1];d[h]=i}}else{var j=a.state;if(j[b]instanceof z){j[b].add(c);return}j[b]=c}}},{key:"defaultOverride",value:function defaultOverride(a,b,c){var d=a.get(b,c.id);if(d){for(var e=0,f=Object.entries(d);e<f.length;e++){var g=o(f[e],1),h=g[0];if(c[h]===void 0){delete d[h]}}for(var i=0,j=Object.entries(c);i<j.length;i++){var k=o(j[i],2),l=k[0],m=k[1];d[l]=m}}else{var n=a.state;if(n[b]instanceof z){n[b].add(c);return}n[b]=c}}},{key:"get",value:function get(a,b){var c=this.state,d=c[a];if(d instanceof z){if(b===void 0){throw Error("Missing id for ".concat(a," state update"))}d=c[a].get(b)}return d}},{key:"registerStateAction",value:function registerStateAction(a,b,c,d){var e="updated";if(null!==b){this.eventsToPublish.push({eventName:"".concat(a,".").concat(b,":").concat(c),eventData:d,action:c})}else{e=c}if(d.id!==void 0){if(null!==b){this.eventsToPublish.push({eventName:"".concat(a,"[").concat(d.id,"].").concat(b,":").concat(c),eventData:d,action:c})}this.eventsToPublish.push({eventName:"".concat(a,"[").concat(d.id,"]:").concat(e),eventData:d,action:e})}this.eventsToPublish.push({eventName:"".concat(a,":").concat(e),eventData:d,action:e});this.eventsToPublish.push({eventName:"state:updated",eventData:d,action:"updated"})}},{key:"_publishEvents",value:function _publishEvents(){var a=this,b=this.eventsToPublish;this.eventsToPublish=[];this.dispatchEvent({action:"transaction:start",state:this.state,element:null},this.target);b.sort(function(c,a){var b,d,e={created:0,updated:1,deleted:2},f=null!==(b=e[c.action])&&void 0!==b?b:0,g=null!==(d=e[a.action])&&void 0!==d?d:0;if(f===g){return c.eventName.length-a.eventName.length}return f-g});var c=new Set;b.forEach(function(b){var d,e="".concat(b.eventName,".").concat(null!==(d=b.eventData.id)&&void 0!==d?d:0);if(!c.has(e)){a.dispatchEvent({action:b.eventName,state:a.state,element:b.eventData},a.target);c.add(e)}});this.dispatchEvent({action:"transaction:end",state:this.state,element:null},this.target)}}]);return a}();a.default=x;var y=function(){function a(b,c,d){u(this,a);this.name=b;this.stateManager=c;this.proxyValues=null!==d&&void 0!==d?d:!1}w(a,[{key:"set",value:function set(b,c,d,e){if(this.stateManager.readonly){throw new Error("State locked. Use mutations to change ".concat(c," value in ").concat(this.name,"."))}if(JSON.stringify(b[c])===JSON.stringify(d)){return!0}var f=b[c]!==void 0?"updated":"created";if(this.proxyValues){if(Array.isArray(d)){b[c]=new z(c,this.stateManager).loadValues(d)}else{b[c]=new Proxy(d,new a(c,this.stateManager))}}else{b[c]=d}if(this.stateManager.state===void 0){return!0}this.stateManager.registerStateAction(this.name,c,f,e);return!0}},{key:"deleteProperty",value:function deleteProperty(a,b){if(this.stateManager.readonly){throw new Error("State locked. Use mutations to delete ".concat(b," in ").concat(this.name,"."))}if(b in a){delete a[b];this.stateManager.registerStateAction(this.name,b,"deleted",a)}return!0}}]);return a}(),z=function(a){e(d,a);var g=f(d);function d(a,b,c){var e;u(this,d);e=g.call(this,c);e.name=a;e.stateManager=b;return e}w(d,[{key:"set",value:function set(a,b){if(this.stateManager.readonly){throw new Error("State locked. Use mutations to change ".concat(a," value in ").concat(this.name,"."))}a=this.normalizeKey(a);this.checkValue(b);if(a===void 0||null===a){throw Error("State lists keys cannot be null or undefined")}if(this.normalizeKey(b.id)!==a){throw new Error("State error: ".concat(this.name," list element ID (").concat(b.id,") and key (").concat(a,") mismatch"))}var e=c(n(d.prototype),"has",this).call(this,a)?"updated":"created",f=c(n(d.prototype),"set",this).call(this,a,new Proxy(b,new y(this.name,this.stateManager)));if(this.stateManager.state===void 0){return f}this.stateManager.registerStateAction(this.name,null,e,c(n(d.prototype),"get",this).call(this,a));return f}},{key:"checkValue",value:function checkValue(a){if("object"===!b(a)&&null!==a){throw Error("State lists can contain objects only")}if(a.id===void 0){throw Error("State lists elements must contain at least an id attribute")}}},{key:"normalizeKey",value:function normalizeKey(a){return(a+"").valueOf()}},{key:"add",value:function add(a){this.checkValue(a);return this.set(a.id,a)}},{key:"get",value:function get(a){return c(n(d.prototype),"get",this).call(this,this.normalizeKey(a))}},{key:"has",value:function has(a){return c(n(d.prototype),"has",this).call(this,this.normalizeKey(a))}},{key:"delete",value:function _delete(a){a=this.normalizeKey(a);if(this.stateManager.readonly){throw new Error("State locked. Use mutations to change ".concat(a," value in ").concat(this.name,"."))}var b=c(n(d.prototype),"get",this).call(this,a),e=c(n(d.prototype),"delete",this).call(this,a);if(!e){return e}this.stateManager.registerStateAction(this.name,null,"deleted",b);return e}},{key:"toJSON",value:function toJSON(){var a=[];this.forEach(function(b){a.push(b)});return a}},{key:"loadValues",value:function loadValues(a){var b=this;a.forEach(function(a){b.checkValue(a);var c=a.id,d=new Proxy(a,new y(b.name,b.stateManager));b.set(c,d)});return this}}]);return d}(i(Map));return a.default}); +define ("core/local/reactive/statemanager",["exports"],function(a){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.default=void 0;function b(a){"@babel/helpers - typeof";if("function"==typeof Symbol&&"symbol"==typeof Symbol.iterator){b=function(a){return typeof a}}else{b=function(a){return a&&"function"==typeof Symbol&&a.constructor===Symbol&&a!==Symbol.prototype?"symbol":typeof a}}return b(a)}function c(a,b,e){if("undefined"!=typeof Reflect&&Reflect.get){c=Reflect.get}else{c=function(a,b,c){var e=d(a,b);if(!e)return;var f=Object.getOwnPropertyDescriptor(e,b);if(f.get){return f.get.call(c)}return f.value}}return c(a,b,e||a)}function d(a,b){while(!Object.prototype.hasOwnProperty.call(a,b)){a=n(a);if(null===a)break}return a}function e(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 f(a){return function(){var b=n(a),c;if(k()){var d=n(this).constructor;c=Reflect.construct(b,arguments,d)}else{c=b.apply(this,arguments)}return g(this,c)}}function g(a,c){if(c&&("object"===b(c)||"function"==typeof c)){return c}return h(a)}function h(a){if(void 0===a){throw new ReferenceError("this hasn't been initialised - super() hasn't been called")}return a}function i(a){var b="function"==typeof Map?new Map:void 0;i=function(a){if(null===a||!l(a))return a;if("function"!=typeof a){throw new TypeError("Super expression must either be null or a function")}if("undefined"!=typeof b){if(b.has(a))return b.get(a);b.set(a,c)}function c(){return j(a,arguments,n(this).constructor)}c.prototype=Object.create(a.prototype,{constructor:{value:c,enumerable:!1,writable:!0,configurable:!0}});return m(c,a)};return i(a)}function j(){if(k()){j=Reflect.construct}else{j=function(b,c,d){var e=[null];e.push.apply(e,c);var a=Function.bind.apply(b,e),f=new a;if(d)m(f,d.prototype);return f}}return j.apply(null,arguments)}function k(){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 l(a){return-1!==Function.toString.call(a).indexOf("[native code]")}function m(a,b){m=Object.setPrototypeOf||function(a,b){a.__proto__=b;return a};return m(a,b)}function n(a){n=Object.setPrototypeOf?Object.getPrototypeOf:function(a){return a.__proto__||Object.getPrototypeOf(a)};return n(a)}function o(a,b){return t(a)||s(a,b)||q(a,b)||p()}function p(){throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}function q(a,b){if(!a)return;if("string"==typeof a)return r(a,b);var c=Object.prototype.toString.call(a).slice(8,-1);if("Object"===c&&a.constructor)c=a.constructor.name;if("Map"===c||"Set"===c)return Array.from(c);if("Arguments"===c||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(c))return r(a,b)}function r(a,b){if(null==b||b>a.length)b=a.length;for(var c=0,d=Array(b);c<b;c++){d[c]=a[c]}return d}function s(a,b){if("undefined"==typeof Symbol||!(Symbol.iterator in Object(a)))return;var c=[],d=!0,e=!1,f=void 0;try{for(var g=a[Symbol.iterator](),h;!(d=(h=g.next()).done);d=!0){c.push(h.value);if(b&&c.length===b)break}}catch(a){e=!0;f=a}finally{try{if(!d&&null!=g["return"])g["return"]()}finally{if(e)throw f}}return c}function t(a){if(Array.isArray(a))return a}function u(a,b){if(!(a instanceof b)){throw new TypeError("Cannot call a class as a function")}}function v(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 w(a,b,c){if(b)v(a.prototype,b);if(c)v(a,c);return a}var x=function(){function a(b,c){var d=this;u(this,a);this.dispatchEvent=b;this.target=null!==c&&void 0!==c?c:document;this.readonly=!1;this.eventsToPublish=[];this.updateTypes={create:this.defaultCreate.bind(this),update:this.defaultUpdate.bind(this),delete:this.defaultDelete.bind(this),put:this.defaultPut.bind(this),override:this.defaultOverride.bind(this),prepareFields:this.defaultPrepareFields.bind(this)};this.initialPromise=new Promise(function(a){d.target.addEventListener("state:loaded",function initialStateDone(b){a(b.detail.state)})})}w(a,[{key:"setInitialState",value:function setInitialState(a){if(this.state!==void 0){throw Error("Initial state can only be initialized ones")}for(var b=new Proxy({},new y("state",this,!0)),c=0,d=Object.entries(a);c<d.length;c++){var e=o(d[c],2),f=e[0],g=e[1];b[f]=g}this.state=b;this.readonly=!0;this.dispatchEvent({action:"state:loaded",state:this.state},this.target)}},{key:"getInitialPromise",value:function getInitialPromise(){return this.initialPromise}},{key:"setReadOnly",value:function setReadOnly(a){this.readonly=a;var b="off";if(this.readonly){b="on";this._publishEvents()}this.dispatchEvent({action:"readmode:".concat(b),state:this.state,element:null},this.target)}},{key:"addUpdateTypes",value:function addUpdateTypes(a){for(var b=0,c=Object.entries(a);b<c.length;b++){var d=o(c[b],2),e=d[0],f=d[1];if("function"==typeof f){this.updateTypes[e]=f.bind(a)}}}},{key:"processUpdates",value:function processUpdates(a,b){var c=this;if(!Array.isArray(a)){throw Error("State updates must be an array")}this.setReadOnly(!1);a.forEach(function(a){if(a.name===void 0){throw Error("Missing state update name")}c.processUpdate(a.name,a.action,a.fields,b)});this.setReadOnly(!0)}},{key:"processUpdate",value:function processUpdate(a,b,c,d){var e,f,g;if(!c){throw Error("Missing state update fields")}if(d===void 0){d={}}b=null!==(e=b)&&void 0!==e?e:"update";var h=null!==(f=d[b])&&void 0!==f?f:this.updateTypes[b];if(h===void 0){throw Error("Unkown update action ".concat(b))}var i=null!==(g=d.prepareFields)&&void 0!==g?g:this.updateTypes.prepareFields;h(this,a,i(this,a,c))}},{key:"defaultPrepareFields",value:function defaultPrepareFields(a,b,c){return c}},{key:"defaultCreate",value:function defaultCreate(a,b,c){var d=a.state;if(d[b]instanceof z){d[b].add(c);return}d[b]=c}},{key:"defaultDelete",value:function defaultDelete(a,b,c){var d=a.get(b,c.id);if(!d){throw Error("Inexistent ".concat(b," ").concat(c.id))}var e=a.state;if(e[b]instanceof z){e[b].delete(c.id);return}delete e[b]}},{key:"defaultUpdate",value:function defaultUpdate(a,b,c){var d=a.get(b,c.id);if(!d){throw Error("Inexistent ".concat(b," ").concat(c.id))}for(var e=0,f=Object.entries(c);e<f.length;e++){var g=o(f[e],2),h=g[0],i=g[1];d[h]=i}}},{key:"defaultPut",value:function defaultPut(a,b,c){var d=a.get(b,c.id);if(d){for(var e=0,f=Object.entries(c);e<f.length;e++){var g=o(f[e],2),h=g[0],i=g[1];d[h]=i}}else{var j=a.state;if(j[b]instanceof z){j[b].add(c);return}j[b]=c}}},{key:"defaultOverride",value:function defaultOverride(a,b,c){var d=a.get(b,c.id);if(d){for(var e=0,f=Object.entries(d);e<f.length;e++){var g=o(f[e],1),h=g[0];if(c[h]===void 0){delete d[h]}}for(var i=0,j=Object.entries(c);i<j.length;i++){var k=o(j[i],2),l=k[0],m=k[1];d[l]=m}}else{var n=a.state;if(n[b]instanceof z){n[b].add(c);return}n[b]=c}}},{key:"get",value:function get(a,b){var c=this.state,d=c[a];if(d instanceof z){if(b===void 0){throw Error("Missing id for ".concat(a," state update"))}d=c[a].get(b)}return d}},{key:"registerStateAction",value:function registerStateAction(a,b,c,d){var e="updated";if(null!==b){this.eventsToPublish.push({eventName:"".concat(a,".").concat(b,":").concat(c),eventData:d,action:c})}else{e=c}if(d.id!==void 0){if(null!==b){this.eventsToPublish.push({eventName:"".concat(a,"[").concat(d.id,"].").concat(b,":").concat(c),eventData:d,action:c})}this.eventsToPublish.push({eventName:"".concat(a,"[").concat(d.id,"]:").concat(e),eventData:d,action:e})}this.eventsToPublish.push({eventName:"".concat(a,":").concat(e),eventData:d,action:e});this.eventsToPublish.push({eventName:"state:updated",eventData:d,action:"updated"})}},{key:"_publishEvents",value:function _publishEvents(){var a=this,b=this.eventsToPublish;this.eventsToPublish=[];this.dispatchEvent({action:"transaction:start",state:this.state,element:null,changes:b},this.target);b.sort(function(c,a){var b,d,e={created:0,updated:1,deleted:2},f=null!==(b=e[c.action])&&void 0!==b?b:0,g=null!==(d=e[a.action])&&void 0!==d?d:0;if(f===g){return c.eventName.length-a.eventName.length}return f-g});var c=new Set;b.forEach(function(b){var d,e="".concat(b.eventName,".").concat(null!==(d=b.eventData.id)&&void 0!==d?d:0);if(!c.has(e)){a.dispatchEvent({action:b.eventName,state:a.state,element:b.eventData},a.target);c.add(e)}});this.dispatchEvent({action:"transaction:end",state:this.state,element:null},this.target)}}]);return a}();a.default=x;var y=function(){function a(b,c,d){u(this,a);this.name=b;this.stateManager=c;this.proxyValues=null!==d&&void 0!==d?d:!1}w(a,[{key:"set",value:function set(b,c,d,e){if(this.stateManager.readonly){throw new Error("State locked. Use mutations to change ".concat(c," value in ").concat(this.name,"."))}if(JSON.stringify(b[c])===JSON.stringify(d)){return!0}var f=b[c]!==void 0?"updated":"created";if(this.proxyValues){if(Array.isArray(d)){b[c]=new z(c,this.stateManager).loadValues(d)}else{b[c]=new Proxy(d,new a(c,this.stateManager))}}else{b[c]=d}if(this.stateManager.state===void 0){return!0}this.stateManager.registerStateAction(this.name,c,f,e);return!0}},{key:"deleteProperty",value:function deleteProperty(a,b){if(this.stateManager.readonly){throw new Error("State locked. Use mutations to delete ".concat(b," in ").concat(this.name,"."))}if(b in a){delete a[b];this.stateManager.registerStateAction(this.name,b,"deleted",a)}return!0}}]);return a}(),z=function(a){e(d,a);var g=f(d);function d(a,b,c){var e;u(this,d);e=g.call(this,c);e.name=a;e.stateManager=b;return e}w(d,[{key:"set",value:function set(a,b){if(this.stateManager.readonly){throw new Error("State locked. Use mutations to change ".concat(a," value in ").concat(this.name,"."))}a=this.normalizeKey(a);this.checkValue(b);if(a===void 0||null===a){throw Error("State lists keys cannot be null or undefined")}if(this.normalizeKey(b.id)!==a){throw new Error("State error: ".concat(this.name," list element ID (").concat(b.id,") and key (").concat(a,") mismatch"))}var e=c(n(d.prototype),"has",this).call(this,a)?"updated":"created",f=c(n(d.prototype),"set",this).call(this,a,new Proxy(b,new y(this.name,this.stateManager)));if(this.stateManager.state===void 0){return f}this.stateManager.registerStateAction(this.name,null,e,c(n(d.prototype),"get",this).call(this,a));return f}},{key:"checkValue",value:function checkValue(a){if("object"===!b(a)&&null!==a){throw Error("State lists can contain objects only")}if(a.id===void 0){throw Error("State lists elements must contain at least an id attribute")}}},{key:"normalizeKey",value:function normalizeKey(a){return(a+"").valueOf()}},{key:"add",value:function add(a){this.checkValue(a);return this.set(a.id,a)}},{key:"get",value:function get(a){return c(n(d.prototype),"get",this).call(this,this.normalizeKey(a))}},{key:"has",value:function has(a){return c(n(d.prototype),"has",this).call(this,this.normalizeKey(a))}},{key:"delete",value:function _delete(a){a=this.normalizeKey(a);if(this.stateManager.readonly){throw new Error("State locked. Use mutations to change ".concat(a," value in ").concat(this.name,"."))}var b=c(n(d.prototype),"get",this).call(this,a),e=c(n(d.prototype),"delete",this).call(this,a);if(!e){return e}this.stateManager.registerStateAction(this.name,null,"deleted",b);return e}},{key:"toJSON",value:function toJSON(){var a=[];this.forEach(function(b){a.push(b)});return a}},{key:"loadValues",value:function loadValues(a){var b=this;a.forEach(function(a){b.checkValue(a);var c=a.id,d=new Proxy(a,new y(b.name,b.stateManager));b.set(c,d)});return this}}]);return d}(i(Map));return a.default}); //# sourceMappingURL=statemanager.min.js.map diff --git a/lib/amd/build/local/reactive/statemanager.min.js.map b/lib/amd/build/local/reactive/statemanager.min.js.map index a045bce3539..cff93ef0167 100644 --- a/lib/amd/build/local/reactive/statemanager.min.js.map +++ b/lib/amd/build/local/reactive/statemanager.min.js.map @@ -1 +1 @@ -{"version":3,"sources":["../../../src/local/reactive/statemanager.js"],"names":["StateManager","dispatchEvent","target","document","readonly","eventsToPublish","updateTypes","defaultCreate","bind","defaultUpdate","defaultDelete","defaultPut","defaultOverride","defaultPrepareFields","initialPromise","Promise","resolve","addEventListener","initialStateDone","event","detail","state","initialState","Error","Proxy","Handler","Object","entries","prop","propValue","action","_publishEvents","newFunctions","updateType","updateFunction","updates","Array","isArray","setReadOnly","forEach","update","name","processUpdate","fields","updateName","method","prepareFields","stateManager","StateMap","add","current","get","id","delete","fieldName","fieldValue","field","data","parentAction","push","eventName","eventData","fieldChanges","element","sort","a","b","weights","created","updated","deleted","aweight","bweight","length","publishedEvents","Set","eventkey","has","proxyValues","obj","value","receiver","JSON","stringify","loadValues","registerStateAction","iterable","key","normalizeKey","checkValue","result","valueOf","set","previous","values","newvalue","Map"],"mappings":"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","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 * Reactive simple state manager.\n *\n * The state manager contains the state data, trigger update events and\n * can lock and unlock the state data.\n *\n * This file contains the three main elements of the state manager:\n * - State manager: the public class to alter the state, dispatch events and process update messages.\n * - Proxy handler: a private class to keep track of the state object changes.\n * - StateMap class: a private class extending Map class that triggers event when a state list is modifed.\n *\n * @module core/local/reactive/stateManager\n * @class core/local/reactive/stateManager\n * @copyright 2021 Ferran Recio <ferran@moodle.com>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\n/**\n * State manager class.\n *\n * This class handle the reactive state and ensure only valid mutations can modify the state.\n * It also provide methods to apply batch state update messages (see processUpdates function doc\n * for more details on update messages).\n *\n * Implementing a deep state manager is complex and will require many frontend resources. To keep\n * the state fast and simple, the state can ONLY store two kind of data:\n * - Object with attributes\n * - Sets of objects with id attributes.\n *\n * This is an example of a valid state:\n *\n * {\n * course: {\n * name: 'course name',\n * shortname: 'courseshort',\n * sectionlist: [21, 34]\n * },\n * sections: [\n * {id: 21, name: 'Topic 1', visible: true},\n * {id: 34, name: 'Topic 2', visible: false,\n * ],\n * }\n *\n * The following cases are NOT allowed at a state ROOT level (throws an exception if they are assigned):\n * - Simple values (strings, boolean...).\n * - Arrays of simple values.\n * - Array of objects without ID attribute (all arrays will be converted to maps and requires an ID).\n *\n * Thanks to those limitations it can simplify the state update messages and the event names. If You\n * need to store simple data, just group them in an object.\n *\n * To grant any state change triggers the proper events, the class uses two private structures:\n * - proxy handler: any object stored in the state is proxied using this class.\n * - StateMap class: any object set in the state will be converted to StateMap using the\n * objects id attribute.\n */\nexport default class StateManager {\n\n /**\n * Create a basic reactive state store.\n *\n * The state manager is meant to work with native JS events. To ensure each reactive module can use\n * it in its own way, the parent element must provide a valid event dispatcher function and an optional\n * DOM element to anchor the event.\n *\n * @param {function} dispatchEvent the function to dispatch the custom event when the state changes.\n * @param {element} target the state changed custom event target (document if none provided)\n */\n constructor(dispatchEvent, target) {\n\n // The dispatch event function.\n /** @package */\n this.dispatchEvent = dispatchEvent;\n\n // The DOM container to trigger events.\n /** @package */\n this.target = target ?? document;\n\n // State can be altered freely until initial state is set.\n /** @package */\n this.readonly = false;\n\n // List of state changes pending to be published as events.\n /** @package */\n this.eventsToPublish = [];\n\n // The update state types functions.\n /** @package */\n this.updateTypes = {\n \"create\": this.defaultCreate.bind(this),\n \"update\": this.defaultUpdate.bind(this),\n \"delete\": this.defaultDelete.bind(this),\n \"put\": this.defaultPut.bind(this),\n \"override\": this.defaultOverride.bind(this),\n \"prepareFields\": this.defaultPrepareFields.bind(this),\n };\n\n // The state_loaded event is special because it only happens one but all components\n // may react to that state, even if they are registered after the setIinitialState.\n // For these reason we use a promise for that event.\n this.initialPromise = new Promise((resolve) => {\n const initialStateDone = (event) => {\n resolve(event.detail.state);\n };\n this.target.addEventListener('state:loaded', initialStateDone);\n });\n }\n\n /**\n * Loads the initial state.\n *\n * Note this method will trigger a state changed event with \"state:loaded\" actionname.\n *\n * The state mode will be set to read only when the initial state is loaded.\n *\n * @param {object} initialState\n */\n setInitialState(initialState) {\n\n if (this.state !== undefined) {\n throw Error('Initial state can only be initialized ones');\n }\n\n // Create the state object.\n const state = new Proxy({}, new Handler('state', this, true));\n for (const [prop, propValue] of Object.entries(initialState)) {\n state[prop] = propValue;\n }\n this.state = state;\n\n // When the state is loaded we can lock it to prevent illegal changes.\n this.readonly = true;\n\n this.dispatchEvent({\n action: 'state:loaded',\n state: this.state,\n }, this.target);\n }\n\n /**\n * Generate a promise that will be resolved when the initial state is loaded.\n *\n * In most cases the final state will be loaded using an ajax call. This is the reason\n * why states manager are created unlocked and won't be reactive until the initial state is set.\n *\n * @return {Promise} the resulting promise\n */\n getInitialPromise() {\n return this.initialPromise;\n }\n\n /**\n * Locks or unlocks the state to prevent illegal updates.\n *\n * Mutations use this method to modify the state. Once the state is updated, they must\n * block again the state.\n *\n * All changes done while the state is writable will be registered using registerStateAction.\n * When the state is set again to read only the method will trigger _publishEvents to communicate\n * changes to all watchers.\n *\n * @param {bool} readonly if the state is in read only mode enabled\n */\n setReadOnly(readonly) {\n\n this.readonly = readonly;\n\n // When the state is in readonly again is time to publish all pending events.\n if (this.readonly) {\n this._publishEvents();\n }\n }\n\n /**\n * Add methods to process update state messages.\n *\n * The state manager provide a default update, create and delete methods. However,\n * some applications may require to override the default methods or even add new ones\n * like \"refresh\" or \"error\".\n *\n * @param {Object} newFunctions the new update types functions.\n */\n addUpdateTypes(newFunctions) {\n for (const [updateType, updateFunction] of Object.entries(newFunctions)) {\n if (typeof updateFunction === 'function') {\n this.updateTypes[updateType] = updateFunction.bind(newFunctions);\n }\n }\n }\n\n /**\n * Process a state updates array and do all the necessary changes.\n *\n * Note this method unlocks the state while it is executing and relocks it\n * when finishes.\n *\n * @param {array} updates\n * @param {Object} updateTypes optional functions to override the default update types.\n */\n processUpdates(updates, updateTypes) {\n if (!Array.isArray(updates)) {\n throw Error('State updates must be an array');\n }\n this.setReadOnly(false);\n updates.forEach((update) => {\n if (update.name === undefined) {\n throw Error('Missing state update name');\n }\n this.processUpdate(\n update.name,\n update.action,\n update.fields,\n updateTypes\n );\n });\n this.setReadOnly(true);\n }\n\n /**\n * Process a single state update.\n *\n * Note this method will not lock or unlock the state by itself.\n *\n * @param {string} updateName the state element to update\n * @param {string} action to action to perform\n * @param {object} fields the new data\n * @param {Object} updateTypes optional functions to override the default update types.\n */\n processUpdate(updateName, action, fields, updateTypes) {\n\n if (!fields) {\n throw Error('Missing state update fields');\n }\n\n if (updateTypes === undefined) {\n updateTypes = {};\n }\n\n action = action ?? 'update';\n\n const method = updateTypes[action] ?? this.updateTypes[action];\n\n if (method === undefined) {\n throw Error(`Unkown update action ${action}`);\n }\n\n // Some state data may require some cooking before sending to the\n // state. Reactive instances can overrdide the default fieldDefaults\n // method to add extra logic to all updates.\n const prepareFields = updateTypes.prepareFields ?? this.updateTypes.prepareFields;\n\n method(this, updateName, prepareFields(this, updateName, fields));\n }\n\n /**\n * Prepare fields for processing.\n *\n * This method is used to add default values or calculations from the frontend side.\n *\n * @param {Object} stateManager the state manager\n * @param {String} updateName the state element to update\n * @param {Object} fields the new data\n * @returns {Object} final fields data\n */\n defaultPrepareFields(stateManager, updateName, fields) {\n return fields;\n }\n\n\n /**\n * Process a create state message.\n *\n * @param {Object} stateManager the state manager\n * @param {String} updateName the state element to update\n * @param {Object} fields the new data\n */\n defaultCreate(stateManager, updateName, fields) {\n\n let state = stateManager.state;\n\n // Create can be applied only to lists, not to objects.\n if (state[updateName] instanceof StateMap) {\n state[updateName].add(fields);\n return;\n }\n state[updateName] = fields;\n }\n\n /**\n * Process a delete state message.\n *\n * @param {Object} stateManager the state manager\n * @param {String} updateName the state element to update\n * @param {Object} fields the new data\n */\n defaultDelete(stateManager, updateName, fields) {\n\n // Get the current value.\n let current = stateManager.get(updateName, fields.id);\n if (!current) {\n throw Error(`Inexistent ${updateName} ${fields.id}`);\n }\n\n // Process deletion.\n let state = stateManager.state;\n\n if (state[updateName] instanceof StateMap) {\n state[updateName].delete(fields.id);\n return;\n }\n delete state[updateName];\n }\n\n /**\n * Process a update state message.\n *\n * @param {Object} stateManager the state manager\n * @param {String} updateName the state element to update\n * @param {Object} fields the new data\n */\n defaultUpdate(stateManager, updateName, fields) {\n\n // Get the current value.\n let current = stateManager.get(updateName, fields.id);\n if (!current) {\n throw Error(`Inexistent ${updateName} ${fields.id}`);\n }\n\n // Execute updates.\n for (const [fieldName, fieldValue] of Object.entries(fields)) {\n current[fieldName] = fieldValue;\n }\n }\n\n /**\n * Process a put state message.\n *\n * @param {Object} stateManager the state manager\n * @param {String} updateName the state element to update\n * @param {Object} fields the new data\n */\n defaultPut(stateManager, updateName, fields) {\n\n // Get the current value.\n let current = stateManager.get(updateName, fields.id);\n if (current) {\n // Update attributes.\n for (const [fieldName, fieldValue] of Object.entries(fields)) {\n current[fieldName] = fieldValue;\n }\n } else {\n // Create new object.\n let state = stateManager.state;\n if (state[updateName] instanceof StateMap) {\n state[updateName].add(fields);\n return;\n }\n state[updateName] = fields;\n }\n }\n\n /**\n * Process an override state message.\n *\n * @param {Object} stateManager the state manager\n * @param {String} updateName the state element to update\n * @param {Object} fields the new data\n */\n defaultOverride(stateManager, updateName, fields) {\n\n // Get the current value.\n let current = stateManager.get(updateName, fields.id);\n if (current) {\n // Remove any unnecessary fields.\n for (const [fieldName] of Object.entries(current)) {\n if (fields[fieldName] === undefined) {\n delete current[fieldName];\n }\n }\n // Update field.\n for (const [fieldName, fieldValue] of Object.entries(fields)) {\n current[fieldName] = fieldValue;\n }\n } else {\n // Create the element if not exists.\n let state = stateManager.state;\n if (state[updateName] instanceof StateMap) {\n state[updateName].add(fields);\n return;\n }\n state[updateName] = fields;\n }\n }\n\n /**\n * Get an element from the state or form an alternative state object.\n *\n * The altstate param is used by external update functions that gets the current\n * state as param.\n *\n * @param {String} name the state object name\n * @param {*} id and object id for state maps.\n * @return {Object|undefined} the state object found\n */\n get(name, id) {\n const state = this.state;\n\n let current = state[name];\n if (current instanceof StateMap) {\n if (id === undefined) {\n throw Error(`Missing id for ${name} state update`);\n }\n current = state[name].get(id);\n }\n\n return current;\n }\n\n /**\n * Register a state modification and generate the necessary events.\n *\n * This method is used mainly by proxy helpers to dispatch state change event.\n * However, mutations can use it to inform components about non reactive changes\n * in the state (only the two first levels of the state are reactive).\n *\n * Each action can produce several events:\n * - The specific attribute updated, created or deleter (example: \"cm.visible:updated\")\n * - The general state object updated, created or deleted (example: \"cm:updated\")\n * - If the element has an ID attribute, the specific event with id (example: \"cm[42].visible:updated\")\n * - If the element has an ID attribute, the general event with id (example: \"cm[42]:updated\")\n * - A generic state update event \"state:update\"\n *\n * @param {string} field the affected state field name\n * @param {string|null} prop the affecter field property (null if affect the full object)\n * @param {string} action the action done (created/updated/deleted)\n * @param {*} data the affected data\n */\n registerStateAction(field, prop, action, data) {\n\n let parentAction = 'updated';\n\n if (prop !== null) {\n this.eventsToPublish.push({\n eventName: `${field}.${prop}:${action}`,\n eventData: data,\n action,\n });\n } else {\n parentAction = action;\n }\n\n // Trigger extra events if the element has an ID attribute.\n if (data.id !== undefined) {\n if (prop !== null) {\n this.eventsToPublish.push({\n eventName: `${field}[${data.id}].${prop}:${action}`,\n eventData: data,\n action,\n });\n }\n this.eventsToPublish.push({\n eventName: `${field}[${data.id}]:${parentAction}`,\n eventData: data,\n action: parentAction,\n });\n }\n\n // Register the general change.\n this.eventsToPublish.push({\n eventName: `${field}:${parentAction}`,\n eventData: data,\n action: parentAction,\n });\n\n // Register state updated event.\n this.eventsToPublish.push({\n eventName: `state:updated`,\n eventData: data,\n action: 'updated',\n });\n }\n\n /**\n * Internal method to publish events.\n *\n * This is a private method, it will be invoked when the state is set back to read only mode.\n */\n _publishEvents() {\n const fieldChanges = this.eventsToPublish;\n this.eventsToPublish = [];\n\n // Dispatch a transaction start event.\n this.dispatchEvent({\n action: 'transaction:start',\n state: this.state,\n element: null,\n }, this.target);\n\n // State changes can be registered in any order. However it will avoid many\n // components errors if they are sorted to have creations-updates-deletes in case\n // some component needs to create or destroy DOM elements before updating them.\n fieldChanges.sort((a, b) => {\n const weights = {\n created: 0,\n updated: 1,\n deleted: 2,\n };\n const aweight = weights[a.action] ?? 0;\n const bweight = weights[b.action] ?? 0;\n // In case both have the same weight, the eventName length decide.\n if (aweight === bweight) {\n return a.eventName.length - b.eventName.length;\n }\n return aweight - bweight;\n });\n\n // List of the published events to prevent redundancies.\n let publishedEvents = new Set();\n\n fieldChanges.forEach((event) => {\n\n const eventkey = `${event.eventName}.${event.eventData.id ?? 0}`;\n\n if (!publishedEvents.has(eventkey)) {\n this.dispatchEvent({\n action: event.eventName,\n state: this.state,\n element: event.eventData\n }, this.target);\n\n publishedEvents.add(eventkey);\n }\n });\n\n // Dispatch a transaction end event.\n this.dispatchEvent({\n action: 'transaction:end',\n state: this.state,\n element: null,\n }, this.target);\n }\n}\n\n// Proxy helpers.\n\n/**\n * The proxy handler.\n *\n * This class will inform any value change directly to the state manager.\n *\n * The proxied variable will throw an error if it is altered when the state manager is\n * in read only mode.\n */\nclass Handler {\n\n /**\n * Class constructor.\n *\n * @param {string} name the variable name used for identify triggered actions\n * @param {StateManager} stateManager the state manager object\n * @param {boolean} proxyValues if new values must be proxied (used only at state root level)\n */\n constructor(name, stateManager, proxyValues) {\n this.name = name;\n this.stateManager = stateManager;\n this.proxyValues = proxyValues ?? false;\n }\n\n /**\n * Set trap to trigger events when the state changes.\n *\n * @param {object} obj the source object (not proxied)\n * @param {string} prop the attribute to set\n * @param {*} value the value to save\n * @param {*} receiver the proxied element to be attached to events\n * @returns {boolean} if the value is set\n */\n set(obj, prop, value, receiver) {\n\n // Only mutations should be able to set state values.\n if (this.stateManager.readonly) {\n throw new Error(`State locked. Use mutations to change ${prop} value in ${this.name}.`);\n }\n\n // Check any data change.\n if (JSON.stringify(obj[prop]) === JSON.stringify(value)) {\n return true;\n }\n\n const action = (obj[prop] !== undefined) ? 'updated' : 'created';\n\n // Proxy value if necessary (used at state root level).\n if (this.proxyValues) {\n if (Array.isArray(value)) {\n obj[prop] = new StateMap(prop, this.stateManager).loadValues(value);\n } else {\n obj[prop] = new Proxy(value, new Handler(prop, this.stateManager));\n }\n } else {\n obj[prop] = value;\n }\n\n // If the state is not ready yet means the initial state is not yet loaded.\n if (this.stateManager.state === undefined) {\n return true;\n }\n\n this.stateManager.registerStateAction(this.name, prop, action, receiver);\n\n return true;\n }\n\n /**\n * Delete property trap to trigger state change events.\n *\n * @param {*} obj the affected object (not proxied)\n * @param {*} prop the prop to delete\n * @returns {boolean} if prop is deleted\n */\n deleteProperty(obj, prop) {\n // Only mutations should be able to set state values.\n if (this.stateManager.readonly) {\n throw new Error(`State locked. Use mutations to delete ${prop} in ${this.name}.`);\n }\n if (prop in obj) {\n\n delete obj[prop];\n\n this.stateManager.registerStateAction(this.name, prop, 'deleted', obj);\n }\n return true;\n }\n}\n\n/**\n * Class to add events dispatching to the JS Map class.\n *\n * When the state has a list of objects (with IDs) it will be converted into a StateMap.\n * StateMap is used almost in the same way as a regular JS map. Because all elements have an\n * id attribute, it has some specific methods:\n * - add: a convenient method to add an element without specifying the key (\"id\" attribute will be used as a key).\n * - loadValues: to add many elements at once wihout specifying keys (\"id\" attribute will be used).\n *\n * Apart, the main difference between regular Map and MapState is that this one will inform any change to the\n * state manager.\n */\nclass StateMap extends Map {\n\n /**\n * Create a reactive Map.\n *\n * @param {string} name the property name\n * @param {StateManager} stateManager the state manager\n * @param {iterable} iterable an iterable object to create the Map\n */\n constructor(name, stateManager, iterable) {\n // We don't have any \"this\" until be call super.\n super(iterable);\n this.name = name;\n this.stateManager = stateManager;\n }\n\n /**\n * Set an element into the map.\n *\n * Each value needs it's own id attribute. Objects without id will be rejected.\n * The function will throw an error if the value id and the key are not the same.\n *\n * @param {*} key the key to store\n * @param {*} value the value to store\n * @returns {Map} the resulting Map object\n */\n set(key, value) {\n\n // Only mutations should be able to set state values.\n if (this.stateManager.readonly) {\n throw new Error(`State locked. Use mutations to change ${key} value in ${this.name}.`);\n }\n\n // Normalize keys as string to prevent json decoding errors.\n key = this.normalizeKey(key);\n\n this.checkValue(value);\n\n if (key === undefined || key === null) {\n throw Error('State lists keys cannot be null or undefined');\n }\n\n // ID is mandatory and should be the same as the key.\n if (this.normalizeKey(value.id) !== key) {\n throw new Error(`State error: ${this.name} list element ID (${value.id}) and key (${key}) mismatch`);\n }\n\n const action = (super.has(key)) ? 'updated' : 'created';\n\n // Save proxied data into the list.\n const result = super.set(key, new Proxy(value, new Handler(this.name, this.stateManager)));\n\n // If the state is not ready yet means the initial state is not yet loaded.\n if (this.stateManager.state === undefined) {\n return result;\n }\n\n this.stateManager.registerStateAction(this.name, null, action, super.get(key));\n\n return result;\n }\n\n /**\n * Check if a value is valid to be stored in a a State List.\n *\n * Only objects with id attribute can be stored in State lists.\n *\n * This method throws an error if the value is not valid.\n *\n * @param {object} value (with ID)\n */\n checkValue(value) {\n if (!typeof value === 'object' && value !== null) {\n throw Error('State lists can contain objects only');\n }\n\n if (value.id === undefined) {\n throw Error('State lists elements must contain at least an id attribute');\n }\n }\n\n /**\n * Return a normalized key value for state map.\n *\n * Regular maps uses strict key comparissons but state maps are indexed by ID.JSON conversions\n * and webservices sometimes do unexpected types conversions so we convert any integer key to string.\n *\n * @param {*} key the provided key\n * @returns {string}\n */\n normalizeKey(key) {\n return String(key).valueOf();\n }\n\n /**\n * Insert a new element int a list.\n *\n * Each value needs it's own id attribute. Objects withouts id will be rejected.\n *\n * @param {object} value the value to add (needs an id attribute)\n * @returns {Map} the resulting Map object\n */\n add(value) {\n this.checkValue(value);\n return this.set(value.id, value);\n }\n\n /**\n * Return a state map element.\n *\n * @param {*} key the element id\n * @return {Object}\n */\n get(key) {\n return super.get(this.normalizeKey(key));\n }\n\n /**\n * Check whether an element with the specified key exists or not.\n *\n * @param {*} key the key to find\n * @return {boolean}\n */\n has(key) {\n return super.has(this.normalizeKey(key));\n }\n\n /**\n * Delete an element from the map.\n *\n * @param {*} key\n * @returns {boolean}\n */\n delete(key) {\n // State maps uses only string keys to avoid strict comparisons.\n key = this.normalizeKey(key);\n\n // Only mutations should be able to set state values.\n if (this.stateManager.readonly) {\n throw new Error(`State locked. Use mutations to change ${key} value in ${this.name}.`);\n }\n\n const previous = super.get(key);\n\n const result = super.delete(key);\n if (!result) {\n return result;\n }\n\n this.stateManager.registerStateAction(this.name, null, 'deleted', previous);\n\n return result;\n }\n\n /**\n * Return a suitable structure for JSON conversion.\n *\n * This function is needed because new values are compared in JSON. StateMap has Private\n * attributes which cannot be stringified (like this.stateManager which will produce an\n * infinite recursivity).\n *\n * @returns {array}\n */\n toJSON() {\n let result = [];\n this.forEach((value) => {\n result.push(value);\n });\n return result;\n }\n\n /**\n * Insert a full list of values using the id attributes as keys.\n *\n * This method is used mainly to initialize the list. Note each element is indexed by its \"id\" attribute.\n * This is a basic restriction of StateMap. All elements need an id attribute, otherwise it won't be saved.\n *\n * @param {iterable} values the values to load\n * @returns {StateMap} return the this value\n */\n loadValues(values) {\n values.forEach((data) => {\n this.checkValue(data);\n let key = data.id;\n let newvalue = new Proxy(data, new Handler(this.name, this.stateManager));\n this.set(key, newvalue);\n });\n return this;\n }\n}\n"],"file":"statemanager.min.js"} \ No newline at end of file +{"version":3,"sources":["../../../src/local/reactive/statemanager.js"],"names":["StateManager","dispatchEvent","target","document","readonly","eventsToPublish","updateTypes","defaultCreate","bind","defaultUpdate","defaultDelete","defaultPut","defaultOverride","defaultPrepareFields","initialPromise","Promise","resolve","addEventListener","initialStateDone","event","detail","state","initialState","Error","Proxy","Handler","Object","entries","prop","propValue","action","mode","_publishEvents","element","newFunctions","updateType","updateFunction","updates","Array","isArray","setReadOnly","forEach","update","name","processUpdate","fields","updateName","method","prepareFields","stateManager","StateMap","add","current","get","id","delete","fieldName","fieldValue","field","data","parentAction","push","eventName","eventData","fieldChanges","changes","sort","a","b","weights","created","updated","deleted","aweight","bweight","length","publishedEvents","Set","eventkey","has","proxyValues","obj","value","receiver","JSON","stringify","loadValues","registerStateAction","iterable","key","normalizeKey","checkValue","result","valueOf","set","previous","values","newvalue","Map"],"mappings":"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","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 * Reactive simple state manager.\n *\n * The state manager contains the state data, trigger update events and\n * can lock and unlock the state data.\n *\n * This file contains the three main elements of the state manager:\n * - State manager: the public class to alter the state, dispatch events and process update messages.\n * - Proxy handler: a private class to keep track of the state object changes.\n * - StateMap class: a private class extending Map class that triggers event when a state list is modifed.\n *\n * @module core/local/reactive/stateManager\n * @class core/local/reactive/stateManager\n * @copyright 2021 Ferran Recio <ferran@moodle.com>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\n/**\n * State manager class.\n *\n * This class handle the reactive state and ensure only valid mutations can modify the state.\n * It also provide methods to apply batch state update messages (see processUpdates function doc\n * for more details on update messages).\n *\n * Implementing a deep state manager is complex and will require many frontend resources. To keep\n * the state fast and simple, the state can ONLY store two kind of data:\n * - Object with attributes\n * - Sets of objects with id attributes.\n *\n * This is an example of a valid state:\n *\n * {\n * course: {\n * name: 'course name',\n * shortname: 'courseshort',\n * sectionlist: [21, 34]\n * },\n * sections: [\n * {id: 21, name: 'Topic 1', visible: true},\n * {id: 34, name: 'Topic 2', visible: false,\n * ],\n * }\n *\n * The following cases are NOT allowed at a state ROOT level (throws an exception if they are assigned):\n * - Simple values (strings, boolean...).\n * - Arrays of simple values.\n * - Array of objects without ID attribute (all arrays will be converted to maps and requires an ID).\n *\n * Thanks to those limitations it can simplify the state update messages and the event names. If You\n * need to store simple data, just group them in an object.\n *\n * To grant any state change triggers the proper events, the class uses two private structures:\n * - proxy handler: any object stored in the state is proxied using this class.\n * - StateMap class: any object set in the state will be converted to StateMap using the\n * objects id attribute.\n */\nexport default class StateManager {\n\n /**\n * Create a basic reactive state store.\n *\n * The state manager is meant to work with native JS events. To ensure each reactive module can use\n * it in its own way, the parent element must provide a valid event dispatcher function and an optional\n * DOM element to anchor the event.\n *\n * @param {function} dispatchEvent the function to dispatch the custom event when the state changes.\n * @param {element} target the state changed custom event target (document if none provided)\n */\n constructor(dispatchEvent, target) {\n\n // The dispatch event function.\n /** @package */\n this.dispatchEvent = dispatchEvent;\n\n // The DOM container to trigger events.\n /** @package */\n this.target = target ?? document;\n\n // State can be altered freely until initial state is set.\n /** @package */\n this.readonly = false;\n\n // List of state changes pending to be published as events.\n /** @package */\n this.eventsToPublish = [];\n\n // The update state types functions.\n /** @package */\n this.updateTypes = {\n \"create\": this.defaultCreate.bind(this),\n \"update\": this.defaultUpdate.bind(this),\n \"delete\": this.defaultDelete.bind(this),\n \"put\": this.defaultPut.bind(this),\n \"override\": this.defaultOverride.bind(this),\n \"prepareFields\": this.defaultPrepareFields.bind(this),\n };\n\n // The state_loaded event is special because it only happens one but all components\n // may react to that state, even if they are registered after the setIinitialState.\n // For these reason we use a promise for that event.\n this.initialPromise = new Promise((resolve) => {\n const initialStateDone = (event) => {\n resolve(event.detail.state);\n };\n this.target.addEventListener('state:loaded', initialStateDone);\n });\n }\n\n /**\n * Loads the initial state.\n *\n * Note this method will trigger a state changed event with \"state:loaded\" actionname.\n *\n * The state mode will be set to read only when the initial state is loaded.\n *\n * @param {object} initialState\n */\n setInitialState(initialState) {\n\n if (this.state !== undefined) {\n throw Error('Initial state can only be initialized ones');\n }\n\n // Create the state object.\n const state = new Proxy({}, new Handler('state', this, true));\n for (const [prop, propValue] of Object.entries(initialState)) {\n state[prop] = propValue;\n }\n this.state = state;\n\n // When the state is loaded we can lock it to prevent illegal changes.\n this.readonly = true;\n\n this.dispatchEvent({\n action: 'state:loaded',\n state: this.state,\n }, this.target);\n }\n\n /**\n * Generate a promise that will be resolved when the initial state is loaded.\n *\n * In most cases the final state will be loaded using an ajax call. This is the reason\n * why states manager are created unlocked and won't be reactive until the initial state is set.\n *\n * @return {Promise} the resulting promise\n */\n getInitialPromise() {\n return this.initialPromise;\n }\n\n /**\n * Locks or unlocks the state to prevent illegal updates.\n *\n * Mutations use this method to modify the state. Once the state is updated, they must\n * block again the state.\n *\n * All changes done while the state is writable will be registered using registerStateAction.\n * When the state is set again to read only the method will trigger _publishEvents to communicate\n * changes to all watchers.\n *\n * @param {bool} readonly if the state is in read only mode enabled\n */\n setReadOnly(readonly) {\n\n this.readonly = readonly;\n\n let mode = 'off';\n\n // When the state is in readonly again is time to publish all pending events.\n if (this.readonly) {\n mode = 'on';\n this._publishEvents();\n }\n\n // Dispatch a read only event.\n this.dispatchEvent({\n action: `readmode:${mode}`,\n state: this.state,\n element: null,\n }, this.target);\n }\n\n /**\n * Add methods to process update state messages.\n *\n * The state manager provide a default update, create and delete methods. However,\n * some applications may require to override the default methods or even add new ones\n * like \"refresh\" or \"error\".\n *\n * @param {Object} newFunctions the new update types functions.\n */\n addUpdateTypes(newFunctions) {\n for (const [updateType, updateFunction] of Object.entries(newFunctions)) {\n if (typeof updateFunction === 'function') {\n this.updateTypes[updateType] = updateFunction.bind(newFunctions);\n }\n }\n }\n\n /**\n * Process a state updates array and do all the necessary changes.\n *\n * Note this method unlocks the state while it is executing and relocks it\n * when finishes.\n *\n * @param {array} updates\n * @param {Object} updateTypes optional functions to override the default update types.\n */\n processUpdates(updates, updateTypes) {\n if (!Array.isArray(updates)) {\n throw Error('State updates must be an array');\n }\n this.setReadOnly(false);\n updates.forEach((update) => {\n if (update.name === undefined) {\n throw Error('Missing state update name');\n }\n this.processUpdate(\n update.name,\n update.action,\n update.fields,\n updateTypes\n );\n });\n this.setReadOnly(true);\n }\n\n /**\n * Process a single state update.\n *\n * Note this method will not lock or unlock the state by itself.\n *\n * @param {string} updateName the state element to update\n * @param {string} action to action to perform\n * @param {object} fields the new data\n * @param {Object} updateTypes optional functions to override the default update types.\n */\n processUpdate(updateName, action, fields, updateTypes) {\n\n if (!fields) {\n throw Error('Missing state update fields');\n }\n\n if (updateTypes === undefined) {\n updateTypes = {};\n }\n\n action = action ?? 'update';\n\n const method = updateTypes[action] ?? this.updateTypes[action];\n\n if (method === undefined) {\n throw Error(`Unkown update action ${action}`);\n }\n\n // Some state data may require some cooking before sending to the\n // state. Reactive instances can overrdide the default fieldDefaults\n // method to add extra logic to all updates.\n const prepareFields = updateTypes.prepareFields ?? this.updateTypes.prepareFields;\n\n method(this, updateName, prepareFields(this, updateName, fields));\n }\n\n /**\n * Prepare fields for processing.\n *\n * This method is used to add default values or calculations from the frontend side.\n *\n * @param {Object} stateManager the state manager\n * @param {String} updateName the state element to update\n * @param {Object} fields the new data\n * @returns {Object} final fields data\n */\n defaultPrepareFields(stateManager, updateName, fields) {\n return fields;\n }\n\n\n /**\n * Process a create state message.\n *\n * @param {Object} stateManager the state manager\n * @param {String} updateName the state element to update\n * @param {Object} fields the new data\n */\n defaultCreate(stateManager, updateName, fields) {\n\n let state = stateManager.state;\n\n // Create can be applied only to lists, not to objects.\n if (state[updateName] instanceof StateMap) {\n state[updateName].add(fields);\n return;\n }\n state[updateName] = fields;\n }\n\n /**\n * Process a delete state message.\n *\n * @param {Object} stateManager the state manager\n * @param {String} updateName the state element to update\n * @param {Object} fields the new data\n */\n defaultDelete(stateManager, updateName, fields) {\n\n // Get the current value.\n let current = stateManager.get(updateName, fields.id);\n if (!current) {\n throw Error(`Inexistent ${updateName} ${fields.id}`);\n }\n\n // Process deletion.\n let state = stateManager.state;\n\n if (state[updateName] instanceof StateMap) {\n state[updateName].delete(fields.id);\n return;\n }\n delete state[updateName];\n }\n\n /**\n * Process a update state message.\n *\n * @param {Object} stateManager the state manager\n * @param {String} updateName the state element to update\n * @param {Object} fields the new data\n */\n defaultUpdate(stateManager, updateName, fields) {\n\n // Get the current value.\n let current = stateManager.get(updateName, fields.id);\n if (!current) {\n throw Error(`Inexistent ${updateName} ${fields.id}`);\n }\n\n // Execute updates.\n for (const [fieldName, fieldValue] of Object.entries(fields)) {\n current[fieldName] = fieldValue;\n }\n }\n\n /**\n * Process a put state message.\n *\n * @param {Object} stateManager the state manager\n * @param {String} updateName the state element to update\n * @param {Object} fields the new data\n */\n defaultPut(stateManager, updateName, fields) {\n\n // Get the current value.\n let current = stateManager.get(updateName, fields.id);\n if (current) {\n // Update attributes.\n for (const [fieldName, fieldValue] of Object.entries(fields)) {\n current[fieldName] = fieldValue;\n }\n } else {\n // Create new object.\n let state = stateManager.state;\n if (state[updateName] instanceof StateMap) {\n state[updateName].add(fields);\n return;\n }\n state[updateName] = fields;\n }\n }\n\n /**\n * Process an override state message.\n *\n * @param {Object} stateManager the state manager\n * @param {String} updateName the state element to update\n * @param {Object} fields the new data\n */\n defaultOverride(stateManager, updateName, fields) {\n\n // Get the current value.\n let current = stateManager.get(updateName, fields.id);\n if (current) {\n // Remove any unnecessary fields.\n for (const [fieldName] of Object.entries(current)) {\n if (fields[fieldName] === undefined) {\n delete current[fieldName];\n }\n }\n // Update field.\n for (const [fieldName, fieldValue] of Object.entries(fields)) {\n current[fieldName] = fieldValue;\n }\n } else {\n // Create the element if not exists.\n let state = stateManager.state;\n if (state[updateName] instanceof StateMap) {\n state[updateName].add(fields);\n return;\n }\n state[updateName] = fields;\n }\n }\n\n /**\n * Get an element from the state or form an alternative state object.\n *\n * The altstate param is used by external update functions that gets the current\n * state as param.\n *\n * @param {String} name the state object name\n * @param {*} id and object id for state maps.\n * @return {Object|undefined} the state object found\n */\n get(name, id) {\n const state = this.state;\n\n let current = state[name];\n if (current instanceof StateMap) {\n if (id === undefined) {\n throw Error(`Missing id for ${name} state update`);\n }\n current = state[name].get(id);\n }\n\n return current;\n }\n\n /**\n * Register a state modification and generate the necessary events.\n *\n * This method is used mainly by proxy helpers to dispatch state change event.\n * However, mutations can use it to inform components about non reactive changes\n * in the state (only the two first levels of the state are reactive).\n *\n * Each action can produce several events:\n * - The specific attribute updated, created or deleter (example: \"cm.visible:updated\")\n * - The general state object updated, created or deleted (example: \"cm:updated\")\n * - If the element has an ID attribute, the specific event with id (example: \"cm[42].visible:updated\")\n * - If the element has an ID attribute, the general event with id (example: \"cm[42]:updated\")\n * - A generic state update event \"state:update\"\n *\n * @param {string} field the affected state field name\n * @param {string|null} prop the affecter field property (null if affect the full object)\n * @param {string} action the action done (created/updated/deleted)\n * @param {*} data the affected data\n */\n registerStateAction(field, prop, action, data) {\n\n let parentAction = 'updated';\n\n if (prop !== null) {\n this.eventsToPublish.push({\n eventName: `${field}.${prop}:${action}`,\n eventData: data,\n action,\n });\n } else {\n parentAction = action;\n }\n\n // Trigger extra events if the element has an ID attribute.\n if (data.id !== undefined) {\n if (prop !== null) {\n this.eventsToPublish.push({\n eventName: `${field}[${data.id}].${prop}:${action}`,\n eventData: data,\n action,\n });\n }\n this.eventsToPublish.push({\n eventName: `${field}[${data.id}]:${parentAction}`,\n eventData: data,\n action: parentAction,\n });\n }\n\n // Register the general change.\n this.eventsToPublish.push({\n eventName: `${field}:${parentAction}`,\n eventData: data,\n action: parentAction,\n });\n\n // Register state updated event.\n this.eventsToPublish.push({\n eventName: `state:updated`,\n eventData: data,\n action: 'updated',\n });\n }\n\n /**\n * Internal method to publish events.\n *\n * This is a private method, it will be invoked when the state is set back to read only mode.\n */\n _publishEvents() {\n const fieldChanges = this.eventsToPublish;\n this.eventsToPublish = [];\n\n // Dispatch a transaction start event.\n this.dispatchEvent({\n action: 'transaction:start',\n state: this.state,\n element: null,\n changes: fieldChanges,\n }, this.target);\n\n // State changes can be registered in any order. However it will avoid many\n // components errors if they are sorted to have creations-updates-deletes in case\n // some component needs to create or destroy DOM elements before updating them.\n fieldChanges.sort((a, b) => {\n const weights = {\n created: 0,\n updated: 1,\n deleted: 2,\n };\n const aweight = weights[a.action] ?? 0;\n const bweight = weights[b.action] ?? 0;\n // In case both have the same weight, the eventName length decide.\n if (aweight === bweight) {\n return a.eventName.length - b.eventName.length;\n }\n return aweight - bweight;\n });\n\n // List of the published events to prevent redundancies.\n let publishedEvents = new Set();\n\n fieldChanges.forEach((event) => {\n\n const eventkey = `${event.eventName}.${event.eventData.id ?? 0}`;\n\n if (!publishedEvents.has(eventkey)) {\n this.dispatchEvent({\n action: event.eventName,\n state: this.state,\n element: event.eventData\n }, this.target);\n\n publishedEvents.add(eventkey);\n }\n });\n\n // Dispatch a transaction end event.\n this.dispatchEvent({\n action: 'transaction:end',\n state: this.state,\n element: null,\n }, this.target);\n }\n}\n\n// Proxy helpers.\n\n/**\n * The proxy handler.\n *\n * This class will inform any value change directly to the state manager.\n *\n * The proxied variable will throw an error if it is altered when the state manager is\n * in read only mode.\n */\nclass Handler {\n\n /**\n * Class constructor.\n *\n * @param {string} name the variable name used for identify triggered actions\n * @param {StateManager} stateManager the state manager object\n * @param {boolean} proxyValues if new values must be proxied (used only at state root level)\n */\n constructor(name, stateManager, proxyValues) {\n this.name = name;\n this.stateManager = stateManager;\n this.proxyValues = proxyValues ?? false;\n }\n\n /**\n * Set trap to trigger events when the state changes.\n *\n * @param {object} obj the source object (not proxied)\n * @param {string} prop the attribute to set\n * @param {*} value the value to save\n * @param {*} receiver the proxied element to be attached to events\n * @returns {boolean} if the value is set\n */\n set(obj, prop, value, receiver) {\n\n // Only mutations should be able to set state values.\n if (this.stateManager.readonly) {\n throw new Error(`State locked. Use mutations to change ${prop} value in ${this.name}.`);\n }\n\n // Check any data change.\n if (JSON.stringify(obj[prop]) === JSON.stringify(value)) {\n return true;\n }\n\n const action = (obj[prop] !== undefined) ? 'updated' : 'created';\n\n // Proxy value if necessary (used at state root level).\n if (this.proxyValues) {\n if (Array.isArray(value)) {\n obj[prop] = new StateMap(prop, this.stateManager).loadValues(value);\n } else {\n obj[prop] = new Proxy(value, new Handler(prop, this.stateManager));\n }\n } else {\n obj[prop] = value;\n }\n\n // If the state is not ready yet means the initial state is not yet loaded.\n if (this.stateManager.state === undefined) {\n return true;\n }\n\n this.stateManager.registerStateAction(this.name, prop, action, receiver);\n\n return true;\n }\n\n /**\n * Delete property trap to trigger state change events.\n *\n * @param {*} obj the affected object (not proxied)\n * @param {*} prop the prop to delete\n * @returns {boolean} if prop is deleted\n */\n deleteProperty(obj, prop) {\n // Only mutations should be able to set state values.\n if (this.stateManager.readonly) {\n throw new Error(`State locked. Use mutations to delete ${prop} in ${this.name}.`);\n }\n if (prop in obj) {\n\n delete obj[prop];\n\n this.stateManager.registerStateAction(this.name, prop, 'deleted', obj);\n }\n return true;\n }\n}\n\n/**\n * Class to add events dispatching to the JS Map class.\n *\n * When the state has a list of objects (with IDs) it will be converted into a StateMap.\n * StateMap is used almost in the same way as a regular JS map. Because all elements have an\n * id attribute, it has some specific methods:\n * - add: a convenient method to add an element without specifying the key (\"id\" attribute will be used as a key).\n * - loadValues: to add many elements at once wihout specifying keys (\"id\" attribute will be used).\n *\n * Apart, the main difference between regular Map and MapState is that this one will inform any change to the\n * state manager.\n */\nclass StateMap extends Map {\n\n /**\n * Create a reactive Map.\n *\n * @param {string} name the property name\n * @param {StateManager} stateManager the state manager\n * @param {iterable} iterable an iterable object to create the Map\n */\n constructor(name, stateManager, iterable) {\n // We don't have any \"this\" until be call super.\n super(iterable);\n this.name = name;\n this.stateManager = stateManager;\n }\n\n /**\n * Set an element into the map.\n *\n * Each value needs it's own id attribute. Objects without id will be rejected.\n * The function will throw an error if the value id and the key are not the same.\n *\n * @param {*} key the key to store\n * @param {*} value the value to store\n * @returns {Map} the resulting Map object\n */\n set(key, value) {\n\n // Only mutations should be able to set state values.\n if (this.stateManager.readonly) {\n throw new Error(`State locked. Use mutations to change ${key} value in ${this.name}.`);\n }\n\n // Normalize keys as string to prevent json decoding errors.\n key = this.normalizeKey(key);\n\n this.checkValue(value);\n\n if (key === undefined || key === null) {\n throw Error('State lists keys cannot be null or undefined');\n }\n\n // ID is mandatory and should be the same as the key.\n if (this.normalizeKey(value.id) !== key) {\n throw new Error(`State error: ${this.name} list element ID (${value.id}) and key (${key}) mismatch`);\n }\n\n const action = (super.has(key)) ? 'updated' : 'created';\n\n // Save proxied data into the list.\n const result = super.set(key, new Proxy(value, new Handler(this.name, this.stateManager)));\n\n // If the state is not ready yet means the initial state is not yet loaded.\n if (this.stateManager.state === undefined) {\n return result;\n }\n\n this.stateManager.registerStateAction(this.name, null, action, super.get(key));\n\n return result;\n }\n\n /**\n * Check if a value is valid to be stored in a a State List.\n *\n * Only objects with id attribute can be stored in State lists.\n *\n * This method throws an error if the value is not valid.\n *\n * @param {object} value (with ID)\n */\n checkValue(value) {\n if (!typeof value === 'object' && value !== null) {\n throw Error('State lists can contain objects only');\n }\n\n if (value.id === undefined) {\n throw Error('State lists elements must contain at least an id attribute');\n }\n }\n\n /**\n * Return a normalized key value for state map.\n *\n * Regular maps uses strict key comparissons but state maps are indexed by ID.JSON conversions\n * and webservices sometimes do unexpected types conversions so we convert any integer key to string.\n *\n * @param {*} key the provided key\n * @returns {string}\n */\n normalizeKey(key) {\n return String(key).valueOf();\n }\n\n /**\n * Insert a new element int a list.\n *\n * Each value needs it's own id attribute. Objects withouts id will be rejected.\n *\n * @param {object} value the value to add (needs an id attribute)\n * @returns {Map} the resulting Map object\n */\n add(value) {\n this.checkValue(value);\n return this.set(value.id, value);\n }\n\n /**\n * Return a state map element.\n *\n * @param {*} key the element id\n * @return {Object}\n */\n get(key) {\n return super.get(this.normalizeKey(key));\n }\n\n /**\n * Check whether an element with the specified key exists or not.\n *\n * @param {*} key the key to find\n * @return {boolean}\n */\n has(key) {\n return super.has(this.normalizeKey(key));\n }\n\n /**\n * Delete an element from the map.\n *\n * @param {*} key\n * @returns {boolean}\n */\n delete(key) {\n // State maps uses only string keys to avoid strict comparisons.\n key = this.normalizeKey(key);\n\n // Only mutations should be able to set state values.\n if (this.stateManager.readonly) {\n throw new Error(`State locked. Use mutations to change ${key} value in ${this.name}.`);\n }\n\n const previous = super.get(key);\n\n const result = super.delete(key);\n if (!result) {\n return result;\n }\n\n this.stateManager.registerStateAction(this.name, null, 'deleted', previous);\n\n return result;\n }\n\n /**\n * Return a suitable structure for JSON conversion.\n *\n * This function is needed because new values are compared in JSON. StateMap has Private\n * attributes which cannot be stringified (like this.stateManager which will produce an\n * infinite recursivity).\n *\n * @returns {array}\n */\n toJSON() {\n let result = [];\n this.forEach((value) => {\n result.push(value);\n });\n return result;\n }\n\n /**\n * Insert a full list of values using the id attributes as keys.\n *\n * This method is used mainly to initialize the list. Note each element is indexed by its \"id\" attribute.\n * This is a basic restriction of StateMap. All elements need an id attribute, otherwise it won't be saved.\n *\n * @param {iterable} values the values to load\n * @returns {StateMap} return the this value\n */\n loadValues(values) {\n values.forEach((data) => {\n this.checkValue(data);\n let key = data.id;\n let newvalue = new Proxy(data, new Handler(this.name, this.stateManager));\n this.set(key, newvalue);\n });\n return this;\n }\n}\n"],"file":"statemanager.min.js"} \ No newline at end of file diff --git a/lib/amd/build/reactive.min.js b/lib/amd/build/reactive.min.js index 2ec39b1b507..4bf4a6f66e2 100644 --- a/lib/amd/build/reactive.min.js +++ b/lib/amd/build/reactive.min.js @@ -1,2 +1,2 @@ -define ("core/reactive",["exports","core/local/reactive/basecomponent","core/local/reactive/reactive","core/local/reactive/dragdrop"],function(a,b,c,d){"use strict";Object.defineProperty(a,"__esModule",{value:!0});Object.defineProperty(a,"BaseComponent",{enumerable:!0,get:function get(){return b.default}});Object.defineProperty(a,"Reactive",{enumerable:!0,get:function get(){return c.default}});Object.defineProperty(a,"DragDrop",{enumerable:!0,get:function get(){return d.default}});b=e(b);c=e(c);d=e(d);function e(a){return a&&a.__esModule?a:{default:a}}}); +define ("core/reactive",["exports","core/local/reactive/basecomponent","core/local/reactive/reactive","core/local/reactive/dragdrop","core/local/reactive/debug"],function(a,b,c,d,e){"use strict";Object.defineProperty(a,"__esModule",{value:!0});Object.defineProperty(a,"BaseComponent",{enumerable:!0,get:function get(){return b.default}});Object.defineProperty(a,"Reactive",{enumerable:!0,get:function get(){return c.default}});Object.defineProperty(a,"DragDrop",{enumerable:!0,get:function get(){return d.default}});a.debug=void 0;b=f(b);c=f(c);d=f(d);function f(a){return a&&a.__esModule?a:{default:a}}var g;a.debug=g;if(M.cfg.developerdebug&&M.reactive===void 0){var h=(0,e.initDebug)();M.reactive=h.debuggers;a.debug=g=h.debug}}); //# sourceMappingURL=reactive.min.js.map diff --git a/lib/amd/build/reactive.min.js.map b/lib/amd/build/reactive.min.js.map index 3956add0c16..aa1b34576d5 100644 --- a/lib/amd/build/reactive.min.js.map +++ b/lib/amd/build/reactive.min.js.map @@ -1 +1 @@ -{"version":3,"sources":["../src/reactive.js"],"names":[],"mappings":"seAuBA,OACA,OACA,O","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 * Generic reactive module used in the course editor.\n *\n * @module core/reactive\n * @copyright 2021 Ferran Recio <ferran@moodle.com>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport BaseComponent from 'core/local/reactive/basecomponent';\nimport Reactive from 'core/local/reactive/reactive';\nimport DragDrop from 'core/local/reactive/dragdrop';\n\nexport {Reactive, BaseComponent, DragDrop};\n"],"file":"reactive.min.js"} \ No newline at end of file +{"version":3,"sources":["../src/reactive.js"],"names":["debug","M","cfg","developerdebug","reactive","debugOBject","debuggers"],"mappings":"mhBAuBA,OACA,OACA,O,mDAIA,GAAIA,CAAAA,CAAJ,C,UACA,GAAIC,CAAC,CAACC,GAAF,CAAMC,cAAN,EAAwBF,CAAC,CAACG,QAAF,SAA5B,CAAsD,CAClD,GAAMC,CAAAA,CAAW,CAAG,iBAApB,CACAJ,CAAC,CAACG,QAAF,CAAaC,CAAW,CAACC,SAAzB,CACA,QAAAN,CAAK,CAAGK,CAAW,CAACL,KACvB,C","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 * Generic reactive module used in the course editor.\n *\n * @module core/reactive\n * @copyright 2021 Ferran Recio <ferran@moodle.com>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport BaseComponent from 'core/local/reactive/basecomponent';\nimport Reactive from 'core/local/reactive/reactive';\nimport DragDrop from 'core/local/reactive/dragdrop';\nimport {initDebug} from 'core/local/reactive/debug';\n\n// Register a debug module if we are in debug mode.\nlet debug;\nif (M.cfg.developerdebug && M.reactive === undefined) {\n const debugOBject = initDebug();\n M.reactive = debugOBject.debuggers;\n debug = debugOBject.debug;\n}\n\nexport {Reactive, BaseComponent, DragDrop, debug};\n"],"file":"reactive.min.js"} \ No newline at end of file diff --git a/lib/amd/src/local/reactive/debug.js b/lib/amd/src/local/reactive/debug.js new file mode 100644 index 00000000000..12057481a1f --- /dev/null +++ b/lib/amd/src/local/reactive/debug.js @@ -0,0 +1,372 @@ +// 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/>. + +/** + * Reactive module debug tools. + * + * @module core/reactive/local/reactive/debug + * @copyright 2021 Ferran Recio <ferran@moodle.com> + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +import Reactive from 'core/local/reactive/reactive'; +import log from 'core/log'; + +// The list of reactives instances. +const reactiveInstances = {}; + +// The reactive debugging objects. +const reactiveDebuggers = {}; + +/** + * Reactive module debug tools. + * + * If debug is enabled, this reactive module will spy all the reactive instances and keep a record + * of the changes and components they have. + * + * It is important to note that the Debug class is also a Reactive module. The debug instance keeps + * the reactive instances data as its own state. This way it is possible to implement development tools + * that whatches this data. + * + * @class core/reactive/local/reactive/debug/Debug + * @copyright 2021 Ferran Recio <ferran@moodle.com> + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class Debug extends Reactive { + + /** + * Set the initial state. + * + * @param {object} stateData the initial state data. + */ + setInitialState(stateData) { + super.setInitialState(stateData); + log.debug(`Debug module "M.reactive" loaded.`); + } + + /** + * List the currents page reactives instances. + */ + get list() { + return JSON.parse(JSON.stringify(this.state.reactives)); + } + + /** + * Register a new Reactive instance. + * + * This method is called every time a "new Reactive" is executed. + * + * @param {Reactive} instance the reactive instance + */ + registerNewInstance(instance) { + + // Generate a valid variable name for that instance. + let name = instance.name ?? `instance${this.state.reactives.length}`; + name = name.replace(/\W/g, ''); + + log.debug(`Registering new reactive instance "M.reactive.${name}"`); + + reactiveInstances[name] = instance; + reactiveDebuggers[name] = new DebugInstance(reactiveInstances[name]); + // Register also in the state. + this.dispatch('putInstance', name, instance); + // Add debug watchers to instance. + const refreshMethod = () => { + this.dispatch('putInstance', name, instance); + }; + instance.target.addEventListener('readmode:on', refreshMethod); + instance.target.addEventListener('readmode:off', refreshMethod); + instance.target.addEventListener('registerComponent:success', refreshMethod); + instance.target.addEventListener('transaction:end', refreshMethod); + // We store the last transaction into the state. + const storeTransaction = ({detail}) => { + const changes = detail?.changes; + this.dispatch('lastTransaction', name, changes); + }; + instance.target.addEventListener('transaction:start', storeTransaction); + } + + /** + * Returns a debugging object for a specific Reactive instance. + * + * A debugging object is a class that wraps a Reactive instance to quick access some of the + * reactive methods using the browser JS console. + * + * @param {string} name the Reactive instance name + * @returns {DebugInstance} a debug object wrapping the Reactive instance + */ + debug(name) { + return reactiveDebuggers[name]; + } +} + +/** + * The debug state mutations class. + * + * @class core/reactive/local/reactive/debug/Mutations + */ +class Mutations { + + /** + * Insert or update a new instance into the debug state. + * + * @param {StateManager} stateManager the debug state manager + * @param {string} name the instance name + * @param {Reactive} instance the reactive instance + */ + putInstance(stateManager, name, instance) { + const state = stateManager.state; + + stateManager.setReadOnly(false); + + if (state.reactives.has(name)) { + state.reactives.get(name).countcomponents = instance.components.length; + state.reactives.get(name).readOnly = instance.stateManager.readonly; + state.reactives.get(name).modified = new Date().getTime(); + } else { + state.reactives.add({ + id: name, + countcomponents: instance.components.length, + readOnly: instance.stateManager.readonly, + lastChanges: [], + modified: new Date().getTime(), + }); + } + stateManager.setReadOnly(true); + } + + /** + * Update the lastChanges attribute with a list of changes + * + * @param {StateManager} stateManager the debug reactive state + * @param {string} name tje instance name + * @param {array} changes the list of changes + */ + lastTransaction(stateManager, name, changes) { + if (!changes || changes.length === 0) { + return; + } + + const state = stateManager.state; + const lastChanges = ['transaction:start']; + + changes.forEach(change => { + lastChanges.push(change.eventName); + }); + + lastChanges.push('transaction:end'); + + stateManager.setReadOnly(false); + + state.reactives.get(name).lastChanges = lastChanges; + + stateManager.setReadOnly(true); + } +} + +/** + * Class used to debug a specific instance and manipulate the state from the JS console. + * + * @class core/reactive/local/reactive/debug/DebugInstance + * @copyright 2021 Ferran Recio <ferran@moodle.com> + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class DebugInstance { + + /** + * Constructor. + * + * @param {Reactive} instance the reactive instance + */ + constructor(instance) { + this.instance = instance; + // Add some debug data directly into the instance. This way we avoid having attributes + // that will confuse the console aoutocomplete. + if (instance._reactiveDebugData === undefined) { + instance._reactiveDebugData = { + highlighted: false, + }; + } + } + + /** + * Set the read only mode. + * + * Quick access to the instance setReadOnly method. + * + * @param {bool} value: the new read only value + */ + set readOnly(value) { + this.instance.stateManager.setReadOnly(value); + } + + /** + * Get the read only value + * + * @return {bool} + */ + get readOnly() { + return this.instance.stateManager.readonly; + } + + /** + * Return the current state object. + * + * @return {object} + */ + get state() { + return this.instance.state; + } + + /** + * Tooggle the reactive HTML element highlight registered in this reactive instance. + * + * @param {bool} value the highlight value + */ + set highlight(value) { + this.instance._reactiveDebugData.highlighted = value; + this.instance.components.forEach(({element}) => { + const border = (value) ? `thick solid #0000FF` : ''; + element.style.border = border; + }); + } + + /** + * Get the current highligh value. + * + * @return {bool} + */ + get highlight() { + return this.instance._reactiveDebugData.highlighted; + } + + /** + * List all the components registered in this instance. + * + * @return {array} + */ + get components() { + return [...this.instance.components]; + } + + /** + * List all the state changes evenet pending to dispatch. + * + * @return {array} + */ + get changes() { + const result = []; + this.instance.stateManager.eventsToPublish.forEach( + (element) => { + result.push(element.eventName); + } + ); + return result; + } + + /** + * Dispatch a change in the state. + * + * Usually reactive modules throw an error directly to the components when something + * goes wrong. However, course editor can directly display a notification. + * + * @method dispatch + * @param {string} actionName the action name (usually the mutation name) + * @param {*} param any number of params the mutation needs. + */ + async dispatch(...args) { + this.instance.dispatch(...args); + } + + /** + * Return all the HTML elements registered in the instance components. + * + * @return {array} + */ + get elements() { + const result = []; + this.instance.components.forEach(({element}) => { + result.push(element); + }); + return result; + } + + /** + * Return a plain copy of the state data. + * + * @return {object} + */ + get stateData() { + return JSON.parse(JSON.stringify(this.state)); + } + + /** + * Process an update state array. + * + * @param {array} updates an array of update state messages + */ + processUpdates(updates) { + this.instance.stateManager.processUpdates(updates); + } +} + +const stateChangedEventName = 'core_reactive_debug:stateChanged'; + +/** + * Internal state changed event. + * + * @method dispatchStateChangedEvent + * @param {object} detail the full state + * @param {object} target the custom event target (document if none provided) + */ +function dispatchStateChangedEvent(detail, target) { + if (target === undefined) { + target = document; + } + target.dispatchEvent( + new CustomEvent( + stateChangedEventName, + { + bubbles: true, + detail: detail, + } + ) + ); +} + +/** + * The main init method to initialize the reactive debug. + * @returns {object} + */ +export const initDebug = () => { + const debug = new Debug({ + name: 'CoreReactiveDebug', + eventName: stateChangedEventName, + eventDispatch: dispatchStateChangedEvent, + mutations: new Mutations(), + state: { + reactives: [], + }, + }); + + // The reactiveDebuggers will be used as a way of access the debug instances but also to register every new + // instance. To ensure this will update the reactive debug state we add the registerNewInstance method to it. + reactiveDebuggers.registerNewInstance = debug.registerNewInstance.bind(debug); + + return { + debug, + debuggers: reactiveDebuggers, + }; +}; diff --git a/lib/amd/src/local/reactive/debugpanel.js b/lib/amd/src/local/reactive/debugpanel.js new file mode 100644 index 00000000000..3aa63937408 --- /dev/null +++ b/lib/amd/src/local/reactive/debugpanel.js @@ -0,0 +1,563 @@ +// 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/>. + +/** + * Reactive module debug panel. + * + * This module contains all the UI components for the reactive debug tools. + * Those tools are only available if the debug is enables and could be used + * from the footer. + * + * @module core/local/reactive/debugpanel + * @copyright 2021 Ferran Recio <ferran@moodle.com> + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +import {BaseComponent, DragDrop, debug} from 'core/reactive'; +import log from 'core/log'; +import {debounce} from 'core/utils'; + +/** + * Init the main reactive panel. + * + * @param {element|string} target the DOM main element or its ID + * @param {object} selectors optional css selector overrides + */ +export const init = (target, selectors) => { + const element = document.getElementById(target); + // Check if the debug reactive module is available. + if (debug === undefined) { + element.remove(); + return; + } + // Create the main component. + new GlobalDebugPanel({ + element, + reactive: debug, + selectors, + }); +}; + +/** + * Init an instance reactive subpanel. + * + * @param {element|string} target the DOM main element or its ID + * @param {object} selectors optional css selector overrides + */ +export const initsubpanel = (target, selectors) => { + const element = document.getElementById(target); + // Check if the debug reactive module is available. + if (debug === undefined) { + element.remove(); + return; + } + // Create the main component. + new DebugInstanceSubpanel({ + element, + reactive: debug, + selectors, + }); +}; + +/** + * Component for the main reactive dev panel. + * + * This component shows the list of reactive instances and handle the buttons + * to open a specific instance panel. + */ +class GlobalDebugPanel extends BaseComponent { + + /** + * Constructor hook. + */ + create() { + // Optional component name for debugging. + this.name = 'GlobalDebugPanel'; + // Default query selectors. + this.selectors = { + LOADERS: `[data-for='loaders']`, + SUBPANEL: `[data-for='subpanel']`, + LOG: `[data-for='log']`, + }; + } + + /** + * Initial state ready method. + * + * @param {object} state the initial state + */ + stateReady(state) { + if (state.reactives.size > 0) { + this.getElement(this.selectors.LOADERS).innerHTML = ''; + } + // Generate loading buttons. + state.reactives.forEach( + instance => { + this._createLoader(instance); + } + ); + // Remove loading wheel. + this.getElement(this.selectors.SUBPANEL).innerHTML = ''; + } + + /** + * Create a debug panel button for a specific reactive instance. + * + * @param {object} instance hte instance data + */ + _createLoader(instance) { + const loaders = this.getElement(this.selectors.LOADERS); + const btn = document.createElement("button"); + btn.innerHTML = instance.id; + btn.dataset.id = instance.id; + loaders.appendChild(btn); + // Add click event. + this.addEventListener(btn, 'click', () => this._openPanel(btn, instance)); + } + + /** + * Open a debug panel. + * + * @param {Element} btn the button element + * @param {object} instance the instance data + */ + async _openPanel(btn, instance) { + try { + const target = this.getElement(this.selectors.SUBPANEL); + const data = {...instance}; + await this.renderComponent(target, 'core/local/reactive/debuginstancepanel', data); + } catch (error) { + log.error('Cannot load reactive debug subpanel'); + throw error; + } + } +} + +/** + * Component for the main reactive dev panel. + * + * This component shows the list of reactive instances and handle the buttons + * to open a specific instance panel. + */ +class DebugInstanceSubpanel extends BaseComponent { + + /** + * Constructor hook. + */ + create() { + // Optional component name for debugging. + this.name = 'DebugInstanceSubpanel'; + // Default query selectors. + this.selectors = { + NAME: `[data-for='name']`, + CLOSE: `[data-for='close']`, + READMODE: `[data-for='readmode']`, + HIGHLIGHT: `[data-for='highlight']`, + LOG: `[data-for='log']`, + STATE: `[data-for='state']`, + CLEAN: `[data-for='clean']`, + PIN: `[data-for='pin']`, + SAVE: `[data-for='save']`, + INVALID: `[data-for='invalid']`, + }; + this.id = this.element.dataset.id; + this.controller = M.reactive[this.id]; + + // The component is created always pinned. + this.draggable = false; + // We want the element to be dragged like modal. + this.relativeDrag = true; + // Save warning (will be loaded when state is ready. + this.strings = { + savewarning: '', + }; + } + + /** + * Initial state ready method. + * + */ + stateReady() { + // Enable drag and drop. + this.dragdrop = new DragDrop(this); + + // Close button. + this.addEventListener( + this.getElement(this.selectors.CLOSE), + 'click', + this.remove + ); + // Highlight button. + if (this.controller.highlight) { + this._toggleButtonText(this.getElement(this.selectors.HIGHLIGHT)); + } + this.addEventListener( + this.getElement(this.selectors.HIGHLIGHT), + 'click', + () => { + this.controller.highlight = !this.controller.highlight; + this._toggleButtonText(this.getElement(this.selectors.HIGHLIGHT)); + } + ); + // Edit mode button. + this.addEventListener( + this.getElement(this.selectors.READMODE), + 'click', + this._toggleEditMode + ); + // Clean log and state. + this.addEventListener( + this.getElement(this.selectors.CLEAN), + 'click', + this._cleanAreas + ); + // Unpin panel butotn. + this.addEventListener( + this.getElement(this.selectors.PIN), + 'click', + this._togglePin + ); + // Save button, state format error message and state textarea. + this.getElement(this.selectors.SAVE).disabled = true; + + this.addEventListener( + this.getElement(this.selectors.STATE), + 'keyup', + debounce(this._checkJSON, 500) + ); + + this.addEventListener( + this.getElement(this.selectors.SAVE), + 'click', + this._saveState + ); + // Save the default save warning message. + this.strings.savewarning = this.getElement(this.selectors.INVALID)?.innerHTML ?? ''; + // Add current state. + this._refreshState(); + } + + /** + * Remove all subcomponents dependencies. + */ + destroy() { + if (this.dragdrop !== undefined) { + this.dragdrop.unregister(); + } + } + + /** + * Component watchers. + * + * @returns {Array} of watchers + */ + getWatchers() { + return [ + {watch: `reactives[${this.id}].lastChanges:updated`, handler: this._refreshLog}, + {watch: `reactives[${this.id}].modified:updated`, handler: this._refreshState}, + {watch: `reactives[${this.id}].readOnly:updated`, handler: this._refreshReadOnly}, + ]; + } + + /** + * Wtacher method to refresh the log panel. + * + * @param {object} detail of the change + */ + _refreshLog({element}) { + const list = element?.lastChanges ?? []; + + const logContent = list.join("\n"); + // Append last log. + const target = this.getElement(this.selectors.LOG); + target.value += `\n\n= Transaction =\n ${logContent}`; + target.scrollTop = target.scrollHeight; + } + + /** + * Listener method to clean the log area. + */ + _cleanAreas() { + let target = this.getElement(this.selectors.LOG); + target.value = ''; + + this._refreshState(); + } + + /** + * Watcher to refresh the state information. + */ + _refreshState() { + const target = this.getElement(this.selectors.STATE); + target.value = JSON.stringify(this.controller.state, null, 4); + } + + /** + * Watcher to update the read only information. + */ + _refreshReadOnly() { + // Toggle the read mode button. + const target = this.getElement(this.selectors.READMODE); + if (target.dataset.readonly === undefined) { + target.dataset.readonly = target.innerHTML; + } + if (this.controller.readOnly) { + target.innerHTML = target.dataset.readonly; + } else { + target.innerHTML = target.dataset.alt; + } + } + + /** + * Listener to toggle the edit mode of the component. + */ + _toggleEditMode() { + this.controller.readOnly = !this.controller.readOnly; + } + + /** + * Check that the edited state JSON is valid. + * + * Not all valid JSON are suitable for transforming the state. For example, + * the first level attributes cannot change the type. + * + * @return {undefined|array} Array of state updates. + */ + _checkJSON() { + const invalid = this.getElement(this.selectors.INVALID); + const save = this.getElement(this.selectors.SAVE); + + const edited = this.getElement(this.selectors.STATE).value; + + const currentStateData = this.controller.stateData; + + // Check if the json is tha same as state. + if (edited == JSON.stringify(this.controller.state, null, 4)) { + invalid.style.color = ''; + invalid.innerHTML = ''; + save.disabled = true; + return undefined; + } + + // Check if the json format is valid. + try { + const newState = JSON.parse(edited); + // Check the first level did not change types. + const result = this._generateStateUpdates(currentStateData, newState); + // Enable save button. + invalid.style.color = ''; + invalid.innerHTML = this.strings.savewarning; + save.disabled = false; + return result; + } catch (error) { + invalid.style.color = 'red'; + invalid.innerHTML = error.message ?? 'Invalid JSON sctructure'; + save.disabled = true; + return undefined; + } + } + + /** + * Listener to save the current edited state into the real state. + */ + _saveState() { + const updates = this._checkJSON(); + if (!updates) { + return; + } + // Sent the updates to the state manager. + this.controller.processUpdates(updates); + } + + /** + * Check that the edited state JSON is valid. + * + * Not all valid JSON are suitable for transforming the state. For example, + * the first level attributes cannot change the type. This method do a two + * steps comparison between the current state data and the new state data. + * + * A reactive state cannot be overridden like any other variable. To keep + * the watchers updated is necessary to transform the current state into + * the new one. As a result, this method generates all the necessary state + * updates to convert the state into the new state. + * + * @param {object} currentStateData + * @param {object} newStateData + * @return {array} Array of state updates. + * @throws {Error} is the structure is not compatible + */ + _generateStateUpdates(currentStateData, newStateData) { + + const updates = []; + + const ids = {}; + + // Step 1: Add all overrides newStateData. + for (const [key, newValue] of Object.entries(newStateData)) { + // Check is it is new. + if (Array.isArray(newValue)) { + ids[key] = {}; + newValue.forEach(element => { + if (element.id === undefined) { + throw Error(`Array ${key} element without id attribute`); + } + updates.push({ + name: key, + action: 'override', + fields: element, + }); + const index = String(element.id).valueOf(); + ids[key][index] = true; + }); + } else { + updates.push({ + name: key, + action: 'override', + fields: newValue, + }); + } + } + // Step 2: delete unnecesary data from currentStateData. + for (const [key, oldValue] of Object.entries(currentStateData)) { + let deleteField = false; + // Check if the attribute is still there. + if (newStateData[key] === undefined) { + deleteField = true; + } + if (Array.isArray(oldValue)) { + if (!deleteField && ids[key] === undefined) { + throw Error(`Array ${key} cannot change to object.`); + } + oldValue.forEach(element => { + const index = String(element.id).valueOf(); + let deleteEntry = deleteField; + // Check if the id is there. + if (!deleteEntry && ids[key][index] === undefined) { + deleteEntry = true; + } + if (deleteEntry) { + updates.push({ + name: key, + action: 'delete', + fields: element, + }); + } + }); + } else { + if (!deleteField && ids[key] !== undefined) { + throw Error(`Object ${key} cannot change to array.`); + } + if (deleteField) { + updates.push({ + name: key, + action: 'delete', + fields: oldValue, + }); + } + } + } + // Delete all elements without action. + return updates; + } + + // Drag and drop methods. + + /** + * Get the draggable data of this component. + * + * @returns {Object} exported course module drop data + */ + getDraggableData() { + return this.draggable; + } + + /** + * The element drop end hook. + * + * @param {Object} dropdata the dropdata + * @param {Event} event the dropdata + */ + dragEnd(dropdata, event) { + this.element.style.top = `${event.newFixedTop}px`; + this.element.style.left = `${event.newFixedLeft}px`; + } + + /** + * Pin and unpin the panel. + */ + _togglePin() { + this.draggable = !this.draggable; + this.dragdrop.setDraggable(this.draggable); + if (this.draggable) { + this._unpin(); + } else { + this._pin(); + } + } + + /** + * Unpin the panel form the footer. + */ + _unpin() { + // Find the initial spot. + const pageCenterY = window.innerHeight / 2; + const pageCenterX = window.innerWidth / 2; + // Put the element in the middle of the screen + const style = { + position: 'fixed', + resize: 'both', + overflow: 'auto', + height: '400px', + width: '400px', + top: `${pageCenterY - 200}px`, + left: `${pageCenterX - 200}px`, + }; + Object.assign(this.element.style, style); + // Small also the text areas. + this.getElement(this.selectors.STATE).style.height = '50px'; + this.getElement(this.selectors.LOG).style.height = '50px'; + + this._toggleButtonText(this.getElement(this.selectors.PIN)); + } + + /** + * Pin the panel into the footer. + */ + _pin() { + const props = [ + 'position', + 'resize', + 'overflow', + 'top', + 'left', + 'height', + 'width', + ]; + props.forEach( + prop => this.element.style.removeProperty(prop) + ); + this._toggleButtonText(this.getElement(this.selectors.PIN)); + } + + /** + * Toogle the button text with the data-alt value. + * + * @param {Element} element the button element + */ + _toggleButtonText(element) { + [element.innerHTML, element.dataset.alt] = [element.dataset.alt, element.innerHTML]; + } + +} diff --git a/lib/amd/src/local/reactive/reactive.js b/lib/amd/src/local/reactive/reactive.js index f772c724b4f..119f2fef98b 100644 --- a/lib/amd/src/local/reactive/reactive.js +++ b/lib/amd/src/local/reactive/reactive.js @@ -109,6 +109,11 @@ export default class { if (description.state !== undefined) { this.setInitialState(description.state); } + + // Check if we have a debug instance to register the instance. + if (M.reactive !== undefined) { + M.reactive.registerNewInstance(this); + } } /** @@ -302,6 +307,12 @@ export default class { this.watchers.set(component, listeners); this.components.add(component); + // Dispatch an event to communicate the registration to the debug module. + this.target.dispatchEvent(new CustomEvent('registerComponent:success', { + bubbles: false, + detail: {component}, + })); + dispatchSuccess(); return component; } diff --git a/lib/amd/src/local/reactive/statemanager.js b/lib/amd/src/local/reactive/statemanager.js index 8f22e0371ef..b6a66001318 100644 --- a/lib/amd/src/local/reactive/statemanager.js +++ b/lib/amd/src/local/reactive/statemanager.js @@ -180,10 +180,20 @@ export default class StateManager { this.readonly = readonly; + let mode = 'off'; + // When the state is in readonly again is time to publish all pending events. if (this.readonly) { + mode = 'on'; this._publishEvents(); } + + // Dispatch a read only event. + this.dispatchEvent({ + action: `readmode:${mode}`, + state: this.state, + element: null, + }, this.target); } /** @@ -509,6 +519,7 @@ export default class StateManager { action: 'transaction:start', state: this.state, element: null, + changes: fieldChanges, }, this.target); // State changes can be registered in any order. However it will avoid many diff --git a/lib/amd/src/reactive.js b/lib/amd/src/reactive.js index 317f3ca7601..1a562ce8cc4 100644 --- a/lib/amd/src/reactive.js +++ b/lib/amd/src/reactive.js @@ -24,5 +24,14 @@ import BaseComponent from 'core/local/reactive/basecomponent'; import Reactive from 'core/local/reactive/reactive'; import DragDrop from 'core/local/reactive/dragdrop'; +import {initDebug} from 'core/local/reactive/debug'; -export {Reactive, BaseComponent, DragDrop}; +// Register a debug module if we are in debug mode. +let debug; +if (M.cfg.developerdebug && M.reactive === undefined) { + const debugOBject = initDebug(); + M.reactive = debugOBject.debuggers; + debug = debugOBject.debug; +} + +export {Reactive, BaseComponent, DragDrop, debug}; diff --git a/lib/outputrenderers.php b/lib/outputrenderers.php index 5d49e947d1c..8c23b0136f7 100644 --- a/lib/outputrenderers.php +++ b/lib/outputrenderers.php @@ -873,6 +873,7 @@ class core_renderer extends renderer_base { $this->page->debug_summary()) . '</div>'; } if (debugging(null, DEBUG_DEVELOPER) and has_capability('moodle/site:config', context_system::instance())) { // Only in developer mode + // Add link to profiling report if necessary if (function_exists('profiling_is_running') && profiling_is_running()) { $txt = get_string('profiledscript', 'admin'); @@ -885,6 +886,9 @@ class core_renderer extends renderer_base { 'sesskey' => sesskey(), 'returnurl' => $this->page->url->out_as_local_url(false))); $output .= '<div class="purgecaches">' . html_writer::link($purgeurl, get_string('purgecaches', 'admin')) . '</div>'; + + // Reactive module debug panel. + $output .= $this->render_from_template('core/local/reactive/debugpanel', []); } if (!empty($CFG->debugvalidators)) { // NOTE: this is not a nice hack, $this->page->url is not always accurate and diff --git a/lib/templates/local/reactive/debuginstancepanel.mustache b/lib/templates/local/reactive/debuginstancepanel.mustache new file mode 100644 index 00000000000..761099a7f9f --- /dev/null +++ b/lib/templates/local/reactive/debuginstancepanel.mustache @@ -0,0 +1,86 @@ +{{! + 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/reactive/debuginstancepanel + + Template to render the global reactive debug panel. + + Classes required for JS: + * none + + Data attributes required for JS: + * none + + Example context (json): + { + "id": "instanceid" + } +}} +<div id="{{uniqid}}-reactive-debugpanel-instance" data-id="{{id}}" class="card text-dark"> + <div class="card-body"> + <h5 class="card-title" data-for="name"> + {{id}} + <button href="#" class="btn btn-secondary" data-for="pin" data-alt="{{#str}} reactive_pin , core_debug {{/str}}"> + {{#str}} reactive_unpin , core_debug {{/str}} + </button> + <button href="#" class="btn btn-danger float-right" data-for="close"> + {{#str}} close , admin {{/str}} + </button> + </h5> + <p class="card-text"> + <button + class="btn btn-secondary" + data-for="highlight" + data-alt="{{#str}} reactive_highlighton , core_debug {{/str}}" + > + {{#str}} reactive_highlightoff , core_debug {{/str}} + </button> + <button + class="btn btn-secondary" + data-for="readmode" + data-alt="{{#str}} reactive_readmodeoff , core_debug {{/str}}" + > + {{#str}} reactive_readmodeon , core_debug {{/str}} + </button> + <button + class="btn btn-secondary" + data-for="clean" + > + {{#str}} reactive_resetpanel , core_debug {{/str}} + </button> + </p> + <div> + <h6> + {{#str}} reactive_statedata , core_debug {{/str}} + <button class="btn btn-secondary" data-for="save" disabled> + {{#str}} save , admin {{/str}} + </button> + <span data-for="invalid">{{#str}} reactive_saveingwarning , core_debug {{/str}}</span> + </h6> + <textarea class="w-100" style="resize:vertical; height: 20em;" data-for="state" draggable="false"></textarea> + </div> + <div> + <h6>State events log</h6> + <textarea class="w-100" style="resize:vertical; height: 20em;" data-for="log" draggable="false"></textarea> + </div> + </div> +</div> +{{#js}} +require(['core/local/reactive/debugpanel'], function(component) { + component.initsubpanel('{{uniqid}}-reactive-debugpanel-instance'); +}); +{{/js}} diff --git a/lib/templates/local/reactive/debugpanel.mustache b/lib/templates/local/reactive/debugpanel.mustache new file mode 100644 index 00000000000..2b015f1fec8 --- /dev/null +++ b/lib/templates/local/reactive/debugpanel.mustache @@ -0,0 +1,45 @@ +{{! + 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/reactive/debugpanel + + Template to render the global reactive debug panel. + + Classes required for JS: + * none + + Data attributes required for JS: + * none + + Example context (json): + { + } +}} +<div id="{{uniqid}}-reactive-debugpanel" class="py-1"> + <div> + {{#str}} reactive_instances , core_debug {{/str}} + <span data-for="loaders">{{#str}} reactive_noinstances , core_debug {{/str}}</span> + </div> + <div data-for="subpanel"> + {{> core/loading }} + </div> +</div> +{{#js}} +require(['core/local/reactive/debugpanel'], function(component) { + component.init('{{uniqid}}-reactive-debugpanel'); +}); +{{/js}}