1/**
2 * Copyright (c) 2006-2020, JGraph Ltd
3 * Copyright (c) 2006-2020, draw.io AG
4 */
5
6var StorageDialog = function(editorUi, fn, rowLimit)
7{
8	rowLimit = (rowLimit != null) ? rowLimit : 2;
9
10	var div = document.createElement('div');
11	div.style.textAlign = 'center';
12	div.style.whiteSpace = 'nowrap';
13	div.style.paddingTop = '0px';
14	div.style.paddingBottom = '20px';
15
16	var buttons = document.createElement('div');
17	buttons.style.border = '1px solid #d3d3d3';
18	buttons.style.borderWidth = '1px 0px 1px 0px';
19	buttons.style.padding = '10px 0px 20px 0px';
20
21	var count = 0, totalBtns = 0;
22	var container = document.createElement('div');
23	container.style.paddingTop = '2px';
24	buttons.appendChild(container);
25
26	var p3 = document.createElement('p');
27
28	function addLogo(img, title, mode, clientName, labels, clientFn)
29	{
30		totalBtns++;
31
32		if (++count > rowLimit)
33		{
34			mxUtils.br(container);
35			count = 1;
36		}
37
38		var button = document.createElement('a');
39		button.style.overflow = 'hidden';
40		button.style.display = 'inline-block';
41		button.className = 'geBaseButton';
42		button.style.boxSizing = 'border-box';
43		button.style.fontSize = '11px';
44		button.style.position = 'relative';
45		button.style.margin = '4px';
46		button.style.marginTop = '8px';
47		button.style.marginBottom = '0px';
48		button.style.padding = '8px 10px 8px 10px';
49		button.style.width = '88px';
50		button.style.height = '100px';
51		button.style.whiteSpace = 'nowrap';
52		button.setAttribute('title', title);
53
54		var label = document.createElement('div');
55		label.style.textOverflow = 'ellipsis';
56		label.style.overflow = 'hidden';
57		label.style.position = 'absolute';
58		label.style.bottom = '8px';
59		label.style.left = '0px';
60		label.style.right = '0px';
61		mxUtils.write(label, title);
62		button.appendChild(label);
63
64		if (img != null)
65		{
66			var logo = document.createElement('img');
67			logo.setAttribute('src', img);
68			logo.setAttribute('border', '0');
69			logo.setAttribute('align', 'absmiddle');
70			logo.style.width = '60px';
71			logo.style.height = '60px';
72			logo.style.paddingBottom = '6px';
73
74			button.appendChild(logo);
75		}
76		else
77		{
78			label.style.paddingTop = '5px';
79			label.style.whiteSpace = 'normal';
80
81			// Handles special case
82			if (mxClient.IS_IOS)
83			{
84				button.style.padding = '0px 10px 20px 10px';
85				button.style.top = '6px';
86			}
87			else if (mxClient.IS_FF)
88			{
89				label.style.paddingTop = '0px';
90				label.style.marginTop = '-2px';
91			}
92		}
93
94		if (labels != null)
95		{
96			for (var i = 0; i < labels.length; i++)
97			{
98				mxUtils.br(label);
99				mxUtils.write(label, labels[i]);
100			}
101		}
102
103		function initButton()
104		{
105			mxEvent.addListener(button, 'click', (clientFn != null) ? clientFn : function()
106			{
107				// Special case: Redirect all drive users to draw.io pro
108				if (mode == App.MODE_GOOGLE && !editorUi.isDriveDomain())
109				{
110					window.location.hostname = DriveClient.prototype.newAppHostname;
111				}
112				else if (mode == App.MODE_GOOGLE && editorUi.spinner.spin(document.body, mxResources.get('authorizing')))
113				{
114					// Tries immediate authentication
115					editorUi.drive.checkToken(mxUtils.bind(this, function()
116					{
117						editorUi.spinner.stop();
118						editorUi.setMode(mode, true);
119						fn();
120					}));
121				}
122				else if (mode == App.MODE_ONEDRIVE && editorUi.spinner.spin(document.body, mxResources.get('authorizing')))
123				{
124					// Tries immediate authentication
125					editorUi.oneDrive.checkToken(mxUtils.bind(this, function()
126					{
127						editorUi.spinner.stop();
128						editorUi.setMode(mode, true);
129						fn();
130					}));
131				}
132				else
133				{
134					editorUi.setMode(mode, true);
135					fn();
136				}
137			});
138		};
139
140		// Supports lazy loading
141		if (clientName != null && editorUi[clientName] == null)
142		{
143			logo.style.visibility = 'hidden';
144			mxUtils.setOpacity(label, 10);
145			var size = 12;
146
147			var spinner = new Spinner({
148				lines: 12, // The number of lines to draw
149				length: size, // The length of each line
150				width: 5, // The line thickness
151				radius: 10, // The radius of the inner circle
152				rotate: 0, // The rotation offset
153				color: Editor.isDarkMode() ? '#c0c0c0' : '#000', // #rgb or #rrggbb
154				speed: 1.5, // Rounds per second
155				trail: 60, // Afterglow percentage
156				shadow: false, // Whether to render a shadow
157				hwaccel: false, // Whether to use hardware acceleration
158				top: '40%',
159				zIndex: 2e9 // The z-index (defaults to 2000000000)
160			});
161			spinner.spin(button);
162
163			// Timeout after 30 secs
164			var timeout = window.setTimeout(function()
165			{
166				if (editorUi[clientName] == null)
167				{
168					spinner.stop();
169					button.style.display = 'none';
170				}
171			}, 30000);
172
173			editorUi.addListener('clientLoaded', mxUtils.bind(this, function(sender, evt)
174			{
175				if (editorUi[clientName] != null && evt.getProperty('client') == editorUi[clientName])
176				{
177					window.clearTimeout(timeout);
178					mxUtils.setOpacity(label, 100);
179					logo.style.visibility = '';
180					spinner.stop();
181					initButton();
182
183					if (clientName == 'drive' && p3.parentNode != null)
184					{
185						p3.parentNode.removeChild(p3);
186					}
187				}
188			}));
189		}
190		else
191		{
192			initButton();
193		}
194
195		container.appendChild(button);
196	};
197
198	var hd = document.createElement('p');
199	hd.style.cssText = 'font-size:22px;padding:4px 0 16px 0;margin:0;color:gray;';
200	mxUtils.write(hd, mxResources.get('saveDiagramsTo') + ':');
201	div.appendChild(hd);
202
203	var addButtons = function()
204	{
205		count = 0;
206
207		if (typeof window.DriveClient === 'function')
208		{
209			addLogo(IMAGE_PATH + '/google-drive-logo.svg', mxResources.get('googleDrive'), App.MODE_GOOGLE, 'drive');
210		}
211
212		if (typeof window.OneDriveClient === 'function')
213		{
214			addLogo(IMAGE_PATH + '/onedrive-logo.svg', mxResources.get('oneDrive'), App.MODE_ONEDRIVE, 'oneDrive');
215		}
216
217		if (urlParams['noDevice'] != '1')
218		{
219			addLogo(IMAGE_PATH + '/osa_drive-harddisk.png', mxResources.get('device'), App.MODE_DEVICE);
220		}
221
222		if (isLocalStorage && (urlParams['browser'] == '1' || urlParams['offline'] == '1'))
223		{
224			addLogo(IMAGE_PATH + '/osa_database.png', mxResources.get('browser'), App.MODE_BROWSER);
225		}
226
227		if (typeof window.DropboxClient === 'function')
228		{
229			addLogo(IMAGE_PATH + '/dropbox-logo.svg', mxResources.get('dropbox'), App.MODE_DROPBOX, 'dropbox');
230		}
231
232		if (editorUi.gitHub != null)
233		{
234			addLogo(IMAGE_PATH + '/github-logo.svg', mxResources.get('github'), App.MODE_GITHUB, 'gitHub');
235		}
236
237		if (editorUi.gitLab != null)
238		{
239			addLogo(IMAGE_PATH + '/gitlab-logo.svg', mxResources.get('gitlab'), App.MODE_GITLAB, 'gitLab');
240		}
241
242		if (totalBtns < 6 && editorUi.notion != null)
243		{
244			addLogo(IMAGE_PATH + '/notion-logo.svg', mxResources.get('notion'), App.MODE_NOTION, 'notion');
245		}
246	};
247
248	div.appendChild(buttons);
249	addButtons();
250
251	var later = document.createElement('span');
252	later.style.cssText = 'position:absolute;cursor:pointer;bottom:27px;color:gray;userSelect:none;text-align:center;left:50%;';
253	mxUtils.setPrefixedStyle(later.style, 'transform', 'translate(-50%,0)');
254	mxUtils.write(later, mxResources.get('decideLater'));
255
256	mxEvent.addListener(later, 'click', function()
257	{
258		editorUi.hideDialog();
259		var prev = Editor.useLocalStorage;
260		editorUi.createFile(editorUi.defaultFilename,
261			null, null, null, null, null, null, true);
262		Editor.useLocalStorage = prev;
263	});
264
265	div.appendChild(later);
266
267	// Checks if Google Drive is missing after a 5 sec delay
268	if (mxClient.IS_SVG && isLocalStorage && urlParams['gapi'] != '0' &&
269		(document.documentMode == null || document.documentMode >= 10))
270	{
271		window.setTimeout(function()
272		{
273			if (editorUi.drive == null)
274			{
275				// To check for Disconnect plugin in chrome use mxClient.IS_GC and check for URL:
276				// chrome-extension://jeoacafpbcihiomhlakheieifhpjdfeo/scripts/vendor/jquery/jquery-2.0.3.min.map
277				p3.style.padding = '7px';
278				p3.style.fontSize = '9pt';
279				p3.style.marginTop = '-14px';
280				p3.innerHTML = '<a style="background-color:#dcdcdc;padding:6px;color:black;text-decoration:none;" ' +
281					'href="https://desk.draw.io/a/solutions/articles/16000074659" target="_blank">' +
282					'<img border="0" src="' + mxGraph.prototype.warningImage.src + '" align="absmiddle" ' +
283					'style="margin-top:-4px"> ' + mxResources.get('googleDriveMissingClickHere') + '</a>';
284				div.appendChild(p3);
285			}
286		}, 5000);
287	}
288
289	this.container = div;
290};
291
292/**
293 * Constructs a dialog for creating new files from templates.
294 */
295var SplashDialog = function(editorUi)
296{
297	var div = document.createElement('div');
298	div.style.textAlign = 'center';
299
300	if (mxClient.IS_CHROMEAPP || EditorUi.isElectronApp)
301	{
302		var elt = editorUi.addLanguageMenu(div, true);
303
304		if (elt != null)
305		{
306			elt.style.bottom = '19px';
307		}
308	}
309
310	var serviceCount = editorUi.getServiceCount();
311	var logo = document.createElement('img');
312	logo.setAttribute('border', '0');
313	logo.setAttribute('align', 'absmiddle');
314	logo.style.width = '32px';
315	logo.style.height = '32px';
316	logo.style.marginRight = '8px';
317	logo.style.marginTop = '-4px';
318
319	var buttons = document.createElement('div');
320	buttons.style.margin = '8px 0px 0px 0px';
321	buttons.style.padding = '18px 0px 24px 0px';
322
323	var service = '';
324
325	if (editorUi.mode == App.MODE_GOOGLE)
326	{
327		logo.src = IMAGE_PATH + '/google-drive-logo.svg';
328		service = mxResources.get('googleDrive');
329	}
330	else if (editorUi.mode == App.MODE_DROPBOX)
331	{
332		logo.src = IMAGE_PATH + '/dropbox-logo.svg';
333		service = mxResources.get('dropbox');
334	}
335	else if (editorUi.mode == App.MODE_ONEDRIVE)
336	{
337		logo.src = IMAGE_PATH + '/onedrive-logo.svg';
338		service = mxResources.get('oneDrive');
339	}
340	else if (editorUi.mode == App.MODE_GITHUB)
341	{
342		logo.src = IMAGE_PATH + '/github-logo.svg';
343		service = mxResources.get('github');
344	}
345	else if (editorUi.mode == App.MODE_GITLAB)
346	{
347		logo.src = IMAGE_PATH + '/gitlab-logo.svg';
348		service = mxResources.get('gitlab');
349	}
350	else if (editorUi.mode == App.MODE_NOTION)
351	{
352		logo.src = IMAGE_PATH + '/notion-logo.svg';
353		service = mxResources.get('notion');
354	}
355	else if (editorUi.mode == App.MODE_BROWSER)
356	{
357		logo.src = IMAGE_PATH + '/osa_database.png';
358		service = mxResources.get('browser');
359	}
360	else if (editorUi.mode == App.MODE_TRELLO)
361	{
362		logo.src = IMAGE_PATH + '/trello-logo.svg';
363		service = mxResources.get('trello');
364	}
365	else
366	{
367		logo.src = IMAGE_PATH + '/osa_drive-harddisk.png';
368		buttons.style.paddingBottom = '10px';
369		buttons.style.paddingTop = '30px';
370		service = mxResources.get('device');
371	}
372
373	var btn = document.createElement('button');
374	btn.className = 'geBigButton';
375	btn.style.marginBottom = '8px';
376	btn.style.fontSize = '18px';
377	btn.style.padding = '10px';
378	btn.style.width = '340px';
379
380	if (!mxClient.IS_CHROMEAPP && !EditorUi.isElectronApp)
381	{
382		buttons.style.border = '1px solid #d3d3d3';
383		buttons.style.borderWidth = '1px 0px 1px 0px';
384
385		var table = document.createElement('table');
386		var tbody = document.createElement('tbody');
387		var row = document.createElement('tr');
388		var left = document.createElement('td');
389		var right = document.createElement('td');
390		table.setAttribute('align', 'center');
391		left.appendChild(logo);
392
393		var title = document.createElement('div');
394		title.style.fontSize = '22px';
395		title.style.paddingBottom = '6px';
396		title.style.color = 'gray';
397		mxUtils.write(title, service);
398
399		right.style.textAlign = 'left';
400		right.appendChild(title);
401
402		row.appendChild(left);
403		row.appendChild(right);
404		tbody.appendChild(row);
405		table.appendChild(tbody);
406		div.appendChild(table);
407
408		var change = document.createElement('span');
409		change.style.cssText = 'position:absolute;cursor:pointer;bottom:27px;color:gray;userSelect:none;text-align:center;left:50%;';
410		mxUtils.setPrefixedStyle(change.style, 'transform', 'translate(-50%,0)');
411		mxUtils.write(change, mxResources.get('changeStorage'));
412
413		mxEvent.addListener(change, 'click', function()
414		{
415			editorUi.hideDialog(false);
416			editorUi.setMode(null);
417			editorUi.clearMode();
418			editorUi.showSplash(true);
419		});
420
421		div.appendChild(change);
422	}
423	else
424	{
425		buttons.style.padding = '42px 0px 10px 0px';
426		btn.style.marginBottom = '12px';
427	}
428
429	mxUtils.write(btn, mxResources.get('createNewDiagram'));
430
431	mxEvent.addListener(btn, 'click', function()
432	{
433		editorUi.hideDialog();
434		editorUi.actions.get('new').funct();
435	});
436
437	buttons.appendChild(btn);
438	mxUtils.br(buttons);
439
440	var btn = document.createElement('button');
441	btn.className = 'geBigButton';
442	btn.style.marginBottom = '22px';
443	btn.style.fontSize = '18px';
444	btn.style.padding = '10px';
445	btn.style.width = '340px';
446
447	mxUtils.write(btn, mxResources.get('openExistingDiagram'));
448
449	mxEvent.addListener(btn, 'click', function()
450	{
451		editorUi.actions.get('open').funct();
452	});
453
454	buttons.appendChild(btn);
455
456	var storage = 'undefined';
457
458	if (editorUi.mode == App.MODE_GOOGLE)
459	{
460		storage = mxResources.get('googleDrive');
461	}
462	else if (editorUi.mode == App.MODE_DROPBOX)
463	{
464		storage = mxResources.get('dropbox');
465	}
466	else if (editorUi.mode == App.MODE_ONEDRIVE)
467	{
468		storage = mxResources.get('oneDrive');
469	}
470	else if (editorUi.mode == App.MODE_GITHUB)
471	{
472		storage = mxResources.get('github');
473	}
474	else if (editorUi.mode == App.MODE_GITLAB)
475	{
476		storage = mxResources.get('gitlab');
477	}
478	else if (editorUi.mode == App.MODE_NOTION)
479	{
480		storage = mxResources.get('notion');
481	}
482	else if (editorUi.mode == App.MODE_TRELLO)
483	{
484		storage = mxResources.get('trello');
485	}
486	else if (editorUi.mode == App.MODE_DEVICE)
487	{
488		storage = mxResources.get('device');
489	}
490	else if (editorUi.mode == App.MODE_BROWSER)
491	{
492		storage = mxResources.get('browser');
493	}
494
495	if (!mxClient.IS_CHROMEAPP && !EditorUi.isElectronApp)
496	{
497		function addLogout(logout)
498		{
499			btn.style.marginBottom = '24px';
500
501			var link = document.createElement('a');
502			link.style.display = 'inline-block';
503			link.style.color = 'gray';
504			link.style.cursor = 'pointer';
505			link.style.marginTop = '6px';
506			mxUtils.write(link, mxResources.get('signOut'));
507
508			// Makes room after last big buttons
509			btn.style.marginBottom = '16px';
510			buttons.style.paddingBottom = '18px';
511
512			mxEvent.addListener(link, 'click', function()
513			{
514				editorUi.confirm(mxResources.get('areYouSure'), function()
515				{
516					logout();
517				});
518			});
519
520			buttons.appendChild(link);
521		};
522
523		if (editorUi.mode == App.MODE_GOOGLE && editorUi.drive != null)
524		{
525			var driveUsers =editorUi.drive.getUsersList();
526
527			if (driveUsers.length > 0)
528			{
529				var title = document.createElement('span');
530				title.style.marginTop = '6px';
531				mxUtils.write(title, mxResources.get('changeUser') + ':');
532
533				// Makes room after last big buttons
534				btn.style.marginBottom = '16px';
535				buttons.style.paddingBottom = '18px';
536				buttons.appendChild(title);
537
538				var usersSelect = document.createElement('select');
539				usersSelect.style.marginLeft = '4px';
540				usersSelect.style.width = '140px';
541
542				for (var i = 0; i < driveUsers.length; i++)
543				{
544					var option = document.createElement('option');
545					mxUtils.write(option, driveUsers[i].displayName);
546					option.value = i;
547					usersSelect.appendChild(option);
548					//More info (email) about the user in a disabled option
549					option = document.createElement('option');
550					option.innerHTML = '&nbsp;&nbsp;&nbsp;';
551					mxUtils.write(option, '<' + driveUsers[i].email + '>');
552					option.setAttribute('disabled', 'disabled');
553					usersSelect.appendChild(option);
554				}
555
556				//Add account option
557				var option = document.createElement('option');
558				mxUtils.write(option, mxResources.get('addAccount'));
559				option.value = driveUsers.length;
560				usersSelect.appendChild(option);
561
562				mxEvent.addListener(usersSelect, 'change', function()
563				{
564					var userIndex = usersSelect.value;
565					var existingAccount = driveUsers.length != userIndex;
566
567					if (existingAccount)
568					{
569						editorUi.drive.setUser(driveUsers[userIndex]);
570					}
571
572					editorUi.drive.authorize(existingAccount, function()
573					{
574						editorUi.setMode(App.MODE_GOOGLE);
575						editorUi.hideDialog();
576						editorUi.showSplash();
577					}, function(resp)
578					{
579						editorUi.handleError(resp, null, function()
580						{
581							editorUi.hideDialog();
582							editorUi.showSplash();
583						});
584					}, true);
585				});
586
587				buttons.appendChild(usersSelect);
588			}
589			else
590			{
591				addLogout(function()
592				{
593					editorUi.drive.logout();
594				});
595			}
596		}
597		else if (editorUi.mode == App.MODE_ONEDRIVE && editorUi.oneDrive != null && !editorUi.oneDrive.noLogout)
598		{
599			addLogout(function()
600			{
601				editorUi.oneDrive.logout();
602			});
603		}
604		else if (editorUi.mode == App.MODE_GITHUB && editorUi.gitHub != null)
605		{
606			addLogout(function()
607			{
608				editorUi.gitHub.logout();
609				editorUi.openLink('https://www.github.com/logout');
610			});
611		}
612		else if (editorUi.mode == App.MODE_GITLAB && editorUi.gitLab != null)
613		{
614			addLogout(function()
615			{
616				editorUi.gitLab.logout();
617				editorUi.openLink(DRAWIO_GITLAB_URL + '/users/sign_out');
618			});
619		}
620		else if (editorUi.mode == App.MODE_NOTION && editorUi.notion != null)
621		{
622			addLogout(function()
623			{
624				editorUi.notion.logout();
625			});
626		}
627		else if (editorUi.mode == App.MODE_TRELLO && editorUi.trello != null)
628		{
629			if (editorUi.trello.isAuthorized())
630			{
631				addLogout(function()
632				{
633					editorUi.trello.logout();
634				});
635			}
636		}
637		else if (editorUi.mode == App.MODE_DROPBOX && editorUi.dropbox != null)
638		{
639			// NOTE: Dropbox has a logout option in the picker
640			addLogout(function()
641			{
642				editorUi.dropbox.logout();
643				editorUi.openLink('https://www.dropbox.com/logout');
644			});
645		}
646	}
647
648	div.appendChild(buttons);
649	this.container = div;
650};
651
652/**
653 * Constructs a new embed dialog
654 */
655var EmbedDialog = function(editorUi, result, timeout, ignoreSize, previewFn, title, tweet, previewTitle, filename)
656{
657	tweet = (tweet != null) ? tweet : 'Check out the diagram I made using @drawio';
658	var div = document.createElement('div');
659	var maxSize = 500000;
660	var maxFbSize = 51200;
661	var maxTwitterSize = 7168;
662
663	// Checks if result is a link
664	var validUrl = /^https?:\/\//.test(result) || /^mailto:\/\//.test(result);
665
666	if (title != null)
667	{
668		mxUtils.write(div, title);
669	}
670	else
671	{
672		mxUtils.write(div, mxResources.get((result.length < maxSize) ?
673			((validUrl) ? 'link' : 'mainEmbedNotice') : 'preview') + ':');
674	}
675	mxUtils.br(div);
676
677	var size = document.createElement('div');
678	size.style.position = 'absolute';
679	size.style.top = '30px';
680	size.style.right = '30px';
681	size.style.color = 'gray';
682	mxUtils.write(size, editorUi.formatFileSize(result.length));
683
684	div.appendChild(size);
685
686	// Using DIV for faster rendering
687	var text = document.createElement('textarea');
688	text.setAttribute('autocomplete', 'off');
689	text.setAttribute('autocorrect', 'off');
690	text.setAttribute('autocapitalize', 'off');
691	text.setAttribute('spellcheck', 'false');
692	text.style.fontFamily = 'monospace';
693	text.style.wordBreak = 'break-all';
694	text.style.marginTop = '10px';
695	text.style.resize = 'none';
696	text.style.height = '150px';
697	text.style.width = '440px';
698	text.style.border = '1px solid gray';
699	text.value = mxResources.get('updatingDocument');
700	div.appendChild(text);
701	mxUtils.br(div);
702
703	this.init = function()
704	{
705		window.setTimeout(function()
706		{
707			if (result.length < maxSize)
708			{
709				text.value = result;
710				text.focus();
711
712				if (mxClient.IS_GC || mxClient.IS_FF || document.documentMode >= 5)
713				{
714					text.select();
715				}
716				else
717				{
718					document.execCommand('selectAll', false, null);
719				}
720			}
721			else
722			{
723				text.setAttribute('readonly', 'true');
724				text.value = mxResources.get('tooLargeUseDownload');
725			}
726		}, 0);
727	};
728
729	var buttons = document.createElement('div');
730	buttons.style.position = 'absolute';
731	buttons.style.bottom = '36px';
732	buttons.style.right = '32px';
733
734	var previewBtn = null;
735
736	// Loads forever in IE9
737	if (EmbedDialog.showPreviewOption && (!mxClient.IS_CHROMEAPP || validUrl) && !navigator.standalone && (validUrl ||
738		(mxClient.IS_SVG && (document.documentMode == null || document.documentMode > 9))))
739	{
740		previewBtn = mxUtils.button((previewTitle != null) ? previewTitle :
741			mxResources.get((result.length < maxSize) ? 'preview' : 'openInNewWindow'), function()
742		{
743			var value = (result.length < maxSize) ? text.value : result;
744
745			if (previewFn != null)
746			{
747				previewFn(value);
748			}
749			else
750			{
751				if (validUrl)
752				{
753					try
754					{
755						var win = editorUi.openLink(value);
756
757						if (win != null && (timeout == null || timeout > 0))
758						{
759							window.setTimeout(mxUtils.bind(this, function()
760							{
761								try
762								{
763									if (win != null && win.location.href != null &&
764										win.location.href.substring(0, 8) != value.substring(0, 8))
765									{
766										win.close();
767										editorUi.handleError({message: mxResources.get('drawingTooLarge')});
768									}
769								}
770								catch (e)
771								{
772									// ignore
773								}
774							}), timeout || 500);
775						}
776					}
777					catch (e)
778					{
779						editorUi.handleError({message: e.message || mxResources.get('drawingTooLarge')});
780					}
781				}
782				else
783				{
784					var wnd = window.open();
785					var doc = (wnd != null) ? wnd.document : null;
786
787					if (doc != null)
788					{
789						doc.writeln('<html><head><title>' + encodeURIComponent(mxResources.get('preview')) +
790							'</title><meta charset="utf-8"></head>' +
791							'<body>' + result + '</body></html>');
792						doc.close();
793					}
794					else
795					{
796						editorUi.handleError({message: mxResources.get('errorUpdatingPreview')});
797					}
798				}
799			}
800		});
801
802		previewBtn.className = 'geBtn';
803		buttons.appendChild(previewBtn);
804	}
805
806	if (!validUrl || result.length > 7500)
807	{
808		var downloadBtn = mxUtils.button(mxResources.get('download'), function()
809		{
810			editorUi.hideDialog();
811			editorUi.saveData((filename != null) ? filename : 'embed.txt', 'txt', result, 'text/plain');
812		});
813
814		downloadBtn.className = 'geBtn';
815		buttons.appendChild(downloadBtn);
816	}
817
818	// Twitter-intent does not allow more characters, must be pasted manually
819	if (validUrl && (!editorUi.isOffline() || mxClient.IS_CHROMEAPP))
820	{
821		if (result.length < maxFbSize)
822		{
823			var fbBtn = mxUtils.button('', function()
824			{
825				try
826				{
827					var url = 'https://www.facebook.com/sharer.php?p[url]=' +
828						encodeURIComponent(text.value);
829					editorUi.openLink(url);
830				}
831				catch (e)
832				{
833					editorUi.handleError({message: e.message || mxResources.get('drawingTooLarge')});
834				}
835			});
836
837			var img = document.createElement('img');
838			img.setAttribute('src', Editor.facebookImage);
839			img.setAttribute('width', '18');
840			img.setAttribute('height', '18');
841			img.setAttribute('border', '0');
842
843			fbBtn.appendChild(img);
844			fbBtn.setAttribute('title', mxResources.get('facebook') + ' (' +
845					editorUi.formatFileSize(maxFbSize) + ' max)');
846			fbBtn.style.verticalAlign = 'bottom';
847			fbBtn.style.paddingTop = '4px';
848			fbBtn.style.minWidth = '46px'
849			fbBtn.className = 'geBtn';
850			buttons.appendChild(fbBtn);
851		}
852
853		if (result.length < maxTwitterSize)
854		{
855			var tweetBtn = mxUtils.button('', function()
856			{
857				try
858				{
859					var url = 'https://twitter.com/intent/tweet?text=' +
860						encodeURIComponent(tweet) + '&url=' +
861						encodeURIComponent(text.value);
862					editorUi.openLink(url);
863				}
864				catch (e)
865				{
866					editorUi.handleError({message: e.message || mxResources.get('drawingTooLarge')});
867				}
868			});
869
870			var img = document.createElement('img');
871			img.setAttribute('src', Editor.tweetImage);
872			img.setAttribute('width', '18');
873			img.setAttribute('height', '18');
874			img.setAttribute('border', '0');
875			img.style.marginBottom = '5px'
876
877			tweetBtn.appendChild(img);
878			tweetBtn.setAttribute('title', mxResources.get('twitter') + ' (' +
879					editorUi.formatFileSize(maxTwitterSize) + ' max)');
880			tweetBtn.style.verticalAlign = 'bottom';
881			tweetBtn.style.paddingTop = '4px';
882			tweetBtn.style.minWidth = '46px'
883			tweetBtn.className = 'geBtn';
884			buttons.appendChild(tweetBtn);
885		}
886	}
887
888	var closeBtn = mxUtils.button(mxResources.get('close'), function()
889	{
890		editorUi.hideDialog();
891	});
892
893	buttons.appendChild(closeBtn);
894
895	var copyBtn = mxUtils.button(mxResources.get('copy'), function()
896	{
897		text.focus();
898
899		if (mxClient.IS_GC || mxClient.IS_FF || document.documentMode >= 5)
900		{
901			text.select();
902		}
903		else
904		{
905			document.execCommand('selectAll', false, null);
906		}
907
908		document.execCommand('copy');
909		editorUi.alert(mxResources.get('copiedToClipboard'));
910	});
911
912	if (result.length < maxSize)
913	{
914		// Does not work in Safari and shows annoying dialog for IE11-
915		if (!mxClient.IS_SF && document.documentMode == null)
916		{
917			buttons.appendChild(copyBtn);
918			copyBtn.className = 'geBtn gePrimaryBtn';
919			closeBtn.className = 'geBtn';
920		}
921		else
922		{
923			closeBtn.className = 'geBtn gePrimaryBtn';
924		}
925	}
926	else
927	{
928		buttons.appendChild(previewBtn);
929		closeBtn.className = 'geBtn';
930		previewBtn.className = 'geBtn gePrimaryBtn';
931	}
932
933	div.appendChild(buttons);
934	this.container = div;
935};
936
937/**
938 * Add embed dialog option.
939 */
940EmbedDialog.showPreviewOption = true;
941
942/**
943 * Constructs a dialog for embedding the diagram in Google Sites.
944 */
945var GoogleSitesDialog = function(editorUi, publicUrl)
946{
947	var div = document.createElement('div');
948
949	var graph = editorUi.editor.graph;
950	var bounds = graph.getGraphBounds();
951	var scale = graph.view.scale;
952	var x0 = Math.floor(bounds.x / scale - graph.view.translate.x);
953	var y0 = Math.floor(bounds.y / scale - graph.view.translate.y);
954
955	mxUtils.write(div, mxResources.get('googleGadget') + ':');
956	mxUtils.br(div);
957
958	var gadgetInput = document.createElement('input');
959	gadgetInput.setAttribute('type', 'text');
960	gadgetInput.style.marginBottom = '8px';
961	gadgetInput.style.marginTop = '2px';
962	gadgetInput.style.width = '410px';
963	div.appendChild(gadgetInput);
964	mxUtils.br(div);
965
966	this.init = function()
967	{
968		gadgetInput.focus();
969
970		if (mxClient.IS_GC || mxClient.IS_FF || document.documentMode >= 5)
971		{
972			gadgetInput.select();
973		}
974		else
975		{
976			document.execCommand('selectAll', false, null);
977		}
978	};
979
980	mxUtils.write(div, mxResources.get('top') + ':');
981	var topInput = document.createElement('input');
982	topInput.setAttribute('type', 'text');
983	topInput.setAttribute('size', '4');
984	topInput.style.marginRight = '16px';
985	topInput.style.marginLeft = '4px';
986	topInput.value = x0;
987	div.appendChild(topInput);
988
989	mxUtils.write(div, mxResources.get('height') + ':');
990	var heightInput = document.createElement('input');
991	heightInput.setAttribute('type', 'text');
992	heightInput.setAttribute('size', '4');
993	heightInput.style.marginLeft = '4px';
994	heightInput.value = Math.ceil(bounds.height / scale);
995	div.appendChild(heightInput);
996	mxUtils.br(div);
997
998	var hr = document.createElement('hr');
999	hr.setAttribute('size', '1');
1000	hr.style.marginBottom = '16px';
1001	hr.style.marginTop = '16px';
1002	div.appendChild(hr);
1003
1004	mxUtils.write(div, mxResources.get('publicDiagramUrl') + ':');
1005	mxUtils.br(div);
1006
1007	var urlInput = document.createElement('input');
1008	urlInput.setAttribute('type', 'text');
1009	urlInput.setAttribute('size', '28');
1010	urlInput.style.marginBottom = '8px';
1011	urlInput.style.marginTop = '2px';
1012	urlInput.style.width = '410px';
1013	urlInput.value = publicUrl || '';
1014	div.appendChild(urlInput);
1015	mxUtils.br(div);
1016
1017	mxUtils.write(div, mxResources.get('borderWidth') + ':');
1018	var borderInput = document.createElement('input');
1019	borderInput.setAttribute('type', 'text');
1020	borderInput.setAttribute('size', '3');
1021	borderInput.style.marginBottom = '8px';
1022	borderInput.style.marginLeft = '4px';
1023	borderInput.value = '0';
1024	div.appendChild(borderInput);
1025	mxUtils.br(div);
1026
1027	var panCheckBox = document.createElement('input');
1028	panCheckBox.setAttribute('type', 'checkbox');
1029	panCheckBox.setAttribute('checked', 'checked');
1030	panCheckBox.defaultChecked = true;
1031	panCheckBox.style.marginLeft = '16px';
1032	div.appendChild(panCheckBox);
1033	mxUtils.write(div, mxResources.get('pan') + ' ');
1034
1035	var zoomCheckBox = document.createElement('input');
1036	zoomCheckBox.setAttribute('type', 'checkbox');
1037	zoomCheckBox.setAttribute('checked', 'checked');
1038	zoomCheckBox.defaultChecked = true;
1039	zoomCheckBox.style.marginLeft = '8px';
1040	div.appendChild(zoomCheckBox);
1041	mxUtils.write(div, mxResources.get('zoom') + ' ');
1042
1043	var editCheckBox = document.createElement('input');
1044	editCheckBox.setAttribute('type', 'checkbox');
1045	editCheckBox.style.marginLeft = '8px';
1046	editCheckBox.setAttribute('title', window.location.href);
1047	div.appendChild(editCheckBox);
1048	mxUtils.write(div, mxResources.get('edit') + ' ');
1049
1050	var editBlankCheckBox = document.createElement('input');
1051	editBlankCheckBox.setAttribute('type', 'checkbox');
1052	editBlankCheckBox.style.marginLeft = '8px';
1053	div.appendChild(editBlankCheckBox);
1054	mxUtils.write(div, mxResources.get('asNew') + ' ');
1055	mxUtils.br(div);
1056
1057	var resizeCheckBox = document.createElement('input');
1058	resizeCheckBox.setAttribute('type', 'checkbox');
1059	resizeCheckBox.setAttribute('checked', 'checked');
1060	resizeCheckBox.defaultChecked = true;
1061	resizeCheckBox.style.marginLeft = '16px';
1062	div.appendChild(resizeCheckBox);
1063	mxUtils.write(div, mxResources.get('resize') + ' ');
1064
1065	var fitCheckBox = document.createElement('input');
1066	fitCheckBox.setAttribute('type', 'checkbox');
1067	fitCheckBox.style.marginLeft = '8px';
1068	div.appendChild(fitCheckBox);
1069	mxUtils.write(div, mxResources.get('fit') + ' ');
1070
1071	var embedCheckBox = document.createElement('input');
1072	embedCheckBox.setAttribute('type', 'checkbox');
1073	embedCheckBox.style.marginLeft = '8px';
1074	div.appendChild(embedCheckBox);
1075	mxUtils.write(div, mxResources.get('embed') + ' ');
1076
1077	var node = null;
1078	var s = editorUi.getBasenames().join(';');
1079	var file = editorUi.getCurrentFile();
1080
1081	function update()
1082	{
1083		var title = (file != null && file.getTitle() != null) ? file.getTitle() : this.defaultFilename;
1084
1085		if (embedCheckBox.checked && urlInput.value != '')
1086		{
1087			var encUrl = encodeURIComponent(mxUtils.htmlEntities(urlInput.value));
1088			var gurl = 'https://www.draw.io/gadget.xml?type=4&diagram=' + encUrl;
1089
1090			if (title != null)
1091			{
1092				gurl += '&title=' + encodeURIComponent(title);
1093			}
1094
1095			if (s.length > 0)
1096			{
1097				gurl += '&s=' + s;
1098			}
1099
1100			if (borderInput.value != '' && borderInput.value != '0')
1101			{
1102				gurl += '&border=' + borderInput.value;
1103			}
1104
1105			if (heightInput.value != '')
1106			{
1107				gurl += '&height=' + heightInput.value;
1108			}
1109
1110			gurl += '&pan=' + ((panCheckBox.checked) ? '1': '0');
1111			gurl += '&zoom=' + ((zoomCheckBox.checked) ? '1': '0');
1112			gurl += '&fit=' + ((fitCheckBox.checked) ? '1': '0');
1113			gurl += '&resize=' + ((resizeCheckBox.checked) ? '1': '0');
1114			gurl += '&x0=' + Number(topInput.value);
1115			gurl += '&y0=' + y0;
1116
1117			if (graph.mathEnabled)
1118			{
1119				gurl += '&math=1';
1120			}
1121
1122			if (editBlankCheckBox.checked)
1123			{
1124				gurl += '&edit=_blank';
1125			}
1126			else if (editCheckBox.checked)
1127			{
1128				gurl += '&edit=' + encodeURIComponent(mxUtils.htmlEntities(window.location.href));
1129			}
1130
1131			gadgetInput.value = gurl;
1132		}
1133		else if (file.constructor == DriveFile || file.constructor == DropboxFile)
1134		{
1135			var gurl = 'https://www.draw.io/gadget.xml?embed=0&diagram=';
1136
1137			if (urlInput.value != '')
1138			{
1139				gurl += encodeURIComponent(mxUtils.htmlEntities(urlInput.value)) + '&type=3';
1140			}
1141			else
1142			{
1143				gurl += file.getHash().substring(1);
1144
1145				if (file.constructor == DropboxFile)
1146				{
1147					gurl += '&type=2';
1148				}
1149				else
1150				{
1151					gurl += '&type=1';
1152				}
1153			}
1154
1155			if (title != null)
1156			{
1157				gurl += '&title=' + encodeURIComponent(title);
1158			}
1159
1160			if (heightInput.value != '')
1161			{
1162				var h = parseInt(heightInput.value) + parseInt(topInput.value);
1163				gurl += '&height=' + h;
1164			}
1165
1166			gadgetInput.value = gurl;
1167		}
1168		else
1169		{
1170			gadgetInput.value = '';
1171		}
1172	};
1173
1174	mxEvent.addListener(panCheckBox, 'change', update);
1175	mxEvent.addListener(zoomCheckBox, 'change', update);
1176	mxEvent.addListener(resizeCheckBox, 'change', update);
1177	mxEvent.addListener(fitCheckBox, 'change', update);
1178	mxEvent.addListener(editCheckBox, 'change', update);
1179	mxEvent.addListener(editBlankCheckBox, 'change', update);
1180	mxEvent.addListener(embedCheckBox, 'change', update);
1181	mxEvent.addListener(heightInput, 'change', update);
1182	mxEvent.addListener(topInput, 'change', update);
1183	mxEvent.addListener(borderInput, 'change', update);
1184	mxEvent.addListener(urlInput, 'change', update);
1185	update();
1186
1187	mxEvent.addListener(gadgetInput, 'click', function()
1188	{
1189		gadgetInput.focus();
1190
1191		if (mxClient.IS_GC || mxClient.IS_FF || document.documentMode >= 5)
1192		{
1193			gadgetInput.select();
1194		}
1195		else
1196		{
1197			document.execCommand('selectAll', false, null);
1198		}
1199	});
1200
1201	var buttons = document.createElement('div');
1202	buttons.style.paddingTop = '12px';
1203	buttons.style.textAlign = 'right';
1204
1205	var closeBtn = mxUtils.button(mxResources.get('close'), function()
1206	{
1207		editorUi.hideDialog();
1208	});
1209	closeBtn.className = 'geBtn gePrimaryBtn';
1210	buttons.appendChild(closeBtn);
1211
1212	div.appendChild(buttons);
1213
1214	this.container = div;
1215};
1216
1217/**
1218 * Constructs a new parse dialog.
1219 */
1220var CreateGraphDialog = function(editorUi, title, type)
1221{
1222	var div = document.createElement('div');
1223	div.style.textAlign = 'right';
1224
1225	this.init = function()
1226	{
1227		var container = document.createElement('div');
1228		container.style.position = 'relative';
1229		container.style.border = '1px solid gray';
1230		container.style.width = '100%';
1231		container.style.height = '360px';
1232		container.style.overflow = 'hidden';
1233		container.style.marginBottom = '16px';
1234		mxEvent.disableContextMenu(container);
1235		div.appendChild(container);
1236
1237		var graph = new Graph(container);
1238
1239		graph.setCellsCloneable(true);
1240		graph.setPanning(true);
1241		graph.setAllowDanglingEdges(false);
1242		graph.connectionHandler.select = false;
1243		graph.view.setTranslate(20, 20);
1244		graph.border = 20;
1245		graph.panningHandler.useLeftButtonForPanning = true;
1246
1247		var vertexStyle = 'rounded=1;';
1248		var edgeStyle = 'curved=1;';
1249		var startStyle = 'ellipse';
1250
1251		// FIXME: Does not work in iPad
1252		var mxCellRendererInstallCellOverlayListeners = mxCellRenderer.prototype.installCellOverlayListeners;
1253		graph.cellRenderer.installCellOverlayListeners = function(state, overlay, shape)
1254		{
1255			mxCellRenderer.prototype.installCellOverlayListeners.apply(this, arguments);
1256
1257			mxEvent.addListener(shape.node, (mxClient.IS_POINTER) ? 'pointerdown' : 'mousedown', function (evt)
1258			{
1259				overlay.fireEvent(new mxEventObject('pointerdown', 'event', evt, 'state', state));
1260			});
1261
1262			if (!mxClient.IS_POINTER && mxClient.IS_TOUCH)
1263			{
1264				mxEvent.addListener(shape.node, 'touchstart', function (evt)
1265				{
1266					overlay.fireEvent(new mxEventObject('pointerdown', 'event', evt, 'state', state));
1267				});
1268			}
1269		};
1270
1271		graph.getAllConnectionConstraints = function()
1272		{
1273			return null;
1274		};
1275
1276		// Keeps highlight behind overlays
1277		graph.connectionHandler.marker.highlight.keepOnTop = false;
1278
1279		graph.connectionHandler.createEdgeState = function(me)
1280		{
1281			var edge = graph.createEdge(null, null, null, null, null, edgeStyle);
1282
1283			return new mxCellState(this.graph.view, edge, this.graph.getCellStyle(edge));
1284		};
1285
1286		// Gets the default parent for inserting new cells. This
1287		// is normally the first child of the root (ie. layer 0).
1288		var parent = graph.getDefaultParent();
1289
1290		var addOverlay = mxUtils.bind(this, function(cell)
1291		{
1292			// Creates a new overlay with an image and a tooltip
1293			var overlay = new mxCellOverlay(this.connectImage, 'Add outgoing');
1294			overlay.cursor = 'hand';
1295
1296			// Installs a handler for clicks on the overlay
1297			overlay.addListener(mxEvent.CLICK, function(sender, evt2)
1298			{
1299				// TODO: Add menu for picking next shape
1300				graph.connectionHandler.reset();
1301				graph.clearSelection();
1302				var geo = graph.getCellGeometry(cell);
1303
1304				var v2;
1305
1306				executeLayout(function()
1307				{
1308					v2 = graph.insertVertex(parent, null, 'Entry', geo.x, geo.y, 80, 30, vertexStyle);
1309					addOverlay(v2);
1310					graph.view.refresh(v2);
1311					var e1 = graph.insertEdge(parent, null, '', cell, v2, edgeStyle);
1312				}, function()
1313				{
1314					graph.scrollCellToVisible(v2);
1315				});
1316			});
1317
1318			// FIXME: Does not work in iPad (inserts loop)
1319			overlay.addListener('pointerdown', function(sender, eo)
1320			{
1321				var evt2 = eo.getProperty('event');
1322				var state = eo.getProperty('state');
1323
1324				graph.popupMenuHandler.hideMenu();
1325				graph.stopEditing(false);
1326
1327				var pt = mxUtils.convertPoint(graph.container,
1328						mxEvent.getClientX(evt2), mxEvent.getClientY(evt2));
1329				graph.connectionHandler.start(state, pt.x, pt.y);
1330				graph.isMouseDown = true;
1331				graph.isMouseTrigger = mxEvent.isMouseEvent(evt2);
1332				mxEvent.consume(evt2);
1333			});
1334
1335			// Sets the overlay for the cell in the graph
1336			graph.addCellOverlay(cell, overlay);
1337		});
1338
1339		// Adds cells to the model in a single step
1340		graph.getModel().beginUpdate();
1341		var v1;
1342		try
1343		{
1344			v1 = graph.insertVertex(parent, null, 'Start', 0, 0, 80, 30, startStyle);
1345			addOverlay(v1);
1346		}
1347		finally
1348		{
1349			// Updates the display
1350			graph.getModel().endUpdate();
1351		}
1352
1353		var layout;
1354
1355		if (type == 'horizontalTree')
1356		{
1357			layout = new mxCompactTreeLayout(graph);
1358			layout.edgeRouting = false;
1359			layout.levelDistance = 30;
1360			edgeStyle = 'edgeStyle=elbowEdgeStyle;elbow=horizontal;';
1361		}
1362		else if (type == 'verticalTree')
1363		{
1364			layout = new mxCompactTreeLayout(graph, false);
1365			layout.edgeRouting = false;
1366			layout.levelDistance = 30;
1367			edgeStyle = 'edgeStyle=elbowEdgeStyle;elbow=vertical;';
1368		}
1369		else if (type == 'radialTree')
1370		{
1371			layout = new mxRadialTreeLayout(graph, false);
1372			layout.edgeRouting = false;
1373			layout.levelDistance = 80;
1374		}
1375		else if (type == 'verticalFlow')
1376		{
1377			layout = new mxHierarchicalLayout(graph, mxConstants.DIRECTION_NORTH);
1378		}
1379		else if (type == 'horizontalFlow')
1380		{
1381			layout = new mxHierarchicalLayout(graph, mxConstants.DIRECTION_WEST);
1382		}
1383		else if (type == 'organic')
1384		{
1385			layout = new mxFastOrganicLayout(graph, false);
1386			layout.forceConstant = 80;
1387		}
1388		else if (type == 'circle')
1389		{
1390			layout = new mxCircleLayout(graph);
1391		}
1392
1393		if (layout != null)
1394		{
1395			var executeLayout = function(change, post)
1396			{
1397				graph.getModel().beginUpdate();
1398				try
1399				{
1400					if (change != null)
1401					{
1402						change();
1403					}
1404
1405					layout.execute(graph.getDefaultParent(), v1);
1406				}
1407				catch (e)
1408				{
1409					throw e;
1410				}
1411				finally
1412				{
1413					// New API for animating graph layout results asynchronously
1414					var morph = new mxMorphing(graph);
1415					morph.addListener(mxEvent.DONE, mxUtils.bind(this, function()
1416					{
1417						graph.getModel().endUpdate();
1418
1419						if (post != null)
1420						{
1421							post();
1422						}
1423					}));
1424
1425					morph.startAnimation();
1426				}
1427			};
1428
1429			var edgeHandleConnect = mxEdgeHandler.prototype.connect;
1430			mxEdgeHandler.prototype.connect = function(edge, terminal, isSource, isClone, me)
1431			{
1432				edgeHandleConnect.apply(this, arguments);
1433				executeLayout();
1434			};
1435
1436			graph.resizeCell = function()
1437			{
1438				mxGraph.prototype.resizeCell.apply(this, arguments);
1439
1440				executeLayout();
1441			};
1442
1443			graph.connectionHandler.addListener(mxEvent.CONNECT, function()
1444			{
1445				executeLayout();
1446			});
1447		}
1448
1449		var cancelBtn = mxUtils.button(mxResources.get('close'), function()
1450		{
1451			editorUi.confirm(mxResources.get('areYouSure'), function()
1452			{
1453				if (container.parentNode != null)
1454				{
1455					graph.destroy();
1456					container.parentNode.removeChild(container);
1457				}
1458
1459				editorUi.hideDialog();
1460			});
1461		})
1462
1463		cancelBtn.className = 'geBtn';
1464
1465		if (editorUi.editor.cancelFirst)
1466		{
1467			div.appendChild(cancelBtn);
1468		}
1469
1470		var okBtn = mxUtils.button(mxResources.get('insert'), function(evt)
1471		{
1472			graph.clearCellOverlays();
1473
1474			var cells = graph.getModel().getChildren(graph.getDefaultParent());
1475			var pt = (mxEvent.isAltDown(evt)) ?
1476				editorUi.editor.graph.getFreeInsertPoint() :
1477				editorUi.editor.graph.getCenterInsertPoint(
1478				graph.getBoundingBoxFromGeometry(cells, true));
1479			cells = editorUi.editor.graph.importCells(cells, pt.x, pt.y);
1480			var view = editorUi.editor.graph.view;
1481			var temp = view.getBounds(cells);
1482			temp.x -= view.translate.x;
1483			temp.y -= view.translate.y;
1484			editorUi.editor.graph.scrollRectToVisible(temp);
1485			editorUi.editor.graph.setSelectionCells(cells);
1486
1487			if (container.parentNode != null)
1488			{
1489				graph.destroy();
1490				container.parentNode.removeChild(container);
1491			}
1492
1493			editorUi.hideDialog();
1494		});
1495
1496		div.appendChild(okBtn);
1497		okBtn.className = 'geBtn gePrimaryBtn';
1498
1499		if (!editorUi.editor.cancelFirst)
1500		{
1501			div.appendChild(cancelBtn);
1502		}
1503	};
1504
1505	this.container = div;
1506};
1507
1508/**
1509 *
1510 */
1511CreateGraphDialog.prototype.connectImage = new mxImage((mxClient.IS_SVG) ? 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABoAAAAaCAYAAACpSkzOAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMC1jMDYwIDYxLjEzNDc3NywgMjAxMC8wMi8xMi0xNzozMjowMCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNSBNYWNpbnRvc2giIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6RjQ3OTk0QjMyRDcyMTFFNThGQThGNDVBMjNBMjFDMzkiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6RjQ3OTk0QjQyRDcyMTFFNThGQThGNDVBMjNBMjFDMzkiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDoyRjA0N0I2MjJENzExMUU1OEZBOEY0NUEyM0EyMUMzOSIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDpGNDc5OTRCMjJENzIxMUU1OEZBOEY0NUEyM0EyMUMzOSIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PjIf+MgAAATlSURBVHjanFZraFxFFD735u4ru3ls0yZG26ShgmJoKK1J2vhIYzBgRdtIURHyw1hQUH9IxIgI2h8iCEUF/1RRlNQYCsYfCTHVhiTtNolpZCEStqSC22xIsrs1bDfu7t37Gs/cO3Ozxs1DBw73zpk555vzmHNGgJ0NYatFgmNLYUHYUoHASMz5ijmgVLmxgfKCUiBxC4ACJAeSG8nb1dVVOTc3dyoSibwWDofPBIPBJzo7O8vpGtvjpDICGztxkciECpF2LS0tvZtOpwNkk5FKpcYXFxffwL1+JuPgllPj8nk1F6RoaGjoKCqZ5ApljZDZO4SMRA0SuG2QUJIQRV8HxMOM9vf3H0ZZH9Nhg20MMl2QkFwjIyNHWlpahtADnuUMwLcRHX5aNSBjCJYEsSSLUeLEbhGe3ytCmQtA1/XY+Pj46dbW1iDuyCJp9BC5ycBj4hoeHq5ra2sbw0Xn1ZgBZ+dVkA1Lc+6p0Ck2p0QS4Ox9EhwpEylYcmBg4LH29vYQLilIOt0u5FhDfevNZDI/u93uw6PLOrwTUtjxrbPYbhD42WgMrF8JmR894ICmCgnQjVe8Xu8pXEkzMJKbuo5oNPomBbm1ZsD7s2kwFA1JZ6QBUXWT1nmGNc/qoMgavDcrQzxjQGFh4aOYIJ0sFAXcEtui4uLiVjr5KpSBVFYDDZVrWUaKRRWSAYeK0fmKykgDXbVoNaPChRuyqdDv97czL5nXxQbq6empQmsaklkDBiNpSwFVrmr2P6UyicD5piI4f8wHh0oEm8/p4h8pyGiEWvVQd3e3nxtjAzU1NR2jP7NRBWQ8GbdEzzJAmc0V3RR4cI8Dvmwuhc8fKUFA0d6/ltHg5p+Kuaejo6OeY0jcNJ/PV00ZS0nFUoZRvvFS1bZFsKHCCQ2Pl8H0chY+C96B6ZUsrCQ1qKtwQVFRURW/QhIXMAzDPAZ6BgOr8tTa8dDxCmiYGApaJbJMxSzV+brE8pdgWkcpY5dbMF1AR9XH8/xu2ilef48bvn92n82ZwHh+8ssqTEXS9p7dHisiiURikd8PbpExNTU1UVNTA3V3Y7lC16n0gpB/NwpNcZjfa7dScC4Qh0kOQCwnlEgi3F/hMVl9fX0zvKrzSk2lfXjRhj0eT/2rvWG4+Pta3oJY7XfC3hInXAv/ldeFLx8shQ+eqQL0UAAz7ylkpej5eNZRVBWL6BU6ef14OYiY1oqyTtmsavr/5koaRucT1pzx+ZpL1+GV5nLutksUgIcmtwTRiuuVZXnU5XId7A2swJkfFsymRWC91hHg1Viw6x23+7vn9sPJ+j20BE1hCXqSWaNSQ8ScbknRZWxub1PGCw/fBV+c3AeijlUbY5bBjEqr9GuYZP4jP41WudGSC6erTRCqdGZm5i1WvXWeDHnbBCZGc2Nj4wBl/hZOwrmBBfgmlID1HmGJutHaF+tKoevp/XCgstDkjo2NtWKLuc6AVN4mNjY+s1XQxoenOoFuDPHGtnRbJj9ej5GvL0dI7+giuRyMk1giazc+DP6vgUDgOJVlOv7R+PJ12QIeL6SyeDz+Kfp8ZrNWjgDTsVjsQ7qXyTjztXJhm9ePxFLfMTg4eG9tbe1RTP9KFFYQfHliYmIS69kCC7jKYmKwxxD5P88tkVkqbPPcIps9t4T/+HjcuJ/s5BFJgf4WYABCtxGuxIZ90gAAAABJRU5ErkJggg==' :
1512	IMAGE_PATH + '/handle-connect.png', 26, 26);
1513
1514/**
1515 * Constructs a new parse dialog.
1516 */
1517var BackgroundImageDialog = function(editorUi, applyFn, img)
1518{
1519	var div = document.createElement('div');
1520	div.style.whiteSpace = 'nowrap';
1521
1522	var h3 = document.createElement('h2');
1523	mxUtils.write(h3, mxResources.get('backgroundImage'));
1524	h3.style.marginTop = '0px';
1525	div.appendChild(h3);
1526
1527	var isPageLink = img != null && img.originalSrc != null;
1528	var pageFound = false;
1529
1530	var urlRadio = document.createElement('input');
1531	urlRadio.style.cssText = 'margin-right:8px;margin-bottom:8px;';
1532	urlRadio.setAttribute('value', 'url');
1533	urlRadio.setAttribute('type', 'radio');
1534	urlRadio.setAttribute('name', 'geBackgroundImageDialogOption');
1535
1536	var pageRadio = document.createElement('input');
1537	pageRadio.style.cssText = 'margin-right:8px;margin-bottom:8px;';
1538	pageRadio.setAttribute('value', 'url');
1539	pageRadio.setAttribute('type', 'radio');
1540	pageRadio.setAttribute('name', 'geBackgroundImageDialogOption');
1541
1542	var urlInput = document.createElement('input');
1543	urlInput.setAttribute('type', 'text');
1544	urlInput.style.marginBottom = '8px';
1545	urlInput.style.width = '320px';
1546	urlInput.value = (isPageLink || img == null) ? '' : img.src;
1547
1548	var pageSelect = document.createElement('select');
1549	pageSelect.style.width = '320px';
1550
1551	if (editorUi.pages != null)
1552	{
1553		for (var i = 0; i < editorUi.pages.length; i++)
1554		{
1555			var pageOption = document.createElement('option');
1556			mxUtils.write(pageOption, editorUi.pages[i].getName() ||
1557				mxResources.get('pageWithNumber', [i + 1]));
1558			pageOption.setAttribute('value', 'data:page/id,' +
1559				editorUi.pages[i].getId());
1560
1561			if (editorUi.pages[i] == editorUi.currentPage)
1562			{
1563				pageOption.setAttribute('disabled', 'disabled');
1564			}
1565
1566			if (img != null && img.originalSrc == pageOption.getAttribute('value'))
1567			{
1568				pageOption.setAttribute('selected', 'selected');
1569				pageFound = true;
1570			}
1571
1572			pageSelect.appendChild(pageOption);
1573		}
1574	}
1575
1576	if (!isPageLink && (editorUi.pages == null || editorUi.pages.length == 1))
1577	{
1578		urlRadio.style.display = 'none';
1579		pageRadio.style.display = 'none';
1580		pageSelect.style.display = 'none';
1581	}
1582
1583	var resetting = false;
1584	var ignoreEvt = false;
1585
1586	var urlChanged = function(evt, done)
1587	{
1588		// Skips blur event if called from apply button
1589		if (!resetting && (evt == null || !ignoreEvt))
1590		{
1591			if (pageRadio.checked)
1592			{
1593				if (done != null)
1594				{
1595					done(pageSelect.value);
1596				}
1597			}
1598			else if (urlInput.value != '' && !editorUi.isOffline())
1599			{
1600				urlInput.value = mxUtils.trim(urlInput.value);
1601
1602				editorUi.loadImage(urlInput.value, function(img)
1603				{
1604					widthInput.value = img.width;
1605					heightInput.value = img.height;
1606
1607					if (done != null)
1608					{
1609						done(urlInput.value);
1610					}
1611				}, function()
1612				{
1613					editorUi.showError(mxResources.get('error'), mxResources.get('fileNotFound'), mxResources.get('ok'));
1614					widthInput.value = '';
1615					heightInput.value = '';
1616
1617					if (done != null)
1618					{
1619						done(null);
1620					}
1621				});
1622			}
1623			else
1624			{
1625				widthInput.value = '';
1626				heightInput.value = '';
1627
1628				if (done != null)
1629				{
1630					done('');
1631				}
1632			}
1633		}
1634	};
1635
1636	this.init = function()
1637	{
1638		if (isPageLink)
1639		{
1640			pageSelect.focus();
1641		}
1642		else
1643		{
1644			urlInput.focus();
1645		}
1646
1647		mxEvent.addListener(pageSelect, 'focus', function()
1648		{
1649			urlRadio.removeAttribute('checked');
1650			pageRadio.setAttribute('checked', 'checked');
1651			pageRadio.checked = true;
1652		});
1653
1654		mxEvent.addListener(urlInput, 'focus', function()
1655		{
1656			pageRadio.removeAttribute('checked');
1657			urlRadio.setAttribute('checked', 'checked');
1658			urlRadio.checked = true;
1659		});
1660
1661		// Installs drag and drop handler for local images and links
1662		if (Graph.fileSupport)
1663		{
1664			urlInput.setAttribute('placeholder', mxResources.get('dragImagesHere'));
1665
1666			// Setup the dnd listeners
1667			var dlg = div.parentNode;
1668			var graph = editorUi.editor.graph;
1669			var dropElt = null;
1670
1671			mxEvent.addListener(dlg, 'dragleave', function(evt)
1672			{
1673				if (dropElt != null)
1674			    {
1675			    	dropElt.parentNode.removeChild(dropElt);
1676			    	dropElt = null;
1677			    }
1678
1679				evt.stopPropagation();
1680				evt.preventDefault();
1681			});
1682
1683			mxEvent.addListener(dlg, 'dragover', mxUtils.bind(this, function(evt)
1684			{
1685				// IE 10 does not implement pointer-events so it can't have a drop highlight
1686				if (dropElt == null && (!mxClient.IS_IE || document.documentMode > 10))
1687				{
1688					dropElt = editorUi.highlightElement(dlg);
1689				}
1690
1691				evt.stopPropagation();
1692				evt.preventDefault();
1693			}));
1694
1695			mxEvent.addListener(dlg, 'drop', mxUtils.bind(this, function(evt)
1696			{
1697			    if (dropElt != null)
1698			    {
1699			    	dropElt.parentNode.removeChild(dropElt);
1700			    	dropElt = null;
1701			    }
1702
1703			    if (evt.dataTransfer.files.length > 0)
1704			    {
1705			    	editorUi.importFiles(evt.dataTransfer.files, 0, 0, editorUi.maxBackgroundSize, function(data, mimeType, x, y, w, h)
1706			    	{
1707			    		urlInput.value = data;
1708			    		urlChanged();
1709			    	}, function()
1710			    	{
1711			    		// No post processing
1712			    	}, function(file)
1713			    	{
1714			    		// Handles only images
1715			    		return file.type.substring(0, 6) == 'image/';
1716			    	}, function(queue)
1717			    	{
1718			    		// Invokes elements of queue in order
1719			    		for (var i = 0; i < queue.length; i++)
1720			    		{
1721			    			queue[i]();
1722			    		}
1723			    	}, true, editorUi.maxBackgroundBytes, editorUi.maxBackgroundBytes, true);
1724	    		}
1725			    else if (mxUtils.indexOf(evt.dataTransfer.types, 'text/uri-list') >= 0)
1726			    {
1727			    	var uri = evt.dataTransfer.getData('text/uri-list');
1728
1729			    	if ((/\.(gif|jpg|jpeg|tiff|png|svg)$/i).test(uri))
1730					{
1731			    		urlInput.value = decodeURIComponent(uri);
1732			    		urlChanged();
1733					}
1734			    }
1735
1736			    evt.stopPropagation();
1737			    evt.preventDefault();
1738			}), false);
1739		}
1740	};
1741
1742	div.appendChild(urlRadio);
1743	div.appendChild(urlInput);
1744	mxUtils.br(div);
1745
1746	var span = document.createElement('span');
1747	span.style.marginLeft = '30px';
1748	mxUtils.write(span, mxResources.get('width') + ':');
1749	div.appendChild(span);
1750
1751	var widthInput = document.createElement('input');
1752	widthInput.setAttribute('type', 'text');
1753	widthInput.style.width = '60px';
1754	widthInput.style.marginLeft = '8px';
1755	widthInput.style.marginRight = '16px';
1756	widthInput.value = (img != null && !isPageLink) ? img.width : '';
1757
1758	div.appendChild(widthInput);
1759
1760	mxUtils.write(div, mxResources.get('height') + ':');
1761
1762	var heightInput = document.createElement('input');
1763	heightInput.setAttribute('type', 'text');
1764	heightInput.style.width = '60px';
1765	heightInput.style.marginLeft = '8px';
1766	heightInput.style.marginRight = '16px';
1767	heightInput.value = (img != null && !isPageLink) ? img.height : '';
1768
1769	div.appendChild(heightInput);
1770	mxUtils.br(div);
1771	mxUtils.br(div);
1772
1773	mxEvent.addListener(urlInput, 'change', urlChanged);
1774
1775	ImageDialog.filePicked = function(data)
1776	{
1777        if (data.action == google.picker.Action.PICKED)
1778        {
1779        	if (data.docs[0].thumbnails != null)
1780        	{
1781	        	var thumb = data.docs[0].thumbnails[data.docs[0].thumbnails.length - 1];
1782
1783	        	if (thumb != null)
1784	        	{
1785	        		urlInput.value = thumb.url;
1786	        		urlChanged();
1787	        	}
1788        	}
1789        }
1790
1791        urlInput.focus();
1792	};
1793
1794	div.appendChild(pageRadio);
1795	div.appendChild(pageSelect);
1796	mxUtils.br(div);
1797
1798	if (isPageLink)
1799	{
1800		pageRadio.setAttribute('checked', 'checked');
1801		pageRadio.checked = true;
1802	}
1803	else
1804	{
1805		urlRadio.setAttribute('checked', 'checked');
1806		urlRadio.checked = true;
1807	}
1808
1809	if (!pageFound && pageRadio.checked)
1810	{
1811		var notFoundOption = document.createElement('option');
1812		mxUtils.write(notFoundOption, mxResources.get('pageNotFound'));
1813		notFoundOption.setAttribute('disabled', 'disabled');
1814		notFoundOption.setAttribute('selected', 'selected');
1815		notFoundOption.setAttribute('value', 'pageNotFound');
1816		pageSelect.appendChild(notFoundOption);
1817
1818		mxEvent.addListener(pageSelect, 'change', function()
1819		{
1820			if (notFoundOption.parentNode != null && !notFoundOption.selected)
1821			{
1822				notFoundOption.parentNode.removeChild(notFoundOption);
1823			}
1824		});
1825	}
1826
1827	var btns = document.createElement('div');
1828	btns.style.marginTop = '30px';
1829	btns.style.textAlign = 'right';
1830
1831	var resetBtn = mxUtils.button(mxResources.get('reset'), function()
1832	{
1833		urlInput.value = '';
1834		widthInput.value = '';
1835		heightInput.value = '';
1836		urlRadio.checked = true;
1837		resetting = false;
1838	});
1839	mxEvent.addGestureListeners(resetBtn, function()
1840	{
1841		// Blocks processing a image URL while clicking reset
1842		resetting = true;
1843	});
1844	resetBtn.className = 'geBtn';
1845	resetBtn.width = '100';
1846	btns.appendChild(resetBtn);
1847
1848	var cancelBtn = mxUtils.button(mxResources.get('cancel'), function()
1849	{
1850		resetting = true;
1851		editorUi.hideDialog();
1852	});
1853
1854	cancelBtn.className = 'geBtn';
1855
1856	if (editorUi.editor.cancelFirst)
1857	{
1858		btns.appendChild(cancelBtn);
1859	}
1860
1861	applyBtn = mxUtils.button(mxResources.get('apply'), function()
1862	{
1863		editorUi.hideDialog();
1864
1865		urlChanged(null, function(url)
1866		{
1867			applyFn((url != '' && url != null) ? new mxImage(url,
1868				widthInput.value, heightInput.value) : null,
1869				url == null);
1870		});
1871	});
1872
1873	mxEvent.addGestureListeners(applyBtn, function()
1874	{
1875		ignoreEvt = true;
1876	});
1877
1878	applyBtn.className = 'geBtn gePrimaryBtn';
1879	btns.appendChild(applyBtn);
1880
1881	if (!editorUi.editor.cancelFirst)
1882	{
1883		btns.appendChild(cancelBtn);
1884	}
1885
1886	div.appendChild(btns);
1887
1888	this.container = div;
1889};
1890
1891/**
1892 * Constructs a new parse dialog.
1893 */
1894var ParseDialog = function(editorUi, title, defaultType)
1895{
1896	var plantUmlExample = '@startuml\nskinparam shadowing false\nAlice -> Bob: Authentication Request\nBob --> Alice: Authentication Response\n\nAlice -> Bob: Another authentication Request\nAlice <-- Bob: Another authentication Response\n@enduml';
1897	var insertPoint = editorUi.editor.graph.getFreeInsertPoint();
1898
1899	function parse(text, type, evt)
1900	{
1901		var lines = text.split('\n');
1902
1903		if (type == 'plantUmlPng' || type == 'plantUmlSvg' || type == 'plantUmlTxt')
1904		{
1905			if (editorUi.spinner.spin(document.body, mxResources.get('inserting')))
1906			{
1907				var graph = editorUi.editor.graph;
1908				var format = (type == 'plantUmlTxt') ? 'txt' :
1909					((type == 'plantUmlPng') ? 'png' : 'svg');
1910
1911				function insertPlantUmlImage(text, format, data, w, h)
1912				{
1913					insertPoint = (mxEvent.isAltDown(evt)) ? insertPoint : graph.getCenterInsertPoint(new mxRectangle(0, 0, w, h));
1914					var cell = null;
1915
1916					graph.getModel().beginUpdate();
1917					try
1918					{
1919						cell = (format == 'txt') ?
1920							editorUi.insertAsPreText(data, insertPoint.x, insertPoint.y) :
1921							graph.insertVertex(null, null, null, insertPoint.x, insertPoint.y,
1922								w, h, 'shape=image;noLabel=1;verticalAlign=top;aspect=fixed;imageAspect=0;' +
1923								'image=' + editorUi.convertDataUri(data) + ';')
1924						graph.setAttributeForCell(cell, 'plantUmlData',
1925							JSON.stringify({data: text, format: format},
1926							null, 2));
1927					}
1928					finally
1929					{
1930						graph.getModel().endUpdate();
1931					}
1932
1933					if (cell != null)
1934					{
1935						graph.setSelectionCell(cell);
1936						graph.scrollCellToVisible(cell);
1937					}
1938				};
1939
1940				// Hardcoded response for default settings
1941				if (text == plantUmlExample && format == 'svg')
1942				{
1943					window.setTimeout(function()
1944					{
1945						editorUi.spinner.stop();
1946						insertPlantUmlImage(text, format, 'data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiBjb250ZW50U2NyaXB0VHlwZT0iYXBwbGljYXRpb24vZWNtYXNjcmlwdCIgY29udGVudFN0eWxlVHlwZT0idGV4dC9jc3MiIGhlaWdodD0iMjEycHgiIHByZXNlcnZlQXNwZWN0UmF0aW89Im5vbmUiIHN0eWxlPSJ3aWR0aDoyOTVweDtoZWlnaHQ6MjEycHg7IiB2ZXJzaW9uPSIxLjEiIHZpZXdCb3g9IjAgMCAyOTUgMjEyIiB3aWR0aD0iMjk1cHgiIHpvb21BbmRQYW49Im1hZ25pZnkiPjxkZWZzLz48Zz48bGluZSBzdHlsZT0ic3Ryb2tlOiAjQTgwMDM2OyBzdHJva2Utd2lkdGg6IDEuMDsgc3Ryb2tlLWRhc2hhcnJheTogNS4wLDUuMDsiIHgxPSIzMSIgeDI9IjMxIiB5MT0iMzQuNDg4MyIgeTI9IjE3MS43MzA1Ii8+PGxpbmUgc3R5bGU9InN0cm9rZTogI0E4MDAzNjsgc3Ryb2tlLXdpZHRoOiAxLjA7IHN0cm9rZS1kYXNoYXJyYXk6IDUuMCw1LjA7IiB4MT0iMjY0LjUiIHgyPSIyNjQuNSIgeTE9IjM0LjQ4ODMiIHkyPSIxNzEuNzMwNSIvPjxyZWN0IGZpbGw9IiNGRUZFQ0UiIGhlaWdodD0iMzAuNDg4MyIgc3R5bGU9InN0cm9rZTogI0E4MDAzNjsgc3Ryb2tlLXdpZHRoOiAxLjU7IiB3aWR0aD0iNDciIHg9IjgiIHk9IjMiLz48dGV4dCBmaWxsPSIjMDAwMDAwIiBmb250LWZhbWlseT0ic2Fucy1zZXJpZiIgZm9udC1zaXplPSIxNCIgbGVuZ3RoQWRqdXN0PSJzcGFjaW5nQW5kR2x5cGhzIiB0ZXh0TGVuZ3RoPSIzMyIgeD0iMTUiIHk9IjIzLjUzNTIiPkFsaWNlPC90ZXh0PjxyZWN0IGZpbGw9IiNGRUZFQ0UiIGhlaWdodD0iMzAuNDg4MyIgc3R5bGU9InN0cm9rZTogI0E4MDAzNjsgc3Ryb2tlLXdpZHRoOiAxLjU7IiB3aWR0aD0iNDciIHg9IjgiIHk9IjE3MC43MzA1Ii8+PHRleHQgZmlsbD0iIzAwMDAwMCIgZm9udC1mYW1pbHk9InNhbnMtc2VyaWYiIGZvbnQtc2l6ZT0iMTQiIGxlbmd0aEFkanVzdD0ic3BhY2luZ0FuZEdseXBocyIgdGV4dExlbmd0aD0iMzMiIHg9IjE1IiB5PSIxOTEuMjY1NiI+QWxpY2U8L3RleHQ+PHJlY3QgZmlsbD0iI0ZFRkVDRSIgaGVpZ2h0PSIzMC40ODgzIiBzdHlsZT0ic3Ryb2tlOiAjQTgwMDM2OyBzdHJva2Utd2lkdGg6IDEuNTsiIHdpZHRoPSI0MCIgeD0iMjQ0LjUiIHk9IjMiLz48dGV4dCBmaWxsPSIjMDAwMDAwIiBmb250LWZhbWlseT0ic2Fucy1zZXJpZiIgZm9udC1zaXplPSIxNCIgbGVuZ3RoQWRqdXN0PSJzcGFjaW5nQW5kR2x5cGhzIiB0ZXh0TGVuZ3RoPSIyNiIgeD0iMjUxLjUiIHk9IjIzLjUzNTIiPkJvYjwvdGV4dD48cmVjdCBmaWxsPSIjRkVGRUNFIiBoZWlnaHQ9IjMwLjQ4ODMiIHN0eWxlPSJzdHJva2U6ICNBODAwMzY7IHN0cm9rZS13aWR0aDogMS41OyIgd2lkdGg9IjQwIiB4PSIyNDQuNSIgeT0iMTcwLjczMDUiLz48dGV4dCBmaWxsPSIjMDAwMDAwIiBmb250LWZhbWlseT0ic2Fucy1zZXJpZiIgZm9udC1zaXplPSIxNCIgbGVuZ3RoQWRqdXN0PSJzcGFjaW5nQW5kR2x5cGhzIiB0ZXh0TGVuZ3RoPSIyNiIgeD0iMjUxLjUiIHk9IjE5MS4yNjU2Ij5Cb2I8L3RleHQ+PHBvbHlnb24gZmlsbD0iI0E4MDAzNiIgcG9pbnRzPSIyNTIuNSw2MS43OTg4LDI2Mi41LDY1Ljc5ODgsMjUyLjUsNjkuNzk4OCwyNTYuNSw2NS43OTg4IiBzdHlsZT0ic3Ryb2tlOiAjQTgwMDM2OyBzdHJva2Utd2lkdGg6IDEuMDsiLz48bGluZSBzdHlsZT0ic3Ryb2tlOiAjQTgwMDM2OyBzdHJva2Utd2lkdGg6IDEuMDsiIHgxPSIzMS41IiB4Mj0iMjU4LjUiIHkxPSI2NS43OTg4IiB5Mj0iNjUuNzk4OCIvPjx0ZXh0IGZpbGw9IiMwMDAwMDAiIGZvbnQtZmFtaWx5PSJzYW5zLXNlcmlmIiBmb250LXNpemU9IjEzIiBsZW5ndGhBZGp1c3Q9InNwYWNpbmdBbmRHbHlwaHMiIHRleHRMZW5ndGg9IjE0NyIgeD0iMzguNSIgeT0iNjEuMDU2NiI+QXV0aGVudGljYXRpb24gUmVxdWVzdDwvdGV4dD48cG9seWdvbiBmaWxsPSIjQTgwMDM2IiBwb2ludHM9IjQyLjUsOTEuMTA5NCwzMi41LDk1LjEwOTQsNDIuNSw5OS4xMDk0LDM4LjUsOTUuMTA5NCIgc3R5bGU9InN0cm9rZTogI0E4MDAzNjsgc3Ryb2tlLXdpZHRoOiAxLjA7Ii8+PGxpbmUgc3R5bGU9InN0cm9rZTogI0E4MDAzNjsgc3Ryb2tlLXdpZHRoOiAxLjA7IHN0cm9rZS1kYXNoYXJyYXk6IDIuMCwyLjA7IiB4MT0iMzYuNSIgeDI9IjI2My41IiB5MT0iOTUuMTA5NCIgeTI9Ijk1LjEwOTQiLz48dGV4dCBmaWxsPSIjMDAwMDAwIiBmb250LWZhbWlseT0ic2Fucy1zZXJpZiIgZm9udC1zaXplPSIxMyIgbGVuZ3RoQWRqdXN0PSJzcGFjaW5nQW5kR2x5cGhzIiB0ZXh0TGVuZ3RoPSIxNTciIHg9IjQ4LjUiIHk9IjkwLjM2NzIiPkF1dGhlbnRpY2F0aW9uIFJlc3BvbnNlPC90ZXh0Pjxwb2x5Z29uIGZpbGw9IiNBODAwMzYiIHBvaW50cz0iMjUyLjUsMTIwLjQxOTksMjYyLjUsMTI0LjQxOTksMjUyLjUsMTI4LjQxOTksMjU2LjUsMTI0LjQxOTkiIHN0eWxlPSJzdHJva2U6ICNBODAwMzY7IHN0cm9rZS13aWR0aDogMS4wOyIvPjxsaW5lIHN0eWxlPSJzdHJva2U6ICNBODAwMzY7IHN0cm9rZS13aWR0aDogMS4wOyIgeDE9IjMxLjUiIHgyPSIyNTguNSIgeTE9IjEyNC40MTk5IiB5Mj0iMTI0LjQxOTkiLz48dGV4dCBmaWxsPSIjMDAwMDAwIiBmb250LWZhbWlseT0ic2Fucy1zZXJpZiIgZm9udC1zaXplPSIxMyIgbGVuZ3RoQWRqdXN0PSJzcGFjaW5nQW5kR2x5cGhzIiB0ZXh0TGVuZ3RoPSIxOTkiIHg9IjM4LjUiIHk9IjExOS42Nzc3Ij5Bbm90aGVyIGF1dGhlbnRpY2F0aW9uIFJlcXVlc3Q8L3RleHQ+PHBvbHlnb24gZmlsbD0iI0E4MDAzNiIgcG9pbnRzPSI0Mi41LDE0OS43MzA1LDMyLjUsMTUzLjczMDUsNDIuNSwxNTcuNzMwNSwzOC41LDE1My43MzA1IiBzdHlsZT0ic3Ryb2tlOiAjQTgwMDM2OyBzdHJva2Utd2lkdGg6IDEuMDsiLz48bGluZSBzdHlsZT0ic3Ryb2tlOiAjQTgwMDM2OyBzdHJva2Utd2lkdGg6IDEuMDsgc3Ryb2tlLWRhc2hhcnJheTogMi4wLDIuMDsiIHgxPSIzNi41IiB4Mj0iMjYzLjUiIHkxPSIxNTMuNzMwNSIgeTI9IjE1My43MzA1Ii8+PHRleHQgZmlsbD0iIzAwMDAwMCIgZm9udC1mYW1pbHk9InNhbnMtc2VyaWYiIGZvbnQtc2l6ZT0iMTMiIGxlbmd0aEFkanVzdD0ic3BhY2luZ0FuZEdseXBocyIgdGV4dExlbmd0aD0iMjA5IiB4PSI0OC41IiB5PSIxNDguOTg4MyI+QW5vdGhlciBhdXRoZW50aWNhdGlvbiBSZXNwb25zZTwvdGV4dD48IS0tTUQ1PVs3ZjNlNGQwYzkwMWVmZGJjNTdlYjQ0MjQ5YTNiODE5N10KQHN0YXJ0dW1sDQpza2lucGFyYW0gc2hhZG93aW5nIGZhbHNlDQpBbGljZSAtPiBCb2I6IEF1dGhlbnRpY2F0aW9uIFJlcXVlc3QNCkJvYiAtIC0+IEFsaWNlOiBBdXRoZW50aWNhdGlvbiBSZXNwb25zZQ0KDQpBbGljZSAtPiBCb2I6IEFub3RoZXIgYXV0aGVudGljYXRpb24gUmVxdWVzdA0KQWxpY2UgPC0gLSBCb2I6IEFub3RoZXIgYXV0aGVudGljYXRpb24gUmVzcG9uc2UNCkBlbmR1bWwNCgpQbGFudFVNTCB2ZXJzaW9uIDEuMjAyMC4wMihTdW4gTWFyIDAxIDA0OjIyOjA3IENTVCAyMDIwKQooTUlUIHNvdXJjZSBkaXN0cmlidXRpb24pCkphdmEgUnVudGltZTogT3BlbkpESyBSdW50aW1lIEVudmlyb25tZW50CkpWTTogT3BlbkpESyA2NC1CaXQgU2VydmVyIFZNCkphdmEgVmVyc2lvbjogMTIrMzMKT3BlcmF0aW5nIFN5c3RlbTogTWFjIE9TIFgKRGVmYXVsdCBFbmNvZGluZzogVVRGLTgKTGFuZ3VhZ2U6IGVuCkNvdW50cnk6IFVTCi0tPjwvZz48L3N2Zz4=',
1947							295, 212);
1948					}, 200);
1949
1950				}
1951				else
1952				{
1953					editorUi.generatePlantUmlImage(text, format, function(data, w, h)
1954					{
1955						editorUi.spinner.stop();
1956						insertPlantUmlImage(text, format, data, w, h);
1957
1958					}, function(e)
1959					{
1960						editorUi.handleError(e);
1961					});
1962				}
1963			}
1964		}
1965		else if (type == 'mermaid')
1966		{
1967			if (editorUi.spinner.spin(document.body, mxResources.get('inserting')))
1968			{
1969				var graph = editorUi.editor.graph;
1970
1971				editorUi.generateMermaidImage(text, format, function(data, w, h)
1972				{
1973					insertPoint = (mxEvent.isAltDown(evt)) ? insertPoint : graph.getCenterInsertPoint(new mxRectangle(0, 0, w, h));
1974					editorUi.spinner.stop();
1975					var cell = null;
1976
1977					graph.getModel().beginUpdate();
1978					try
1979					{
1980						cell = graph.insertVertex(null, null, null, insertPoint.x, insertPoint.y,
1981								w, h, 'shape=image;noLabel=1;verticalAlign=top;imageAspect=1;' +
1982								'image=' + data + ';')
1983						graph.setAttributeForCell(cell, 'mermaidData',
1984							JSON.stringify({data: text, config:
1985							EditorUi.defaultMermaidConfig}, null, 2));
1986					}
1987					finally
1988					{
1989						graph.getModel().endUpdate();
1990					}
1991
1992					if (cell != null)
1993					{
1994						graph.setSelectionCell(cell);
1995						graph.scrollCellToVisible(cell);
1996					}
1997				}, function(e)
1998				{
1999					editorUi.handleError(e);
2000				});
2001			}
2002		}
2003		else if (type == 'table')
2004		{
2005			var tableCell = null;
2006			var cells = [];
2007			var dx = 0;
2008			var pkMap = {};
2009
2010			//First pass to find primary keys
2011			for (var i = 0; i < lines.length; i++)
2012			{
2013				var line = mxUtils.trim(lines[i]);
2014
2015				if (line.substring(0, 11).toLowerCase() == 'primary key')
2016				{
2017					var pk = line.match(/\((.+)\)/);
2018
2019					if (pk && pk[1])
2020					{
2021						pkMap[pk[1]] = true;
2022					}
2023
2024					lines.splice(i, 1);
2025				}
2026				else if (line.toLowerCase().indexOf('primary key') > 0)
2027				{
2028					pkMap[line.split(' ')[0]] = true;
2029					lines[i] = mxUtils.trim(line.replace(/primary key/i, ''));
2030				}
2031			}
2032
2033			for (var i = 0; i < lines.length; i++)
2034			{
2035				var tmp = mxUtils.trim(lines[i]);
2036
2037				if (tmp.substring(0, 12).toLowerCase() == 'create table')
2038				{
2039					var name = mxUtils.trim(tmp.substring(12));
2040
2041					if (name.charAt(name.length - 1) == '(')
2042					{
2043						name = mxUtils.trim(name.substring(0, name.length - 1));
2044					}
2045
2046					tableCell = new mxCell(name, new mxGeometry(dx, 0, 160, 40),
2047						'shape=table;startSize=30;container=1;collapsible=1;childLayout=tableLayout;fixedRows=1;rowLines=0;fontStyle=1;align=center;resizeLast=1;');
2048					tableCell.vertex = true;
2049					cells.push(tableCell);
2050
2051					var size = editorUi.editor.graph.getPreferredSizeForCell(rowCell);
2052
2053		   			if (size != null)
2054		   			{
2055		   				tableCell.geometry.width = size.width + 10;
2056		   			}
2057				}
2058				else if (tableCell != null && tmp.charAt(0) == ')')
2059				{
2060					dx += tableCell.geometry.width + 40;
2061					tableCell = null;
2062				}
2063				else if (tmp != '(' && tableCell != null)
2064				{
2065					var name = tmp.substring(0, (tmp.charAt(tmp.length - 1) == ',') ? tmp.length - 1 : tmp.length);
2066
2067					var pk = pkMap[name.split(' ')[0]];
2068					var rowCell = new mxCell('', new mxGeometry(0, 0, 160, 30),
2069						'shape=partialRectangle;collapsible=0;dropTarget=0;pointerEvents=0;fillColor=none;' +
2070						'points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=' +
2071						(pk ? '1' : '0') + ';');
2072					rowCell.vertex = true;
2073
2074					var left = new mxCell(pk ? 'PK' : '', new mxGeometry(0, 0, 30, 30),
2075						'shape=partialRectangle;overflow=hidden;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;' + (pk ? 'fontStyle=1;' : ''));
2076					left.vertex = true;
2077					rowCell.insert(left);
2078
2079					var right = new mxCell(name, new mxGeometry(30, 0, 130, 30),
2080						'shape=partialRectangle;overflow=hidden;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;' + (pk ? 'fontStyle=5;' : ''));
2081					right.vertex = true;
2082					rowCell.insert(right);
2083
2084		   			var size = editorUi.editor.graph.getPreferredSizeForCell(right);
2085
2086		   			if (size != null && tableCell.geometry.width < size.width + 30)
2087		   			{
2088		   				tableCell.geometry.width = Math.min(320, Math.max(tableCell.geometry.width, size.width + 30));
2089		   			}
2090
2091		   			tableCell.insert(rowCell, pk? 0 : null);
2092		   			tableCell.geometry.height += 30;
2093				}
2094			}
2095
2096			if (cells.length > 0)
2097			{
2098				var graph = editorUi.editor.graph;
2099				insertPoint = (mxEvent.isAltDown(evt)) ? insertPoint :
2100					graph.getCenterInsertPoint(graph.getBoundingBoxFromGeometry(cells, true));
2101				graph.setSelectionCells(graph.importCells(cells, insertPoint.x, insertPoint.y));
2102				graph.scrollCellToVisible(graph.getSelectionCell());
2103			}
2104		}
2105		else if (type == 'list')
2106		{
2107			if (lines.length > 0)
2108			{
2109				var graph = editorUi.editor.graph;
2110				var listCell = null;
2111				var cells = [];
2112				var x0 = 0;
2113
2114				for (var i = 0; i < lines.length; i++)
2115				{
2116					if (lines[i].charAt(0) != ';')
2117					{
2118						if (lines[i].length == 0)
2119						{
2120							listCell = null;
2121						}
2122						else
2123						{
2124							if (listCell == null)
2125							{
2126								listCell = new mxCell(lines[i], new mxGeometry(x0, 0, 160, 26 + 4),
2127									'swimlane;fontStyle=1;childLayout=stackLayout;horizontal=1;startSize=26;horizontalStack=0;resizeParent=1;resizeParentMax=0;resizeLast=0;collapsible=1;marginBottom=0;');
2128								listCell.vertex = true;
2129								cells.push(listCell);
2130
2131								var size = graph.getPreferredSizeForCell(listCell);
2132
2133					   			if (size != null && listCell.geometry.width < size.width + 10)
2134					   			{
2135					   				listCell.geometry.width = size.width + 10;
2136					   			}
2137
2138					   			x0 += listCell.geometry.width + 40;
2139							}
2140							else if (lines[i] == '--')
2141							{
2142								var divider = new mxCell('', new mxGeometry(0, 0, 40, 8), 'line;strokeWidth=1;fillColor=none;align=left;verticalAlign=middle;spacingTop=-1;spacingLeft=3;spacingRight=3;rotatable=0;labelPosition=right;points=[];portConstraint=eastwest;');
2143								divider.vertex = true;
2144								listCell.geometry.height += divider.geometry.height;
2145								listCell.insert(divider);
2146							}
2147							else if (lines[i].length > 0)
2148							{
2149								var field = new mxCell(lines[i], new mxGeometry(0, 0, 60, 26), 'text;strokeColor=none;fillColor=none;align=left;verticalAlign=top;spacingLeft=4;spacingRight=4;overflow=hidden;rotatable=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;');
2150								field.vertex = true;
2151
2152								var size = graph.getPreferredSizeForCell(field);
2153
2154					   			if (size != null && field.geometry.width < size.width)
2155					   			{
2156					   				field.geometry.width = size.width;
2157					   			}
2158
2159					   			listCell.geometry.width = Math.max(listCell.geometry.width, field.geometry.width);
2160								listCell.geometry.height += field.geometry.height;
2161								listCell.insert(field);
2162							}
2163						}
2164					}
2165				}
2166
2167				if (cells.length > 0)
2168				{
2169					insertPoint = (mxEvent.isAltDown(evt)) ? insertPoint :
2170						graph.getCenterInsertPoint(graph.getBoundingBoxFromGeometry(cells, true));
2171
2172					graph.getModel().beginUpdate();
2173					try
2174					{
2175						cells = graph.importCells(cells, insertPoint.x, insertPoint.y);
2176						var inserted = [];
2177
2178						for (var i = 0; i < cells.length; i++)
2179						{
2180							inserted.push(cells[i]);
2181							inserted = inserted.concat(cells[i].children);
2182						}
2183
2184						graph.fireEvent(new mxEventObject('cellsInserted', 'cells', inserted));
2185					}
2186					finally
2187					{
2188						graph.getModel().endUpdate();
2189					}
2190
2191					graph.setSelectionCells(cells);
2192					graph.scrollCellToVisible(graph.getSelectionCell());
2193				}
2194			}
2195		}
2196		else
2197		{
2198			var vertices = new Object();
2199			var cells = [];
2200
2201			function getOrCreateVertex(id)
2202			{
2203				var vertex = vertices[id];
2204
2205				if (vertex == null)
2206				{
2207					vertex = new mxCell(id, new mxGeometry(0, 0, 80, 30), 'whiteSpace=wrap;html=1;');
2208					vertex.vertex = true;
2209					vertices[id] = vertex;
2210					cells.push(vertex);
2211				}
2212
2213				return vertex;
2214			};
2215
2216			for (var i = 0; i < lines.length; i++)
2217			{
2218				if (lines[i].charAt(0) != ';')
2219				{
2220					var values = lines[i].split('->');
2221
2222					if (values.length >= 2)
2223					{
2224						var source = getOrCreateVertex(values[0]);
2225						var target = getOrCreateVertex(values[values.length - 1]);
2226
2227						var edge = new mxCell((values.length > 2) ? values[1] : '', new mxGeometry());
2228						edge.edge = true;
2229						source.insertEdge(edge, true);
2230						target.insertEdge(edge, false);
2231						cells.push(edge);
2232					}
2233				}
2234			}
2235
2236			if (cells.length > 0)
2237			{
2238				var container = document.createElement('div');
2239				container.style.visibility = 'hidden';
2240				document.body.appendChild(container);
2241
2242				// Temporary graph for running the layout
2243				var graph = new Graph(container);
2244
2245				graph.getModel().beginUpdate();
2246				try
2247				{
2248					cells = graph.importCells(cells);
2249
2250					for (var i = 0; i < cells.length; i++)
2251					{
2252						if (graph.getModel().isVertex(cells[i]))
2253						{
2254							var size = graph.getPreferredSizeForCell(cells[i]);
2255							cells[i].geometry.width = Math.max(cells[i].geometry.width, size.width);
2256							cells[i].geometry.height = Math.max(cells[i].geometry.height, size.height);
2257						}
2258					}
2259
2260					var layout = new mxFastOrganicLayout(graph);
2261					layout.disableEdgeStyle = false;
2262					layout.forceConstant = 120;
2263					layout.execute(graph.getDefaultParent());
2264
2265					var edgeLayout = new mxParallelEdgeLayout(graph);
2266					edgeLayout.spacing = 20;
2267					edgeLayout.execute(graph.getDefaultParent());
2268				}
2269				finally
2270				{
2271					graph.getModel().endUpdate();
2272				}
2273
2274				graph.clearCellOverlays();
2275
2276				// Copy to actual graph
2277				var inserted = [];
2278
2279				editorUi.editor.graph.getModel().beginUpdate();
2280				try
2281				{
2282					cells = graph.getModel().getChildren(graph.getDefaultParent());
2283					insertPoint = (mxEvent.isAltDown(evt)) ? insertPoint :
2284						editorUi.editor.graph.getCenterInsertPoint(graph.getBoundingBoxFromGeometry(cells, true));
2285					inserted = editorUi.editor.graph.importCells(cells, insertPoint.x, insertPoint.y)
2286					editorUi.editor.graph.fireEvent(new mxEventObject('cellsInserted', 'cells', inserted));
2287				}
2288				finally
2289				{
2290					editorUi.editor.graph.getModel().endUpdate();
2291				}
2292
2293				editorUi.editor.graph.setSelectionCells(inserted);
2294				editorUi.editor.graph.scrollCellToVisible(editorUi.editor.graph.getSelectionCell());
2295				graph.destroy();
2296				container.parentNode.removeChild(container);
2297			}
2298		}
2299	};
2300
2301	var div = document.createElement('div');
2302	div.style.textAlign = 'right';
2303
2304	var textarea = document.createElement('textarea');
2305	textarea.style.resize = 'none';
2306	textarea.style.width = '100%';
2307	textarea.style.height = '354px';
2308	textarea.style.marginBottom = '16px';
2309
2310	var typeSelect = document.createElement('select');
2311
2312	if (defaultType == 'formatSql' || defaultType == 'mermaid')
2313	{
2314		typeSelect.style.display = 'none';
2315	}
2316
2317	var listOption = document.createElement('option');
2318	listOption.setAttribute('value', 'list');
2319	mxUtils.write(listOption, mxResources.get('list'));
2320
2321	if (defaultType != 'plantUml')
2322	{
2323		typeSelect.appendChild(listOption);
2324	}
2325
2326	if (defaultType == null || defaultType == 'fromText')
2327	{
2328		listOption.setAttribute('selected', 'selected');
2329	}
2330
2331	var tableOption = document.createElement('option');
2332	tableOption.setAttribute('value', 'table');
2333	mxUtils.write(tableOption, mxResources.get('formatSql'));
2334
2335	if (defaultType == 'formatSql')
2336	{
2337		typeSelect.appendChild(tableOption);
2338		tableOption.setAttribute('selected', 'selected');
2339	}
2340
2341	var mermaidOption = document.createElement('option');
2342	mermaidOption.setAttribute('value', 'mermaid');
2343	mxUtils.write(mermaidOption, mxResources.get('formatSql'));
2344
2345	if (defaultType == 'mermaid')
2346	{
2347		typeSelect.appendChild(mermaidOption);
2348		mermaidOption.setAttribute('selected', 'selected');
2349	}
2350
2351	var diagramOption = document.createElement('option');
2352	diagramOption.setAttribute('value', 'diagram');
2353	mxUtils.write(diagramOption, mxResources.get('diagram'));
2354
2355	if (defaultType != 'plantUml')
2356	{
2357		typeSelect.appendChild(diagramOption);
2358	}
2359
2360	var plantUmlSvgOption = document.createElement('option');
2361	plantUmlSvgOption.setAttribute('value', 'plantUmlSvg');
2362	mxUtils.write(plantUmlSvgOption, mxResources.get('plantUml') + ' (' + mxResources.get('formatSvg') + ')');
2363
2364	if (defaultType == 'plantUml')
2365	{
2366		plantUmlSvgOption.setAttribute('selected', 'selected');
2367	}
2368
2369	var plantUmlPngOption = document.createElement('option');
2370	plantUmlPngOption.setAttribute('value', 'plantUmlPng');
2371	mxUtils.write(plantUmlPngOption, mxResources.get('plantUml') + ' (' + mxResources.get('formatPng') + ')');
2372
2373	var plantUmlTxtOption = document.createElement('option');
2374	plantUmlTxtOption.setAttribute('value', 'plantUmlTxt');
2375	mxUtils.write(plantUmlTxtOption, mxResources.get('plantUml') + ' (' + mxResources.get('text') + ')');
2376
2377	// Disabled for invalid hosts via CORS headers
2378	if (EditorUi.enablePlantUml && Graph.fileSupport &&
2379		!editorUi.isOffline() && defaultType == 'plantUml')
2380	{
2381		typeSelect.appendChild(plantUmlSvgOption);
2382		typeSelect.appendChild(plantUmlPngOption);
2383		typeSelect.appendChild(plantUmlTxtOption);
2384	}
2385
2386	function getDefaultValue()
2387	{
2388		if (typeSelect.value == 'list')
2389		{
2390			return 'Person\n-name: String\n-birthDate: Date\n--\n+getName(): String\n+setName(String): void\n+isBirthday(): boolean\n\n' +
2391				'Address\n-street: String\n-city: String\n-state: String';
2392		}
2393		else if (typeSelect.value == 'mermaid')
2394		{
2395			return 'graph TD;\n  A-->B;\n  A-->C;\n  B-->D;\n  C-->D;';
2396		}
2397		else if (typeSelect.value == 'table')
2398		{
2399			return 'CREATE TABLE Suppliers\n(\nsupplier_id int NOT NULL PRIMARY KEY,\n' +
2400				'supplier_name char(50) NOT NULL,\ncontact_name char(50),\n);\n' +
2401				'CREATE TABLE Customers\n(\ncustomer_id int NOT NULL PRIMARY KEY,\n' +
2402				'customer_name char(50) NOT NULL,\naddress char(50),\n' +
2403				'city char(50),\nstate char(25),\nzip_code char(10)\n);\n';
2404		}
2405		else if (typeSelect.value == 'plantUmlPng')
2406		{
2407			return '@startuml\nskinparam backgroundcolor transparent\nskinparam shadowing false\nAlice -> Bob: Authentication Request\nBob --> Alice: Authentication Response\n\nAlice -> Bob: Another authentication Request\nAlice <-- Bob: Another authentication Response\n@enduml';
2408		}
2409		else if (typeSelect.value == 'plantUmlSvg' || typeSelect.value == 'plantUmlTxt')
2410		{
2411			return plantUmlExample;
2412		}
2413		else
2414		{
2415			return ';Example:\na->b\nb->edge label->c\nc->a\n';
2416		}
2417	};
2418
2419	var defaultValue = getDefaultValue();
2420	textarea.value = defaultValue;
2421	div.appendChild(textarea);
2422
2423	this.init = function()
2424	{
2425		textarea.focus();
2426	};
2427
2428	// Enables dropping files
2429	if (Graph.fileSupport)
2430	{
2431		function handleDrop(evt)
2432		{
2433		    evt.stopPropagation();
2434		    evt.preventDefault();
2435
2436		    if (evt.dataTransfer.files.length > 0)
2437		    {
2438		    	var file = evt.dataTransfer.files[0];
2439
2440				var reader = new FileReader();
2441				reader.onload = function(e) { textarea.value = e.target.result; };
2442				reader.readAsText(file);
2443    		}
2444		};
2445
2446		function handleDragOver(evt)
2447		{
2448			evt.stopPropagation();
2449			evt.preventDefault();
2450		};
2451
2452		// Setup the dnd listeners.
2453		textarea.addEventListener('dragover', handleDragOver, false);
2454		textarea.addEventListener('drop', handleDrop, false);
2455	}
2456
2457	div.appendChild(typeSelect);
2458
2459	mxEvent.addListener(typeSelect, 'change', function()
2460	{
2461		var newDefaultValue = getDefaultValue();
2462
2463		if (textarea.value.length == 0 || textarea.value == defaultValue)
2464		{
2465			defaultValue = newDefaultValue;
2466			textarea.value = defaultValue;
2467		}
2468	});
2469
2470	if (!editorUi.isOffline() && (defaultType == 'mermaid' || defaultType == 'plantUml'))
2471	{
2472		var helpBtn = mxUtils.button(mxResources.get('help'), function()
2473		{
2474			editorUi.openLink((defaultType == 'mermaid') ?
2475				'https://mermaid-js.github.io/mermaid/#/' :
2476				'https://plantuml.com/');
2477		});
2478
2479		helpBtn.className = 'geBtn';
2480		div.appendChild(helpBtn);
2481	}
2482
2483	var cancelBtn = mxUtils.button(mxResources.get('close'), function()
2484	{
2485		if (textarea.value == defaultValue)
2486		{
2487			editorUi.hideDialog();
2488		}
2489		else
2490		{
2491			editorUi.confirm(mxResources.get('areYouSure'), function()
2492			{
2493				editorUi.hideDialog();
2494			});
2495		}
2496	});
2497
2498	cancelBtn.className = 'geBtn';
2499
2500	if (editorUi.editor.cancelFirst)
2501	{
2502		div.appendChild(cancelBtn);
2503	}
2504
2505	var okBtn = mxUtils.button(mxResources.get('insert'), function(evt)
2506	{
2507		editorUi.hideDialog();
2508		parse(textarea.value, typeSelect.value, evt);
2509	});
2510	div.appendChild(okBtn);
2511
2512	okBtn.className = 'geBtn gePrimaryBtn';
2513
2514	if (!editorUi.editor.cancelFirst)
2515	{
2516		div.appendChild(cancelBtn);
2517	}
2518
2519	this.container = div;
2520};
2521
2522/**
2523 * Constructs a new dialog for creating files from templates.
2524 */
2525var NewDialog = function(editorUi, compact, showName, callback, createOnly, cancelCallback,
2526		leftHighlight, rightHighlight, rightHighlightBorder, itemPadding, templateFile,
2527		recentDocsCallback, searchDocsCallback, openExtDocCallback, showImport, createButtonLabel, customTempCallback, withoutType)
2528{
2529	var ww = window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth;
2530	var smallScreen = ww < 500;
2531	showName = (showName != null) ? showName : true;
2532	createOnly = (createOnly != null) ? createOnly : false;
2533	leftHighlight = (leftHighlight != null) ? leftHighlight : '#ebf2f9';
2534	rightHighlight = (rightHighlight != null) ? rightHighlight : (Editor.isDarkMode() ? '#a2a2a2' : '#e6eff8');
2535	rightHighlightBorder = (rightHighlightBorder != null) ? rightHighlightBorder : (Editor.isDarkMode() ? '1px dashed #00a8ff' : '1px solid #ccd9ea');
2536	templateFile = (templateFile != null) ? templateFile : EditorUi.templateFile;
2537
2538	var outer = document.createElement('div');
2539	outer.style.userSelect = 'none';
2540	outer.style.height = '100%';
2541
2542	var header = document.createElement('div');
2543	header.style.whiteSpace = 'nowrap';
2544	header.style.height = '46px';
2545
2546	if (showName)
2547	{
2548		outer.appendChild(header);
2549	}
2550
2551	var logo = document.createElement('img');
2552	logo.setAttribute('border', '0');
2553	logo.setAttribute('align', 'absmiddle');
2554	logo.style.width = '40px';
2555	logo.style.height = '40px';
2556	logo.style.marginRight = '10px';
2557	logo.style.paddingBottom = '4px';
2558
2559	if (editorUi.mode == App.MODE_GOOGLE)
2560	{
2561		logo.src = IMAGE_PATH + '/google-drive-logo.svg';
2562	}
2563	else if (editorUi.mode == App.MODE_DROPBOX)
2564	{
2565		logo.src = IMAGE_PATH + '/dropbox-logo.svg';
2566	}
2567	else if (editorUi.mode == App.MODE_ONEDRIVE)
2568	{
2569		logo.src = IMAGE_PATH + '/onedrive-logo.svg';
2570	}
2571	else if (editorUi.mode == App.MODE_GITHUB)
2572	{
2573		logo.src = IMAGE_PATH + '/github-logo.svg';
2574	}
2575	else if (editorUi.mode == App.MODE_GITLAB)
2576	{
2577		logo.src = IMAGE_PATH + '/gitlab-logo.svg';
2578	}
2579	else if (editorUi.mode == App.MODE_NOTION)
2580	{
2581		logo.src = IMAGE_PATH + '/notion-logo.svg';
2582	}
2583	else if (editorUi.mode == App.MODE_TRELLO)
2584	{
2585		logo.src = IMAGE_PATH + '/trello-logo.svg';
2586	}
2587	else if (editorUi.mode == App.MODE_BROWSER)
2588	{
2589		logo.src = IMAGE_PATH + '/osa_database.png';
2590	}
2591	else
2592	{
2593		logo.src = IMAGE_PATH + '/osa_drive-harddisk.png';
2594	}
2595
2596	if (!compact && !smallScreen && showName)
2597	{
2598		header.appendChild(logo);
2599	}
2600
2601	if (showName)
2602	{
2603		mxUtils.write(header, (smallScreen? mxResources.get('name') : ((editorUi.mode == null || editorUi.mode == App.MODE_GOOGLE ||
2604				editorUi.mode == App.MODE_BROWSER) ? mxResources.get('diagramName') : mxResources.get('filename'))) + ':');
2605	}
2606
2607	var ext = '.drawio';
2608
2609	if (editorUi.mode == App.MODE_GOOGLE && editorUi.drive != null)
2610	{
2611		ext = editorUi.drive.extension;
2612	}
2613	else if (editorUi.mode == App.MODE_DROPBOX && editorUi.dropbox != null)
2614	{
2615		ext = editorUi.dropbox.extension;
2616	}
2617	else if (editorUi.mode == App.MODE_ONEDRIVE && editorUi.oneDrive != null)
2618	{
2619		ext = editorUi.oneDrive.extension;
2620	}
2621	else if (editorUi.mode == App.MODE_GITHUB && editorUi.gitHub != null)
2622	{
2623		ext = editorUi.gitHub.extension;
2624	}
2625	else if (editorUi.mode == App.MODE_GITLAB && editorUi.gitLab != null)
2626	{
2627		ext = editorUi.gitLab.extension;
2628	}
2629	else if (editorUi.mode == App.MODE_NOTION && editorUi.notion != null)
2630	{
2631		ext = editorUi.notion.extension;
2632	}
2633	else if (editorUi.mode == App.MODE_TRELLO && editorUi.trello != null)
2634	{
2635		ext = editorUi.trello.extension;
2636	}
2637
2638	var nameInput = document.createElement('input');
2639	nameInput.setAttribute('value', editorUi.defaultFilename + ext);
2640	nameInput.style.marginLeft = '10px';
2641	nameInput.style.width = (compact || smallScreen) ? '144px' : '244px';
2642
2643	this.init = function()
2644	{
2645		if (showName)
2646		{
2647			nameInput.focus();
2648
2649			if (mxClient.IS_GC || mxClient.IS_FF || document.documentMode >= 5)
2650			{
2651				nameInput.select();
2652			}
2653			else
2654			{
2655				document.execCommand('selectAll', false, null);
2656			}
2657		}
2658
2659		if (div.parentNode != null && div.parentNode.parentNode != null)
2660		{
2661			mxEvent.addGestureListeners(div.parentNode.parentNode, mxUtils.bind(this, function(evt)
2662			{
2663				editorUi.sidebar.hideTooltip();
2664			}), null, null);
2665		}
2666	};
2667
2668	// Adds filetype dropdown
2669	if (showName)
2670	{
2671		header.appendChild(nameInput);
2672
2673		if (withoutType)
2674		{
2675			nameInput.style.width = (compact || smallScreen) ? '350px' : '450px';
2676		}
2677		else
2678		{
2679			if (editorUi.editor.diagramFileTypes != null)
2680			{
2681				var typeSelect = FilenameDialog.createFileTypes(editorUi, nameInput, editorUi.editor.diagramFileTypes);
2682				typeSelect.style.marginLeft = '6px';
2683				typeSelect.style.width = (compact || smallScreen) ? '80px' : '180px';
2684				header.appendChild(typeSelect);
2685			}
2686
2687			if (editorUi.editor.fileExtensions != null)
2688			{
2689				var hint = FilenameDialog.createTypeHint(editorUi,
2690					nameInput, editorUi.editor.fileExtensions);
2691				hint.style.marginTop = '12px';
2692
2693				header.appendChild(hint);
2694			}
2695		}
2696	}
2697
2698	var hasTabs = false;
2699	var i0 = 0;
2700
2701	// Dynamic loading
2702	function addTemplates(smallSize)
2703	{
2704		//smallSize: Reduce template button size to fit 4 in a row
2705		if (smallSize != null)
2706		{
2707			w = h = smallSize? 135 : 140;
2708		}
2709
2710		var first = true;
2711
2712		//TODO support paging of external templates
2713		if (templates != null)
2714		{
2715			while (i0 < templates.length && (first || mxUtils.mod(i0, 30) != 0))
2716			{
2717				var tmp = templates[i0++];
2718				var btn = addButton(tmp.url, tmp.libs, tmp.title, tmp.tooltip? tmp.tooltip : tmp.title,
2719					tmp.select, tmp.imgUrl, tmp.info, tmp.onClick, tmp.preview, tmp.noImg, tmp.clibs);
2720
2721				if (first)
2722				{
2723					btn.click();
2724				}
2725
2726				first = false;
2727			}
2728		}
2729	};
2730
2731	var spinner = new Spinner({
2732		lines: 12, // The number of lines to draw
2733		length: 10, // The length of each line
2734		width: 5, // The line thickness
2735		radius: 10, // The radius of the inner circle
2736		rotate: 0, // The rotation offset
2737		color: '#000', // #rgb or #rrggbb
2738		speed: 1.5, // Rounds per second
2739		trail: 60, // Afterglow percentage
2740		shadow: false, // Whether to render a shadow
2741		hwaccel: false, // Whether to use hardware acceleration
2742		top: '40%',
2743		zIndex: 2e9 // The z-index (defaults to 2000000000)
2744	});
2745
2746	var createButton = mxUtils.button(createButtonLabel || mxResources.get('create'), function()
2747	{
2748		createButton.setAttribute('disabled', 'disabled');
2749		create();
2750		createButton.removeAttribute('disabled');
2751	});
2752
2753	createButton.className = 'geBtn gePrimaryBtn';
2754
2755	if (recentDocsCallback || searchDocsCallback)
2756	{
2757		var tabsEl = [];
2758		var oldTemplates = null, origCategories = null, origCustomCatCount = null;
2759
2760		var setActiveTab = function(index)
2761		{
2762			createButton.setAttribute('disabled', 'disabled');
2763
2764			for (var i = 0; i < tabsEl.length; i++)
2765			{
2766				if (i == index)
2767					tabsEl[i].className = 'geBtn gePrimaryBtn';
2768				else
2769					tabsEl[i].className = 'geBtn';
2770			}
2771		}
2772
2773		hasTabs = true;
2774		var tabs = document.createElement('div');
2775		tabs.style.whiteSpace = 'nowrap';
2776		tabs.style.height = '30px';
2777		outer.appendChild(tabs);
2778
2779		var templatesTab = mxUtils.button(mxResources.get('Templates', null, 'Templates'), function()
2780		{
2781			list.style.display = '';
2782			searchBox.style.display = '';
2783			div.style.left = '160px';
2784			setActiveTab(0);
2785
2786			div.scrollTop = 0;
2787			div.innerHTML = '';
2788			i0 = 0;
2789
2790			if (oldTemplates != templates)
2791			{
2792				templates = oldTemplates;
2793				categories = origCategories;
2794				customCatCount = origCustomCatCount;
2795				list.innerHTML = '';
2796				initUi();
2797				oldTemplates = null;
2798			}
2799		});
2800
2801		tabsEl.push(templatesTab);
2802		tabs.appendChild(templatesTab);
2803
2804		var getExtTemplates = function(isSearch)
2805		{
2806			list.style.display = 'none';
2807			searchBox.style.display = 'none';
2808			div.style.left = '30px';
2809
2810			setActiveTab(isSearch? -1 : 1); //deselect all of them if isSearch
2811
2812			if (oldTemplates == null)
2813			{
2814				oldTemplates = templates;
2815			}
2816
2817			div.scrollTop = 0;
2818			div.innerHTML = '';
2819			spinner.spin(div);
2820
2821			var callback2 = function(docList, errorMsg, searchImportCats)
2822			{
2823				i0 = 0;
2824				spinner.stop();
2825				templates = docList;
2826				searchImportCats = searchImportCats || {};
2827				var importListsCount = 0;
2828
2829				for (var cat in searchImportCats)
2830				{
2831					importListsCount += searchImportCats[cat].length;
2832				}
2833
2834				if (errorMsg)
2835				{
2836					div.innerHTML = errorMsg;
2837				}
2838				else if (docList.length == 0 && importListsCount == 0)
2839				{
2840					div.innerHTML = mxUtils.htmlEntities(mxResources.get('noDiagrams', null, 'No Diagrams Found'));
2841				}
2842				else
2843				{
2844					div.innerHTML = '';
2845
2846					if (importListsCount > 0)
2847					{
2848						list.style.display = '';
2849						div.style.left = '160px';
2850						list.innerHTML = '';
2851
2852						customCatCount = 0;
2853						categories = {'draw.io': docList};
2854
2855						for (var cat in searchImportCats)
2856						{
2857							categories[cat] = searchImportCats[cat];
2858						}
2859
2860						initUi();
2861					}
2862					else
2863					{
2864						addTemplates(true);
2865					}
2866				}
2867			}
2868
2869			if (isSearch)
2870			{
2871				searchDocsCallback(searchInput.value, callback2);
2872			}
2873			else
2874			{
2875				recentDocsCallback(callback2);
2876			}
2877		}
2878
2879		if (recentDocsCallback)
2880		{
2881			var recentTab = mxUtils.button(mxResources.get('Recent', null, 'Recent'), function()
2882			{
2883				getExtTemplates();
2884			});
2885
2886			tabs.appendChild(recentTab);
2887			tabsEl.push(recentTab);
2888		}
2889
2890		if (searchDocsCallback)
2891		{
2892			var searchTab = document.createElement('span');
2893			searchTab.style.marginLeft = '10px';
2894			searchTab.innerHTML = mxUtils.htmlEntities(mxResources.get('search') + ':');
2895			tabs.appendChild(searchTab);
2896
2897			var searchInput = document.createElement('input');
2898			searchInput.style.marginRight = '10px';
2899			searchInput.style.marginLeft = '10px';
2900			searchInput.style.width = '220px';
2901
2902			mxEvent.addListener(searchInput, 'keypress', function(e)
2903			{
2904				if (e.keyCode == 13)
2905				{
2906					getExtTemplates(true);
2907				}
2908			});
2909
2910			tabs.appendChild(searchInput);
2911
2912			var searchBtn = mxUtils.button(mxResources.get('search'), function()
2913			{
2914				getExtTemplates(true);
2915			});
2916
2917			searchBtn.className = 'geBtn';
2918
2919			tabs.appendChild(searchBtn);
2920		}
2921
2922		setActiveTab(0);
2923	}
2924
2925	var templateLibs = null;
2926	var templateClibs = null;
2927	var templateXml = null;
2928	var selectedElt = null;
2929	var templateExtUrl = null;
2930	var templateRealUrl = null;
2931	var templateInfoObj = null;
2932
2933	function create()
2934	{
2935		if (templateExtUrl && openExtDocCallback != null)
2936		{
2937			if (!showName)
2938			{
2939				editorUi.hideDialog();
2940			}
2941
2942			openExtDocCallback(templateExtUrl, templateInfoObj, nameInput.value);
2943		}
2944		else if (callback)
2945		{
2946			if (!showName)
2947			{
2948				editorUi.hideDialog();
2949			}
2950
2951			callback(templateXml, nameInput.value, templateRealUrl, templateLibs);
2952		}
2953		else
2954		{
2955			var title = nameInput.value;
2956
2957			if (title != null && title.length > 0)
2958			{
2959				editorUi.pickFolder(editorUi.mode, function(folderId)
2960				{
2961					editorUi.createFile(title, templateXml, (templateLibs != null &&
2962						templateLibs.length > 0) ? templateLibs : null, null, function()
2963					{
2964						editorUi.hideDialog();
2965					}, null, folderId, null, (templateClibs != null &&
2966						templateClibs.length > 0) ? templateClibs : null);
2967				}, editorUi.mode != App.MODE_GOOGLE ||
2968					editorUi.stateArg == null ||
2969					editorUi.stateArg.folderId == null);
2970			}
2971		}
2972	};
2973
2974	var div = document.createElement('div');
2975	div.style.border = '1px solid #d3d3d3';
2976	div.style.position = 'absolute';
2977	div.style.left = '160px';
2978	div.style.right = '34px';
2979	var divTop = (showName) ? 72 : 40;
2980	divTop += hasTabs? 30 : 0;
2981	div.style.top = divTop + 'px';
2982	div.style.bottom = '68px';
2983	div.style.margin = '6px 0 0 -1px';
2984	div.style.padding = '6px';
2985	div.style.overflow = 'auto';
2986
2987//	mxEvent.addListener(div, 'dragstart', function(evt)
2988//	{
2989//		if (!mxEvent.isTouchEvent(evt))
2990//		{
2991//			mxEvent.consume(evt);
2992//		}
2993//	});
2994
2995	var searchBox = document.createElement('div');
2996	searchBox.style.cssText = 'position:absolute;left:30px;width:128px;top:' + divTop + 'px;height:22px;margin-top: 6px;white-space: nowrap';
2997	var tmplSearchInput = document.createElement('input');
2998	tmplSearchInput.style.cssText = 'width:105px;height:16px;border:1px solid #d3d3d3;padding: 3px 20px 3px 3px;font-size: 12px';
2999	tmplSearchInput.setAttribute('placeholder', mxResources.get('search'));
3000	tmplSearchInput.setAttribute('type', 'text');
3001	searchBox.appendChild(tmplSearchInput);
3002
3003	var cross = document.createElement('img');
3004	var searchImg = typeof Sidebar != 'undefined'? Sidebar.prototype.searchImage : IMAGE_PATH + '/search.png';
3005	cross.setAttribute('src', searchImg);
3006	cross.setAttribute('title', mxResources.get('search'));
3007	cross.style.position = 'relative';
3008	cross.style.left = '-18px';
3009	cross.style.top = '1px';
3010	// Needed to block event transparency in IE
3011	cross.style.background = 'url(\'' + editorUi.editor.transparentImage + '\')';
3012	searchBox.appendChild(cross);
3013
3014	mxEvent.addListener(cross, 'click', function()
3015	{
3016		if (cross.getAttribute('src') == Dialog.prototype.closeImage)
3017		{
3018			cross.setAttribute('src', searchImg);
3019			cross.setAttribute('title', mxResources.get('search'));
3020			tmplSearchInput.value = '';
3021			resetTemplates();
3022		}
3023
3024		tmplSearchInput.focus();
3025	});
3026
3027	mxEvent.addListener(tmplSearchInput, 'keydown', mxUtils.bind(this, function(evt)
3028	{
3029		if (evt.keyCode == 13 /* Enter */)
3030		{
3031			filterTemplates();
3032			mxEvent.consume(evt);
3033		}
3034	}));
3035
3036	mxEvent.addListener(tmplSearchInput, 'keyup', mxUtils.bind(this, function(evt)
3037	{
3038		if (tmplSearchInput.value == '')
3039		{
3040			cross.setAttribute('src', searchImg);
3041			cross.setAttribute('title', mxResources.get('search'));
3042		}
3043		else
3044		{
3045			cross.setAttribute('src', Dialog.prototype.closeImage);
3046			cross.setAttribute('title', mxResources.get('reset'));
3047		}
3048	}));
3049
3050	divTop += 23;
3051
3052	var list = document.createElement('div');
3053	list.style.cssText = 'position:absolute;left:30px;width:128px;top:' + divTop + 'px;bottom:68px;margin-top:6px;overflow:auto;border:1px solid #d3d3d3;';
3054
3055	mxEvent.addListener(div, 'scroll', function()
3056	{
3057		editorUi.sidebar.hideTooltip();
3058	});
3059
3060	var w = 140;
3061	var h = 140;
3062
3063	function selectElement(elt, xml, libs, extUrl, infoObj, clibs, realUrl)
3064	{
3065		if (selectedElt != null)
3066		{
3067			selectedElt.style.backgroundColor = 'transparent';
3068			selectedElt.style.border = '1px solid transparent';
3069		}
3070
3071		createButton.removeAttribute('disabled');
3072
3073		templateXml = xml;
3074		templateLibs = libs;
3075		templateClibs = clibs;
3076		selectedElt = elt;
3077		templateExtUrl = extUrl;
3078		templateRealUrl = realUrl;
3079		templateInfoObj = infoObj;
3080
3081		selectedElt.style.backgroundColor = rightHighlight;
3082		selectedElt.style.border = rightHighlightBorder;
3083	};
3084
3085	function addButton(url, libs, title, tooltip, select, imgUrl, infoObj, onClick, preview, noImg, clibs)
3086	{
3087		var elt = document.createElement('div');
3088		elt.className = 'geTemplate';
3089		elt.style.position = 'relative';
3090		elt.style.height = w + 'px';
3091		elt.style.width = h + 'px';
3092		var xmlData = null;
3093
3094		if (Editor.isDarkMode())
3095		{
3096			elt.style.filter = 'invert(100%)';
3097		}
3098
3099		if (title != null)
3100		{
3101			elt.setAttribute('title', mxResources.get(title, null, title));
3102		}
3103		else if (tooltip != null && tooltip.length > 0)
3104		{
3105			elt.setAttribute('title', tooltip);
3106		}
3107
3108		function loadXmlData(url, callback)
3109		{
3110			if (xmlData == null)
3111			{
3112				var realUrl = url;
3113
3114				if (/^https?:\/\//.test(realUrl) && !editorUi.editor.isCorsEnabledForUrl(realUrl))
3115				{
3116					realUrl = PROXY_URL + '?url=' + encodeURIComponent(realUrl);
3117				}
3118				else
3119				{
3120					realUrl = TEMPLATE_PATH + '/' + realUrl;
3121				}
3122
3123				mxUtils.get(realUrl, mxUtils.bind(this, function(req)
3124				{
3125					if (req.getStatus() >= 200 && req.getStatus() <= 299)
3126					{
3127						xmlData = req.getText();
3128						callback(xmlData);
3129					}
3130					else
3131					{
3132						callback(xmlData);
3133					}
3134				}));
3135			}
3136			else
3137			{
3138				callback(xmlData);
3139			}
3140		}
3141
3142		// Shows a tooltip with the rendered template
3143		var loading = false;
3144
3145		function showTooltip(xml, x, y)
3146		{
3147			// Checks if dialog still visible
3148			if (xml != null && mxUtils.isAncestorNode(document.body, elt))
3149			{
3150				var doc = mxUtils.parseXml(xml);
3151				var tempNode = Editor.parseDiagramNode(doc.documentElement);
3152				var codec = new mxCodec(tempNode.ownerDocument);
3153				var model = new mxGraphModel();
3154				codec.decode(tempNode, model);
3155				var cells = model.root.getChildAt(0).children;
3156
3157				var ww = window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth;
3158				var wh = window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight;
3159
3160				// TODO: Use maxscreensize
3161				editorUi.sidebar.createTooltip(elt, cells, Math.min(ww - 80, 1000), Math.min(wh - 80, 800),
3162					(title != null) ? mxResources.get(title, null, title) : null,
3163					true, new mxPoint(x, y), true, function()
3164					{
3165						wasVisible = editorUi.sidebar.tooltip != null &&
3166							editorUi.sidebar.tooltip.style.display != 'none';
3167						selectElement(elt, null, null, url, infoObj, clibs);
3168					}, true, false);
3169			}
3170		};
3171
3172		function loadTooltip(evt, magElt)
3173		{
3174			if (url != null && !loading && editorUi.sidebar.currentElt != elt)
3175			{
3176				editorUi.sidebar.hideTooltip();
3177				editorUi.sidebar.currentElt = elt;
3178				loading = true;
3179
3180				loadXmlData(url, function(xml)
3181				{
3182					if (loading && editorUi.sidebar.currentElt == elt)
3183					{
3184						showTooltip(xml, mxEvent.getClientX(evt), mxEvent.getClientY(evt));
3185					}
3186
3187					loading = false;
3188				});
3189			}
3190			else
3191			{
3192				editorUi.sidebar.hideTooltip();
3193			}
3194		};
3195
3196		if (imgUrl != null)
3197		{
3198			elt.style.display = 'inline-flex';
3199			elt.style.justifyContent = 'center';
3200			elt.style.alignItems = 'center';
3201			var img = document.createElement('img');
3202			img.setAttribute('src', imgUrl);
3203			img.setAttribute('alt', tooltip);
3204			img.style.maxWidth = w + 'px';
3205			img.style.maxHeight = h + 'px';
3206
3207			var fallbackImgUrl = imgUrl.replace('.drawio.xml', '').replace('.drawio', '').replace('.xml', '');
3208			elt.appendChild(img);
3209
3210			img.onerror = function()
3211			{
3212				if (this.src != fallbackImgUrl)
3213				{
3214					this.src = fallbackImgUrl;
3215				}
3216				else
3217				{
3218					this.src = Editor.errorImage;
3219					this.onerror = null;
3220				}
3221			};
3222
3223			mxEvent.addGestureListeners(elt, mxUtils.bind(this, function(evt)
3224			{
3225				selectElement(elt, null, null, url, infoObj, clibs);
3226			}), null, null);
3227
3228			mxEvent.addListener(elt, 'dblclick', function(evt)
3229			{
3230				create();
3231				mxEvent.consume(evt);
3232			});
3233		}
3234		else if (!noImg && url != null && url.length > 0)
3235		{
3236			var png = preview || (TEMPLATE_PATH + '/' + url.substring(0, url.length - 4) + '.png');
3237
3238			elt.style.backgroundImage = 'url(' + png + ')';
3239			elt.style.backgroundPosition = 'center center';
3240			elt.style.backgroundRepeat = 'no-repeat';
3241
3242			if (title != null)
3243			{
3244				elt.innerHTML = '<table width="100%" height="100%" style="line-height:1.3em;' + (Editor.isDarkMode() ? '' : 'background:rgba(255,255,255,0.85);') +
3245					'border:inherit;"><tr><td align="center" valign="middle"><span style="display:inline-block;padding:4px 8px 4px 8px;user-select:none;' +
3246					'border-radius:3px;background:rgba(255,255,255,0.85);overflow:hidden;text-overflow:ellipsis;max-width:' + (w - 34) + 'px;">' +
3247					mxUtils.htmlEntities(mxResources.get(title, null, title)) + '</span></td></tr></table>';
3248			}
3249
3250			function activate(doCreate)
3251			{
3252				createButton.setAttribute('disabled', 'disabled');
3253				elt.style.backgroundColor = 'transparent';
3254				elt.style.border = '1px solid transparent';
3255				spinner.spin(div);
3256
3257				loadXmlData(url, function(xml)
3258				{
3259					spinner.stop();
3260
3261					if (xml != null)
3262					{
3263						selectElement(elt, xml, libs, null, null, clibs, realUrl);
3264
3265						if (doCreate)
3266						{
3267							create();
3268						}
3269					}
3270				});
3271			};
3272
3273			mxEvent.addGestureListeners(elt, mxUtils.bind(this, function(evt)
3274			{
3275				activate();
3276			}), null, null);
3277
3278			mxEvent.addListener(elt, 'dblclick', function(evt)
3279			{
3280				activate(true);
3281				mxEvent.consume(evt);
3282			});
3283		}
3284		else
3285		{
3286			elt.innerHTML = '<table width="100%" height="100%" style="line-height:1.3em;"><tr>' +
3287				'<td align="center" valign="middle"><span style="display:inline-block;padding:4px 8px 4px 8px;user-select:none;' +
3288				'border-radius:3px;background:#ffffff;overflow:hidden;text-overflow:ellipsis;max-width:' + (w - 34) + 'px;">' +
3289				mxUtils.htmlEntities(mxResources.get(title, null, title)) + '</span></td></tr></table>';
3290
3291			if (select)
3292			{
3293				selectElement(elt);
3294			}
3295
3296			mxEvent.addGestureListeners(elt, mxUtils.bind(this, function(evt)
3297			{
3298				selectElement(elt, null, null, url, infoObj);
3299			}), null, null);
3300
3301			if (onClick != null)
3302			{
3303				mxEvent.addListener(elt, 'click', onClick);
3304			}
3305			else
3306			{
3307				mxEvent.addListener(elt, 'click', function(evt)
3308				{
3309					selectElement(elt, null, null, url, infoObj);
3310				});
3311
3312				mxEvent.addListener(elt, 'dblclick', function(evt)
3313				{
3314					create();
3315					mxEvent.consume(evt);
3316				});
3317			}
3318		}
3319
3320		// Adds preview button
3321		if (url != null)
3322		{
3323			var magnify = document.createElement('img');
3324			magnify.setAttribute('src', Sidebar.prototype.searchImage);
3325			magnify.setAttribute('title', mxResources.get('preview'));
3326			magnify.className = 'geActiveButton';
3327			magnify.style.position = 'absolute';
3328			magnify.style.cursor = 'default';
3329			magnify.style.padding = '8px';
3330			magnify.style.right = '0px';
3331			magnify.style.top = '0px';
3332			elt.appendChild(magnify);
3333
3334			var wasVisible = false;
3335
3336			mxEvent.addGestureListeners(magnify, mxUtils.bind(this, function(evt)
3337			{
3338				wasVisible = editorUi.sidebar.currentElt == elt;
3339			}), null, null);
3340
3341			mxEvent.addListener(magnify, 'click', mxUtils.bind(this, function(evt)
3342			{
3343				if (!wasVisible)
3344				{
3345					loadTooltip(evt, magnify);
3346				}
3347
3348				mxEvent.consume(evt);
3349			}));
3350		}
3351
3352		div.appendChild(elt);
3353		return elt;
3354	};
3355
3356	var categories = {}, subCategories = {}, customCats = {};
3357	var customCatCount = 0, firstInitUi = true;
3358	var currentEntry = null, lastEntry = null;
3359
3360	// Adds local basic templates
3361	categories['basic'] = [{title: 'blankDiagram', select: true}];
3362	var templates = categories['basic'];
3363
3364	function resetTemplates()
3365	{
3366		if (lastEntry != null)
3367		{
3368			lastEntry.click();
3369			lastEntry = null;
3370		}
3371	};
3372
3373	function filterTemplates()
3374	{
3375		var searchTerms = tmplSearchInput.value;
3376
3377		if (searchTerms == '')
3378		{
3379			resetTemplates();
3380			return;
3381		}
3382
3383		if (NewDialog.tagsList[templateFile] == null)
3384		{
3385			var tagsList = {};
3386
3387			for (var cat in categories)
3388			{
3389				var templateList = categories[cat];
3390
3391				for (var i = 0; i < templateList.length; i++)
3392				{
3393					var temp = templateList[i];
3394
3395					if (temp.tags != null)
3396					{
3397						var tags = temp.tags.toLowerCase().split(';');
3398
3399						for (var j = 0; j < tags.length; j++)
3400						{
3401							if (tagsList[tags[j]] == null)
3402							{
3403								tagsList[tags[j]] = [];
3404							}
3405
3406							tagsList[tags[j]].push(temp);
3407						}
3408					}
3409				}
3410			}
3411
3412			NewDialog.tagsList[templateFile] = tagsList;
3413		}
3414
3415		var tmp = searchTerms.toLowerCase().split(' ');
3416		tagsList = NewDialog.tagsList[templateFile];
3417
3418		if (customCatCount > 0 && tagsList.__tagsList__ == null)
3419		{
3420			for (var cat in customCats)
3421			{
3422				var templateList = customCats[cat];
3423
3424				for (var i = 0; i < templateList.length; i++)
3425				{
3426					var temp = templateList[i];
3427					var tags = temp.title.split(' ');
3428					tags.push(cat);
3429
3430					for (var j = 0; j < tags.length; j++)
3431					{
3432						var tag = tags[j].toLowerCase();
3433
3434						if (tagsList[tag] == null)
3435						{
3436							tagsList[tag] = [];
3437						}
3438
3439						tagsList[tag].push(temp);
3440					}
3441				}
3442			}
3443
3444			tagsList.__tagsList__ = true;
3445		}
3446
3447		var results = [], resMap = {}, index = 0;
3448
3449		for (var i = 0; i < tmp.length; i++)
3450		{
3451			if (tmp[i].length > 0)
3452			{
3453				var list = tagsList[tmp[i]];
3454				var tmpResMap = {};
3455				results = [];
3456
3457				if (list != null)
3458				{
3459					for (var j = 0; j < list.length; j++)
3460					{
3461						var temp = list[j];
3462
3463						//ANDing terms
3464						if ((index == 0) == (resMap[temp.url] == null))
3465						{
3466							tmpResMap[temp.url] = true;
3467							results.push(temp);
3468						}
3469					}
3470				}
3471
3472				resMap = tmpResMap;
3473				index++;
3474			}
3475		}
3476
3477		div.scrollTop = 0;
3478		div.innerHTML = '';
3479		i0 = 0;
3480		var msgDiv = document.createElement('div');
3481		msgDiv.style.cssText = 'border: 1px solid #D3D3D3; padding: 6px; background: #F5F5F5;';
3482		mxUtils.write(msgDiv, mxResources.get(results.length == 0 ? 'noResultsFor' : 'resultsFor', [searchTerms]));
3483		div.appendChild(msgDiv);
3484
3485		if (currentEntry != null && lastEntry == null)
3486		{
3487			currentEntry.style.backgroundColor = '';
3488			lastEntry = currentEntry;
3489			currentEntry = msgDiv; //To prevebt NPE later
3490		}
3491
3492		templates = results;
3493		oldTemplates = null;
3494		addTemplates(false);
3495	};
3496
3497	function initUi()
3498	{
3499		if (firstInitUi)
3500		{
3501			firstInitUi = false;
3502
3503			mxEvent.addListener(div, 'scroll', function(evt)
3504			{
3505				if (div.scrollTop + div.clientHeight >= div.scrollHeight)
3506				{
3507					addTemplates();
3508					mxEvent.consume(evt);
3509				}
3510			});
3511		}
3512
3513		if (customCatCount > 0)
3514		{
3515			var titleCss = 'font-weight: bold;background: #f9f9f9;padding: 5px 0 5px 0;text-align: center;';
3516			var title = document.createElement('div');
3517			title.style.cssText = titleCss;
3518			mxUtils.write(title, mxResources.get('custom'));
3519			list.appendChild(title);
3520
3521			for (var cat in customCats)
3522			{
3523				var entry = document.createElement('div');
3524				var label = cat;
3525				var templateList = customCats[cat];
3526
3527				if (label.length > 18)
3528				{
3529					label = label.substring(0, 18) + '&hellip;';
3530				}
3531
3532				entry.style.cssText = 'display:block;cursor:pointer;padding:6px;white-space:nowrap;margin-bottom:-1px;overflow:hidden;text-overflow:ellipsis;user-select:none;';
3533				entry.setAttribute('title', label + ' (' + templateList.length + ')');
3534				mxUtils.write(entry, entry.getAttribute('title'));
3535
3536				if (itemPadding != null)
3537				{
3538					entry.style.padding = itemPadding;
3539				}
3540
3541				list.appendChild(entry);
3542
3543				(function(cat2, entry2)
3544				{
3545					mxEvent.addListener(entry, 'click', function()
3546					{
3547						if (currentEntry != entry2)
3548						{
3549							currentEntry.style.backgroundColor = '';
3550							currentEntry = entry2;
3551							currentEntry.style.backgroundColor = leftHighlight;
3552
3553							div.scrollTop = 0;
3554							div.innerHTML = '';
3555							i0 = 0;
3556
3557							templates = customCats[cat2];
3558							oldTemplates = null;
3559							addTemplates(false);
3560						}
3561					});
3562				})(cat, entry);
3563			}
3564
3565			title = document.createElement('div');
3566			title.style.cssText = titleCss;
3567			mxUtils.write(title, 'draw.io');
3568			list.appendChild(title);
3569		}
3570
3571		function getEntryTitle(cat, templateList)
3572		{
3573			var label = mxResources.get(cat);
3574
3575			if (label == null)
3576			{
3577				label = cat.substring(0, 1).toUpperCase() + cat.substring(1);
3578			}
3579
3580			if (label.length > 18)
3581			{
3582				label = label.substring(0, 18) + '&hellip;';
3583			}
3584
3585			return label + ' (' + templateList.length + ')';
3586		};
3587
3588		function addEntryHandler(cat, entry, subCat)
3589		{
3590			mxEvent.addListener(entry, 'click', function()
3591			{
3592				if (currentEntry != entry)
3593				{
3594					currentEntry.style.backgroundColor = '';
3595					currentEntry = entry;
3596					currentEntry.style.backgroundColor = leftHighlight;
3597
3598					div.scrollTop = 0;
3599					div.innerHTML = '';
3600					i0 = 0;
3601
3602					templates = subCat? subCategories[cat][subCat] : categories[cat];
3603					oldTemplates = null;
3604					addTemplates(false);
3605				}
3606			});
3607		};
3608
3609		for (var cat in categories)
3610		{
3611			var subCats = subCategories[cat];
3612			var entry = document.createElement(subCats? 'ul' : 'div');
3613			var clickElem = entry;
3614			var templateList = categories[cat];
3615			var entryTitle = getEntryTitle(cat, templateList);
3616
3617			if (subCats != null)
3618			{
3619				var entryLi = document.createElement('li');
3620				var entryDiv = document.createElement('div');
3621				entryDiv.className = 'geTempTreeCaret';
3622				entryDiv.setAttribute('title', entryTitle);
3623				mxUtils.write(entryDiv, entryTitle);
3624				clickElem = entryDiv;
3625				entryLi.appendChild(entryDiv);
3626				//We support one level deep only
3627				var subUl = document.createElement('ul');
3628				subUl.className = 'geTempTreeNested';
3629				subUl.style.visibility = 'hidden';
3630
3631				for (var subCat in subCats)
3632				{
3633					var subLi = document.createElement('li');
3634					var subTitle = getEntryTitle(subCat, subCats[subCat]);
3635					subLi.setAttribute('title', subTitle);
3636					mxUtils.write(subLi, subTitle);
3637					addEntryHandler(cat, subLi, subCat);
3638					subUl.appendChild(subLi);
3639				}
3640
3641				entryLi.appendChild(subUl);
3642				entry.className = 'geTempTree';
3643				entry.appendChild(entryLi);
3644
3645				(function(subUl2, entryDiv2)
3646				{
3647					mxEvent.addListener(entryDiv2, 'click', function()
3648					{
3649						subUl2.style.visibility = 'visible';
3650						subUl2.classList.toggle('geTempTreeActive');
3651
3652						if (subUl2.classList.toggle('geTempTreeNested'))
3653						{
3654							//Must hide sub elements to allow click on elements above it
3655							setTimeout(function()
3656							{
3657								subUl2.style.visibility = 'hidden';
3658							}, 550);
3659						}
3660
3661	    				entryDiv2.classList.toggle('geTempTreeCaret-down');
3662					});
3663				})(subUl, entryDiv);
3664			}
3665			else
3666			{
3667				entry.style.cssText = 'display:block;cursor:pointer;padding:6px;white-space:nowrap;margin-bottom:-1px;overflow:hidden;text-overflow:ellipsis;user-select:none;transition: all 0.5s;';
3668				entry.setAttribute('title', entryTitle);
3669				mxUtils.write(entry, entryTitle);
3670			}
3671
3672			if (itemPadding != null)
3673			{
3674				entry.style.padding = itemPadding;
3675			}
3676
3677			list.appendChild(entry);
3678
3679			if (currentEntry == null && templateList.length > 0)
3680			{
3681				currentEntry = entry;
3682				currentEntry.style.backgroundColor = leftHighlight;
3683				templates = templateList;
3684			}
3685
3686			addEntryHandler(cat, clickElem);
3687		}
3688
3689		addTemplates(false);
3690	};
3691
3692	if (!compact)
3693	{
3694		outer.appendChild(searchBox);
3695		outer.appendChild(list);
3696		outer.appendChild(div);
3697		var indexLoaded = false;
3698		var realUrl = templateFile;
3699
3700		if (/^https?:\/\//.test(realUrl) && !editorUi.editor.isCorsEnabledForUrl(realUrl))
3701		{
3702			realUrl = PROXY_URL + '?url=' + encodeURIComponent(realUrl);
3703		}
3704
3705		function loadDrawioTemplates()
3706		{
3707			mxUtils.get(realUrl, function(req)
3708			{
3709				// Workaround for index loaded 3 times in iOS offline mode
3710				if (!indexLoaded)
3711				{
3712					indexLoaded = true;
3713					var tmpDoc = req.getXml();
3714					var node = tmpDoc.documentElement.firstChild;
3715					var clibs = {};
3716
3717					while (node != null)
3718					{
3719						if (typeof(node.getAttribute) !== 'undefined')
3720						{
3721							if (node.nodeName == 'clibs')
3722							{
3723								var name = node.getAttribute('name');
3724								var adds = node.getElementsByTagName('add');
3725								var temp = [];
3726
3727								for (var i = 0; i < adds.length; i++)
3728								{
3729									temp.push(encodeURIComponent(mxUtils.getTextContent(adds[i])));
3730								}
3731
3732								if (name != null && temp.length > 0)
3733								{
3734									clibs[name] = temp.join(';');
3735								}
3736							}
3737							else
3738							{
3739								var url = node.getAttribute('url');
3740
3741								if (url != null)
3742								{
3743									var category = node.getAttribute('section');
3744									var subCategory = node.getAttribute('subsection');
3745
3746									if (category == null)
3747									{
3748										var slash = url.indexOf('/');
3749										category = url.substring(0, slash);
3750
3751										if (subCategory == null)
3752										{
3753											var nextSlash = url.indexOf('/', slash + 1);
3754
3755											if (nextSlash > -1)
3756											{
3757												subCategory = url.substring(slash + 1, nextSlash);
3758											}
3759										}
3760									}
3761
3762									var list = categories[category];
3763
3764									if (list == null)
3765									{
3766										list = [];
3767										categories[category] = list;
3768									}
3769
3770									var tempLibs = node.getAttribute('clibs');
3771
3772									if (clibs[tempLibs] != null)
3773									{
3774										tempLibs = clibs[tempLibs];
3775									}
3776
3777									var tempObj = {url: node.getAttribute('url'), libs: node.getAttribute('libs'),
3778										title: node.getAttribute('title'), tooltip: node.getAttribute('name') || node.getAttribute('url'),
3779										preview: node.getAttribute('preview'), clibs: tempLibs, tags: node.getAttribute('tags')};
3780									list.push(tempObj);
3781
3782									if (subCategory != null)
3783									{
3784										var subCats = subCategories[category];
3785
3786										if (subCats == null)
3787										{
3788											subCats = {};
3789											subCategories[category] = subCats;
3790										}
3791
3792										var subCatList = subCats[subCategory];
3793
3794										if (subCatList == null)
3795										{
3796											subCatList = [];
3797											subCats[subCategory] = subCatList;
3798										}
3799
3800										subCatList.push(tempObj);
3801									}
3802								}
3803							}
3804						}
3805
3806						node = node.nextSibling;
3807					}
3808
3809
3810				spinner.stop();
3811					initUi();
3812				}
3813			});
3814		};
3815
3816		spinner.spin(div);
3817
3818		if (customTempCallback != null)
3819		{
3820			customTempCallback(function(cats, count)
3821			{
3822				customCats = cats;
3823				customCatCount = count;
3824				//Custom templates doesn't change after being loaded, so cache them here. Also, only count is overridden
3825				origCustomCatCount = count;
3826
3827				loadDrawioTemplates();
3828			},
3829
3830			loadDrawioTemplates); //In case of an error, just load draw.io templates only
3831		}
3832		else
3833		{
3834			loadDrawioTemplates();
3835		}
3836
3837		//draw.io templates doesn't change after being loaded, so cache them here
3838		origCategories = categories;
3839	}
3840
3841	mxEvent.addListener(nameInput, 'keypress', function(e)
3842	{
3843		if (editorUi.dialog.container.firstChild == outer &&
3844			e.keyCode == 13)
3845		{
3846			create();
3847		}
3848	});
3849
3850	var btns = document.createElement('div');
3851	btns.style.marginTop = (compact) ? '4px' : '16px';
3852	btns.style.textAlign = 'right';
3853	btns.style.position = 'absolute';
3854	btns.style.left = '40px';
3855	btns.style.bottom = '24px';
3856	btns.style.right = '40px';
3857
3858	if (!compact && !editorUi.isOffline() && showName && callback == null && !createOnly)
3859	{
3860		var helpBtn = mxUtils.button(mxResources.get('help'), function()
3861		{
3862			editorUi.openLink('https://support.draw.io/display/DO/Creating+and+Opening+Files');
3863		});
3864
3865		helpBtn.className = 'geBtn';
3866		btns.appendChild(helpBtn);
3867	}
3868
3869	var cancelBtn = mxUtils.button(mxResources.get('cancel'), function()
3870	{
3871		if (cancelCallback != null)
3872		{
3873			cancelCallback();
3874		}
3875
3876		editorUi.hideDialog(true);
3877	});
3878
3879	cancelBtn.className = 'geBtn';
3880
3881	if (editorUi.editor.cancelFirst && (!createOnly || cancelCallback != null))
3882	{
3883		btns.appendChild(cancelBtn);
3884	}
3885
3886	if (!compact && urlParams['embed'] != '1' && !createOnly &&
3887			!mxClient.IS_ANDROID && !mxClient.IS_IOS && urlParams['noDevice'] != '1')
3888	{
3889		var fromTmpBtn = mxUtils.button(mxResources.get('fromTemplateUrl'), function()
3890		{
3891			var dlg = new FilenameDialog(editorUi, '', mxResources.get('create'), function(fileUrl)
3892			{
3893				if (fileUrl != null && fileUrl.length > 0)
3894				{
3895					var url = editorUi.getUrl(window.location.pathname + '?mode=' + editorUi.mode +
3896							'&title=' + encodeURIComponent(nameInput.value) +
3897							'&create=' + encodeURIComponent(fileUrl));
3898
3899					if (editorUi.getCurrentFile() == null)
3900					{
3901						window.location.href = url;
3902					}
3903					else
3904					{
3905						window.openWindow(url);
3906					}
3907				}
3908			}, mxResources.get('url'));
3909			editorUi.showDialog(dlg.container, 300, 80, true, true);
3910			dlg.init();
3911		});
3912		fromTmpBtn.className = 'geBtn';
3913		btns.appendChild(fromTmpBtn);
3914	}
3915
3916	if (Graph.fileSupport && showImport)
3917	{
3918		var importBtn = mxUtils.button(mxResources.get('import'), function()
3919		{
3920			if (editorUi.newDlgFileInputElt == null)
3921			{
3922				var fileInput = document.createElement('input');
3923				fileInput.setAttribute('multiple', 'multiple');
3924				fileInput.setAttribute('type', 'file');
3925
3926				mxEvent.addListener(fileInput, 'change', function(evt)
3927				{
3928					editorUi.openFiles(fileInput.files, true);
3929					fileInput.value = '';
3930				});
3931
3932				fileInput.style.display = 'none';
3933				document.body.appendChild(fileInput);
3934				editorUi.newDlgFileInputElt = fileInput;
3935			}
3936
3937			editorUi.newDlgFileInputElt.click();
3938		});
3939
3940		importBtn.className = 'geBtn';
3941		btns.appendChild(importBtn);
3942	}
3943
3944	btns.appendChild(createButton);
3945
3946	if (!editorUi.editor.cancelFirst && callback == null && (!createOnly || cancelCallback != null))
3947	{
3948		btns.appendChild(cancelBtn);
3949	}
3950
3951	outer.appendChild(btns);
3952
3953	this.container = outer;
3954};
3955
3956NewDialog.tagsList = {};
3957/**
3958 * Constructs a dialog for creating new files from a template URL.
3959 * Also used for dialog choosing where to save or export resources
3960 */
3961var CreateDialog = function(editorUi, title, createFn, cancelFn, dlgTitle, btnLabel, overrideExtension, allowBrowser,
3962	allowTab, helpLink, showDeviceButton, rowLimit, data, mimeType, base64Encoded, hints, hideDialog)
3963{
3964	showDeviceButton = urlParams['noDevice'] == '1'? false : showDeviceButton;
3965	overrideExtension = (overrideExtension != null) ? overrideExtension : true;
3966	allowBrowser = (allowBrowser != null) ? allowBrowser : true;
3967	rowLimit = (rowLimit != null) ? rowLimit : 4;
3968	hideDialog = (hideDialog != null) ? hideDialog : true;
3969
3970	var div = document.createElement('div');
3971	div.style.whiteSpace = 'nowrap';
3972
3973	var showButtons = true;
3974
3975	if (cancelFn == null)
3976	{
3977		editorUi.addLanguageMenu(div);
3978	}
3979
3980	var h3 = document.createElement('h2');
3981	mxUtils.write(h3, dlgTitle || mxResources.get('create'));
3982	h3.style.marginTop = '0px';
3983	h3.style.marginBottom = '24px';
3984	div.appendChild(h3);
3985
3986	mxUtils.write(div, mxResources.get('filename') + ':');
3987
3988	var nameInput = document.createElement('input');
3989	nameInput.setAttribute('value', title);
3990	nameInput.style.width = '200px';
3991	nameInput.style.marginLeft = '10px';
3992	nameInput.style.marginBottom = '20px';
3993	nameInput.style.maxWidth = '70%';
3994
3995	this.init = function()
3996	{
3997		nameInput.focus();
3998
3999		if (mxClient.IS_GC || mxClient.IS_FF || document.documentMode >= 5)
4000		{
4001			nameInput.select();
4002		}
4003		else
4004		{
4005			document.execCommand('selectAll', false, null);
4006		}
4007	};
4008
4009	div.appendChild(nameInput);
4010
4011	if (hints != null)
4012	{
4013		if (editorUi.editor.diagramFileTypes != null)
4014		{
4015			var typeSelect = FilenameDialog.createFileTypes(editorUi, nameInput, editorUi.editor.diagramFileTypes);
4016			typeSelect.style.marginLeft = '6px';
4017			typeSelect.style.width = '80px';
4018			div.appendChild(typeSelect);
4019		}
4020
4021		div.appendChild(FilenameDialog.createTypeHint(editorUi, nameInput, hints));
4022	}
4023
4024	var copyBtn = null;
4025
4026	// Disables SVG preview if SVG is not supported in browser
4027	if (urlParams['noDevice'] != '1' && data != null && mimeType != null && (mimeType.substring(0, 6) == 'image/' &&
4028		(mimeType.substring(0, 9) != 'image/svg' || mxClient.IS_SVG)))
4029	{
4030		nameInput.style.width = '160px';
4031		var preview = document.createElement('img');
4032		var temp = (base64Encoded) ? data : btoa(unescape(encodeURIComponent(data)));
4033		preview.setAttribute('src', 'data:' + mimeType + ';base64,' + temp);
4034		preview.style.position = 'absolute';
4035		preview.style.top = '70px';
4036		preview.style.right = '100px';
4037		preview.style.maxWidth = '120px';
4038		preview.style.maxHeight = '80px';
4039		mxUtils.setPrefixedStyle(preview.style, 'transform',
4040			'translate(50%,-50%)');
4041		div.appendChild(preview);
4042
4043		if (!mxClient.IS_FF && navigator.clipboard != null && mimeType == 'image/png')
4044		{
4045			copyBtn = mxUtils.button(mxResources.get('copy'), function(evt)
4046			{
4047				var blob = editorUi.base64ToBlob(temp, 'image/png');
4048				var html = '<img src="' + 'data:' + mimeType + ';base64,' + temp + '">';
4049				var cbi = new ClipboardItem({'image/png': blob,
4050					'text/html': new Blob([html], {type: 'text/html'})});
4051				navigator.clipboard.write([cbi]).then(mxUtils.bind(this, function()
4052				{
4053					editorUi.alert(mxResources.get('copiedToClipboard'));
4054				}))['catch'](mxUtils.bind(this, function(e)
4055				{
4056					editorUi.handleError(e);
4057				}));
4058			});
4059
4060			copyBtn.style.marginTop = '6px';
4061			copyBtn.className = 'geBtn';
4062		}
4063
4064		if (allowTab && Editor.popupsAllowed)
4065		{
4066			preview.style.cursor = 'pointer';
4067
4068			mxEvent.addGestureListeners(preview, null, null, function(evt)
4069			{
4070				if (!mxEvent.isPopupTrigger(evt))
4071				{
4072					create('_blank');
4073				}
4074			});
4075		}
4076	}
4077
4078	mxUtils.br(div);
4079
4080	var buttons = document.createElement('div');
4081	buttons.style.textAlign = 'center';
4082	var count = 0;
4083
4084	function addLogo(img, title, mode, clientName)
4085	{
4086		var button = document.createElement('a');
4087		button.style.overflow = 'hidden';
4088
4089		var logo = document.createElement('img');
4090		logo.src = img;
4091		logo.setAttribute('border', '0');
4092		logo.setAttribute('align', 'absmiddle');
4093		logo.style.width = '60px';
4094		logo.style.height = '60px';
4095		logo.style.paddingBottom = '6px';
4096		button.style.display = 'inline-block';
4097		button.className = 'geBaseButton';
4098		button.style.position = 'relative';
4099		button.style.margin = '4px';
4100		button.style.padding = '8px 8px 10px 8px';
4101		button.style.whiteSpace = 'nowrap';
4102
4103		button.appendChild(logo);
4104
4105		button.style.color = 'gray';
4106		button.style.fontSize = '11px';
4107
4108		var label = document.createElement('div');
4109		button.appendChild(label);
4110		mxUtils.write(label, title);
4111
4112		function initButton()
4113		{
4114			mxEvent.addListener(button, 'click', function()
4115			{
4116				// Updates extension
4117				change(mode);
4118				create(mode);
4119			});
4120		};
4121
4122		// Supports lazy loading
4123		if (clientName != null && editorUi[clientName] == null)
4124		{
4125			logo.style.visibility = 'hidden';
4126			mxUtils.setOpacity(label, 10);
4127			var size = 12;
4128
4129			var spinner = new Spinner({
4130				lines: 12, // The number of lines to draw
4131				length: size, // The length of each line
4132				width: 5, // The line thickness
4133				radius: 10, // The radius of the inner circle
4134				rotate: 0, // The rotation offset
4135				color: '#000', // #rgb or #rrggbb
4136				speed: 1.5, // Rounds per second
4137				trail: 60, // Afterglow percentage
4138				shadow: false, // Whether to render a shadow
4139				hwaccel: false, // Whether to use hardware acceleration
4140				top: '40%',
4141				zIndex: 2e9 // The z-index (defaults to 2000000000)
4142			});
4143			spinner.spin(button);
4144
4145			// Timeout after 30 secs
4146			var timeout = window.setTimeout(function()
4147			{
4148				if (editorUi[clientName] == null)
4149				{
4150					spinner.stop();
4151					button.style.display = 'none';
4152				}
4153			}, 30000);
4154
4155			editorUi.addListener('clientLoaded', mxUtils.bind(this, function()
4156			{
4157				if (editorUi[clientName] != null)
4158				{
4159					window.clearTimeout(timeout);
4160					mxUtils.setOpacity(label, 100);
4161					logo.style.visibility = '';
4162					spinner.stop();
4163					initButton();
4164				}
4165			}));
4166		}
4167		else
4168		{
4169			initButton();
4170		}
4171
4172		buttons.appendChild(button);
4173
4174		if (++count == rowLimit)
4175		{
4176			mxUtils.br(buttons);
4177			count = 0;
4178		}
4179	};
4180
4181	if (!showButtons)
4182	{
4183		mxUtils.write(div, mxResources.get('chooseAnOption') + ':');
4184	}
4185	else
4186	{
4187		buttons.style.marginTop = '6px';
4188		div.appendChild(buttons);
4189	}
4190
4191	// Adds all papersize options
4192	var serviceSelect = document.createElement('select');
4193	serviceSelect.style.marginLeft = '10px';
4194
4195	if (!editorUi.isOfflineApp() && !editorUi.isOffline())
4196	{
4197		if (typeof window.DriveClient === 'function')
4198		{
4199			var googleOption = document.createElement('option');
4200			googleOption.setAttribute('value', App.MODE_GOOGLE);
4201			mxUtils.write(googleOption, mxResources.get('googleDrive'));
4202			serviceSelect.appendChild(googleOption);
4203
4204			addLogo(IMAGE_PATH + '/google-drive-logo.svg', mxResources.get('googleDrive'), App.MODE_GOOGLE, 'drive');
4205		}
4206
4207		if (typeof window.OneDriveClient === 'function')
4208		{
4209			var oneDriveOption = document.createElement('option');
4210			oneDriveOption.setAttribute('value', App.MODE_ONEDRIVE);
4211			mxUtils.write(oneDriveOption, mxResources.get('oneDrive'));
4212			serviceSelect.appendChild(oneDriveOption);
4213
4214			if (editorUi.mode == App.MODE_ONEDRIVE)
4215			{
4216				oneDriveOption.setAttribute('selected', 'selected');
4217			}
4218
4219			addLogo(IMAGE_PATH + '/onedrive-logo.svg', mxResources.get('oneDrive'), App.MODE_ONEDRIVE, 'oneDrive');
4220		}
4221
4222		if (typeof window.DropboxClient === 'function')
4223		{
4224			var dropboxOption = document.createElement('option');
4225			dropboxOption.setAttribute('value', App.MODE_DROPBOX);
4226			mxUtils.write(dropboxOption, mxResources.get('dropbox'));
4227			serviceSelect.appendChild(dropboxOption);
4228
4229			if (editorUi.mode == App.MODE_DROPBOX)
4230			{
4231				dropboxOption.setAttribute('selected', 'selected');
4232			}
4233
4234			addLogo(IMAGE_PATH + '/dropbox-logo.svg', mxResources.get('dropbox'), App.MODE_DROPBOX, 'dropbox');
4235		}
4236
4237		if (editorUi.gitHub != null)
4238		{
4239			var gitHubOption = document.createElement('option');
4240			gitHubOption.setAttribute('value', App.MODE_GITHUB);
4241			mxUtils.write(gitHubOption, mxResources.get('github'));
4242			serviceSelect.appendChild(gitHubOption);
4243
4244			addLogo(IMAGE_PATH + '/github-logo.svg', mxResources.get('github'), App.MODE_GITHUB, 'gitHub');
4245		}
4246
4247		if (editorUi.gitLab != null)
4248		{
4249			var gitLabOption = document.createElement('option');
4250			gitLabOption.setAttribute('value', App.MODE_GITLAB);
4251			mxUtils.write(gitLabOption, mxResources.get('gitlab'));
4252			serviceSelect.appendChild(gitLabOption);
4253
4254			addLogo(IMAGE_PATH + '/gitlab-logo.svg', mxResources.get('gitlab'), App.MODE_GITLAB, 'gitLab');
4255		}
4256
4257		if (editorUi.notion != null)
4258		{
4259			var notionOption = document.createElement('option');
4260			notionOption.setAttribute('value', App.MODE_NOTION);
4261			mxUtils.write(notionOption, mxResources.get('notion'));
4262			serviceSelect.appendChild(notionOption);
4263
4264			addLogo(IMAGE_PATH + '/notion-logo.svg', mxResources.get('notion'), App.MODE_NOTION, 'notion');
4265		}
4266
4267		if (typeof window.TrelloClient === 'function')
4268		{
4269			var trelloOption = document.createElement('option');
4270			trelloOption.setAttribute('value', App.MODE_TRELLO);
4271			mxUtils.write(trelloOption, mxResources.get('trello'));
4272			serviceSelect.appendChild(trelloOption);
4273
4274			addLogo(IMAGE_PATH + '/trello-logo.svg', mxResources.get('trello'), App.MODE_TRELLO, 'trello');
4275		}
4276	}
4277
4278	if (!Editor.useLocalStorage || urlParams['storage'] == 'device' ||
4279		(editorUi.getCurrentFile() != null/* && !mxClient.IS_IOS*/ && urlParams['noDevice'] != '1'))
4280	{
4281		var deviceOption = document.createElement('option');
4282		deviceOption.setAttribute('value', App.MODE_DEVICE);
4283		mxUtils.write(deviceOption, mxResources.get('device'));
4284		serviceSelect.appendChild(deviceOption);
4285
4286		if (editorUi.mode == App.MODE_DEVICE || !allowBrowser)
4287		{
4288			deviceOption.setAttribute('selected', 'selected');
4289		}
4290
4291		if (showDeviceButton)
4292		{
4293			addLogo(IMAGE_PATH + '/osa_drive-harddisk.png', mxResources.get('device'), App.MODE_DEVICE);
4294		}
4295	}
4296
4297	if (allowBrowser && isLocalStorage && urlParams['browser'] != '0')
4298	{
4299		var browserOption = document.createElement('option');
4300		browserOption.setAttribute('value', App.MODE_BROWSER);
4301		mxUtils.write(browserOption, mxResources.get('browser'));
4302		serviceSelect.appendChild(browserOption);
4303
4304		if (editorUi.mode == App.MODE_BROWSER)
4305		{
4306			browserOption.setAttribute('selected', 'selected');
4307		}
4308
4309		addLogo(IMAGE_PATH + '/osa_database.png', mxResources.get('browser'), App.MODE_BROWSER);
4310	}
4311
4312	function change(newMode)
4313	{
4314		if (overrideExtension)
4315		{
4316			var fn = nameInput.value;
4317			var idx = fn.lastIndexOf('.');
4318
4319			if (title.lastIndexOf('.') < 0 && (!showButtons || idx < 0))
4320			{
4321				newMode = (newMode != null) ? newMode : serviceSelect.value;
4322				var ext = '';
4323
4324				if (newMode == App.MODE_GOOGLE)
4325				{
4326					ext = editorUi.drive.extension;
4327				}
4328				else if (newMode == App.MODE_GITHUB)
4329				{
4330					ext = editorUi.gitHub.extension;
4331				}
4332				else if (newMode == App.MODE_GITLAB)
4333				{
4334					ext = editorUi.gitLab.extension;
4335				}
4336				else if (newMode == App.MODE_NOTION)
4337				{
4338					ext = editorUi.notion.extension;
4339				}
4340				else if (newMode == App.MODE_TRELLO)
4341				{
4342					ext = editorUi.trello.extension;
4343				}
4344				else if (newMode == App.MODE_DROPBOX)
4345				{
4346					ext = editorUi.dropbox.extension;
4347				}
4348				else if (newMode == App.MODE_ONEDRIVE)
4349				{
4350					ext = editorUi.oneDrive.extension;
4351				}
4352				else if (newMode == App.MODE_DEVICE)
4353				{
4354					ext = '.drawio';
4355				}
4356
4357				if (idx >= 0)
4358				{
4359					fn = fn.substring(0, idx);
4360				}
4361
4362				nameInput.value = fn + ext;
4363			}
4364		}
4365	};
4366
4367	var btns = document.createElement('div');
4368	btns.style.marginTop = (showButtons) ? '26px' : '38px';
4369	btns.style.textAlign = 'center';
4370
4371	if (!showButtons)
4372	{
4373		div.appendChild(serviceSelect);
4374		mxEvent.addListener(serviceSelect, 'change', change);
4375		change();
4376	}
4377
4378	if (helpLink != null)
4379	{
4380		var helpBtn = mxUtils.button(mxResources.get('help'), function()
4381		{
4382			editorUi.openLink(helpLink);
4383		});
4384
4385		helpBtn.className = 'geBtn';
4386		btns.appendChild(helpBtn);
4387	}
4388
4389	var cancelBtn = mxUtils.button(mxResources.get((cancelFn != null) ? 'close' : 'cancel'), function()
4390	{
4391		if (cancelFn != null)
4392		{
4393			cancelFn();
4394		}
4395		else
4396		{
4397			editorUi.fileLoaded(null);
4398			editorUi.hideDialog();
4399			window.close();
4400			window.location.href = editorUi.getUrl();
4401		}
4402	});
4403
4404	cancelBtn.className = 'geBtn';
4405
4406	if (editorUi.editor.cancelFirst && cancelFn == null)
4407	{
4408		btns.appendChild(cancelBtn);
4409	}
4410
4411	function create(mode)
4412	{
4413		var title = nameInput.value;
4414
4415		if (mode == null || (title != null && title.length > 0))
4416		{
4417			if (hideDialog)
4418			{
4419				editorUi.hideDialog();
4420			}
4421
4422			createFn(title, mode, nameInput);
4423		};
4424	}
4425
4426	if (cancelFn == null)
4427	{
4428		var laterBtn = mxUtils.button(mxResources.get('decideLater'), function()
4429		{
4430			create(null);
4431		});
4432
4433		laterBtn.className = 'geBtn';
4434		btns.appendChild(laterBtn);
4435	}
4436
4437	if (allowTab && Editor.popupsAllowed)
4438	{
4439		var openBtn = mxUtils.button(mxResources.get('openInNewWindow'), function()
4440		{
4441			create('_blank');
4442		});
4443
4444		openBtn.className = 'geBtn';
4445		btns.appendChild(openBtn);
4446	}
4447
4448	if (CreateDialog.showDownloadButton)
4449	{
4450		var downloadButton = mxUtils.button(mxResources.get('download'), function()
4451		{
4452			create('download');
4453		});
4454
4455		downloadButton.className = 'geBtn';
4456		btns.appendChild(downloadButton);
4457
4458		if (copyBtn != null)
4459		{
4460			downloadButton.style.marginTop = '6px';
4461			btns.style.marginTop = '6px';
4462		}
4463	}
4464
4465	if (copyBtn != null)
4466	{
4467		mxUtils.br(btns);
4468		btns.appendChild(copyBtn);
4469	}
4470
4471	if (/*!mxClient.IS_IOS || */!showButtons)
4472	{
4473		var createBtn = mxUtils.button(btnLabel || mxResources.get('create'), function()
4474		{
4475			create((showDeviceButton) ? 'download' : ((showButtons) ? App.MODE_DEVICE : serviceSelect.value));
4476		});
4477
4478		createBtn.className = 'geBtn gePrimaryBtn';
4479		btns.appendChild(createBtn);
4480	}
4481
4482	if (!editorUi.editor.cancelFirst || cancelFn != null)
4483	{
4484		btns.appendChild(cancelBtn);
4485	}
4486
4487	mxEvent.addListener(nameInput, 'keypress', function(e)
4488	{
4489		if (e.keyCode == 13)
4490		{
4491			create((showButtons) ? App.MODE_DEVICE : serviceSelect.value);
4492		}
4493		else if (e.keyCode == 27)
4494		{
4495			editorUi.fileLoaded(null);
4496			editorUi.hideDialog();
4497			window.close();
4498		}
4499	});
4500
4501	div.appendChild(btns);
4502
4503	this.container = div;
4504};
4505
4506/**
4507 *
4508 */
4509CreateDialog.showDownloadButton = urlParams['noDevice'] != '1';
4510
4511/**
4512 * Constructs a new popup dialog.
4513 */
4514var PopupDialog = function(editorUi, url, pre, fallback, hideDialog)
4515{
4516	hideDialog = (hideDialog != null) ? hideDialog : true;
4517
4518	var div = document.createElement('div');
4519	div.style.textAlign = 'left';
4520	div.style.height = '100%';
4521
4522	mxUtils.write(div, mxResources.get('fileOpenLocation'));
4523	mxUtils.br(div);
4524	mxUtils.br(div);
4525
4526	var replaceBtn = mxUtils.button(mxResources.get('openInThisWindow'), function()
4527	{
4528		if (hideDialog)
4529		{
4530			editorUi.hideDialog();
4531		}
4532
4533		if (fallback != null)
4534		{
4535			fallback();
4536		}
4537	});
4538	replaceBtn.className = 'geBtn';
4539	replaceBtn.style.marginBottom = '8px';
4540	replaceBtn.style.width = '280px';
4541	div.appendChild(replaceBtn);
4542
4543	mxUtils.br(div);
4544
4545	var wndBtn = mxUtils.button(mxResources.get('openInNewWindow'), function()
4546	{
4547		if (hideDialog)
4548		{
4549			editorUi.hideDialog();
4550		}
4551
4552		if (pre != null)
4553		{
4554			pre();
4555		}
4556
4557		editorUi.openLink(url, null, true);
4558	});
4559	wndBtn.className = 'geBtn gePrimaryBtn';
4560	wndBtn.style.width = replaceBtn.style.width;
4561	div.appendChild(wndBtn);
4562
4563	mxUtils.br(div);
4564	mxUtils.br(div);
4565	mxUtils.write(div, mxResources.get('allowPopups'));
4566
4567	this.container = div;
4568};
4569
4570/**
4571 * Constructs a new image dialog.
4572 */
4573var ImageDialog = function(editorUi, title, initialValue, fn, ignoreExisting, convertDataUri)
4574{
4575	convertDataUri = (convertDataUri != null) ? convertDataUri : true;
4576
4577	var graph = editorUi.editor.graph;
4578	var div = document.createElement('div');
4579	mxUtils.write(div, title);
4580
4581	var inner = document.createElement('div');
4582	inner.className = 'geTitle';
4583	inner.style.backgroundColor = 'transparent';
4584	inner.style.borderColor = 'transparent';
4585	inner.style.whiteSpace = 'nowrap';
4586	inner.style.textOverflow = 'clip';
4587	inner.style.cursor = 'default';
4588	inner.style.paddingRight = '20px';
4589
4590	var linkInput = document.createElement('input');
4591	linkInput.setAttribute('value', initialValue);
4592	linkInput.setAttribute('type', 'text');
4593	linkInput.setAttribute('spellcheck', 'false');
4594	linkInput.setAttribute('autocorrect', 'off');
4595	linkInput.setAttribute('autocomplete', 'off');
4596	linkInput.setAttribute('autocapitalize', 'off');
4597	linkInput.style.marginTop = '6px';
4598	var realWidth = (Graph.fileSupport) ? 460 : 340;
4599	linkInput.style.width = realWidth - 20 + 'px';
4600	linkInput.style.backgroundImage = 'url(\'' + Dialog.prototype.clearImage + '\')';
4601	linkInput.style.backgroundRepeat = 'no-repeat';
4602	linkInput.style.backgroundPosition = '100% 50%';
4603	linkInput.style.paddingRight = '14px';
4604
4605	var cross = document.createElement('div');
4606	cross.setAttribute('title', mxResources.get('reset'));
4607	cross.style.position = 'relative';
4608	cross.style.left = '-16px';
4609	cross.style.width = '12px';
4610	cross.style.height = '14px';
4611	cross.style.cursor = 'pointer';
4612
4613	// Workaround for inline-block not supported in IE
4614	cross.style.display = 'inline-block';
4615	cross.style.top = '3px';
4616
4617	// Needed to block event transparency in IE
4618	cross.style.background = 'url(\'' + editorUi.editor.transparentImage + '\')';
4619
4620	mxEvent.addListener(cross, 'click', function()
4621	{
4622		linkInput.value = '';
4623		linkInput.focus();
4624	});
4625
4626	inner.appendChild(linkInput);
4627	inner.appendChild(cross);
4628	div.appendChild(inner);
4629
4630	var insertImage = function(newValue, w, h, resize)
4631	{
4632		var dataUri = newValue.substring(0, 5) == 'data:';
4633
4634		if (!editorUi.isOffline() || (dataUri && typeof chrome === 'undefined'))
4635		{
4636			if (newValue.length > 0 && editorUi.spinner.spin(document.body, mxResources.get('inserting')))
4637			{
4638				var maxSize = 520;
4639
4640				editorUi.loadImage(newValue, function(img)
4641				{
4642					editorUi.spinner.stop();
4643					editorUi.hideDialog();
4644					var s = (resize === false) ? 1 :
4645						(w != null && h != null) ? Math.max(w / img.width, h / img.height) :
4646						Math.min(1, Math.min(maxSize / img.width, maxSize / img.height));
4647
4648					// Handles special case of data URI which needs to be rewritten
4649					// to be used in a cell style to remove the semicolon
4650					if (convertDataUri)
4651					{
4652						newValue = editorUi.convertDataUri(newValue);
4653					}
4654
4655	    				fn(newValue, Math.round(Number(img.width) * s), Math.round(Number(img.height) * s));
4656		    		}, function()
4657		    		{
4658		    			editorUi.spinner.stop();
4659		    			fn(null);
4660					editorUi.showError(mxResources.get('error'), mxResources.get('fileNotFound'), mxResources.get('ok'));
4661		    		});
4662			}
4663			else
4664			{
4665				editorUi.hideDialog();
4666				fn(newValue);
4667			}
4668		}
4669		else
4670		{
4671			newValue = editorUi.convertDataUri(newValue);
4672			w = (w == null) ? 120 : w;
4673			h = (h == null) ? 100 : h;
4674
4675			editorUi.hideDialog();
4676			fn(newValue, w, h);
4677		}
4678	};
4679
4680	var apply = function(newValue, resize)
4681	{
4682		if (newValue != null)
4683		{
4684			var geo = (ignoreExisting) ? null : graph.getModel().getGeometry(graph.getSelectionCell());
4685
4686			// Reuses width and height of existing cell
4687			if (geo != null)
4688			{
4689				insertImage(newValue, geo.width, geo.height, resize);
4690			}
4691			else
4692			{
4693				insertImage(newValue, null, null, resize);
4694			}
4695		}
4696		else
4697		{
4698			editorUi.hideDialog();
4699			fn(null);
4700		}
4701	};
4702
4703	this.init = function()
4704	{
4705		linkInput.focus();
4706
4707		// Installs drag and drop handler for local images and links
4708		if (Graph.fileSupport)
4709		{
4710			linkInput.setAttribute('placeholder', mxResources.get('dragImagesHere'));
4711
4712			// Setup the dnd listeners
4713			var dlg = div.parentNode;
4714			var graph = editorUi.editor.graph;
4715			var dropElt = null;
4716
4717			mxEvent.addListener(dlg, 'dragleave', function(evt)
4718			{
4719				if (dropElt != null)
4720			    {
4721			    	dropElt.parentNode.removeChild(dropElt);
4722			    	dropElt = null;
4723			    }
4724
4725				evt.stopPropagation();
4726				evt.preventDefault();
4727			});
4728
4729			mxEvent.addListener(dlg, 'dragover', mxUtils.bind(this, function(evt)
4730			{
4731				// IE 10 does not implement pointer-events so it can't have a drop highlight
4732				if (dropElt == null && (!mxClient.IS_IE || document.documentMode > 10))
4733				{
4734					dropElt = editorUi.highlightElement(dlg);
4735				}
4736
4737				evt.stopPropagation();
4738				evt.preventDefault();
4739			}));
4740
4741			mxEvent.addListener(dlg, 'drop', mxUtils.bind(this, function(evt)
4742			{
4743			    if (dropElt != null)
4744			    {
4745				    	dropElt.parentNode.removeChild(dropElt);
4746				    	dropElt = null;
4747			    }
4748
4749			    if (evt.dataTransfer.files.length > 0)
4750			    {
4751			    	editorUi.importFiles(evt.dataTransfer.files, 0, 0, editorUi.maxImageSize, function(data, mimeType, x, y, w, h, fileName, resize)
4752			    	{
4753			    		apply(data, resize);
4754			    	}, function()
4755			    	{
4756			    		// No post processing
4757			    	}, function(file)
4758			    	{
4759			    		// Handles only images
4760			    		return file.type.substring(0, 6) == 'image/';
4761			    	}, function(queue)
4762			    	{
4763			    		// Invokes elements of queue in order
4764			    		for (var i = 0; i < queue.length; i++)
4765			    		{
4766			    			queue[i]();
4767			    		}
4768			    	}, !mxEvent.isControlDown(evt), null, null, true);
4769	    		}
4770			    else if (mxUtils.indexOf(evt.dataTransfer.types, 'text/uri-list') >= 0)
4771			    {
4772				    	var uri = evt.dataTransfer.getData('text/uri-list');
4773
4774				    	if ((/\.(gif|jpg|jpeg|tiff|png|svg)($|\?)/i).test(uri))
4775					{
4776				    		apply(decodeURIComponent(uri));
4777					}
4778			    }
4779
4780			    evt.stopPropagation();
4781			    evt.preventDefault();
4782			}), false);
4783		}
4784	};
4785
4786	var btns = document.createElement('div');
4787	btns.style.marginTop = '14px';
4788	btns.style.textAlign = 'center';
4789
4790	var cancelBtn = mxUtils.button(mxResources.get('cancel'), function()
4791	{
4792		// Just in case a spinner is spinning, has no effect otherwise
4793		editorUi.spinner.stop();
4794		editorUi.hideDialog();
4795	});
4796
4797	cancelBtn.className = 'geBtn';
4798
4799	if (editorUi.editor.cancelFirst)
4800	{
4801		btns.appendChild(cancelBtn);
4802	}
4803
4804	ImageDialog.filePicked = function(data)
4805	{
4806        if (data.action == google.picker.Action.PICKED)
4807        {
4808        	if (data.docs[0].thumbnails != null)
4809        	{
4810	        	var thumb = data.docs[0].thumbnails[data.docs[0].thumbnails.length - 1];
4811
4812	        	if (thumb != null)
4813	        	{
4814	        		linkInput.value = thumb.url;
4815	        	}
4816        	}
4817        }
4818
4819        linkInput.focus();
4820	};
4821
4822	if (Graph.fileSupport)
4823	{
4824		if (editorUi.imgDlgFileInputElt == null)
4825		{
4826			var fileInput = document.createElement('input');
4827			fileInput.setAttribute('multiple', 'multiple');
4828			fileInput.setAttribute('type', 'file');
4829
4830			mxEvent.addListener(fileInput, 'change', function(evt)
4831			{
4832				if (fileInput.files != null)
4833				{
4834					editorUi.importFiles(fileInput.files, 0, 0, editorUi.maxImageSize, function(data, mimeType, x, y, w, h)
4835			    	{
4836			    		apply(data);
4837			    	}, function()
4838			    	{
4839			    		// No post processing
4840			    	}, function(file)
4841			    	{
4842			    		// Handles only images
4843			    		return file.type.substring(0, 6) == 'image/';
4844			    	}, function(queue)
4845			    	{
4846			    		// Invokes elements of queue in order
4847			    		for (var i = 0; i < queue.length; i++)
4848			    		{
4849			    			queue[i]();
4850			    		}
4851			    	}, true);
4852
4853		    		// Resets input to force change event for same file (type reset required for IE)
4854					fileInput.type = '';
4855					fileInput.type = 'file';
4856					fileInput.value = '';
4857				}
4858			});
4859
4860			fileInput.style.display = 'none';
4861			document.body.appendChild(fileInput);
4862			editorUi.imgDlgFileInputElt = fileInput;
4863		}
4864
4865		var btn = mxUtils.button(mxResources.get('open'), function()
4866		{
4867			editorUi.imgDlgFileInputElt.click();
4868		});
4869
4870		btn.className = 'geBtn';
4871		btns.appendChild(btn);
4872	}
4873
4874	// Image cropping (experimental)
4875	if (!!document.createElement('canvas').getContext && linkInput.value.substring(0, 11) == 'data:image/' &&
4876		linkInput.value.substring(0, 14) != 'data:image/svg')
4877	{
4878		var cropBtn = mxUtils.button(mxResources.get('crop'), function()
4879		{
4880	    	var dlg = new CropImageDialog(editorUi, linkInput.value, function(image)
4881	    	{
4882	    		linkInput.value = image;
4883	    	});
4884
4885	    	editorUi.showDialog(dlg.container, 300, 380, true, true);
4886			dlg.init();
4887		});
4888
4889		cropBtn.className = 'geBtn';
4890		btns.appendChild(cropBtn);
4891	}
4892
4893	mxEvent.addListener(linkInput, 'keypress', function(e)
4894	{
4895		if (e.keyCode == 13)
4896		{
4897			apply(linkInput.value);
4898		}
4899	});
4900
4901	var applyBtn = mxUtils.button(mxResources.get('apply'), function()
4902	{
4903		apply(linkInput.value);
4904	});
4905
4906	applyBtn.className = 'geBtn gePrimaryBtn';
4907	btns.appendChild(applyBtn);
4908
4909	if (!editorUi.editor.cancelFirst)
4910	{
4911		btns.appendChild(cancelBtn);
4912	}
4913
4914	// Shows drop icon in dialog background
4915	if (Graph.fileSupport)
4916	{
4917		btns.style.marginTop = '120px';
4918		div.style.backgroundImage = 'url(\'' + IMAGE_PATH + '/droptarget.png\')';
4919		div.style.backgroundPosition = 'center 65%';
4920		div.style.backgroundRepeat = 'no-repeat';
4921
4922		var bg = document.createElement('div');
4923		bg.style.position = 'absolute';
4924		bg.style.width = '420px';
4925		bg.style.top = '58%';
4926		bg.style.textAlign = 'center';
4927		bg.style.fontSize = '18px';
4928		bg.style.color = '#a0c3ff';
4929		mxUtils.write(bg, mxResources.get('dragImagesHere'));
4930		div.appendChild(bg);
4931	}
4932
4933	div.appendChild(btns);
4934
4935	this.container = div;
4936};
4937
4938/**
4939 * Overrides link dialog to add Google Picker.
4940 */
4941var LinkDialog = function(editorUi, initialValue, btnLabel, fn, showPages, showNewWindowOption, linkTarget)
4942{
4943	var div = document.createElement('div');
4944	div.style.height = '100%';
4945	mxUtils.write(div, mxResources.get('editLink') + ':');
4946
4947	var inner = document.createElement('div');
4948	inner.className = 'geTitle';
4949	inner.style.backgroundColor = 'transparent';
4950	inner.style.borderColor = 'transparent';
4951	inner.style.whiteSpace = 'nowrap';
4952	inner.style.textOverflow = 'clip';
4953	inner.style.cursor = 'default';
4954	inner.style.paddingRight = '20px';
4955
4956	var linkInput = document.createElement('input');
4957	linkInput.setAttribute('placeholder', mxResources.get('dragUrlsHere'));
4958	linkInput.setAttribute('type', 'text');
4959	linkInput.style.marginTop = '6px';
4960	linkInput.style.width = '100%';
4961	linkInput.style.boxSizing = 'border-box';
4962	linkInput.style.backgroundImage = 'url(\'' + Dialog.prototype.clearImage + '\')';
4963	linkInput.style.backgroundRepeat = 'no-repeat';
4964	linkInput.style.backgroundPosition = '100% 50%';
4965	linkInput.style.paddingRight = '14px';
4966	linkInput.style.marginBottom = '4px';
4967
4968	var cross = document.createElement('div');
4969	cross.setAttribute('title', mxResources.get('reset'));
4970	cross.style.position = 'relative';
4971	cross.style.left = '-16px';
4972	cross.style.width = '12px';
4973	cross.style.height = '14px';
4974	cross.style.cursor = 'pointer';
4975
4976	// Workaround for inline-block not supported in IE
4977	cross.style.display = 'inline-block';
4978	cross.style.top = '3px';
4979
4980	// Needed to block event transparency in IE
4981	cross.style.background = 'url(\'' + editorUi.editor.transparentImage + '\')';
4982
4983	mxEvent.addListener(cross, 'click', function()
4984	{
4985		linkInput.value = '';
4986		linkInput.focus();
4987	});
4988
4989	var urlRadio = document.createElement('input');
4990	urlRadio.style.cssText = 'margin-right:8px;margin-bottom:8px;';
4991	urlRadio.setAttribute('value', 'url');
4992	urlRadio.setAttribute('type', 'radio');
4993	urlRadio.setAttribute('name', 'geLinkDialogOption');
4994
4995	var pageRadio = document.createElement('input');
4996	pageRadio.style.cssText = 'margin-right:8px;margin-bottom:8px;';
4997	pageRadio.setAttribute('value', 'url');
4998	pageRadio.setAttribute('type', 'radio');
4999	pageRadio.setAttribute('name', 'geLinkDialogOption');
5000
5001	var pageSelect = document.createElement('select');
5002	pageSelect.style.width = '520px';
5003
5004	var newWindowCheckbox = document.createElement('input');
5005	newWindowCheckbox.setAttribute('type', 'checkbox');
5006
5007	newWindowCheckbox.style.margin = '0 6p 0 6px';
5008
5009	if (linkTarget != null)
5010	{
5011		newWindowCheckbox.setAttribute('checked', 'checked');
5012		newWindowCheckbox.defaultChecked = true;
5013	}
5014
5015	linkTarget = (linkTarget != null) ? linkTarget : '_blank';
5016	newWindowCheckbox.setAttribute('title', linkTarget);
5017
5018	if (showNewWindowOption)
5019	{
5020		linkInput.style.width = '340px';
5021	}
5022
5023	if (showPages && editorUi.pages != null)
5024	{
5025		if (initialValue != null && Graph.isPageLink(initialValue))
5026		{
5027			pageRadio.setAttribute('checked', 'checked');
5028			pageRadio.defaultChecked = true;
5029		}
5030		else
5031		{
5032			linkInput.setAttribute('value', initialValue);
5033			urlRadio.setAttribute('checked', 'checked');
5034			urlRadio.defaultChecked = true;
5035		}
5036
5037		inner.appendChild(urlRadio);
5038		inner.appendChild(linkInput);
5039		inner.appendChild(cross);
5040
5041		if (showNewWindowOption)
5042		{
5043			inner.appendChild(newWindowCheckbox);
5044			mxUtils.write(inner, mxResources.get('openInNewWindow'));
5045		}
5046
5047		mxUtils.br(inner);
5048		inner.appendChild(pageRadio);
5049
5050		var pageFound = false;
5051
5052		for (var i = 0; i < editorUi.pages.length; i++)
5053		{
5054			var pageOption = document.createElement('option');
5055			mxUtils.write(pageOption, editorUi.pages[i].getName() ||
5056				mxResources.get('pageWithNumber', [i + 1]));
5057			pageOption.setAttribute('value', 'data:page/id,' +
5058				editorUi.pages[i].getId());
5059
5060			if (initialValue == pageOption.getAttribute('value'))
5061			{
5062				pageOption.setAttribute('selected', 'selected');
5063				pageFound = true;
5064			}
5065
5066			pageSelect.appendChild(pageOption);
5067		}
5068
5069		if (!pageFound && pageRadio.checked)
5070		{
5071			var notFoundOption = document.createElement('option');
5072			mxUtils.write(notFoundOption, mxResources.get('pageNotFound'));
5073			notFoundOption.setAttribute('disabled', 'disabled');
5074			notFoundOption.setAttribute('selected', 'selected');
5075			notFoundOption.setAttribute('value', 'pageNotFound');
5076			pageSelect.appendChild(notFoundOption);
5077
5078			mxEvent.addListener(pageSelect, 'change', function()
5079			{
5080				if (notFoundOption.parentNode != null && !notFoundOption.selected)
5081				{
5082					notFoundOption.parentNode.removeChild(notFoundOption);
5083				}
5084			});
5085		}
5086
5087		inner.appendChild(pageSelect);
5088	}
5089	else
5090	{
5091		linkInput.setAttribute('value', initialValue);
5092		inner.appendChild(linkInput);
5093		inner.appendChild(cross);
5094	}
5095
5096	div.appendChild(inner);
5097
5098	var mainBtn = mxUtils.button(btnLabel, function()
5099	{
5100		editorUi.hideDialog();
5101		var value = (pageRadio.checked) ? ((pageSelect.value !== 'pageNotFound') ?
5102			pageSelect.value : initialValue) : linkInput.value;
5103		fn(value, LinkDialog.selectedDocs, (newWindowCheckbox.checked) ? linkTarget : null);
5104	});
5105	mainBtn.style.verticalAlign = 'middle';
5106	mainBtn.className = 'geBtn gePrimaryBtn';
5107
5108	this.init = function()
5109	{
5110		if (pageRadio.checked)
5111		{
5112			pageSelect.focus();
5113		}
5114		else
5115		{
5116			linkInput.focus();
5117
5118			if (mxClient.IS_GC || mxClient.IS_FF || document.documentMode >= 5)
5119			{
5120				linkInput.select();
5121			}
5122			else
5123			{
5124				document.execCommand('selectAll', false, null);
5125			}
5126		}
5127
5128		mxEvent.addListener(pageSelect, 'focus', function()
5129		{
5130			urlRadio.removeAttribute('checked');
5131			pageRadio.setAttribute('checked', 'checked');
5132			pageRadio.checked = true;
5133		});
5134
5135		mxEvent.addListener(linkInput, 'focus', function()
5136		{
5137			pageRadio.removeAttribute('checked');
5138			urlRadio.setAttribute('checked', 'checked');
5139			urlRadio.checked = true;
5140		});
5141
5142		// Installs drag and drop handler for links
5143		if (Graph.fileSupport)
5144		{
5145			// Setup the dnd listeners
5146			var dlg = div.parentNode;
5147			var graph = editorUi.editor.graph;
5148			var dropElt = null;
5149
5150			mxEvent.addListener(dlg, 'dragleave', function(evt)
5151			{
5152				if (dropElt != null)
5153			    {
5154			    	dropElt.parentNode.removeChild(dropElt);
5155			    	dropElt = null;
5156			    }
5157
5158				evt.stopPropagation();
5159				evt.preventDefault();
5160			});
5161
5162			mxEvent.addListener(dlg, 'dragover', mxUtils.bind(this, function(evt)
5163			{
5164				// IE 10 does not implement pointer-events so it can't have a drop highlight
5165				if (dropElt == null && (!mxClient.IS_IE || document.documentMode > 10))
5166				{
5167					dropElt = editorUi.highlightElement(dlg);
5168				}
5169
5170				evt.stopPropagation();
5171				evt.preventDefault();
5172			}));
5173
5174			mxEvent.addListener(dlg, 'drop', mxUtils.bind(this, function(evt)
5175			{
5176			    if (dropElt != null)
5177			    {
5178			    	dropElt.parentNode.removeChild(dropElt);
5179			    	dropElt = null;
5180			    }
5181
5182			    if (mxUtils.indexOf(evt.dataTransfer.types, 'text/uri-list') >= 0)
5183			    {
5184			    	linkInput.value = decodeURIComponent(evt.dataTransfer.getData('text/uri-list'));
5185			    	urlRadio.setAttribute('checked', 'checked');
5186			    	urlRadio.checked = true;
5187			    	mainBtn.click();
5188			    }
5189
5190			    evt.stopPropagation();
5191			    evt.preventDefault();
5192			}), false);
5193		}
5194	};
5195
5196	var btns = document.createElement('div');
5197	btns.style.marginTop = '18px';
5198	btns.style.textAlign = 'center';
5199
5200	var helpBtn = mxUtils.button(mxResources.get('help'), function()
5201	{
5202		editorUi.openLink('https://www.diagrams.net/doc/faq/custom-links');
5203	});
5204
5205	helpBtn.style.verticalAlign = 'middle';
5206	helpBtn.className = 'geBtn';
5207	btns.appendChild(helpBtn);
5208
5209	if (editorUi.isOffline() && !mxClient.IS_CHROMEAPP)
5210	{
5211		helpBtn.style.display = 'none';
5212	}
5213
5214	var cancelBtn = mxUtils.button(mxResources.get('cancel'), function()
5215	{
5216		editorUi.hideDialog();
5217	});
5218	cancelBtn.style.verticalAlign = 'middle';
5219	cancelBtn.className = 'geBtn';
5220
5221	if (editorUi.editor.cancelFirst)
5222	{
5223		btns.appendChild(cancelBtn);
5224	}
5225
5226	LinkDialog.selectedDocs = null;
5227
5228	LinkDialog.filePicked = function(data)
5229	{
5230		if (data.action == google.picker.Action.PICKED)
5231        {
5232			LinkDialog.selectedDocs = data.docs;
5233        	var href = data.docs[0].url;
5234
5235    		if (data.docs[0].mimeType == 'application/mxe' || (data.docs[0].mimeType != null &&
5236    			data.docs[0].mimeType.substring(0, 23) == 'application/vnd.jgraph.'))
5237    		{
5238				href = 'https://www.draw.io/#G' + data.docs[0].id;
5239    		}
5240    		else if (data.docs[0].mimeType == 'application/vnd.google-apps.folder')
5241    		{
5242    			// Do not use folderview in data.docs[0].url link to Google Drive instead
5243    			href = 'https://drive.google.com/#folders/' + data.docs[0].id;
5244    		}
5245
5246    		linkInput.value = href;
5247    		linkInput.focus();
5248        }
5249		else
5250		{
5251			LinkDialog.selectedDocs = null;
5252		}
5253
5254		linkInput.focus();
5255	};
5256
5257	function addButton(src, tooltip, fn)
5258	{
5259		var btn = mxUtils.button('', fn);
5260		btn.className = 'geBtn';
5261		btn.setAttribute('title', tooltip);
5262		var img = document.createElement('img');
5263		img.style.height = '26px';
5264		img.style.width = '26px';
5265		img.setAttribute('src', src);
5266		btn.style.minWidth = '42px';
5267		btn.style.verticalAlign = 'middle';
5268		btn.appendChild(img);
5269		btns.appendChild(btn);
5270	};
5271
5272	if (typeof(google) != 'undefined' && typeof(google.picker) != 'undefined' && editorUi.drive != null)
5273	{
5274		addButton(IMAGE_PATH + '/google-drive-logo.svg', mxResources.get('googlePlus'), function()
5275		{
5276			if (editorUi.spinner.spin(document.body, mxResources.get('authorizing')))
5277			{
5278				editorUi.drive.checkToken(mxUtils.bind(this, function()
5279				{
5280					editorUi.spinner.stop();
5281
5282					// Creates one picker and reuses it to avoid polluting the DOM
5283					if (editorUi.linkPicker == null)
5284					{
5285						var picker = editorUi.drive.createLinkPicker();
5286
5287						editorUi.linkPicker = picker.setCallback(function(data)
5288						{
5289							LinkDialog.filePicked(data);
5290					    }).build();
5291					}
5292
5293					editorUi.linkPicker.setVisible(true);
5294				}));
5295			}
5296		});
5297	}
5298
5299	if (typeof(Dropbox) != 'undefined' && typeof(Dropbox.choose) != 'undefined')
5300	{
5301		addButton(IMAGE_PATH + '/dropbox-logo.svg', mxResources.get('dropbox'), function()
5302		{
5303			// Authentication will be carried out on open to make sure the
5304			// autosave does not show an auth dialog. Showing it here will
5305			// block the second dialog (the file picker) so it's too early.
5306			Dropbox.choose(
5307			{
5308				linkType : 'direct',
5309				cancel: function()
5310				{
5311					// do nothing
5312		        },
5313				success : function(files)
5314				{
5315					linkInput.value = files[0].link;
5316					linkInput.focus();
5317				}
5318			});
5319		});
5320	}
5321
5322	if (editorUi.oneDrive != null)
5323	{
5324		addButton(IMAGE_PATH + '/onedrive-logo.svg', mxResources.get('oneDrive'), function()
5325		{
5326			editorUi.oneDrive.pickFile(function(id, files)
5327			{
5328				linkInput.value = files.value[0].webUrl;
5329				linkInput.focus();
5330			});
5331		});
5332	}
5333
5334	if (editorUi.gitHub != null)
5335	{
5336		addButton(IMAGE_PATH + '/github-logo.svg', mxResources.get('github'), function()
5337		{
5338			editorUi.gitHub.pickFile(function(path)
5339			{
5340				if (path != null)
5341				{
5342					var tokens = path.split('/');
5343					var org = tokens[0];
5344					var repo = tokens[1];
5345					var ref = tokens[2];
5346					var path = tokens.slice(3, tokens.length).join('/');
5347
5348					linkInput.value = 'https://github.com/' + org + '/' +
5349						repo + '/blob/' + ref + '/' + path;
5350					linkInput.focus();
5351				}
5352			});
5353		});
5354	}
5355
5356	if (editorUi.gitLab != null)
5357	{
5358		addButton(IMAGE_PATH + '/gitlab-logo.svg', mxResources.get('gitlab'), function()
5359		{
5360			editorUi.gitLab.pickFile(function(path)
5361			{
5362				if (path != null)
5363				{
5364					var tokens = path.split('/');
5365					var org = tokens[0];
5366					var repo = tokens[1];
5367					var ref = tokens[2];
5368					var path = tokens.slice(3, tokens.length).join('/');
5369
5370					linkInput.value = DRAWIO_GITLAB_URL + '/' + org + '/' +
5371						repo + '/blob/' + ref + '/' + path;
5372					linkInput.focus();
5373				}
5374			});
5375		});
5376	}
5377
5378	//TODO should Notion support this?
5379	//TODO should Trello support this?
5380
5381	mxEvent.addListener(linkInput, 'keypress', function(e)
5382	{
5383		if (e.keyCode == 13)
5384		{
5385			editorUi.hideDialog();
5386			var value = (pageRadio.checked) ? pageSelect.value : linkInput.value;
5387			fn(value, LinkDialog.selectedDocs);
5388		}
5389	});
5390
5391	btns.appendChild(mainBtn);
5392
5393	if (!editorUi.editor.cancelFirst)
5394	{
5395		btns.appendChild(cancelBtn);
5396	}
5397
5398	div.appendChild(btns);
5399
5400	this.container = div;
5401};
5402
5403/**
5404 * Constructs a new about dialog
5405 */
5406var FeedbackDialog = function(editorUi, subject, emailOptional, diagramData)
5407{
5408	var div = document.createElement('div');
5409
5410	var label = document.createElement('div');
5411	mxUtils.write(label, mxResources.get('sendYourFeedback'));
5412	label.style.fontSize = '18px';
5413	label.style.marginBottom = '18px';
5414
5415	div.appendChild(label);
5416
5417	label = document.createElement('div');
5418	mxUtils.write(label, mxResources.get('yourEmailAddress') +
5419		((emailOptional) ? '' : ' (' + mxResources.get('required') + ')'));
5420
5421	div.appendChild(label);
5422
5423	var email = document.createElement('input');
5424	email.setAttribute('type', 'text');
5425	email.style.marginTop = '6px';
5426	email.style.width = '600px';
5427
5428	var sendButton = mxUtils.button(mxResources.get('sendMessage'), function()
5429	{
5430		var diagram = textarea.value +
5431			((cb.checked) ? '\nDiagram:\n' + ((diagramData != null) ?
5432			diagramData : mxUtils.getXml(editorUi.getXmlFileData())) : '') +
5433			'\nuserAgent:\n' + navigator.userAgent +
5434			'\nappVersion:\n' + navigator.appVersion +
5435			'\nappName:\n' + navigator.appName +
5436			'\nplatform:\n' + navigator.platform;
5437
5438		if (diagram.length > FeedbackDialog.maxAttachmentSize)
5439		{
5440			editorUi.alert(mxResources.get('drawingTooLarge'));
5441		}
5442		else
5443		{
5444			editorUi.hideDialog();
5445
5446			if (editorUi.spinner.spin(document.body))
5447			{
5448				var postUrl = (FeedbackDialog.feedbackUrl != null) ? FeedbackDialog.feedbackUrl : '/email';
5449				mxUtils.post(postUrl, 'email=' + encodeURIComponent(email.value) +
5450						'&version=' + encodeURIComponent(EditorUi.VERSION) +
5451						'&url=' + encodeURIComponent(window.location.href) +
5452						'&body=' + encodeURIComponent(((subject != null) ?
5453							subject : 'Feedback') + ':\n' + diagram),
5454					function(req)
5455					{
5456						editorUi.spinner.stop();
5457
5458						if (req.getStatus() >= 200 && req.getStatus() <= 299)
5459						{
5460							editorUi.alert(mxResources.get('feedbackSent'));
5461						}
5462						else
5463						{
5464							editorUi.alert(mxResources.get('errorSendingFeedback'));
5465						}
5466					},
5467					function()
5468					{
5469						editorUi.spinner.stop();
5470						editorUi.alert(mxResources.get('errorSendingFeedback'));
5471					});
5472			}
5473		}
5474	});
5475	sendButton.className = 'geBtn gePrimaryBtn';
5476
5477	if (!emailOptional)
5478	{
5479		sendButton.setAttribute('disabled', 'disabled');
5480
5481		var re = /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
5482
5483		mxEvent.addListener(email, 'change', function()
5484		{
5485			if (email.value.length > 0 && re.test(email.value) > 0)
5486			{
5487				sendButton.removeAttribute('disabled');
5488			}
5489			else
5490			{
5491				sendButton.setAttribute('disabled', 'disabled');
5492			}
5493		});
5494
5495		mxEvent.addListener(email, 'keyup', function()
5496		{
5497			if (email.value.length > 0 && re.test(email.value))
5498			{
5499				sendButton.removeAttribute('disabled');
5500			}
5501			else
5502			{
5503				sendButton.setAttribute('disabled', 'disabled');
5504			}
5505		});
5506	}
5507
5508	div.appendChild(email);
5509
5510	this.init = function()
5511	{
5512		email.focus();
5513	};
5514
5515	var cb = document.createElement('input');
5516	cb.setAttribute('type', 'checkbox');
5517	cb.setAttribute('checked', 'checked');
5518	cb.defaultChecked = true;
5519
5520	var p2 = document.createElement('p');
5521	p2.style.marginTop = '14px';
5522	p2.appendChild(cb);
5523
5524	var span = document.createElement('span');
5525	mxUtils.write(span, ' ' + mxResources.get('includeCopyOfMyDiagram'));
5526	p2.appendChild(span);
5527
5528	mxEvent.addListener(span, 'click', function(evt)
5529	{
5530		cb.checked = !cb.checked;
5531		mxEvent.consume(evt);
5532	});
5533
5534	div.appendChild(p2);
5535
5536	label = document.createElement('div');
5537	mxUtils.write(label, mxResources.get('feedback'));
5538
5539	div.appendChild(label);
5540
5541	var textarea = document.createElement('textarea');
5542	textarea.style.resize = 'none';
5543	textarea.style.width = '600px';
5544	textarea.style.height = '140px';
5545	textarea.style.marginTop = '6px';
5546
5547	textarea.setAttribute('placeholder', mxResources.get('comments'));
5548
5549	div.appendChild(textarea);
5550
5551	var buttons = document.createElement('div');
5552	buttons.style.marginTop = '26px';
5553	buttons.style.textAlign = 'right';
5554
5555	var cancelBtn = mxUtils.button(mxResources.get('cancel'), function()
5556	{
5557		editorUi.hideDialog();
5558	});
5559	cancelBtn.className = 'geBtn';
5560
5561	if (editorUi.editor.cancelFirst)
5562	{
5563		buttons.appendChild(cancelBtn);
5564		buttons.appendChild(sendButton);
5565	}
5566	else
5567	{
5568		buttons.appendChild(sendButton);
5569		buttons.appendChild(cancelBtn);
5570	}
5571
5572	div.appendChild(buttons);
5573	this.container = div;
5574};
5575
5576/**
5577 * Maximum size of attachments in bytes. Default is 1000000.
5578 */
5579FeedbackDialog.maxAttachmentSize = 1000000;
5580
5581/**
5582 * Constructs a new revision dialog
5583 */
5584var RevisionDialog = function(editorUi, revs, restoreFn)
5585{
5586	var div = document.createElement('div');
5587
5588	var title = document.createElement('h3');
5589	title.style.marginTop = '0px';
5590	mxUtils.write(title, mxResources.get('revisionHistory'));
5591	div.appendChild(title);
5592
5593	var list = document.createElement('div');
5594	list.style.position = 'absolute';
5595	list.style.overflow = 'auto';
5596	list.style.width = '170px';
5597	list.style.height = '378px';
5598	div.appendChild(list);
5599
5600	var container = document.createElement('div');
5601	container.style.position = 'absolute';
5602	container.style.border = '1px solid lightGray';
5603	container.style.left = '199px';
5604	container.style.width = '470px';
5605	container.style.height = '376px';
5606	container.style.overflow = 'hidden';
5607
5608	// Contains possible error messages
5609	var errorNode = document.createElement('div');
5610	errorNode.style.cssText = 'position:absolute;left:0;right:0;top:0;bottom:20px;text-align:center;transform:translate(0,50%);pointer-events:none;';
5611	container.appendChild(errorNode);
5612
5613	mxEvent.disableContextMenu(container);
5614	div.appendChild(container);
5615
5616	var graph = new Graph(container);
5617	graph.setTooltips(false);
5618	graph.setEnabled(false);
5619	graph.setPanning(true);
5620	graph.panningHandler.ignoreCell = true;
5621	graph.panningHandler.useLeftButtonForPanning = true;
5622	graph.minFitScale = null;
5623	graph.maxFitScale = null;
5624	graph.centerZoom = true;
5625
5626	// Handles placeholders for pages
5627	var currentPage = 0;
5628	var diagrams = null;
5629	var realPage = 0;
5630
5631	var graphGetGlobalVariable = graph.getGlobalVariable;
5632
5633	graph.getGlobalVariable = function(name)
5634	{
5635		if (name == 'page' && diagrams != null && diagrams[realPage] != null)
5636		{
5637			return diagrams[realPage].getAttribute('name');
5638		}
5639		else if (name == 'pagenumber')
5640		{
5641			return realPage + 1;
5642		}
5643		else if (name == 'pagecount')
5644		{
5645			return (diagrams != null) ? diagrams.length : 1;
5646		}
5647
5648		return graphGetGlobalVariable.apply(this, arguments);
5649	};
5650
5651	// Disables hyperlinks
5652	graph.getLinkForCell = function()
5653	{
5654		return null;
5655	};
5656
5657	if (Editor.MathJaxRender)
5658	{
5659		graph.addListener(mxEvent.SIZE, mxUtils.bind(this, function(sender, evt)
5660		{
5661			// LATER: Math support is used if current graph has math enabled
5662			// should use switch from history instead but requires setting the
5663			// global mxClient.NO_FO switch
5664			if (editorUi.editor.graph.mathEnabled)
5665			{
5666				Editor.MathJaxRender(graph.container);
5667			}
5668		}));
5669	}
5670
5671	var opts = {
5672	  lines: 11, // The number of lines to draw
5673	  length: 15, // The length of each line
5674	  width: 6, // The line thickness
5675	  radius: 10, // The radius of the inner circle
5676	  corners: 1, // Corner roundness (0..1)
5677	  rotate: 0, // The rotation offset
5678	  direction: 1, // 1: clockwise, -1: counterclockwise
5679	  color: Editor.isDarkMode() ? '#c0c0c0' : '#000', // #rgb or #rrggbb
5680	  speed: 1.4, // Rounds per second
5681	  trail: 60, // Afterglow percentage
5682	  shadow: false, // Whether to render a shadow
5683	  hwaccel: false, // Whether to use hardware acceleration
5684	  className: 'spinner', // The CSS class to assign to the spinner
5685	  zIndex: 2e9, // The z-index (defaults to 2000000000)
5686	  top: '50%', // Top position relative to parent
5687	  left: '50%' // Left position relative to parent
5688	};
5689
5690	var spinner = new Spinner(opts);
5691
5692	var file = editorUi.getCurrentFile();
5693	var fileNode = editorUi.getXmlFileData(true, false, true);
5694	var tmp = fileNode.getElementsByTagName('diagram');
5695	var currentDiagrams = {};
5696
5697	for (var i = 0; i < tmp.length; i++)
5698	{
5699		currentDiagrams[tmp[i].getAttribute('id')] = tmp[i];
5700	}
5701
5702	var currentRow = null;
5703	var currentRev = null;
5704	var currentDoc = null;
5705	var currentXml = null;
5706
5707	var zoomInBtn = mxUtils.button('', function()
5708	{
5709		if (currentDoc != null)
5710		{
5711			graph.zoomIn();
5712		}
5713	});
5714	zoomInBtn.className = 'geSprite geSprite-zoomin';
5715	zoomInBtn.setAttribute('title', mxResources.get('zoomIn'));
5716	zoomInBtn.style.outline = 'none';
5717	zoomInBtn.style.border = 'none';
5718	zoomInBtn.style.margin = '2px';
5719	zoomInBtn.setAttribute('disabled', 'disabled');
5720	mxUtils.setOpacity(zoomInBtn, 20);
5721
5722	var zoomOutBtn = mxUtils.button('', function()
5723	{
5724		if (currentDoc != null)
5725		{
5726			graph.zoomOut();
5727		}
5728	});
5729	zoomOutBtn.className = 'geSprite geSprite-zoomout';
5730	zoomOutBtn.setAttribute('title', mxResources.get('zoomOut'));
5731	zoomOutBtn.style.outline = 'none';
5732	zoomOutBtn.style.border = 'none';
5733	zoomOutBtn.style.margin = '2px';
5734	zoomOutBtn.setAttribute('disabled', 'disabled');
5735	mxUtils.setOpacity(zoomOutBtn, 20);
5736
5737	var zoomFitBtn = mxUtils.button('', function()
5738	{
5739		if (currentDoc != null)
5740		{
5741			graph.maxFitScale = 8;
5742			graph.fit(8);
5743			graph.center();
5744		}
5745	});
5746	zoomFitBtn.className = 'geSprite geSprite-fit';
5747	zoomFitBtn.setAttribute('title', mxResources.get('fit'));
5748	zoomFitBtn.style.outline = 'none';
5749	zoomFitBtn.style.border = 'none';
5750	zoomFitBtn.style.margin = '2px';
5751	zoomFitBtn.setAttribute('disabled', 'disabled');
5752	mxUtils.setOpacity(zoomFitBtn, 20);
5753
5754	var zoomActualBtn = mxUtils.button('', function()
5755	{
5756		if (currentDoc != null)
5757		{
5758			graph.zoomActual();
5759			graph.center();
5760		}
5761	});
5762	zoomActualBtn.className = 'geSprite geSprite-actualsize';
5763	zoomActualBtn.setAttribute('title', mxResources.get('actualSize'));
5764	zoomActualBtn.style.outline = 'none';
5765	zoomActualBtn.style.border = 'none';
5766	zoomActualBtn.style.margin = '2px';
5767	zoomActualBtn.setAttribute('disabled', 'disabled');
5768	mxUtils.setOpacity(zoomActualBtn, 20);
5769
5770	// Gesture listener added below to handle pressed state
5771	var compareBtn = mxUtils.button('', function() { });
5772	compareBtn.className = 'geSprite geSprite-middle';
5773	compareBtn.setAttribute('title', mxResources.get('compare'));
5774	compareBtn.style.outline = 'none';
5775	compareBtn.style.border = 'none';
5776	compareBtn.style.margin = '2px';
5777	mxUtils.setOpacity(compareBtn, 60);
5778
5779	var cmpContainer = container.cloneNode(false);
5780	cmpContainer.style.pointerEvent = 'none';
5781	container.parentNode.appendChild(cmpContainer);
5782
5783	var cmpGraph = new Graph(cmpContainer);
5784	cmpGraph.setTooltips(false);
5785	cmpGraph.setEnabled(false);
5786	cmpGraph.setPanning(true);
5787	cmpGraph.panningHandler.ignoreCell = true;
5788	cmpGraph.panningHandler.useLeftButtonForPanning = true;
5789	cmpGraph.minFitScale = null;
5790	cmpGraph.maxFitScale = null;
5791	cmpGraph.centerZoom = true;
5792
5793	mxEvent.addGestureListeners(compareBtn, function(e)
5794	{
5795		// Gets current state of page with given ID
5796		var curr = currentDiagrams[diagrams[currentPage].getAttribute('id')];
5797		mxUtils.setOpacity(compareBtn, 20);
5798		errorNode.innerHTML = '';
5799
5800		if (curr == null)
5801		{
5802			mxUtils.write(errorNode, mxResources.get('pageNotFound'));
5803		}
5804		else
5805		{
5806			fileInfo.style.display = 'none';
5807			container.style.display = 'none';
5808			cmpContainer.style.display = '';
5809			cmpContainer.style.backgroundColor = container.style.backgroundColor;
5810
5811			var tempNode = Editor.parseDiagramNode(curr);
5812			var codec = new mxCodec(tempNode.ownerDocument);
5813			codec.decode(tempNode, cmpGraph.getModel());
5814			cmpGraph.view.scaleAndTranslate(graph.view.scale,
5815				graph.view.translate.x, graph.view.translate.y);
5816		}
5817	}, null, function()
5818	{
5819		mxUtils.setOpacity(compareBtn, 60);
5820		errorNode.innerHTML = '';
5821
5822		if (container.style.display == 'none')
5823		{
5824			fileInfo.style.display = '';
5825			container.style.display = '';
5826			cmpContainer.style.display = 'none';
5827		}
5828	});
5829
5830	var fileInfo = document.createElement('div');
5831	fileInfo.style.position = 'absolute';
5832	fileInfo.style.textAlign = 'right';
5833	fileInfo.style.color = 'gray';
5834	fileInfo.style.marginTop = '10px';
5835	fileInfo.style.backgroundColor = 'transparent';
5836	fileInfo.style.top = '440px';
5837	fileInfo.style.right = '32px';
5838	fileInfo.style.maxWidth = '380px';
5839	fileInfo.style.cursor = 'default';
5840
5841	var downloadBtn = mxUtils.button(mxResources.get('download'), function()
5842	{
5843		if (currentDoc != null)
5844		{
5845    		var data = mxUtils.getXml(currentDoc.documentElement);
5846			var filename = editorUi.getBaseFilename() + '.drawio';
5847
5848	    	if (editorUi.isLocalFileSave())
5849	    	{
5850	    		editorUi.saveLocalFile(data, filename, 'text/xml');
5851	    	}
5852	    	else
5853	    	{
5854	    		var param = (typeof(pako) === 'undefined') ? '&xml=' + encodeURIComponent(data) :
5855	    			'&data=' + encodeURIComponent(Graph.compress(data));
5856	    		new mxXmlRequest(SAVE_URL, 'filename=' + encodeURIComponent(filename) +
5857	    			'&format=xml' + param).simulate(document, '_blank');
5858	    	}
5859		}
5860	});
5861	downloadBtn.className = 'geBtn';
5862	downloadBtn.setAttribute('disabled', 'disabled');
5863
5864	var restoreBtn = mxUtils.button(mxResources.get('restore'), function(e)
5865	{
5866		if (currentDoc != null && currentXml != null)
5867		{
5868			if (mxEvent.isShiftDown(e))
5869			{
5870				if (currentDoc != null)
5871				{
5872					var pages = editorUi.getPagesForNode(currentDoc.documentElement);
5873					var patch = editorUi.diffPages(editorUi.pages, pages);
5874
5875					var dlg = new TextareaDialog(editorUi, mxResources.get('compare'),
5876						JSON.stringify(patch, null, 2), function(newValue)
5877					{
5878						if (newValue.length > 0)
5879						{
5880							try
5881							{
5882								// TODO: Make add/remove pages undoable
5883								editorUi.confirm(mxResources.get('areYouSure'), function()
5884								{
5885									file.patch([JSON.parse(newValue)], null, true);
5886
5887									// Hides compare dialog
5888									editorUi.hideDialog();
5889
5890									// Hides revision history dialog
5891									editorUi.hideDialog();
5892								});
5893							}
5894							catch (e)
5895							{
5896								editorUi.handleError(e);
5897							}
5898						}
5899					}, null, null, null, null, null, true, null, mxResources.get('merge'));
5900
5901					dlg.textarea.style.width = '600px';
5902					dlg.textarea.style.height = '380px';
5903					editorUi.showDialog(dlg.container, 620, 460, true, true);
5904					dlg.init();
5905				}
5906			}
5907			else
5908			{
5909				editorUi.confirm(mxResources.get('areYouSure'), function()
5910				{
5911					if (restoreFn != null)
5912					{
5913						restoreFn(currentXml);
5914					}
5915					else
5916					{
5917						if (editorUi.spinner.spin(document.body, mxResources.get('restoring')))
5918						{
5919							file.save(true, function(resp)
5920							{
5921								editorUi.spinner.stop();
5922								editorUi.replaceFileData(currentXml);
5923								editorUi.hideDialog();
5924							}, function(resp)
5925							{
5926								editorUi.spinner.stop();
5927								editorUi.editor.setStatus('');
5928								editorUi.handleError(resp, (resp != null) ? mxResources.get('errorSavingFile') : null);
5929							});
5930						}
5931					}
5932				});
5933			}
5934		}
5935	});
5936	restoreBtn.className = 'geBtn';
5937	restoreBtn.setAttribute('disabled', 'disabled');
5938	restoreBtn.setAttribute('title', 'Shift+Click for Diff');
5939
5940	var pageSelect = document.createElement('select');
5941	pageSelect.setAttribute('disabled', 'disabled');
5942	pageSelect.style.maxWidth = '80px';
5943	pageSelect.style.position = 'relative';
5944	pageSelect.style.top = '-2px';
5945	pageSelect.style.verticalAlign = 'bottom';
5946	pageSelect.style.marginRight = '6px';
5947	pageSelect.style.display = 'none';
5948
5949	var pageSelectFunction = null;
5950
5951	mxEvent.addListener(pageSelect, 'change', function(evt)
5952	{
5953		if (pageSelectFunction != null)
5954		{
5955			pageSelectFunction(evt);
5956			mxEvent.consume(evt);
5957		}
5958	});
5959
5960	var newBtn = mxUtils.button(mxResources.get('edit'), function()
5961	{
5962		if (currentDoc != null)
5963		{
5964			window.openFile = new OpenFile(function()
5965			{
5966				window.openFile = null;
5967			});
5968
5969			window.openFile.setData(mxUtils.getXml(currentDoc.documentElement));
5970			editorUi.openLink(editorUi.getUrl(), null, true);
5971		}
5972	});
5973	newBtn.className = 'geBtn';
5974	newBtn.setAttribute('disabled', 'disabled');
5975
5976	if (restoreFn != null)
5977	{
5978		newBtn.style.display = 'none';
5979	}
5980
5981	var showBtn = mxUtils.button(mxResources.get('show'), function()
5982	{
5983		if (currentRev != null)
5984		{
5985			editorUi.openLink(currentRev.getUrl(pageSelect.selectedIndex));
5986		}
5987	});
5988	showBtn.className = 'geBtn gePrimaryBtn';
5989	showBtn.setAttribute('disabled', 'disabled');
5990
5991	if (restoreFn != null)
5992	{
5993		showBtn.style.display = 'none';
5994		restoreBtn.className = 'geBtn gePrimaryBtn';
5995	}
5996
5997	var buttons = document.createElement('div');
5998	buttons.style.position = 'absolute';
5999	buttons.style.top = '482px';
6000	buttons.style.width = '640px';
6001	buttons.style.textAlign = 'right';
6002
6003	var tb = document.createElement('div');
6004	tb.className = 'geToolbarContainer';
6005	tb.style.backgroundColor = 'transparent';
6006	tb.style.padding = '2px';
6007	tb.style.border = 'none';
6008	tb.style.left = '199px';
6009	tb.style.top = '442px';
6010
6011	var currentElt = null;
6012
6013	if (revs != null && revs.length > 0)
6014	{
6015		container.style.cursor = 'move';
6016
6017		var table = document.createElement('table');
6018		table.style.border = '1px solid lightGray';
6019		table.style.borderCollapse = 'collapse';
6020		table.style.borderSpacing = '0px';
6021		table.style.width = '100%';
6022		var tbody = document.createElement('tbody');
6023		var today = new Date().toDateString();
6024
6025		if (editorUi.currentPage != null && editorUi.pages != null)
6026		{
6027			currentPage = mxUtils.indexOf(editorUi.pages, editorUi.currentPage);
6028		}
6029
6030		for (var i = revs.length - 1; i >= 0; i--)
6031		{
6032			var elt = (function(item)
6033			{
6034				var ts = new Date(item.modifiedDate);
6035				var row = null;
6036				var pd = '6px';
6037
6038				// Workaround for negative timestamps in Dropbox
6039				if (ts.getTime() >= 0)
6040				{
6041					row = document.createElement('tr');
6042					row.style.borderBottom = '1px solid lightGray';
6043					row.style.fontSize = '12px';
6044					row.style.cursor = 'pointer';
6045
6046					var date = document.createElement('td');
6047					date.style.padding = pd;
6048					date.style.whiteSpace = 'nowrap';
6049
6050					if (item == revs[revs.length - 1])
6051					{
6052						mxUtils.write(date, mxResources.get('current'));
6053					}
6054					else
6055					{
6056						if (ts.toDateString() === today)
6057						{
6058							mxUtils.write(date, ts.toLocaleTimeString());
6059						}
6060						else
6061						{
6062							mxUtils.write(date, ts.toLocaleDateString() + ' ' +
6063								ts.toLocaleTimeString());
6064						}
6065					}
6066
6067					row.appendChild(date);
6068
6069					row.setAttribute('title', ts.toLocaleDateString() + ' ' +
6070						ts.toLocaleTimeString() +
6071						((item.fileSize != null)? ' ' + editorUi.formatFileSize(parseInt(item.fileSize)) : '') +
6072						((item.lastModifyingUserName != null) ? ' ' + item.lastModifyingUserName : ''));
6073
6074					function updateGraph(xml)
6075					{
6076						spinner.stop();
6077						errorNode.innerHTML = '';
6078						var doc = mxUtils.parseXml(xml);
6079						var node = editorUi.editor.extractGraphModel(doc.documentElement, true);
6080
6081						if (node != null)
6082						{
6083							pageSelect.style.display = 'none';
6084							pageSelect.innerHTML = '';
6085							currentDoc = doc;
6086							currentXml = xml;
6087							parseSelectFunction = null;
6088							diagrams = null;
6089							realPage = 0;
6090
6091							function parseGraphModel(dataNode)
6092							{
6093								var bg = dataNode.getAttribute('background');
6094
6095								if (bg == null || bg == '' || bg == mxConstants.NONE)
6096								{
6097									bg = graph.defaultPageBackgroundColor;
6098								}
6099
6100								container.style.backgroundColor = bg;
6101
6102								var codec = new mxCodec(dataNode.ownerDocument);
6103								codec.decode(dataNode, graph.getModel());
6104								graph.maxFitScale = 1;
6105								graph.fit(8);
6106								graph.center();
6107
6108								return dataNode;
6109							}
6110
6111							function parseDiagram(diagramNode)
6112							{
6113								if (diagramNode != null)
6114								{
6115									diagramNode = parseGraphModel(Editor.parseDiagramNode(diagramNode));
6116								}
6117
6118								return diagramNode;
6119							}
6120
6121							if (node.nodeName == 'mxfile')
6122							{
6123								// Workaround for "invalid calling object" error in IE
6124								var tmp = node.getElementsByTagName('diagram');
6125								diagrams = [];
6126
6127								for (var i = 0; i < tmp.length; i++)
6128								{
6129									diagrams.push(tmp[i]);
6130								}
6131
6132								realPage = Math.min(currentPage, diagrams.length - 1);
6133
6134								if (diagrams.length > 0)
6135								{
6136									parseDiagram(diagrams[realPage]);
6137								}
6138
6139								if (diagrams.length > 1)
6140								{
6141									pageSelect.removeAttribute('disabled');
6142									pageSelect.style.display = '';
6143
6144									for (var i = 0; i < diagrams.length; i++)
6145									{
6146										var pageOption = document.createElement('option');
6147										mxUtils.write(pageOption, diagrams[i].getAttribute('name') ||
6148											mxResources.get('pageWithNumber', [i + 1]));
6149										pageOption.setAttribute('value', i);
6150
6151										if (i == realPage)
6152										{
6153											pageOption.setAttribute('selected', 'selected');
6154										}
6155
6156										pageSelect.appendChild(pageOption);
6157									}
6158								}
6159
6160								pageSelectFunction = function()
6161								{
6162									try
6163									{
6164										var temp = parseInt(pageSelect.value);
6165										currentPage = temp;
6166										realPage = currentPage;
6167										parseDiagram(diagrams[temp]);
6168									}
6169									catch (e)
6170									{
6171										pageSelect.value = currentPage;
6172										editorUi.handleError(e);
6173									}
6174								};
6175							}
6176							else
6177							{
6178								parseGraphModel(node);
6179							}
6180
6181							var shortUser = item.lastModifyingUserName;
6182
6183							if (shortUser != null && shortUser.length > 20)
6184							{
6185								shortUser = shortUser.substring(0, 20) + '...';
6186							}
6187
6188							fileInfo.innerHTML = '';
6189							mxUtils.write(fileInfo, ((shortUser != null) ?
6190								(shortUser + ' ') : '') + ts.toLocaleDateString() +
6191								' ' + ts.toLocaleTimeString());
6192
6193							fileInfo.setAttribute('title', row.getAttribute('title'));
6194							zoomInBtn.removeAttribute('disabled');
6195							zoomOutBtn.removeAttribute('disabled');
6196							zoomFitBtn.removeAttribute('disabled');
6197							zoomActualBtn.removeAttribute('disabled');
6198							compareBtn.removeAttribute('disabled');
6199
6200							if (file == null || !file.isRestricted())
6201							{
6202								if (editorUi.editor.graph.isEnabled())
6203								{
6204									restoreBtn.removeAttribute('disabled');
6205								}
6206
6207								downloadBtn.removeAttribute('disabled');
6208								showBtn.removeAttribute('disabled');
6209								newBtn.removeAttribute('disabled');
6210							}
6211
6212							mxUtils.setOpacity(zoomInBtn, 60);
6213							mxUtils.setOpacity(zoomOutBtn, 60);
6214							mxUtils.setOpacity(zoomFitBtn, 60);
6215							mxUtils.setOpacity(zoomActualBtn, 60);
6216							mxUtils.setOpacity(compareBtn, 60);
6217						}
6218						else
6219						{
6220							pageSelect.style.display = 'none';
6221							pageSelect.innerHTML = '';
6222							fileInfo.innerHTML = '';
6223							mxUtils.write(fileInfo, mxResources.get('errorLoadingFile'));
6224							mxUtils.write(errorNode, mxResources.get('errorLoadingFile'));
6225						}
6226					};
6227
6228					mxEvent.addListener(row, 'click', function(evt)
6229					{
6230						if (currentRev != item)
6231						{
6232							spinner.stop();
6233
6234							if (currentRow != null)
6235							{
6236								currentRow.style.backgroundColor = '';
6237							}
6238
6239							currentRev = item;
6240							currentRow = row;
6241							currentRow.style.backgroundColor = Editor.isDarkMode() ? '#000000' : '#ebf2f9';
6242							currentDoc = null;
6243							currentXml = null;
6244
6245							fileInfo.removeAttribute('title');
6246							fileInfo.innerHTML = mxUtils.htmlEntities(mxResources.get('loading') + '...');
6247							container.style.backgroundColor = graph.defaultPageBackgroundColor;
6248							errorNode.innerHTML = '';
6249							graph.getModel().clear();
6250
6251							restoreBtn.setAttribute('disabled', 'disabled');
6252							downloadBtn.setAttribute('disabled', 'disabled');
6253							zoomInBtn.setAttribute('disabled', 'disabled');
6254							zoomOutBtn.setAttribute('disabled', 'disabled');
6255							zoomActualBtn.setAttribute('disabled', 'disabled');
6256							zoomFitBtn.setAttribute('disabled', 'disabled');
6257							compareBtn.setAttribute('disabled', 'disabled');
6258
6259							newBtn.setAttribute('disabled', 'disabled');
6260							showBtn.setAttribute('disabled', 'disabled');
6261							pageSelect.setAttribute('disabled', 'disabled');
6262
6263							mxUtils.setOpacity(zoomInBtn, 20);
6264							mxUtils.setOpacity(zoomOutBtn, 20);
6265							mxUtils.setOpacity(zoomFitBtn, 20);
6266							mxUtils.setOpacity(zoomActualBtn, 20);
6267							mxUtils.setOpacity(compareBtn, 20);
6268
6269							spinner.spin(container);
6270
6271							item.getXml(function(xml)
6272				   			{
6273								if (currentRev == item)
6274								{
6275									try
6276									{
6277										updateGraph(xml);
6278									}
6279									catch (e)
6280									{
6281										fileInfo.innerHTML = mxUtils.htmlEntities(
6282											mxResources.get('error') + ': ' + e.message);
6283									}
6284								}
6285				   			}, function(err)
6286				   			{
6287				   				spinner.stop();
6288								pageSelect.style.display = 'none';
6289								pageSelect.innerHTML = '';
6290				   				fileInfo.innerHTML = '';
6291								mxUtils.write(fileInfo, mxResources.get('errorLoadingFile'));
6292								mxUtils.write(errorNode, mxResources.get('errorLoadingFile'));
6293				   			});
6294
6295							mxEvent.consume(evt);
6296						}
6297					});
6298
6299					mxEvent.addListener(row, 'dblclick', function(evt)
6300					{
6301						showBtn.click();
6302
6303						if (window.getSelection)
6304						{
6305							window.getSelection().removeAllRanges();
6306						}
6307					    else if (document.selection)
6308					    {
6309					    	document.selection.empty();
6310					    }
6311
6312						mxEvent.consume(evt);
6313					}, false);
6314
6315					tbody.appendChild(row);
6316				}
6317
6318				return row;
6319			})(revs[i]);
6320
6321			// Selects and loads first element in list (ie current version) after
6322			// graph container was initialized since there is no loading delay
6323			if (elt != null && i == revs.length - 1)
6324			{
6325				currentElt = elt;
6326			}
6327		}
6328
6329		table.appendChild(tbody);
6330		list.appendChild(table);
6331	}
6332	else if (file == null || (editorUi.drive == null && file.constructor == window.DriveFile) ||
6333		(editorUi.dropbox == null && file.constructor == window.DropboxFile))
6334	{
6335		container.style.display = 'none';
6336		tb.style.display = 'none';
6337		mxUtils.write(list, mxResources.get('notAvailable'));
6338	}
6339	else
6340	{
6341		container.style.display = 'none';
6342		tb.style.display = 'none';
6343		mxUtils.write(list, mxResources.get('noRevisions'));
6344	}
6345
6346	this.init = function()
6347	{
6348		if (currentElt != null)
6349		{
6350			currentElt.click();
6351		}
6352	};
6353
6354	var closeBtn = mxUtils.button(mxResources.get('close'), function()
6355	{
6356		editorUi.hideDialog();
6357	});
6358	closeBtn.className = 'geBtn';
6359
6360	tb.appendChild(pageSelect);
6361	tb.appendChild(zoomInBtn);
6362	tb.appendChild(zoomOutBtn);
6363	tb.appendChild(zoomActualBtn);
6364	tb.appendChild(zoomFitBtn);
6365	tb.appendChild(compareBtn);
6366
6367	if (editorUi.editor.cancelFirst)
6368	{
6369		buttons.appendChild(closeBtn);
6370		buttons.appendChild(downloadBtn);
6371		buttons.appendChild(newBtn);
6372		buttons.appendChild(restoreBtn);
6373		buttons.appendChild(showBtn);
6374	}
6375	else
6376	{
6377		buttons.appendChild(downloadBtn);
6378		buttons.appendChild(newBtn);
6379		buttons.appendChild(restoreBtn);
6380		buttons.appendChild(showBtn);
6381		buttons.appendChild(closeBtn);
6382	}
6383
6384	div.appendChild(buttons);
6385	div.appendChild(tb);
6386	div.appendChild(fileInfo);
6387
6388	this.container = div;
6389};
6390
6391/**
6392 * Constructs a new revision dialog
6393 */
6394var DraftDialog = function(editorUi, title, xml, editFn, discardFn, editLabel, discardLabel, ignoreFn, drafts)
6395{
6396	var div = document.createElement('div');
6397
6398	var titleDiv = document.createElement('div');
6399	titleDiv.style.marginTop = '0px';
6400	titleDiv.style.whiteSpace = 'nowrap';
6401	titleDiv.style.overflow = 'auto';
6402	titleDiv.style.lineHeight = 'normal';
6403	mxUtils.write(titleDiv, title);
6404	div.appendChild(titleDiv);
6405
6406	var select = document.createElement('select');
6407
6408	var draftSelected = mxUtils.bind(this, function()
6409	{
6410		doc = mxUtils.parseXml(drafts[select.value].data);
6411		node = editorUi.editor.extractGraphModel(doc.documentElement, true);
6412		currentPage = 0;
6413
6414		this.init();
6415	});
6416
6417	if (drafts != null)
6418	{
6419		select.style.marginLeft = '4px';
6420
6421		for (var i = 0; i < drafts.length; i++)
6422		{
6423			var opt = document.createElement('option');
6424			opt.setAttribute('value', i);
6425			var ts0 = new Date(drafts[i].created);
6426			var ts1 = new Date(drafts[i].modified);
6427
6428			mxUtils.write(opt, ts0.toLocaleDateString() + ' ' +
6429				ts0.toLocaleTimeString() + ' - ' +
6430				((ts0.toDateString() != ts1.toDateString() || true) ?
6431				ts1.toLocaleDateString() : ' ') +
6432				' ' + ts1.toLocaleTimeString());
6433
6434			select.appendChild(opt);
6435		}
6436
6437		titleDiv.appendChild(select);
6438
6439		mxEvent.addListener(select, 'change', draftSelected);
6440	}
6441
6442	if (xml == null)
6443	{
6444		xml = drafts[0].data;
6445	}
6446
6447	var container = document.createElement('div');
6448	container.style.position = 'absolute';
6449	container.style.border = '1px solid lightGray';
6450	container.style.marginTop = '10px';
6451	container.style.left = '40px';
6452	container.style.right = '40px';
6453	container.style.top = '46px';
6454	container.style.bottom = '74px';
6455	container.style.overflow = 'hidden';
6456
6457	mxEvent.disableContextMenu(container);
6458	div.appendChild(container);
6459
6460	var graph = new Graph(container);
6461	graph.setEnabled(false);
6462	graph.setPanning(true);
6463	graph.panningHandler.ignoreCell = true;
6464	graph.panningHandler.useLeftButtonForPanning = true;
6465	graph.minFitScale = null;
6466	graph.maxFitScale = null;
6467	graph.centerZoom = true;
6468
6469	// Handles placeholders for pages
6470	var doc = mxUtils.parseXml(xml);
6471	var node = editorUi.editor.extractGraphModel(doc.documentElement, true);
6472	var currentPage = 0;
6473	var diagrams = null;
6474	var graphGetGlobalVariable = graph.getGlobalVariable;
6475
6476	graph.getGlobalVariable = function(name)
6477	{
6478		if (name == 'page' && diagrams != null && diagrams[currentPage] != null)
6479		{
6480			return diagrams[currentPage].getAttribute('name');
6481		}
6482		else if (name == 'pagenumber')
6483		{
6484			return currentPage + 1;
6485		}
6486		else if (name == 'pagecount')
6487		{
6488			return (diagrams != null) ? diagrams.length : 1;
6489		}
6490
6491		return graphGetGlobalVariable.apply(this, arguments);
6492	};
6493
6494	// Disables hyperlinks
6495	graph.getLinkForCell = function()
6496	{
6497		return null;
6498	};
6499
6500	// TODO: Enable per-page math
6501//	if (Editor.MathJaxRender)
6502//	{
6503//		graph.addListener(mxEvent.SIZE, mxUtils.bind(this, function(sender, evt)
6504//		{
6505//			// LATER: Math support is used if current graph has math enabled
6506//			// should use switch from history instead but requires setting the
6507//			// global mxClient.NO_FO switch
6508//			if (editorUi.editor.graph.mathEnabled)
6509//			{
6510//				Editor.MathJaxRender(graph.container);
6511//			}
6512//		}));
6513//	}
6514
6515	var zoomInBtn = mxUtils.button('', function()
6516	{
6517		graph.zoomIn();
6518	});
6519	zoomInBtn.className = 'geSprite geSprite-zoomin';
6520	zoomInBtn.setAttribute('title', mxResources.get('zoomIn'));
6521	zoomInBtn.style.outline = 'none';
6522	zoomInBtn.style.border = 'none';
6523	zoomInBtn.style.margin = '2px';
6524	mxUtils.setOpacity(zoomInBtn, 60);
6525
6526	var zoomOutBtn = mxUtils.button('', function()
6527	{
6528		graph.zoomOut();
6529	});
6530	zoomOutBtn.className = 'geSprite geSprite-zoomout';
6531	zoomOutBtn.setAttribute('title', mxResources.get('zoomOut'));
6532	zoomOutBtn.style.outline = 'none';
6533	zoomOutBtn.style.border = 'none';
6534	zoomOutBtn.style.margin = '2px';
6535	mxUtils.setOpacity(zoomOutBtn, 60);
6536
6537	var zoomFitBtn = mxUtils.button('', function()
6538	{
6539		graph.maxFitScale = 8;
6540		graph.fit(8);
6541		graph.center();
6542	});
6543	zoomFitBtn.className = 'geSprite geSprite-fit';
6544	zoomFitBtn.setAttribute('title', mxResources.get('fit'));
6545	zoomFitBtn.style.outline = 'none';
6546	zoomFitBtn.style.border = 'none';
6547	zoomFitBtn.style.margin = '2px';
6548	mxUtils.setOpacity(zoomFitBtn, 60);
6549
6550	var zoomActualBtn = mxUtils.button('', function()
6551	{
6552		graph.zoomActual();
6553		graph.center();
6554	});
6555	zoomActualBtn.className = 'geSprite geSprite-actualsize';
6556	zoomActualBtn.setAttribute('title', mxResources.get('actualSize'));
6557	zoomActualBtn.style.outline = 'none';
6558	zoomActualBtn.style.border = 'none';
6559	zoomActualBtn.style.margin = '2px';
6560	mxUtils.setOpacity(zoomActualBtn, 60);
6561
6562	var restoreBtn = mxUtils.button(discardLabel || mxResources.get('discard'), function()
6563	{
6564		discardFn.apply(this, [select.value, mxUtils.bind(this, function()
6565		{
6566			if (select.parentNode != null)
6567			{
6568				select.options[select.selectedIndex].parentNode.removeChild(select.options[select.selectedIndex]);
6569
6570				if (select.options.length > 0)
6571				{
6572					select.value = select.options[0].value;
6573					draftSelected();
6574				}
6575				else
6576				{
6577					editorUi.hideDialog(true);
6578				}
6579			}
6580		})]);
6581	});
6582	restoreBtn.className = 'geBtn';
6583
6584	var pageSelect = document.createElement('select');
6585	pageSelect.style.maxWidth = '80px';
6586	pageSelect.style.position = 'relative';
6587	pageSelect.style.top = '-2px';
6588	pageSelect.style.verticalAlign = 'bottom';
6589	pageSelect.style.marginRight = '6px';
6590	pageSelect.style.display = 'none';
6591
6592	var showBtn = mxUtils.button(editLabel || mxResources.get('edit'), function()
6593	{
6594		editFn.apply(this, [select.value])
6595	});
6596	showBtn.className = 'geBtn gePrimaryBtn';
6597
6598	var buttons = document.createElement('div');
6599	buttons.style.position = 'absolute';
6600	buttons.style.bottom = '30px';
6601	buttons.style.right = '40px';
6602	buttons.style.textAlign = 'right';
6603
6604	var tb = document.createElement('div');
6605	tb.className = 'geToolbarContainer';
6606	tb.style.cssText = 'box-shadow:none !important;background-color:transparent;' +
6607		'padding:2px;border-style:none !important;bottom:30px;';
6608
6609	this.init = function()
6610	{
6611		function parseGraphModel(dataNode)
6612		{
6613			if (dataNode != null)
6614			{
6615				var bg = dataNode.getAttribute('background');
6616
6617				if (bg == null || bg == '' || bg == mxConstants.NONE)
6618				{
6619					bg = Editor.isDarkMode() ? 'transparent' : '#ffffff';
6620				}
6621
6622				container.style.backgroundColor = bg;
6623
6624				var codec = new mxCodec(dataNode.ownerDocument);
6625				codec.decode(dataNode, graph.getModel());
6626				graph.maxFitScale = 1;
6627				graph.fit(8);
6628				graph.center();
6629			}
6630
6631			return dataNode;
6632		};
6633
6634		function parseDiagram(diagramNode)
6635		{
6636			if (diagramNode != null)
6637			{
6638				diagramNode = parseGraphModel(Editor.parseDiagramNode(diagramNode));
6639			}
6640
6641			return diagramNode;
6642		};
6643
6644		mxEvent.addListener(pageSelect, 'change', function(evt)
6645		{
6646			currentPage = parseInt(pageSelect.value);
6647			parseDiagram(diagrams[currentPage]);
6648			mxEvent.consume(evt);
6649		});
6650
6651		if (node.nodeName == 'mxfile')
6652		{
6653			// Workaround for "invalid calling object" error in IE
6654			var tmp = node.getElementsByTagName('diagram');
6655			diagrams = [];
6656
6657			for (var i = 0; i < tmp.length; i++)
6658			{
6659				diagrams.push(tmp[i]);
6660			}
6661
6662			if (diagrams.length > 0)
6663			{
6664				parseDiagram(diagrams[currentPage]);
6665			}
6666
6667			pageSelect.innerHTML = '';
6668
6669			if (diagrams.length > 1)
6670			{
6671				pageSelect.style.display = '';
6672
6673				for (var i = 0; i < diagrams.length; i++)
6674				{
6675					var pageOption = document.createElement('option');
6676					mxUtils.write(pageOption, diagrams[i].getAttribute('name') ||
6677						mxResources.get('pageWithNumber', [i + 1]));
6678					pageOption.setAttribute('value', i);
6679
6680					if (i == currentPage)
6681					{
6682						pageOption.setAttribute('selected', 'selected');
6683					}
6684
6685					pageSelect.appendChild(pageOption);
6686				}
6687			}
6688			else
6689			{
6690				pageSelect.style.display = 'none';
6691			}
6692		}
6693		else
6694		{
6695			parseGraphModel(node);
6696		}
6697	};
6698
6699	tb.appendChild(pageSelect);
6700	tb.appendChild(zoomInBtn);
6701	tb.appendChild(zoomOutBtn);
6702	tb.appendChild(zoomActualBtn);
6703	tb.appendChild(zoomFitBtn);
6704
6705	var cancelBtn = mxUtils.button(mxResources.get('cancel'), function()
6706	{
6707		editorUi.hideDialog(true);
6708	});
6709
6710	cancelBtn.className = 'geBtn';
6711
6712	var ignoreBtn = (ignoreFn != null) ? mxUtils.button(mxResources.get('ignore'), ignoreFn) : null;
6713
6714	if (ignoreBtn != null)
6715	{
6716		ignoreBtn.className = 'geBtn';
6717	}
6718
6719	if (editorUi.editor.cancelFirst)
6720	{
6721		buttons.appendChild(cancelBtn);
6722
6723		if (ignoreBtn != null)
6724		{
6725			buttons.appendChild(ignoreBtn);
6726		}
6727
6728		buttons.appendChild(restoreBtn);
6729		buttons.appendChild(showBtn);
6730	}
6731	else
6732	{
6733		buttons.appendChild(showBtn);
6734		buttons.appendChild(restoreBtn);
6735
6736		if (ignoreBtn != null)
6737		{
6738			buttons.appendChild(ignoreBtn);
6739		}
6740
6741		buttons.appendChild(cancelBtn);
6742	}
6743
6744	div.appendChild(buttons);
6745	div.appendChild(tb);
6746
6747	this.container = div;
6748};
6749
6750/**
6751 *
6752 */
6753var FindWindow = function(ui, x, y, w, h, withReplace)
6754{
6755	var action = ui.actions.get('findReplace');
6756
6757	var graph = ui.editor.graph;
6758	var lastSearch = null;
6759	var lastFound = null;
6760	var lastSearchSuccessful = false;
6761	var allChecked = false;
6762	var lblMatch = null;
6763	var lblMatchPos = 0;
6764	var marker = 1;
6765
6766	var div = document.createElement('div');
6767	div.style.userSelect = 'none';
6768	div.style.overflow = 'hidden';
6769	div.style.padding = '10px';
6770	div.style.height = '100%';
6771
6772	var txtWidth = withReplace? '260px' : '200px';
6773	var searchInput = document.createElement('input');
6774	searchInput.setAttribute('placeholder', mxResources.get('find'));
6775	searchInput.setAttribute('type', 'text');
6776	searchInput.style.marginTop = '4px';
6777	searchInput.style.marginBottom = '6px';
6778	searchInput.style.width = txtWidth;
6779	searchInput.style.fontSize = '12px';
6780	searchInput.style.borderRadius = '4px';
6781	searchInput.style.padding = '6px';
6782	div.appendChild(searchInput);
6783	mxUtils.br(div);
6784
6785	var replaceInput;
6786
6787	if (withReplace)
6788	{
6789		replaceInput = document.createElement('input');
6790		replaceInput.setAttribute('placeholder', mxResources.get('replaceWith'));
6791		replaceInput.setAttribute('type', 'text');
6792		replaceInput.style.marginTop = '4px';
6793		replaceInput.style.marginBottom = '6px';
6794		replaceInput.style.width = txtWidth;
6795		replaceInput.style.fontSize = '12px';
6796		replaceInput.style.borderRadius = '4px';
6797		replaceInput.style.padding = '6px';
6798		div.appendChild(replaceInput);
6799		mxUtils.br(div);
6800
6801		mxEvent.addListener(replaceInput, 'input', updateReplBtns);
6802	}
6803
6804	var regexInput = document.createElement('input');
6805	regexInput.setAttribute('id', 'geFindWinRegExChck');
6806	regexInput.setAttribute('type', 'checkbox');
6807	regexInput.style.marginRight = '4px';
6808	div.appendChild(regexInput);
6809
6810	var regexLabel = document.createElement('label');
6811	regexLabel.setAttribute('for', 'geFindWinRegExChck');
6812	div.appendChild(regexLabel);
6813	mxUtils.write(regexLabel, mxResources.get('regularExpression'));
6814	div.appendChild(regexLabel);
6815
6816    var help = ui.menus.createHelpLink('https://www.diagrams.net/doc/faq/find-shapes');
6817    help.style.position = 'relative';
6818    help.style.marginLeft = '6px';
6819    help.style.top = '-1px';
6820    div.appendChild(help);
6821
6822	mxUtils.br(div);
6823
6824    var allPagesInput = document.createElement('input');
6825    allPagesInput.setAttribute('id', 'geFindWinAllPagesChck');
6826    allPagesInput.setAttribute('type', 'checkbox');
6827    allPagesInput.style.marginRight = '4px';
6828	div.appendChild(allPagesInput);
6829
6830	var allPagesLabel = document.createElement('label');
6831	allPagesLabel.setAttribute('for', 'geFindWinAllPagesChck');
6832	div.appendChild(allPagesLabel);
6833	mxUtils.write(allPagesLabel, mxResources.get('allPages'));
6834	div.appendChild(allPagesLabel);
6835
6836	var tmp = document.createElement('div');
6837
6838	function testMeta(re, cell, search, checkIndex)
6839	{
6840		if (typeof cell.value === 'object' && cell.value.attributes != null)
6841		{
6842			var attrs = cell.value.attributes;
6843
6844			for (var i = 0; i < attrs.length; i++)
6845			{
6846				if (attrs[i].nodeName != 'label')
6847				{
6848					var value = mxUtils.trim(attrs[i].nodeValue.replace(/[\x00-\x1F\x7F-\x9F]|\s+/g, ' ')).toLowerCase();
6849
6850					if ((re == null && ((checkIndex && value.indexOf(search) >= 0) ||
6851						(!checkIndex && value.substring(0, search.length) === search))) ||
6852						(re != null && re.test(value)))
6853					{
6854						return true;
6855					}
6856				}
6857			}
6858		}
6859
6860		return false;
6861	};
6862
6863	function updateReplBtns()
6864	{
6865		if (lastSearchSuccessful && replaceInput.value)
6866		{
6867			replaceFindBtn.removeAttribute('disabled');
6868			replaceBtn.removeAttribute('disabled');
6869		}
6870		else
6871		{
6872			replaceFindBtn.setAttribute('disabled', 'disabled');
6873			replaceBtn.setAttribute('disabled', 'disabled');
6874		}
6875
6876		if (replaceInput.value && searchInput.value)
6877		{
6878			replaceAllBtn.removeAttribute('disabled');
6879		}
6880		else
6881		{
6882			replaceAllBtn.setAttribute('disabled', 'disabled');
6883		}
6884	}
6885
6886	function search(internalCall, trySameCell, stayOnPage)
6887	{
6888		replAllNotif.innerHTML = '';
6889		var cells = graph.model.getDescendants(graph.model.getRoot());
6890		var searchStr = searchInput.value.toLowerCase();
6891		var re = (regexInput.checked) ? new RegExp(searchStr) : null;
6892		var firstMatch = null;
6893		lblMatch = null;
6894
6895		if (lastSearch != searchStr)
6896		{
6897			lastSearch = searchStr;
6898			lastFound = null;
6899			allChecked = false;
6900		}
6901
6902		var active = lastFound == null;
6903
6904		if (searchStr.length > 0)
6905		{
6906			if (allChecked)
6907			{
6908				allChecked = false;
6909
6910				//Find current page index
6911				var currentPageIndex;
6912
6913				for (var i = 0; i < ui.pages.length; i++)
6914				{
6915					if (ui.currentPage == ui.pages[i])
6916					{
6917						currentPageIndex = i;
6918						break;
6919					}
6920				}
6921
6922				var nextPageIndex = (currentPageIndex + 1) % ui.pages.length, nextPage;
6923				lastFound = null;
6924
6925				do
6926				{
6927					allChecked = false;
6928					nextPage = ui.pages[nextPageIndex];
6929					graph = ui.createTemporaryGraph(graph.getStylesheet());
6930					ui.updatePageRoot(nextPage);
6931					graph.model.setRoot(nextPage.root);
6932					nextPageIndex = (nextPageIndex + 1) % ui.pages.length;
6933				}
6934				while(!search(true, trySameCell, stayOnPage) && nextPageIndex != currentPageIndex);
6935
6936				if (lastFound)
6937				{
6938					lastFound = null;
6939
6940					if (!stayOnPage)
6941					{
6942						ui.selectPage(nextPage);
6943					}
6944					else
6945					{
6946						ui.editor.graph.model.execute(new SelectPage(ui, nextPage));
6947					}
6948				}
6949
6950				allChecked = false;
6951				graph = ui.editor.graph;
6952
6953				return search(true, trySameCell, stayOnPage);
6954			}
6955
6956			var i;
6957
6958			for (i = 0; i < cells.length; i++)
6959			{
6960				var state = graph.view.getState(cells[i]);
6961
6962				//Try the same cell with replace to find other occurances
6963				if (trySameCell && re != null)
6964				{
6965					active = active || state == lastFound;
6966				}
6967
6968				if (state != null && state.cell.value != null && (active || firstMatch == null) &&
6969					(graph.model.isVertex(state.cell) || graph.model.isEdge(state.cell)))
6970				{
6971					if (state.style != null && state.style['html'] == '1')
6972					{
6973						tmp.innerHTML = graph.sanitizeHtml(graph.getLabel(state.cell));
6974						label = mxUtils.extractTextWithWhitespace([tmp]);
6975					}
6976					else
6977					{
6978						label = graph.getLabel(state.cell);
6979					}
6980
6981					label = mxUtils.trim(label.replace(/[\x00-\x1F\x7F-\x9F]|\s+/g, ' ')).toLowerCase();
6982					var lblPosShift = 0;
6983
6984					if (trySameCell && withReplace && re != null && state == lastFound)
6985					{
6986						label = label.substr(lblMatchPos);
6987						lblPosShift = lblMatchPos;
6988					}
6989
6990					var checkMeta = replaceInput.value == '';
6991					var checkIndex = checkMeta;
6992
6993					if ((re == null && ((checkIndex && label.indexOf(searchStr) >= 0) ||
6994						(!checkIndex && label.substring(0, searchStr.length) === searchStr) ||
6995						(checkMeta && testMeta(re, state.cell, searchStr, checkIndex)))) ||
6996						(re != null && (re.test(label) || (checkMeta &&
6997						testMeta(re, state.cell, searchStr, checkIndex)))))
6998					{
6999						if (withReplace)
7000						{
7001							if (re != null)
7002							{
7003								var result = label.match(re);
7004								lblMatch = result[0].toLowerCase();
7005								lblMatchPos = lblPosShift + result.index + lblMatch.length;
7006							}
7007							else
7008							{
7009								lblMatch = searchStr;
7010								lblMatchPos = lblMatch.length;
7011							}
7012						}
7013
7014						if (active)
7015						{
7016							firstMatch = state;
7017
7018							break;
7019						}
7020						else if (firstMatch == null)
7021						{
7022							firstMatch = state;
7023						}
7024					}
7025				}
7026
7027				active = active || state == lastFound;
7028			}
7029		}
7030
7031		if (firstMatch != null)
7032		{
7033			if (i == cells.length && allPagesInput.checked)
7034			{
7035				lastFound = null;
7036				allChecked = true;
7037				return search(true, trySameCell, stayOnPage);
7038			}
7039
7040			lastFound = firstMatch;
7041			graph.scrollCellToVisible(lastFound.cell);
7042
7043			if (graph.isEnabled() && !graph.isCellLocked(lastFound.cell))
7044			{
7045				if (!stayOnPage &&
7046					(graph.getSelectionCell() != lastFound.cell ||
7047					graph.getSelectionCount() != 1))
7048				{
7049					graph.setSelectionCell(lastFound.cell);
7050				}
7051			}
7052			else
7053			{
7054				graph.highlightCell(lastFound.cell);
7055			}
7056		}
7057		//Check other pages
7058		else if (!internalCall && allPagesInput.checked)
7059		{
7060			allChecked = true;
7061			return search(true, trySameCell, stayOnPage);
7062		}
7063		else if (graph.isEnabled() && !stayOnPage)
7064		{
7065			graph.clearSelection();
7066		}
7067
7068		lastSearchSuccessful = firstMatch != null;
7069
7070		if (withReplace && !internalCall)
7071		{
7072			updateReplBtns();
7073		}
7074
7075		return searchStr.length == 0 || firstMatch != null;
7076	};
7077
7078	mxUtils.br(div);
7079
7080	var btnsCont = document.createElement('div');
7081	btnsCont.style.left = '0px';
7082	btnsCont.style.right = '0px';
7083	btnsCont.style.marginTop = '6px';
7084	btnsCont.style.padding = '0 6px 0 6px';
7085	btnsCont.style.textAlign = 'center';
7086	div.appendChild(btnsCont);
7087
7088	var resetBtn = mxUtils.button(mxResources.get('reset'), function()
7089	{
7090		replAllNotif.innerHTML = '';
7091		searchInput.value = '';
7092		searchInput.style.backgroundColor = '';
7093
7094		if (withReplace)
7095		{
7096			replaceInput.value = '';
7097			updateReplBtns();
7098		}
7099
7100		lastFound = null;
7101		lastSearch = null;
7102		allChecked = false;
7103		searchInput.focus();
7104	});
7105
7106	resetBtn.setAttribute('title', mxResources.get('reset'));
7107	resetBtn.style.float = 'none';
7108	resetBtn.style.width = '120px';
7109	resetBtn.style.marginTop = '6px';
7110	resetBtn.style.marginLeft = '8px';
7111	resetBtn.style.overflow = 'hidden';
7112	resetBtn.style.textOverflow = 'ellipsis';
7113	resetBtn.className = 'geBtn';
7114
7115	if (!withReplace)
7116	{
7117		btnsCont.appendChild(resetBtn);
7118	}
7119
7120	var btn = mxUtils.button(mxResources.get('find'), function()
7121	{
7122		try
7123		{
7124			searchInput.style.backgroundColor = search() ? '' :
7125				(Editor.isDarkMode() ? '#ff0000' : '#ffcfcf');
7126		}
7127		catch (e)
7128		{
7129			ui.handleError(e);
7130		}
7131	});
7132
7133	// TODO: Reset state after selection change
7134	btn.setAttribute('title', mxResources.get('find') + ' (Enter)');
7135	btn.style.float = 'none';
7136	btn.style.width = '120px';
7137	btn.style.marginTop = '6px';
7138	btn.style.marginLeft = '8px';
7139	btn.style.overflow = 'hidden';
7140	btn.style.textOverflow = 'ellipsis';
7141	btn.className = 'geBtn gePrimaryBtn';
7142
7143	btnsCont.appendChild(btn);
7144
7145	var replAllNotif = document.createElement('div');
7146	replAllNotif.style.marginTop = '10px';
7147
7148	if (!withReplace)
7149	{
7150		resetBtn.style.width = '90px';
7151		btn.style.width = '90px';
7152	}
7153	else
7154	{
7155		function replaceInLabel(str, substr, newSubstr, startIndex, style)
7156		{
7157			if (style == null || style['html'] != '1')
7158			{
7159				var replStart = str.toLowerCase().indexOf(substr, startIndex);
7160				return replStart < 0? str : str.substr(0, replStart) + newSubstr + str.substr(replStart + substr.length);
7161			}
7162
7163			var origStr = str;
7164			substr = mxUtils.htmlEntities(substr);
7165			var tagPos = [], p = -1;
7166
7167			while((p = str.indexOf('<', p + 1)) > -1)
7168			{
7169				tagPos.push(p);
7170			}
7171
7172			var tags = str.match(/<[^>]*>/g);
7173			str = str.replace(/<[^>]*>/g, '');
7174			var lStr = str.toLowerCase();
7175			var replStart = lStr.indexOf(substr, startIndex);
7176
7177			if (replStart < 0)
7178			{
7179				return origStr;
7180			}
7181
7182			var replEnd = replStart + substr.length;
7183			var newSubstr = mxUtils.htmlEntities(newSubstr);
7184
7185			//Tags within the replaced text is added before it
7186			var newStr = str.substr(0, replStart) + newSubstr + str.substr(replEnd);
7187			var tagDiff = 0;
7188
7189			for (var i = 0; i < tagPos.length; i++)
7190			{
7191				if (tagPos[i] - tagDiff < replStart)
7192				{
7193					newStr = newStr.substr(0, tagPos[i]) + tags[i] + newStr.substr(tagPos[i]);
7194				}
7195				else if (tagPos[i] - tagDiff < replEnd)
7196				{
7197					var inPos = replStart + tagDiff;
7198					newStr = newStr.substr(0, inPos) + tags[i] + newStr.substr(inPos);
7199				}
7200				else
7201				{
7202					var inPos = tagPos[i] + (newSubstr.length - substr.length);
7203					newStr = newStr.substr(0, inPos) + tags[i] + newStr.substr(inPos);
7204				}
7205
7206				tagDiff += tags[i].length;
7207			}
7208
7209			return newStr;
7210		};
7211
7212		var replaceFindBtn = mxUtils.button(mxResources.get('replFind'), function()
7213		{
7214			try
7215			{
7216				if (lblMatch != null && lastFound != null && replaceInput.value)
7217				{
7218					var cell = lastFound.cell, lbl = graph.getLabel(cell);
7219
7220					if (graph.isCellEditable(cell))
7221					{
7222						graph.model.setValue(cell, replaceInLabel(lbl, lblMatch, replaceInput.value,
7223							lblMatchPos - lblMatch.length, graph.getCurrentCellStyle(cell)));
7224					}
7225
7226					searchInput.style.backgroundColor = search(false, true) ? '' :
7227						(Editor.isDarkMode() ? '#ff0000' : '#ffcfcf');
7228				}
7229			}
7230			catch (e)
7231			{
7232				ui.handleError(e);
7233			}
7234		});
7235
7236		replaceFindBtn.setAttribute('title', mxResources.get('replFind'));
7237		replaceFindBtn.style.float = 'none';
7238		replaceFindBtn.style.width = '120px';
7239		replaceFindBtn.style.marginTop = '6px';
7240		replaceFindBtn.style.marginLeft = '8px';
7241		replaceFindBtn.style.overflow = 'hidden';
7242		replaceFindBtn.style.textOverflow = 'ellipsis';
7243		replaceFindBtn.className = 'geBtn gePrimaryBtn';
7244		replaceFindBtn.setAttribute('disabled', 'disabled');
7245
7246		btnsCont.appendChild(replaceFindBtn);
7247		mxUtils.br(btnsCont);
7248
7249		var replaceBtn = mxUtils.button(mxResources.get('replace'), function()
7250		{
7251			try
7252			{
7253				if (lblMatch != null && lastFound != null && replaceInput.value)
7254				{
7255					var cell = lastFound.cell, lbl = graph.getLabel(cell);
7256
7257					graph.model.setValue(cell, replaceInLabel(lbl, lblMatch, replaceInput.value, lblMatchPos - lblMatch.length, graph.getCurrentCellStyle(cell)));
7258					replaceFindBtn.setAttribute('disabled', 'disabled');
7259					replaceBtn.setAttribute('disabled', 'disabled');
7260				}
7261			}
7262			catch (e)
7263			{
7264				ui.handleError(e);
7265			}
7266		});
7267
7268		replaceBtn.setAttribute('title', mxResources.get('replace'));
7269		replaceBtn.style.float = 'none';
7270		replaceBtn.style.width = '120px';
7271		replaceBtn.style.marginTop = '6px';
7272		replaceBtn.style.marginLeft = '8px';
7273		replaceBtn.style.overflow = 'hidden';
7274		replaceBtn.style.textOverflow = 'ellipsis';
7275		replaceBtn.className = 'geBtn gePrimaryBtn';
7276		replaceBtn.setAttribute('disabled', 'disabled');
7277
7278		btnsCont.appendChild(replaceBtn);
7279
7280		var replaceAllBtn = mxUtils.button(mxResources.get('replaceAll'), function()
7281		{
7282			replAllNotif.innerHTML = '';
7283
7284			if (replaceInput.value)
7285			{
7286				var currentPage = ui.currentPage;
7287				var cells = ui.editor.graph.getSelectionCells();
7288				ui.editor.graph.rendering = false;
7289
7290				graph.getModel().beginUpdate();
7291				try
7292				{
7293					var safeguard = 0;
7294					var seen = {};
7295
7296					while (search(false, true, true) && safeguard < 100)
7297					{
7298						var cell = lastFound.cell, lbl = graph.getLabel(cell);
7299						var oldSeen = seen[cell.id];
7300
7301						if (oldSeen && oldSeen.replAllMrk == marker && oldSeen.replAllPos >= lblMatchPos)
7302						{
7303							break;
7304						}
7305
7306						seen[cell.id] = {replAllMrk: marker, replAllPos: lblMatchPos};
7307
7308						if (graph.isCellEditable(cell))
7309						{
7310							graph.model.setValue(cell, replaceInLabel(lbl, lblMatch, replaceInput.value,
7311								lblMatchPos - lblMatch.length, graph.getCurrentCellStyle(cell)));
7312							safeguard++;
7313						}
7314					}
7315
7316					if (currentPage != ui.currentPage)
7317					{
7318						ui.editor.graph.model.execute(new SelectPage(ui, currentPage));
7319					}
7320
7321					mxUtils.write(replAllNotif, mxResources.get('matchesRepl', [safeguard]));
7322				}
7323				catch (e)
7324				{
7325					ui.handleError(e);
7326				}
7327				finally
7328				{
7329					graph.getModel().endUpdate();
7330					ui.editor.graph.setSelectionCells(cells);
7331					ui.editor.graph.rendering = true;
7332				}
7333
7334				marker++;
7335			}
7336		});
7337
7338		replaceAllBtn.setAttribute('title', mxResources.get('replaceAll'));
7339		replaceAllBtn.style.float = 'none';
7340		replaceAllBtn.style.width = '120px';
7341		replaceAllBtn.style.marginTop = '6px';
7342		replaceAllBtn.style.marginLeft = '8px';
7343		replaceAllBtn.style.overflow = 'hidden';
7344		replaceAllBtn.style.textOverflow = 'ellipsis';
7345		replaceAllBtn.className = 'geBtn gePrimaryBtn';
7346		replaceAllBtn.setAttribute('disabled', 'disabled');
7347
7348		btnsCont.appendChild(replaceAllBtn);
7349		mxUtils.br(btnsCont);
7350		btnsCont.appendChild(resetBtn);
7351
7352		var closeBtn = mxUtils.button(mxResources.get('close'), mxUtils.bind(this, function()
7353		{
7354			this.window.setVisible(false);
7355		}));
7356
7357		closeBtn.setAttribute('title', mxResources.get('close'));
7358		closeBtn.style.float = 'none';
7359		closeBtn.style.width = '120px';
7360		closeBtn.style.marginTop = '6px';
7361		closeBtn.style.marginLeft = '8px';
7362		closeBtn.style.overflow = 'hidden';
7363		closeBtn.style.textOverflow = 'ellipsis';
7364		closeBtn.className = 'geBtn';
7365
7366		btnsCont.appendChild(closeBtn);
7367		mxUtils.br(btnsCont);
7368		btnsCont.appendChild(replAllNotif);
7369	}
7370
7371	mxEvent.addListener(searchInput, 'keyup', function(evt)
7372	{
7373		// Ctrl or Cmd keys
7374		if (evt.keyCode == 91 || evt.keyCode == 93 || evt.keyCode == 17)
7375		{
7376			// Workaround for lost focus on show
7377			mxEvent.consume(evt);
7378		}
7379		else if (evt.keyCode == 27)
7380		{
7381			// Escape closes window
7382			action.funct();
7383		}
7384		else if (lastSearch != searchInput.value.toLowerCase() || evt.keyCode == 13)
7385		{
7386			try
7387			{
7388				searchInput.style.backgroundColor = search() ? '' :
7389					(Editor.isDarkMode() ? '#ff0000' : '#ffcfcf');
7390			}
7391			catch (e)
7392			{
7393				searchInput.style.backgroundColor = Editor.isDarkMode() ? '#ff0000' : '#ffcfcf';
7394			}
7395		}
7396	});
7397
7398	// Ctrl+F closes window
7399	mxEvent.addListener(div, 'keydown', function(evt)
7400	{
7401		if (evt.keyCode == 70 && ui.keyHandler.isControlDown(evt) && !mxEvent.isShiftDown(evt))
7402		{
7403			action.funct();
7404			mxEvent.consume(evt);
7405		}
7406	});
7407
7408	this.window = new mxWindow(mxResources.get('find') + ((withReplace) ?
7409		'/' + mxResources.get('replace') : ''),
7410		div, x, y, w, h, true, true);
7411	this.window.destroyOnClose = false;
7412	this.window.setMaximizable(false);
7413	this.window.setResizable(false);
7414	this.window.setClosable(true);
7415
7416	this.window.addListener('show', mxUtils.bind(this, function()
7417	{
7418		this.window.fit();
7419
7420		if (this.window.isVisible())
7421		{
7422			searchInput.focus();
7423
7424			if (mxClient.IS_GC || mxClient.IS_FF || document.documentMode >= 5)
7425			{
7426				searchInput.select();
7427			}
7428			else
7429			{
7430				document.execCommand('selectAll', false, null);
7431			}
7432
7433			if (ui.pages != null && ui.pages.length > 1)
7434			{
7435				allPagesInput.removeAttribute('disabled');
7436			}
7437			else
7438			{
7439				allPagesInput.checked = false;
7440				allPagesInput.setAttribute('disabled', 'disabled');
7441			}
7442		}
7443		else
7444		{
7445			graph.container.focus();
7446		}
7447	}));
7448
7449	this.window.setLocation = function(x, y)
7450	{
7451		var iw = window.innerWidth || document.body.clientWidth || document.documentElement.clientWidth;
7452		var ih = window.innerHeight || document.body.clientHeight || document.documentElement.clientHeight;
7453
7454		x = Math.max(0, Math.min(x, iw - this.table.clientWidth));
7455		y = Math.max(0, Math.min(y, ih - this.table.clientHeight - ((urlParams['sketch'] == '1') ? 3 : 48)));
7456
7457		if (this.getX() != x || this.getY() != y)
7458		{
7459			mxWindow.prototype.setLocation.apply(this, arguments);
7460		}
7461	};
7462
7463	var resizeListener = mxUtils.bind(this, function()
7464	{
7465		var x = this.window.getX();
7466		var y = this.window.getY();
7467
7468		this.window.setLocation(x, y);
7469	});
7470
7471	mxEvent.addListener(window, 'resize', resizeListener);
7472
7473	this.destroy = function()
7474	{
7475		mxEvent.removeListener(window, 'resize', resizeListener);
7476		this.window.destroy();
7477	}
7478};
7479
7480/**
7481 *
7482 */
7483var FreehandWindow = function(editorUi, x, y, w, h)
7484{
7485	var graph = editorUi.editor.graph;
7486
7487	var div = document.createElement('div');
7488	div.style.textAlign = 'center';
7489	div.style.userSelect = 'none';
7490	div.style.overflow = 'hidden';
7491	div.style.height = '100%';
7492
7493	var startBtn = mxUtils.button(mxResources.get('startDrawing'), function()
7494	{
7495		if (graph.freehand.isDrawing())
7496		{
7497			graph.freehand.stopDrawing();
7498		}
7499		else
7500		{
7501			graph.freehand.startDrawing();
7502		}
7503	});
7504
7505	startBtn.setAttribute('title', mxResources.get('startDrawing'));
7506	startBtn.style.marginTop = '10px';
7507	startBtn.style.width = '90%';
7508	startBtn.style.boxSizing = 'border-box';
7509	startBtn.style.overflow = 'hidden';
7510	startBtn.style.textOverflow = 'ellipsis';
7511	startBtn.style.textAlign = 'center';
7512	startBtn.className = 'geBtn gePrimaryBtn';
7513
7514	div.appendChild(startBtn);
7515
7516	this.window = new mxWindow(mxResources.get('freehand'), div, x, y, w, h, true, true);
7517	this.window.destroyOnClose = false;
7518	this.window.setMaximizable(false);
7519	this.window.setResizable(false);
7520	this.window.setClosable(true);
7521
7522	graph.addListener('freehandStateChanged', mxUtils.bind(this, function()
7523	{
7524		startBtn.innerHTML = '';
7525		mxUtils.write(startBtn, mxResources.get(graph.freehand.isDrawing() ? 'stopDrawing' : 'startDrawing'));
7526		startBtn.setAttribute('title', mxResources.get(graph.freehand.isDrawing() ? 'stopDrawing' : 'startDrawing'));
7527		startBtn.className = 'geBtn' + (!graph.freehand.isDrawing() ? ' gePrimaryBtn' : '');
7528	}));
7529
7530	this.window.addListener('show', mxUtils.bind(this, function()
7531	{
7532		this.window.fit();
7533	}));
7534
7535	this.window.addListener('hide', mxUtils.bind(this, function()
7536	{
7537		if (graph.freehand.isDrawing())
7538		{
7539			graph.freehand.stopDrawing();
7540		}
7541	}));
7542
7543	this.window.setLocation = function(x, y)
7544	{
7545		var iw = window.innerWidth || document.body.clientWidth || document.documentElement.clientWidth;
7546		var ih = window.innerHeight || document.body.clientHeight || document.documentElement.clientHeight;
7547
7548		x = Math.max(0, Math.min(x, iw - this.table.clientWidth));
7549		y = Math.max(0, Math.min(y, ih - this.table.clientHeight - ((urlParams['sketch'] == '1') ? 3 : 48)));
7550
7551		if (this.getX() != x || this.getY() != y)
7552		{
7553			mxWindow.prototype.setLocation.apply(this, arguments);
7554		}
7555	};
7556
7557	var resizeListener = mxUtils.bind(this, function()
7558	{
7559		var x = this.window.getX();
7560		var y = this.window.getY();
7561
7562		this.window.setLocation(x, y);
7563	});
7564
7565	mxEvent.addListener(window, 'resize', resizeListener);
7566
7567	this.destroy = function()
7568	{
7569		mxEvent.removeListener(window, 'resize', resizeListener);
7570		this.window.destroy();
7571	}
7572};
7573
7574/**
7575 *
7576 */
7577var TagsWindow = function(editorUi, x, y, w, h)
7578{
7579	var graph = editorUi.editor.graph;
7580
7581	var tagsComponent = editorUi.editor.graph.createTagsDialog(mxUtils.bind(this, function()
7582	{
7583		return this.window.isVisible();
7584	}), null, function(allTags, updateFn)
7585	{
7586		if (graph.isEnabled())
7587		{
7588			var dlg = new FilenameDialog(editorUi, '', mxResources.get('add'), function(newValue)
7589			{
7590				editorUi.hideDialog();
7591
7592				if (newValue != null && newValue.length > 0)
7593				{
7594					var temp = newValue.split(' ');
7595					var tags = [];
7596
7597					for (var i = 0; i < temp.length; i++)
7598					{
7599						var token = mxUtils.trim(temp[i]);
7600
7601						if (token != '' && mxUtils.indexOf(
7602							allTags, token) < 0)
7603						{
7604							tags.push(token);
7605						}
7606					}
7607
7608					if (tags.length > 0)
7609					{
7610						if (graph.isSelectionEmpty())
7611						{
7612							updateFn(allTags.concat(tags));
7613						}
7614						else
7615						{
7616							graph.addTagsForCells(graph.getSelectionCells(), tags);
7617						}
7618					}
7619				}
7620			}, mxResources.get('enterValue') + ' (' + mxResources.get('tags') + ')');
7621
7622			editorUi.showDialog(dlg.container, 300, 80, true, true);
7623			dlg.init();
7624		}
7625	});
7626
7627	var div = tagsComponent.div;
7628	this.window = new mxWindow(mxResources.get('tags'), div, x, y, w, h, true, true);
7629	this.window.minimumSize = new mxRectangle(0, 0, 212, 120);
7630	this.window.destroyOnClose = false;
7631	this.window.setMaximizable(false);
7632	this.window.setResizable(true);
7633	this.window.setClosable(true);
7634
7635	this.window.addListener('show', mxUtils.bind(this, function()
7636	{
7637		tagsComponent.refresh();
7638		this.window.fit();
7639	}));
7640
7641	this.window.setLocation = function(x, y)
7642	{
7643		var iw = window.innerWidth || document.body.clientWidth || document.documentElement.clientWidth;
7644		var ih = window.innerHeight || document.body.clientHeight || document.documentElement.clientHeight;
7645
7646		x = Math.max(0, Math.min(x, iw - this.table.clientWidth));
7647		y = Math.max(0, Math.min(y, ih - this.table.clientHeight - ((urlParams['sketch'] == '1') ? 3 : 48)));
7648
7649		if (this.getX() != x || this.getY() != y)
7650		{
7651			mxWindow.prototype.setLocation.apply(this, arguments);
7652		}
7653	};
7654
7655	var resizeListener = mxUtils.bind(this, function()
7656	{
7657		var x = this.window.getX();
7658		var y = this.window.getY();
7659
7660		this.window.setLocation(x, y);
7661	});
7662
7663	mxEvent.addListener(window, 'resize', resizeListener);
7664
7665	this.destroy = function()
7666	{
7667		mxEvent.removeListener(window, 'resize', resizeListener);
7668		this.window.destroy();
7669	}
7670};
7671
7672/**
7673 * Constructs a new auth dialog.
7674 */
7675var AuthDialog = function(editorUi, peer, showRememberOption, fn)
7676{
7677	var div = document.createElement('div');
7678	div.style.textAlign = 'center';
7679
7680	var hd = document.createElement('p');
7681	hd.style.fontSize = '16pt';
7682	hd.style.padding = '0px';
7683	hd.style.margin = '0px';
7684	hd.style.color = 'gray';
7685
7686	mxUtils.write(hd, mxResources.get('authorizationRequired'));
7687
7688	var service = 'Unknown';
7689
7690	var img = document.createElement('img');
7691	img.setAttribute('border', '0');
7692	img.setAttribute('align', 'absmiddle');
7693	img.style.marginRight = '10px';
7694
7695	if (peer == editorUi.drive)
7696	{
7697		service = mxResources.get('googleDrive');
7698		img.src = IMAGE_PATH + '/google-drive-logo-white.svg';
7699	}
7700	else if (peer == editorUi.dropbox)
7701	{
7702		service = mxResources.get('dropbox');
7703		img.src = IMAGE_PATH + '/dropbox-logo-white.svg';
7704	}
7705	else if (peer == editorUi.oneDrive)
7706	{
7707		service = mxResources.get('oneDrive');
7708		img.src = IMAGE_PATH + '/onedrive-logo-white.svg';
7709	}
7710	else if (peer == editorUi.gitHub)
7711	{
7712		service = mxResources.get('github');
7713		img.src = IMAGE_PATH + '/github-logo-white.svg';
7714	}
7715	else if (peer == editorUi.gitLab)
7716	{
7717		service = mxResources.get('gitlab');
7718		img.src = IMAGE_PATH + '/gitlab-logo.svg';
7719		img.style.width = '32px';
7720	}
7721	else if (peer == editorUi.notion)
7722	{
7723		service = mxResources.get('notion');
7724		img.src = IMAGE_PATH + '/notion-logo-white.svg';
7725		img.style.width = '32px';
7726	}
7727	else if (peer == editorUi.trello)
7728	{
7729		service = mxResources.get('trello');
7730		img.src = IMAGE_PATH + '/trello-logo-white.svg';
7731	}
7732
7733	var p = document.createElement('p');
7734	mxUtils.write(p, mxResources.get('authorizeThisAppIn', [service]));
7735
7736	var cb = document.createElement('input');
7737	cb.setAttribute('type', 'checkbox');
7738
7739	var button = mxUtils.button(mxResources.get('authorize'), function()
7740	{
7741		fn(cb.checked);
7742	});
7743
7744	button.insertBefore(img, button.firstChild);
7745	button.style.marginTop = '6px';
7746	button.className = 'geBigButton';
7747	button.style.fontSize = '18px';
7748	button.style.padding = '14px';
7749
7750	div.appendChild(hd);
7751	div.appendChild(p);
7752	div.appendChild(button);
7753
7754	if (showRememberOption)
7755	{
7756		var p2 = document.createElement('p');
7757		p2.style.marginTop = '20px';
7758		p2.appendChild(cb);
7759		var span = document.createElement('span');
7760		mxUtils.write(span, ' ' + mxResources.get('rememberMe'));
7761		p2.appendChild(span);
7762		div.appendChild(p2);
7763		cb.checked = true;
7764		cb.defaultChecked = true;
7765
7766		mxEvent.addListener(span, 'click', function(evt)
7767		{
7768			cb.checked = !cb.checked;
7769			mxEvent.consume(evt);
7770		});
7771	}
7772
7773	this.container = div;
7774};
7775
7776var MoreShapesDialog = function(editorUi, expanded, entries)
7777{
7778	entries = (entries != null) ? entries : editorUi.sidebar.entries;
7779	var div = document.createElement('div');
7780	var newEntries = [];
7781
7782	// Adds custom sections first
7783	if (editorUi.sidebar.customEntries != null)
7784	{
7785		for (var i = 0; i < editorUi.sidebar.customEntries.length; i++)
7786		{
7787			var section = editorUi.sidebar.customEntries[i];
7788			var tmp = {title: editorUi.getResource(section.title), entries: []};
7789
7790			for (var j = 0; j < section.entries.length; j++)
7791			{
7792				var entry = section.entries[j];
7793				tmp.entries.push({id: entry.id, title:
7794					editorUi.getResource(entry.title),
7795					desc: editorUi.getResource(entry.desc),
7796					image: entry.preview});
7797			}
7798
7799			newEntries.push(tmp);
7800		}
7801	}
7802
7803	// Adds built-in sections and filter entries
7804	for (var i = 0; i < entries.length; i++)
7805	{
7806		if (editorUi.sidebar.enabledLibraries == null)
7807		{
7808			newEntries.push(entries[i]);
7809		}
7810		else
7811		{
7812			var tmp = {title: entries[i].title, entries: []};
7813
7814			for (var j = 0; j < entries[i].entries.length; j++)
7815			{
7816				if (mxUtils.indexOf(editorUi.sidebar.enabledLibraries,
7817					entries[i].entries[j].id) >= 0)
7818				{
7819					tmp.entries.push(entries[i].entries[j]);
7820				}
7821			}
7822
7823			if (tmp.entries.length > 0)
7824			{
7825				newEntries.push(tmp);
7826			}
7827		}
7828	}
7829
7830	entries = newEntries;
7831
7832	if (expanded)
7833	{
7834		var addEntries = mxUtils.bind(this, function(e)
7835		{
7836			for (var i = 0; i < e.length; i++)
7837			{
7838				(function(section)
7839				{
7840					var title = listEntry.cloneNode(false);
7841					title.style.fontWeight = 'bold';
7842					title.style.backgroundColor = Editor.isDarkMode() ? '#505759' : '#e5e5e5';
7843					title.style.padding = '6px 0px 6px 20px';
7844					mxUtils.write(title, section.title);
7845					list.appendChild(title);
7846
7847					for (var j = 0; j < section.entries.length; j++)
7848					{
7849						(function(entry)
7850						{
7851							var option = listEntry.cloneNode(false);
7852							option.style.cursor = 'pointer';
7853							option.style.padding = '4px 0px 4px 20px';
7854							option.style.whiteSpace = 'nowrap';
7855							option.style.overflow = 'hidden';
7856							option.style.textOverflow = 'ellipsis';
7857							option.setAttribute('title', entry.title + ' (' + entry.id + ')');
7858
7859							var checkbox = document.createElement('input');
7860							checkbox.setAttribute('type', 'checkbox');
7861							checkbox.checked = editorUi.sidebar.isEntryVisible(entry.id);
7862							checkbox.defaultChecked = checkbox.checked;
7863							option.appendChild(checkbox);
7864							mxUtils.write(option, ' ' + entry.title);
7865
7866							list.appendChild(option);
7867
7868							var itemClicked = function(evt)
7869							{
7870								if (evt == null || mxEvent.getSource(evt).nodeName != 'INPUT')
7871								{
7872									preview.style.textAlign = 'center';
7873									preview.style.padding = '0px';
7874									preview.style.color = '';
7875									preview.innerHTML = '';
7876
7877									if (entry.desc != null)
7878									{
7879										var pre = document.createElement('pre');
7880										pre.style.boxSizing = 'border-box';
7881										pre.style.fontFamily = 'inherit';
7882										pre.style.margin = '20px';
7883										pre.style.right = '0px';
7884										pre.style.textAlign = 'left';
7885										mxUtils.write(pre, entry.desc);
7886										preview.appendChild(pre);
7887									}
7888
7889									if (entry.imageCallback != null)
7890									{
7891										entry.imageCallback(preview);
7892									}
7893									else if (entry.image != null)
7894									{
7895										preview.innerHTML += '<img border="0" src="' + entry.image + '"/>';
7896									}
7897									else if (entry.desc == null)
7898									{
7899										preview.style.padding = '20px';
7900										preview.style.color = 'rgb(179, 179, 179)';
7901										mxUtils.write(preview, mxResources.get('noPreview'));
7902									}
7903
7904									if (currentListItem != null)
7905									{
7906										currentListItem.style.backgroundColor = '';
7907									}
7908
7909									currentListItem = option;
7910									currentListItem.style.backgroundColor = Editor.isDarkMode() ? '#000000' : '#ebf2f9';
7911
7912									if (evt != null)
7913									{
7914										mxEvent.consume(evt);
7915									}
7916								}
7917							};
7918
7919							mxEvent.addListener(option, 'click', itemClicked);
7920							mxEvent.addListener(option, 'dblclick', function(evt)
7921							{
7922								checkbox.checked = !checkbox.checked;
7923								mxEvent.consume(evt);
7924							});
7925
7926							applyFunctions.push(function()
7927							{
7928								return (checkbox.checked) ? entry.id : null;
7929							});
7930
7931							// Selects first entry
7932							if (i == 0 && j == 0)
7933							{
7934								itemClicked();
7935							}
7936						})(section.entries[j]);
7937					}
7938				})(e[i]);
7939			}
7940		});
7941
7942		var hd = document.createElement('div');
7943		hd.className = 'geDialogTitle';
7944		mxUtils.write(hd, mxResources.get('shapes'));
7945		hd.style.position = 'absolute';
7946		hd.style.top = '0px';
7947		hd.style.left = '0px';
7948		hd.style.lineHeight = '40px';
7949		hd.style.height = '40px';
7950		hd.style.right = '0px';
7951
7952		var list = document.createElement('div');
7953		var preview = document.createElement('div');
7954
7955		list.style.position = 'absolute';
7956		list.style.top = '40px';
7957		list.style.left = '0px';
7958		list.style.width = '202px';
7959		list.style.bottom = '60px';
7960		list.style.overflow = 'auto';
7961
7962		preview.style.position = 'absolute';
7963		preview.style.left = '202px';
7964		preview.style.right = '0px';
7965		preview.style.top = '40px';
7966		preview.style.bottom = '60px';
7967		preview.style.overflow = 'auto';
7968		preview.style.borderLeft = '1px solid rgb(211, 211, 211)';
7969		preview.style.textAlign = 'center';
7970
7971		var currentListItem = null;
7972		var applyFunctions = [];
7973
7974		var listEntry = document.createElement('div');
7975		listEntry.style.position = 'relative';
7976		listEntry.style.left = '0px';
7977		listEntry.style.right = '0px';
7978
7979		addEntries(entries);
7980		div.style.padding = '30px';
7981
7982		div.appendChild(hd);
7983		div.appendChild(list);
7984		div.appendChild(preview);
7985
7986		var buttons = document.createElement('div');
7987		buttons.className = 'geDialogFooter';
7988		buttons.style.position = 'absolute';
7989		buttons.style.paddingRight = '16px';
7990		buttons.style.color = 'gray';
7991		buttons.style.left = '0px';
7992		buttons.style.right = '0px';
7993		buttons.style.bottom = '0px';
7994		buttons.style.height = '60px';
7995		buttons.style.lineHeight = '52px';
7996
7997		var cb = document.createElement('input');
7998		cb.setAttribute('type', 'checkbox');
7999
8000		if (isLocalStorage || mxClient.IS_CHROMEAPP)
8001		{
8002			var span = document.createElement('span');
8003			span.style.paddingRight = '20px';
8004			span.appendChild(cb);
8005			mxUtils.write(span, ' ' + mxResources.get('rememberThisSetting'));
8006			cb.checked = true;
8007			cb.defaultChecked = true;
8008
8009			mxEvent.addListener(span, 'click', function(evt)
8010			{
8011				if (mxEvent.getSource(evt) != cb)
8012				{
8013					cb.checked = !cb.checked;
8014					mxEvent.consume(evt);
8015				}
8016			});
8017
8018			buttons.appendChild(span);
8019		}
8020
8021		var cancelBtn = mxUtils.button(mxResources.get('cancel'), function()
8022		{
8023			editorUi.hideDialog();
8024		});
8025		cancelBtn.className = 'geBtn';
8026
8027		var applyBtn = mxUtils.button(mxResources.get('apply'), function()
8028		{
8029	    	editorUi.hideDialog();
8030	    	var libs = [];
8031
8032			for (var i = 0; i < applyFunctions.length; i++)
8033			{
8034				var lib = applyFunctions[i].apply(this, arguments);
8035
8036				if (lib != null)
8037				{
8038					libs.push(lib);
8039				}
8040			}
8041
8042			editorUi.sidebar.showEntries(libs.join(';'), cb.checked, true);
8043		});
8044		applyBtn.className = 'geBtn gePrimaryBtn';
8045
8046		if (editorUi.editor.cancelFirst)
8047		{
8048			buttons.appendChild(cancelBtn);
8049			buttons.appendChild(applyBtn);
8050		}
8051		else
8052		{
8053			buttons.appendChild(applyBtn);
8054			buttons.appendChild(cancelBtn);
8055		}
8056
8057		div.appendChild(buttons);
8058	}
8059	else
8060	{
8061		var libFS = document.createElement('table');
8062		var tbody = document.createElement('tbody');
8063		div.style.height = '100%';
8064		div.style.overflow = 'auto';
8065		var row = document.createElement('tr');
8066		libFS.style.width = '100%';
8067
8068		var leftDiv = document.createElement('td');
8069		var midDiv = document.createElement('td');
8070		var rightDiv = document.createElement('td');
8071
8072		var addLibCB = mxUtils.bind(this, function(wrapperDiv, title, key)
8073		{
8074			var libCB = document.createElement('input');
8075			libCB.type = 'checkbox';
8076			libFS.appendChild(libCB);
8077
8078			libCB.checked = editorUi.sidebar.isEntryVisible(key);
8079
8080			var libSpan = document.createElement('span');
8081			mxUtils.write(libSpan, title);
8082
8083			var label = document.createElement('div');
8084			label.style.display = 'block';
8085			label.appendChild(libCB);
8086			label.appendChild(libSpan);
8087
8088			mxEvent.addListener(libSpan, 'click', function(evt)
8089			{
8090				libCB.checked = !libCB.checked;
8091				mxEvent.consume(evt);
8092			});
8093
8094			wrapperDiv.appendChild(label);
8095
8096			return function()
8097			{
8098				return (libCB.checked) ? key : null;
8099			};
8100		});
8101
8102		row.appendChild(leftDiv);
8103		row.appendChild(midDiv);
8104		row.appendChild(rightDiv);
8105
8106		tbody.appendChild(row);
8107		libFS.appendChild(tbody);
8108
8109		var applyFunctions = [];
8110		var count = 0;
8111
8112		// Counts total number of entries
8113		for (var i = 0; i < entries.length; i++)
8114		{
8115			for (var j = 0; j < entries[i].entries.length; j++)
8116			{
8117				count++;
8118			}
8119		}
8120
8121		// Distributes entries on columns
8122		var cols = [leftDiv, midDiv, rightDiv];
8123		var counter = 0;
8124
8125		for (var i = 0; i < entries.length; i++)
8126		{
8127			(function(section)
8128			{
8129				for (var j = 0; j < section.entries.length; j++)
8130				{
8131					(function(entry)
8132					{
8133						var index = Math.floor(counter / (count / 3));
8134						applyFunctions.push(addLibCB(cols[index], entry.title, entry.id));
8135						counter++;
8136					})(section.entries[j]);
8137				}
8138			})(entries[i]);
8139		}
8140
8141		div.appendChild(libFS);
8142
8143		var remember = document.createElement('div');
8144		remember.style.marginTop = '18px';
8145		remember.style.textAlign = 'center';
8146
8147		var cb = document.createElement('input');
8148
8149		if (isLocalStorage)
8150		{
8151			cb.setAttribute('type', 'checkbox');
8152			cb.checked = true;
8153			cb.defaultChecked = true;
8154			remember.appendChild(cb);
8155			var span = document.createElement('span');
8156			mxUtils.write(span, ' ' + mxResources.get('rememberThisSetting'));
8157			remember.appendChild(span);
8158
8159			mxEvent.addListener(span, 'click', function(evt)
8160			{
8161				cb.checked = !cb.checked;
8162				mxEvent.consume(evt);
8163			});
8164		}
8165
8166		div.appendChild(remember);
8167
8168		var cancelBtn = mxUtils.button(mxResources.get('cancel'), function()
8169		{
8170			editorUi.hideDialog();
8171		});
8172		cancelBtn.className = 'geBtn';
8173
8174		var applyBtn = mxUtils.button(mxResources.get('apply'), function()
8175		{
8176			var libs = ['search'];
8177
8178			for (var i = 0; i < applyFunctions.length; i++)
8179			{
8180				var lib = applyFunctions[i].apply(this, arguments);
8181
8182				if (lib != null)
8183				{
8184					libs.push(lib);
8185				}
8186			}
8187
8188			editorUi.sidebar.showEntries((libs.length > 0) ? libs.join(';') : '', cb.checked);
8189	    	editorUi.hideDialog();
8190		});
8191		applyBtn.className = 'geBtn gePrimaryBtn';
8192
8193		var buttons = document.createElement('div');
8194		buttons.style.marginTop = '26px';
8195		buttons.style.textAlign = 'right';
8196
8197		if (editorUi.editor.cancelFirst)
8198		{
8199			buttons.appendChild(cancelBtn);
8200			buttons.appendChild(applyBtn);
8201		}
8202		else
8203		{
8204			buttons.appendChild(applyBtn);
8205			buttons.appendChild(cancelBtn);
8206		}
8207
8208		div.appendChild(buttons);
8209	}
8210
8211	this.container = div;
8212};
8213
8214var PluginsDialog = function(editorUi, addFn, delFn)
8215{
8216	var div = document.createElement('div');
8217	var inner = document.createElement('div');
8218
8219	inner.style.height = '120px';
8220	inner.style.overflow = 'auto';
8221
8222	var plugins = mxSettings.getPlugins().slice();
8223
8224	function refresh()
8225	{
8226		if (plugins.length == 0)
8227		{
8228			inner.innerHTML = mxUtils.htmlEntities(mxResources.get('noPlugins'));
8229		}
8230		else
8231		{
8232			inner.innerHTML = '';
8233
8234			for (var i = 0; i < plugins.length; i++)
8235			{
8236				var span = document.createElement('span');
8237				span.style.whiteSpace = 'nowrap';
8238
8239				var img = document.createElement('span');
8240				img.className = 'geSprite geSprite-delete';
8241				img.style.position = 'relative';
8242				img.style.cursor = 'pointer';
8243				img.style.top = '5px';
8244				img.style.marginRight = '4px';
8245				img.style.display = 'inline-block';
8246				span.appendChild(img);
8247
8248				mxUtils.write(span, plugins[i]);
8249				inner.appendChild(span);
8250
8251				mxUtils.br(inner);
8252
8253				mxEvent.addListener(img, 'click', (function(index)
8254				{
8255					return function()
8256					{
8257						editorUi.confirm(mxResources.get('delete') + ' "' + plugins[index] + '"?', function()
8258						{
8259							if (delFn != null)
8260							{
8261								delFn(plugins[index]);
8262							}
8263
8264							plugins.splice(index, 1);
8265							refresh();
8266						});
8267					};
8268				})(i));
8269			}
8270		}
8271	}
8272
8273	div.appendChild(inner);
8274	refresh();
8275
8276	var addBtn = mxUtils.button(mxResources.get('add') + '...', addFn != null? function()
8277	{
8278		addFn(function(newPlugin)
8279		{
8280			if (newPlugin && mxUtils.indexOf(plugins, newPlugin) < 0)
8281			{
8282				plugins.push(newPlugin);
8283			}
8284
8285			refresh();
8286		});
8287	}
8288	: function()
8289	{
8290		var div = document.createElement('div');
8291
8292		var title = document.createElement('span');
8293		title.style.marginTop = '6px';
8294		mxUtils.write(title, mxResources.get('builtinPlugins') + ': ');
8295		div.appendChild(title);
8296
8297		var pluginsSelect = document.createElement('select');
8298		pluginsSelect.style.width = '150px';
8299
8300		for (var i = 0; i < App.publicPlugin.length; i++)
8301		{
8302			var option = document.createElement('option');
8303			mxUtils.write(option, App.publicPlugin[i]);
8304			option.value = App.publicPlugin[i];
8305			pluginsSelect.appendChild(option);
8306		}
8307
8308		div.appendChild(pluginsSelect);
8309		mxUtils.br(div);
8310		mxUtils.br(div);
8311
8312		var customBtn = mxUtils.button(mxResources.get('custom') + '...', function()
8313		{
8314			var dlg = new FilenameDialog(editorUi, '', mxResources.get('add'), function(newValue)
8315			{
8316				editorUi.hideDialog();
8317
8318				if (newValue != null && newValue.length > 0)
8319				{
8320					var tokens = newValue.split(';');
8321
8322					for (var i = 0; i < tokens.length; i++)
8323					{
8324						var token = tokens[i];
8325						var url = App.pluginRegistry[token];
8326
8327						if (url != null)
8328						{
8329							token = url;
8330						}
8331
8332						if (token.length > 0 && mxUtils.indexOf(plugins, token) < 0)
8333						{
8334							plugins.push(token);
8335						}
8336					}
8337
8338					refresh();
8339				}
8340			}, mxResources.get('enterValue') + ' (' + mxResources.get('url') + ')');
8341
8342			editorUi.showDialog(dlg.container, 300, 80, true, true);
8343			dlg.init();
8344		});
8345
8346		customBtn.className = 'geBtn';
8347
8348		var dlg = new CustomDialog(editorUi, div, mxUtils.bind(this, function()
8349		{
8350			var token = App.pluginRegistry[pluginsSelect.value];
8351
8352			if (mxUtils.indexOf(plugins, token) < 0)
8353			{
8354				plugins.push(token);
8355				refresh();
8356			}
8357		}), null, null, null, customBtn);
8358		editorUi.showDialog(dlg.container, 300, 100, true, true);
8359	});
8360
8361	addBtn.className = 'geBtn';
8362
8363	var cancelBtn = mxUtils.button(mxResources.get('cancel'), function()
8364	{
8365		editorUi.hideDialog();
8366	});
8367
8368	cancelBtn.className = 'geBtn';
8369
8370	var applyBtn = mxUtils.button(mxResources.get('apply'), function()
8371	{
8372		mxSettings.setPlugins(plugins);
8373		mxSettings.save();
8374		editorUi.hideDialog();
8375		editorUi.alert(mxResources.get('restartForChangeRequired'));
8376	});
8377
8378	applyBtn.className = 'geBtn gePrimaryBtn';
8379
8380	var buttons = document.createElement('div');
8381	buttons.style.marginTop = '14px';
8382	buttons.style.textAlign = 'right';
8383
8384	var helpBtn = mxUtils.button(mxResources.get('help'), function()
8385	{
8386		editorUi.openLink('https://www.diagrams.net/doc/faq/plugins');
8387	});
8388
8389	helpBtn.className = 'geBtn';
8390
8391	if (editorUi.isOffline() && !mxClient.IS_CHROMEAPP)
8392	{
8393		helpBtn.style.display = 'none';
8394	}
8395
8396	buttons.appendChild(helpBtn);
8397
8398	if (editorUi.editor.cancelFirst)
8399	{
8400		buttons.appendChild(cancelBtn);
8401		buttons.appendChild(addBtn);
8402		buttons.appendChild(applyBtn);
8403	}
8404	else
8405	{
8406		buttons.appendChild(addBtn);
8407		buttons.appendChild(applyBtn);
8408		buttons.appendChild(cancelBtn);
8409	}
8410
8411	div.appendChild(buttons);
8412
8413	this.container = div;
8414};
8415
8416var CropImageDialog = function(editorUi, image, fn)
8417{
8418	var div = document.createElement('div');
8419
8420	var croppieDiv = document.createElement('div');
8421	croppieDiv.style.width = '300px';
8422	croppieDiv.style.height = '300px';
8423	div.appendChild(croppieDiv);
8424	var croppie = null;
8425
8426	function createCroppie(isCircle)
8427	{
8428		if (croppie != null)
8429		{
8430			croppie.destroy();
8431		}
8432
8433		if (isCircle)
8434		{
8435			croppie = new Croppie(croppieDiv, {
8436				viewport: { width: 150, height: 150, type: 'circle' },
8437				enableExif: true,
8438			    showZoomer: false,
8439			    enableResize: false,
8440			    enableOrientation: true
8441			});
8442
8443			croppie.bind({
8444			    url: image
8445			});
8446		}
8447		else
8448		{
8449			croppie = new Croppie(croppieDiv, {
8450				viewport: { width: 150, height: 150, type: 'square' },
8451				enableExif: true,
8452			    showZoomer: false,
8453			    enableResize: true,
8454			    enableOrientation: true
8455			});
8456
8457			croppie.bind({
8458			    url: image
8459			});
8460		}
8461	};
8462
8463	this.init = function()
8464	{
8465		createCroppie();
8466	};
8467
8468	var circleInput = document.createElement('input');
8469	circleInput.setAttribute('type', 'checkbox');
8470	circleInput.setAttribute('id', 'croppieCircle');
8471	circleInput.style.margin = '5px';
8472	div.appendChild(circleInput);
8473
8474	var circleLbl = document.createElement('label');
8475	circleLbl.setAttribute('for', 'croppieCircle');
8476	mxUtils.write(circleLbl, mxResources.get('circle'));
8477	div.appendChild(circleLbl);
8478
8479	var wrap, btnLeft, btnRight, iLeft, iRight;
8480
8481    wrap = document.createElement('div');
8482    btnLeft = document.createElement('button');
8483    btnRight = document.createElement('button');
8484
8485    wrap.appendChild(btnLeft);
8486    wrap.appendChild(btnRight);
8487
8488    iLeft = document.createElement('i');
8489    iRight = document.createElement('i');
8490    btnLeft.appendChild(iLeft);
8491    btnRight.appendChild(iRight);
8492
8493    wrap.className = 'cr-rotate-controls';
8494    wrap.style.float = 'right';
8495    wrap.style.position = 'inherit';
8496    btnLeft.className = 'cr-rotate-l';
8497    btnRight.className = 'cr-rotate-r';
8498
8499    div.appendChild(wrap);
8500
8501    btnLeft.addEventListener('click', function ()
8502    {
8503    	croppie.rotate(-90);
8504    });
8505
8506    btnRight.addEventListener('click', function ()
8507    {
8508    	croppie.rotate(90);
8509    });
8510
8511	mxEvent.addListener(circleInput, 'change', function()
8512	{
8513		createCroppie(this.checked);
8514	});
8515
8516	var cancelBtn = mxUtils.button(mxResources.get('cancel'), function()
8517	{
8518		editorUi.hideDialog();
8519	});
8520	cancelBtn.className = 'geBtn';
8521
8522	var applyBtn = mxUtils.button(mxResources.get('apply'), function()
8523	{
8524		croppie.result({type: 'base64', size: 'original'}).then(function(base64Img) {
8525			fn(base64Img);
8526			editorUi.hideDialog();
8527		});
8528	});
8529
8530	applyBtn.className = 'geBtn gePrimaryBtn';
8531
8532	var buttons = document.createElement('div');
8533	buttons.style.marginTop = '20px';
8534	buttons.style.textAlign = 'right';
8535
8536	if (editorUi.editor.cancelFirst)
8537	{
8538		buttons.appendChild(cancelBtn);
8539		buttons.appendChild(applyBtn);
8540	}
8541	else
8542	{
8543		buttons.appendChild(applyBtn);
8544		buttons.appendChild(cancelBtn);
8545	}
8546
8547	div.appendChild(buttons);
8548
8549	this.container = div;
8550};
8551
8552var EditGeometryDialog = function(editorUi, vertices)
8553{
8554	var graph = editorUi.editor.graph;
8555	var geo = (vertices.length == 1) ? graph.getCellGeometry(vertices[0]) : null;
8556	var div = document.createElement('div');
8557
8558	var table = document.createElement('table');
8559	var tbody = document.createElement('tbody');
8560	var row = document.createElement('tr');
8561	var left = document.createElement('td');
8562	var right = document.createElement('td');
8563	table.style.paddingLeft = '6px';
8564
8565	mxUtils.write(left, mxResources.get('relative') + ':');
8566
8567	var relInput = document.createElement('input');
8568	relInput.setAttribute('type', 'checkbox');
8569
8570	if (geo != null && geo.relative)
8571	{
8572		relInput.setAttribute('checked', 'checked');
8573		relInput.defaultChecked = true;
8574	}
8575
8576	this.init = function()
8577	{
8578		relInput.focus();
8579	};
8580
8581	right.appendChild(relInput);
8582
8583	row.appendChild(left);
8584	row.appendChild(right);
8585
8586	tbody.appendChild(row);
8587
8588	row = document.createElement('tr');
8589	left = document.createElement('td');
8590	right = document.createElement('td');
8591
8592	mxUtils.write(left, mxResources.get('left') + ':');
8593
8594	var xInput = document.createElement('input');
8595	xInput.setAttribute('type', 'text');
8596	xInput.style.width = '100px';
8597	xInput.value = (geo != null) ? geo.x : '';
8598
8599	right.appendChild(xInput);
8600
8601	row.appendChild(left);
8602	row.appendChild(right);
8603
8604	tbody.appendChild(row);
8605
8606	row = document.createElement('tr');
8607	left = document.createElement('td');
8608	right = document.createElement('td');
8609
8610	mxUtils.write(left, mxResources.get('top') + ':');
8611
8612	var yInput = document.createElement('input');
8613	yInput.setAttribute('type', 'text');
8614	yInput.style.width = '100px';
8615	yInput.value = (geo != null) ? geo.y : '';
8616
8617	right.appendChild(yInput);
8618
8619	row.appendChild(left);
8620	row.appendChild(right);
8621
8622	tbody.appendChild(row);
8623
8624	row = document.createElement('tr');
8625	left = document.createElement('td');
8626	right = document.createElement('td');
8627
8628	mxUtils.write(left, mxResources.get('dx') + ':');
8629
8630	var dxInput = document.createElement('input');
8631	dxInput.setAttribute('type', 'text');
8632	dxInput.style.width = '100px';
8633	dxInput.value = (geo != null && geo.offset != null) ? geo.offset.x : '';
8634
8635	right.appendChild(dxInput);
8636
8637	row.appendChild(left);
8638	row.appendChild(right);
8639
8640	tbody.appendChild(row);
8641
8642	row = document.createElement('tr');
8643	left = document.createElement('td');
8644	right = document.createElement('td');
8645
8646	mxUtils.write(left, mxResources.get('dy') + ':');
8647
8648	var dyInput = document.createElement('input');
8649	dyInput.setAttribute('type', 'text');
8650	dyInput.style.width = '100px';
8651	dyInput.value = (geo != null && geo.offset != null) ? geo.offset.y : '';
8652
8653	right.appendChild(dyInput);
8654
8655	row.appendChild(left);
8656	row.appendChild(right);
8657
8658	tbody.appendChild(row);
8659
8660	row = document.createElement('tr');
8661	left = document.createElement('td');
8662	right = document.createElement('td');
8663
8664	mxUtils.write(left, mxResources.get('width') + ':');
8665
8666	var wInput = document.createElement('input');
8667	wInput.setAttribute('type', 'text');
8668	wInput.style.width = '100px';
8669	wInput.value = (geo != null) ? geo.width : '';
8670
8671	right.appendChild(wInput);
8672
8673	row.appendChild(left);
8674	row.appendChild(right);
8675
8676	tbody.appendChild(row);
8677
8678	row = document.createElement('tr');
8679	left = document.createElement('td');
8680	right = document.createElement('td');
8681
8682	mxUtils.write(left, mxResources.get('height') + ':');
8683
8684	var hInput = document.createElement('input');
8685	hInput.setAttribute('type', 'text');
8686	hInput.style.width = '100px';
8687	hInput.value = (geo != null) ? geo.height : '';
8688
8689	right.appendChild(hInput);
8690
8691	row.appendChild(left);
8692	row.appendChild(right);
8693
8694	tbody.appendChild(row);
8695
8696	row = document.createElement('tr');
8697	left = document.createElement('td');
8698	right = document.createElement('td');
8699
8700	mxUtils.write(left, mxResources.get('rotation') + ':');
8701
8702	var rotInput = document.createElement('input');
8703	rotInput.setAttribute('type', 'text');
8704	rotInput.style.width = '100px';
8705	rotInput.value = (vertices.length == 1) ? mxUtils.getValue(graph.getCellStyle(vertices[0]),
8706			mxConstants.STYLE_ROTATION, 0) : '';
8707
8708	right.appendChild(rotInput);
8709
8710	row.appendChild(left);
8711	row.appendChild(right);
8712
8713	tbody.appendChild(row);
8714
8715	table.appendChild(tbody);
8716	div.appendChild(table);
8717
8718	var cancelBtn = mxUtils.button(mxResources.get('cancel'), function()
8719	{
8720		editorUi.hideDialog();
8721	});
8722
8723	cancelBtn.className = 'geBtn';
8724
8725	var applyBtn = mxUtils.button(mxResources.get('apply'), function()
8726	{
8727		editorUi.hideDialog();
8728
8729		graph.getModel().beginUpdate();
8730		try
8731		{
8732			for (var i = 0; i < vertices.length; i++)
8733			{
8734				var g = graph.getCellGeometry(vertices[i]);
8735
8736				if (g != null)
8737				{
8738					g = g.clone();
8739
8740					if (graph.isCellMovable(vertices[i]))
8741					{
8742						g.relative = relInput.checked;
8743
8744						if (mxUtils.trim(xInput.value).length > 0)
8745						{
8746							g.x = Number(xInput.value);
8747						}
8748
8749						if (mxUtils.trim(yInput.value).length > 0)
8750						{
8751							g.y = Number(yInput.value);
8752						}
8753
8754						if (mxUtils.trim(dxInput.value).length > 0)
8755						{
8756							if (g.offset == null)
8757							{
8758								g.offset = new mxPoint();
8759							}
8760
8761							g.offset.x = Number(dxInput.value);
8762						}
8763
8764						if (mxUtils.trim(dyInput.value).length > 0)
8765						{
8766							if (g.offset == null)
8767							{
8768								g.offset = new mxPoint();
8769							}
8770
8771							g.offset.y = Number(dyInput.value);
8772						}
8773					}
8774
8775					if (graph.isCellResizable(vertices[i]))
8776					{
8777						if (mxUtils.trim(wInput.value).length > 0)
8778						{
8779							g.width = Number(wInput.value);
8780						}
8781
8782						if (mxUtils.trim(hInput.value).length > 0)
8783						{
8784							g.height = Number(hInput.value);
8785						}
8786					}
8787
8788					graph.getModel().setGeometry(vertices[i], g);
8789				}
8790
8791				if (mxUtils.trim(rotInput.value).length > 0)
8792				{
8793					graph.setCellStyles(mxConstants.STYLE_ROTATION, Number(rotInput.value), [vertices[i]]);
8794				}
8795			}
8796		}
8797		finally
8798		{
8799			graph.getModel().endUpdate();
8800		}
8801	});
8802
8803	applyBtn.className = 'geBtn gePrimaryBtn';
8804
8805	mxEvent.addListener(div, 'keypress', function(e)
8806	{
8807		if (e.keyCode == 13)
8808		{
8809			applyBtn.click();
8810		}
8811	});
8812
8813	var buttons = document.createElement('div');
8814	buttons.style.marginTop = '20px';
8815	buttons.style.textAlign = 'right';
8816
8817	if (editorUi.editor.cancelFirst)
8818	{
8819		buttons.appendChild(cancelBtn);
8820		buttons.appendChild(applyBtn);
8821	}
8822	else
8823	{
8824		buttons.appendChild(applyBtn);
8825		buttons.appendChild(cancelBtn);
8826	}
8827
8828	div.appendChild(buttons);
8829
8830	this.container = div;
8831};
8832
8833/**
8834 * Constructs a new dialog for creating files from templates.
8835 */
8836var LibraryDialog = function(editorUi, name, library, initialImages, file, mode)
8837{
8838	var images = [];
8839	var graph = editorUi.editor.graph;
8840	var outer = document.createElement('div');
8841	outer.style.height = '100%';
8842
8843	var header = document.createElement('div');
8844	header.style.whiteSpace = 'nowrap';
8845	header.style.height = '40px';
8846	outer.appendChild(header);
8847
8848	mxUtils.write(header, mxResources.get('filename') + ':');
8849
8850	var nameValue = name;
8851
8852	if (nameValue == null)
8853	{
8854		nameValue = editorUi.defaultLibraryName + '.xml';
8855	}
8856
8857	var nameInput = document.createElement('input');
8858	nameInput.setAttribute('value', nameValue);
8859	nameInput.style.marginRight = '20px';
8860	nameInput.style.marginLeft = '10px';
8861	nameInput.style.width = '500px';
8862
8863	if (file != null && !file.isRenamable())
8864	{
8865		nameInput.setAttribute('disabled', 'true');
8866	}
8867
8868	this.init = function()
8869	{
8870		if (file == null || file.isRenamable())
8871		{
8872			nameInput.focus();
8873
8874			if (mxClient.IS_GC || mxClient.IS_FF || document.documentMode >= 5)
8875			{
8876				nameInput.select();
8877			}
8878			else
8879			{
8880				document.execCommand('selectAll', false, null);
8881			}
8882		}
8883	};
8884
8885	header.appendChild(nameInput);
8886
8887	var div = document.createElement('div');
8888	div.style.borderWidth = '1px 0px 1px 0px';
8889	div.style.borderColor = '#d3d3d3';
8890	div.style.borderStyle = 'solid';
8891	div.style.marginTop = '6px';
8892	div.style.overflow = 'auto';
8893	div.style.height = '340px';
8894	div.style.backgroundPosition = 'center center';
8895	div.style.backgroundRepeat = 'no-repeat';
8896
8897	if (images.length == 0 && Graph.fileSupport)
8898	{
8899		div.style.backgroundImage = 'url(\'' + IMAGE_PATH + '/droptarget.png\')';
8900	}
8901
8902	var bg = document.createElement('div');
8903	bg.style.position = 'absolute';
8904	bg.style.width = '640px';
8905	bg.style.top = '260px';
8906	bg.style.textAlign = 'center';
8907	bg.style.fontSize = '22px';
8908	bg.style.color = '#a0c3ff';
8909	mxUtils.write(bg, mxResources.get('dragImagesHere'));
8910	outer.appendChild(bg);
8911
8912	var entries = {};
8913	var ew = 100;
8914	var eh = 100;
8915
8916	var dragSourceIndex = null;
8917	var dropTargetIndex = null;
8918
8919	function getIndexForEvent(evt)
8920	{
8921		var dropTarget = document.elementFromPoint(evt.clientX, evt.clientY);
8922
8923		while (dropTarget != null && dropTarget.parentNode != div)
8924		{
8925			dropTarget = dropTarget.parentNode;
8926		}
8927
8928		var result = null;
8929
8930		if (dropTarget != null)
8931		{
8932			var tmp = div.firstChild;
8933			result = 0;
8934
8935			while (tmp != null && tmp != dropTarget)
8936			{
8937				tmp = tmp.nextSibling;
8938				result++;
8939			}
8940		}
8941
8942		return result;
8943	};
8944
8945	var stopEditing = null;
8946	var stopWrapper = function(evt)
8947	{
8948		var source = mxEvent.getSource(evt);
8949
8950		if (source.getAttribute('contentEditable') != 'true' && stopEditing != null)
8951		{
8952			stopEditing();
8953			stopEditing = null;
8954
8955			mxEvent.consume(evt);
8956		}
8957	};
8958
8959	mxEvent.addListener(div, 'mousedown', stopWrapper);
8960	mxEvent.addListener(div, 'pointerdown', stopWrapper);
8961	mxEvent.addListener(div, 'touchstart', stopWrapper);
8962
8963	// For converting image URLs
8964	var converter = new mxUrlConverter();
8965	var errorShowed = false;
8966
8967	function addButton(data, mimeType, x, y, w, h, img, aspect, title)
8968	{
8969		// Ignores duplicates
8970		try
8971		{
8972			editorUi.spinner.stop();
8973
8974			if (mimeType == null || mimeType.substring(0, 6) == 'image/')
8975			{
8976				if ((data == null && img != null) || entries[data] == null)
8977				{
8978					div.style.backgroundImage = '';
8979					bg.style.display = 'none';
8980
8981					var iw = w;
8982					var ih = h;
8983
8984					if (w > editorUi.maxImageSize || h > editorUi.maxImageSize)
8985					{
8986						var s = Math.min(1, Math.min(editorUi.maxImageSize / Math.max(1, w)),
8987							editorUi.maxImageSize / Math.max(1, h));
8988						w *= s;
8989						h *= s;
8990					}
8991
8992					if (iw > ih)
8993					{
8994						ih = Math.round(ih * ew / iw);
8995						iw = ew;
8996					}
8997					else
8998					{
8999						iw = Math.round(iw * eh / ih);
9000						ih = eh;
9001					}
9002
9003					var wrapper = document.createElement('div');
9004					wrapper.setAttribute('draggable', 'true');
9005					wrapper.style.display = 'inline-block';
9006					wrapper.style.position = 'relative';
9007					wrapper.style.padding = '0 12px';
9008					wrapper.style.cursor = 'move';
9009					mxUtils.setPrefixedStyle(wrapper.style, 'transition', 'transform .1s ease-in-out');
9010
9011					if (data != null)
9012					{
9013						var elt = document.createElement('img');
9014						elt.setAttribute('src', converter.convert(data));
9015						elt.style.width = iw + 'px';
9016						elt.style.height = ih + 'px';
9017						elt.style.margin = '10px';
9018
9019						elt.style.paddingBottom = Math.floor((eh - ih) / 2) + 'px';
9020						elt.style.paddingLeft = Math.floor((ew - iw) / 2) + 'px';
9021
9022						wrapper.appendChild(elt);
9023					}
9024					else if (img != null)
9025					{
9026						var cells = editorUi.stringToCells(Graph.decompress(img.xml));
9027
9028						if (cells.length > 0)
9029						{
9030							editorUi.sidebar.createThumb(cells, ew, eh, wrapper, null, true, false);
9031
9032							// Needs inline block on SVG for delete icon to appear on same line
9033							wrapper.firstChild.style.display = 'inline-block';
9034							wrapper.firstChild.style.cursor = '';
9035						}
9036					}
9037
9038					var rem = document.createElement('img');
9039					rem.setAttribute('src', Editor.closeBlackImage);
9040					rem.setAttribute('border', '0');
9041					rem.setAttribute('title', mxResources.get('delete'));
9042					rem.setAttribute('align', 'top');
9043					rem.style.paddingTop = '4px';
9044					rem.style.position = 'absolute';
9045					rem.style.marginLeft = '-12px';
9046					rem.style.zIndex = '1';
9047					rem.style.cursor = 'pointer';
9048
9049					// Blocks dragging of remove icon
9050					mxEvent.addListener(rem, 'dragstart', function(evt)
9051					{
9052						mxEvent.consume(evt);
9053					});
9054
9055					(function(wrapperDiv, dataParam, imgParam)
9056					{
9057						mxEvent.addListener(rem, 'click', function(evt)
9058						{
9059							entries[dataParam] = null;
9060
9061							for (var i = 0; i < images.length; i++)
9062							{
9063								if ((images[i].data != null && images[i].data == dataParam) ||
9064									(images[i].xml != null && imgParam != null &&
9065									images[i].xml == imgParam.xml))
9066								{
9067									images.splice(i, 1);
9068									break;
9069								}
9070							}
9071
9072							wrapper.parentNode.removeChild(wrapperDiv);
9073
9074							if (images.length == 0)
9075							{
9076								div.style.backgroundImage = 'url(\'' + IMAGE_PATH + '/droptarget.png\')';
9077								bg.style.display = '';
9078							}
9079
9080							mxEvent.consume(evt);
9081						});
9082						// Workaround for accidental select all
9083						mxEvent.addListener(rem, 'dblclick', function(evt)
9084						{
9085							mxEvent.consume(evt);
9086						});
9087					})(wrapper, data, img);
9088
9089					wrapper.appendChild(rem);
9090					wrapper.style.marginBottom = '30px';
9091
9092					var label = document.createElement('div');
9093					label.style.position = 'absolute';
9094					label.style.boxSizing = 'border-box';
9095					label.style.bottom = '-18px';
9096					label.style.left = '10px';
9097					label.style.right = '10px';
9098					label.style.backgroundColor = Editor.isDarkMode() ? Editor.darkColor : '#ffffff';
9099					label.style.overflow = 'hidden';
9100					label.style.textAlign = 'center';
9101
9102					var entry = null;
9103
9104					if (data != null)
9105					{
9106						entry = {data: data, w: w, h: h, title: title};
9107
9108						if (aspect != null)
9109						{
9110							entry.aspect = aspect;
9111						}
9112
9113						entries[data] = elt;
9114						images.push(entry);
9115					}
9116					else if (img != null)
9117					{
9118						img.aspect = 'fixed';
9119						images.push(img);
9120						entry = img;
9121					}
9122
9123					function updateLabel()
9124					{
9125						label.innerHTML = '';
9126						label.style.cursor = 'pointer';
9127						label.style.whiteSpace = 'nowrap';
9128						label.style.textOverflow = 'ellipsis';
9129						mxUtils.write(label, (entry.title != null && entry.title.length > 0) ?
9130							entry.title : mxResources.get('untitled'));
9131
9132						if (entry.title == null || entry.title.length == 0)
9133						{
9134							label.style.color = '#d0d0d0';
9135						}
9136						else
9137						{
9138							label.style.color = '';
9139						}
9140					};
9141
9142					mxEvent.addListener(label, 'keydown', function(evt)
9143					{
9144						if (evt.keyCode == 13 && stopEditing != null)
9145						{
9146							stopEditing();
9147							stopEditing = null;
9148
9149							mxEvent.consume(evt);
9150						}
9151					});
9152
9153					updateLabel();
9154					wrapper.appendChild(label);
9155
9156					// Blocks dragging of label
9157					mxEvent.addListener(label, 'mousedown', function(evt)
9158					{
9159						if (label.getAttribute('contentEditable') != 'true')
9160						{
9161							mxEvent.consume(evt);
9162						}
9163					});
9164
9165					var startEditing = function(evt)
9166					{
9167						// Workaround for various issues in IE
9168						if (!mxClient.IS_IOS && !mxClient.IS_FF &&
9169							(document.documentMode == null || document.documentMode > 9))
9170						{
9171							if (label.getAttribute('contentEditable') != 'true')
9172							{
9173								if (stopEditing != null)
9174								{
9175									stopEditing();
9176									stopEditing = null;
9177								}
9178
9179								if (entry.title == null || entry.title.length == 0)
9180								{
9181									label.innerHTML = '';
9182								}
9183
9184								label.style.textOverflow = '';
9185								label.style.whiteSpace = '';
9186								label.style.cursor = 'text';
9187								label.style.color = '';
9188								label.setAttribute('contentEditable', 'true');
9189								mxUtils.setPrefixedStyle(label.style, 'user-select', 'text');
9190								label.focus();
9191								document.execCommand('selectAll', false, null);
9192
9193								stopEditing = function()
9194								{
9195									label.removeAttribute('contentEditable');
9196									label.style.cursor = 'pointer';
9197									entry.title = label.innerHTML;
9198									updateLabel();
9199								}
9200
9201								mxEvent.consume(evt);
9202							}
9203						}
9204						else
9205						{
9206							var dlg = new FilenameDialog(editorUi, entry.title || '', mxResources.get('ok'), function(newTitle)
9207							{
9208								if (newTitle != null)
9209								{
9210									entry.title = newTitle;
9211									updateLabel();
9212								}
9213							}, mxResources.get('enterValue'));
9214							editorUi.showDialog(dlg.container, 300, 80, true, true);
9215							dlg.init();
9216
9217							mxEvent.consume(evt);
9218						}
9219					};
9220
9221					mxEvent.addListener(label, 'click', startEditing);
9222					mxEvent.addListener(wrapper, 'dblclick', startEditing);
9223
9224					div.appendChild(wrapper);
9225
9226					mxEvent.addListener(wrapper, 'dragstart', function(evt)
9227					{
9228						if (data == null && img != null)
9229						{
9230							rem.style.visibility = 'hidden';
9231							label.style.visibility = 'hidden';
9232						}
9233
9234						// Workaround for no DnD on DIV in FF
9235						if (mxClient.IS_FF && img.xml != null)
9236						{
9237							evt.dataTransfer.setData('Text', img.xml);
9238						}
9239
9240						dragSourceIndex = getIndexForEvent(evt);
9241
9242						// Workaround for missing drag preview in Google Chrome
9243						if (mxClient.IS_GC)
9244						{
9245							wrapper.style.opacity = '0.9';
9246						}
9247
9248						window.setTimeout(function()
9249						{
9250							mxUtils.setPrefixedStyle(wrapper.style, 'transform', 'scale(0.5,0.5)');
9251							mxUtils.setOpacity(wrapper, 30);
9252							rem.style.visibility = '';
9253							label.style.visibility = '';
9254						}, 0);
9255					});
9256
9257					mxEvent.addListener(wrapper, 'dragend', function(evt)
9258					{
9259						if (rem.style.visibility == 'hidden')
9260						{
9261							rem.style.visibility = '';
9262							label.style.visibility = '';
9263						}
9264
9265						dragSourceIndex = null;
9266						mxUtils.setOpacity(wrapper, 100);
9267						mxUtils.setPrefixedStyle(wrapper.style, 'transform', null);
9268					});
9269				}
9270				else if (!errorShowed)
9271				{
9272					errorShowed = true;
9273					editorUi.handleError({message: mxResources.get('fileExists')})
9274				}
9275			}
9276			else
9277			{
9278				var done = false;
9279
9280				try
9281				{
9282					var doc = mxUtils.parseXml(data);
9283
9284					if (doc.documentElement.nodeName == 'mxlibrary')
9285					{
9286						var temp = JSON.parse(mxUtils.getTextContent(doc.documentElement));
9287
9288						if (temp != null && temp.length > 0)
9289						{
9290							for (var i = 0; i < temp.length; i++)
9291							{
9292								if (temp[i].xml != null)
9293								{
9294									addButton(null, null, 0, 0, 0, 0, temp[i]);
9295								}
9296								else
9297								{
9298									addButton(temp[i].data, null, 0, 0, temp[i].w, temp[i].h, null, 'fixed', temp[i].title);
9299								}
9300							}
9301						}
9302
9303						done = true;
9304					}
9305					else if (doc.documentElement.nodeName == 'mxfile')
9306					{
9307						var pages = doc.documentElement.getElementsByTagName('diagram');
9308
9309						for (var i = 0; i < pages.length; i++)
9310						{
9311							var temp = mxUtils.getTextContent(pages[i]);
9312							var cells = editorUi.stringToCells(Graph.decompress(temp));
9313							var size = editorUi.editor.graph.getBoundingBoxFromGeometry(cells);
9314							addButton(null, null, 0, 0, 0, 0, {xml: temp, w: size.width, h: size.height});
9315						}
9316
9317						done = true;
9318					}
9319				}
9320				catch (e)
9321				{
9322					// ignore
9323				}
9324
9325				if (!done)
9326				{
9327					editorUi.spinner.stop();
9328					editorUi.handleError({message: mxResources.get('errorLoadingFile')})
9329				}
9330			}
9331		}
9332		catch (e)
9333		{
9334			// ignore
9335		}
9336
9337		return null;
9338	};
9339
9340	if (initialImages != null)
9341	{
9342		for (var i = 0; i < initialImages.length; i++)
9343		{
9344			var img = initialImages[i];
9345			addButton(img.data, null, 0, 0, img.w, img.h, img, img.aspect, img.title);
9346		}
9347	}
9348
9349	// Setup the dnd listeners
9350	mxEvent.addListener(div, 'dragleave', function(evt)
9351	{
9352		bg.style.cursor = '';
9353		var source = mxEvent.getSource(evt);
9354
9355		while (source != null)
9356		{
9357			if (source == div || source == bg)
9358			{
9359				evt.stopPropagation();
9360				evt.preventDefault();
9361				break;
9362			}
9363
9364			source = source.parentNode;
9365		}
9366	});
9367
9368	function dragOver(evt)
9369	{
9370		evt.dataTransfer.dropEffect = (dragSourceIndex != null) ? 'move' : 'copy';
9371		evt.stopPropagation();
9372		evt.preventDefault();
9373	};
9374
9375	var createImportHandler = function(evt)
9376	{
9377		return function(data, mimeType, x, y, w, h, img, doneFn, file)
9378		{
9379			if (file != null && (/(\.v(dx|sdx?))($|\?)/i.test(file.name) || /(\.vs(x|sx?))($|\?)/i.test(file.name)))
9380			{
9381				editorUi.importVisio(file, mxUtils.bind(this, function(xml)
9382				{
9383		    		addButton(xml, mimeType, x, y, w, h, img, 'fixed', (mxEvent.isAltDown(evt)) ?
9384		    			null : img.substring(0, img.lastIndexOf('.')).replace(/_/g, ' '));
9385				}));
9386			}
9387			else if (file != null && !editorUi.isOffline() && new XMLHttpRequest().upload && editorUi.isRemoteFileFormat(data, file.name))
9388			{
9389				editorUi.parseFile(file, mxUtils.bind(this, function(xhr)
9390				{
9391					if (xhr.readyState == 4)
9392					{
9393						editorUi.spinner.stop();
9394
9395	    				if (xhr.status >= 200 && xhr.status <= 299)
9396						{
9397							var xml = xhr.responseText;
9398							addButton(xml, mimeType, x, y, w, h, img, 'fixed', (mxEvent.isAltDown(evt)) ?
9399				    			null : img.substring(0, img.lastIndexOf('.')).replace(/_/g, ' '));
9400							div.scrollTop = div.scrollHeight;
9401						}
9402					}
9403				}));
9404			}
9405			else
9406			{
9407				addButton(data, mimeType, x, y, w, h, img, 'fixed', (mxEvent.isAltDown(evt)) ?
9408					null : img.substring(0, img.lastIndexOf('.')).replace(/_/g, ' '));
9409				div.scrollTop = div.scrollHeight;
9410			}
9411		};
9412	};
9413
9414	function dropHandler(evt)
9415	{
9416		evt.stopPropagation();
9417		evt.preventDefault();
9418		errorShowed = false;
9419		dropTargetIndex = getIndexForEvent(evt);
9420
9421		if (dragSourceIndex != null)
9422		{
9423	    	if (dropTargetIndex != null && dropTargetIndex < div.children.length)
9424	    	{
9425				images.splice((dropTargetIndex > dragSourceIndex) ? dropTargetIndex - 1 : dropTargetIndex,
9426					0, images.splice(dragSourceIndex, 1)[0]);
9427				div.insertBefore(div.children[dragSourceIndex], div.children[dropTargetIndex]);
9428			}
9429			else
9430			{
9431				images.push(images.splice(dragSourceIndex, 1)[0]);
9432				div.appendChild(div.children[dragSourceIndex]);
9433			}
9434		}
9435		else if (evt.dataTransfer.files.length > 0)
9436		{
9437			editorUi.importFiles(evt.dataTransfer.files, 0, 0, editorUi.maxImageSize, createImportHandler(evt));
9438		}
9439		else if (mxUtils.indexOf(evt.dataTransfer.types, 'text/uri-list') >= 0)
9440		{
9441			var uri = decodeURIComponent(evt.dataTransfer.getData('text/uri-list'));
9442
9443			if (/(\.jpg)($|\?)/i.test(uri) || /(\.png)($|\?)/i.test(uri) ||
9444				/(\.gif)($|\?)/i.test(uri) || /(\.svg)($|\?)/i.test(uri))
9445			{
9446				editorUi.loadImage(uri, function(img)
9447				{
9448					addButton(uri, null, 0, 0, img.width, img.height);
9449					div.scrollTop = div.scrollHeight;
9450				});
9451			}
9452		}
9453
9454		evt.stopPropagation();
9455		evt.preventDefault();
9456	};
9457
9458	mxEvent.addListener(div, 'dragover', dragOver);
9459	mxEvent.addListener(div, 'drop', dropHandler);
9460	mxEvent.addListener(bg, 'dragover', dragOver);
9461	mxEvent.addListener(bg, 'drop', dropHandler);
9462
9463	outer.appendChild(div);
9464
9465	var btns = document.createElement('div');
9466	btns.style.textAlign = 'right';
9467	btns.style.marginTop = '20px';
9468
9469	var cancelBtn = mxUtils.button(mxResources.get('cancel'), function()
9470	{
9471		editorUi.hideDialog(true);
9472	});
9473
9474	cancelBtn.setAttribute('id', 'btnCancel');
9475	cancelBtn.className = 'geBtn';
9476
9477	if (editorUi.editor.cancelFirst)
9478	{
9479		btns.appendChild(cancelBtn);
9480	}
9481
9482	if (editorUi.getServiceName() == 'draw.io' && file != null &&
9483		// Limits button to ibraries which are known to have public URLs
9484		(file.constructor == DriveLibrary || file.constructor == GitHubLibrary))
9485	{
9486		var btn = mxUtils.button(mxResources.get('link'), function()
9487		{
9488			if (editorUi.spinner.spin(document.body, mxResources.get('loading')))
9489			{
9490		    	file.getPublicUrl(function(url)
9491				{
9492					editorUi.spinner.stop();
9493
9494					if (url != null)
9495					{
9496						var search = editorUi.getSearch(['create', 'title', 'mode', 'url', 'drive', 'splash', 'state', 'clibs', 'ui']);
9497						search += ((search.length == 0) ? '?' : '&') + 'splash=0&clibs=U' + encodeURIComponent(url);
9498						var dlg = new EmbedDialog(editorUi, window.location.protocol + '//' +
9499							window.location.host + '/' + search, null, null, null, null,
9500							'Check out the library I made using @drawio');
9501						editorUi.showDialog(dlg.container, 450, 240, true);
9502						dlg.init();
9503					}
9504					else if (file.constructor == DriveLibrary)
9505					{
9506					    editorUi.showError(mxResources.get('error'), mxResources.get('diagramIsNotPublic'),
9507							mxResources.get('share'), mxUtils.bind(this, function()
9508							{
9509								editorUi.drive.showPermissions(file.getId());
9510							}), null, mxResources.get('ok'), mxUtils.bind(this, function()
9511							{
9512								// Hides dialog
9513							})
9514						);
9515					}
9516					else
9517					{
9518						editorUi.handleError({message: mxResources.get('diagramIsNotPublic')});
9519					}
9520				});
9521			}
9522		});
9523
9524		btn.className = 'geBtn';
9525		btns.appendChild(btn);
9526	}
9527
9528	var btn = mxUtils.button(mxResources.get('export'), function()
9529	{
9530    	var data = editorUi.createLibraryDataFromImages(images);
9531    	var filename = nameInput.value;
9532
9533		if (!/(\.xml)$/i.test(filename))
9534		{
9535			filename += '.xml';
9536		}
9537
9538    	if (editorUi.isLocalFileSave())
9539    	{
9540    		editorUi.saveLocalFile(data, filename, 'text/xml', null, null, true, null, 'xml');
9541    	}
9542    	else
9543    	{
9544    		new mxXmlRequest(SAVE_URL, 'filename=' + encodeURIComponent(filename) +
9545    			'&format=xml&xml=' + encodeURIComponent(data)).simulate(document, '_blank');
9546    	}
9547	});
9548
9549	btn.setAttribute('id', 'btnDownload');
9550	btn.className = 'geBtn';
9551	btns.appendChild(btn);
9552
9553	if (Graph.fileSupport)
9554	{
9555		if (editorUi.libDlgFileInputElt == null)
9556		{
9557			var fileInput = document.createElement('input');
9558			fileInput.setAttribute('multiple', 'multiple');
9559			fileInput.setAttribute('type', 'file');
9560
9561			mxEvent.addListener(fileInput, 'change', function(evt)
9562			{
9563		    	errorShowed = false;
9564
9565		    	editorUi.importFiles(fileInput.files, 0, 0, editorUi.maxImageSize, function(data, mimeType, x, y, w, h, img, doneFn, file)
9566		    	{
9567					if (fileInput.files != null)
9568					{
9569			    		createImportHandler(evt)(data, mimeType, x, y, w, h, img, doneFn, file);
9570
9571			    		// Resets input to force change event for same file (type reset required for IE)
9572			    		fileInput.type = '';
9573			    		fileInput.type = 'file';
9574			    		fileInput.value = '';
9575					}
9576		    	});
9577
9578				div.scrollTop = div.scrollHeight;
9579			});
9580
9581			fileInput.style.display = 'none';
9582			document.body.appendChild(fileInput);
9583			editorUi.libDlgFileInputElt = fileInput;
9584		}
9585
9586		var btn = mxUtils.button(mxResources.get('import'), function()
9587		{
9588			if (stopEditing != null)
9589			{
9590				stopEditing();
9591				stopEditing = null;
9592			}
9593
9594			editorUi.libDlgFileInputElt.click();
9595		});
9596		btn.setAttribute('id', 'btnAddImage');
9597		btn.className = 'geBtn';
9598
9599		btns.appendChild(btn);
9600	}
9601
9602	var btn = mxUtils.button(mxResources.get('addImages'), function()
9603	{
9604		if (stopEditing != null)
9605		{
9606			stopEditing();
9607			stopEditing = null;
9608		}
9609
9610		editorUi.showImageDialog(mxResources.get('addImageUrl'), '', function(url, w, h)
9611		{
9612			errorShowed = false;
9613
9614			if (url != null)
9615			{
9616				// Image dialog returns modified data URLs which
9617				// must be converted back to real data URL
9618				if (url.substring(0, 11) == 'data:image/')
9619				{
9620					var comma = url.indexOf(',');
9621
9622					if (comma > 0)
9623					{
9624						url = url.substring(0, comma) + ';base64,' + url.substring(comma + 1);
9625					}
9626				}
9627
9628				addButton(url, null, 0, 0, w, h);
9629				div.scrollTop = div.scrollHeight;
9630			}
9631		});
9632	});
9633
9634	btn.setAttribute('id', 'btnAddImageUrl');
9635	btn.className = 'geBtn';
9636	btns.appendChild(btn);
9637
9638	// Indirection for overriding
9639	this.saveBtnClickHandler = function(name, images, file, mode)
9640	{
9641		editorUi.saveLibrary(name, images, file, mode);
9642	};
9643
9644	var btn = mxUtils.button(mxResources.get('save'),mxUtils.bind(this, function()
9645	{
9646		if (stopEditing != null)
9647		{
9648			stopEditing();
9649			stopEditing = null;
9650		}
9651
9652		this.saveBtnClickHandler(nameInput.value, images, file, mode);
9653	}));
9654
9655	btn.setAttribute('id', 'btnSave');
9656	btn.className = 'geBtn gePrimaryBtn';
9657	btns.appendChild(btn);
9658
9659	if (!editorUi.editor.cancelFirst)
9660	{
9661		btns.appendChild(cancelBtn);
9662	}
9663
9664	outer.appendChild(btns);
9665
9666	this.container = outer;
9667};
9668
9669/**
9670 * Constructs a new textarea dialog.
9671 */
9672var EditShapeDialog = function(editorUi, cell, title, w, h)
9673{
9674	w = (w != null) ? w : 300;
9675	h = (h != null) ? h : 120;
9676	var row, td;
9677
9678	var table = document.createElement('table');
9679	var tbody = document.createElement('tbody');
9680	table.style.cellPadding = '4px';
9681
9682	row = document.createElement('tr');
9683
9684	td = document.createElement('td');
9685	td.setAttribute('colspan', '2');
9686	td.style.fontSize = '10pt';
9687	mxUtils.write(td, title);
9688
9689	row.appendChild(td);
9690	tbody.appendChild(row);
9691
9692	row = document.createElement('tr');
9693	td = document.createElement('td');
9694
9695	var nameInput = document.createElement('textarea');
9696	nameInput.style.outline = 'none';
9697	nameInput.style.resize = 'none';
9698	nameInput.style.width = (w - 200) + 'px';
9699	nameInput.style.height = h + 'px';
9700
9701	this.textarea = nameInput;
9702
9703	this.init = function()
9704	{
9705		nameInput.focus();
9706		nameInput.scrollTop = 0;
9707	};
9708
9709	td.appendChild(nameInput);
9710	row.appendChild(td);
9711
9712	td = document.createElement('td');
9713
9714	var container = document.createElement('div');
9715	container.style.position = 'relative';
9716	container.style.border = '1px solid gray';
9717	container.style.top = '6px';
9718	container.style.width = '200px';
9719	container.style.height = (h + 4) + 'px';
9720	container.style.overflow = 'hidden';
9721	container.style.marginBottom = '16px';
9722	mxEvent.disableContextMenu(container);
9723	td.appendChild(container);
9724
9725	var graph = new Graph(container);
9726	graph.setEnabled(false);
9727
9728	var clone = editorUi.editor.graph.cloneCell(cell);
9729	graph.addCells([clone]);
9730
9731	var state = graph.view.getState(clone);
9732	var stencil = '';
9733
9734	if (state.shape != null && state.shape.stencil != null)
9735	{
9736		stencil = mxUtils.getPrettyXml(state.shape.stencil.desc);
9737	}
9738
9739	mxUtils.write(nameInput, stencil || '');
9740
9741	var b = graph.getGraphBounds();
9742	var ns = Math.min((200 - 40) / b.width, (h - 40) / b.height);
9743	graph.view.scaleAndTranslate(ns, 20 / ns - b.x, 20 / ns - b.y);
9744
9745	row.appendChild(td);
9746	tbody.appendChild(row);
9747
9748	row = document.createElement('tr');
9749	td = document.createElement('td');
9750	td.setAttribute('colspan', '2');
9751	td.style.paddingTop = '2px';
9752	td.style.whiteSpace = 'nowrap';
9753	td.setAttribute('align', 'right');
9754
9755	if (!editorUi.isOffline())
9756	{
9757		var helpBtn = mxUtils.button(mxResources.get('help'), function()
9758		{
9759			editorUi.openLink('https://www.diagrams.net/doc/faq/shape-complex-create-edit');
9760		});
9761
9762		helpBtn.className = 'geBtn';
9763		td.appendChild(helpBtn);
9764	}
9765
9766	var cancelBtn = mxUtils.button(mxResources.get('cancel'), function()
9767	{
9768		editorUi.hideDialog();
9769	});
9770	cancelBtn.className = 'geBtn';
9771
9772	if (editorUi.editor.cancelFirst)
9773	{
9774		td.appendChild(cancelBtn);
9775	}
9776
9777	var updateShape = function(targetGraph, targetCell, hide)
9778	{
9779		var newValue = nameInput.value;
9780
9781		// Checks if XML has changed (getPrettyXml "normalizes" DOM)
9782		var doc = mxUtils.parseXml(newValue);
9783		newValue = mxUtils.getPrettyXml(doc.documentElement);
9784
9785		// Checks for validation errors
9786		// LATER: Validate against XSD
9787		var errors = doc.documentElement.getElementsByTagName('parsererror');
9788
9789		if (errors != null && errors.length > 0)
9790		{
9791			editorUi.showError(mxResources.get('error'), mxResources.get('containsValidationErrors'), mxResources.get('ok'));
9792		}
9793		else
9794		{
9795			if (hide)
9796			{
9797				editorUi.hideDialog();
9798			}
9799
9800			var isNew = !targetGraph.model.contains(targetCell);
9801
9802			if (!hide || isNew || newValue != stencil)
9803			{
9804				// Transform XML value to be used in cell style
9805				newValue = Graph.compress(newValue);
9806
9807				targetGraph.getModel().beginUpdate();
9808				try
9809				{
9810					// Inserts cell if required
9811					if (isNew)
9812					{
9813						var pt = editorUi.editor.graph.getFreeInsertPoint();
9814						targetCell.geometry.x = pt.x;
9815						targetCell.geometry.y = pt.y;
9816						targetGraph.addCell(targetCell)
9817					}
9818
9819					targetGraph.setCellStyles(mxConstants.STYLE_SHAPE, 'stencil(' + newValue + ')', [targetCell]);
9820				}
9821				catch (e)
9822				{
9823					throw e;
9824				}
9825				finally
9826				{
9827					// Updates the display
9828					targetGraph.getModel().endUpdate();
9829				}
9830
9831				// Updates selection after stencil was created for rendering
9832				if (isNew)
9833				{
9834					targetGraph.setSelectionCell(targetCell);
9835					targetGraph.scrollCellToVisible(targetCell);
9836				}
9837			}
9838		}
9839	};
9840
9841	var previewBtn = mxUtils.button(mxResources.get('preview'), function()
9842	{
9843		updateShape(graph, clone, false);
9844	});
9845
9846	previewBtn.className = 'geBtn';
9847	td.appendChild(previewBtn);
9848
9849	var applyBtn = mxUtils.button(mxResources.get('apply'), function()
9850	{
9851		updateShape(editorUi.editor.graph, cell, true);
9852	});
9853
9854	applyBtn.className = 'geBtn gePrimaryBtn';
9855	td.appendChild(applyBtn);
9856
9857	if (!editorUi.editor.cancelFirst)
9858	{
9859		td.appendChild(cancelBtn);
9860	}
9861
9862	row.appendChild(td);
9863	tbody.appendChild(row);
9864	table.appendChild(tbody);
9865	this.container = table;
9866};
9867
9868var CustomDialog = function(editorUi, content, okFn, cancelFn, okButtonText, helpLink, buttonsContent, hideCancel, cancelButtonText, hideAfterOKFn)
9869{
9870	var div = document.createElement('div');
9871	div.appendChild(content);
9872
9873	var btns = document.createElement('div');
9874	btns.style.marginTop = '30px';
9875	btns.style.textAlign = 'center';
9876
9877	if (buttonsContent != null)
9878	{
9879		btns.appendChild(buttonsContent);
9880	}
9881
9882	if (!editorUi.isOffline() && helpLink != null)
9883	{
9884		var helpBtn = mxUtils.button(mxResources.get('help'), function()
9885		{
9886			editorUi.openLink(helpLink);
9887		});
9888
9889		helpBtn.className = 'geBtn';
9890		btns.appendChild(helpBtn);
9891	}
9892
9893	var cancelBtn = mxUtils.button(cancelButtonText || mxResources.get('cancel'), function()
9894	{
9895		editorUi.hideDialog();
9896
9897		if (cancelFn != null)
9898		{
9899			cancelFn();
9900		}
9901	});
9902
9903	cancelBtn.className = 'geBtn';
9904
9905	if (hideCancel)
9906	{
9907		cancelBtn.style.display = 'none';
9908	}
9909
9910	if (editorUi.editor.cancelFirst)
9911	{
9912		btns.appendChild(cancelBtn);
9913	}
9914
9915	var okBtn = mxUtils.button(okButtonText || mxResources.get('ok'), mxUtils.bind(this, function()
9916	{
9917		if (!hideAfterOKFn)
9918		{
9919			editorUi.hideDialog(null, null, this.container);
9920		}
9921
9922		if (okFn != null)
9923		{
9924			var okRet = okFn();
9925
9926			if (typeof okRet === 'string')
9927			{
9928				editorUi.showError(mxResources.get('error'), okRet);
9929				return;
9930			}
9931		}
9932
9933		if (hideAfterOKFn)
9934		{
9935			editorUi.hideDialog(null, null, this.container);
9936		}
9937	}));
9938	btns.appendChild(okBtn);
9939
9940	okBtn.className = 'geBtn gePrimaryBtn';
9941
9942	if (!editorUi.editor.cancelFirst)
9943	{
9944		btns.appendChild(cancelBtn);
9945	}
9946
9947	div.appendChild(btns);
9948
9949	this.cancelBtn = cancelBtn;
9950	this.okButton = okBtn;
9951	this.container = div;
9952};
9953
9954//TODO Many of the code is the same as NewDialog. Needs merging
9955var TemplatesDialog = function(editorUi, callback, cancelCallback,
9956		templateFile, newDiagramCatsFile, username, recentDocsCallback, searchDocsCallback,
9957		loadExtDoc, linkToDiagramCallback, customTempCallback, withOpen, withLink, withoutType, noDlgHide)
9958{
9959	var dialogSkeleton =
9960	'<div class="geTempDlgHeader">' +
9961		'<img src="/images/draw.io-logo.svg" class="geTempDlgHeaderLogo">' +
9962		'<input type="search" class="geTempDlgSearchBox" ' + (searchDocsCallback? '' : 'style="display: none"')
9963			+ ' placeholder="'+ mxResources.get('search') +'">' +
9964	'</div>' +
9965	'<div class="geTemplatesList" style="display: none">' +
9966		'<div class="geTempDlgBack">&lt; '+ mxResources.get('back') + '</div>' +
9967		'<div class="geTempDlgHLine"></div>' +
9968		'<div class="geTemplatesLbl">'+ mxResources.get('templates') + '</div>' +
9969	'</div>' +
9970	'<div class="geTempDlgContent" style="width: 100%">' +
9971		'<div class="geTempDlgNewDiagramCat">' +
9972			'<div class="geTempDlgNewDiagramCatLbl">'+ mxResources.get('newDiagram') +'</div>' +
9973			'<div class="geTempDlgNewDiagramCatList">' +
9974			'</div>' +
9975			'<div class="geTempDlgNewDiagramCatFooter">' +
9976				'<div class="geTempDlgShowAllBtn">'+ mxResources.get('showMore') +'</div>' +
9977			'</div>' +
9978		'</div>' +
9979		'<div class="geTempDlgDiagramsList">' +
9980			'<div class="geTempDlgDiagramsListHeader">' +
9981				'<div class="geTempDlgDiagramsListTitle"></div>' +
9982				'<div class="geTempDlgDiagramsListBtns">' +
9983					'<div class="geTempDlgRadioBtn geTempDlgRadioBtnLarge" data-id="myDiagramsBtn">' +
9984						'<img src="/images/my-diagrams.svg" class="geTempDlgMyDiagramsBtnImg"> <span>'+ mxResources.get('myDiagrams') + '</span>' +
9985					'</div><div class="geTempDlgRadioBtn geTempDlgRadioBtnLarge geTempDlgRadioBtnActive" data-id="allDiagramsBtn">' +
9986						'<img src="/images/all-diagrams-sel.svg" class="geTempDlgAllDiagramsBtnImg"> <span>'+ mxResources.get('allDiagrams') + '</span>' +
9987					'</div><div class="geTempDlgSpacer"> </div><div class="geTempDlgRadioBtn geTempDlgRadioBtnSmall geTempDlgRadioBtnActive" data-id="tilesBtn">' +
9988						'<img src="/images/tiles-sel.svg" class="geTempDlgTilesBtnImg">' +
9989					'</div><div class="geTempDlgRadioBtn geTempDlgRadioBtnSmall" data-id="listBtn">' +
9990						'<img src="/images/list.svg" class="geTempDlgListBtnImg">' +
9991					'</div>' +
9992				'</div>' +
9993			'</div>' +
9994			'<div class="geTempDlgDiagramsTiles">' +
9995			'</div>'+
9996		'</div>' +
9997	'</div>' +
9998	'<br style="clear:both;"/>' +
9999	'<div class="geTempDlgFooter">' +
10000		'<div class="geTempDlgErrMsg"></div>' +
10001		(withLink?
10002			'<span class="geTempDlgLinkToDiagram geTempDlgLinkToDiagramHint">' + mxResources.get('linkToDiagramHint') + '</span>' +
10003			'<button class="geTempDlgLinkToDiagram geTempDlgLinkToDiagramBtn">'+ mxResources.get('linkToDiagram') + '</button>' :
10004			''
10005		) +
10006		(withOpen? '<div class="geTempDlgOpenBtn">'+ mxResources.get('open') + '</div>' : '') +
10007		'<div class="geTempDlgCreateBtn">'+ mxResources.get('create') + '</div>' +
10008		'<div class="geTempDlgCancelBtn">'+ mxResources.get('cancel') + '</div>' +
10009	'</div>';
10010
10011	var dlg = this;
10012	var dlgDiv = document.createElement('div');
10013	dlgDiv.innerHTML = dialogSkeleton;
10014	dlgDiv.className = "geTemplateDlg";
10015	this.container = dlgDiv;
10016	templateFile = (templateFile != null) ? templateFile : (TEMPLATE_PATH + '/index.xml');
10017	newDiagramCatsFile = (newDiagramCatsFile != null) ? newDiagramCatsFile : NEW_DIAGRAM_CATS_PATH + '/index.xml';
10018	var callInitiated = false;
10019	var cancelPendingCall = false;
10020	var currentEntry = null, lastEntry = null;
10021	var currentItem = null;
10022	var currentItemInfo = null;
10023	var showingAll = false;
10024	var isGetAll = true;
10025	var showAsList = false;
10026	var curDiagList = [], curSearchImportCats = null;
10027	var lastSearchStr, lastGetAll;
10028	var categorySelected = true;
10029	var inTempScreen = false;
10030	var showAllBtn = dlgDiv.querySelector(".geTempDlgShowAllBtn");
10031	var diagramsTiles = dlgDiv.querySelector('.geTempDlgDiagramsTiles');
10032	var diagramsListTitle = dlgDiv.querySelector('.geTempDlgDiagramsListTitle');
10033	var diagramsListBtns = dlgDiv.querySelector('.geTempDlgDiagramsListBtns');
10034	var tempDlgContent = dlgDiv.querySelector('.geTempDlgContent');
10035	var diagramsList = dlgDiv.querySelector('.geTempDlgDiagramsList');
10036	var newDiagramCat = dlgDiv.querySelector('.geTempDlgNewDiagramCat');
10037	var newDiagramCatList = dlgDiv.querySelector(".geTempDlgNewDiagramCatList");
10038	var createBtn = dlgDiv.querySelector('.geTempDlgCreateBtn');
10039	var openBtn = dlgDiv.querySelector('.geTempDlgOpenBtn');
10040	var searchInout = dlgDiv.querySelector('.geTempDlgSearchBox');
10041	var errMsg = dlgDiv.querySelector('.geTempDlgErrMsg');
10042	var spinner = new Spinner({
10043		lines: 12, // The number of lines to draw
10044		length: 10, // The length of each line
10045		width: 5, // The line thickness
10046		radius: 10, // The radius of the inner circle
10047		rotate: 0, // The rotation offset
10048		color: '#000', // #rgb or #rrggbb
10049		speed: 1.5, // Rounds per second
10050		trail: 60, // Afterglow percentage
10051		shadow: false, // Whether to render a shadow
10052		hwaccel: false, // Whether to use hardware acceleration
10053		top: '50px',
10054		zIndex: 2e9 // The z-index (defaults to 2000000000)
10055	});
10056
10057	function showError(msg)
10058	{
10059		errMsg.innerHTML = mxUtils.htmlEntities(msg);
10060		errMsg.style.display = 'block';
10061
10062		setTimeout(function()
10063		{
10064			errMsg.style.display = 'none';
10065		}, 4000);
10066	};
10067
10068	function deselectTempCat()
10069	{
10070		if (currentEntry != null)
10071		{
10072			currentEntry.style.fontWeight = 'normal';
10073			currentEntry.style.textDecoration = 'none';
10074			lastEntry = currentEntry;
10075			currentEntry = null;
10076		}
10077	};
10078
10079	mxEvent.addListener(dlgDiv.querySelector('.geTempDlgBack'), 'click', function()
10080	{
10081		deselectTempCat();
10082		inTempScreen = false;
10083		var list = dlgDiv.querySelector(".geTemplatesList");
10084		list.style.display = 'none';
10085		tempDlgContent.style.width = '100%';
10086		newDiagramCat.style.display = '';
10087		diagramsList.style.minHeight = 'calc(100% - 280px)';
10088		searchInout.style.display = searchDocsCallback? '' : 'none';
10089		searchInout.value = '';
10090		lastSearchStr = null;
10091
10092 		getRecentDocs(isGetAll);
10093	});
10094
10095	function radioClick(btn, btnImgId, btnImgFile, otherBtnId, otherBtnImgId, otherBtnImgFile, isLarge)
10096	{
10097		if (btn.className.indexOf('geTempDlgRadioBtnActive') > -1)
10098		{
10099			return false;
10100		}
10101		else
10102		{
10103			btn.className += ' geTempDlgRadioBtnActive';
10104			dlgDiv.querySelector('.geTempDlgRadioBtn[data-id='+ otherBtnId +']').className = "geTempDlgRadioBtn " +
10105											(isLarge? "geTempDlgRadioBtnLarge" : "geTempDlgRadioBtnSmall");
10106			dlgDiv.querySelector('.'+ btnImgId).src = "/images/"+ btnImgFile +"-sel.svg";
10107			dlgDiv.querySelector('.'+ otherBtnImgId).src = "/images/"+ otherBtnImgFile +".svg";
10108			return true;
10109		}
10110	};
10111
10112	mxEvent.addListener(dlgDiv.querySelector('.geTempDlgRadioBtn[data-id=allDiagramsBtn]'), 'click', function()
10113	{
10114		if (radioClick(this, 'geTempDlgAllDiagramsBtnImg', 'all-diagrams', 'myDiagramsBtn', 'geTempDlgMyDiagramsBtnImg', 'my-diagrams', true))
10115		{
10116			isGetAll = true;
10117			lastSearchStr == null? getRecentDocs(isGetAll) : doSearch(lastSearchStr);
10118		}
10119	});
10120
10121	mxEvent.addListener(dlgDiv.querySelector('.geTempDlgRadioBtn[data-id=myDiagramsBtn]'), 'click', function()
10122	{
10123		if (radioClick(this, 'geTempDlgMyDiagramsBtnImg', 'my-diagrams', 'allDiagramsBtn', 'geTempDlgAllDiagramsBtnImg', 'all-diagrams', true))
10124		{
10125			isGetAll = false;
10126			lastSearchStr == null? getRecentDocs(isGetAll) : doSearch(lastSearchStr);
10127		}
10128	});
10129
10130	mxEvent.addListener(dlgDiv.querySelector('.geTempDlgRadioBtn[data-id=listBtn]'), 'click', function()
10131	{
10132		if (radioClick(this, 'geTempDlgListBtnImg', 'list', 'tilesBtn', 'geTempDlgTilesBtnImg', 'tiles', false))
10133		{
10134			showAsList = true;
10135			fillDiagramsList(curDiagList, false, showAsList, curSearchImportCats);
10136		}
10137	});
10138
10139	mxEvent.addListener(dlgDiv.querySelector('.geTempDlgRadioBtn[data-id=tilesBtn]'), 'click', function()
10140	{
10141		if (radioClick(this, 'geTempDlgTilesBtnImg', 'tiles', 'listBtn', 'geTempDlgListBtnImg', 'list', false))
10142		{
10143			showAsList = false;
10144			fillDiagramsList(curDiagList, false, showAsList, curSearchImportCats);
10145		}
10146	});
10147
10148	var loading = false;
10149
10150	function createPreview(diagram, elt, img, evt)
10151	{
10152		var xmlData = null;
10153
10154		function loadXmlData(url, callback)
10155		{
10156			if (xmlData == null)
10157			{
10158				var realUrl = url;
10159
10160				if (/^https?:\/\//.test(realUrl) && !editorUi.editor.isCorsEnabledForUrl(realUrl))
10161				{
10162					realUrl = PROXY_URL + '?url=' + encodeURIComponent(realUrl);
10163				}
10164				else
10165				{
10166					realUrl = TEMPLATE_PATH + '/' + realUrl;
10167				}
10168
10169				mxUtils.get(realUrl, mxUtils.bind(this, function(req)
10170				{
10171					if (req.getStatus() >= 200 && req.getStatus() <= 299)
10172					{
10173						xmlData = req.getText();
10174						callback(xmlData);
10175					}
10176					else
10177					{
10178						callback(xmlData);
10179					}
10180				}));
10181			}
10182			else
10183			{
10184				callback(xmlData);
10185			}
10186		}
10187
10188		// Shows a tooltip with the rendered template
10189		function showTooltip(xml, x, y)
10190		{
10191			// Checks if dialog still visible
10192			if (xml != null && mxUtils.isAncestorNode(document.body, elt))
10193			{
10194				var doc = mxUtils.parseXml(xml);
10195				var tempNode = Editor.extractGraphModel(doc.documentElement, true);
10196
10197				if (tempNode == null) return; //Not a diagram file
10198
10199				if (tempNode.nodeName == 'mxfile')
10200				{
10201					var tempNode = Editor.parseDiagramNode(tempNode.getElementsByTagName('diagram')[0]);
10202				}
10203
10204				var codec = new mxCodec(tempNode.ownerDocument);
10205				var model = new mxGraphModel();
10206				codec.decode(tempNode, model);
10207				var cells = model.root.getChildAt(0).children || [];
10208
10209				var ww = window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth;
10210				var wh = window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight;
10211
10212				// TODO: Use maxscreensize
10213				editorUi.sidebar.createTooltip(elt, cells, Math.min(ww - 80, 1000), Math.min(wh - 80, 800),
10214					(diagram.title != null) ? mxResources.get(diagram.title, null, diagram.title) : null,
10215					true, new mxPoint(x, y), true, null, true);
10216
10217				var mask = document.createElement('div');
10218				mask.className = "geTempDlgDialogMask";
10219				dlgDiv.appendChild(mask);
10220				//Remove the mask when tooltop is hidden
10221				var origHideTooltip = editorUi.sidebar.hideTooltip;
10222
10223				editorUi.sidebar.hideTooltip = function()
10224				{
10225					if (mask)
10226					{
10227						dlgDiv.removeChild(mask);
10228						mask = null;
10229						origHideTooltip.apply(this, arguments);
10230						editorUi.sidebar.hideTooltip = origHideTooltip;
10231					}
10232				};
10233
10234				mxEvent.addListener(mask, 'click', function()
10235				{
10236					editorUi.sidebar.hideTooltip();
10237				});
10238			}
10239		};
10240
10241		if (!loading && editorUi.sidebar.currentElt != elt)
10242		{
10243			editorUi.sidebar.hideTooltip();
10244			editorUi.sidebar.currentElt = elt;
10245			loading = true;
10246			img.src = '/images/aui-wait.gif';
10247
10248			function renderXML(xml)
10249			{
10250				if (loading && editorUi.sidebar.currentElt == elt)
10251				{
10252					showTooltip(xml, mxEvent.getClientX(evt), mxEvent.getClientY(evt));
10253				}
10254
10255				loading = false;
10256				img.src = '/images/icon-search.svg';
10257			};
10258
10259			if (diagram.isExt)
10260			{
10261				loadExtDoc(diagram, renderXML, function()
10262				{
10263					showError(mxResources.get('cantLoadPrev'));
10264					loading = false;
10265					img.src = '/images/icon-search.svg';
10266				});
10267			}
10268			else
10269			{
10270				loadXmlData(diagram.url, renderXML);
10271			}
10272		}
10273		else
10274		{
10275			editorUi.sidebar.hideTooltip();
10276		}
10277	};
10278
10279	function swapActiveItem(newItem, activeCls, itemInfo)
10280	{
10281		if (currentItem != null)
10282		{
10283			var classes = currentItem.className.split(" ");
10284
10285			for (var i = 0; i < classes.length; i++)
10286			{
10287				if (classes[i].indexOf("Active") > -1)
10288				{
10289					classes.splice(i, 1);
10290					break;
10291				}
10292			}
10293
10294			currentItem.className = classes.join(" ");
10295		}
10296
10297		if (newItem != null)
10298		{
10299			currentItem = newItem;
10300			currentItem.className += " " + activeCls;
10301			currentItemInfo = itemInfo;
10302			categorySelected = itemInfo.isCategory;
10303			//activate create button
10304			createBtn.className = "geTempDlgCreateBtn";
10305		}
10306		else
10307		{
10308			currentItem = null;
10309			currentItemInfo = null;
10310			//disable create button
10311			createBtn.className = "geTempDlgCreateBtn geTempDlgBtnDisabled";
10312		}
10313	};
10314
10315	function handleDialogOK(linkToDiagram, openDiagram)
10316	{
10317		if (currentItemInfo != null)
10318		{
10319			var itemInfo = currentItemInfo;
10320			//disable create button
10321			currentItemInfo = null;
10322
10323			if (typeof openDiagram !== 'boolean')
10324			{
10325				openDiagram = itemInfo.isExternal && withOpen; //Double click default to open with external docs
10326			}
10327
10328			function cancelSubmit()
10329			{
10330				currentItemInfo = itemInfo;
10331				createBtn.className = 'geTempDlgCreateBtn';
10332
10333				if (openDiagram)
10334				{
10335					openBtn.className = 'geTempDlgOpenBtn';
10336				}
10337			};
10338
10339			function submitErr()
10340			{
10341				showError(mxResources.get('cannotLoad'));
10342				cancelSubmit();
10343			};
10344
10345			function doSubmit(xml, filename)
10346			{
10347				if (!noDlgHide)
10348				{
10349					editorUi.hideDialog(true);
10350				}
10351
10352				callback(xml, filename, itemInfo, openDiagram);
10353			};
10354
10355			function startSubmit(filename)
10356			{
10357				if (itemInfo.isExternal)
10358				{
10359					loadExtDoc(itemInfo, function(xml)
10360					{
10361						doSubmit(xml, filename);
10362					}, submitErr);
10363				}
10364				else if (itemInfo.url)
10365				{
10366					mxUtils.get(TEMPLATE_PATH + '/' + itemInfo.url, mxUtils.bind(this, function(req)
10367					{
10368						if (req.getStatus() >= 200 && req.getStatus() <= 299)
10369						{
10370							doSubmit(req.getText(), filename);
10371						}
10372						else
10373						{
10374							submitErr();
10375						}
10376					}));
10377				}
10378				else
10379				{
10380					doSubmit(editorUi.emptyDiagramXml, filename);
10381				}
10382			};
10383
10384			if (linkToDiagram == true)
10385			{
10386				linkToDiagramCallback(itemInfo.url, itemInfo);
10387			}
10388			else if (openDiagram)
10389			{
10390				openBtn.className = "geTempDlgOpenBtn geTempDlgBtnDisabled geTempDlgBtnBusy";
10391				startSubmit();
10392			}
10393			else
10394			{
10395				createBtn.className = "geTempDlgCreateBtn geTempDlgBtnDisabled geTempDlgBtnBusy";
10396				var nameTitle = (((editorUi.mode == null || editorUi.mode == App.MODE_GOOGLE ||
10397						editorUi.mode == App.MODE_BROWSER) ? mxResources.get('diagramName') : mxResources.get('filename')));
10398				var nameDlg = new FilenameDialog(editorUi, editorUi.defaultFilename + '.drawio',
10399						mxResources.get('ok'), startSubmit, nameTitle, function(name)
10400						{
10401							//TODO validate?
10402							var valid = name != null && name.length > 0;
10403
10404							if (valid && noDlgHide)
10405							{
10406								startSubmit(name);
10407								return false; //prevent closing
10408							}
10409
10410							return valid;
10411						}, null, null, null, cancelSubmit, withoutType? null : []);
10412
10413				editorUi.showDialog(nameDlg.container, 350, 80, true, true);
10414				nameDlg.init();
10415			}
10416		}
10417	};
10418
10419	function toggleButtons(isTemplate)
10420	{
10421		createBtn.innerHTML = mxUtils.htmlEntities(mxResources.get(inTempScreen || isTemplate? 'create' : 'copy'));
10422
10423		var opemDisplay = isTemplate? 'none' : '';
10424
10425		if (withOpen)
10426		{
10427			openBtn.style.display = opemDisplay;
10428		}
10429
10430		var elems = dlgDiv.querySelectorAll(".geTempDlgLinkToDiagram");
10431
10432		for(var i = 0; i < elems.length; i++)
10433		{
10434			elems[i].style.display = opemDisplay;
10435		}
10436	};
10437
10438	function fillDiagramsList(diagrams, isTemplate, asList, searchImportCats, internalCall)
10439	{
10440		function setSubmitBtnLbl()
10441		{
10442			toggleButtons(isTemplate);
10443		};
10444
10445		if (!internalCall)
10446		{
10447			diagramsTiles.innerHTML = '';
10448			swapActiveItem();
10449			curDiagList = diagrams;
10450			curSearchImportCats = searchImportCats;
10451		}
10452
10453		var grid = null;
10454
10455		if (asList)
10456		{
10457			grid = document.createElement('table');
10458			grid.className = 'geTempDlgDiagramsListGrid';
10459			//create header row
10460			var hrow = document.createElement('tr');
10461			var th = document.createElement('th');
10462			th.style.width = "50%";
10463			th.innerHTML = mxUtils.htmlEntities(mxResources.get('diagram'));
10464			hrow.appendChild(th);
10465			th = document.createElement('th');
10466			th.style.width = "25%";
10467			th.innerHTML = mxUtils.htmlEntities(mxResources.get('changedBy'));
10468			hrow.appendChild(th);
10469			th = document.createElement('th');
10470			th.style.width = "25%";
10471			th.innerHTML = mxUtils.htmlEntities(mxResources.get('lastModifiedOn'));
10472			hrow.appendChild(th);
10473			grid.appendChild(hrow);
10474			diagramsTiles.appendChild(grid);
10475		}
10476
10477		//TODO support paging
10478		for (var i = 0; i < diagrams.length; i++)
10479		{
10480			diagrams[i].isExternal = !isTemplate;
10481			var url = diagrams[i].url;
10482			var title = mxUtils.htmlEntities(isTemplate?
10483							mxResources.get(diagrams[i].title, null, diagrams[i].title):
10484							diagrams[i].title);
10485			var tooltip = title || diagrams[i].url;
10486			var imgUrl = diagrams[i].imgUrl;
10487			var changedBy = mxUtils.htmlEntities(diagrams[i].changedBy || "");
10488			var lastModifiedOn = '';
10489
10490			if (diagrams[i].lastModifiedOn)
10491			{
10492				var str = editorUi.timeSince(new Date(diagrams[i].lastModifiedOn));
10493
10494				if (str == null)
10495				{
10496					str = mxResources.get('lessThanAMinute');
10497				}
10498
10499				lastModifiedOn = mxUtils.htmlEntities(mxResources.get('timeAgo', [str], '{1} ago'));
10500			}
10501
10502			if (!imgUrl)
10503			{
10504				imgUrl = TEMPLATE_PATH + '/' + url.substring(0, url.length - 4) + '.png';
10505			}
10506
10507			var titleLimit = asList? 50 : 15;
10508
10509			if (title != null && title.length > titleLimit)
10510			{
10511				title = title.substring(0, titleLimit) + '&hellip;';
10512			}
10513
10514			if (asList)
10515			{
10516				var row = document.createElement('tr');
10517				var td = document.createElement('td');
10518				var prevImg = document.createElement('img');
10519				prevImg.src = "/images/icon-search.svg";
10520				prevImg.className = "geTempDlgDiagramListPreviewBtn";
10521				prevImg.setAttribute('title', mxResources.get("preview"));
10522
10523				if (!internalCall)
10524				{
10525					td.appendChild(prevImg);
10526				}
10527
10528				var titleSpan = document.createElement('span');
10529				titleSpan.className = "geTempDlgDiagramTitle";
10530				titleSpan.innerHTML = title;
10531				td.appendChild(titleSpan);
10532				row.appendChild(td);
10533				td = document.createElement('td');
10534				td.innerHTML = changedBy;
10535				row.appendChild(td);
10536				td = document.createElement('td');
10537				td.innerHTML = lastModifiedOn;
10538				row.appendChild(td);
10539				grid.appendChild(row);
10540
10541				if (currentItem == null)
10542				{
10543					setSubmitBtnLbl();
10544					swapActiveItem(row, "geTempDlgDiagramsListGridActive", diagrams[i]);
10545				}
10546
10547				(function(diagram2, row2, prevImg2)
10548				{
10549					mxEvent.addListener(row, 'click', function()
10550					{
10551						if (currentItem != row2)
10552						{
10553							setSubmitBtnLbl();
10554							swapActiveItem(row2, "geTempDlgDiagramsListGridActive", diagram2);
10555						}
10556					});
10557
10558					mxEvent.addListener(row, 'dblclick', handleDialogOK);
10559
10560					mxEvent.addListener(prevImg, 'click', function(evt)
10561					{
10562						createPreview(diagram2, row2, prevImg2, evt);
10563					});
10564				})(diagrams[i], row, prevImg);
10565			}
10566			else
10567			{
10568				var tile = document.createElement('div');
10569				tile.className = "geTempDlgDiagramTile";
10570				tile.setAttribute('title', tooltip);
10571
10572				if (currentItem == null)
10573				{
10574					setSubmitBtnLbl();
10575					swapActiveItem(tile, "geTempDlgDiagramTileActive", diagrams[i]);
10576				}
10577
10578				var imgDiv = document.createElement('div');
10579				imgDiv.className = "geTempDlgDiagramTileImg geTempDlgDiagramTileImgLoading";
10580				var img = document.createElement('img');
10581				img.style.display = "none";
10582
10583				(function(img2, imgDiv2, fallbackImgUrl)
10584				{
10585					img.onload = function()
10586					{
10587						imgDiv2.className = "geTempDlgDiagramTileImg";
10588						img2.style.display = "";
10589					}
10590
10591					img.onerror = function()
10592					{
10593						if (this.src != fallbackImgUrl)
10594						{
10595							this.src = fallbackImgUrl;
10596						}
10597						else
10598						{
10599							imgDiv2.className = "geTempDlgDiagramTileImg geTempDlgDiagramTileImgError";
10600						}
10601					}
10602				})(img, imgDiv, imgUrl? imgUrl.replace('.drawio.xml', '').replace('.drawio', '').replace('.xml', '') : '');
10603
10604				img.src = imgUrl;
10605				imgDiv.appendChild(img);
10606				tile.appendChild(imgDiv);
10607
10608				var lblDiv = document.createElement('div');
10609				lblDiv.className = "geTempDlgDiagramTileLbl";
10610				lblDiv.innerHTML = title != null? title : '';
10611				tile.appendChild(lblDiv);
10612
10613				var prevImg = document.createElement('img');
10614				prevImg.src = "/images/icon-search.svg";
10615				prevImg.className = "geTempDlgDiagramPreviewBtn";
10616				prevImg.setAttribute('title', mxResources.get("preview"));
10617
10618				if (!internalCall)
10619				{
10620					tile.appendChild(prevImg);
10621				}
10622
10623				(function(diagram2, tile2, prevImg2)
10624				{
10625					mxEvent.addListener(tile, 'click', function()
10626					{
10627						if (currentItem != tile2)
10628						{
10629							setSubmitBtnLbl();
10630							swapActiveItem(tile2, "geTempDlgDiagramTileActive", diagram2);
10631						}
10632					});
10633
10634					mxEvent.addListener(tile, 'dblclick', handleDialogOK);
10635
10636					mxEvent.addListener(prevImg, 'click', function(evt)
10637					{
10638						createPreview(diagram2, tile2, prevImg2, evt);
10639					});
10640				})(diagrams[i], tile, prevImg);
10641
10642				diagramsTiles.appendChild(tile);
10643			}
10644		}
10645
10646		for (var cat in searchImportCats)
10647		{
10648			var catList = searchImportCats[cat];
10649
10650			if (catList.length > 0)
10651			{
10652				var header = document.createElement('div');
10653				header.className = 'geTempDlgImportCat';
10654				header.innerHTML = mxResources.get(cat, null, cat);
10655				diagramsTiles.appendChild(header);
10656				fillDiagramsList(catList, isTemplate, asList, null, true);
10657			}
10658		}
10659	};
10660
10661	function fillNewDiagramCats(newDiagramCats, showAll)
10662	{
10663		newDiagramCatList.innerHTML = "";
10664		swapActiveItem();
10665		var oneRowCount = Math.floor(newDiagramCatList.offsetWidth / 150) - 1;
10666		var catCount = !showAll && newDiagramCats.length > oneRowCount ? oneRowCount : newDiagramCats.length;
10667
10668		for (var i = 0; i < catCount; i++)
10669		{
10670			var cat = newDiagramCats[i];
10671			cat.isCategory = true;
10672			var entry = document.createElement('div');
10673			var label = mxResources.get(cat.title);
10674
10675			if (label == null)
10676			{
10677				label = cat.title.substring(0, 1).toUpperCase() + cat.title.substring(1);
10678			}
10679
10680			entry.className = 'geTempDlgNewDiagramCatItem';
10681			entry.setAttribute('title', label);
10682
10683			label = mxUtils.htmlEntities(label);
10684
10685			if (label.length > 15)
10686			{
10687				label = label.substring(0, 15) + '&hellip;';
10688			}
10689
10690			if (currentItem == null)
10691			{
10692				toggleButtons(true);
10693				swapActiveItem(entry, "geTempDlgNewDiagramCatItemActive", cat);
10694			}
10695
10696			var imgDiv = document.createElement('div');
10697			imgDiv.className = "geTempDlgNewDiagramCatItemImg";
10698			var img = document.createElement('img');
10699			img.src = NEW_DIAGRAM_CATS_PATH + '/' + cat.img;
10700			imgDiv.appendChild(img);
10701			entry.appendChild(imgDiv);
10702
10703			var lblDiv = document.createElement('div');
10704			lblDiv.className = "geTempDlgNewDiagramCatItemLbl";
10705			lblDiv.innerHTML = label;
10706			entry.appendChild(lblDiv);
10707
10708			newDiagramCatList.appendChild(entry);
10709
10710			(function(cat2, entry2)
10711			{
10712				mxEvent.addListener(entry, 'click', function()
10713				{
10714					if (currentItem != entry2)
10715					{
10716						toggleButtons(true);
10717						swapActiveItem(entry2, "geTempDlgNewDiagramCatItemActive", cat2);
10718					}
10719				});
10720
10721				mxEvent.addListener(entry, 'dblclick', handleDialogOK);
10722			})(cat, entry);
10723		}
10724
10725		//Add the "Show All Templates" card
10726		var entry = document.createElement('div');
10727		entry.className = 'geTempDlgNewDiagramCatItem';
10728		var label = mxResources.get('showAllTemps');
10729		entry.setAttribute('title', label);
10730		var imgDiv = document.createElement('div');
10731		imgDiv.className = "geTempDlgNewDiagramCatItemImg";
10732		imgDiv.innerHTML = '...';
10733		imgDiv.style.fontSize = '32px';
10734		entry.appendChild(imgDiv);
10735		var lblDiv = document.createElement('div');
10736		lblDiv.className = "geTempDlgNewDiagramCatItemLbl";
10737		lblDiv.innerHTML = label;
10738		entry.appendChild(lblDiv);
10739		newDiagramCatList.appendChild(entry);
10740
10741		mxEvent.addListener(entry, 'click', function()
10742		{
10743			//Show templates screen
10744			inTempScreen = true;
10745			var list = dlgDiv.querySelector(".geTemplatesList");
10746			list.style.display = 'block';
10747			tempDlgContent.style.width = '';
10748			searchInout.style.display = '';
10749			searchInout.value = '';
10750			lastSearchStr = null;
10751
10752			function openFirstCat()
10753			{
10754				var firstCat = list.querySelector('.geTemplateDrawioCatLink');
10755
10756				if (firstCat != null)
10757				{
10758					firstCat.click();
10759				}
10760				else
10761				{
10762					setTimeout(openFirstCat, 200);
10763				}
10764			};
10765
10766			openFirstCat();
10767		});
10768
10769		showAllBtn.style.display = newDiagramCats.length <= oneRowCount ? "none" : "";
10770	};
10771
10772	mxEvent.addListener(showAllBtn, 'click', function()
10773	{
10774		if (showingAll)
10775		{
10776			newDiagramCat.style.height = "280px";
10777			newDiagramCatList.style.height = "190px";
10778			showAllBtn.innerHTML = mxUtils.htmlEntities(mxResources.get('showMore'));
10779			fillNewDiagramCats(newDiagramCats);
10780		}
10781		else
10782		{
10783			newDiagramCat.style.height = "440px";
10784			newDiagramCatList.style.height = "355px";
10785			showAllBtn.innerHTML = mxUtils.htmlEntities(mxResources.get('showLess'));
10786			fillNewDiagramCats(newDiagramCats, true);
10787		}
10788
10789		showingAll = !showingAll;
10790	});
10791
10792	function fillTemplatesList(categories, customCats, customCatCount)
10793	{
10794		var list = dlgDiv.querySelector(".geTemplatesList");
10795
10796		function getEntryTitle(cat, templateList)
10797		{
10798			var label = mxResources.get(cat);
10799
10800			if (label == null)
10801			{
10802				label = cat.substring(0, 1).toUpperCase() + cat.substring(1);
10803			}
10804
10805			var fullLbl = label + ' (' + templateList.length + ')';
10806			label = mxUtils.htmlEntities(label);
10807			var lblOnly = label;
10808
10809			if (label.length > 15)
10810			{
10811				label = label.substring(0, 15) + '&hellip;';
10812			}
10813
10814			return {lbl: label + ' (' + templateList.length + ')', fullLbl: fullLbl, lblOnly: lblOnly};
10815		};
10816
10817		function addEntryHandler(cat, label, entry, subCat, isCustom)
10818		{
10819			mxEvent.addListener(entry, 'click', function()
10820			{
10821				if (currentEntry != entry)
10822				{
10823					if (currentEntry != null)
10824					{
10825						currentEntry.style.fontWeight = 'normal';
10826						currentEntry.style.textDecoration = 'none';
10827					}
10828					else
10829					{
10830						newDiagramCat.style.display = 'none';
10831						diagramsList.style.minHeight = '100%';
10832					}
10833
10834					currentEntry = entry;
10835					currentEntry.style.fontWeight = 'bold';
10836					currentEntry.style.textDecoration = 'underline';
10837
10838					tempDlgContent.scrollTop = 0;
10839
10840					if (callInitiated)
10841					{
10842						cancelPendingCall = true;
10843					}
10844
10845					diagramsListTitle.innerHTML = label;
10846					diagramsListBtns.style.display = 'none';
10847					fillDiagramsList(isCustom ? customCats[cat] :
10848							(subCat? subCategories[cat][subCat] : categories[cat]), isCustom ? false : true);
10849				}
10850			});
10851		};
10852
10853		if (customCatCount > 0)
10854		{
10855			var titleCss = 'font-weight: bold;background: #f9f9f9;padding: 5px 0 5px 0;text-align: center;margin-top: 10px;';
10856			var title = document.createElement('div');
10857			title.style.cssText = titleCss;
10858			mxUtils.write(title, mxResources.get('custom'));
10859			list.appendChild(title);
10860
10861			for (var cat in customCats)
10862			{
10863				var entry = document.createElement('div');
10864				var templateList = customCats[cat];
10865				var lbls = getEntryTitle(cat, templateList);
10866				entry.className = 'geTemplateCatLink';
10867				entry.setAttribute('title', lbls.fullLbl);
10868				entry.innerHTML = lbls.lbl;
10869				list.appendChild(entry);
10870				addEntryHandler(cat, lbls.lblOnly, entry, null, true);
10871			}
10872
10873			title = document.createElement('div');
10874			title.style.cssText = titleCss;
10875			mxUtils.write(title, 'draw.io');
10876			list.appendChild(title);
10877		}
10878
10879		for (var cat in categories)
10880		{
10881			var subCats = subCategories[cat];
10882			var entry = document.createElement(subCats? 'ul' : 'div');
10883			var clickElem = entry;
10884			var templateList = categories[cat];
10885			var lbls = getEntryTitle(cat, templateList);
10886
10887			if (subCats != null)
10888			{
10889				var entryLi = document.createElement('li');
10890				var entryDiv = document.createElement('div');
10891				entryDiv.className = 'geTempTreeCaret geTemplateCatLink geTemplateDrawioCatLink';
10892				entryDiv.style.padding = '0';
10893				entryDiv.setAttribute('title', lbls.fullLbl);
10894				entryDiv.innerHTML = lbls.lbl;
10895				clickElem = entryDiv;
10896				entryLi.appendChild(entryDiv);
10897				//We support one level deep only
10898				var subUl = document.createElement('ul');
10899				subUl.className = 'geTempTreeNested';
10900				subUl.style.visibility = 'hidden';
10901
10902				for (var subCat in subCats)
10903				{
10904					var subLi = document.createElement('li');
10905					var subLbls = getEntryTitle(subCat, subCats[subCat]);
10906					subLi.setAttribute('title', subLbls.fullLbl);
10907					subLi.innerHTML = subLbls.lbl;
10908					subLi.className = 'geTemplateCatLink';
10909					subLi.style.padding = '0';
10910					subLi.style.margin = '0';
10911					addEntryHandler(cat, subLbls.lblOnly, subLi, subCat);
10912					subUl.appendChild(subLi);
10913				}
10914
10915				entryLi.appendChild(subUl);
10916				entry.className = 'geTempTree';
10917				entry.appendChild(entryLi);
10918
10919				(function(subUl2, entryDiv2)
10920				{
10921					mxEvent.addListener(entryDiv2, 'click', function()
10922					{
10923						var lis = subUl2.querySelectorAll('li');
10924
10925						for (var i = 0; i < lis.length; i++)
10926						{
10927							lis[i].style.margin = '';
10928						}
10929
10930						subUl2.style.visibility = 'visible';
10931						subUl2.classList.toggle('geTempTreeActive');
10932
10933						if (subUl2.classList.toggle('geTempTreeNested'))
10934						{
10935							//Must hide sub elements to allow click on elements above it
10936							setTimeout(function()
10937							{
10938								for (var i = 0; i < lis.length; i++)
10939								{
10940									lis[i].style.margin = '0';
10941								}
10942
10943								subUl2.style.visibility = 'hidden';
10944							}, 250);
10945						}
10946
10947	    				entryDiv2.classList.toggle('geTempTreeCaret-down');
10948					});
10949				})(subUl, entryDiv);
10950			}
10951			else
10952			{
10953				entry.className = 'geTemplateCatLink geTemplateDrawioCatLink';
10954				entry.setAttribute('title', lbls.fullLbl);
10955				entry.innerHTML = lbls.lbl;
10956			}
10957
10958			list.appendChild(entry);
10959			addEntryHandler(cat, lbls.lblOnly, clickElem);
10960		}
10961	};
10962
10963	var indexLoaded = false, indexLoaded2 = false;
10964	var categories = {}, subCategories = {}, customCats = {};
10965	var newDiagramCats = [];
10966	var categoryCount = 1, customCatCount = 0;
10967
10968	function loadDrawioTemplates()
10969	{
10970		mxUtils.get(templateFile, function(req)
10971		{
10972			// Workaround for index loaded 3 times in iOS offline mode
10973			if (!indexLoaded)
10974			{
10975				indexLoaded = true;
10976				var tmpDoc = req.getXml();
10977				var node = tmpDoc.documentElement.firstChild;
10978				var clibs = {};
10979
10980				while (node != null)
10981				{
10982					if (typeof(node.getAttribute) !== 'undefined')
10983					{
10984						if (node.nodeName == 'clibs')
10985						{
10986							var name = node.getAttribute('name');
10987							var adds = node.getElementsByTagName('add');
10988							var temp = [];
10989
10990							for (var i = 0; i < adds.length; i++)
10991							{
10992								temp.push(encodeURIComponent(mxUtils.getTextContent(adds[i])));
10993							}
10994
10995							if (name != null && temp.length > 0)
10996							{
10997								clibs[name] = temp.join(';');
10998							}
10999						}
11000						else
11001						{
11002							var url = node.getAttribute('url');
11003
11004							if (url != null)
11005							{
11006								var category = node.getAttribute('section');
11007								var subCategory = node.getAttribute('subsection');
11008
11009								if (category == null)
11010								{
11011									var slash = url.indexOf('/');
11012									category = url.substring(0, slash);
11013
11014									if (subCategory == null)
11015									{
11016										var nextSlash = url.indexOf('/', slash + 1);
11017
11018										if (nextSlash > -1)
11019										{
11020											subCategory = url.substring(slash + 1, nextSlash);
11021										}
11022									}
11023								}
11024
11025								var list = categories[category];
11026
11027								if (list == null)
11028								{
11029									categoryCount++;
11030									list = [];
11031									categories[category] = list;
11032								}
11033
11034								var tempLibs = node.getAttribute('clibs');
11035
11036								if (clibs[tempLibs] != null)
11037								{
11038									tempLibs = clibs[tempLibs];
11039								}
11040
11041								var tempObj = {url: node.getAttribute('url'), libs: node.getAttribute('libs'),
11042									title: node.getAttribute('title') || node.getAttribute('name'),
11043									preview: node.getAttribute('preview'), clibs: tempLibs, tags: node.getAttribute('tags')};
11044								list.push(tempObj);
11045
11046								if (subCategory != null)
11047								{
11048									var subCats = subCategories[category];
11049
11050									if (subCats == null)
11051									{
11052										subCats = {};
11053										subCategories[category] = subCats;
11054									}
11055
11056									var subCatList = subCats[subCategory];
11057
11058									if (subCatList == null)
11059									{
11060										subCatList = [];
11061										subCats[subCategory] = subCatList;
11062									}
11063
11064									subCatList.push(tempObj);
11065								}
11066							}
11067						}
11068					}
11069
11070					node = node.nextSibling;
11071				}
11072
11073				fillTemplatesList(categories, customCats, customCatCount);
11074			}
11075		});
11076	};
11077
11078	if (customTempCallback != null)
11079	{
11080		customTempCallback(function(cats, count)
11081		{
11082			customCats = cats;
11083			customCatCount = count;
11084			loadDrawioTemplates();
11085		}, loadDrawioTemplates); //In case of an error, just load draw.io templates only
11086	}
11087	else
11088	{
11089		loadDrawioTemplates();
11090	}
11091
11092	mxUtils.get(newDiagramCatsFile, function(req)
11093	{
11094		// Workaround for index loaded 3 times in iOS offline mode
11095		if (!indexLoaded2)
11096		{
11097			indexLoaded2 = true;
11098			var tmpDoc = req.getXml();
11099			var node = tmpDoc.documentElement.firstChild;
11100
11101			while (node != null)
11102			{
11103				if (typeof(node.getAttribute) !== 'undefined')
11104				{
11105					var title = node.getAttribute('title');
11106
11107					if (title != null)
11108					{
11109						newDiagramCats.push({img: node.getAttribute('img'), libs: node.getAttribute('libs'),
11110							clibs: node.getAttribute('clibs'), title: node.getAttribute('title')});
11111					}
11112				}
11113
11114				node = node.nextSibling;
11115			}
11116
11117			fillNewDiagramCats(newDiagramCats);
11118		}
11119	});
11120
11121	var extDiagramsCallback = function(list, errorMsg, searchImportCats)
11122	{
11123		diagramsListBtns.style.display = '';
11124		spinner.stop();
11125
11126		callInitiated = false;
11127
11128		if (cancelPendingCall)
11129		{
11130			cancelPendingCall = false;
11131			return;
11132		}
11133
11134		if (errorMsg)
11135		{
11136			diagramsTiles.innerHTML = errorMsg;
11137		}
11138		else
11139		{
11140			searchImportCats = searchImportCats || {};
11141			var importListsCount = 0;
11142
11143			for (var cat in searchImportCats)
11144			{
11145				importListsCount += searchImportCats[cat].length;
11146			}
11147
11148			if (list.length == 0 && importListsCount == 0)
11149			{
11150				diagramsTiles.innerHTML = mxUtils.htmlEntities(mxResources.get('noDiagrams'));
11151			}
11152			else
11153			{
11154				fillDiagramsList(list, false, showAsList, importListsCount == 0? null : searchImportCats);
11155			}
11156		}
11157	};
11158
11159	function getRecentDocs(getAll)
11160	{
11161		if (recentDocsCallback)
11162		{
11163			tempDlgContent.scrollTop = 0;
11164			diagramsTiles.innerHTML = '';
11165			spinner.spin(diagramsTiles);
11166			cancelPendingCall = false;
11167			callInitiated = true;
11168			diagramsListTitle.innerHTML = mxUtils.htmlEntities(mxResources.get('recentDiag'));
11169			lastSearchStr = null;
11170			recentDocsCallback(extDiagramsCallback, function()
11171			{
11172				showError(mxResources.get('cannotLoad'));
11173				extDiagramsCallback([]);
11174			}, getAll? null : username);
11175		}
11176	};
11177
11178	getRecentDocs(isGetAll);
11179
11180	var delayTimer = null;
11181
11182	function resetTemplates()
11183	{
11184		if (lastEntry != null)
11185		{
11186			lastEntry.click();
11187			lastEntry = null;
11188		}
11189	};
11190
11191	function filterTemplates(searchTerms)
11192	{
11193		if (searchTerms == '')
11194		{
11195			resetTemplates();
11196			return;
11197		}
11198
11199		if (TemplatesDialog.tagsList[templateFile] == null)
11200		{
11201			var tagsList = {};
11202
11203			for (var cat in categories)
11204			{
11205				var templateList = categories[cat];
11206
11207				for (var i = 0; i < templateList.length; i++)
11208				{
11209					var temp = templateList[i];
11210
11211					if (temp.tags != null)
11212					{
11213						var tags = temp.tags.toLowerCase().split(';');
11214
11215						for (var j = 0; j < tags.length; j++)
11216						{
11217							if (tagsList[tags[j]] == null)
11218							{
11219								tagsList[tags[j]] = [];
11220							}
11221
11222							tagsList[tags[j]].push(temp);
11223						}
11224					}
11225				}
11226			}
11227
11228			TemplatesDialog.tagsList[templateFile] = tagsList;
11229		}
11230
11231		var tmp = searchTerms.toLowerCase().split(' ');
11232		tagsList = TemplatesDialog.tagsList[templateFile];
11233
11234		if (customCatCount > 0 && tagsList.__tagsList__ == null)
11235		{
11236			for (var cat in customCats)
11237			{
11238				var templateList = customCats[cat];
11239
11240				for (var i = 0; i < templateList.length; i++)
11241				{
11242					var temp = templateList[i];
11243					var tags = temp.title.split(' ');
11244					tags.push(cat);
11245
11246					for (var j = 0; j < tags.length; j++)
11247					{
11248						var tag = tags[j].toLowerCase();
11249
11250						if (tagsList[tag] == null)
11251						{
11252							tagsList[tag] = [];
11253						}
11254
11255						tagsList[tag].push(temp);
11256					}
11257				}
11258			}
11259
11260			tagsList.__tagsList__ = true;
11261		}
11262
11263		var results = [], resMap = {}, index = 0;
11264
11265		for (var i = 0; i < tmp.length; i++)
11266		{
11267			if (tmp[i].length > 0)
11268			{
11269				var list = tagsList[tmp[i]];
11270				var tmpResMap = {};
11271				results = [];
11272
11273				if (list != null)
11274				{
11275					for (var j = 0; j < list.length; j++)
11276					{
11277						var temp = list[j];
11278
11279						//ANDing terms
11280						if ((index == 0) == (resMap[temp.url] == null))
11281						{
11282							tmpResMap[temp.url] = true;
11283							results.push(temp);
11284						}
11285					}
11286				}
11287
11288				resMap = tmpResMap;
11289				index++;
11290			}
11291		}
11292
11293		if (results.length == 0)
11294		{
11295			diagramsListTitle.innerHTML = mxResources.get('noResultsFor', [searchTerms]);
11296		}
11297		else
11298		{
11299			fillDiagramsList(results, true);
11300		}
11301	};
11302
11303	function doSearch(searchStr)
11304	{
11305		if (lastSearchStr == searchStr && isGetAll == lastGetAll) return;
11306
11307		deselectTempCat();
11308		tempDlgContent.scrollTop = 0;
11309		diagramsTiles.innerHTML = '';
11310		diagramsListTitle.innerHTML = mxUtils.htmlEntities(mxResources.get('searchResults')) +
11311										' "' + mxUtils.htmlEntities(searchStr) + '"';
11312		delayTimer = null;
11313
11314		if (inTempScreen)
11315		{
11316			//Do search in templates
11317			filterTemplates(searchStr);
11318		}
11319		else if (searchDocsCallback)
11320		{
11321			if (searchStr)
11322			{
11323				spinner.spin(diagramsTiles);
11324				cancelPendingCall = false;
11325				callInitiated = true;
11326				//TODO use request id to allow only last request to show results
11327				searchDocsCallback(searchStr, extDiagramsCallback, function()
11328				{
11329					showError(mxResources.get('searchFailed'));
11330					extDiagramsCallback([]);
11331				}, isGetAll? null : username);
11332			}
11333			else
11334			{
11335				getRecentDocs(isGetAll); //Load recent doc again
11336			}
11337		}
11338
11339		lastSearchStr = searchStr;
11340		lastGetAll = isGetAll;
11341	};
11342
11343	function searchEvent(evt)
11344	{
11345		if (delayTimer != null)
11346		{
11347			clearTimeout(delayTimer);
11348		}
11349
11350		if (evt.keyCode == 13)
11351		{
11352			doSearch(searchInout.value);
11353		}
11354		else
11355		{
11356			delayTimer = setTimeout(function()
11357			{
11358				doSearch(searchInout.value);
11359			}, 1000);
11360		}
11361	};
11362
11363	//Use keyup to detect delete and backspace
11364	mxEvent.addListener(searchInout, 'keyup', searchEvent);
11365	//To detect copy/paste and clear
11366	mxEvent.addListener(searchInout, 'search', searchEvent);
11367	mxEvent.addListener(searchInout, 'input', searchEvent);
11368
11369	mxEvent.addListener(createBtn, 'click', function(evt)
11370	{
11371		handleDialogOK(false, false);
11372	});
11373
11374	if (withOpen)
11375	{
11376		mxEvent.addListener(openBtn, 'click', function(evt)
11377		{
11378			handleDialogOK(false, true);
11379		});
11380	}
11381
11382	if (withLink)
11383	{
11384		mxEvent.addListener(dlgDiv.querySelector(".geTempDlgLinkToDiagramBtn"), 'click', function(evt)
11385		{
11386			handleDialogOK(true);
11387		});
11388	}
11389
11390	mxEvent.addListener(dlgDiv.querySelector('.geTempDlgCancelBtn'), 'click', function()
11391	{
11392		if (cancelCallback != null)
11393		{
11394			cancelCallback();
11395		}
11396
11397		if (!noDlgHide)
11398		{
11399			editorUi.hideDialog(true);
11400		}
11401	});
11402};
11403
11404TemplatesDialog.tagsList = {};
11405/**
11406 * Constructs a new popup opener button dialog.
11407 */
11408var BtnDialog = function(editorUi, peer, btnLbl, fn)
11409{
11410	var div = document.createElement('div');
11411	div.style.textAlign = 'center';
11412
11413	var hd = document.createElement('p');
11414	hd.style.fontSize = '16pt';
11415	hd.style.padding = '0px';
11416	hd.style.margin = '0px';
11417	hd.style.color = 'gray';
11418
11419	mxUtils.write(hd, mxResources.get('done'));
11420
11421	var service = 'Unknown';
11422
11423	var img = document.createElement('img');
11424	img.setAttribute('border', '0');
11425	img.setAttribute('align', 'absmiddle');
11426	img.style.marginRight = '10px';
11427
11428	if (peer == editorUi.drive)
11429	{
11430		service = mxResources.get('googleDrive');
11431		img.src = IMAGE_PATH + '/google-drive-logo-white.svg';
11432	}
11433	else if (peer == editorUi.dropbox)
11434	{
11435		service = mxResources.get('dropbox');
11436		img.src = IMAGE_PATH + '/dropbox-logo-white.svg';
11437	}
11438	else if (peer == editorUi.oneDrive)
11439	{
11440		service = mxResources.get('oneDrive');
11441		img.src = IMAGE_PATH + '/onedrive-logo-white.svg';
11442	}
11443	else if (peer == editorUi.gitHub)
11444	{
11445		service = mxResources.get('github');
11446		img.src = IMAGE_PATH + '/github-logo-white.svg';
11447	}
11448	else if (peer == editorUi.gitLab)
11449	{
11450		service = mxResources.get('gitlab');
11451		img.src = IMAGE_PATH + '/gitlab-logo.svg';
11452	}
11453	else if (peer == editorUi.notion)
11454	{
11455		service = mxResources.get('notion');
11456		img.src = IMAGE_PATH + '/notion-logo.svg';
11457	}
11458	else if (peer == editorUi.trello)
11459	{
11460		service = mxResources.get('trello');
11461		img.src = IMAGE_PATH + '/trello-logo-white.svg';
11462	}
11463
11464	var p = document.createElement('p');
11465	mxUtils.write(p, mxResources.get('authorizedIn', [service], 'You are now authorized in {1}'));
11466
11467	var button = mxUtils.button(btnLbl, fn);
11468
11469	button.insertBefore(img, button.firstChild);
11470	button.style.marginTop = '6px';
11471	button.className = 'geBigButton';
11472	button.style.fontSize = '18px';
11473	button.style.padding = '14px';
11474
11475	div.appendChild(hd);
11476	div.appendChild(p);
11477	div.appendChild(button);
11478
11479	this.container = div;
11480};
11481
11482/**
11483 * Constructs a new font dialog.
11484 */
11485var FontDialog = function(editorUi, curFontname, curUrl, curType, fn)
11486{
11487	var row, td, label;
11488
11489	var table = document.createElement('table');
11490	var tbody = document.createElement('tbody');
11491	table.style.marginTop = '8px';
11492
11493	//System fonts section
11494	row = document.createElement('tr');
11495
11496	td = document.createElement('td');
11497	td.colSpan = 2;
11498	td.style.whiteSpace = 'nowrap';
11499	td.style.fontSize = '10pt';
11500	td.style.fontWeight = 'bold';
11501
11502	var sysFontRadio = document.createElement('input');
11503	sysFontRadio.style.cssText = 'margin-right:8px;margin-bottom:8px;';
11504	sysFontRadio.setAttribute('value', 'sysfonts');
11505	sysFontRadio.setAttribute('type', 'radio');
11506	sysFontRadio.setAttribute('name', 'current-fontdialog');
11507	sysFontRadio.setAttribute('id', 'fontdialog-sysfonts');
11508	td.appendChild(sysFontRadio);
11509
11510	label = document.createElement('label');
11511	label.setAttribute('for', 'fontdialog-sysfonts');
11512	mxUtils.write(label, (mxResources.get('sysFonts', null, 'System Fonts')));
11513	td.appendChild(label);
11514
11515	row.appendChild(td);
11516	tbody.appendChild(row);
11517
11518	row = document.createElement('tr');
11519
11520	td = document.createElement('td');
11521	td.style.whiteSpace = 'nowrap';
11522	td.style.fontSize = '10pt';
11523	td.style.width = '120px';
11524	td.style.paddingLeft = '15px';
11525	mxUtils.write(td, (mxResources.get('fontname', null, 'Font Name')) + ':');
11526
11527	row.appendChild(td);
11528
11529	var sysFontInput = document.createElement('input');
11530
11531	if (curType == 's')
11532	{
11533		sysFontInput.setAttribute('value', curFontname);
11534	}
11535
11536	sysFontInput.style.marginLeft = '4px';
11537	sysFontInput.style.width = '250px';
11538	sysFontInput.className = 'dlg_fontName_s';
11539
11540	td = document.createElement('td');
11541	td.appendChild(sysFontInput);
11542	row.appendChild(td);
11543
11544	tbody.appendChild(row);
11545
11546	//Google fonts section
11547	row = document.createElement('tr');
11548
11549	td = document.createElement('td');
11550	td.colSpan = 2;
11551	td.style.whiteSpace = 'nowrap';
11552	td.style.fontSize = '10pt';
11553	td.style.fontWeight = 'bold';
11554
11555	var googleFontRadio = document.createElement('input');
11556	googleFontRadio.style.cssText = 'margin-right:8px;margin-bottom:8px;';
11557	googleFontRadio.setAttribute('value', 'googlefonts');
11558	googleFontRadio.setAttribute('type', 'radio');
11559	googleFontRadio.setAttribute('name', 'current-fontdialog');
11560	googleFontRadio.setAttribute('id', 'fontdialog-googlefonts');
11561	td.appendChild(googleFontRadio);
11562
11563	label = document.createElement('label');
11564	label.setAttribute('for', 'fontdialog-googlefonts');
11565	mxUtils.write(label, (mxResources.get('googleFonts', null, 'Google Fonts')));
11566	td.appendChild(label);
11567
11568	// Link to Google Fonts
11569	if (!mxClient.IS_CHROMEAPP && (!editorUi.isOffline() || EditorUi.isElectronApp))
11570	{
11571		var link = editorUi.menus.createHelpLink('https://fonts.google.com/');
11572		link.getElementsByTagName('img')[0].setAttribute('valign', 'middle');
11573		td.appendChild(link);
11574	}
11575
11576	row.appendChild(td);
11577	tbody.appendChild(row);
11578
11579	row = document.createElement('tr');
11580
11581	td = document.createElement('td');
11582	td.style.whiteSpace = 'nowrap';
11583	td.style.fontSize = '10pt';
11584	td.style.width = '120px';
11585	td.style.paddingLeft = '15px';
11586	mxUtils.write(td, (mxResources.get('fontname', null, 'Font Name')) + ':');
11587
11588	row.appendChild(td);
11589
11590	var googleFontInput = document.createElement('input');
11591
11592	if (curType == 'g')
11593	{
11594		googleFontInput.setAttribute('value', curFontname);
11595	}
11596
11597	googleFontInput.style.marginLeft = '4px';
11598	googleFontInput.style.width = '250px';
11599	googleFontInput.className = 'dlg_fontName_g';
11600
11601	td = document.createElement('td');
11602	td.appendChild(googleFontInput);
11603	row.appendChild(td);
11604	tbody.appendChild(row);
11605
11606	//Generic remote fonts section
11607	row = document.createElement('tr');
11608
11609	td = document.createElement('td');
11610	td.colSpan = 2;
11611	td.style.whiteSpace = 'nowrap';
11612	td.style.fontSize = '10pt';
11613	td.style.fontWeight = 'bold';
11614
11615	var webFontRadio = document.createElement('input');
11616	webFontRadio.style.cssText = 'margin-right:8px;margin-bottom:8px;';
11617	webFontRadio.setAttribute('value', 'webfonts');
11618	webFontRadio.setAttribute('type', 'radio');
11619	webFontRadio.setAttribute('name', 'current-fontdialog');
11620	webFontRadio.setAttribute('id', 'fontdialog-webfonts');
11621	td.appendChild(webFontRadio);
11622
11623	label = document.createElement('label');
11624	label.setAttribute('for', 'fontdialog-webfonts');
11625	mxUtils.write(label, (mxResources.get('webfonts', null, 'Web Fonts')));
11626	td.appendChild(label);
11627
11628	row.appendChild(td);
11629
11630	if (Editor.enableWebFonts)
11631	{
11632		tbody.appendChild(row);
11633	}
11634
11635	row = document.createElement('tr');
11636
11637	td = document.createElement('td');
11638	td.style.whiteSpace = 'nowrap';
11639	td.style.fontSize = '10pt';
11640	td.style.width = '120px';
11641	td.style.paddingLeft = '15px';
11642	mxUtils.write(td, (mxResources.get('fontname', null, 'Font Name')) + ':');
11643
11644	row.appendChild(td);
11645
11646	var webFontInput = document.createElement('input');
11647
11648	if (curType == 'w')
11649	{
11650		if (Editor.enableWebFonts)
11651		{
11652			webFontInput.setAttribute('value', curFontname);
11653		}
11654		else
11655		{
11656			sysFontInput.setAttribute('value', curFontname);
11657		}
11658	}
11659
11660	webFontInput.style.marginLeft = '4px';
11661	webFontInput.style.width = '250px';
11662	webFontInput.className = 'dlg_fontName_w';
11663
11664	td = document.createElement('td');
11665	td.appendChild(webFontInput);
11666	row.appendChild(td);
11667
11668	if (Editor.enableWebFonts)
11669	{
11670		tbody.appendChild(row);
11671	}
11672
11673	row = document.createElement('tr');
11674
11675	td = document.createElement('td');
11676	td.style.whiteSpace = 'nowrap';
11677	td.style.fontSize = '10pt';
11678	td.style.width = '120px';
11679	td.style.paddingLeft = '15px';
11680	mxUtils.write(td, (mxResources.get('fontUrl', null, 'Font URL')) + ':');
11681
11682	row.appendChild(td);
11683
11684	var webFontUrlInput = document.createElement('input');
11685	webFontUrlInput.setAttribute('value', curUrl || '');
11686	webFontUrlInput.style.marginLeft = '4px';
11687	webFontUrlInput.style.width = '250px';
11688	webFontUrlInput.className = 'dlg_fontUrl';
11689
11690	td = document.createElement('td');
11691	td.appendChild(webFontUrlInput);
11692	row.appendChild(td);
11693
11694	if (Editor.enableWebFonts)
11695	{
11696		tbody.appendChild(row);
11697	}
11698
11699	this.init = function()
11700	{
11701		var input = sysFontInput;
11702
11703		if (curType == 'g')
11704		{
11705			input = googleFontInput;
11706		}
11707		else if (curType == 'w' && Editor.enableWebFonts)
11708		{
11709			input = webFontInput;
11710		}
11711
11712		input.focus();
11713
11714		if (mxClient.IS_GC || mxClient.IS_FF || document.documentMode >= 5)
11715		{
11716			input.select();
11717		}
11718		else
11719		{
11720			document.execCommand('selectAll', false, null);
11721		}
11722	};
11723
11724	row = document.createElement('tr');
11725	td = document.createElement('td');
11726	td.colSpan = 2;
11727	td.style.paddingTop = '20px';
11728	td.style.whiteSpace = 'nowrap';
11729	td.setAttribute('align', 'right');
11730
11731	if (!editorUi.isOffline())
11732	{
11733		var helpBtn = mxUtils.button(mxResources.get('help'), function()
11734		{
11735			editorUi.openLink('https://www.diagrams.net/blog/external-fonts');
11736		});
11737
11738		helpBtn.className = 'geBtn';
11739		td.appendChild(helpBtn);
11740	}
11741
11742	var cancelBtn = mxUtils.button(mxResources.get('cancel'), function()
11743	{
11744		editorUi.hideDialog();
11745		fn();
11746	});
11747	cancelBtn.className = 'geBtn';
11748
11749	if (editorUi.editor.cancelFirst)
11750	{
11751		td.appendChild(cancelBtn);
11752	}
11753
11754	function validateFn(fontName, fontUrl, type)
11755	{
11756		var urlPattern = /(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?/;
11757
11758		if (fontName == null || fontName.length == 0)
11759		{
11760			table.querySelector('.dlg_fontName_' + type).style.border = '1px solid red';
11761			return false;
11762		}
11763
11764		if (type == 'w' && !urlPattern.test(fontUrl))
11765		{
11766			table.querySelector('.dlg_fontUrl').style.border = '1px solid red';
11767			return false;
11768		}
11769
11770		return true;
11771	};
11772
11773	var okBtn = mxUtils.button(mxResources.get('apply'), function()
11774	{
11775		var fontName, fontUrl, type;
11776
11777		if (sysFontRadio.checked)
11778		{
11779			fontName = sysFontInput.value;
11780			type = 's';
11781		}
11782		else if (googleFontRadio.checked)
11783		{
11784			fontName = googleFontInput.value;
11785			fontUrl = Editor.GOOGLE_FONTS + encodeURIComponent(fontName).replace(/%20/g, '+');
11786			type = 'g';
11787		}
11788		else if (webFontRadio.checked)
11789		{
11790			fontName = webFontInput.value;
11791			fontUrl = webFontUrlInput.value;
11792			type = 'w';
11793		}
11794
11795		if (validateFn(fontName, fontUrl, type))
11796		{
11797			fn(fontName, fontUrl, type);
11798			editorUi.hideDialog();
11799		}
11800	});
11801	okBtn.className = 'geBtn gePrimaryBtn';
11802
11803	function enterSubmit(e)
11804	{
11805		this.style.border = '';
11806
11807		if (e.keyCode == 13)
11808		{
11809			okBtn.click();
11810		}
11811	};
11812
11813	mxEvent.addListener(sysFontInput, 'keypress', enterSubmit);
11814	mxEvent.addListener(googleFontInput, 'keypress', enterSubmit);
11815	mxEvent.addListener(webFontInput, 'keypress', enterSubmit);
11816	mxEvent.addListener(webFontUrlInput, 'keypress', enterSubmit);
11817
11818	mxEvent.addListener(sysFontInput, 'focus', function()
11819	{
11820		sysFontRadio.setAttribute('checked', 'checked');
11821		sysFontRadio.checked = true;
11822	});
11823
11824	mxEvent.addListener(googleFontInput, 'focus', function()
11825	{
11826		googleFontRadio.setAttribute('checked', 'checked');
11827		googleFontRadio.checked = true;
11828	});
11829
11830	mxEvent.addListener(webFontInput, 'focus', function()
11831	{
11832		webFontRadio.setAttribute('checked', 'checked');
11833		webFontRadio.checked = true;
11834	});
11835
11836	mxEvent.addListener(webFontUrlInput, 'focus', function()
11837	{
11838		webFontRadio.setAttribute('checked', 'checked');
11839		webFontRadio.checked = true;
11840	});
11841
11842	td.appendChild(okBtn);
11843
11844	if (!editorUi.editor.cancelFirst)
11845	{
11846		td.appendChild(cancelBtn);
11847	}
11848
11849	row.appendChild(td);
11850	tbody.appendChild(row);
11851	table.appendChild(tbody);
11852
11853	this.container = table;
11854};
11855
11856/* Aspect Dialog
11857 * @module drawio/aspect-dialog
11858 */
11859function AspectDialog(editorUi, pageId, layerIds, okFn, cancelFn)
11860{
11861	this.aspect = {pageId : pageId || (editorUi.pages? editorUi.pages[0].getId() : null), layerIds : layerIds || []};
11862	var div = document.createElement('div');
11863
11864	var title = document.createElement('h5');
11865	title.style.margin = '0 0 10px';
11866	mxUtils.write(title, mxResources.get('pages'));
11867	div.appendChild(title);
11868
11869	var pagesContainer = document.createElement('div');
11870	pagesContainer.className = 'geAspectDlgList';
11871	div.appendChild(pagesContainer);
11872
11873	title = document.createElement('h5');
11874	title.style.margin = '0 0 10px';
11875	mxUtils.write(title, mxResources.get('layers'));
11876	div.appendChild(title);
11877
11878	var layersContainer = document.createElement('div');
11879	layersContainer.className = 'geAspectDlgList';
11880	div.appendChild(layersContainer);
11881
11882	this.pagesContainer = pagesContainer;
11883	this.layersContainer = layersContainer;
11884	this.ui = editorUi;
11885
11886	var btns = document.createElement('div');
11887	btns.style.marginTop = '16px';
11888	btns.style.textAlign = 'center';
11889
11890	var cancelBtn = mxUtils.button(mxResources.get('cancel'), function()
11891	{
11892		editorUi.hideDialog();
11893
11894		if (cancelFn != null)
11895		{
11896			cancelFn();
11897		}
11898	});
11899
11900	cancelBtn.className = 'geBtn';
11901
11902	if (editorUi.editor.cancelFirst)
11903	{
11904		btns.appendChild(cancelBtn);
11905	}
11906
11907	var okBtn = mxUtils.button(mxResources.get('ok'), mxUtils.bind(this, function()
11908	{
11909		editorUi.hideDialog();
11910		okFn({pageId: this.selectedPage, layerIds: Object.keys(this.selectedLayers)});
11911	}));
11912
11913	btns.appendChild(okBtn);
11914	okBtn.className = 'geBtn gePrimaryBtn';
11915
11916	if (!editorUi.editor.cancelFirst)
11917	{
11918		btns.appendChild(cancelBtn);
11919	}
11920
11921	okBtn.setAttribute('disabled', 'disabled');
11922	this.okBtn = okBtn;
11923	div.appendChild(btns);
11924	this.container = div;
11925};
11926
11927//Drawing the graph with dialog not visible doesn't get dimensions right. It has to be visible!
11928AspectDialog.prototype.init = function()
11929{
11930	var xml = this.ui.getFileData(true); //Force pages to update their nodes
11931
11932	if (this.ui.pages)
11933	{
11934		for (var i = 0; i < this.ui.pages.length; i++)
11935		{
11936			var page = this.ui.updatePageRoot(this.ui.pages[i]);
11937
11938			this.createPageItem(page.getId(), page.getName(), page.node);
11939		}
11940	}
11941	else
11942	{
11943		this.createPageItem('1', 'Page-1', mxUtils.parseXml(xml).documentElement);
11944	}
11945};
11946
11947AspectDialog.prototype.createViewer = function(container, pageNode, layerId, defaultBackground)
11948{
11949	mxEvent.disableContextMenu(container);
11950	container.style.userSelect = 'none';
11951
11952	var graph = new Graph(container);
11953	graph.setTooltips(false);
11954	graph.setEnabled(false);
11955	graph.setPanning(false);
11956	graph.minFitScale = null;
11957	graph.maxFitScale = null;
11958	graph.centerZoom = true;
11959
11960	var node = pageNode.nodeName == 'mxGraphModel'? pageNode : Editor.parseDiagramNode(pageNode); //Handles compressed and non-compressed page node
11961
11962	if (node != null)
11963	{
11964		var bg = node.getAttribute('background');
11965
11966		if (bg == null || bg == '' || bg == mxConstants.NONE)
11967		{
11968			bg = (defaultBackground != null) ? defaultBackground : '#ffffff';
11969		}
11970
11971		container.style.backgroundColor = bg;
11972
11973		var codec = new mxCodec(node.ownerDocument);
11974		var model = graph.getModel();
11975		codec.decode(node, model);
11976
11977		var childCount = model.getChildCount(model.root);
11978
11979		var showAll = layerId == null;
11980
11981		// handle layers visibility
11982		for (var i = 0; i < childCount; i++)
11983		{
11984			var child = model.getChildAt(model.root, i);
11985			model.setVisible(child, showAll || layerId == child.id);
11986		}
11987
11988		graph.maxFitScale = 1;
11989		graph.fit(0);
11990		graph.center();
11991	}
11992
11993	return graph;
11994};
11995
11996AspectDialog.prototype.createPageItem = function(pageId, pageName, pageNode)
11997{
11998	var $listItem = document.createElement('div');
11999	$listItem.className = 'geAspectDlgListItem';
12000	$listItem.setAttribute('data-page-id', pageId)
12001	$listItem.innerHTML = '<div style="max-width: 100%; max-height: 100%;"></div><div class="geAspectDlgListItemText">' + mxUtils.htmlEntities(pageName) + '</div>';
12002
12003	this.pagesContainer.appendChild($listItem);
12004
12005	var graph = this.createViewer($listItem.childNodes[0], pageNode);
12006
12007	var onClick = mxUtils.bind(this, function()
12008	{
12009		if (this.selectedItem != null)
12010		{
12011			this.selectedItem.className = 'geAspectDlgListItem';
12012		}
12013
12014		this.selectedItem = $listItem;
12015		this.selectedPage = pageId;
12016		$listItem.className += ' geAspectDlgListItemSelected';
12017		this.layersContainer.innerHTML = '';
12018		this.selectedLayers = {};
12019		this.okBtn.setAttribute('disabled', 'disabled');
12020
12021		var graphModel = graph.model;
12022		var layers = graphModel.getChildCells(graphModel.getRoot());
12023
12024		for (var i = 0; i < layers.length; i++)
12025		{
12026			this.createLayerItem(layers[i], pageId, graph, pageNode);
12027		}
12028	});
12029
12030	mxEvent.addListener($listItem, 'click', onClick);
12031
12032	if(this.aspect.pageId == pageId)
12033	{
12034		onClick();
12035	}
12036};
12037
12038AspectDialog.prototype.createLayerItem = function(layer, pageId, graph, pageNode)
12039{
12040	var layerName = graph.convertValueToString(layer) || (mxResources.get('background') || 'Background');
12041	var $listItem = document.createElement('div');
12042	$listItem.setAttribute('data-layer-id', layer.id);
12043	$listItem.className = 'geAspectDlgListItem';
12044	$listItem.innerHTML = '<div style="max-width: 100%; max-height: 100%;"></div><div class="geAspectDlgListItemText">' + mxUtils.htmlEntities(layerName) + '</div>';
12045	this.layersContainer.appendChild($listItem);
12046
12047	this.createViewer($listItem.childNodes[0], pageNode, layer.id);
12048
12049	var onClick = mxUtils.bind(this, function()
12050	{
12051		if ($listItem.className.indexOf('geAspectDlgListItemSelected') >= 0) //Selected
12052		{
12053			$listItem.className = 'geAspectDlgListItem';
12054			delete this.selectedLayers[layer.id];
12055
12056			if (Object.keys(this.selectedLayers).length == 0)
12057			{
12058				this.okBtn.setAttribute('disabled', 'disabled');
12059			}
12060		}
12061		else
12062		{
12063			$listItem.className += ' geAspectDlgListItemSelected';
12064			this.selectedLayers[layer.id] = true;
12065			this.okBtn.removeAttribute('disabled');
12066		}
12067	});
12068
12069	mxEvent.addListener($listItem, 'click', onClick);
12070
12071	if(this.aspect.layerIds.indexOf(layer.id) != -1)
12072	{
12073		onClick();
12074	}
12075};
12076
12077/**
12078 * Constructs a new page setup dialog.
12079 */
12080var FilePropertiesDialog = function(editorUi)
12081{
12082	var row, td;
12083	var table = document.createElement('table');
12084	var tbody = document.createElement('tbody');
12085	table.style.width = '100%';
12086	table.style.marginTop = '8px';
12087
12088	var file = editorUi.getCurrentFile();
12089	var filename = (file != null && file.getTitle() != null) ?
12090		file.getTitle() : editorUi.defaultFilename;
12091	var isPng = /(\.png)$/i.test(filename);
12092	var apply = null;
12093
12094	if (isPng)
12095	{
12096		var scale = 1;
12097		var border = 0;
12098		var node = editorUi.fileNode;
12099
12100		if (node != null)
12101		{
12102			if (node.hasAttribute('scale'))
12103			{
12104				scale = parseFloat(node.getAttribute('scale'));
12105			}
12106
12107			if (node.hasAttribute('border'))
12108			{
12109				border = parseInt(node.getAttribute('border'));
12110			}
12111		}
12112
12113		row = document.createElement('tr');
12114		td = document.createElement('td');
12115		td.style.whiteSpace = 'nowrap';
12116		td.style.fontSize = '10pt';
12117		td.style.width = '120px';
12118		mxUtils.write(td, mxResources.get('zoom') + ':');
12119
12120		row.appendChild(td);
12121
12122		var zoomInput = document.createElement('input');
12123		zoomInput.setAttribute('value', (scale * 100) + '%');
12124		zoomInput.style.marginLeft = '4px';
12125		zoomInput.style.width ='180px';
12126
12127		td = document.createElement('td');
12128		td.style.whiteSpace = 'nowrap';
12129		td.appendChild(zoomInput);
12130		row.appendChild(td);
12131		tbody.appendChild(row);
12132
12133		row = document.createElement('tr');
12134		td = document.createElement('td');
12135		td.style.whiteSpace = 'nowrap';
12136		td.style.fontSize = '10pt';
12137		td.style.width = '120px';
12138		mxUtils.write(td, mxResources.get('borderWidth') + ':');
12139
12140		row.appendChild(td);
12141
12142		var borderInput = document.createElement('input');
12143		borderInput.setAttribute('value', border);
12144		borderInput.style.marginLeft = '4px';
12145		borderInput.style.width ='180px';
12146
12147		td = document.createElement('td');
12148		td.style.whiteSpace = 'nowrap';
12149		td.appendChild(borderInput);
12150		row.appendChild(td);
12151		tbody.appendChild(row);
12152
12153		this.init = function()
12154		{
12155			zoomInput.focus();
12156
12157			if (mxClient.IS_GC || mxClient.IS_FF || document.documentMode >= 5)
12158			{
12159				zoomInput.select();
12160			}
12161			else
12162			{
12163				document.execCommand('selectAll', false, null);
12164			}
12165		};
12166
12167		apply = function()
12168		{
12169			if (editorUi.fileNode != null)
12170			{
12171				editorUi.fileNode.setAttribute('scale', Math.max(0, parseInt(zoomInput.value) / 100));
12172				editorUi.fileNode.setAttribute('border', Math.max(0, parseInt(borderInput.value)));
12173
12174				if (file != null)
12175				{
12176					file.fileChanged();
12177				}
12178			}
12179
12180			editorUi.hideDialog();
12181		};
12182	}
12183	else
12184	{
12185		var compressed = (file != null) ? file.isCompressed() : Editor.compressXml;
12186
12187		row = document.createElement('tr');
12188		td = document.createElement('td');
12189		td.style.whiteSpace = 'nowrap';
12190		td.style.fontSize = '10pt';
12191		td.style.width = '120px';
12192		mxUtils.write(td, mxResources.get('compressed') + ':');
12193
12194		row.appendChild(td);
12195
12196		var compressedInput = document.createElement('input');
12197		compressedInput.setAttribute('type', 'checkbox');
12198
12199		if (compressed)
12200		{
12201			compressedInput.setAttribute('checked', 'checked');
12202			compressedInput.defaultChecked = true;
12203		}
12204
12205		td = document.createElement('td');
12206		td.style.whiteSpace = 'nowrap';
12207		td.appendChild(compressedInput);
12208		row.appendChild(td);
12209		tbody.appendChild(row);
12210
12211		this.init = function()
12212		{
12213			compressedInput.focus();
12214		};
12215
12216		apply = function()
12217		{
12218			if (editorUi.fileNode != null)
12219			{
12220				editorUi.fileNode.setAttribute('compressed', (compressedInput.checked) ? 'true' : 'false');
12221
12222				if (file != null)
12223				{
12224					file.fileChanged();
12225				}
12226			}
12227
12228			editorUi.hideDialog();
12229		};
12230	}
12231
12232	var genericBtn = mxUtils.button(mxResources.get('apply'), apply);
12233	genericBtn.className = 'geBtn gePrimaryBtn';
12234
12235	row = document.createElement('tr');
12236	td = document.createElement('td');
12237	td.colSpan = 2;
12238	td.style.paddingTop = '20px';
12239	td.style.whiteSpace = 'nowrap';
12240	td.setAttribute('align', 'right');
12241
12242	var cancelBtn = mxUtils.button(mxResources.get('cancel'), function()
12243	{
12244		editorUi.hideDialog();
12245	});
12246	cancelBtn.className = 'geBtn';
12247
12248	if (editorUi.editor.cancelFirst)
12249	{
12250		td.appendChild(cancelBtn);
12251	}
12252
12253	td.appendChild(genericBtn);
12254
12255	if (!editorUi.editor.cancelFirst)
12256	{
12257		td.appendChild(cancelBtn);
12258	}
12259
12260	row.appendChild(td);
12261	tbody.appendChild(row);
12262	table.appendChild(tbody);
12263
12264	this.container = table;
12265};
12266