GraphViewer.js 71 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771
  1. /**
  2. * Copyright (c) 2006-2016, JGraph Ltd
  3. */
  4. // Disables theme in viewer and lightbox
  5. Editor.currentTheme = '';
  6. window.uiTheme = '';
  7. /**
  8. * No CSS and resources available in embed mode. Parameters and docs:
  9. * https://www.drawio.com/doc/faq/embed-html-options
  10. */
  11. GraphViewer = function(container, xmlNode, graphConfig)
  12. {
  13. this.init(container, xmlNode, graphConfig);
  14. };
  15. // Editor inherits from mxEventSource
  16. mxUtils.extend(GraphViewer, mxEventSource);
  17. /**
  18. * Redirects editing to absolue URLs.
  19. */
  20. GraphViewer.prototype.editBlankUrl = (urlParams['dev'] == '1') ?
  21. 'https://test.draw.io/' : 'https://app.diagrams.net/';
  22. /**
  23. * Base URL for relative images.
  24. */
  25. GraphViewer.prototype.imageBaseUrl = window.DRAWIO_BASE_URL + '/';
  26. /**
  27. * Redirects editing to absolue URLs.
  28. */
  29. GraphViewer.prototype.toolbarHeight = (document.compatMode == 'BackCompat') ? 24 : 26;
  30. /**
  31. * Redirects editing to absolue URLs.
  32. */
  33. GraphViewer.prototype.lightboxChrome = true;
  34. /**
  35. * Redirects editing to absolue URLs.
  36. */
  37. GraphViewer.prototype.lightboxZIndex = 999;
  38. /**
  39. * Redirects editing to absolue URLs.
  40. */
  41. GraphViewer.prototype.toolbarZIndex = 999;
  42. /**
  43. * If automatic fit should be enabled if zoom is disabled. Default is true.
  44. */
  45. GraphViewer.prototype.autoFit = false;
  46. /**
  47. * If automatic crop should be enabled when layers are toggled. Default is false.
  48. */
  49. GraphViewer.prototype.autoCrop = false;
  50. /**
  51. * Specifies if the graph should be moved if a layer is made visible that
  52. * extends the graph beyong the top left corner. Default is true. Is this is
  53. * false then the viewport of the viewer will include all cells in all layers
  54. * regardless of their initial visible state.
  55. */
  56. GraphViewer.prototype.autoOrigin = true;
  57. /**
  58. * If the diagram should be centered. Default is false.
  59. */
  60. GraphViewer.prototype.center = false;
  61. /**
  62. * Force centering of the diagram. Default is false.
  63. */
  64. GraphViewer.prototype.forceCenter = false;
  65. /**
  66. * Specifies if zooming in for auto fit is allowed. Default is false.
  67. */
  68. GraphViewer.prototype.allowZoomIn = false;
  69. /**
  70. * Specifies if zooming out for auto fit is allowed. Default is true.
  71. * If toolbar-nohide is true then overflow content is visible.
  72. */
  73. GraphViewer.prototype.allowZoomOut = true;
  74. /**
  75. * Whether the title should be shown as a tooltip if the toolbar is disabled.
  76. * Default is false.
  77. */
  78. GraphViewer.prototype.showTitleAsTooltip = false;
  79. /**
  80. * Specifies if the constructur should delay the rendering if the container
  81. * is not visible by default.
  82. */
  83. GraphViewer.prototype.checkVisibleState = true;
  84. /**
  85. * Defines the minimum height of the container. Default is 28.
  86. */
  87. GraphViewer.prototype.minHeight = 28;
  88. /**
  89. * Defines the minimum width of the container. Default is 100.
  90. */
  91. GraphViewer.prototype.minWidth = 100;
  92. /**
  93. * Implements viewBox to keep the contents inside the bounding box
  94. * of the container. This is currently not supported in Safari (due
  95. * to clipping in labels with viewBox) and all browsers that do not
  96. * support foreignObjects (eg. IE11).
  97. */
  98. GraphViewer.prototype.responsive = false;
  99. /**
  100. * Dark mode
  101. */
  102. GraphViewer.prototype.darkMode = false;
  103. /**
  104. * Initializes the viewer.
  105. */
  106. GraphViewer.prototype.init = function(container, xmlNode, graphConfig)
  107. {
  108. this.graphConfig = (graphConfig != null) ? graphConfig : {};
  109. this.autoFit = (this.graphConfig['auto-fit'] != null) ?
  110. this.graphConfig['auto-fit'] : this.autoFit;
  111. this.autoCrop = (this.graphConfig['auto-crop'] != null) ?
  112. this.graphConfig['auto-crop'] : this.autoCrop;
  113. this.autoOrigin = (this.graphConfig['auto-origin'] != null) ?
  114. this.graphConfig['auto-origin'] : this.autoOrigin;
  115. this.allowZoomOut = (this.graphConfig['allow-zoom-out'] != null) ?
  116. this.graphConfig['allow-zoom-out'] : this.allowZoomOut;
  117. this.allowZoomIn = (this.graphConfig['allow-zoom-in'] != null) ?
  118. this.graphConfig['allow-zoom-in'] : this.allowZoomIn;
  119. this.forceCenter = (this.graphConfig['forceCenter'] != null) ?
  120. this.graphConfig['forceCenter'] : this.forceCenter;
  121. this.center = (this.graphConfig['center'] != null) ?
  122. this.graphConfig['center'] : (this.center || this.forceCenter);
  123. this.checkVisibleState = (this.graphConfig['check-visible-state'] != null) ?
  124. this.graphConfig['check-visible-state'] : this.checkVisibleState;
  125. this.darkMode = (this.graphConfig['dark-mode'] != null) ?
  126. this.graphConfig['dark-mode'] : this.darkMode;
  127. this.toolbarItems = (this.graphConfig.toolbar != null) ?
  128. this.graphConfig.toolbar.split(' ') : [];
  129. this.zoomEnabled = mxUtils.indexOf(this.toolbarItems, 'zoom') >= 0;
  130. this.layersEnabled = mxUtils.indexOf(this.toolbarItems, 'layers') >= 0;
  131. this.tagsEnabled = mxUtils.indexOf(this.toolbarItems, 'tags') >= 0;
  132. this.lightboxEnabled = mxUtils.indexOf(this.toolbarItems, 'lightbox') >= 0;
  133. this.lightboxClickEnabled = this.graphConfig.lightbox != false;
  134. this.initialOverflow = document.body.style.overflow;
  135. this.initialWidth = (container != null) ? container.style.width : null;
  136. this.widthIsEmpty = (this.initialWidth != null) ? this.initialWidth == '' : true;
  137. this.currentPage = parseInt(this.graphConfig.page) || 0;
  138. this.responsive = ((this.graphConfig['responsive'] != null) ?
  139. this.graphConfig['responsive'] : this.responsive) &&
  140. !this.zoomEnabled && !mxClient.NO_FO && !mxClient.IS_SF;
  141. this.pageId = this.graphConfig.pageId;
  142. this.editor = null;
  143. var self = this;
  144. if (this.graphConfig['toolbar-position'] == 'inline')
  145. {
  146. this.minHeight += this.toolbarHeight;
  147. }
  148. if (xmlNode != null)
  149. {
  150. this.xmlDocument = xmlNode.ownerDocument;
  151. this.xmlNode = xmlNode;
  152. this.xml = mxUtils.getXml(xmlNode);
  153. if (container != null)
  154. {
  155. var render = mxUtils.bind(this, function()
  156. {
  157. this.graph = new Graph(container);
  158. if (this.darkMode)
  159. {
  160. if (Editor.enableCssDarkMode)
  161. {
  162. container.classList.add('geDarkMode');
  163. }
  164. else
  165. {
  166. EditorUi.setGraphDarkMode(this.graph, null, true);
  167. }
  168. }
  169. this.graph.enableFlowAnimation = true;
  170. this.graph.defaultPageBackgroundColor = 'transparent';
  171. this.graph.diagramBackgroundColor = 'transparent';
  172. this.graph.transparentBackground = false;
  173. if (this.responsive && this.graph.dialect == mxConstants.DIALECT_SVG)
  174. {
  175. var root = this.graph.view.getDrawPane().ownerSVGElement;
  176. if (this.graphConfig.border != null)
  177. {
  178. root.style.padding = this.graphConfig.border + 'px';
  179. }
  180. else if (container.style.padding == '')
  181. {
  182. root.style.padding = '8px';
  183. }
  184. root.style.boxSizing = 'border-box';
  185. root.style.overflow = 'visible';
  186. this.graph.fit = function()
  187. {
  188. // Automatic
  189. };
  190. this.graph.sizeDidChange = function()
  191. {
  192. var bounds = this.view.graphBounds;
  193. var tr = this.view.translate;
  194. root.setAttribute('viewBox',
  195. (bounds.x + tr.x - this.panDx) + ' ' +
  196. (bounds.y + tr.y - this.panDy) + ' ' +
  197. (bounds.width + 1) + ' ' +
  198. (bounds.height + 1));
  199. this.container.style.backgroundColor =
  200. root.style.backgroundColor;
  201. this.fireEvent(new mxEventObject(mxEvent.SIZE, 'bounds', bounds));
  202. };
  203. }
  204. if (this.graphConfig.move)
  205. {
  206. this.graph.isMoveCellsEvent = function(evt)
  207. {
  208. return true;
  209. };
  210. }
  211. // Adds lightbox and link handling for shapes
  212. if (this.lightboxClickEnabled)
  213. {
  214. container.style.cursor = 'pointer';
  215. }
  216. // Hack for using EditorUi methods on the graph instance
  217. this.editor = new Editor(true, null, null, this.graph);
  218. this.editor.editBlankUrl = this.editBlankUrl;
  219. this.graph.lightbox = true;
  220. this.graph.centerZoom = false;
  221. this.graph.autoExtend = false;
  222. this.graph.autoScroll = false;
  223. this.graph.setEnabled(false);
  224. if (this.graphConfig['toolbar-nohide'] == true)
  225. {
  226. this.editor.defaultGraphOverflow = 'visible';
  227. }
  228. // Extract graph model from html & svg formats
  229. var temp = this.editor.extractGraphModel(this.xmlNode, true);
  230. if (temp != null && temp != xmlNode)
  231. {
  232. try
  233. {
  234. this.xml = mxUtils.getXml(temp);
  235. this.xmlNode = temp;
  236. this.xmlDocument = temp.ownerDocument;
  237. }
  238. catch (e)
  239. {
  240. // ignore
  241. }
  242. }
  243. // Handles relative images
  244. this.graph.getImageFromBundles = function(key)
  245. {
  246. return self.getImageUrl(key);
  247. };
  248. if (mxClient.IS_SVG)
  249. {
  250. // LATER: Add shadow for labels in graph.container (eg. math, NO_FO), scaling
  251. this.graph.addSvgShadow(this.graph.view.canvas.ownerSVGElement, null, true);
  252. }
  253. // Adds page placeholders
  254. if (this.xmlNode.nodeName == 'mxfile')
  255. {
  256. var diagrams = this.xmlNode.getElementsByTagName('diagram');
  257. if (diagrams.length > 0)
  258. {
  259. // Finds index for given page ID
  260. if (this.pageId != null)
  261. {
  262. for (var i = 0; i < diagrams.length; i++)
  263. {
  264. if (this.pageId == diagrams[i].getAttribute('id'))
  265. {
  266. this.currentPage = i;
  267. break;
  268. }
  269. }
  270. }
  271. var graphGetGlobalVariable = this.graph.getGlobalVariable;
  272. this.graph.getGlobalVariable = function(name)
  273. {
  274. var diagram = diagrams[self.currentPage];
  275. if (name == 'page')
  276. {
  277. return diagram.getAttribute('name') || 'Page-' + (self.currentPage + 1);
  278. }
  279. else if (name == 'pagenumber')
  280. {
  281. return self.currentPage + 1;
  282. }
  283. else if (name == 'pagecount')
  284. {
  285. return diagrams.length;
  286. }
  287. return graphGetGlobalVariable.apply(this, arguments);
  288. };
  289. }
  290. }
  291. this.diagrams = [];
  292. var lastXmlNode = null;
  293. this.selectPage = function(number)
  294. {
  295. if(this.handlingResize)
  296. return;
  297. this.currentPage = mxUtils.mod(number, this.diagrams.length);
  298. this.updateGraphXml(Editor.parseDiagramNode(this.diagrams[this.currentPage]));
  299. };
  300. this.selectPageById = function(id)
  301. {
  302. var index = this.getIndexById(id);
  303. var found = index >= 0;
  304. if (found)
  305. {
  306. this.selectPage(index);
  307. }
  308. return found;
  309. };
  310. var update = mxUtils.bind(this, function()
  311. {
  312. if (this.xmlNode == null || this.xmlNode.nodeName != 'mxfile')
  313. {
  314. this.diagrams = [];
  315. }
  316. if (this.xmlNode != lastXmlNode)
  317. {
  318. this.diagrams = this.xmlNode.getElementsByTagName('diagram');
  319. lastXmlNode = this.xmlNode;
  320. }
  321. });
  322. // Replaces background page reference with SVG
  323. var graphSetBackgroundImage = this.graph.setBackgroundImage;
  324. this.graph.setBackgroundImage = function(img)
  325. {
  326. if (img != null && Graph.isPageLink(img.src))
  327. {
  328. var src = img.src;
  329. var comma = src.indexOf(',');
  330. if (comma > 0)
  331. {
  332. var index = self.getIndexById(src.substring(comma + 1));
  333. if (index >= 0)
  334. {
  335. img = self.getImageForGraphModel(
  336. Editor.parseDiagramNode(
  337. self.diagrams[index]));
  338. img.originalSrc = src;
  339. }
  340. }
  341. }
  342. graphSetBackgroundImage.apply(this, arguments);
  343. };
  344. // Overrides graph bounds to include background pages
  345. var graphGetGraphBounds = this.graph.getGraphBounds;
  346. this.graph.getGraphBounds = function(img)
  347. {
  348. var bounds = graphGetGraphBounds.apply(this, arguments);
  349. var img = this.backgroundImage;
  350. // Check img.originalSrc to ignore background
  351. // images but not background pages
  352. if (img != null)
  353. {
  354. var t = this.view.translate;
  355. var s = this.view.scale;
  356. bounds = mxRectangle.fromRectangle(bounds);
  357. bounds.add(new mxRectangle(
  358. (t.x + img.x) * s, (t.y + img.y) * s,
  359. img.width * s, img.height * s));
  360. }
  361. return bounds;
  362. };
  363. // LATER: Add event for setGraphXml
  364. this.addListener('xmlNodeChanged', update);
  365. update();
  366. // Passes current page via urlParams global variable
  367. // to let the parser know which page we're using
  368. urlParams['page'] = self.currentPage;
  369. var visible = null;
  370. this.graph.getModel().beginUpdate();
  371. try
  372. {
  373. // Required for correct parsing of fold parameter
  374. urlParams['nav'] = (this.graphConfig.nav != false) ? '1' : '0';
  375. this.editor.setGraphXml(this.xmlNode);
  376. this.graph.view.scale = this.graphConfig.zoom || 1;
  377. visible = this.setLayersVisible();
  378. if (!this.responsive)
  379. {
  380. this.graph.border = (this.graphConfig.border != null) ? this.graphConfig.border : 8;
  381. }
  382. }
  383. finally
  384. {
  385. this.graph.getModel().endUpdate();
  386. }
  387. // Adds left-button panning only if scrollbars are visible
  388. if (!this.responsive)
  389. {
  390. this.graph.panningHandler.isForcePanningEvent = function(me)
  391. {
  392. return !mxEvent.isPopupTrigger(me.getEvent()) &&
  393. this.graph.container.style.overflow == 'auto';
  394. };
  395. this.graph.panningHandler.useLeftButtonForPanning = true;
  396. this.graph.panningHandler.ignoreCell = true;
  397. this.graph.panningHandler.usePopupTrigger = false;
  398. this.graph.panningHandler.pinchEnabled = false;
  399. }
  400. this.graph.setPanning(false);
  401. if (this.graphConfig.toolbar != null)
  402. {
  403. this.addToolbar();
  404. }
  405. else if (this.graphConfig.title != null && this.showTitleAsTooltip)
  406. {
  407. container.setAttribute('title', this.graphConfig.titleTooltip || this.graphConfig.title);
  408. }
  409. if (!this.responsive)
  410. {
  411. this.addSizeHandler();
  412. }
  413. // Crops to visible layers if no layers toolbar button
  414. if (this.showLayers(this.graph) && !this.forceCenter && (!this.layersEnabled || this.autoCrop))
  415. {
  416. this.crop();
  417. }
  418. this.addClickHandler(this.graph);
  419. this.graph.setTooltips(this.graphConfig.tooltips != false);
  420. this.graph.initialViewState = {
  421. translate: this.graph.view.translate.clone(),
  422. scale: this.graph.view.scale
  423. };
  424. if (visible != null)
  425. {
  426. this.setLayersVisible(visible);
  427. }
  428. this.graph.customLinkClicked = function(href, associatedCell)
  429. {
  430. try
  431. {
  432. if (Graph.isPageLink(href))
  433. {
  434. var comma = href.indexOf(',');
  435. if (!self.selectPageById(href.substring(comma + 1)))
  436. {
  437. alert(mxResources.get('pageNotFound') || 'Page not found');
  438. }
  439. }
  440. else
  441. {
  442. var bounds = this.getGraphBounds();
  443. this.handleCustomLink(href, associatedCell);
  444. if (!bounds.equals(this.getGraphBounds()))
  445. {
  446. self.crop();
  447. }
  448. }
  449. }
  450. catch (e)
  451. {
  452. alert(e.message);
  453. }
  454. return true;
  455. };
  456. // Updates origin after tree cell folding
  457. var graphFoldTreeCell = this.graph.foldTreeCell;
  458. this.graph.foldTreeCell = mxUtils.bind(this, function()
  459. {
  460. this.treeCellFolded = true;
  461. return graphFoldTreeCell.apply(this.graph, arguments);
  462. });
  463. this.fireEvent(new mxEventObject('render'));
  464. });
  465. var MutObs = window.MutationObserver ||
  466. window.WebKitMutationObserver ||
  467. window.MozMutationObserver;
  468. if (this.checkVisibleState && container.offsetWidth == 0 &&
  469. this.getAncestorDetails(container) == null &&
  470. typeof MutObs !== 'undefined')
  471. {
  472. // Delayed rendering if inside hidden container and event available
  473. var par = this.getObservableParent(container);
  474. var observer = new MutObs(mxUtils.bind(this, function(mutation)
  475. {
  476. if (container.offsetWidth > 0)
  477. {
  478. observer.disconnect();
  479. render();
  480. }
  481. }));
  482. observer.observe(par, {attributes: true});
  483. }
  484. else
  485. {
  486. // Immediate rendering in all other cases
  487. render();
  488. }
  489. }
  490. }
  491. };
  492. /**
  493. *
  494. */
  495. GraphViewer.prototype.getAncestorDetails = function(container)
  496. {
  497. while (container != null)
  498. {
  499. if (container.nodeName == 'DETAILS')
  500. {
  501. return container;
  502. }
  503. container = container.parentNode;
  504. }
  505. return null;
  506. };
  507. /**
  508. *
  509. */
  510. GraphViewer.prototype.getObservableParent = function(container)
  511. {
  512. var node = container.parentNode;
  513. while (node != document.body && node.parentNode != null &&
  514. mxUtils.getCurrentStyle(node).display !== 'none')
  515. {
  516. node = node.parentNode;
  517. }
  518. return node;
  519. };
  520. /**
  521. *
  522. */
  523. GraphViewer.prototype.getImageUrl = function(url)
  524. {
  525. if (url != null && url.substring(0, 7) != 'http://' &&
  526. url.substring(0, 8) != 'https://' && url.substring(0, 10) != 'data:image')
  527. {
  528. if (url.charAt(0) == '/')
  529. {
  530. url = url.substring(1, url.length);
  531. }
  532. url = this.imageBaseUrl + url;
  533. }
  534. return url;
  535. };
  536. /**
  537. *
  538. */
  539. GraphViewer.prototype.getImageForGraphModel = function(node)
  540. {
  541. var graph = Graph.createOffscreenGraph(this.graph.getStylesheet());
  542. graph.getGlobalVariable = this.graph.getGlobalVariable;
  543. document.body.appendChild(graph.container);
  544. var codec = new mxCodec(node.ownerDocument);
  545. var root = codec.decode(node).root;
  546. graph.model.setRoot(root);
  547. var svgRoot = graph.getSvg();
  548. var bounds = graph.getGraphBounds();
  549. document.body.removeChild(graph.container);
  550. return new mxImage(Editor.createSvgDataUri(mxUtils.getXml(svgRoot)),
  551. bounds.width, bounds.height, bounds.x, bounds.y);
  552. };
  553. /**
  554. *
  555. */
  556. GraphViewer.prototype.getIndexById = function(id)
  557. {
  558. if (this.diagrams != null)
  559. {
  560. for (var i = 0; i < this.diagrams.length; i++)
  561. {
  562. if (this.diagrams[i].getAttribute('id') == id)
  563. {
  564. return i;
  565. }
  566. }
  567. }
  568. return -1;
  569. };
  570. /**
  571. *
  572. */
  573. GraphViewer.prototype.setXmlNode = function(xmlNode)
  574. {
  575. //Extract graph model from html & svg formats
  576. xmlNode = this.editor.extractGraphModel(xmlNode, true);
  577. this.xmlDocument = xmlNode.ownerDocument;
  578. this.xml = mxUtils.getXml(xmlNode);
  579. this.xmlNode = xmlNode;
  580. this.updateGraphXml(xmlNode);
  581. this.fireEvent(new mxEventObject('xmlNodeChanged'));
  582. };
  583. /**
  584. *
  585. */
  586. GraphViewer.prototype.setFileNode = function(xmlNode)
  587. {
  588. if (this.xmlNode == null)
  589. {
  590. this.xmlDocument = xmlNode.ownerDocument;
  591. this.xml = mxUtils.getXml(xmlNode);
  592. this.xmlNode = xmlNode;
  593. }
  594. this.setGraphXml(xmlNode);
  595. };
  596. /**
  597. *
  598. */
  599. GraphViewer.prototype.updateGraphXml = function(xmlNode)
  600. {
  601. this.setGraphXml(xmlNode);
  602. this.fireEvent(new mxEventObject('graphChanged'));
  603. };
  604. /**
  605. *
  606. */
  607. GraphViewer.prototype.setLayersVisible = function(visible)
  608. {
  609. var allVisible = true;
  610. if (!this.autoOrigin)
  611. {
  612. var result = [];
  613. var model = this.graph.getModel();
  614. model.beginUpdate();
  615. try
  616. {
  617. for (var i = 0; i < model.getChildCount(model.root); i++)
  618. {
  619. var layer = model.getChildAt(model.root, i);
  620. allVisible = allVisible && model.isVisible(layer);
  621. result.push(model.isVisible(layer));
  622. model.setVisible(layer, (visible != null) ? visible[i] : true);
  623. }
  624. }
  625. finally
  626. {
  627. model.endUpdate();
  628. }
  629. }
  630. return (allVisible) ? null : result;
  631. };
  632. /**
  633. *
  634. */
  635. GraphViewer.prototype.setGraphXml = function(xmlNode)
  636. {
  637. if (this.graph != null)
  638. {
  639. this.graph.view.translate = new mxPoint();
  640. this.graph.view.scale = 1;
  641. var visible = null;
  642. this.graph.getModel().beginUpdate();
  643. try
  644. {
  645. this.graph.getModel().clear();
  646. this.editor.setGraphXml(xmlNode);
  647. visible = this.setLayersVisible(true);
  648. }
  649. finally
  650. {
  651. this.graph.getModel().endUpdate();
  652. }
  653. if (!this.responsive)
  654. {
  655. // Restores initial CSS state
  656. if (this.widthIsEmpty)
  657. {
  658. this.graph.container.style.width = '';
  659. this.graph.container.style.height = '';
  660. }
  661. else
  662. {
  663. this.graph.container.style.width = this.initialWidth;
  664. }
  665. this.positionGraph();
  666. }
  667. this.graph.initialViewState = {
  668. translate: this.graph.view.translate.clone(),
  669. scale: this.graph.view.scale
  670. };
  671. if (visible)
  672. {
  673. this.setLayersVisible(visible);
  674. }
  675. }
  676. };
  677. /**
  678. *
  679. */
  680. GraphViewer.prototype.addSizeHandler = function()
  681. {
  682. var container = this.graph.container;
  683. var bounds = this.graph.getGraphBounds();
  684. var updatingOverflow = false;
  685. if (this.graphConfig['toolbar-nohide'] != true)
  686. {
  687. container.style.overflow = 'hidden';
  688. }
  689. else
  690. {
  691. container.style.overflow = 'visible';
  692. }
  693. var updateOverflow = mxUtils.bind(this, function()
  694. {
  695. if (!updatingOverflow)
  696. {
  697. updatingOverflow = true;
  698. var tmp = this.graph.getGraphBounds();
  699. if (this.graphConfig['toolbar-nohide'] != true)
  700. {
  701. // Shows scrollbars if graph is larger than available width
  702. if (tmp.width + 2 * this.graph.border > container.offsetWidth - 2)
  703. {
  704. container.style.overflow = 'auto';
  705. }
  706. else
  707. {
  708. container.style.overflow = 'hidden';
  709. }
  710. }
  711. else
  712. {
  713. container.style.overflow = 'visible';
  714. }
  715. if (this.toolbar != null && this.graphConfig['toolbar-nohide'] != true)
  716. {
  717. var r = container.getBoundingClientRect();
  718. // Workaround for position:relative set in ResizeSensor
  719. var origin = mxUtils.getScrollOrigin(document.body)
  720. var b = (document.body.style.position === 'relative') ?
  721. document.body.getBoundingClientRect() :
  722. {left: -origin.x, top: -origin.y};
  723. r = {left: r.left - b.left, top: r.top - b.top, bottom: r.bottom - b.top, right: r.right - b.left};
  724. this.toolbar.style.left = r.left + 'px';
  725. if (this.graphConfig['toolbar-position'] == 'bottom')
  726. {
  727. this.toolbar.style.top = r.bottom - 1 + 'px';
  728. }
  729. else
  730. {
  731. if (this.graphConfig['toolbar-position'] != 'inline')
  732. {
  733. this.toolbar.style.width = Math.max(this.minToolbarWidth, container.offsetWidth) + 'px';
  734. this.toolbar.style.top = r.top + 1 + 'px';
  735. }
  736. else
  737. {
  738. this.toolbar.style.top = r.top + 'px';
  739. }
  740. }
  741. }
  742. else if (this.toolbar != null)
  743. {
  744. this.toolbar.style.width = Math.max(this.minToolbarWidth, container.offsetWidth) + 'px';
  745. }
  746. // Updates origin after tree cell folding
  747. if (this.treeCellFolded)
  748. {
  749. this.treeCellFolded = false;
  750. this.positionGraph(this.graph.view.translate);
  751. this.graph.initialViewState.translate = this.graph.view.translate.clone();
  752. }
  753. updatingOverflow = false;
  754. }
  755. });
  756. var lastOffsetWidth = null;
  757. var cachedOffsetWidth = null;
  758. this.handlingResize = false;
  759. // Installs function on instance
  760. this.fitGraph = mxUtils.bind(this, function(maxScale)
  761. {
  762. var cachedOffsetWidth = container.offsetWidth;
  763. if (cachedOffsetWidth != lastOffsetWidth && !this.handlingResize)
  764. {
  765. this.handlingResize = true;
  766. // Hides scrollbars to force update of translate
  767. if (container.style.overflow == 'auto')
  768. {
  769. container.style.overflow = 'hidden';
  770. }
  771. this.graph.maxFitScale = (maxScale != null) ? maxScale : (this.graphConfig.zoom ||
  772. ((this.allowZoomIn) ? null : 1));
  773. this.graph.fit(null, null, null, null, null, true);
  774. if (this.center || !(this.graphConfig.resize != false || container.style.height == ''))
  775. {
  776. this.graph.center();
  777. }
  778. this.graph.maxFitScale = null;
  779. if (this.graphConfig.resize != false || container.style.height == '')
  780. {
  781. this.updateContainerHeight(container, Math.max(this.minHeight,
  782. this.graph.getGraphBounds().height +
  783. 2 * this.graph.border + 1));
  784. }
  785. this.graph.initialViewState = {
  786. translate: this.graph.view.translate.clone(),
  787. scale: this.graph.view.scale
  788. };
  789. lastOffsetWidth = cachedOffsetWidth;
  790. // Workaround for fit triggering scrollbars triggering doResize (infinite loop)
  791. window.setTimeout(mxUtils.bind(this, function()
  792. {
  793. this.handlingResize = false;
  794. }), 0);
  795. }
  796. });
  797. if (GraphViewer.useResizeSensor)
  798. {
  799. if (document.documentMode <= 9)
  800. {
  801. mxEvent.addListener(window, 'resize', updateOverflow);
  802. this.graph.addListener('size', updateOverflow);
  803. }
  804. else
  805. {
  806. new ResizeSensor(this.graph.container, updateOverflow);
  807. }
  808. }
  809. if (this.graphConfig.resize || ((this.zoomEnabled || !this.autoFit) && this.graphConfig.resize != false))
  810. {
  811. this.graph.minimumContainerSize = new mxRectangle(0, 0, this.minWidth, this.minHeight);
  812. this.graph.resizeContainer = true;
  813. }
  814. else
  815. {
  816. // Sets initial size for responsive diagram to stop at actual size
  817. if (this.widthIsEmpty && !(container.style.height != '' && this.autoFit))
  818. {
  819. this.updateContainerWidth(container, bounds.width + 2 * this.graph.border);
  820. }
  821. if (this.graphConfig.resize != false || container.style.height == '')
  822. {
  823. this.updateContainerHeight(container, Math.max(this.minHeight, bounds.height + 2 * this.graph.border + 1));
  824. }
  825. if (!this.zoomEnabled && this.autoFit)
  826. {
  827. var lastOffsetWidth = null;
  828. var scheduledResize = null;
  829. var cachedOffsetWidth = null;
  830. var doResize = mxUtils.bind(this, function()
  831. {
  832. window.clearTimeout(scheduledResize);
  833. if (!this.handlingResize)
  834. {
  835. scheduledResize = window.setTimeout(mxUtils.bind(this, this.fitGraph), 100);
  836. }
  837. });
  838. if (GraphViewer.useResizeSensor)
  839. {
  840. if (document.documentMode <= 9)
  841. {
  842. mxEvent.addListener(window, 'resize', doResize);
  843. }
  844. else
  845. {
  846. new ResizeSensor(this.graph.container, doResize);
  847. }
  848. }
  849. }
  850. else if (!(document.documentMode <= 9))
  851. {
  852. this.graph.addListener('size', updateOverflow);
  853. }
  854. }
  855. var positionGraph = mxUtils.bind(this, function(origin)
  856. {
  857. // Allocates maximum width while setting initial view state
  858. var prev = container.style.minWidth;
  859. if (this.widthIsEmpty)
  860. {
  861. container.style.minWidth = '100%';
  862. }
  863. var maxHeight = (this.graphConfig['max-height'] != null) ? this.graphConfig['max-height'] :
  864. ((container.style.height != '' && this.autoFit) ? container.offsetHeight : undefined);
  865. if (container.offsetWidth > 0 && origin == null && this.allowZoomOut && (this.allowZoomIn ||
  866. bounds.width + 2 * this.graph.border > container.offsetWidth ||
  867. bounds.height + 2 * this.graph.border > maxHeight))
  868. {
  869. var maxScale = null;
  870. if (maxHeight != null && bounds.height + 2 * this.graph.border > maxHeight - 2)
  871. {
  872. maxScale = (maxHeight - 2 * this.graph.border - 2) / bounds.height;
  873. }
  874. this.fitGraph(maxScale);
  875. }
  876. else if (!this.widthIsEmpty && origin == null && !(this.graphConfig.resize != false || container.style.height == ''))
  877. {
  878. this.graph.center((!this.widthIsEmpty || bounds.width < this.minWidth) && this.graphConfig.resize != true);
  879. }
  880. else
  881. {
  882. origin = (origin != null) ? origin : new mxPoint();
  883. this.graph.view.setTranslate(Math.floor(this.graph.border - bounds.x / this.graph.view.scale) + origin.x,
  884. Math.floor(this.graph.border - bounds.y / this.graph.view.scale) + origin.y);
  885. lastOffsetWidth = container.offsetWidth;
  886. }
  887. container.style.minWidth = prev
  888. });
  889. if (document.documentMode == 8)
  890. {
  891. window.setTimeout(positionGraph, 0);
  892. }
  893. else
  894. {
  895. positionGraph();
  896. }
  897. // Installs function on instance
  898. this.positionGraph = function(origin)
  899. {
  900. bounds = this.graph.getGraphBounds();
  901. lastOffsetWidth = null;
  902. positionGraph(origin);
  903. };
  904. };
  905. /**
  906. * Moves the origin of the graph to the top, right corner.
  907. */
  908. GraphViewer.prototype.crop = function()
  909. {
  910. var graph = this.graph;
  911. var bounds = graph.getGraphBounds();
  912. var border = graph.border;
  913. var s = graph.view.scale;
  914. var x0 = (bounds.x != null) ? Math.floor(graph.view.translate.x - bounds.x / s + border) : border;
  915. var y0 = (bounds.y != null) ? Math.floor(graph.view.translate.y - bounds.y / s + border) : border;
  916. graph.view.setTranslate(x0, y0);
  917. };
  918. /**
  919. *
  920. */
  921. GraphViewer.prototype.updateContainerWidth = function(container, width)
  922. {
  923. container.style.width = width + 'px';
  924. };
  925. /**
  926. *
  927. */
  928. GraphViewer.prototype.updateContainerHeight = function(container, height)
  929. {
  930. if (this.forceCenter || this.zoomEnabled || !this.autoFit || document.compatMode == 'BackCompat' ||
  931. document.documentMode == 8)
  932. {
  933. container.style.height = height + 'px';
  934. }
  935. };
  936. /**
  937. * Shows the
  938. */
  939. GraphViewer.prototype.showLayers = function(graph, sourceGraph)
  940. {
  941. var layers = this.graphConfig.layers;
  942. var idx = (layers != null && layers.length > 0) ? layers.split(' ') : [];
  943. var layerIds = this.graphConfig.layerIds;
  944. var hasLayerIds = layerIds != null && layerIds.length > 0;
  945. var result = false;
  946. if (idx.length > 0 || hasLayerIds || sourceGraph != null)
  947. {
  948. var source = (sourceGraph != null) ? sourceGraph.getModel() : null;
  949. var model = graph.getModel();
  950. model.beginUpdate();
  951. try
  952. {
  953. var childCount = model.getChildCount(model.root);
  954. // Shows specified layers (eg. 0 1 3)
  955. if (source == null)
  956. {
  957. var layersFound = false, visibleLayers = {};
  958. if (hasLayerIds)
  959. {
  960. for (var i = 0; i < layerIds.length; i++)
  961. {
  962. var layer = model.getCell(layerIds[i]);
  963. if (layer != null)
  964. {
  965. layersFound = true;
  966. visibleLayers[layer.id] = true;
  967. }
  968. }
  969. }
  970. else
  971. {
  972. for (var i = 0; i < idx.length; i++)
  973. {
  974. var layer = model.getChildAt(model.root, parseInt(idx[i]));
  975. if (layer != null)
  976. {
  977. layersFound = true;
  978. visibleLayers[layer.id] = true;
  979. }
  980. }
  981. }
  982. //To prevent hiding all layers, only apply if the specified layers are found
  983. //This prevents incorrect settings from showing an empty viewer
  984. for (var i = 0; layersFound && i < childCount; i++)
  985. {
  986. var layer = model.getChildAt(model.root, i);
  987. model.setVisible(layer, visibleLayers[layer.id] || false);
  988. }
  989. }
  990. else
  991. {
  992. // Match visible layers in source graph
  993. for (var i = 0; i < childCount; i++)
  994. {
  995. model.setVisible(model.getChildAt(model.root, i),
  996. source.isVisible(source.getChildAt(source.root, i)));
  997. }
  998. }
  999. }
  1000. finally
  1001. {
  1002. model.endUpdate();
  1003. }
  1004. result = true;
  1005. }
  1006. return result;
  1007. };
  1008. /**
  1009. *
  1010. */
  1011. GraphViewer.prototype.addToolbar = function()
  1012. {
  1013. var container = this.graph.container;
  1014. if (this.graphConfig['toolbar-position'] == 'bottom')
  1015. {
  1016. container.style.marginBottom = this.toolbarHeight + 'px';
  1017. }
  1018. else if (this.graphConfig['toolbar-position'] != 'inline')
  1019. {
  1020. container.style.marginTop = this.toolbarHeight + 'px';
  1021. }
  1022. // Creates toolbar for viewer
  1023. var toolbar = container.ownerDocument.createElement('div');
  1024. toolbar.style.display = 'flex';
  1025. toolbar.style.alignItems = 'center';
  1026. toolbar.style.position = 'absolute';
  1027. toolbar.style.overflow = 'hidden';
  1028. toolbar.style.boxSizing = 'border-box';
  1029. toolbar.style.whiteSpace = 'nowrap';
  1030. toolbar.style.textAlign = 'left';
  1031. toolbar.style.zIndex = this.toolbarZIndex;
  1032. toolbar.style.backgroundColor = '#eee';
  1033. toolbar.style.height = this.toolbarHeight + 'px';
  1034. if (this.darkMode)
  1035. {
  1036. if (Editor.enableCssDarkMode)
  1037. {
  1038. toolbar.classList.add('geDarkMode');
  1039. }
  1040. else
  1041. {
  1042. toolbar.style.filter = 'invert(1)';
  1043. }
  1044. }
  1045. this.toolbar = toolbar;
  1046. if (this.graphConfig['toolbar-position'] == 'inline')
  1047. {
  1048. mxUtils.setPrefixedStyle(toolbar.style, 'transition', 'opacity 100ms ease-in-out');
  1049. mxUtils.setOpacity(toolbar, 30);
  1050. // Changes toolbar opacity on hover
  1051. var fadeThread = null;
  1052. var fadeThread2 = null;
  1053. var fadeOut = mxUtils.bind(this, function(delay)
  1054. {
  1055. if (fadeThread != null)
  1056. {
  1057. window.clearTimeout(fadeThread);
  1058. fadeThead = null;
  1059. }
  1060. if (fadeThread2 != null)
  1061. {
  1062. window.clearTimeout(fadeThread2);
  1063. fadeThead2 = null;
  1064. }
  1065. fadeThread = window.setTimeout(mxUtils.bind(this, function()
  1066. {
  1067. mxUtils.setOpacity(toolbar, 0);
  1068. fadeThread = null;
  1069. fadeThread2 = window.setTimeout(mxUtils.bind(this, function()
  1070. {
  1071. toolbar.style.display = 'none';
  1072. fadeThread2 = null;
  1073. }), 100);
  1074. }), delay || 200);
  1075. });
  1076. var fadeIn = mxUtils.bind(this, function(opacity)
  1077. {
  1078. if (fadeThread != null)
  1079. {
  1080. window.clearTimeout(fadeThread);
  1081. fadeThead = null;
  1082. }
  1083. if (fadeThread2 != null)
  1084. {
  1085. window.clearTimeout(fadeThread2);
  1086. fadeThead2 = null;
  1087. }
  1088. toolbar.style.display = '';
  1089. mxUtils.setOpacity(toolbar, opacity || 30);
  1090. });
  1091. mxEvent.addListener(this.graph.container, (mxClient.IS_POINTER) ? 'pointermove' : 'mousemove', mxUtils.bind(this, function(evt)
  1092. {
  1093. if (!mxEvent.isTouchEvent(evt))
  1094. {
  1095. fadeIn(30);
  1096. fadeOut();
  1097. }
  1098. }));
  1099. mxEvent.addListener(toolbar, (mxClient.IS_POINTER) ? 'pointermove' : 'mousemove', function(evt)
  1100. {
  1101. mxEvent.consume(evt);
  1102. });
  1103. mxEvent.addListener(toolbar, 'mouseenter', mxUtils.bind(this, function(evt)
  1104. {
  1105. fadeIn(100);
  1106. }));
  1107. mxEvent.addListener(toolbar, 'mousemove', mxUtils.bind(this, function(evt)
  1108. {
  1109. fadeIn(100);
  1110. mxEvent.consume(evt);
  1111. }));
  1112. mxEvent.addListener(toolbar, 'mouseleave', mxUtils.bind(this, function(evt)
  1113. {
  1114. if (!mxEvent.isTouchEvent(evt))
  1115. {
  1116. fadeIn(30);
  1117. }
  1118. }));
  1119. // Shows/hides toolbar for touch devices
  1120. var graph = this.graph;
  1121. var tol = graph.getTolerance();
  1122. graph.addMouseListener(
  1123. {
  1124. startX: 0,
  1125. startY: 0,
  1126. scrollLeft: 0,
  1127. scrollTop: 0,
  1128. mouseDown: function(sender, me)
  1129. {
  1130. this.startX = me.getGraphX();
  1131. this.startY = me.getGraphY();
  1132. this.scrollLeft = graph.container.scrollLeft;
  1133. this.scrollTop = graph.container.scrollTop;
  1134. },
  1135. mouseMove: function(sender, me) {},
  1136. mouseUp: function(sender, me)
  1137. {
  1138. if (mxEvent.isTouchEvent(me.getEvent()))
  1139. {
  1140. if ((Math.abs(this.scrollLeft - graph.container.scrollLeft) < tol &&
  1141. Math.abs(this.scrollTop - graph.container.scrollTop) < tol) &&
  1142. (Math.abs(this.startX - me.getGraphX()) < tol &&
  1143. Math.abs(this.startY - me.getGraphY()) < tol))
  1144. {
  1145. if (parseFloat(toolbar.style.opacity || 0) > 0)
  1146. {
  1147. fadeOut();
  1148. }
  1149. else
  1150. {
  1151. fadeIn(30);
  1152. }
  1153. }
  1154. }
  1155. }
  1156. });
  1157. }
  1158. var tokens = this.toolbarItems;
  1159. var buttonCount = 0;
  1160. var addButton = mxUtils.bind(this, function(fn, imgSrc, tip, enabled)
  1161. {
  1162. var a = this.createToolbarButton(fn, imgSrc, tip, enabled);
  1163. toolbar.appendChild(a);
  1164. buttonCount++;
  1165. return a;
  1166. });
  1167. var model = this.graph.getModel();
  1168. var layersDialog = null;
  1169. var tagsComponent = null;
  1170. var tagsDialog = null;
  1171. var pageInfo = null;
  1172. for (var i = 0; i < tokens.length; i++)
  1173. {
  1174. var token = tokens[i];
  1175. if (token == 'pages')
  1176. {
  1177. pageInfo = container.ownerDocument.createElement('div');
  1178. pageInfo.style.cssText = 'display:inline-flex;position:relative;align-items:center;' +
  1179. 'padding:4px;font-family:Helvetica,Arial;font-size:12px;;cursor:default;color:#000;'
  1180. mxUtils.setOpacity(pageInfo, 70);
  1181. var prevButton = addButton(mxUtils.bind(this, function()
  1182. {
  1183. this.selectPage(this.currentPage - 1);
  1184. }), Editor.previousImage, mxResources.get('previousPage') || 'Previous Page');
  1185. prevButton.style.borderRightStyle = 'none';
  1186. prevButton.style.paddingLeft = '0px';
  1187. prevButton.style.paddingRight = '0px';
  1188. toolbar.appendChild(pageInfo);
  1189. var nextButton = addButton(mxUtils.bind(this, function()
  1190. {
  1191. this.selectPage(this.currentPage + 1);
  1192. }), Editor.nextImage, mxResources.get('nextPage') || 'Next Page');
  1193. nextButton.style.paddingLeft = '0px';
  1194. nextButton.style.paddingRight = '0px';
  1195. var update = mxUtils.bind(this, function()
  1196. {
  1197. pageInfo.innerText = '';
  1198. mxUtils.write(pageInfo, (this.currentPage + 1) + ' / ' + this.diagrams.length);
  1199. pageInfo.style.display = (this.diagrams.length > 1) ? 'inline-flex' : 'none';
  1200. prevButton.style.display = pageInfo.style.display;
  1201. nextButton.style.display = pageInfo.style.display;
  1202. });
  1203. // LATER: Add event for setGraphXml
  1204. this.addListener('graphChanged', update);
  1205. update();
  1206. }
  1207. else if (token == 'zoom')
  1208. {
  1209. if (this.zoomEnabled)
  1210. {
  1211. addButton(mxUtils.bind(this, function()
  1212. {
  1213. this.graph.zoomOut();
  1214. }), Editor.zoomOutImage, mxResources.get('zoomOut') || 'Zoom Out');
  1215. addButton(mxUtils.bind(this, function()
  1216. {
  1217. this.graph.zoomIn();
  1218. }), Editor.zoomInImage, mxResources.get('zoomIn') || 'Zoom In');
  1219. addButton(mxUtils.bind(this, function()
  1220. {
  1221. this.graph.view.scaleAndTranslate(this.graph.initialViewState.scale,
  1222. this.graph.initialViewState.translate.x,
  1223. this.graph.initialViewState.translate.y);
  1224. }), Editor.zoomFitImage, mxResources.get('fit') || 'Fit');
  1225. }
  1226. }
  1227. else if (token == 'layers')
  1228. {
  1229. if (this.layersEnabled)
  1230. {
  1231. var layersButton = addButton(mxUtils.bind(this, function(evt)
  1232. {
  1233. if (layersDialog != null)
  1234. {
  1235. layersDialog.parentNode.removeChild(layersDialog);
  1236. layersDialog = null;
  1237. }
  1238. else
  1239. {
  1240. layersDialog = this.graph.createLayersDialog(mxUtils.bind(this, function()
  1241. {
  1242. if (this.autoCrop)
  1243. {
  1244. this.crop();
  1245. }
  1246. else if (this.autoOrigin)
  1247. {
  1248. var bounds = this.graph.getGraphBounds();
  1249. var v = this.graph.view;
  1250. if (bounds.x < 0 || bounds.y < 0)
  1251. {
  1252. this.crop();
  1253. this.graph.originalViewState = this.graph.initialViewState;
  1254. this.graph.initialViewState = {
  1255. translate: v.translate.clone(),
  1256. scale: v.scale
  1257. };
  1258. }
  1259. else if (this.graph.originalViewState != null &&
  1260. bounds.x / v.scale + this.graph.originalViewState.translate.x - v.translate.x > 0 &&
  1261. bounds.y / v.scale + this.graph.originalViewState.translate.y - v.translate.y > 0)
  1262. {
  1263. v.setTranslate(this.graph.originalViewState.translate.x,
  1264. this.graph.originalViewState.translate.y);
  1265. this.graph.originalViewState = null;
  1266. this.graph.initialViewState = {
  1267. translate: v.translate.clone(),
  1268. scale: v.scale
  1269. };
  1270. }
  1271. }
  1272. }));
  1273. mxEvent.addListener(layersDialog, 'mouseleave', function()
  1274. {
  1275. layersDialog.parentNode.removeChild(layersDialog);
  1276. layersDialog = null;
  1277. });
  1278. var r = layersButton.getBoundingClientRect();
  1279. layersDialog.style.width = '140px';
  1280. layersDialog.style.padding = '2px 0px 2px 0px';
  1281. layersDialog.style.border = '1px solid #d0d0d0';
  1282. layersDialog.style.backgroundColor = '#eee';
  1283. layersDialog.style.fontFamily = Editor.defaultHtmlFont;
  1284. layersDialog.style.fontSize = '11px';
  1285. layersDialog.style.overflowY = 'auto';
  1286. layersDialog.style.maxHeight = (this.graph.container.clientHeight - this.toolbarHeight - 10) + 'px'
  1287. layersDialog.style.zIndex = this.toolbarZIndex + 1;
  1288. layersDialog.style.color = '#000';
  1289. mxUtils.setOpacity(layersDialog, 85);
  1290. var origin = mxUtils.getDocumentScrollOrigin(document);
  1291. layersDialog.style.left = origin.x + r.left - 1 + 'px';
  1292. layersDialog.style.top = origin.y + r.bottom - 2 + 'px';
  1293. if (this.darkMode)
  1294. {
  1295. layersDialog.style.filter = 'invert(93%) hue-rotate(180deg)';
  1296. }
  1297. document.body.appendChild(layersDialog);
  1298. }
  1299. }), Editor.layersImage, mxResources.get('layers') || 'Layers');
  1300. model.addListener(mxEvent.CHANGE, function()
  1301. {
  1302. layersButton.style.display = (model.getChildCount(model.root) > 1) ? 'inline-flex' : 'none';
  1303. });
  1304. layersButton.style.display = (model.getChildCount(model.root) > 1) ? 'inline-flex' : 'none';
  1305. }
  1306. }
  1307. else if (token == 'tags')
  1308. {
  1309. if (this.tagsEnabled)
  1310. {
  1311. var tagsButton = addButton(mxUtils.bind(this, function(evt)
  1312. {
  1313. if (tagsComponent == null)
  1314. {
  1315. tagsComponent = this.graph.createTagsDialog(mxUtils.bind(this, function()
  1316. {
  1317. return true;
  1318. }));
  1319. tagsComponent.div.getElementsByTagName('div')[0].style.position = '';
  1320. tagsComponent.div.style.maxHeight = '160px';
  1321. tagsComponent.div.style.maxWidth = '120px';
  1322. tagsComponent.div.style.padding = '2px';
  1323. tagsComponent.div.style.overflow = 'auto';
  1324. tagsComponent.div.style.height = 'auto';
  1325. tagsComponent.div.style.position = 'fixed';
  1326. tagsComponent.div.style.fontFamily = Editor.defaultHtmlFont;
  1327. tagsComponent.div.style.fontSize = '11px';
  1328. tagsComponent.div.style.backgroundColor = '#eee';
  1329. tagsComponent.div.style.color = '#000';
  1330. tagsComponent.div.style.border = '1px solid #d0d0d0';
  1331. tagsComponent.div.style.zIndex = this.toolbarZIndex + 1;
  1332. if (this.darkMode)
  1333. {
  1334. tagsComponent.div.style.filter = 'invert(93%) hue-rotate(180deg)';
  1335. }
  1336. mxUtils.setOpacity(tagsComponent.div, 85);
  1337. }
  1338. if (tagsDialog != null)
  1339. {
  1340. tagsDialog.parentNode.removeChild(tagsDialog);
  1341. tagsDialog = null;
  1342. }
  1343. else
  1344. {
  1345. tagsDialog = tagsComponent.div;
  1346. tagsDialog.style.position = 'absolute';
  1347. mxEvent.addListener(tagsDialog, 'mouseleave', function()
  1348. {
  1349. if (tagsDialog != null)
  1350. {
  1351. tagsDialog.parentNode.removeChild(tagsDialog);
  1352. tagsDialog = null;
  1353. }
  1354. });
  1355. var r = tagsButton.getBoundingClientRect();
  1356. var origin = mxUtils.getDocumentScrollOrigin(document);
  1357. tagsDialog.style.left = origin.x + r.left - 1 + 'px';
  1358. tagsDialog.style.top = origin.y + r.bottom - 2 + 'px';
  1359. document.body.appendChild(tagsDialog);
  1360. tagsComponent.refresh();
  1361. }
  1362. }), Editor.tagsImage, mxResources.get('tags') || 'Tags');
  1363. model.addListener(mxEvent.CHANGE, mxUtils.bind(this, function()
  1364. {
  1365. tagsButton.style.display = (this.graph.getAllTags().length > 0) ? 'inline-flex' : 'none';
  1366. if (tagsDialog != null && this.graph.getAllTags().length == 0)
  1367. {
  1368. tagsDialog.parentNode.removeChild(tagsDialog);
  1369. tagsDialog = null;
  1370. }
  1371. }));
  1372. tagsButton.style.display = (this.graph.getAllTags().length > 0) ? 'inline-flex' : 'none';
  1373. }
  1374. }
  1375. else if (token == 'lightbox')
  1376. {
  1377. if (this.lightboxEnabled)
  1378. {
  1379. addButton(mxUtils.bind(this, function()
  1380. {
  1381. try
  1382. {
  1383. this.showLightbox();
  1384. }
  1385. catch (e)
  1386. {
  1387. alert(e.message);
  1388. }
  1389. }), Editor.fullscreenImage, (mxResources.get('fullscreen') || 'Fullscreen'));
  1390. }
  1391. }
  1392. else if (this.graphConfig['toolbar-buttons'] != null)
  1393. {
  1394. var def = this.graphConfig['toolbar-buttons'][token];
  1395. if (def != null)
  1396. {
  1397. def.elem = addButton((def.enabled == null || def.enabled) ? def.handler : function() {},
  1398. def.image, def.title, def.enabled);
  1399. }
  1400. }
  1401. }
  1402. if (this.graph.minimumContainerSize != null)
  1403. {
  1404. this.graph.minimumContainerSize.width = buttonCount * 34;
  1405. }
  1406. if (this.graphConfig.title != null)
  1407. {
  1408. var filename = container.ownerDocument.createElement('div');
  1409. filename.style.cssText = 'display:inline-flex;position:relative;align-items:center;' +
  1410. 'padding:6px;font-family:Helvetica,Arial;font-size:12px;cursor:default;color:#000;';
  1411. filename.setAttribute('title', this.graphConfig.titleTooltip || this.graphConfig.title);
  1412. mxUtils.write(filename, this.graphConfig.title);
  1413. mxUtils.setOpacity(filename, 70);
  1414. toolbar.appendChild(filename);
  1415. this.filename = filename;
  1416. }
  1417. this.minToolbarWidth = buttonCount * 34;
  1418. var prevBorder = container.style.border;
  1419. var enter = mxUtils.bind(this, function()
  1420. {
  1421. toolbar.style.width = (this.graphConfig['toolbar-position'] == 'inline') ? 'auto' :
  1422. Math.max(this.minToolbarWidth, container.offsetWidth) + 'px';
  1423. toolbar.style.border = '1px solid #d0d0d0';
  1424. if (this.graphConfig['toolbar-nohide'] != true)
  1425. {
  1426. var r = container.getBoundingClientRect();
  1427. // Workaround for position:relative set in ResizeSensor
  1428. var origin = mxUtils.getScrollOrigin(document.body)
  1429. var b = (document.body.style.position === 'relative') ?
  1430. document.body.getBoundingClientRect() :
  1431. {left: -origin.x, top: -origin.y};
  1432. r = {left: r.left - b.left, top: r.top - b.top,
  1433. bottom: r.bottom - b.top, right: r.right - b.left};
  1434. toolbar.style.left = r.left + 'px';
  1435. if (this.graphConfig['toolbar-position'] == 'bottom')
  1436. {
  1437. toolbar.style.top = r.bottom - 1 + 'px';
  1438. }
  1439. else
  1440. {
  1441. if (this.graphConfig['toolbar-position'] != 'inline')
  1442. {
  1443. toolbar.style.marginTop = -this.toolbarHeight + 'px';
  1444. toolbar.style.top = r.top + 1 + 'px';
  1445. }
  1446. else
  1447. {
  1448. toolbar.style.top = r.top + 'px';
  1449. }
  1450. }
  1451. if (prevBorder == '1px solid transparent')
  1452. {
  1453. container.style.border = '1px solid #d0d0d0';
  1454. }
  1455. document.body.appendChild(toolbar);
  1456. var hideToolbar = mxUtils.bind(this, function()
  1457. {
  1458. if (toolbar.parentNode != null)
  1459. {
  1460. toolbar.parentNode.removeChild(toolbar);
  1461. }
  1462. if (tagsDialog != null)
  1463. {
  1464. tagsDialog.parentNode.removeChild(tagsDialog);
  1465. tagsDialog = null;
  1466. }
  1467. if (layersDialog != null)
  1468. {
  1469. layersDialog.parentNode.removeChild(layersDialog);
  1470. layersDialog = null;
  1471. }
  1472. container.style.border = prevBorder;
  1473. });
  1474. mxEvent.addListener(document, 'mousemove', function(evt)
  1475. {
  1476. var source = mxEvent.getSource(evt);
  1477. while (source != null)
  1478. {
  1479. if (source == container ||
  1480. source == toolbar ||
  1481. source == layersDialog ||
  1482. source == tagsDialog)
  1483. {
  1484. return;
  1485. }
  1486. source = source.parentNode;
  1487. }
  1488. hideToolbar();
  1489. });
  1490. mxEvent.addListener(document.body, 'mouseleave', function(evt)
  1491. {
  1492. hideToolbar();
  1493. });
  1494. }
  1495. else
  1496. {
  1497. toolbar.style.top = -this.toolbarHeight + 'px';
  1498. // geDarkMode already set on container, so remove it from toolbar such that it doesn't invert colors twice
  1499. toolbar.classList.remove('geDarkMode');
  1500. container.appendChild(toolbar);
  1501. }
  1502. });
  1503. if (this.graphConfig['toolbar-nohide'] != true)
  1504. {
  1505. mxEvent.addListener(container, 'mouseenter', enter);
  1506. }
  1507. else
  1508. {
  1509. enter();
  1510. }
  1511. if (this.responsive && typeof ResizeObserver !== 'undefined')
  1512. {
  1513. new ResizeObserver(function()
  1514. {
  1515. if (toolbar.parentNode != null)
  1516. {
  1517. enter();
  1518. }
  1519. }).observe(container)
  1520. }
  1521. };
  1522. /**
  1523. *
  1524. */
  1525. GraphViewer.prototype.createToolbarButton = function(fn, imgSrc, tip, enabled)
  1526. {
  1527. var a = document.createElement('div');
  1528. a.style.borderRight = '1px solid #d0d0d0';
  1529. a.style.display = 'inline-flex';
  1530. a.style.alignItems = 'center';
  1531. a.style.padding = '6px';
  1532. mxEvent.addListener(a, 'click', fn);
  1533. if (tip != null)
  1534. {
  1535. a.setAttribute('title', tip);
  1536. }
  1537. var img = document.createElement('img');
  1538. img.setAttribute('border', '0');
  1539. img.setAttribute('src', imgSrc);
  1540. img.style.width = '18px';
  1541. img.className = 'geAdaptiveAsset';
  1542. if (enabled == null || enabled)
  1543. {
  1544. mxEvent.addListener(a, 'mouseenter', function()
  1545. {
  1546. a.style.backgroundColor = '#ddd';
  1547. });
  1548. mxEvent.addListener(a, 'mouseleave', function()
  1549. {
  1550. a.style.backgroundColor = '#eee';
  1551. });
  1552. mxUtils.setOpacity(img, 60);
  1553. a.style.cursor = 'pointer';
  1554. }
  1555. else
  1556. {
  1557. mxUtils.setOpacity(a, 30);
  1558. }
  1559. a.appendChild(img);
  1560. return a;
  1561. };
  1562. GraphViewer.prototype.disableButton = function(token, tooltip)
  1563. {
  1564. var def = this.graphConfig['toolbar-buttons']? this.graphConfig['toolbar-buttons'][token] : null;
  1565. if (def != null)
  1566. {
  1567. mxUtils.setOpacity(def.elem, 30);
  1568. mxEvent.removeListener(def.elem, 'click', def.handler);
  1569. //Workaround to stop highlighting the disabled button
  1570. mxEvent.addListener(def.elem, 'mouseenter', function()
  1571. {
  1572. def.elem.style.backgroundColor = '#eee';
  1573. });
  1574. if (tooltip)
  1575. {
  1576. def.elem.setAttribute('title', tooltip);
  1577. }
  1578. }
  1579. };
  1580. /**
  1581. * Adds event handler for links and lightbox.
  1582. */
  1583. GraphViewer.prototype.addClickHandler = function(graph, ui)
  1584. {
  1585. graph.linkPolicy = this.graphConfig.target || graph.linkPolicy;
  1586. graph.addClickHandler(this.graphConfig.highlight, mxUtils.bind(this, function(evt, href, associatedCell)
  1587. {
  1588. if (href == null)
  1589. {
  1590. var source = mxEvent.getSource(evt);
  1591. while (source != graph.container && source != null && href == null)
  1592. {
  1593. if (source.nodeName.toLowerCase() == 'a')
  1594. {
  1595. href = source.getAttribute('href');
  1596. }
  1597. source = source.parentNode;
  1598. }
  1599. }
  1600. if (ui != null)
  1601. {
  1602. if (href == null || graph.isCustomLink(href))
  1603. {
  1604. mxEvent.consume(evt);
  1605. }
  1606. else if (!graph.isExternalProtocol(href) &&
  1607. !graph.isBlankLink(href))
  1608. {
  1609. // Hides lightbox if any links are clicked
  1610. // Async handling needed for anchors to work
  1611. window.setTimeout(function()
  1612. {
  1613. ui.destroy();
  1614. }, 0);
  1615. }
  1616. }
  1617. else if (href != null && ui == null && graph.isCustomLink(href) &&
  1618. (mxEvent.isTouchEvent(evt) || !mxEvent.isPopupTrigger(evt)) &&
  1619. graph.customLinkClicked(href, associatedCell))
  1620. {
  1621. // Workaround for text selection in Firefox on Windows
  1622. mxUtils.clearSelection();
  1623. mxEvent.consume(evt);
  1624. }
  1625. }), mxUtils.bind(this, function(evt)
  1626. {
  1627. if (ui == null && this.lightboxClickEnabled &&
  1628. (!mxEvent.isTouchEvent(evt) ||
  1629. this.toolbarItems.length == 0))
  1630. {
  1631. try
  1632. {
  1633. this.showLightbox();
  1634. }
  1635. catch (e)
  1636. {
  1637. alert(e.message);
  1638. }
  1639. }
  1640. }));
  1641. };
  1642. /**
  1643. * Adds the given array of stencils to avoid dynamic loading of shapes.
  1644. */
  1645. GraphViewer.prototype.showLightbox = function(editable, closable, target)
  1646. {
  1647. if (this.graphConfig.lightbox == 'open' || window.self !== window.top)
  1648. {
  1649. if (this.lightboxWindow != null && !this.lightboxWindow.closed)
  1650. {
  1651. this.lightboxWindow.focus();
  1652. }
  1653. else
  1654. {
  1655. editable = (editable != null) ? editable :
  1656. ((this.graphConfig.editable != null) ?
  1657. this.graphConfig.editable : true);
  1658. closable = (closable != null) ? closable : true;
  1659. target = (target != null) ? target : 'blank';
  1660. var param = {'client': 1, 'target': target};
  1661. if (editable)
  1662. {
  1663. param.edit = this.graphConfig.edit || '_blank';
  1664. }
  1665. if (closable)
  1666. {
  1667. param.close = 1;
  1668. }
  1669. if (this.layersEnabled)
  1670. {
  1671. param.layers = 1;
  1672. }
  1673. if (this.tagsEnabled)
  1674. {
  1675. param.tags = {};
  1676. }
  1677. if (this.graphConfig != null && this.graphConfig.nav != false)
  1678. {
  1679. param.nav = 1;
  1680. }
  1681. if (this.graphConfig != null && this.graphConfig.highlight != null)
  1682. {
  1683. param.highlight = this.graphConfig.highlight.substring(1);
  1684. }
  1685. if (this.currentPage != null && this.currentPage > 0)
  1686. {
  1687. param.page = this.currentPage;
  1688. }
  1689. if (typeof window.postMessage !== 'undefined' && (document.documentMode == null || document.documentMode >= 10))
  1690. {
  1691. if (this.lightboxWindow == null)
  1692. {
  1693. mxEvent.addListener(window, 'message', mxUtils.bind(this, function(evt)
  1694. {
  1695. if (evt.data == 'ready' && evt.source == this.lightboxWindow)
  1696. {
  1697. this.lightboxWindow.postMessage(this.xml, '*');
  1698. }
  1699. }));
  1700. }
  1701. }
  1702. else
  1703. {
  1704. // Data is pulled from global variable after tab loads
  1705. param.data = encodeURIComponent(this.xml);
  1706. }
  1707. if (urlParams['dev'] == '1')
  1708. {
  1709. param.dev = '1';
  1710. }
  1711. this.lightboxWindow = window.open(((urlParams['dev'] != '1') ?
  1712. EditorUi.lightboxHost : 'https://test.draw.io') +
  1713. '/#P' + encodeURIComponent(JSON.stringify(param)));
  1714. }
  1715. }
  1716. else
  1717. {
  1718. this.showLocalLightbox();
  1719. }
  1720. };
  1721. /**
  1722. * Adds the given array of stencils to avoid dynamic loading of shapes.
  1723. */
  1724. GraphViewer.prototype.showLocalLightbox = function(container)
  1725. {
  1726. var backdrop = document.createElement('div');
  1727. backdrop.style.cssText = 'position:fixed;top:0;left:0;bottom:0;right:0;';
  1728. backdrop.style.zIndex = this.lightboxZIndex;
  1729. backdrop.style.backgroundColor = '#000000';
  1730. mxUtils.setOpacity(backdrop, 70);
  1731. document.body.appendChild(backdrop);
  1732. var closeImg = document.createElement('img');
  1733. closeImg.setAttribute('border', '0');
  1734. closeImg.setAttribute('src', Editor.closeBlackImage);
  1735. closeImg.style.cssText = 'position:fixed;top:32px;right:32px;';
  1736. closeImg.style.cursor = 'pointer';
  1737. closeImg.className = 'geAdaptiveAsset';
  1738. mxEvent.addListener(closeImg, 'click', function()
  1739. {
  1740. ui.destroy();
  1741. });
  1742. // LATER: Make possible to assign after instance was created
  1743. urlParams['pages'] = '1';
  1744. urlParams['page'] = this.currentPage;
  1745. urlParams['page-id'] = this.graphConfig.pageId;
  1746. urlParams['layer-ids'] = (this.graphConfig.layerIds != null && this.graphConfig.layerIds.length > 0)
  1747. ? this.graphConfig.layerIds.join(' ') : null;
  1748. urlParams['nav'] = (this.graphConfig.nav != false) ? '1' : '0';
  1749. urlParams['layers'] = (this.layersEnabled) ? '1' : '0';
  1750. if (this.tagsEnabled)
  1751. {
  1752. urlParams['tags'] = '{}';
  1753. }
  1754. if (container != null)
  1755. {
  1756. try
  1757. {
  1758. var toolbarConfig = JSON.parse(decodeURIComponent(urlParams['toolbar-config'] || '{}'));
  1759. toolbarConfig.noCloseBtn = true;
  1760. urlParams['toolbar-config'] = encodeURIComponent(JSON.stringify(toolbarConfig));
  1761. }
  1762. catch (e) {}
  1763. }
  1764. // PostMessage not working and Permission denied for opened access in IE9-
  1765. if (document.documentMode == null || document.documentMode >= 10)
  1766. {
  1767. Editor.prototype.editButtonLink = this.graphConfig.edit;
  1768. Editor.prototype.editButtonFunc = this.graphConfig.editFunc;
  1769. }
  1770. Editor.isDarkMode = mxUtils.bind(this, function()
  1771. {
  1772. return this.darkMode;
  1773. });
  1774. EditorUi.prototype.updateActionStates = function() {};
  1775. EditorUi.prototype.addBeforeUnloadListener = function() {};
  1776. EditorUi.prototype.addChromelessClickHandler = function() {};
  1777. // Workaround for lost reference with same ID is to change
  1778. // ID which must be done before calling EditorUi constructor
  1779. var previousShadowId = Graph.prototype.shadowId;
  1780. Graph.prototype.shadowId = 'lightboxDropShadow';
  1781. var ui = new EditorUi(new Editor(true), document.createElement('div'), true);
  1782. if (this.darkMode)
  1783. {
  1784. ui.setDarkMode(true);
  1785. }
  1786. ui.editor.editBlankUrl = this.editBlankUrl;
  1787. // Overrides instance variable and restores prototype state
  1788. ui.editor.graph.shadowId = 'lightboxDropShadow';
  1789. Graph.prototype.shadowId = previousShadowId;
  1790. // Disables refresh
  1791. ui.refresh = function() {};
  1792. // Handles escape keystroke
  1793. var keydownHandler = mxUtils.bind(this, function(evt)
  1794. {
  1795. if (evt.keyCode == 27 /* Escape */)
  1796. {
  1797. ui.destroy();
  1798. }
  1799. });
  1800. var overflow = this.initialOverflow;
  1801. var destroy = ui.destroy;
  1802. ui.destroy = function()
  1803. {
  1804. if (container == null)
  1805. {
  1806. mxEvent.removeListener(document.documentElement, 'keydown', keydownHandler);
  1807. document.body.removeChild(backdrop);
  1808. document.body.removeChild(closeImg);
  1809. document.body.style.overflow = overflow;
  1810. GraphViewer.resizeSensorEnabled = true;
  1811. destroy.apply(this, arguments);
  1812. }
  1813. };
  1814. var graph = ui.editor.graph;
  1815. var lightbox = graph.container;
  1816. lightbox.style.overflow = 'hidden';
  1817. if (this.lightboxChrome && container == null)
  1818. {
  1819. lightbox.style.border = '1px solid #c0c0c0';
  1820. lightbox.style.margin = '40px';
  1821. // Installs the keystroke listener in the target
  1822. mxEvent.addListener(document.documentElement, 'keydown', keydownHandler);
  1823. }
  1824. else
  1825. {
  1826. backdrop.style.display = 'none';
  1827. closeImg.style.display = 'none';
  1828. }
  1829. // Handles relative images
  1830. var self = this;
  1831. graph.getImageFromBundles = function(key)
  1832. {
  1833. return self.getImageUrl(key);
  1834. };
  1835. // Handles relative images in print output and temporary graphs
  1836. var uiCreateTemporaryGraph = ui.createTemporaryGraph;
  1837. ui.createTemporaryGraph = function()
  1838. {
  1839. var newGraph = uiCreateTemporaryGraph.apply(this, arguments);
  1840. newGraph.getImageFromBundles = function(key)
  1841. {
  1842. return self.getImageUrl(key);
  1843. };
  1844. return newGraph;
  1845. };
  1846. if (this.graphConfig.move)
  1847. {
  1848. graph.isMoveCellsEvent = function(evt)
  1849. {
  1850. return true;
  1851. };
  1852. }
  1853. mxUtils.setPrefixedStyle(lightbox.style, 'border-radius', '4px');
  1854. lightbox.style.position = 'fixed';
  1855. GraphViewer.resizeSensorEnabled = false;
  1856. document.body.style.overflow = 'hidden';
  1857. this.addClickHandler(graph, ui);
  1858. window.setTimeout(mxUtils.bind(this, function()
  1859. {
  1860. try
  1861. {
  1862. // Click on backdrop closes lightbox
  1863. mxEvent.addListener(backdrop, 'click', function()
  1864. {
  1865. ui.destroy();
  1866. });
  1867. // Disables focus border in Chrome
  1868. lightbox.style.outline = 'none';
  1869. lightbox.style.zIndex = this.lightboxZIndex;
  1870. closeImg.style.zIndex = this.lightboxZIndex;
  1871. if (container != null)
  1872. {
  1873. container.innerHTML = '';
  1874. container.appendChild(lightbox);
  1875. }
  1876. else
  1877. {
  1878. document.body.appendChild(lightbox);
  1879. document.body.appendChild(closeImg);
  1880. }
  1881. ui.setFileData(this.xml);
  1882. mxUtils.setPrefixedStyle(lightbox.style, 'transform', 'rotateY(0deg)');
  1883. ui.chromelessToolbar.style.bottom = 60 + 'px';
  1884. ui.chromelessToolbar.style.zIndex = this.lightboxZIndex;
  1885. // Workaround for clipping in IE11-
  1886. (container || document.body).appendChild(ui.chromelessToolbar);
  1887. ui.getEditBlankXml = mxUtils.bind(this, function()
  1888. {
  1889. return this.xml;
  1890. });
  1891. this.showLayers(graph, this.graph);
  1892. ui.lightboxFit();
  1893. ui.chromelessResize();
  1894. }
  1895. catch (e)
  1896. {
  1897. ui.handleError(e, null, function()
  1898. {
  1899. ui.destroy();
  1900. });
  1901. }
  1902. }), 0);
  1903. return ui;
  1904. };
  1905. /**
  1906. * Removes the dialog from the DOM.
  1907. */
  1908. Dialog.prototype.getDocumentSize = function()
  1909. {
  1910. var vw = Math.max(document.documentElement.clientWidth || 0, window.innerWidth || 0)
  1911. var vh = Math.max(document.documentElement.clientHeight || 0, window.innerHeight || 0)
  1912. return new mxRectangle(0, 0, vw, vh);
  1913. };
  1914. /**
  1915. *
  1916. */
  1917. GraphViewer.prototype.updateTitle = function(title, titleTooltip)
  1918. {
  1919. title = title || '';
  1920. if (this.showTitleAsTooltip && this.graph != null && this.graph.container != null)
  1921. {
  1922. this.graph.container.setAttribute('title', titleTooltip || title);
  1923. }
  1924. if (this.filename != null)
  1925. {
  1926. this.filename.innerText = '';
  1927. mxUtils.write(this.filename, title);
  1928. this.filename.setAttribute('title', titleTooltip || title);
  1929. }
  1930. };
  1931. /**
  1932. *
  1933. */
  1934. GraphViewer.processElements = function(classname)
  1935. {
  1936. mxUtils.forEach(GraphViewer.getElementsByClassName(classname || 'mxgraph'), function(div)
  1937. {
  1938. try
  1939. {
  1940. div.innerText = '';
  1941. GraphViewer.createViewerForElement(div);
  1942. }
  1943. catch (e)
  1944. {
  1945. div.innerText = e.message;
  1946. if (window.console != null)
  1947. {
  1948. console.error(e);
  1949. }
  1950. }
  1951. });
  1952. };
  1953. /**
  1954. * Adds the given array of stencils to avoid dynamic loading of shapes.
  1955. */
  1956. GraphViewer.getElementsByClassName = function(classname)
  1957. {
  1958. if (document.getElementsByClassName)
  1959. {
  1960. var divs = document.getElementsByClassName(classname);
  1961. // Workaround for changing divs while processing
  1962. var result = [];
  1963. for (var i = 0; i < divs.length; i++)
  1964. {
  1965. result.push(divs[i]);
  1966. }
  1967. return result;
  1968. }
  1969. else
  1970. {
  1971. var tmp = document.getElementsByTagName('*');
  1972. var divs = [];
  1973. for (var i = 0; i < tmp.length; i++)
  1974. {
  1975. var cls = tmp[i].className;
  1976. if (cls != null && cls.length > 0)
  1977. {
  1978. var tokens = cls.split(' ');
  1979. if (mxUtils.indexOf(tokens, classname) >= 0)
  1980. {
  1981. divs.push(tmp[i]);
  1982. }
  1983. }
  1984. }
  1985. return divs;
  1986. }
  1987. };
  1988. /**
  1989. * Adds the given array of stencils to avoid dynamic loading of shapes.
  1990. */
  1991. GraphViewer.createViewerForElement = function(element, callback)
  1992. {
  1993. var data = element.getAttribute('data-mxgraph');
  1994. if (data != null)
  1995. {
  1996. var config = JSON.parse(data);
  1997. var createViewer = function(xml)
  1998. {
  1999. var xmlDoc = mxUtils.parseXml(xml);
  2000. var viewer = new GraphViewer(element, xmlDoc.documentElement, config);
  2001. if (callback != null)
  2002. {
  2003. callback(viewer);
  2004. }
  2005. };
  2006. if (config.url != null)
  2007. {
  2008. GraphViewer.getUrl(config.url, function(xml)
  2009. {
  2010. createViewer(xml);
  2011. });
  2012. }
  2013. else
  2014. {
  2015. createViewer(config.xml);
  2016. }
  2017. }
  2018. };
  2019. GraphViewer.blockedAncestorFrames = function()
  2020. {
  2021. try
  2022. {
  2023. if (window.location.ancestorOrigins && window.location.hostname &&
  2024. window.location.ancestorOrigins.length && window.location.ancestorOrigins.length > 0)
  2025. {
  2026. var hostname = window.location.hostname;
  2027. if (hostname && hostname.length > 1 && hostname.charAt(hostname.length - 1) == '/')
  2028. {
  2029. hostname = hostname.substring(0, hostname.length - 1)
  2030. }
  2031. var message = '';
  2032. for (var i = 0; i < window.location.ancestorOrigins.length; i++)
  2033. {
  2034. message += ' -> ' + window.location.ancestorOrigins[i];
  2035. // Running commercial, competing services using our infrastructure isn't allowed.
  2036. if (message.endsWith('.appsplus.co') || message.endsWith('confluence-cloud-excalidraw-ll3likebca-uc.a.run.app'))
  2037. {
  2038. return true;
  2039. }
  2040. }
  2041. if ((hostname.endsWith('ac.draw.io') || hostname.endsWith('aj.draw.io')) && window.location.ancestorOrigins.length == 1 &&
  2042. window.location.ancestorOrigins[0] && window.location.ancestorOrigins[0].endsWith('.atlassian.net'))
  2043. {
  2044. // do not log *.draw.io domains embedded directly into atlassian.net
  2045. }
  2046. // else if (window.location.ancestorOrigins.length > 0)
  2047. // {
  2048. // var img = new Image();
  2049. // img.src = 'https://log.diagrams.net/images/1x1.png?src=ViewerAncestorFrames' +
  2050. // ((typeof window.EditorUi !== 'undefined') ? '&v=' + encodeURIComponent(EditorUi.VERSION) : '') +
  2051. // '&data=' + encodeURIComponent(message);
  2052. // }
  2053. }
  2054. }
  2055. catch (e)
  2056. {
  2057. // ignore
  2058. }
  2059. return false;
  2060. };
  2061. /**
  2062. * Adds event if grid size is changed.
  2063. */
  2064. GraphViewer.initCss = function()
  2065. {
  2066. try
  2067. {
  2068. var style = document.createElement('style')
  2069. style.type = 'text/css';
  2070. style.innerHTML = ['.geDarkMode {',
  2071. 'filter: invert(93%) hue-rotate(180deg);',
  2072. 'background-color: transparent;}',
  2073. '.geDarkMode image, .geDarkMode img:not(.geAdaptiveAsset) {',
  2074. 'filter: invert(100%) hue-rotate(180deg) saturate(1.25);}',
  2075. 'div.mxTooltip {',
  2076. '-webkit-box-shadow: 3px 3px 12px #C0C0C0;',
  2077. '-moz-box-shadow: 3px 3px 12px #C0C0C0;',
  2078. 'box-shadow: 3px 3px 12px #C0C0C0;',
  2079. 'background: #FFFFCC;',
  2080. 'border-style: solid;',
  2081. 'border-width: 1px;',
  2082. 'border-color: black;',
  2083. 'font-family: Arial;',
  2084. 'font-size: 8pt;',
  2085. 'position: absolute;',
  2086. 'cursor: default;',
  2087. 'padding: 4px;',
  2088. 'color: black;}',
  2089. 'td.mxPopupMenuIcon div {',
  2090. 'width:16px;',
  2091. 'height:16px;}',
  2092. 'html div.mxPopupMenu {',
  2093. '-webkit-box-shadow:2px 2px 3px #d5d5d5;',
  2094. '-moz-box-shadow:2px 2px 3px #d5d5d5;',
  2095. 'box-shadow:2px 2px 3px #d5d5d5;',
  2096. '_filter:progid:DXImageTransform.Microsoft.DropShadow(OffX=2, OffY=2, Color=\'#d0d0d0\',Positive=\'true\');',
  2097. 'background:white;',
  2098. 'position:absolute;',
  2099. 'border:3px solid #e7e7e7;',
  2100. 'padding:3px;}',
  2101. 'html table.mxPopupMenu {',
  2102. 'border-collapse:collapse;',
  2103. 'margin:0px;}',
  2104. 'html td.mxPopupMenuItem {',
  2105. 'padding:7px 30px 7px 30px;',
  2106. 'font-family:Helvetica Neue,Helvetica,Arial Unicode MS,Arial;',
  2107. 'font-size:10pt;}',
  2108. 'html td.mxPopupMenuIcon {',
  2109. 'background-color:white;',
  2110. 'padding:0px;}',
  2111. 'td.mxPopupMenuIcon .geIcon {',
  2112. 'padding:2px;',
  2113. 'padding-bottom:4px;',
  2114. 'margin:2px;',
  2115. 'border:1px solid transparent;',
  2116. 'opacity:0.5;',
  2117. '_width:26px;',
  2118. '_height:26px;}',
  2119. 'td.mxPopupMenuIcon .geIcon:hover {',
  2120. 'border:1px solid gray;',
  2121. 'border-radius:2px;',
  2122. 'opacity:1;}',
  2123. 'html tr.mxPopupMenuItemHover {',
  2124. 'background-color: #eeeeee;',
  2125. 'color: black;}',
  2126. 'table.mxPopupMenu hr {',
  2127. 'color:#cccccc;',
  2128. 'background-color:#cccccc;',
  2129. 'border:none;',
  2130. 'height:1px;}',
  2131. 'table.mxPopupMenu tr { font-size:4pt;}',
  2132. // Modified to only apply to the print dialog
  2133. '.geDialog, .geDialog table { font-family:Helvetica Neue,Helvetica,Arial Unicode MS,Arial;',
  2134. 'font-size:10pt;',
  2135. 'border:none;',
  2136. 'margin:0px;}',
  2137. // These are required for the print dialog
  2138. '.geDialog { position:absolute; background:white; overflow:hidden; padding:30px; border:1px solid #acacac; -webkit-box-shadow:0px 0px 2px 2px #d5d5d5; -moz-box-shadow:0px 0px 2px 2px #d5d5d5; box-shadow:0px 0px 2px 2px #d5d5d5; _filter:progid:DXImageTransform.Microsoft.DropShadow(OffX=2, OffY=2, Color=\'#d5d5d5\', Positive=\'true\'); z-index: 2;}.geDialogClose { position:absolute; width:9px; height:9px; opacity:0.5; cursor:pointer; _filter:alpha(opacity=50);}.geDialogClose:hover { opacity:1;}.geDialogTitle { box-sizing:border-box; white-space:nowrap; background:rgb(229, 229, 229); border-bottom:1px solid rgb(192, 192, 192); font-size:15px; font-weight:bold; text-align:center; color:rgb(35, 86, 149);}.geDialogFooter { background:whiteSmoke; white-space:nowrap; text-align:right; box-sizing:border-box; border-top:1px solid #e5e5e5; color:darkGray;}',
  2139. '.geBtn { background-color: #f5f5f5; border-radius: 2px; border: 1px solid #d8d8d8; color: #333; cursor: default; font-size: 11px; font-weight: bold; height: 29px; line-height: 27px; margin: 0 0 0 8px; min-width: 72px; outline: 0; padding: 0 8px; cursor: pointer;}.geBtn:hover, .geBtn:focus { -webkit-box-shadow: 0px 1px 1px rgba(0,0,0,0.1); -moz-box-shadow: 0px 1px 1px rgba(0,0,0,0.1); box-shadow: 0px 1px 1px rgba(0,0,0,0.1); border: 1px solid #c6c6c6; background-color: #f8f8f8; background-image: linear-gradient(#f8f8f8 0px,#f1f1f1 100%); color: #111;}.geBtn:disabled { opacity: .5;}.gePrimaryBtn { background-color: #4d90fe; background-image: linear-gradient(#4d90fe 0px,#4787ed 100%); border: 1px solid #3079ed; color: #fff;}.gePrimaryBtn:hover, .gePrimaryBtn:focus { background-color: #357ae8; background-image: linear-gradient(#4d90fe 0px,#357ae8 100%); border: 1px solid #2f5bb7; color: #fff;}.gePrimaryBtn:disabled { opacity: .5;}'].join('\n');
  2140. if (!GraphViewer.blockedAncestorFrames())
  2141. {
  2142. document.getElementsByTagName('head')[0].appendChild(style);
  2143. }
  2144. }
  2145. catch (e)
  2146. {
  2147. // ignore
  2148. }
  2149. };
  2150. /**
  2151. * Lookup for URLs.
  2152. */
  2153. GraphViewer.cachedUrls = {};
  2154. /**
  2155. * Workaround for unsupported CORS in IE9 XHR
  2156. */
  2157. GraphViewer.getUrl = function(url, onload, onerror)
  2158. {
  2159. if (GraphViewer.cachedUrls[url] != null)
  2160. {
  2161. onload(GraphViewer.cachedUrls[url]);
  2162. }
  2163. else
  2164. {
  2165. var xhr = (navigator.userAgent != null && navigator.userAgent.indexOf('MSIE 9') > 0) ?
  2166. new XDomainRequest() : new XMLHttpRequest();
  2167. xhr.open('GET', url);
  2168. xhr.onload = function()
  2169. {
  2170. onload((xhr.getText != null) ? xhr.getText() : xhr.responseText);
  2171. };
  2172. xhr.onerror = onerror;
  2173. xhr.send();
  2174. }
  2175. };
  2176. /**
  2177. * Redirects editing to absolue URLs.
  2178. */
  2179. GraphViewer.resizeSensorEnabled = true;
  2180. /**
  2181. * Specifies if ResizeObserver should be used instead of ResizeSensor.
  2182. * Default is true.
  2183. */
  2184. GraphViewer.useResizeObserver = true;
  2185. /**
  2186. * Redirects editing to absolue URLs.
  2187. */
  2188. GraphViewer.useResizeSensor = true;
  2189. /**
  2190. * Copyright Marc J. Schmidt. See the LICENSE file at the top-level
  2191. * directory of this distribution and at
  2192. * https://github.com/marcj/css-element-queries/blob/master/LICENSE.
  2193. */
  2194. (function() {
  2195. // Only used for the dirty checking, so the event callback count is limted to max 1 call per fps per sensor.
  2196. // In combination with the event based resize sensor this saves cpu time, because the sensor is too fast and
  2197. // would generate too many unnecessary events.
  2198. var requestAnimationFrame = window.requestAnimationFrame ||
  2199. window.mozRequestAnimationFrame ||
  2200. window.webkitRequestAnimationFrame ||
  2201. function (fn) {
  2202. return window.setTimeout(fn, 20);
  2203. };
  2204. /**
  2205. * Class for dimension change detection.
  2206. *
  2207. * @param {Element|Element[]|Elements|jQuery} element
  2208. * @param {Function} callback
  2209. *
  2210. * @constructor
  2211. */
  2212. var ResizeSensor = function(element, fn) {
  2213. var callback = function()
  2214. {
  2215. if (GraphViewer.resizeSensorEnabled)
  2216. {
  2217. fn();
  2218. }
  2219. };
  2220. // Uses ResizeObserver, if available
  2221. if (GraphViewer.useResizeObserver && typeof ResizeObserver !== 'undefined')
  2222. {
  2223. var callbackThread = null;
  2224. var active = false;
  2225. new ResizeObserver(function()
  2226. {
  2227. if (!active)
  2228. {
  2229. if (callbackThread != null)
  2230. {
  2231. window.clearTimeout(callbackThread);
  2232. }
  2233. callbackThread = window.setTimeout(function()
  2234. {
  2235. active = true;
  2236. callback();
  2237. callbackThread = null;
  2238. active = false;
  2239. }, 200);
  2240. }
  2241. }).observe(element)
  2242. return
  2243. }
  2244. /**
  2245. *
  2246. * @constructor
  2247. */
  2248. function EventQueue() {
  2249. this.q = [];
  2250. this.add = function(ev) {
  2251. this.q.push(ev);
  2252. };
  2253. var i, j;
  2254. this.call = function() {
  2255. for (i = 0, j = this.q.length; i < j; i++) {
  2256. this.q[i].call();
  2257. }
  2258. };
  2259. }
  2260. /**
  2261. * @param {HTMLElement} element
  2262. * @param {String} prop
  2263. * @returns {String|Number}
  2264. */
  2265. function getComputedStyle(element, prop) {
  2266. if (element.currentStyle) {
  2267. return element.currentStyle[prop];
  2268. } else if (window.getComputedStyle) {
  2269. return window.getComputedStyle(element, null).getPropertyValue(prop);
  2270. } else {
  2271. return element.style[prop];
  2272. }
  2273. }
  2274. /**
  2275. *
  2276. * @param {HTMLElement} element
  2277. * @param {Function} resized
  2278. */
  2279. function attachResizeEvent(element, resized) {
  2280. if (!element.resizedAttached) {
  2281. element.resizedAttached = new EventQueue();
  2282. element.resizedAttached.add(resized);
  2283. } else if (element.resizedAttached) {
  2284. element.resizedAttached.add(resized);
  2285. return;
  2286. }
  2287. element.resizeSensor = document.createElement('div');
  2288. element.resizeSensor.className = 'resize-sensor';
  2289. var style = 'position: absolute; left: 0; top: 0; right: 0; bottom: 0; overflow: hidden; z-index: -1; visibility: hidden;';
  2290. var styleChild = 'position: absolute; left: 0; top: 0; transition: 0s;';
  2291. element.resizeSensor.style.cssText = style;
  2292. element.resizeSensor.innerHTML =
  2293. '<div class="resize-sensor-expand" style="' + style + '">' +
  2294. '<div style="' + styleChild + '"></div>' +
  2295. '</div>' +
  2296. '<div class="resize-sensor-shrink" style="' + style + '">' +
  2297. '<div style="' + styleChild + ' width: 200%; height: 200%"></div>' +
  2298. '</div>';
  2299. element.appendChild(element.resizeSensor);
  2300. // FIXME: Should not change element style
  2301. if (getComputedStyle(element, 'position') == 'static') {
  2302. element.style.position = 'relative';
  2303. }
  2304. var expand = element.resizeSensor.childNodes[0];
  2305. var expandChild = expand.childNodes[0];
  2306. var shrink = element.resizeSensor.childNodes[1];
  2307. var reset = function() {
  2308. expandChild.style.width = 100000 + 'px';
  2309. expandChild.style.height = 100000 + 'px';
  2310. expand.scrollLeft = 100000;
  2311. expand.scrollTop = 100000;
  2312. shrink.scrollLeft = 100000;
  2313. shrink.scrollTop = 100000;
  2314. };
  2315. reset();
  2316. var dirty = false;
  2317. var dirtyChecking = function(){
  2318. if (!element.resizedAttached) return;
  2319. if (dirty) {
  2320. element.resizedAttached.call();
  2321. dirty = false;
  2322. }
  2323. requestAnimationFrame(dirtyChecking);
  2324. };
  2325. requestAnimationFrame(dirtyChecking);
  2326. var lastWidth, lastHeight;
  2327. var cachedWidth, cachedHeight; //useful to not query offsetWidth twice
  2328. var onScroll = function() {
  2329. if ((cachedWidth = element.offsetWidth) != lastWidth || (cachedHeight = element.offsetHeight) != lastHeight) {
  2330. dirty = true;
  2331. lastWidth = cachedWidth;
  2332. lastHeight = cachedHeight;
  2333. }
  2334. reset();
  2335. };
  2336. var addEvent = function(el, name, cb) {
  2337. if (el.attachEvent) {
  2338. el.attachEvent('on' + name, cb);
  2339. } else {
  2340. el.addEventListener(name, cb);
  2341. }
  2342. };
  2343. addEvent(expand, 'scroll', onScroll);
  2344. addEvent(shrink, 'scroll', onScroll);
  2345. }
  2346. var elementType = Object.prototype.toString.call(element);
  2347. var isCollectionTyped = ('[object Array]' === elementType
  2348. || ('[object NodeList]' === elementType)
  2349. || ('[object HTMLCollection]' === elementType)
  2350. || ('undefined' !== typeof jQuery && element instanceof jQuery) //jquery
  2351. || ('undefined' !== typeof Elements && element instanceof Elements) //mootools
  2352. );
  2353. if (isCollectionTyped) {
  2354. var i = 0, j = element.length;
  2355. for (; i < j; i++) {
  2356. attachResizeEvent(element[i], callback);
  2357. }
  2358. } else {
  2359. attachResizeEvent(element, callback);
  2360. }
  2361. this.detach = function() {
  2362. if (isCollectionTyped) {
  2363. var i = 0, j = element.length;
  2364. for (; i < j; i++) {
  2365. ResizeSensor.detach(element[i]);
  2366. }
  2367. } else {
  2368. ResizeSensor.detach(element);
  2369. }
  2370. };
  2371. };
  2372. ResizeSensor.detach = function(element) {
  2373. if (element.resizeSensor) {
  2374. element.removeChild(element.resizeSensor);
  2375. delete element.resizeSensor;
  2376. delete element.resizedAttached;
  2377. }
  2378. };
  2379. window.ResizeSensor = ResizeSensor;
  2380. })();