Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 1
// This file is part of Moodle - http://moodle.org/
2
//
3
// Moodle is free software: you can redistribute it and/or modify
4
// it under the terms of the GNU General Public License as published by
5
// the Free Software Foundation, either version 3 of the License, or
6
// (at your option) any later version.
7
//
8
// Moodle is distributed in the hope that it will be useful,
9
// but WITHOUT ANY WARRANTY; without even the implied warranty of
10
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11
// GNU General Public License for more details.
12
//
13
// You should have received a copy of the GNU General Public License
14
// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
15
 
16
/**
17
 * Reactive module debug tools.
18
 *
19
 * @module     core/local/reactive/debug
20
 * @copyright  2021 Ferran Recio <ferran@moodle.com>
21
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
22
 */
23
 
24
import Reactive from 'core/local/reactive/reactive';
25
import log from 'core/log';
26
 
27
// The list of reactives instances.
28
const reactiveInstances = {};
29
 
30
// The reactive debugging objects.
31
const reactiveDebuggers = {};
32
 
33
/**
34
 * Reactive module debug tools.
35
 *
36
 * If debug is enabled, this reactive module will spy all the reactive instances and keep a record
37
 * of the changes and components they have.
38
 *
39
 * It is important to note that the Debug class is also a Reactive module. The debug instance keeps
40
 * the reactive instances data as its own state. This way it is possible to implement development tools
41
 * that whatches this data.
42
 *
43
 * @class      core/reactive/local/reactive/debug/Debug
44
 * @copyright  2021 Ferran Recio <ferran@moodle.com>
45
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
46
 */
47
class Debug extends Reactive {
48
 
49
    /**
50
     * Set the initial state.
51
     *
52
     * @param {object} stateData the initial state data.
53
     */
54
    setInitialState(stateData) {
55
        super.setInitialState(stateData);
56
        log.debug(`Debug module "M.reactive" loaded.`);
57
    }
58
 
59
    /**
60
     * List the currents page reactives instances.
61
     */
62
    get list() {
63
        return JSON.parse(JSON.stringify(this.state.reactives));
64
    }
65
 
66
    /**
67
     * Register a new Reactive instance.
68
     *
69
     * This method is called every time a "new Reactive" is executed.
70
     *
71
     * @param {Reactive} instance the reactive instance
72
     */
73
    registerNewInstance(instance) {
74
 
75
        // Generate a valid variable name for that instance.
76
        let name = instance.name ?? `instance${this.state.reactives.length}`;
77
        name = name.replace(/\W/g, '');
78
 
79
        log.debug(`Registering new reactive instance "M.reactive.${name}"`);
80
 
81
        reactiveInstances[name] = instance;
82
        reactiveDebuggers[name] = new DebugInstance(reactiveInstances[name]);
83
        // Register also in the state.
84
        this.dispatch('putInstance', name, instance);
85
        // Add debug watchers to instance.
86
        const refreshMethod = () => {
87
            this.dispatch('putInstance', name, instance);
88
        };
89
        instance.target.addEventListener('readmode:on', refreshMethod);
90
        instance.target.addEventListener('readmode:off', refreshMethod);
91
        instance.target.addEventListener('registerComponent:success', refreshMethod);
92
        instance.target.addEventListener('transaction:end', refreshMethod);
93
        // We store the last transaction into the state.
94
        const storeTransaction = ({detail}) => {
95
            const changes = detail?.changes;
96
            this.dispatch('lastTransaction', name, changes);
97
        };
98
        instance.target.addEventListener('transaction:start', storeTransaction);
99
    }
100
 
101
    /**
102
     * Returns a debugging object for a specific Reactive instance.
103
     *
104
     * A debugging object is a class that wraps a Reactive instance to quick access some of the
105
     * reactive methods using the browser JS console.
106
     *
107
     * @param {string} name the Reactive instance name
108
     * @returns {DebugInstance} a debug object wrapping the Reactive instance
109
     */
110
    debug(name) {
111
        return reactiveDebuggers[name];
112
    }
113
}
114
 
115
/**
116
 * The debug state mutations class.
117
 *
118
 * @class core/reactive/local/reactive/debug/Mutations
119
 */
120
class Mutations {
121
 
122
    /**
123
     * Insert or update a new instance into the debug state.
124
     *
125
     * @param {StateManager} stateManager the debug state manager
126
     * @param {string} name the instance name
127
     * @param {Reactive} instance the reactive instance
128
     */
129
    putInstance(stateManager, name, instance) {
130
        const state = stateManager.state;
131
 
132
        stateManager.setReadOnly(false);
133
 
134
        if (state.reactives.has(name)) {
135
            state.reactives.get(name).countcomponents = instance.components.length;
136
            state.reactives.get(name).readOnly = instance.stateManager.readonly;
137
            state.reactives.get(name).modified = new Date().getTime();
138
        } else {
139
            state.reactives.add({
140
                id: name,
141
                countcomponents: instance.components.length,
142
                readOnly: instance.stateManager.readonly,
143
                lastChanges: [],
144
                modified: new Date().getTime(),
145
            });
146
        }
147
        stateManager.setReadOnly(true);
148
    }
149
 
150
    /**
151
     * Update the lastChanges attribute with a list of changes
152
     *
153
     * @param {StateManager} stateManager the debug reactive state
154
     * @param {string} name tje instance name
155
     * @param {array} changes the list of changes
156
     */
157
    lastTransaction(stateManager, name, changes) {
158
        if (!changes || changes.length === 0) {
159
            return;
160
        }
161
 
162
        const state = stateManager.state;
163
        const lastChanges = ['transaction:start'];
164
 
165
        changes.forEach(change => {
166
            lastChanges.push(change.eventName);
167
        });
168
 
169
        lastChanges.push('transaction:end');
170
 
171
        stateManager.setReadOnly(false);
172
 
173
        state.reactives.get(name).lastChanges = lastChanges;
174
 
175
        stateManager.setReadOnly(true);
176
    }
177
}
178
 
