Skip to content
This repository was archived by the owner on Jun 26, 2020. It is now read-only.

Commit eb855d9

Browse files
authored
Merge pull request #902 from ckeditor/t/828
Feature: Introduced `dev-utils.DeltaReplayer`. Introduced new logging methods in `dev-utils.enableEngineDebug`. Closes #828.
2 parents 0aec182 + dd3015b commit eb855d9

File tree

4 files changed

+433
-2
lines changed

4 files changed

+433
-2
lines changed

src/dev-utils/deltareplayer.js

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
/**
2+
* @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3+
* For licensing, see LICENSE.md.
4+
*/
5+
6+
/**
7+
* @module engine/dev-utils/deltareplayer
8+
*/
9+
10+
/* global setTimeout, console */
11+
12+
import DeltaFactory from '../model/delta/deltafactory';
13+
14+
/**
15+
* DeltaReplayer is a dev-tool created for easily replaying operations on the document from stringified deltas.
16+
*/
17+
export default class DeltaReplayer {
18+
/**
19+
* @param {module:engine/model/document~Document} document Document to reply deltas on.
20+
* @param {String} logSeparator Separator between deltas.
21+
* @param {String} stringifiedDeltas Deltas to replay.
22+
*/
23+
constructor( document, logSeparator, stringifiedDeltas ) {
24+
this._document = document;
25+
this._logSeparator = logSeparator;
26+
this.setStringifiedDeltas( stringifiedDeltas );
27+
}
28+
29+
/**
30+
* Parses given string containing stringified deltas and sets parsed deltas as deltas to reply.
31+
*
32+
* @param {String} stringifiedDeltas Stringified deltas to replay.
33+
*/
34+
setStringifiedDeltas( stringifiedDeltas ) {
35+
if ( stringifiedDeltas === '' ) {
36+
this._deltasToReplay = [];
37+
38+
return;
39+
}
40+
41+
this._deltasToReplay = stringifiedDeltas
42+
.split( this._logSeparator )
43+
.map( stringifiedDelta => JSON.parse( stringifiedDelta ) );
44+
}
45+
46+
/**
47+
* Returns deltas to reply.
48+
*
49+
* @returns {Array.<module:engine/model/delta/delta~Delta>}
50+
*/
51+
getDeltasToReplay() {
52+
return this._deltasToReplay;
53+
}
54+
55+
/**
56+
* Applies all deltas with delay between actions.
57+
*
58+
* @param {Number} timeInterval Time between applying deltas.
59+
* @returns {Promise}
60+
*/
61+
play( timeInterval = 1000 ) {
62+
const deltaReplayer = this;
63+
64+
return new Promise( ( res ) => {
65+
play();
66+
67+
function play() {
68+
if ( deltaReplayer._deltasToReplay.length === 0 ) {
69+
return res();
70+
}
71+
72+
deltaReplayer.applyNextDelta().then( () => {
73+
setTimeout( play, timeInterval );
74+
}, res );
75+
}
76+
} );
77+
}
78+
79+
/**
80+
* Applies `numberOfDeltas` deltas, beginning after the last applied delta (or first delta, if no deltas were applied).
81+
*
82+
* @param {Number} numberOfDeltas Number of deltas to apply.
83+
* @returns {Promise}
84+
*/
85+
applyDeltas( numberOfDeltas ) {
86+
if ( numberOfDeltas <= 0 ) {
87+
return;
88+
}
89+
90+
return this.applyNextDelta()
91+
.then( () => this.applyDeltas( numberOfDeltas - 1 ) )
92+
.catch( err => console.warn( err ) );
93+
}
94+
95+
/**
96+
* Applies all deltas to replay at once.
97+
*
98+
* @returns {Promise}
99+
*/
100+
applyAllDeltas() {
101+
return this.applyNextDelta()
102+
.then( () => this.applyAllDeltas() )
103+
.catch( () => {} );
104+
}
105+
106+
/**
107+
* Applies the next delta to replay.
108+
*
109+
* @returns {Promise}
110+
*/
111+
applyNextDelta() {
112+
const document = this._document;
113+
114+
return new Promise( ( res, rej ) => {
115+
document.enqueueChanges( () => {
116+
const jsonDelta = this._deltasToReplay.shift();
117+
118+
if ( !jsonDelta ) {
119+
return rej( new Error( 'No deltas to replay' ) );
120+
}
121+
122+
const delta = DeltaFactory.fromJSON( jsonDelta, this._document );
123+
124+
const batch = document.batch();
125+
batch.addDelta( delta );
126+
127+
for ( const operation of delta.operations ) {
128+
document.applyOperation( operation );
129+
}
130+
131+
res();
132+
} );
133+
} );
134+
}
135+
}

