1/**
2 * Replay plugin. To record steps in the Editor, click on Extras, Record.
3 * To stop recording click Extras, Record again. Enter the delay between
4 * the steps and use the URL that opens in the new window.
5 */
6Draw.loadPlugin(function(ui) {
7
8	var graph = ui.editor.graph;
9	var model = graph.model;
10
11	function decodeChanges(delta, direct)
12	{
13		var codec2 = new mxCodec(delta.ownerDocument);
14		codec2.lookup = function(id)
15		{
16			return model.getCell(id);
17		};
18
19		var changeNode = (direct) ? delta.firstChild : delta.firstChild.firstChild;
20		var changes = [];
21
22		while (changeNode != null)
23		{
24			var change = codec2.decode(changeNode);
25
26			change.model = model;
27			change.execute();
28			changes.push(change);
29
30			changeNode = changeNode.nextSibling;
31		}
32
33		return changes;
34	};
35
36	function createUndoableEdit(changes)
37	{
38		var edit = new mxUndoableEdit(model);
39		edit.changes = changes;
40
41		edit.notify = function()
42		{
43			// LATER: Remove changes property (deprecated)
44			edit.source.fireEvent(new mxEventObject(mxEvent.CHANGE,
45				'edit', edit, 'changes', edit.changes));
46			edit.source.fireEvent(new mxEventObject(mxEvent.NOTIFY,
47				'edit', edit, 'changes', edit.changes));
48		};
49
50		return edit;
51	};
52
53	function processDelta(delta, direct)
54	{
55		var changes = decodeChanges(delta, direct);
56
57		if (changes.length > 0)
58		{
59			var edit = createUndoableEdit(changes);
60
61			if (ui.chromelessResize)
62			{
63				// No notify event here to avoid the edit from being encoded and transmitted
64				// LATER: Remove changes property (deprecated)
65				model.fireEvent(new mxEventObject(mxEvent.CHANGE,
66					'edit', edit, 'changes', changes));
67				model.fireEvent(new mxEventObject(mxEvent.UNDO, 'edit', edit));
68				ui.chromelessResize();
69			}
70			else
71			{
72				edit.notify();
73			}
74		}
75
76		return edit;
77	};
78
79	if (ui.editor.isChromelessView())
80	{
81		var replayData = urlParams['replay-data'];
82		var delay = parseInt(urlParams['delay-delay'] || 1000);
83
84		if (replayData != null)
85		{
86			var xmlDoc = mxUtils.parseXml(Graph.decompress(replayData));
87			// LATER: Avoid duplicate parsing
88			ui.fileLoaded(new LocalFile(ui, mxUtils.getXml(xmlDoc.documentElement.firstChild.firstChild)));
89
90			// Process deltas
91			var delta = xmlDoc.documentElement.firstChild.nextSibling;
92
93			function nextStep()
94			{
95				if (delta != null)
96				{
97					window.setTimeout(function()
98					{
99						processDelta(delta);
100						delta = delta.nextSibling;
101						nextStep();
102					}, delay);
103				}
104			};
105
106			nextStep();
107		}
108	}
109	else
110	{
111		var tape = null;
112		var codec = new mxCodec();
113
114		codec.lookup = function(id)
115		{
116			return model.getCell(id);
117		};
118
119		model.addListener(mxEvent.CHANGE, function(sender, evt)
120	    {
121	    	if (tape != null)
122	    	{
123		    	var changes = evt.getProperty('changes');
124		    	var node = codec.encode(changes);
125		    	var delta = codec.document.createElement('delta');
126		    	delta.appendChild(node);
127		    	tape.push(mxUtils.getXml(delta));
128	    	}
129	    });
130
131		mxResources.parse('record=Record');
132		mxResources.parse('replay=Replay');
133
134	    // Adds actions
135	    var action = ui.actions.addAction('record...', function()
136	    {
137	    	if (tape == null)
138	    	{
139	    		var node = codec.encode(model);
140		    	var state = codec.document.createElement('state');
141		    	state.appendChild(node);
142		    	tape =[mxUtils.getXml(state)];
143		    	ui.editor.setStatus('Recording started');
144	    	}
145	    	else if (tape != null)
146	    	{
147	    		ui.editor.setStatus('Recording stopped');
148	    		var tmp = tape;
149	    		tape = null;
150
151				var dlg = new FilenameDialog(ui, 1000, mxResources.get('apply'), function(newValue)
152				{
153					if (newValue != null)
154					{
155						var dlg = new EmbedDialog(ui, 'https://www.draw.io/?p=replay&lightbox=1&replay-delay=' +
156							parseFloat(newValue) + '&replay-data=' + Graph.compress('<recording>' +
157							tmp.join('') + '</recording>'));
158						ui.showDialog(dlg.container, 450, 240, true, true);
159						dlg.init();
160					}
161				}, 'Delay');
162				ui.showDialog(dlg.container, 300, 80, true, true);
163				dlg.init();
164	    	}
165
166	    	action.label = (tape != null) ? 'Stop recording' : mxResources.get('record') + '...';
167	    });
168
169	    ui.actions.addAction('replay...', function()
170	    {
171	    	var dlg = new TextareaDialog(ui, 'Changes [JSON export, compressed edits or <edit>..</edit>]:', '',
172	    		function(newValue)
173			{
174				if (newValue.length > 0)
175				{
176					try
177					{
178						var current = null;
179
180						if (newValue.charAt(0) == '{')
181						{
182							var temp = JSON.parse(newValue);
183							current = temp.current;
184							newValue = temp.edits;
185						}
186
187						if (newValue.charAt(0) != '<')
188						{
189							newValue = Graph.decompress(newValue);
190						}
191
192						if (newValue.charAt(0) == '[')
193						{
194							newValue = JSON.parse(newValue);
195							console.log(JSON.stringify(newValue, null, 2));
196							var pageId = null;
197							var temp = [];
198
199							for (var i = 0; i < newValue.length; i++)
200							{
201								if (pageId == null)
202								{
203									pageId = newValue[i].pageid;
204								}
205
206								if (pageId == newValue[i].pageid)
207								{
208									temp.push(newValue[i].data);
209								}
210								else
211								{
212									mxLog.debug('edit ignored for page ' + newValue[i].pageid);
213									mxLog.show();
214								}
215							}
216
217							newValue = temp.join('');
218						}
219
220						var edits = mxUtils.parseXml('<edits>' + newValue + '</edits>');
221						var edit = edits.documentElement.firstChild;
222
223						function step()
224						{
225							console.log(processDelta(edit, true));
226							edit = edit.nextSibling;
227
228							return edit != null;
229						}
230
231						if (ui.buttonContainer != null)
232						{
233							console.log(mxUtils.getPrettyXml(edit));
234
235							var button = mxUtils.button('Step', function()
236							{
237								if (!step())
238								{
239									button.parentNode.removeChild(button);
240								}
241								else
242								{
243									console.log(mxUtils.getPrettyXml(edit));
244								}
245							});
246
247							button.className = 'geBtn gePrimaryBtn';
248
249							ui.buttonContainer.appendChild(button);
250						}
251						else
252						{
253							while (step())
254							{
255								// repeat
256							}
257						}
258					}
259					catch (e)
260					{
261						ui.handleError(e);
262						console.error(e);
263					}
264				}
265			});
266
267	    	dlg.textarea.style.width = '600px';
268	    	dlg.textarea.style.height = '380px';
269			ui.showDialog(dlg.container, 620, 460, true, true);
270			dlg.init();
271	    });
272
273		var menu = ui.menus.get('extras');
274		var oldFunct = menu.funct;
275
276		menu.funct = function(menu, parent)
277		{
278			oldFunct.apply(this, arguments);
279
280			ui.menus.addMenuItems(menu, ['-', 'record', 'replay'], parent);
281		};
282	}
283});