179
/**
180
 * Class used to debug a specific instance and manipulate the state from the JS console.
181
 *
182
 * @class      core/reactive/local/reactive/debug/DebugInstance
183
 * @copyright  2021 Ferran Recio <ferran@moodle.com>
184
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
185
 */
186
class DebugInstance {
187
 
188
    /**
189
     * Constructor.
190
     *
191
     * @param {Reactive} instance the reactive instance
192
     */
193
    constructor(instance) {
194
        this.instance = instance;
195
        // Add some debug data directly into the instance. This way we avoid having attributes
196
        // that will confuse the console aoutocomplete.
197
        if (instance._reactiveDebugData === undefined) {
198
            instance._reactiveDebugData = {
199
                highlighted: false,
200
            };
201
        }
202
    }
203
 
204
    /**
205
     * Set the read only mode.
206
     *
207
     * Quick access to the instance setReadOnly method.
208
     *
209
     * @param {bool} value the new read only value
210
     */
211
    set readOnly(value) {
212
        this.instance.stateManager.setReadOnly(value);
213
    }
214
 
215
    /**
216
     * Get the read only value
217
     *
218
     * @returns {bool}
219
     */
220
    get readOnly() {
221
        return this.instance.stateManager.readonly;
222
    }
223
 
224
    /**
225
     * Return the current state object.
226
     *
227
     * @returns {object}
228
     */
229
    get state() {
230
        return this.instance.state;
231
    }
232
 
233
    /**
234
     * Tooggle the reactive HTML element highlight registered in this reactive instance.
235
     *
236
     * @param {bool} value the highlight value
237
     */
238
    set highlight(value) {
239
        this.instance._reactiveDebugData.highlighted = value;
240
        this.instance.components.forEach(({element}) => {
241
            const border = (value) ? `thick solid #0000FF` : '';
242
            element.style.border = border;
243
        });
244
    }
245
 
246
    /**
247
     * Get the current highligh value.
248
     *
249
     * @returns {bool}
250
     */
251
    get highlight() {
252
        return this.instance._reactiveDebugData.highlighted;
253
    }
254
 
255
    /**
256
     * List all the components registered in this instance.
257
     *
258
     * @returns {array}
259
     */
260
    get components() {
261
        return [...this.instance.components];
262
    }
263
 
264
    /**
265
     * List all the state changes evenet pending to dispatch.
266
     *
267
     * @returns {array}
268
     */
269
    get changes() {
270
        const result = [];
271
        this.instance.stateManager.eventsToPublish.forEach(
272
            (element) => {
273
                result.push(element.eventName);
274
            }
275
        );
276
        return result;
277
    }
278
 
279
    /**
280
     * Dispatch a change in the state.
281
     *
282
     * Usually reactive modules throw an error directly to the components when something
283
     * goes wrong. However, course editor can directly display a notification.
284
     *
285
     * @method dispatch
286
     * @param {*} args
287
     */
288
    async dispatch(...args) {
289
        this.instance.dispatch(...args);
290
    }
291
 
292
    /**
293
     * Return all the HTML elements registered in the instance components.
294
     *
295
     * @returns {array}
296
     */
297
    get elements() {
298
        const result = [];
299
        this.instance.components.forEach(({element}) => {
300
            result.push(element);
301
        });
302
        return result;
303
    }
304
 
305
    /**
306
     * Return a plain copy of the state data.
307
     *
308
     * @returns {object}
309
     */
310
    get stateData() {
311
        return JSON.parse(JSON.stringify(this.state));
312
    }
313
 
314
    /**
315
     * Process an update state array.
316
     *
317
     * @param {array} updates an array of update state messages
318
     */
319
    processUpdates(updates) {
320
        this.instance.stateManager.processUpdates(updates);
321
    }
322
}
323
 
324
const stateChangedEventName = 'core_reactive_debug:stateChanged';
325
 
326
/**
327
 * Internal state changed event.
328
 *
329
 * @method dispatchStateChangedEvent
330
 * @param {object} detail the full state
331
 * @param {object} target the custom event target (document if none provided)
332
 */
333
function dispatchStateChangedEvent(detail, target) {
334
    if (target === undefined) {
335
        target = document;
336
    }
337
    target.dispatchEvent(
338
        new CustomEvent(
339
            stateChangedEventName,
340
            {
341
                bubbles: true,
342
                detail: detail,
343
            }
344
        )
345
    );
346
}
347
 
348
/**
349
 * The main init method to initialize the reactive debug.
350
 * @returns {object}
351
 */
352
export const initDebug = () => {
353
    const debug = new Debug({
354
        name: 'CoreReactiveDebug',
355
        eventName: stateChangedEventName,
356
        eventDispatch: dispatchStateChangedEvent,
357
        mutations: new Mutations(),
358
        state: {
359
            reactives: [],
360
        },
361
    });
362
 
363
    // The reactiveDebuggers will be used as a way of access the debug instances but also to register every new
364
    // instance. To ensure this will update the reactive debug state we add the registerNewInstance method to it.
365
    reactiveDebuggers.registerNewInstance = debug.registerNewInstance.bind(debug);
366
 
367
    return {
368
        debug,
369
        debuggers: reactiveDebuggers,
370
    };
371
};