src/dev-utils/enableenginedebug.js

Lines changed: 47 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,11 +46,16 @@ import ViewDocumentFragment from '../view/documentfragment';
4646
import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
4747
import Editor from '@ckeditor/ckeditor5-core/src/editor/editor';
4848

49+
import DeltaReplayer from './deltareplayer';
50+
4951
const treeDump = Symbol( '_treeDump' );
5052

5153
// Maximum number of stored states of model and view document.
5254
const maxTreeDumpLength = 20;
5355

56+
// Separator used to separate stringified deltas
57+
const LOG_SEPARATOR = '\n----------------\n';
58+
5459
// Specified whether debug tools were already enabled.
5560
let enabled = false;
5661

@@ -96,14 +101,17 @@ let log = console.log;
96101
* @param {Function} [logger] Function used to log messages. By default messages are logged to console.
97102
* @returns {module:engine/dev-utils/enableenginedebug~DebugPlugin} Plugin to be loaded in the editor.
98103
*/
99-
export default function enableEngineDebug( logger = console.log ) {
100-
log = logger;
104+
export default function enableEngineDebug( logger ) {
105+
if ( logger ) {
106+
log = logger;
107+
}
101108

102109
if ( !enabled ) {
103110
enabled = true;
104111

105112
enableLoggingTools();
106113
enableDocumentTools();
114+
enableReplayerTools();
107115
}
108116

109117
return DebugPlugin;
@@ -448,12 +456,49 @@ function enableLoggingTools() {
448456
};
449457
}
450458

459+
function enableReplayerTools() {
460+
const _modelDocumentApplyOperation = ModelDocument.prototype.applyOperation;
461+
462+
ModelDocument.prototype.applyOperation = function( operation ) {
463+
if ( !this._lastDelta ) {
464+
this._appliedDeltas = [];
465+
} else if ( this._lastDelta !== operation.delta ) {
466+
this._appliedDeltas.push( this._lastDelta.toJSON() );
467+
}
468+
469+
this._lastDelta = operation.delta;
470+
471+
_modelDocumentApplyOperation.call( this, operation );
472+
};
473+
474+
ModelDocument.prototype.getAppliedDeltas = function() {
475+
// No deltas has been applied yet, return empty string.
476+
if ( !this._lastDelta ) {
477+
return '';
478+
}
479+
480+
const appliedDeltas = this._appliedDeltas.concat( this._lastDelta.toJSON() );
481+
482+
return appliedDeltas.map( JSON.stringify ).join( LOG_SEPARATOR );
483+
};
484+
485+
ModelDocument.prototype.createReplayer = function( stringifiedDeltas ) {
486+
return new DeltaReplayer( this, LOG_SEPARATOR, stringifiedDeltas );
487+
};
488+
}
489+
451490
function enableDocumentTools() {
452491
const _modelDocumentApplyOperation = ModelDocument.prototype.applyOperation;
453492

454493
ModelDocument.prototype.applyOperation = function( operation ) {
455494
log( 'Applying ' + operation );
456495

496+
if ( !this._operationLogs ) {
497+
this._operationLogs = [];
498+
}
499+
500+
this._operationLogs.push( JSON.stringify( operation.toJSON() ) );
501+
457502
_modelDocumentApplyOperation.call( this, operation );
458503
};
459504

0 commit comments

Comments
 (0)