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});