EditorUi.js 154 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036303730383039304030413042304330443045304630473048304930503051305230533054305530563057305830593060306130623063306430653066306730683069307030713072307330743075307630773078307930803081308230833084308530863087308830893090309130923093309430953096309730983099310031013102310331043105310631073108310931103111311231133114311531163117311831193120312131223123312431253126312731283129313031313132313331343135313631373138313931403141314231433144314531463147314831493150315131523153315431553156315731583159316031613162316331643165316631673168316931703171317231733174317531763177317831793180318131823183318431853186318731883189319031913192319331943195319631973198319932003201320232033204320532063207320832093210321132123213321432153216321732183219322032213222322332243225322632273228322932303231323232333234323532363237323832393240324132423243324432453246324732483249325032513252325332543255325632573258325932603261326232633264326532663267326832693270327132723273327432753276327732783279328032813282328332843285328632873288328932903291329232933294329532963297329832993300330133023303330433053306330733083309331033113312331333143315331633173318331933203321332233233324332533263327332833293330333133323333333433353336333733383339334033413342334333443345334633473348334933503351335233533354335533563357335833593360336133623363336433653366336733683369337033713372337333743375337633773378337933803381338233833384338533863387338833893390339133923393339433953396339733983399340034013402340334043405340634073408340934103411341234133414341534163417341834193420342134223423342434253426342734283429343034313432343334343435343634373438343934403441344234433444344534463447344834493450345134523453345434553456345734583459346034613462346334643465346634673468346934703471347234733474347534763477347834793480348134823483348434853486348734883489349034913492349334943495349634973498349935003501350235033504350535063507350835093510351135123513351435153516351735183519352035213522352335243525352635273528352935303531353235333534353535363537353835393540354135423543354435453546354735483549355035513552355335543555355635573558355935603561356235633564356535663567356835693570357135723573357435753576357735783579358035813582358335843585358635873588358935903591359235933594359535963597359835993600360136023603360436053606360736083609361036113612361336143615361636173618361936203621362236233624362536263627362836293630363136323633363436353636363736383639364036413642364336443645364636473648364936503651365236533654365536563657365836593660366136623663366436653666366736683669367036713672367336743675367636773678367936803681368236833684368536863687368836893690369136923693369436953696369736983699370037013702370337043705370637073708370937103711371237133714371537163717371837193720372137223723372437253726372737283729373037313732373337343735373637373738373937403741374237433744374537463747374837493750375137523753375437553756375737583759376037613762376337643765376637673768376937703771377237733774377537763777377837793780378137823783378437853786378737883789379037913792379337943795379637973798379938003801380238033804380538063807380838093810381138123813381438153816381738183819382038213822382338243825382638273828382938303831383238333834383538363837383838393840384138423843384438453846384738483849385038513852385338543855385638573858385938603861386238633864386538663867386838693870387138723873387438753876387738783879388038813882388338843885388638873888388938903891389238933894389538963897389838993900390139023903390439053906390739083909391039113912391339143915391639173918391939203921392239233924392539263927392839293930393139323933393439353936393739383939394039413942394339443945394639473948394939503951395239533954395539563957395839593960396139623963396439653966396739683969397039713972397339743975397639773978397939803981398239833984398539863987398839893990399139923993399439953996399739983999400040014002400340044005400640074008400940104011401240134014401540164017401840194020402140224023402440254026402740284029403040314032403340344035403640374038403940404041404240434044404540464047404840494050405140524053405440554056405740584059406040614062406340644065406640674068406940704071407240734074407540764077407840794080408140824083408440854086408740884089409040914092409340944095409640974098409941004101410241034104410541064107410841094110411141124113411441154116411741184119412041214122412341244125412641274128412941304131413241334134413541364137413841394140414141424143414441454146414741484149415041514152415341544155415641574158415941604161416241634164416541664167416841694170417141724173417441754176417741784179418041814182418341844185418641874188418941904191419241934194419541964197419841994200420142024203420442054206420742084209421042114212421342144215421642174218421942204221422242234224422542264227422842294230423142324233423442354236423742384239424042414242424342444245424642474248424942504251425242534254425542564257425842594260426142624263426442654266426742684269427042714272427342744275427642774278427942804281428242834284428542864287428842894290429142924293429442954296429742984299430043014302430343044305430643074308430943104311431243134314431543164317431843194320432143224323432443254326432743284329433043314332433343344335433643374338433943404341434243434344434543464347434843494350435143524353435443554356435743584359436043614362436343644365436643674368436943704371437243734374437543764377437843794380438143824383438443854386438743884389439043914392439343944395439643974398439944004401440244034404440544064407440844094410441144124413441444154416441744184419442044214422442344244425442644274428442944304431443244334434443544364437443844394440444144424443444444454446444744484449445044514452445344544455445644574458445944604461446244634464446544664467446844694470447144724473447444754476447744784479448044814482448344844485448644874488448944904491449244934494449544964497449844994500450145024503450445054506450745084509451045114512451345144515451645174518451945204521452245234524452545264527452845294530453145324533453445354536453745384539454045414542454345444545454645474548454945504551455245534554455545564557455845594560456145624563456445654566456745684569457045714572457345744575457645774578457945804581458245834584458545864587458845894590459145924593459445954596459745984599460046014602460346044605460646074608460946104611461246134614461546164617461846194620462146224623462446254626462746284629463046314632463346344635463646374638463946404641464246434644464546464647464846494650465146524653465446554656465746584659466046614662466346644665466646674668466946704671467246734674467546764677467846794680468146824683468446854686468746884689469046914692469346944695469646974698469947004701470247034704470547064707470847094710471147124713471447154716471747184719472047214722472347244725472647274728472947304731473247334734473547364737473847394740474147424743474447454746474747484749475047514752475347544755475647574758475947604761476247634764476547664767476847694770477147724773477447754776477747784779478047814782478347844785478647874788478947904791479247934794479547964797479847994800480148024803480448054806480748084809481048114812481348144815481648174818481948204821482248234824482548264827482848294830483148324833483448354836483748384839484048414842484348444845484648474848484948504851485248534854485548564857485848594860486148624863486448654866486748684869487048714872487348744875487648774878487948804881488248834884488548864887488848894890489148924893489448954896489748984899490049014902490349044905490649074908490949104911491249134914491549164917491849194920492149224923492449254926492749284929493049314932493349344935493649374938493949404941494249434944494549464947494849494950495149524953495449554956495749584959496049614962496349644965496649674968496949704971497249734974497549764977497849794980498149824983498449854986498749884989499049914992499349944995499649974998499950005001500250035004500550065007500850095010501150125013501450155016501750185019502050215022502350245025502650275028502950305031503250335034503550365037503850395040504150425043504450455046504750485049505050515052505350545055505650575058505950605061506250635064506550665067506850695070507150725073507450755076507750785079508050815082508350845085508650875088508950905091509250935094509550965097509850995100510151025103510451055106510751085109511051115112511351145115511651175118511951205121512251235124512551265127512851295130513151325133513451355136513751385139514051415142514351445145514651475148514951505151515251535154515551565157515851595160516151625163516451655166516751685169517051715172517351745175517651775178517951805181518251835184518551865187518851895190519151925193519451955196519751985199520052015202520352045205520652075208520952105211521252135214521552165217521852195220522152225223522452255226522752285229523052315232523352345235523652375238523952405241524252435244524552465247524852495250525152525253525452555256525752585259526052615262526352645265526652675268526952705271527252735274527552765277527852795280528152825283528452855286528752885289529052915292529352945295529652975298529953005301530253035304530553065307530853095310531153125313531453155316531753185319532053215322532353245325532653275328532953305331533253335334533553365337533853395340534153425343534453455346534753485349535053515352535353545355535653575358535953605361536253635364536553665367536853695370537153725373537453755376537753785379538053815382538353845385538653875388538953905391539253935394539553965397539853995400540154025403540454055406540754085409541054115412541354145415541654175418541954205421542254235424542554265427542854295430543154325433543454355436543754385439544054415442544354445445544654475448544954505451545254535454545554565457545854595460546154625463546454655466546754685469547054715472547354745475547654775478547954805481548254835484548554865487548854895490549154925493549454955496549754985499550055015502550355045505550655075508550955105511551255135514551555165517551855195520552155225523552455255526552755285529553055315532553355345535553655375538553955405541554255435544554555465547554855495550555155525553555455555556555755585559556055615562556355645565556655675568556955705571557255735574557555765577557855795580558155825583558455855586558755885589559055915592559355945595559655975598559956005601560256035604560556065607560856095610561156125613561456155616561756185619562056215622562356245625562656275628562956305631563256335634563556365637563856395640564156425643564456455646564756485649565056515652565356545655565656575658565956605661566256635664566556665667566856695670567156725673567456755676567756785679568056815682568356845685568656875688568956905691569256935694
  1. /**
  2. * Copyright (c) 2006-2016, JGraph Ltd
  3. * Copyright (c) 2006-2016, Gaudenz Alder
  4. */
  5. (function()
  6. {
  7. /**
  8. * Version
  9. */
  10. EditorUi.VERSION = '@DRAWIO-VERSION@';
  11. /**
  12. * Overrides compact UI setting.
  13. */
  14. EditorUi.compactUi = uiTheme != 'atlas';
  15. /**
  16. *
  17. */
  18. if (urlParams['dev'] == '1')
  19. {
  20. Editor.prototype.editBlankUrl = Editor.prototype.editBlankUrl + '&dev=1';
  21. Editor.prototype.editBlankFallbackUrl = Editor.prototype.editBlankFallbackUrl + '&dev=1';
  22. }
  23. /**
  24. * Capability check for canvas export
  25. */
  26. (function()
  27. {
  28. EditorUi.prototype.useCanvasForExport = false;
  29. try
  30. {
  31. var canvas = document.createElement('canvas');
  32. var img = new Image();
  33. // LATER: Capability check should not be async
  34. img.onload = function()
  35. {
  36. try
  37. {
  38. var ctx = canvas.getContext('2d');
  39. ctx.drawImage(img, 0, 0);
  40. // LATER: Fix security error caused by foreignObjects in Safari for toDataUri (tainted canvas)
  41. var result = canvas.toDataURL('image/png');
  42. EditorUi.prototype.useCanvasForExport = result != null && result.length > 6;
  43. }
  44. catch (e)
  45. {
  46. // ignore
  47. }
  48. };
  49. // Checks if SVG with foreignObject can be exported
  50. var svg = '<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="1px" height="1px" version="1.1"><foreignObject pointer-events="all" width="1" height="1"><div xmlns="http://www.w3.org/1999/xhtml"></div></foreignObject></svg>';
  51. img.src = 'data:image/svg+xml;base64,' + btoa(unescape(encodeURIComponent(svg)));
  52. }
  53. catch (e)
  54. {
  55. // ignore
  56. }
  57. })();
  58. /**
  59. * Initializes math typesetting and loads respective code.
  60. */
  61. Editor.initMath = function(src, config)
  62. {
  63. src = (src != null) ? src : 'https://cdn.mathjax.org/mathjax/2.6-latest/MathJax.js?config=TeX-MML-AM_HTMLorMML';
  64. Editor.mathJaxQueue = [];
  65. Editor.doMathJaxRender = function(container)
  66. {
  67. MathJax.Hub.Queue(['Typeset', MathJax.Hub, container]);
  68. }
  69. // Disables global typesetting and messages on startup, adds queue for
  70. // asynchronous rendering while MathJax is loading
  71. window.MathJax =
  72. {
  73. skipStartupTypeset: true,
  74. showMathMenu: false,
  75. messageStyle: 'none',
  76. AuthorInit: function ()
  77. {
  78. // Specification recommends using SVG over HTML-CSS if browser is known
  79. // Check if too inconsistent with image export and print output
  80. MathJax.Hub.Config(config || {
  81. jax: ['input/TeX', 'input/MathML', 'input/AsciiMath', 'output/HTML-CSS'],
  82. extensions: ['tex2jax.js', 'mml2jax.js', 'asciimath2jax.js'],
  83. TeX: {
  84. extensions: ['AMSmath.js', 'AMSsymbols.js', 'noErrors.js', 'noUndefined.js']
  85. },
  86. // Ignores math in in-place editor
  87. tex2jax: {
  88. ignoreClass: 'mxCellEditor'
  89. },
  90. asciimath2jax: {
  91. ignoreClass: 'mxCellEditor'
  92. }
  93. });
  94. MathJax.Hub.Register.StartupHook('Begin', function()
  95. {
  96. for (var i = 0; i < Editor.mathJaxQueue.length; i++)
  97. {
  98. Editor.doMathJaxRender(Editor.mathJaxQueue[i]);
  99. }
  100. });
  101. }
  102. };
  103. // Adds global enqueue method for async rendering
  104. Editor.MathJaxRender = function(container)
  105. {
  106. // Initial rendering when MathJax finished loading
  107. if (typeof(MathJax) !== 'undefined' && typeof(MathJax.Hub) !== 'undefined')
  108. {
  109. Editor.doMathJaxRender(container);
  110. }
  111. else
  112. {
  113. Editor.mathJaxQueue.push(container);
  114. }
  115. };
  116. // Adds global clear queue method
  117. Editor.MathJaxClear = function()
  118. {
  119. Editor.mathJaxQueue = [];
  120. };
  121. // Updates typeset after changes
  122. var editorInit = Editor.prototype.init;
  123. Editor.prototype.init = function()
  124. {
  125. this.graph.addListener(mxEvent.SIZE, mxUtils.bind(this, function(sender, evt)
  126. {
  127. if (this.graph.mathEnabled)
  128. {
  129. Editor.MathJaxRender(this.graph.container);
  130. }
  131. }));
  132. };
  133. var tags = document.getElementsByTagName('script');
  134. if (tags != null && tags.length > 0)
  135. {
  136. var script = document.createElement('script');
  137. script.type = 'text/javascript';
  138. script.src = src;
  139. tags[0].parentNode.appendChild(script);
  140. }
  141. };
  142. /**
  143. * Used in the GraphViewer lightbox.
  144. */
  145. Editor.closeImage = (mxClient.IS_SVG) ? 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABkAAAAZCAMAAADzN3VRAAAApVBMVEUAAAD////k5OT///8AAAB1dXXMzMz9/f39/f37+/v5+fn+/v7///9iYmJaWlqFhYWnp6ejo6OHh4f////////////////7+/v5+fnx8fH///8AAAD///8bGxv7+/v5+fkoKCghISFDQ0MYGBjh4eHY2Njb29tQUFBvb29HR0c/Pz82NjYrKyu/v78SEhLu7u7s7OzV1dVVVVU7OzsVFRXAv78QEBBzqehMAAAAG3RSTlMAA/7p/vz5xZlrTiPL/v78+/v7+OXd2TYQDs8L70ZbAAABKUlEQVQoz3VS13LCMBBUXHChd8iukDslQChJ/v/TchaG4cXS+OSb1c7trU7V60OpdRz2ZtNZL4zXNlcN8BEtSG6+NxIXkeRPoBuQ1cjvZ31/VJFB10ISli6diYfH8iYO3WUNCcNlB0gTrXOtkxTo0O1aKKiBBMhhv2MNBQKoiA5wxlZo0JDzD3AYKbWacyj3fs01wxey0pyEP+R8pWKWXoqtIZ0DDg5pbki9krEKOa6LVDQsdoXEsi46Zqh69KFz7B1u7Hb2yDV8firXDKBlZ4UFiswKGRhXTS93/ECK7yxnJ3+S3y/ThpO+cfSD017nqa18aasabU0/t7d+tk0/1oMEJ1NaD67iwdF68OabFSLn+eHb0+vjy+uk8br9fdrftH0O2menfd7+AQfYM/lNjoDHAAAAAElFTkSuQmCC' : IMAGE_PATH + '/delete.png';
  146. /**
  147. * Adds a shadow filter to the given svg root.
  148. */
  149. Editor.prototype.addSvgShadow = function(svgRoot, group, createOnly)
  150. {
  151. createOnly = (createOnly != null) ? createOnly : false;
  152. var svgDoc = svgRoot.ownerDocument;
  153. var filter = (svgDoc.createElementNS != null) ?
  154. svgDoc.createElementNS(mxConstants.NS_SVG, 'filter') : svgDoc.createElement('filter');
  155. filter.setAttribute('id', 'dropShadow');
  156. var blur = (svgDoc.createElementNS != null) ?
  157. svgDoc.createElementNS(mxConstants.NS_SVG, 'feGaussianBlur') : svgDoc.createElement('feGaussianBlur');
  158. blur.setAttribute('in', 'SourceAlpha');
  159. blur.setAttribute('stdDeviation', '1.7');
  160. blur.setAttribute('result', 'blur');
  161. filter.appendChild(blur);
  162. var offset = (svgDoc.createElementNS != null) ?
  163. svgDoc.createElementNS(mxConstants.NS_SVG, 'feOffset') : svgDoc.createElement('feOffset');
  164. offset.setAttribute('in', 'blur');
  165. offset.setAttribute('dx', '3');
  166. offset.setAttribute('dy', '3');
  167. offset.setAttribute('result', 'offsetBlur');
  168. filter.appendChild(offset);
  169. var flood = (svgDoc.createElementNS != null) ?
  170. svgDoc.createElementNS(mxConstants.NS_SVG, 'feFlood') : svgDoc.createElement('feFlood');
  171. flood.setAttribute('flood-color', '#3D4574');
  172. flood.setAttribute('flood-opacity', '0.4');
  173. flood.setAttribute('result', 'offsetColor');
  174. filter.appendChild(flood);
  175. var composite = (svgDoc.createElementNS != null) ?
  176. svgDoc.createElementNS(mxConstants.NS_SVG, 'feComposite') : svgDoc.createElement('feComposite');
  177. composite.setAttribute('in', 'offsetColor');
  178. composite.setAttribute('in2', 'offsetBlur');
  179. composite.setAttribute('operator', 'in');
  180. composite.setAttribute('result', 'offsetBlur');
  181. filter.appendChild(composite);
  182. var feBlend = (svgDoc.createElementNS != null) ?
  183. svgDoc.createElementNS(mxConstants.NS_SVG, 'feBlend') : svgDoc.createElement('feBlend');
  184. feBlend.setAttribute('in', 'SourceGraphic');
  185. feBlend.setAttribute('in2', 'offsetBlur');
  186. filter.appendChild(feBlend);
  187. // Creates defs element if not available
  188. var defs = svgRoot.getElementsByTagName('defs');
  189. var defsElt = null;
  190. if (defs.length == 0)
  191. {
  192. defsElt = (svgDoc.createElementNS != null) ?
  193. svgDoc.createElementNS(mxConstants.NS_SVG, 'defs') : svgDoc.createElement('defs');
  194. if (svgRoot.firstChild != null)
  195. {
  196. svgRoot.insertBefore(defsElt, svgRoot.firstChild);
  197. }
  198. else
  199. {
  200. svgRoot.appendChild(defsElt);
  201. }
  202. }
  203. else
  204. {
  205. defsElt = defs[0];
  206. }
  207. defsElt.appendChild(filter);
  208. if (!createOnly)
  209. {
  210. (group || svgRoot.getElementsByTagName('g')[0]).setAttribute('filter', 'url(#dropShadow)');
  211. if (!isNaN(parseInt(svgRoot.getAttribute('width'))))
  212. {
  213. svgRoot.setAttribute('width', parseInt(svgRoot.getAttribute('width')) + 6);
  214. svgRoot.setAttribute('height', parseInt(svgRoot.getAttribute('height')) + 6);
  215. }
  216. }
  217. return filter;
  218. };
  219. /**
  220. * Math support.
  221. */
  222. Editor.prototype.originalNoForeignObject = mxClient.NO_FO;
  223. var editorUpdateGraphComponents = Editor.prototype.updateGraphComponents;
  224. Editor.prototype.updateGraphComponents = function()
  225. {
  226. editorUpdateGraphComponents.apply(this, arguments);
  227. mxClient.NO_FO = (this.graph.mathEnabled && Editor.MathJaxRender != null) ? true : this.originalNoForeignObject;
  228. };
  229. EditorUi.prototype.setMathEnabled = function(value)
  230. {
  231. this.editor.graph.mathEnabled = value;
  232. this.editor.updateGraphComponents();
  233. this.editor.graph.refresh();
  234. this.fireEvent(new mxEventObject('mathEnabledChanged'));
  235. };
  236. EditorUi.prototype.isMathEnabled = function(value)
  237. {
  238. return this.editor.graph.mathEnabled;
  239. };
  240. // Helper method to move picket to top
  241. EditorUi.prototype.movePickersToTop = function()
  242. {
  243. var divs = document.getElementsByTagName('div');
  244. for (var i = 0; i < divs.length; i++)
  245. {
  246. if (divs[i].className == 'picker modal-dialog picker-dialog')
  247. {
  248. divs[i].style.zIndex = mxPopupMenu.prototype.zIndex + 1;
  249. }
  250. }
  251. };
  252. // Overrides to call print asynchronously after (disables immediate print here)
  253. if (window.PrintDialog)
  254. {
  255. var printDialogShowPrintPreview = PrintDialog.showPreview;
  256. PrintDialog.showPreview = function(preview, print)
  257. {
  258. if (typeof(MathJax) !== 'undefined' && preview.graph.mathEnabled)
  259. {
  260. print = false;
  261. }
  262. return printDialogShowPrintPreview.apply(this, arguments);
  263. };
  264. // Adds MathJax to print preview
  265. var printDialogCreatePrintPreview = PrintDialog.createPrintPreview;
  266. PrintDialog.createPrintPreview = function(graph, scale, pf, border, x0, y0, autoOrigin, print)
  267. {
  268. var preview = printDialogCreatePrintPreview.apply(this, arguments);
  269. if (typeof(MathJax) !== 'undefined' && graph.mathEnabled)
  270. {
  271. // Using writePostfix is workaround for blocking of DOM processing
  272. // in Chrome if using writeHead for injecting async script tag
  273. var writePostfix = preview.writePostfix;
  274. preview.writePostfix = function(doc, css)
  275. {
  276. writePostfix.apply(this, arguments);
  277. // Disables status message in print output
  278. doc.writeln('<script type="text/x-mathjax-config">');
  279. doc.writeln('MathJax.Hub.Config({');
  280. doc.writeln('messageStyle: "none",');
  281. doc.writeln('jax: ["input/TeX", "input/MathML", "input/AsciiMath", "output/HTML-CSS"],');
  282. doc.writeln('extensions: ["tex2jax.js", "mml2jax.js", "asciimath2jax.js"],');
  283. doc.writeln('TeX: {');
  284. doc.writeln(' extensions: ["AMSmath.js", "AMSsymbols.js", "noErrors.js", "noUndefined.js"]');
  285. doc.writeln('}');
  286. doc.writeln('});');
  287. // Adds asynchronous printing when MathJax finished rendering
  288. if (print)
  289. {
  290. doc.writeln('MathJax.Hub.Queue(function () {');
  291. doc.writeln('window.print();');
  292. doc.writeln('});');
  293. }
  294. doc.writeln('</script>');
  295. doc.writeln('<script type="text/javascript" src="https://cdn.mathjax.org/mathjax/2.6-latest/MathJax.js"></script>');
  296. };
  297. }
  298. return preview;
  299. };
  300. }
  301. /**
  302. * Global switches for the export dialog.
  303. */
  304. if (window.ExportDialog)
  305. {
  306. ExportDialog.showXmlOption = false;
  307. ExportDialog.showGifOption = false;
  308. ExportDialog.getExportParameter = function(ui, format)
  309. {
  310. return function()
  311. {
  312. // LATER Fix decoding of HTML file in backend (remove argument in getFileData)
  313. return 'xml=' + encodeURIComponent(ui.getFileData(true));
  314. };
  315. };
  316. }
  317. /**
  318. * Specifies the app name. Default is document.title.
  319. */
  320. Editor.prototype.appName = 'draw.io';
  321. /**
  322. * Translates this point by the given vector.
  323. *
  324. * @param {number} dx X-coordinate of the translation.
  325. * @param {number} dy Y-coordinate of the translation.
  326. */
  327. EditorUi.prototype.isOfflineApp = function()
  328. {
  329. return (urlParams['offline'] == '1');
  330. };
  331. /**
  332. * Returns true if this offline app is offline.
  333. */
  334. EditorUi.prototype.isOffline = function()
  335. {
  336. // In FF navigator.onLine is always true
  337. return (mxClient.IS_FF && this.isOfflineApp()) || !navigator.onLine || urlParams['stealth'] == '1';
  338. };
  339. /**
  340. * Translates this point by the given vector.
  341. *
  342. * @param {number} dx X-coordinate of the translation.
  343. * @param {number} dy Y-coordinate of the translation.
  344. */
  345. EditorUi.prototype.createSpinner = function(x, y, size)
  346. {
  347. size = (size != null) ? size : 24;
  348. var spinner = new Spinner({
  349. lines: 12, // The number of lines to draw
  350. length: size, // The length of each line
  351. width: Math.round(size / 3), // The line thickness
  352. radius: Math.round(size / 2), // The radius of the inner circle
  353. rotate: 0, // The rotation offset
  354. color: '#000', // #rgb or #rrggbb
  355. speed: 1.5, // Rounds per second
  356. trail: 60, // Afterglow percentage
  357. shadow: false, // Whether to render a shadow
  358. hwaccel: false, // Whether to use hardware acceleration
  359. zIndex: 2e9 // The z-index (defaults to 2000000000)
  360. });
  361. // Extends spin method to include an optional label
  362. var oldSpin = spinner.spin;
  363. spinner.spin = function(container, label)
  364. {
  365. var result = false;
  366. if (!this.active)
  367. {
  368. oldSpin.call(this, container);
  369. this.active = true;
  370. if (label != null)
  371. {
  372. var status = document.createElement('div');
  373. status.style.position = 'absolute';
  374. status.style.whiteSpace = 'nowrap';
  375. status.style.background = '#4B4243';
  376. status.style.color = 'white';
  377. status.style.fontFamily = 'Helvetica, Arial';
  378. status.style.fontSize = '9pt';
  379. status.style.padding = '6px';
  380. status.style.paddingLeft = '10px';
  381. status.style.paddingRight = '10px';
  382. status.style.zIndex = 2e9;
  383. status.style.left = Math.max(0, x) + 'px';
  384. status.style.top = Math.max(0, y + 70) + 'px';
  385. mxUtils.setPrefixedStyle(status.style, 'borderRadius', '6px');
  386. mxUtils.setPrefixedStyle(status.style, 'boxShadow', '2px 2px 3px 0px #ddd');
  387. mxUtils.setPrefixedStyle(status.style, 'transform', 'translate(-50%,-50%)');
  388. status.innerHTML = label + '...';
  389. container.appendChild(status);
  390. spinner.status = status;
  391. // Centers the label in older IE versions
  392. if (mxClient.IS_VML && (document.documentMode == null || document.documentMode <= 8))
  393. {
  394. status.style.left = Math.round(Math.max(0, x - status.offsetWidth / 2)) + 'px';
  395. status.style.top = Math.round(Math.max(0, y + 70 - status.offsetHeight / 2)) + 'px';
  396. }
  397. }
  398. // Pause returns a function to resume the spinner
  399. this.pause = mxUtils.bind(this, function()
  400. {
  401. var fn = function() { };
  402. if (this.active)
  403. {
  404. fn = mxUtils.bind(this, function()
  405. {
  406. this.spin(container, label);
  407. });
  408. }
  409. this.stop();
  410. return fn;
  411. });
  412. result = true;
  413. }
  414. return result;
  415. };
  416. // Extends stop method to remove the optional label
  417. var oldStop = spinner.stop;
  418. spinner.stop = function()
  419. {
  420. oldStop.call(this);
  421. this.active = false;
  422. if (spinner.status != null)
  423. {
  424. spinner.status.parentNode.removeChild(spinner.status);
  425. spinner.status = null;
  426. }
  427. };
  428. return spinner;
  429. };
  430. /**
  431. * Static method for pasing PNG files.
  432. */
  433. EditorUi.parsePng = function(f, fn, error)
  434. {
  435. var pos = 0;
  436. function fread(d, count)
  437. {
  438. var start = pos;
  439. pos += count;
  440. return d.substring(start, pos);
  441. };
  442. // Reads unsigned long 32 bit big endian
  443. function _freadint(d)
  444. {
  445. var bytes = fread(d, 4);
  446. return bytes.charCodeAt(3) + (bytes.charCodeAt(2) << 8) +
  447. (bytes.charCodeAt(1) << 16) + (bytes.charCodeAt(0) << 24);
  448. };
  449. // Checks signature
  450. if (fread(f,8) != String.fromCharCode(137) + 'PNG' + String.fromCharCode(13, 10, 26, 10))
  451. {
  452. if (error != null)
  453. {
  454. error();
  455. }
  456. return;
  457. }
  458. // Reads header chunk
  459. fread(f,4);
  460. if (fread(f,4) != 'IHDR')
  461. {
  462. if (error != null)
  463. {
  464. error();
  465. }
  466. return;
  467. }
  468. fread(f, 17);
  469. do
  470. {
  471. var n = _freadint(f);
  472. var type = fread(f,4);
  473. if (fn != null)
  474. {
  475. if (fn(pos - 8, type, n))
  476. {
  477. break;
  478. }
  479. }
  480. value = fread(f,n);
  481. fread(f,4);
  482. if (type == 'IEND')
  483. {
  484. break;
  485. }
  486. }
  487. while (n);
  488. };
  489. /**
  490. * Helper function to extract the graph model XML node.
  491. */
  492. Editor.prototype.extractGraphModel = function(node, allowMxFile)
  493. {
  494. if (node != null && typeof(pako) !== 'undefined')
  495. {
  496. var tmp = node.ownerDocument.getElementsByTagName('div');
  497. var divs = [];
  498. if (tmp != null && tmp.length > 0)
  499. {
  500. for (var i = 0; i < tmp.length; i++)
  501. {
  502. if (tmp[i].getAttribute('class') == 'mxgraph')
  503. {
  504. divs.push(tmp[i]);
  505. break;
  506. }
  507. }
  508. }
  509. if (divs.length > 0)
  510. {
  511. var data = divs[0].getAttribute('data-mxgraph');
  512. if (data != null)
  513. {
  514. var config = JSON.parse(data);
  515. if (config != null && config.xml != null)
  516. {
  517. var doc2 = mxUtils.parseXml(config.xml);
  518. node = doc2.documentElement;
  519. }
  520. }
  521. else
  522. {
  523. var divs2 = divs[0].getElementsByTagName('div');
  524. if (divs2.length > 0)
  525. {
  526. var data = mxUtils.getTextContent(divs2[0]);
  527. data = this.graph.decompress(data);
  528. if (data.length > 0)
  529. {
  530. var doc2 = mxUtils.parseXml(data);
  531. node = doc2.documentElement;
  532. }
  533. }
  534. }
  535. }
  536. }
  537. if (node != null && node.nodeName == 'svg')
  538. {
  539. var tmp = node.getAttribute('content');
  540. if (tmp != null && tmp.charAt(0) != '<' && tmp.charAt(0) != '%')
  541. {
  542. tmp = unescape((window.atob) ? atob(tmp) : Base64.decode(cont, tmp));
  543. }
  544. if (tmp != null && tmp.charAt(0) == '%')
  545. {
  546. tmp = decodeURIComponent(tmp);
  547. }
  548. if (tmp != null && tmp.length > 0)
  549. {
  550. node = mxUtils.parseXml(tmp).documentElement;
  551. }
  552. else
  553. {
  554. throw {message: mxResources.get('notADiagramFile')};
  555. }
  556. }
  557. if (node != null && !allowMxFile)
  558. {
  559. var diagramNode = null;
  560. if (node.nodeName == 'diagram')
  561. {
  562. diagramNode = node;
  563. }
  564. else if (node.nodeName == 'mxfile')
  565. {
  566. var diagrams = node.getElementsByTagName('diagram');
  567. if (diagrams.length > 0)
  568. {
  569. diagramNode = diagrams[Math.max(0, Math.min(diagrams.length - 1, urlParams['page'] || 0))];
  570. }
  571. }
  572. if (diagramNode != null)
  573. {
  574. node = mxUtils.parseXml(this.graph.decompress(mxUtils.getTextContent(diagramNode))).documentElement;
  575. }
  576. }
  577. if (node != null && node.nodeName != 'mxGraphModel' && (!allowMxFile || node.nodeName != 'mxfile'))
  578. {
  579. node = null;
  580. }
  581. return node;
  582. };
  583. /**
  584. * Extracts the mxfile from the given HTML data from a data transfer event.
  585. */
  586. var editorUiExtractGraphModelFromHtml = EditorUi.prototype.extractGraphModelFromHtml;
  587. EditorUi.prototype.extractGraphModelFromHtml = function(data)
  588. {
  589. var result = editorUiExtractGraphModelFromHtml.apply(this, arguments);
  590. if (result == null)
  591. {
  592. try
  593. {
  594. var idx = data.indexOf('&lt;mxfile ');
  595. if (idx >= 0)
  596. {
  597. var idx2 = data.lastIndexOf('&lt;/mxfile&gt;');
  598. if (idx2 > idx)
  599. {
  600. result = data.substring(idx, idx2 + 15).replace(/&gt;/g, '>').
  601. replace(/&lt;/g, '<').replace(/\n/g, '');
  602. }
  603. }
  604. else
  605. {
  606. // Gets compressed data from mxgraph element in HTML document
  607. var doc = mxUtils.parseXml(data);
  608. var node = this.editor.extractGraphModel(doc.documentElement);
  609. result = (node != null) ? mxUtils.getXml(node) : '';
  610. }
  611. }
  612. catch (e)
  613. {
  614. // ignore
  615. }
  616. }
  617. return result;
  618. };
  619. /**
  620. *
  621. */
  622. EditorUi.prototype.setFileData = function(data)
  623. {
  624. this.currentPage = null;
  625. this.fileNode = null;
  626. this.pages = null;
  627. // Workaround for malformed xhtml meta element bug 07.08.16. The trailing slash was missing causing
  628. // reopen to fail trying to parse. Same fix in importFile() for import case.
  629. if (data != null && data.length > 0)
  630. {
  631. var index = data.indexOf('<meta charset="utf-8">');
  632. if (index >= 0)
  633. {
  634. var replaceString = '<meta charset="utf-8"/>';
  635. var replaceStrLen = replaceString.length;
  636. data = data.slice(0, index) + replaceString + data.slice(index + replaceStrLen - 1, data.length);
  637. }
  638. }
  639. var node = (data != null && data.length > 0) ? mxUtils.parseXml(data).documentElement : null;
  640. // Some nodes must be extracted here to find the mxfile node
  641. // LATER: Remove duplicate call to extractGraphModel in overridden setGraphXml
  642. var tmp = (node != null) ? this.editor.extractGraphModel(node, true) : null;
  643. if (tmp != null)
  644. {
  645. node = tmp;
  646. }
  647. if (node != null && node.nodeName == 'mxfile')
  648. {
  649. var nodes = node.getElementsByTagName('diagram');
  650. if (nodes.length > 1 || urlParams['pages'] == '1')
  651. {
  652. this.fileNode = node;
  653. this.pages = [];
  654. // Wraps page nodes
  655. for (var i = 0; i < nodes.length; i++)
  656. {
  657. var page = new DiagramPage(nodes[i]);
  658. // Checks for invalid filenames
  659. if (page.getName() == null)
  660. {
  661. page.setName(mxResources.get('pageWithNumber', [i + 1]));
  662. }
  663. this.pages.push(page);
  664. }
  665. this.currentPage = this.pages[Math.max(0, Math.min(this.pages.length - 1, urlParams['page'] || 0))];
  666. node = this.currentPage.node;
  667. }
  668. }
  669. // Creates tabbed file structure if enforced by URL
  670. if (urlParams['pages'] == '1' && this.fileNode == null)
  671. {
  672. this.fileNode = node.ownerDocument.createElement('mxfile');
  673. this.currentPage = new DiagramPage(node.ownerDocument.createElement('diagram'));
  674. this.pages = [this.currentPage];
  675. // Checks for invalid filenames
  676. if (this.currentPage.getName() == null)
  677. {
  678. this.currentPage.setName(mxResources.get('pageWithNumber', [1]));
  679. }
  680. }
  681. // Avoids scroll offset when switching page
  682. this.editor.setGraphXml(node);
  683. // Avoids duplicate parsing of the XML stored in the node
  684. if (this.currentPage != null)
  685. {
  686. this.currentPage.root = this.editor.graph.model.root;
  687. }
  688. };
  689. /**
  690. * Adds support for old stylesheets and compressed files
  691. */
  692. var editorSetGraphXml = Editor.prototype.setGraphXml;
  693. Editor.prototype.setGraphXml = function(node)
  694. {
  695. node = (node != null && node.nodeName != 'mxlibrary') ? this.extractGraphModel(node) : null;
  696. if (node != null)
  697. {
  698. // Checks input for parser errors
  699. var errs = node.getElementsByTagName('parsererror');
  700. if (errs != null && errs.length > 0)
  701. {
  702. var elt = errs[0];
  703. var divs = elt.getElementsByTagName('div');
  704. if (divs != null && divs.length > 0)
  705. {
  706. elt = divs[0];
  707. }
  708. throw {message: mxUtils.getTextContent(elt)};
  709. }
  710. else if (node.nodeName == 'mxGraphModel')
  711. {
  712. var style = node.getAttribute('style') || 'default-style2';
  713. // Decodes the style if required
  714. if (urlParams['embed'] != '1' && (style == null || style == ''))
  715. {
  716. var node2 = (this.graph.themes != null) ?
  717. this.graph.themes['default-old'] :
  718. mxUtils.load(STYLE_PATH + '/default-old.xml').getDocumentElement();
  719. if (node2 != null)
  720. {
  721. var dec2 = new mxCodec(node2.ownerDocument);
  722. dec2.decode(node2, this.graph.getStylesheet());
  723. }
  724. }
  725. else if (style != this.graph.currentStyle)
  726. {
  727. var node2 = (this.graph.themes != null) ?
  728. this.graph.themes[style] :
  729. mxUtils.load(STYLE_PATH + '/' + style + '.xml').getDocumentElement()
  730. if (node2 != null)
  731. {
  732. var dec2 = new mxCodec(node2.ownerDocument);
  733. dec2.decode(node2, this.graph.getStylesheet());
  734. }
  735. }
  736. this.graph.currentStyle = style;
  737. this.graph.mathEnabled = (urlParams['math'] == '1' || node.getAttribute('math') == '1');
  738. var bgImg = node.getAttribute('backgroundImage');
  739. if (bgImg != null)
  740. {
  741. bgImg = JSON.parse(bgImg);
  742. this.graph.setBackgroundImage(new mxImage(bgImg.src, bgImg.width, bgImg.height));
  743. }
  744. else
  745. {
  746. this.graph.setBackgroundImage(null);
  747. }
  748. mxClient.NO_FO = (this.graph.mathEnabled) ? true : this.originalNoForeignObject;
  749. this.graph.setShadowVisible(node.getAttribute('shadow') == '1', false);
  750. }
  751. // Calls updateGraphComponents
  752. editorSetGraphXml.apply(this, arguments);
  753. }
  754. else
  755. {
  756. throw {
  757. message: mxResources.get('notADiagramFile') || 'Invalid data',
  758. toString: function() { return this.message; }
  759. };
  760. }
  761. };
  762. /**
  763. * Adds persistent style to file
  764. */
  765. var editorGetGraphXml = Editor.prototype.getGraphXml;
  766. Editor.prototype.getGraphXml = function(ignoreSelection)
  767. {
  768. ignoreSelection = (ignoreSelection != null) ? ignoreSelection : true;
  769. var node = editorGetGraphXml.apply(this, arguments);
  770. // Adds the current style
  771. if (this.graph.currentStyle != null && this.graph.currentStyle != 'default-style2')
  772. {
  773. node.setAttribute('style', this.graph.currentStyle);
  774. }
  775. // Adds the background image
  776. if (this.graph.backgroundImage != null)
  777. {
  778. node.setAttribute('backgroundImage', JSON.stringify(this.graph.backgroundImage));
  779. }
  780. node.setAttribute('math', (this.graph.mathEnabled) ? '1' : '0');
  781. node.setAttribute('shadow', (this.graph.shadowVisible) ? '1' : '0');
  782. return node;
  783. };
  784. var editorResetGraph = Editor.prototype.resetGraph;
  785. Editor.prototype.resetGraph = function()
  786. {
  787. this.graph.mathEnabled = (urlParams['math'] == '1');
  788. this.graph.view.x0 = null;
  789. this.graph.view.y0 = null;
  790. mxClient.NO_FO = (this.graph.mathEnabled) ? true : this.originalNoForeignObject;
  791. editorResetGraph.apply(this, arguments);
  792. };
  793. /**
  794. * EditorUi Overrides
  795. */
  796. if (urlParams['offline'] == '1')
  797. {
  798. EditorUi.prototype.footerHeight = 4;
  799. }
  800. else
  801. {
  802. if (uiTheme == 'atlas')
  803. {
  804. if (typeof Toolbar !== 'undefined')
  805. {
  806. Toolbar.prototype.unselectedBackground = (mxClient.IS_QUIRKS) ? 'none' : 'linear-gradient(rgb(255, 255, 255) 0px, rgb(242, 242, 242) 100%)';
  807. Toolbar.prototype.selectedBackground = 'rgb(242, 242, 242)';
  808. }
  809. Editor.prototype.initialTopSpacing = 3;
  810. EditorUi.prototype.menubarHeight = 41;
  811. EditorUi.prototype.toolbarHeight = 38;
  812. EditorUi.prototype.hsplitPosition = 188;
  813. Sidebar.prototype.thumbWidth = 46;
  814. Sidebar.prototype.thumbHeight = 46;
  815. Sidebar.prototype.thumbPadding = (document.documentMode >= 5) ? 0 : 1;
  816. Sidebar.prototype.thumbBorder = 2;
  817. }
  818. else
  819. {
  820. if (urlParams['savesidebar'] == '1')
  821. {
  822. Sidebar.prototype.thumbWidth = 64;
  823. Sidebar.prototype.thumbHeight = 64;
  824. }
  825. }
  826. EditorUi.prototype.footerHeight = (screen.height <= 740) ? 5 : 46;
  827. // Fetches footer from page
  828. EditorUi.prototype.createFooter = function()
  829. {
  830. var footer = document.getElementById('geFooter');
  831. if (footer != null)
  832. {
  833. footer.style.visibility = 'visible';
  834. // Adds button to hide the footer
  835. var img = document.createElement('img');
  836. img.setAttribute('border', '0');
  837. img.setAttribute('src', Dialog.prototype.closeImage);
  838. img.setAttribute('title', mxResources.get('hide'));
  839. footer.appendChild(img)
  840. if (mxClient.IS_QUIRKS)
  841. {
  842. img.style.position = 'relative';
  843. img.style.styleFloat = 'right';
  844. img.style.top = '-30px';
  845. img.style.left = '164px';
  846. img.style.cursor = 'pointer';
  847. }
  848. mxEvent.addListener(img, 'click', mxUtils.bind(this, function()
  849. {
  850. this.hideFooter();
  851. }));
  852. }
  853. return footer;
  854. };
  855. }
  856. /**
  857. * Hides the footer.
  858. */
  859. EditorUi.prototype.hideFooter = function()
  860. {
  861. var footer = document.getElementById('geFooter');
  862. if (footer != null)
  863. {
  864. this.footerHeight = 0;
  865. footer.style.display = 'none';
  866. this.refresh();
  867. }
  868. };
  869. /**
  870. * Overrides image dialog to add image search and Google+.
  871. */
  872. EditorUi.prototype.showImageDialog = function(title, value, fn, ignoreExisting, convertDataUri)
  873. {
  874. // KNOWN: IE+FF don't return keyboard focus after image dialog (calling focus doesn't help)
  875. var dlg = new ImageDialog(this, title, value, fn, ignoreExisting, convertDataUri);
  876. this.showDialog(dlg.container, (Graph.fileSupport) ? 420 : 340, (Graph.fileSupport) ? 200 : 90, true, true);
  877. dlg.init();
  878. };
  879. /**
  880. * Hides the current menu.
  881. */
  882. EditorUi.prototype.showBackgroundImageDialog = function(apply)
  883. {
  884. apply = (apply != null) ? apply : mxUtils.bind(this, function(image)
  885. {
  886. this.setBackgroundImage(image);
  887. });
  888. var dlg = new BackgroundImageDialog(this, mxUtils.bind(this, function(image)
  889. {
  890. apply(image);
  891. }));
  892. this.showDialog(dlg.container, 360, 200, true, true);
  893. dlg.init();
  894. };
  895. /**
  896. * Hides the current menu.
  897. */
  898. EditorUi.prototype.showLibraryDialog = function(name, sidebar, images, file, mode)
  899. {
  900. var dlg = new LibraryDialog(this, name, sidebar, images, file, mode);
  901. this.showDialog(dlg.container, 620, 440, true, true, mxUtils.bind(this, function(cancel)
  902. {
  903. if (cancel && this.getCurrentFile() == null)
  904. {
  905. this.showSplash();
  906. }
  907. }));
  908. dlg.init();
  909. };
  910. /**
  911. * Allows for two buttons in the sidebar footer.
  912. */
  913. EditorUi.prototype.sidebarFooterHeight = (uiTheme == 'atlas') ? 36 : 36;
  914. /**
  915. * Specifies the default custom shape style.
  916. */
  917. EditorUi.prototype.defaultCustomShapeStyle = 'shape=stencil(tZRtTsQgEEBPw1+DJR7AoN6DbWftpAgE0Ortd/jYRGq72R+YNE2YgTePloEJGWblgA18ZuKFDcMj5/Sm8boZq+BgjCX4pTyqk6ZlKROitwusOMXKQDODx5iy4pXxZ5qTHiFHawxB0JrQZH7lCabQ0Fr+XWC1/E8zcsT/gAi+Subo2/3Mh6d/oJb5nU1b5tW7r2knautaa3T+U32o7f7vZwpJkaNDLORJjcu7t59m2jXxqX9un+tt022acsfmoKaQZ+vhhswZtS6Ne/ThQGt0IV0N3Yyv6P3CeT9/tHO0XFI5cAE=);whiteSpace=wrap;html=1;';
  918. /**
  919. * Hook for sidebar footer container.
  920. */
  921. EditorUi.prototype.createSidebarFooterContainer = function()
  922. {
  923. var div = this.createDiv('geSidebarContainer');
  924. div.style.position = 'absolute';
  925. div.style.overflow = 'hidden';
  926. div.style.borderWidth = '3px';
  927. var elt2 = document.createElement('a');
  928. elt2.setAttribute('href', 'javascript:void(0);');
  929. elt2.className = 'geTitle';
  930. elt2.style.height = '100%';
  931. elt2.style.paddingTop = '9px';
  932. mxUtils.write(elt2, mxResources.get('moreShapes') + '...');
  933. mxEvent.addListener(elt2, 'click', mxUtils.bind(this, function(evt)
  934. {
  935. this.actions.get('shapes').funct();
  936. mxEvent.consume(evt);
  937. }));
  938. div.appendChild(elt2);
  939. return div;
  940. };
  941. /**
  942. * Setting the current file.
  943. */
  944. /**
  945. * Defines the maximum size for images.
  946. */
  947. EditorUi.prototype.maxBackgroundSize = 1600;
  948. /**
  949. * Defines the maximum size for images.
  950. */
  951. EditorUi.prototype.maxImageSize = 520;
  952. /**
  953. * Images above 100K should be resampled.
  954. */
  955. EditorUi.prototype.resampleThreshold = 100000;
  956. /**
  957. * Maximum allowed size for images is 1 MB.
  958. */
  959. EditorUi.prototype.maxImageBytes = 1000000;
  960. /**
  961. * Maximum size for background images is 2.5 MB.
  962. */
  963. EditorUi.prototype.maxBackgroundBytes = 2500000;
  964. /**
  965. * Holds the current file.
  966. */
  967. EditorUi.prototype.currentFile = null;
  968. /**
  969. * Translates this point by the given vector.
  970. *
  971. * @param {number} dx X-coordinate of the translation.
  972. * @param {number} dy Y-coordinate of the translation.
  973. */
  974. EditorUi.prototype.setCurrentFile = function(file)
  975. {
  976. this.currentFile = file;
  977. };
  978. /**
  979. * Translates this point by the given vector.
  980. *
  981. * @param {number} dx X-coordinate of the translation.
  982. * @param {number} dy Y-coordinate of the translation.
  983. */
  984. EditorUi.prototype.getCurrentFile = function()
  985. {
  986. return this.currentFile;
  987. };
  988. /**
  989. * Translates this point by the given vector.
  990. *
  991. * @param {number} dx X-coordinate of the translation.
  992. * @param {number} dy Y-coordinate of the translation.
  993. */
  994. EditorUi.prototype.isDiagramEmpty = function()
  995. {
  996. var model = this.editor.graph.getModel();
  997. return model.getChildCount(model.root) == 1 && model.getChildCount(model.getChildAt(model.root, 0)) == 0;
  998. };
  999. /**
  1000. * Handling for canvas export.
  1001. */
  1002. /**
  1003. *
  1004. */
  1005. EditorUi.prototype.isExportToCanvas = function()
  1006. {
  1007. // LATER: Fix security error caused by foreignObjects in Safari for toDataUri (tainted canvas)
  1008. return mxClient.IS_CHROMEAPP || (!this.editor.graph.mathEnabled && this.useCanvasForExport);
  1009. };
  1010. /**
  1011. * Translates this point by the given vector.
  1012. *
  1013. * @param {number} dx X-coordinate of the translation.
  1014. * @param {number} dy Y-coordinate of the translation.
  1015. */
  1016. EditorUi.prototype.createSvgDataUri = function(svg)
  1017. {
  1018. return 'data:image/svg+xml;base64,' + btoa(unescape(encodeURIComponent(svg)));
  1019. };
  1020. /**
  1021. *
  1022. */
  1023. EditorUi.prototype.createPngDataUri = function(canvas, xml)
  1024. {
  1025. var data = canvas.toDataURL('image/png');
  1026. // Checks if output is invalid or empty
  1027. if (data.length <= 6 || data == canvas.cloneNode(false).toDataURL('image/png'))
  1028. {
  1029. throw {message: 'Invalid image'};
  1030. }
  1031. if (xml != null)
  1032. {
  1033. data = this.writeGraphModelToPng(data, 'zTXt', 'mxGraphModel', atob(this.editor.graph.compress(xml)));
  1034. }
  1035. return data;
  1036. };
  1037. /**
  1038. *
  1039. */
  1040. EditorUi.prototype.saveCanvas = function(canvas, xml)
  1041. {
  1042. var file = this.getCurrentFile();
  1043. var filename = (file != null && file.getTitle() != null) ? file.getTitle() : this.defaultFilename;
  1044. var dot = filename.lastIndexOf('.');
  1045. if (dot > 0)
  1046. {
  1047. filename = filename.substring(0, dot);
  1048. }
  1049. filename += '.png';
  1050. var data = this.createPngDataUri(canvas, xml);
  1051. this.saveLocalFile(data.substring(data.lastIndexOf(',') + 1), filename, 'image/png', true);
  1052. };
  1053. /**
  1054. *
  1055. */
  1056. EditorUi.prototype.showRemoteExportDialog = function(btnLabel, helpLink, callback)
  1057. {
  1058. var graph = this.editor.graph;
  1059. var content = document.createElement('div');
  1060. content.style.padding = '6px';
  1061. var cb2 = document.createElement('input');
  1062. cb2.style.marginRight = '8px';
  1063. cb2.setAttribute('type', 'checkbox');
  1064. if (graph.isSelectionEmpty())
  1065. {
  1066. cb2.setAttribute('disabled', 'disabled');
  1067. }
  1068. else
  1069. {
  1070. cb2.setAttribute('checked', 'checked');
  1071. cb2.defaultChecked = true;
  1072. }
  1073. content.appendChild(cb2);
  1074. mxUtils.write(content, mxResources.get('selectionOnly'));
  1075. mxUtils.br(content);
  1076. var cb = document.createElement('input');
  1077. cb.setAttribute('type', 'checkbox');
  1078. cb.setAttribute('checked', 'checked');
  1079. cb.defaultChecked = true;
  1080. cb.style.marginRight = '8px';
  1081. cb.style.marginTop = '16px';
  1082. content.appendChild(cb);
  1083. mxUtils.write(content, mxResources.get('includeCopyOfMyDiagram'));
  1084. var dlg = new CustomDialog(this, content, mxUtils.bind(this, function()
  1085. {
  1086. callback(!cb2.checked, cb.checked);
  1087. }), null, btnLabel, helpLink);
  1088. this.showDialog(dlg.container, 300, 120, true, true);
  1089. }
  1090. /**
  1091. *
  1092. */
  1093. EditorUi.prototype.showExportDialog = function(embedOption, btnLabel, helpLink, callback)
  1094. {
  1095. var graph = this.editor.graph;
  1096. var content = document.createElement('div');
  1097. content.style.paddingTop = '20px';
  1098. content.style.paddingRight = '8px';
  1099. var cb = document.createElement('input');
  1100. cb.style.marginRight = '8px';
  1101. cb.setAttribute('type', 'checkbox');
  1102. if (graph.background == mxConstants.NONE || graph.background == null)
  1103. {
  1104. cb.setAttribute('checked', 'checked');
  1105. cb.defaultChecked = true;
  1106. }
  1107. content.appendChild(cb);
  1108. mxUtils.write(content, mxResources.get('transparentBackground'));
  1109. mxUtils.br(content);
  1110. var cb2 = document.createElement('input');
  1111. cb2.style.marginTop = '16px';
  1112. cb2.style.marginRight = '8px';
  1113. cb2.setAttribute('type', 'checkbox');
  1114. if (graph.isSelectionEmpty())
  1115. {
  1116. cb2.setAttribute('disabled', 'disabled');
  1117. }
  1118. else
  1119. {
  1120. cb2.setAttribute('checked', 'checked');
  1121. cb2.defaultChecked = true;
  1122. }
  1123. content.appendChild(cb2);
  1124. mxUtils.write(content, mxResources.get('selectionOnly'));
  1125. mxUtils.br(content);
  1126. var cb3 = document.createElement('input');
  1127. cb3.style.marginTop = '16px';
  1128. cb3.style.marginRight = '8px';
  1129. cb3.setAttribute('type', 'checkbox');
  1130. content.appendChild(cb3);
  1131. mxUtils.write(content, mxResources.get('shadow'));
  1132. mxUtils.br(content);
  1133. if (graph.shadowVisible)
  1134. {
  1135. cb3.setAttribute('checked', 'checked');
  1136. cb3.defaultChecked = true;
  1137. }
  1138. var cb5 = document.createElement('input');
  1139. cb5.style.marginTop = '16px';
  1140. cb5.style.marginRight = '8px';
  1141. cb5.setAttribute('type', 'checkbox');
  1142. if (this.isOffline() || !this.canvasSupported)
  1143. {
  1144. cb5.setAttribute('disabled', 'disabled');
  1145. }
  1146. if (embedOption)
  1147. {
  1148. content.appendChild(cb5);
  1149. mxUtils.write(content, mxResources.get('embedImages'));
  1150. mxUtils.br(content);
  1151. }
  1152. var cb4 = document.createElement('input');
  1153. cb4.style.marginTop = '16px';
  1154. cb4.style.marginRight = '8px';
  1155. cb4.setAttribute('type', 'checkbox');
  1156. cb4.style.marginBottom = '8px';
  1157. cb4.setAttribute('checked', 'checked');
  1158. cb4.defaultChecked = true;
  1159. content.appendChild(cb4);
  1160. mxUtils.write(content, mxResources.get('includeCopyOfMyDiagram'));
  1161. var dlg = new FilenameDialog(this, 100, btnLabel, mxUtils.bind(this, function(newValue)
  1162. {
  1163. callback(newValue, cb.checked, !cb2.checked, cb3.checked, cb4.checked, cb5.checked);
  1164. }), mxResources.get('zoom') + ' (%)', null, content, (!this.isOffline()) ? helpLink : null);
  1165. this.showDialog(dlg.container, 320, (embedOption) ? 266 : 240, true, true);
  1166. dlg.init();
  1167. }
  1168. /**
  1169. *
  1170. */
  1171. EditorUi.prototype.uploadToGithub = function(file, base64Data, editable)
  1172. {
  1173. var resume = this.spinner.pause();
  1174. var content = document.createElement('div');
  1175. content.style.paddingTop = '20px';
  1176. content.style.paddingRight = '8px';
  1177. var table = document.createElement('table');
  1178. var tbody = document.createElement('tbody');
  1179. var tr = document.createElement('tr');
  1180. var td = document.createElement('td');
  1181. var uname = document.createElement('input');
  1182. uname.setAttribute('type', 'text');
  1183. mxUtils.write(td, 'Username:');
  1184. tr.appendChild(td);
  1185. td = td.cloneNode(false);
  1186. td.appendChild(uname);
  1187. tr.appendChild(td);
  1188. tbody.appendChild(tr);
  1189. td = td.cloneNode(false);
  1190. var pword = document.createElement('input');
  1191. pword.setAttribute('type', 'password');
  1192. mxUtils.write(td, 'Password:');
  1193. tr = tr.cloneNode(false);
  1194. tr.appendChild(td);
  1195. td = td.cloneNode(false);
  1196. td.appendChild(pword);
  1197. tr.appendChild(td);
  1198. tbody.appendChild(tr);
  1199. td = td.cloneNode(false);
  1200. var org = document.createElement('input');
  1201. org.setAttribute('type', 'text');
  1202. mxUtils.write(td, 'Organisation:');
  1203. tr = tr.cloneNode(false);
  1204. tr.appendChild(td);
  1205. td = td.cloneNode(false);
  1206. td.appendChild(org);
  1207. tr.appendChild(td);
  1208. tbody.appendChild(tr);
  1209. td = td.cloneNode(false);
  1210. var repo = document.createElement('input');
  1211. repo.setAttribute('type', 'text');
  1212. mxUtils.write(td, 'Repository:');
  1213. tr = tr.cloneNode(false);
  1214. tr.appendChild(td);
  1215. td = td.cloneNode(false);
  1216. td.appendChild(repo);
  1217. tr.appendChild(td);
  1218. tbody.appendChild(tr);
  1219. td = td.cloneNode(false);
  1220. var path = document.createElement('input');
  1221. path.setAttribute('type', 'text');
  1222. mxUtils.write(td, 'Path:');
  1223. tr = tr.cloneNode(false);
  1224. tr.appendChild(td);
  1225. td = td.cloneNode(false);
  1226. td.appendChild(path);
  1227. tr.appendChild(td);
  1228. tbody.appendChild(tr);
  1229. td = td.cloneNode(false);
  1230. var file = this.getCurrentFile();
  1231. var filename = (file != null && file.getTitle() != null) ? file.getTitle() : this.defaultFilename;
  1232. var dot = filename.lastIndexOf('.');
  1233. if (dot > 0)
  1234. {
  1235. filename = filename.substring(0, dot);
  1236. }
  1237. path.value = filename + '.png';
  1238. var ref = document.createElement('input');
  1239. ref.setAttribute('type', 'text');
  1240. mxUtils.write(td, 'Branch/Tag:');
  1241. ref.value = 'master';
  1242. tr = tr.cloneNode(false);
  1243. tr.appendChild(td);
  1244. td = td.cloneNode(false);
  1245. td.appendChild(ref);
  1246. tr.appendChild(td);
  1247. tbody.appendChild(tr);
  1248. td = td.cloneNode(false);
  1249. var msg = document.createElement('input');
  1250. msg.setAttribute('type', 'text');
  1251. mxUtils.write(td, 'Message:');
  1252. msg.value = 'Updated ' + filename + '.png';
  1253. tr = tr.cloneNode(false);
  1254. tr.appendChild(td);
  1255. td = td.cloneNode(false);
  1256. td.appendChild(msg);
  1257. tr.appendChild(td);
  1258. tbody.appendChild(tr);
  1259. td = td.cloneNode(false);
  1260. table.appendChild(tbody);
  1261. content.appendChild(table);
  1262. var dlg = new FilenameDialog(this, null, mxResources.get('publish'), mxUtils.bind(this, function()
  1263. {
  1264. var url = 'https://api.github.com/repos/' + org.value + '/' + repo.value +
  1265. '/contents/' + path.value + '?ref=' + encodeURIComponent(ref.value);
  1266. resume();
  1267. mxUtils.get(url, mxUtils.bind(this, function(req)
  1268. {
  1269. if (req.getStatus() == 200 || req.getStatus() == 404)
  1270. {
  1271. var obj = JSON.parse(req.getText());
  1272. var entity =
  1273. {
  1274. path: path.value,
  1275. message: msg.value,
  1276. content: base64Data
  1277. };
  1278. if (obj.sha != null)
  1279. {
  1280. entity.sha = obj.sha;
  1281. }
  1282. // Native PUT request
  1283. var req2 = new XMLHttpRequest();
  1284. req2.onreadystatechange = mxUtils.bind(this, function()
  1285. {
  1286. if (req2.readyState == 4)
  1287. {
  1288. if (req2.status >= 200 && req2.status < 300)
  1289. {
  1290. this.spinner.stop();
  1291. this.hideDialog();
  1292. url = 'https://github.com/' + org.value + '/' + repo.value + '/blob/' + ref.value + '/' + path.value;
  1293. var dlg = new ErrorDialog(this, mxResources.get('published'),
  1294. mxResources.get('publishedAt', ['<a href="' + url + '" target="_blank">' + url + '</a>']),
  1295. mxResources.get('close'), mxUtils.bind(this, function()
  1296. {
  1297. this.hideDialog();
  1298. }), null,
  1299. mxResources.get('openInNewWindow'), mxUtils.bind(this, function()
  1300. {
  1301. window.open(url);
  1302. }), false);
  1303. this.showDialog(dlg.container, 340, 170, true, false);
  1304. dlg.init();
  1305. }
  1306. else
  1307. {
  1308. resume = this.spinner.pause();
  1309. this.handleError(JSON.parse(req2.responseText));
  1310. }
  1311. }
  1312. });
  1313. req2.open('PUT', url, true);
  1314. req2.setRequestHeader('Authorization', 'Basic ' +
  1315. btoa(uname.value + ':' + pword.value));
  1316. req2.send(JSON.stringify(entity));
  1317. }
  1318. else
  1319. {
  1320. this.hideDialog();
  1321. this.spinner.stop();
  1322. this.handleError(JSON.parse(req.getText()));
  1323. }
  1324. }), mxUtils.bind(this, function(req)
  1325. {
  1326. this.hideDialog();
  1327. this.spinner.stop();
  1328. this.handleError({message: mxResources.get('unknownError')});
  1329. }));
  1330. }), null, null, content, null, false);
  1331. this.showDialog(dlg.container, 260, 260, true, false);
  1332. dlg.init();
  1333. };
  1334. /**
  1335. *
  1336. */
  1337. EditorUi.prototype.uploadToImgur = function(file, base64Data, editable)
  1338. {
  1339. var resume = this.spinner.pause();
  1340. // Shows a warning dialog before uploading
  1341. var dlg = new ErrorDialog(this, mxResources.get('warning'),
  1342. '<img style="max-width:300px;max-height:80px;margin-bottom:20px;padding:6px;border:1px solid gray;" ' +
  1343. 'src="data:image/png;base64,' + base64Data + '"/><br>' +
  1344. mxResources.get('publishConfirmation'),
  1345. mxResources.get('cancel'), mxUtils.bind(this, function()
  1346. {
  1347. // Do nothing
  1348. }), null,
  1349. mxResources.get('publish'), mxUtils.bind(this, function()
  1350. {
  1351. resume();
  1352. var title = (file.getTitle() != null) ? file.getTitle() : this.defaultFilename;
  1353. var dot = title.lastIndexOf('.');
  1354. var filename = title;
  1355. if (dot > 0)
  1356. {
  1357. title = filename.substring(0, dot);
  1358. filename = title;
  1359. }
  1360. filename += '.png';
  1361. // Indirection via servlet for billing and hiding secrets
  1362. var req = new mxXmlRequest('/imgur', JSON.stringify({type: 'base64', image: base64Data,
  1363. name: filename, title: title, description: 'Made with https://www.draw.io'}), 'POST');
  1364. var extractAndHandleError = mxUtils.bind(this, function(req)
  1365. {
  1366. var e = {message: mxResources.get('unknownError')};
  1367. try
  1368. {
  1369. var res = JSON.parse(req.getText());
  1370. e = {message: res.message || res.data.error};
  1371. }
  1372. catch (err)
  1373. {
  1374. // ignore
  1375. }
  1376. this.handleError(e);
  1377. });
  1378. // First request to upload image to Imgur
  1379. req.send(mxUtils.bind(this, function(req)
  1380. {
  1381. if (req.getStatus() == 200)
  1382. {
  1383. try
  1384. {
  1385. var res = JSON.parse(req.getText());
  1386. // Logs publishing of diagrams
  1387. try
  1388. {
  1389. var img = new Image();
  1390. // Timestamp is added to bypass client-side cache
  1391. img.src = 'images/log.png?published=' + res.data.id + '&v=' +
  1392. encodeURIComponent(EditorUi.VERSION) + '&ts=' + new Date().getTime();
  1393. }
  1394. catch (e)
  1395. {
  1396. // ignore
  1397. }
  1398. var showResult = mxUtils.bind(this, function()
  1399. {
  1400. this.spinner.stop();
  1401. var url = 'http://i.imgur.com/' + res.data.id + '.png';
  1402. var deleteUrl = 'https://www.draw.io/imgur?delete=' + res.data.deletehash;
  1403. var dlg = new ErrorDialog(this, mxResources.get('published'),
  1404. mxResources.get('publishedAt', ['<a href="' + url + '" target="_blank">' + url + '</a>']) +
  1405. '<br>' + mxResources.get('deleteUrl', [deleteUrl]),
  1406. mxResources.get('close'), mxUtils.bind(this, function()
  1407. {
  1408. this.hideDialog();
  1409. }), null,
  1410. mxResources.get('openInNewWindow'), mxUtils.bind(this, function()
  1411. {
  1412. window.open('http://imgur.com/' + res.data.id);
  1413. }), false);
  1414. this.showDialog(dlg.container, 340, 170, true, false);
  1415. dlg.init();
  1416. });
  1417. if (!editable)
  1418. {
  1419. showResult();
  1420. }
  1421. else
  1422. {
  1423. // Second request to update the description with the edit link
  1424. // Replacing the .png is workaround for Imgur to handle it as an image
  1425. // Avoiding URL parameter avoids call to getParameter in the servlet
  1426. var url2 = '/imgur?' + res.data.deletehash;
  1427. var req2 = new mxXmlRequest(url2, JSON.stringify({
  1428. title: title, description: 'Edit a copy of this diagram at https://www.draw.io/i/' + res.data.id}), 'POST');
  1429. req2.send(mxUtils.bind(this, function()
  1430. {
  1431. if (req2.getStatus() == 200)
  1432. {
  1433. showResult();
  1434. }
  1435. else
  1436. {
  1437. extractAndHandleError(req2);
  1438. }
  1439. }), mxUtils.bind(this, function()
  1440. {
  1441. extractAndHandleError(req2);
  1442. }));
  1443. }
  1444. }
  1445. catch (e)
  1446. {
  1447. this.handleError(e);
  1448. }
  1449. }
  1450. else
  1451. {
  1452. extractAndHandleError(req);
  1453. }
  1454. }), mxUtils.bind(this, function(req)
  1455. {
  1456. extractAndHandleError(req);
  1457. }));
  1458. }));
  1459. this.showDialog(dlg.container, 320, 250, true, false);
  1460. dlg.init();
  1461. };
  1462. /**
  1463. *
  1464. */
  1465. EditorUi.prototype.publishImage = function(handler)
  1466. {
  1467. var file = this.getCurrentFile();
  1468. if (file != null)
  1469. {
  1470. if (this.isExportToCanvas())
  1471. {
  1472. this.showExportDialog(false, mxResources.get('publish'), 'https://support.draw.io/pages/viewpage.action?pageId=12222625', mxUtils.bind(this, function(scale, transparentBackground, ignoreSelection, addShadow, editable)
  1473. {
  1474. var val = parseInt(scale);
  1475. if (!isNaN(val) && val > 0)
  1476. {
  1477. var scale = val / 100;
  1478. var selectionEmpty = this.editor.graph.isSelectionEmpty();
  1479. ignoreSelection = (ignoreSelection != null) ? ignoreSelection : selectionEmpty;
  1480. if (this.spinner.spin(document.body, mxResources.get('publishing')))
  1481. {
  1482. try
  1483. {
  1484. this.exportToCanvas(mxUtils.bind(this, function(canvas)
  1485. {
  1486. try
  1487. {
  1488. var xml = (editable) ? mxUtils.getXml(this.editor.getGraphXml(ignoreSelection)) : null;
  1489. var data = this.createPngDataUri(canvas, xml);
  1490. handler(file, data.substring(data.lastIndexOf(',') + 1), editable);
  1491. }
  1492. catch (e)
  1493. {
  1494. this.handleError(e);
  1495. }
  1496. }), null, null, null, mxUtils.bind(this, function(e)
  1497. {
  1498. this.handleError(e);
  1499. }), null, ignoreSelection, scale || 1, transparentBackground, addShadow);
  1500. }
  1501. catch (e)
  1502. {
  1503. this.handleError(e);
  1504. }
  1505. }
  1506. }
  1507. }));
  1508. }
  1509. else
  1510. {
  1511. this.showRemoteExportDialog(mxResources.get('publish'), 'https://support.draw.io/pages/viewpage.action?pageId=12222625', mxUtils.bind(this, function(ignoreSelection, editable)
  1512. {
  1513. if (this.spinner.spin(document.body, mxResources.get('publishing')))
  1514. {
  1515. var bounds = this.editor.graph.getGraphBounds();
  1516. var data = this.getFileData(true, null, null, null, ignoreSelection);
  1517. if (bounds.width * bounds.height <= MAX_AREA && data.length <= MAX_REQUEST_SIZE)
  1518. {
  1519. var embed = (editable) ? '1' : '0';
  1520. try
  1521. {
  1522. var req = new mxXmlRequest(EXPORT_URL, 'format=png' +
  1523. '&base64=1&embedXml=' + embed + '&xml=' +
  1524. encodeURIComponent(data));
  1525. req.send(mxUtils.bind(this, function()
  1526. {
  1527. if (req.getStatus() == 200)
  1528. {
  1529. handler(file, req.getText(), editable);
  1530. }
  1531. else
  1532. {
  1533. this.handleError(req);
  1534. }
  1535. }));
  1536. }
  1537. catch (e)
  1538. {
  1539. this.handleError(e);
  1540. }
  1541. }
  1542. else
  1543. {
  1544. this.handleError({message: mxResources.get('drawingTooLarge')}, mxResources.get('error'));
  1545. }
  1546. }
  1547. }));
  1548. }
  1549. }
  1550. };
  1551. /**
  1552. * Translates this point by the given vector.
  1553. *
  1554. * @param {number} dx X-coordinate of the translation.
  1555. * @param {number} dy Y-coordinate of the translation.
  1556. */
  1557. EditorUi.prototype.timeSince = function(date)
  1558. {
  1559. var seconds = Math.floor((new Date() - date) / 1000);
  1560. var interval = Math.floor(seconds / 31536000);
  1561. if (interval > 1)
  1562. {
  1563. return interval + ' ' + mxResources.get('years');
  1564. }
  1565. interval = Math.floor(seconds / 2592000);
  1566. if (interval > 1)
  1567. {
  1568. return interval + ' ' + mxResources.get('months');
  1569. }
  1570. interval = Math.floor(seconds / 86400);
  1571. if (interval > 1)
  1572. {
  1573. return interval + ' ' + mxResources.get('days');
  1574. }
  1575. interval = Math.floor(seconds / 3600);
  1576. if (interval > 1)
  1577. {
  1578. return interval + ' ' + mxResources.get('hours');
  1579. }
  1580. interval = Math.floor(seconds / 60);
  1581. if (interval > 1)
  1582. {
  1583. return interval + ' ' + mxResources.get('minutes');
  1584. }
  1585. if (interval == 1)
  1586. {
  1587. return interval + ' ' + mxResources.get('minute');
  1588. }
  1589. return null;
  1590. };
  1591. /**
  1592. * Converts math in the given SVG
  1593. */
  1594. EditorUi.prototype.convertMath = function(graph, svgRoot, fixPosition, callback)
  1595. {
  1596. // FIXME: Only horizontal dash in output so better no conversion at all
  1597. if (false && graph.mathEnabled && typeof(MathJax) !== 'undefined' && typeof(MathJax.Hub) !== 'undefined')
  1598. {
  1599. // Workaround for lost gradients in Chrome after remove from DOM
  1600. var elts = svgRoot.getElementsByTagName('*');
  1601. for (var i = 0; i < elts.length; i++)
  1602. {
  1603. if (elts[i].getAttribute('id') != null)
  1604. {
  1605. elts[i].setAttribute('id', 'mxTemporaryPrefix-' + elts[i].getAttribute('id'));
  1606. }
  1607. }
  1608. // Temporarily attaches to DOM for rendering
  1609. svgRoot.style.visibility = 'hidden';
  1610. document.body.appendChild(svgRoot);
  1611. Editor.MathJaxRender(svgRoot);
  1612. MathJax.Hub.Queue(mxUtils.bind(this, function ()
  1613. {
  1614. // Removes from DOM
  1615. svgRoot.parentNode.removeChild(svgRoot);
  1616. svgRoot.style.visibility = '';
  1617. // Restores original IDs
  1618. for (var i = 0; i < elts.length; i++)
  1619. {
  1620. if (elts[i].getAttribute('id') != null)
  1621. {
  1622. elts[i].setAttribute('id', elts[i].getAttribute('id').substring('mxTemporaryPrefix-'.length));
  1623. }
  1624. }
  1625. // Keeping scale but moving translate only works for image export which
  1626. // is fine since we do not want the SVG export to contain a workaround.
  1627. // See https://github.com/mathjax/MathJax/issues/279
  1628. if (fixPosition && navigator.userAgent.indexOf('AppleWebKit/') >= 0)
  1629. {
  1630. var fo = svgRoot.getElementsByTagName('foreignObject');
  1631. for (var i = 0; i < fo.length; i++)
  1632. {
  1633. var tr = fo[i].parentNode.parentNode.getAttribute('transform');
  1634. var translate = /translate\(\s*([^\s,)]+)[ ,]([^\s,)]+)/.exec(tr);
  1635. fo[i].setAttribute('x', Math.round(translate[1]));
  1636. fo[i].setAttribute('y', Math.round(translate[2]));
  1637. // Must use translate for crisp rendering
  1638. fo[i].parentNode.parentNode.setAttribute('transform', 'translate(0.5,0.5)' + tr.substring(tr.indexOf(')') + 1));
  1639. }
  1640. }
  1641. callback();
  1642. }));
  1643. }
  1644. else
  1645. {
  1646. callback();
  1647. }
  1648. };
  1649. /**
  1650. * Returns the SVG of the diagram with embedded XML. If a callback function is
  1651. * used, the images are converted to data URIs.
  1652. */
  1653. EditorUi.prototype.getEmbeddedSvg = function(xml, graph, url, noHeader, callback, ignoreSelection, redirect)
  1654. {
  1655. var bg = null;
  1656. if (graph != null)
  1657. {
  1658. bg = graph.background;
  1659. if (bg == mxConstants.NONE)
  1660. {
  1661. bg = null;
  1662. }
  1663. }
  1664. // Sets or disables alternate text for foreignObjects. Disabling is needed
  1665. // because PhantomJS seems to ignore switch statements and paint all text.
  1666. var svgRoot = this.editor.graph.getSvg(bg, null, null, null, null, ignoreSelection);
  1667. svgRoot.setAttribute('content', encodeURIComponent(xml));
  1668. if (url != null)
  1669. {
  1670. svgRoot.setAttribute('resource', url);
  1671. }
  1672. // LATER: Click on SVG content to start editing
  1673. // if (redirect != null)
  1674. // {
  1675. // // TODO: Ignore anchor tag source for click event
  1676. // svgRoot.setAttribute('style', 'cursor:pointer;');
  1677. // svgRoot.setAttribute('onclick', 'window.location.href=\'' + redirect + '\';');
  1678. // }
  1679. if (callback != null)
  1680. {
  1681. this.convertImages(svgRoot, mxUtils.bind(this, function(svgRoot)
  1682. {
  1683. callback(((!noHeader) ? '<?xml version="1.0" encoding="UTF-8"?>\n' +
  1684. '<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">\n' : '') +
  1685. mxUtils.getXml(svgRoot));
  1686. }));
  1687. }
  1688. else
  1689. {
  1690. return ((!noHeader) ? '<?xml version="1.0" encoding="UTF-8"?>\n' +
  1691. '<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">\n' : '') +
  1692. mxUtils.getXml(svgRoot);
  1693. }
  1694. };
  1695. /**
  1696. *
  1697. */
  1698. EditorUi.prototype.exportToCanvas = function(callback, width, imageCache, background, error, limitHeight,
  1699. ignoreSelection, scale, transparentBackground, addShadow, converter)
  1700. {
  1701. limitHeight = (limitHeight != null) ? limitHeight : true;
  1702. ignoreSelection = (ignoreSelection != null) ? ignoreSelection : true;
  1703. var bg = (transparentBackground) ? null : this.editor.graph.background;
  1704. if (bg == mxConstants.NONE)
  1705. {
  1706. bg = null;
  1707. }
  1708. if (bg == null)
  1709. {
  1710. bg = background;
  1711. }
  1712. // Handles special case where background is null but transparent is false
  1713. if (bg == null && transparentBackground == false)
  1714. {
  1715. bg = '#ffffff';
  1716. }
  1717. this.convertImages(this.editor.graph.getSvg(bg, null, null, null, null, ignoreSelection), mxUtils.bind(this, function(svgRoot)
  1718. {
  1719. var img = new Image();
  1720. img.onload = mxUtils.bind(this, function()
  1721. {
  1722. var canvas = document.createElement('canvas');
  1723. var w = parseInt(svgRoot.getAttribute('width'));
  1724. var h = parseInt(svgRoot.getAttribute('height'));
  1725. scale = (scale != null) ? scale : 1;
  1726. if (width != null)
  1727. {
  1728. scale = (!limitHeight) ? width / w : Math.min(1, Math.min((width * 3) / (h * 4), width / w));
  1729. }
  1730. canvas.setAttribute('width', Math.ceil(scale * w));
  1731. canvas.setAttribute('height', Math.ceil(scale * h));
  1732. var ctx = canvas.getContext('2d');
  1733. ctx.scale(scale, scale);
  1734. ctx.drawImage(img, 0, 0);
  1735. callback(canvas);
  1736. });
  1737. img.onerror = function(e)
  1738. {
  1739. //console.log('img', e, img.src);
  1740. if (error != null)
  1741. {
  1742. error(e);
  1743. }
  1744. };
  1745. try
  1746. {
  1747. if (addShadow)
  1748. {
  1749. this.editor.addSvgShadow(svgRoot);
  1750. }
  1751. this.convertMath(this.editor.graph, svgRoot, true, mxUtils.bind(this, function()
  1752. {
  1753. img.src = this.createSvgDataUri(mxUtils.getXml(svgRoot));
  1754. }));
  1755. }
  1756. catch (e)
  1757. {
  1758. //console.log('src', e, img.src);
  1759. if (error != null)
  1760. {
  1761. error(e);
  1762. }
  1763. }
  1764. }), imageCache, converter);
  1765. };
  1766. /**
  1767. * Converts all images in the SVG output to data URIs for immediate rendering
  1768. */
  1769. EditorUi.prototype.createImageUrlConverter = function()
  1770. {
  1771. var converter = new mxUrlConverter();
  1772. converter.updateBaseUrl();
  1773. // Extends convert to avoid CORS using an image proxy server
  1774. // LATER: Use img.crossOrigin="anonymous" to avoid proxy
  1775. var convert = converter.convert;
  1776. converter.convert = function(src)
  1777. {
  1778. if (src != null)
  1779. {
  1780. if ((src.substring(0, 7) == 'http://' || src.substring(0, 8) == 'https://') &&
  1781. src.substring(0, converter.baseUrl.length) != converter.baseUrl)
  1782. {
  1783. src = PROXY_URL + '?url=' + encodeURIComponent(src);
  1784. }
  1785. else if (src.substring(0, 19) != 'chrome-extension://')
  1786. {
  1787. src = convert.apply(this, arguments);
  1788. }
  1789. }
  1790. return src;
  1791. };
  1792. return converter;
  1793. };
  1794. /**
  1795. * Converts all images in the SVG output to data URIs for immediate rendering
  1796. */
  1797. EditorUi.prototype.convertImages = function(svgRoot, callback, imageCache, converter)
  1798. {
  1799. // Converts images to data URLs for immediate painting
  1800. if (converter == null)
  1801. {
  1802. converter = this.createImageUrlConverter();
  1803. }
  1804. // Barrier for asynchronous image loading
  1805. var counter = 0;
  1806. function inc()
  1807. {
  1808. counter++;
  1809. };
  1810. function dec()
  1811. {
  1812. counter--;
  1813. if (counter == 0)
  1814. {
  1815. callback(svgRoot);
  1816. }
  1817. };
  1818. var cache = imageCache || new Object();
  1819. var convertImages = mxUtils.bind(this, function(tagName, srcAttr)
  1820. {
  1821. var images = svgRoot.getElementsByTagName(tagName);
  1822. for (var i = 0; i < images.length; i++)
  1823. {
  1824. (mxUtils.bind(this, function(img)
  1825. {
  1826. var src = converter.convert(img.getAttribute(srcAttr));
  1827. // Data URIs are pass-through
  1828. if (src != null && src.substring(0, 5) != 'data:')
  1829. {
  1830. var tmp = cache[src];
  1831. if (tmp == null)
  1832. {
  1833. inc();
  1834. this.convertImageToDataUri(src, function(uri)
  1835. {
  1836. if (uri != null)
  1837. {
  1838. cache[src] = uri;
  1839. img.setAttribute(srcAttr, uri);
  1840. }
  1841. dec();
  1842. });
  1843. }
  1844. else
  1845. {
  1846. img.setAttribute(srcAttr, tmp);
  1847. }
  1848. }
  1849. }))(images[i]);
  1850. }
  1851. });
  1852. // Converts all known image tags in output
  1853. // LATER: Add support for images in CSS
  1854. convertImages('image', 'xlink:href');
  1855. convertImages('img', 'src');
  1856. // All from cache or no images
  1857. if (counter == 0)
  1858. {
  1859. callback(svgRoot);
  1860. }
  1861. };
  1862. /**
  1863. * Translates this point by the given vector.
  1864. *
  1865. * @param {number} dx X-coordinate of the translation.
  1866. * @param {number} dy Y-coordinate of the translation.
  1867. */
  1868. EditorUi.prototype.convertImageToDataUri = function(url, callback)
  1869. {
  1870. if (/(\.svg)$/i.test(url))
  1871. {
  1872. mxUtils.get(url, mxUtils.bind(this, function(req)
  1873. {
  1874. callback(this.createSvgDataUri(req.getText()));
  1875. }),
  1876. function()
  1877. {
  1878. callback();
  1879. });
  1880. }
  1881. else
  1882. {
  1883. var img = new Image();
  1884. img.onload = function()
  1885. {
  1886. var canvas = document.createElement('canvas');
  1887. var ctx = canvas.getContext('2d');
  1888. canvas.height = img.height;
  1889. canvas.width = img.width;
  1890. ctx.drawImage(img, 0, 0);
  1891. callback(canvas.toDataURL());
  1892. };
  1893. img.onerror = function()
  1894. {
  1895. callback();
  1896. };
  1897. img.src = url;
  1898. }
  1899. };
  1900. /**
  1901. * Handling drag and drop and import.
  1902. */
  1903. /**
  1904. * Imports the given XML into the existing diagram.
  1905. */
  1906. EditorUi.prototype.importXml = function(xml, dx, dy, crop, noErrorHandling)
  1907. {
  1908. dx = (dx != null) ? dx : 0;
  1909. dy = (dy != null) ? dy : 0;
  1910. var cells = []
  1911. try
  1912. {
  1913. if (xml != null && xml.length > 0)
  1914. {
  1915. var doc = mxUtils.parseXml(xml);
  1916. var node = this.editor.extractGraphModel(doc.documentElement);
  1917. if (node != null)
  1918. {
  1919. var model = new mxGraphModel();
  1920. var codec = new mxCodec(node.ownerDocument);
  1921. codec.decode(node, model);
  1922. var graph = this.editor.graph;
  1923. var childCount = model.getChildCount(model.getRoot());
  1924. var targetChildCount = graph.model.getChildCount(graph.model.getRoot());
  1925. // Merges into active layer if one layer is pasted
  1926. graph.model.beginUpdate();
  1927. try
  1928. {
  1929. // Mapping for multiple calls to cloneCells with the same set of cells
  1930. var mapping = new Object();
  1931. for (var i = 0; i < childCount; i++)
  1932. {
  1933. var parent = model.getChildAt(model.getRoot(), i);
  1934. // Adds cells to existing layer if not locked
  1935. if (childCount == 1 && !graph.isCellLocked(graph.getDefaultParent()))
  1936. {
  1937. var children = model.getChildren(parent);
  1938. cells = cells.concat(graph.importCells(children, dx, dy, graph.getDefaultParent(), null, mapping));
  1939. }
  1940. else
  1941. {
  1942. // Delta is non cascading, needs separate move for layers
  1943. parent = graph.importCells([parent], 0, 0, graph.model.getRoot(), null, mapping)[0];
  1944. var children = graph.model.getChildren(parent);
  1945. graph.moveCells(children, dx, dy);
  1946. cells = cells.concat(children);
  1947. }
  1948. }
  1949. if (crop)
  1950. {
  1951. if (graph.isGridEnabled())
  1952. {
  1953. dx = graph.snap(dx);
  1954. dy = graph.snap(dy);
  1955. }
  1956. var bounds = graph.getBoundingBoxFromGeometry(cells, true);
  1957. graph.moveCells(cells, dx - bounds.x, dy - bounds.y);
  1958. }
  1959. }
  1960. finally
  1961. {
  1962. graph.model.endUpdate();
  1963. }
  1964. }
  1965. }
  1966. }
  1967. catch (e)
  1968. {
  1969. if (!noErrorHandling)
  1970. {
  1971. this.handleError(e, mxResources.get('invalidOrMissingFile'));
  1972. }
  1973. throw e;
  1974. }
  1975. return cells;
  1976. };
  1977. /**
  1978. * Imports the given XML into the existing diagram.
  1979. * TODO: Make this function asynchronous
  1980. */
  1981. EditorUi.prototype.insertTextAt = function(text, dx, dy, html, asImage, crop)
  1982. {
  1983. crop = (crop != null) ? crop : true;
  1984. // Handles special case for Gliffy data which requires async server-side for parsing
  1985. if (Graph.fileSupport && !this.isOffline() && new XMLHttpRequest().upload && this.isRemoteFileFormat(text))
  1986. {
  1987. // Fixes possible parsing problems with ASCII 160 (non-breaking space)
  1988. this.parseFile(new Blob([text.replace(/\s+/g,' ')], {type: 'application/octet-stream'}), mxUtils.bind(this, function(xhr)
  1989. {
  1990. if (xhr.readyState == 4 && xhr.status == 200)
  1991. {
  1992. this.editor.graph.setSelectionCells(this.insertTextAt(xhr.responseText, dx, dy, true));
  1993. }
  1994. }));
  1995. // Returns empty cells array as it is aysynchronous
  1996. return [];
  1997. }
  1998. // Handles special case of data URI which requires async loading for finding size
  1999. else if (!this.isOffline() && (asImage || text.substring(0, 5) == 'data:' || (/\.(gif|jpg|jpeg|tiff|png|svg)$/i).test(text)))
  2000. {
  2001. var graph = this.editor.graph;
  2002. // Checks for embedded XML in PNG
  2003. if (text.substring(0, 22) == 'data:image/png;base64,')
  2004. {
  2005. var xml = this.extractGraphModelFromPng(text);
  2006. var result = this.importXml(xml, dx, dy, crop, true);
  2007. if (result.length > 0)
  2008. {
  2009. return result;
  2010. }
  2011. }
  2012. // Tries to extract embedded XML from SVG data URI
  2013. if (text.substring(0, 19) == 'data:image/svg+xml;')
  2014. {
  2015. try
  2016. {
  2017. var xml = null;
  2018. if (text.substring(0, 26) == 'data:image/svg+xml;base64,')
  2019. {
  2020. xml = text.substring(text.indexOf(',') + 1);
  2021. xml = (window.atob && !mxClient.IS_SF) ? atob(xml) : Base64.decode(xml, true);
  2022. }
  2023. else
  2024. {
  2025. xml = decodeURIComponent(text.substring(text.indexOf(',') + 1));
  2026. }
  2027. var result = this.importXml(xml, dx, dy, crop, true);
  2028. if (result.length > 0)
  2029. {
  2030. return result;
  2031. }
  2032. }
  2033. catch (e)
  2034. {
  2035. // Ignore
  2036. }
  2037. }
  2038. this.loadImage(text, mxUtils.bind(this, function(img)
  2039. {
  2040. if (text.substring(0, 5) == 'data:')
  2041. {
  2042. this.resizeImage(img, text, mxUtils.bind(this, function(data2, w2, h2)
  2043. {
  2044. graph.setSelectionCell(graph.insertVertex(null, null, '', graph.snap(dx), graph.snap(dy),
  2045. w2, h2, 'shape=image;verticalLabelPosition=bottom;labelBackgroundColor=#ffffff;' +
  2046. 'verticalAlign=top;aspect=fixed;image=' + this.convertDataUri(data2) + ';'));
  2047. }), true, this.maxImageSize);
  2048. }
  2049. else
  2050. {
  2051. var s = Math.min(1, Math.min(this.maxImageSize / img.width, this.maxImageSize / img.height));
  2052. var w = Math.round(img.width * s);
  2053. var h = Math.round(img.height * s);
  2054. graph.setSelectionCell(graph.insertVertex(null, null, '', graph.snap(dx), graph.snap(dy),
  2055. w, h, 'shape=image;verticalLabelPosition=bottom;labelBackgroundColor=#ffffff;' +
  2056. 'verticalAlign=top;aspect=fixed;image=' + text + ';'));
  2057. }
  2058. }), mxUtils.bind(this, function()
  2059. {
  2060. var cell = null;
  2061. // Inserts invalid data URIs as text
  2062. graph.getModel().beginUpdate();
  2063. try
  2064. {
  2065. cell = graph.insertVertex(graph.getDefaultParent(), null, text,
  2066. graph.snap(dx), graph.snap(dy), 1, 1, 'text;' + ((html) ? 'html=1;' : ''));
  2067. graph.updateCellSize(cell);
  2068. graph.fireEvent(new mxEventObject('textInserted', 'cells', [cell]));
  2069. }
  2070. finally
  2071. {
  2072. graph.getModel().endUpdate();
  2073. }
  2074. graph.setSelectionCell(cell);
  2075. }));
  2076. return [];
  2077. }
  2078. else
  2079. {
  2080. text = this.editor.graph.zapGremlins(mxUtils.trim(text));
  2081. if (this.isCompatibleString(text))
  2082. {
  2083. return this.importXml(text, dx, dy, crop);
  2084. }
  2085. else if (text.length > 0)
  2086. {
  2087. var graph = this.editor.graph;
  2088. var cell = null;
  2089. graph.getModel().beginUpdate();
  2090. try
  2091. {
  2092. // Fires cellsInserted to apply the current style to the inserted text.
  2093. // This requires the value to be empty when the event is fired.
  2094. cell = graph.insertVertex(graph.getDefaultParent(), null, '',
  2095. graph.snap(dx), graph.snap(dy), 1, 1, 'text;' + ((html) ? 'html=1;' : ''));
  2096. graph.fireEvent(new mxEventObject('textInserted', 'cells', [cell]));
  2097. // Apply value and updates the cell size to fit the text block
  2098. cell.value = text;
  2099. graph.updateCellSize(cell);
  2100. // See http://stackoverflow.com/questions/6927719/url-regex-does-not-work-in-javascript
  2101. var regexp = /\b((?:[a-z][\w-]+:(?:\/{1,3}|[a-z0-9%])|www\d{0,3}[.]|[a-z0-9.\-]+[.][a-z]{2,4}\/)(?:[^\s()<>]+|\(([^\s()<>]+|(\([^\s()<>]+\)))*\))+(?:\(([^\s()<>]+|(\([^\s()<>]+\)))*\)|[^\s`!()\[\]{};:'".,<>?«»“”‘’]))/i;
  2102. if (regexp.test(cell.value))
  2103. {
  2104. graph.setLinkForCell(cell, cell.value);
  2105. }
  2106. // Adds spacing
  2107. cell.geometry.width += graph.gridSize;
  2108. cell.geometry.height += graph.gridSize;
  2109. }
  2110. finally
  2111. {
  2112. graph.getModel().endUpdate();
  2113. }
  2114. return [cell];
  2115. }
  2116. }
  2117. };
  2118. /**
  2119. * Formats the given file size.
  2120. */
  2121. EditorUi.prototype.formatFileSize = function(size)
  2122. {
  2123. var units = [' kB', ' MB', ' GB', ' TB', 'PB', 'EB', 'ZB', 'YB'];
  2124. var i = -1;
  2125. do
  2126. {
  2127. size = size / 1024;
  2128. i++;
  2129. } while (size > 1024);
  2130. return Math.max(size, 0.1).toFixed(1) + units[i];
  2131. };
  2132. /**
  2133. * Imports the given XML into the existing diagram.
  2134. */
  2135. EditorUi.prototype.convertDataUri = function(uri)
  2136. {
  2137. // Handles special case of data URI which needs to be rewritten
  2138. // to be used in a cell style to remove the semicolon
  2139. if (uri.substring(0, 5) == 'data:')
  2140. {
  2141. var semi = uri.indexOf(';');
  2142. if (semi > 0)
  2143. {
  2144. uri = uri.substring(0, semi) + uri.substring(uri.indexOf(',', semi + 1));
  2145. }
  2146. }
  2147. return uri;
  2148. };
  2149. /**
  2150. * Returns true for Gliffy or GraphML data or .vsdx filenames.
  2151. */
  2152. EditorUi.prototype.isRemoteFileFormat = function(data, filename)
  2153. {
  2154. return /(\.*<graphml xmlns=\".*)/.test(data) ||
  2155. /(\"contentType\":\s*\"application\/gliffy\+json\")/.test(data) ||
  2156. (filename != null && /(\.vsdx)($|\?)/i.test(filename));
  2157. };
  2158. /**
  2159. * Imports the given XML into the existing diagram.
  2160. */
  2161. EditorUi.prototype.importFile = function(data, mimeType, dx, dy, w, h, filename, done, file, crop)
  2162. {
  2163. crop = (crop != null) ? crop : true;
  2164. var async = false;
  2165. var cells = null;
  2166. if (mimeType.substring(0, 5) == 'image')
  2167. {
  2168. var containsModel = false;
  2169. if (mimeType.substring(0, 9) == 'image/png')
  2170. {
  2171. var xml = this.extractGraphModelFromPng(data);
  2172. if (xml != null && xml.length > 0)
  2173. {
  2174. cells = this.importXml(xml, dx, dy, crop);
  2175. containsModel = true;
  2176. }
  2177. }
  2178. if (!containsModel)
  2179. {
  2180. var graph = this.editor.graph;
  2181. // Strips encoding bit (eg. ;base64,) for cell style
  2182. var semi = data.indexOf(';');
  2183. if (semi > 0)
  2184. {
  2185. data = data.substring(0, semi) + data.substring(data.indexOf(',', semi + 1));
  2186. }
  2187. if (crop && graph.isGridEnabled())
  2188. {
  2189. dx = graph.snap(dx);
  2190. dy = graph.snap(dy);
  2191. }
  2192. cells = [graph.insertVertex(null, null, '', dx, dy, w, h,
  2193. 'shape=image;verticalLabelPosition=bottom;labelBackgroundColor=#ffffff;' +
  2194. 'verticalAlign=top;aspect=fixed;image=' + data + ';')];
  2195. }
  2196. }
  2197. else if (!this.isOffline() && new XMLHttpRequest().upload && this.isRemoteFileFormat(data, filename))
  2198. {
  2199. // LATER: done and async are a hack before making this asynchronous
  2200. async = true;
  2201. // Returns empty cells array as it is aysynchronous
  2202. this.parseFile((file != null) ? file : new Blob([data], {type: 'application/octet-stream'}), mxUtils.bind(this, function(xhr)
  2203. {
  2204. if (xhr.readyState == 4)
  2205. {
  2206. var importedCells = null;
  2207. if (xhr.status == 200)
  2208. {
  2209. importedCells = this.importXml(xhr.responseText, dx, dy, crop);
  2210. }
  2211. if (done != null)
  2212. {
  2213. done(importedCells);
  2214. }
  2215. }
  2216. }), filename);
  2217. }
  2218. else
  2219. {
  2220. if (/(\.vsdx)($|\?)/i.test(filename))
  2221. {
  2222. var vsdxModel = new mxVsdxModel();
  2223. vsdxModel.decode(file);
  2224. }
  2225. else
  2226. {
  2227. // Workaround for malformed xhtml meta element bug 07.08.16. The trailing slash was missing causing
  2228. // reopen to fail trying to parse. Same fix in setFileData() for open case.
  2229. if (data != null && data.length > 0)
  2230. {
  2231. var index = data.indexOf('<meta charset="utf-8">');
  2232. if (index >= 0)
  2233. {
  2234. var replaceString = '<meta charset="utf-8"/>';
  2235. var replaceStrLen = replaceString.length;
  2236. data = data.slice(0, index) + replaceString + data.slice(index + replaceStrLen - 1, data.length);
  2237. }
  2238. }
  2239. cells = this.insertTextAt(data, dx, dy, true);
  2240. }
  2241. // else if (String.prototype.trim)
  2242. // {
  2243. // var trimmed = data.trim();
  2244. //
  2245. // if (trimmed.substring(0, 6) == 'strict ' || trimmed.substring(0, 5) == 'graph' || trimmed.substring(0, 7) == 'digraph')
  2246. // {
  2247. // // GraphViz dot format http://www.graphviz.org/content/dot-language
  2248. // //var digraph = graphlibDot.read("digraph { 1; 2; 1 -> 2 [label=\"label\"] }");
  2249. // }
  2250. // }
  2251. }
  2252. if (!async && done != null)
  2253. {
  2254. done(cells);
  2255. }
  2256. return cells;
  2257. };
  2258. /**
  2259. * Base64 encodes the given string. This method seems to be more
  2260. * robust for encoding PNG from binary AJAX responses.
  2261. */
  2262. EditorUi.prototype.base64Encode = function(str)
  2263. {
  2264. var CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
  2265. var out = "", i = 0, len = str.length, c1, c2, c3;
  2266. while (i < len)
  2267. {
  2268. c1 = str.charCodeAt(i++) & 0xff;
  2269. if (i == len)
  2270. {
  2271. out += CHARS.charAt(c1 >> 2);
  2272. out += CHARS.charAt((c1 & 0x3) << 4);
  2273. out += "==";
  2274. break;
  2275. }
  2276. c2 = str.charCodeAt(i++);
  2277. if (i == len)
  2278. {
  2279. out += CHARS.charAt(c1 >> 2);
  2280. out += CHARS.charAt(((c1 & 0x3)<< 4) | ((c2 & 0xF0) >> 4));
  2281. out += CHARS.charAt((c2 & 0xF) << 2);
  2282. out += "=";
  2283. break;
  2284. }
  2285. c3 = str.charCodeAt(i++);
  2286. out += CHARS.charAt(c1 >> 2);
  2287. out += CHARS.charAt(((c1 & 0x3) << 4) | ((c2 & 0xF0) >> 4));
  2288. out += CHARS.charAt(((c2 & 0xF) << 2) | ((c3 & 0xC0) >> 6));
  2289. out += CHARS.charAt(c3 & 0x3F);
  2290. }
  2291. return out;
  2292. };
  2293. /**
  2294. *
  2295. */
  2296. EditorUi.prototype.importFiles = function(files, x, y, maxSize, fn, resultFn, filterFn, barrierFn, resizeImages, maxBytes, resampleThreshold)
  2297. {
  2298. var crop = x != null && y != null;
  2299. x = (x != null) ? x : 0;
  2300. y = (y != null) ? y : 0;
  2301. maxSize = (maxSize != null) ? maxSize : this.maxImageSize;
  2302. maxBytes = (maxBytes != null) ? maxBytes : this.maxImageBytes;
  2303. resizeImages = (resizeImages != null) ? resizeImages : true;
  2304. var graph = this.editor.graph;
  2305. var gs = graph.gridSize;
  2306. fn = (fn != null) ? fn : mxUtils.bind(this, function(data, mimeType, x, y, w, h, filename, done, file)
  2307. {
  2308. if (data != null && data.substring(0, 10) == '<mxlibrary')
  2309. {
  2310. this.spinner.stop();
  2311. this.loadLibrary(new LocalLibrary(this, data, filename));
  2312. return null;
  2313. }
  2314. else
  2315. {
  2316. return this.importFile(data, mimeType, x, y, w, h, filename, done, file, crop);
  2317. }
  2318. });
  2319. resultFn = (resultFn != null) ? resultFn : mxUtils.bind(this, function(cells)
  2320. {
  2321. graph.setSelectionCells(cells);
  2322. });
  2323. if (this.spinner.spin(document.body, mxResources.get('loading')))
  2324. {
  2325. var count = files.length;
  2326. var remain = count;
  2327. var queue = [];
  2328. // Barrier waits for all files to be loaded asynchronously
  2329. var barrier = mxUtils.bind(this, function(index, fnc)
  2330. {
  2331. queue[index] = fnc;
  2332. if (--remain == 0)
  2333. {
  2334. this.spinner.stop();
  2335. if (barrierFn != null)
  2336. {
  2337. barrierFn(queue);
  2338. }
  2339. else
  2340. {
  2341. var cells = [];
  2342. graph.getModel().beginUpdate();
  2343. try
  2344. {
  2345. for (var j = 0; j < queue.length; j++)
  2346. {
  2347. var tmp = queue[j]();
  2348. if (tmp != null)
  2349. {
  2350. cells = cells.concat(tmp);
  2351. }
  2352. }
  2353. }
  2354. finally
  2355. {
  2356. graph.getModel().endUpdate();
  2357. }
  2358. }
  2359. resultFn(cells);
  2360. }
  2361. });
  2362. for (var i = 0; i < count; i++)
  2363. {
  2364. (mxUtils.bind(this, function(index)
  2365. {
  2366. var file = files[index];
  2367. var reader = new FileReader();
  2368. reader.onload = mxUtils.bind(this, function(e)
  2369. {
  2370. if (filterFn == null || filterFn(file))
  2371. {
  2372. if (file.type.substring(0, 6) == 'image/')
  2373. {
  2374. if (file.type.substring(0, 9) == 'image/svg')
  2375. {
  2376. // Checks if SVG contains content attribute
  2377. var data = e.target.result;
  2378. var comma = data.indexOf(',');
  2379. var svgText = atob(data.substring(comma + 1));
  2380. var root = mxUtils.parseXml(svgText);
  2381. var svgs = root.getElementsByTagName('svg');
  2382. if (svgs.length > 0)
  2383. {
  2384. var svgRoot = svgs[0];
  2385. var cont = svgRoot.getAttribute('content');
  2386. if (cont != null && cont.charAt(0) != '<' && cont.charAt(0) != '%')
  2387. {
  2388. cont = unescape((window.atob) ? atob(cont) : Base64.decode(cont, true));
  2389. }
  2390. if (cont != null && cont.charAt(0) == '%')
  2391. {
  2392. cont = decodeURIComponent(cont);
  2393. }
  2394. if (cont != null && (cont.substring(0, 8) === '<mxfile ' ||
  2395. cont.substring(0, 14) === '<mxGraphModel '))
  2396. {
  2397. barrier(index, mxUtils.bind(this, function()
  2398. {
  2399. return fn(cont, 'text/xml', x + index * gs, y + index * gs, 0, 0, file.name);
  2400. }));
  2401. }
  2402. else
  2403. {
  2404. // SVG needs special handling to add viewbox if missing and
  2405. // find initial size from SVG attributes (only for IE11)
  2406. barrier(index, mxUtils.bind(this, function()
  2407. {
  2408. try
  2409. {
  2410. var prefix = data.substring(0, comma + 1);
  2411. // Parses SVG and find width and height
  2412. if (root != null)
  2413. {
  2414. var svgs = root.getElementsByTagName('svg');
  2415. if (svgs.length > 0)
  2416. {
  2417. var svgRoot = svgs[0];
  2418. var w = parseFloat(svgRoot.getAttribute('width'));
  2419. var h = parseFloat(svgRoot.getAttribute('height'));
  2420. // Check if viewBox attribute already exists
  2421. var vb = svgRoot.getAttribute('viewBox');
  2422. if (vb == null || vb.length == 0)
  2423. {
  2424. svgRoot.setAttribute('viewBox', '0 0 ' + w + ' ' + h);
  2425. }
  2426. // Uses width and height from viewbox for
  2427. // missing width and height attributes
  2428. else if (isNaN(w) || isNaN(h))
  2429. {
  2430. var tokens = vb.split(' ');
  2431. if (tokens.length > 3)
  2432. {
  2433. w = parseFloat(tokens[2]);
  2434. h = parseFloat(tokens[3]);
  2435. }
  2436. }
  2437. data = this.createSvgDataUri(mxUtils.getXml(svgs[0]));
  2438. var s = Math.min(1, Math.min(maxSize / Math.max(1, w)), maxSize / Math.max(1, h));
  2439. return fn(data, file.type, x + index * gs, y + index * gs,
  2440. Math.max(1, Math.round(w * s)), Math.max(1, Math.round(h * s)), file.name);
  2441. }
  2442. }
  2443. }
  2444. catch (e)
  2445. {
  2446. // ignores any SVG parsing errors
  2447. }
  2448. return null;
  2449. }));
  2450. }
  2451. }
  2452. }
  2453. else
  2454. {
  2455. // Checks if PNG+XML is available to bypass code below
  2456. var containsModel = false;
  2457. if (file.type == 'image/png')
  2458. {
  2459. var xml = this.extractGraphModelFromPng(e.target.result);
  2460. if (xml != null && xml.length > 0)
  2461. {
  2462. var img = new Image();
  2463. img.src = e.target.result;
  2464. barrier(index, mxUtils.bind(this, function()
  2465. {
  2466. return fn(xml, 'text/xml', x + index * gs, y + index * gs,
  2467. img.width, img.height, file.name);
  2468. }));
  2469. containsModel = true;
  2470. }
  2471. }
  2472. // Additional asynchronous step for finding image size
  2473. if (!containsModel)
  2474. {
  2475. // Cannot load local files in Chrome App
  2476. if (window.chrome != null && chrome.app != null && chrome.app.runtime != null)
  2477. {
  2478. this.spinner.stop();
  2479. this.showError(mxResources.get('error'), mxResources.get('dragAndDropNotSupported'),
  2480. mxResources.get('cancel'), mxUtils.bind(this, function()
  2481. {
  2482. // Hides the dialog
  2483. }), null, mxResources.get('ok'), mxUtils.bind(this, function()
  2484. {
  2485. // Redirects to import function
  2486. this.actions.get('import').funct();
  2487. })
  2488. );
  2489. }
  2490. else
  2491. {
  2492. this.loadImage(e.target.result, mxUtils.bind(this, function(img)
  2493. {
  2494. this.resizeImage(img, e.target.result, mxUtils.bind(this, function(data2, w2, h2)
  2495. {
  2496. barrier(index, mxUtils.bind(this, function()
  2497. {
  2498. // Refuses to insert images above a certain size as they kill the app
  2499. if (data2 != null && data2.length < maxBytes)
  2500. {
  2501. var s = (!resizeImages || !this.isResampleImage(e.target.result)) ? 1 : Math.min(1, Math.min(maxSize / w2, maxSize / h2));
  2502. return fn(data2, file.type, x + index * gs, y + index * gs, Math.round(w2 * s), Math.round(h2 * s), file.name);
  2503. }
  2504. else
  2505. {
  2506. this.handleError({message: mxResources.get('imageTooBig')});
  2507. return null;
  2508. }
  2509. }));
  2510. }), resizeImages, maxSize, resampleThreshold);
  2511. }));
  2512. }
  2513. }
  2514. }
  2515. }
  2516. else
  2517. {
  2518. fn(e.target.result, file.type, x + index * gs, y + index * gs, 240, 160, file.name, function(cells)
  2519. {
  2520. barrier(index, function()
  2521. {
  2522. return cells;
  2523. });
  2524. });
  2525. }
  2526. }
  2527. });
  2528. // Handles special case of binary file where the reader should not be used
  2529. if (/(\.vsdx)($|\?)/i.test(file.name))
  2530. {
  2531. fn(null, file.type, x + index * gs, y + index * gs, 240, 160, file.name, function(cells)
  2532. {
  2533. barrier(index, function()
  2534. {
  2535. return cells;
  2536. });
  2537. }, file);
  2538. }
  2539. else if (file.type.substring(0, 5) == 'image')
  2540. {
  2541. reader.readAsDataURL(file);
  2542. }
  2543. else
  2544. {
  2545. reader.readAsText(file);
  2546. }
  2547. }))(i);
  2548. }
  2549. }
  2550. };
  2551. /**
  2552. * Parses the file using XHR2 via the server. File can be a blob or file object.
  2553. * Filename is an optional parameter for blobs (that do not have a filename).
  2554. */
  2555. EditorUi.prototype.parseFile = function(file, fn, filename)
  2556. {
  2557. filename = (filename != null) ? filename : file.name;
  2558. var formData = new FormData();
  2559. formData.append('format', 'xml');
  2560. formData.append('upfile', file, filename);
  2561. var xhr = new XMLHttpRequest();
  2562. xhr.open('POST', OPEN_URL);
  2563. xhr.onreadystatechange = function()
  2564. {
  2565. fn(xhr);
  2566. };
  2567. xhr.send(formData);
  2568. };
  2569. /**
  2570. *
  2571. */
  2572. EditorUi.prototype.isResampleImage = function(data, thresh)
  2573. {
  2574. thresh = (thresh != null) ? thresh : this.resampleThreshold;
  2575. return data.length > thresh;
  2576. };
  2577. /**
  2578. * Resizes the given image if <maxImageBytes> is not null.
  2579. */
  2580. EditorUi.prototype.resizeImage = function(img, data, fn, enabled, maxSize, thresh)
  2581. {
  2582. maxSize = (maxSize != null) ? maxSize : this.maxImageSize;
  2583. var w = Math.max(1, img.width);
  2584. var h = Math.max(1, img.height);
  2585. if (enabled && this.isResampleImage(data, thresh))
  2586. {
  2587. try
  2588. {
  2589. var factor = Math.max(w / maxSize, h / maxSize);
  2590. if (factor > 1)
  2591. {
  2592. var w2 = Math.round(w / factor);
  2593. var h2 = Math.round(h / factor);
  2594. var canvas = document.createElement('canvas');
  2595. canvas.width = w2;
  2596. canvas.height = h2;
  2597. var ctx = canvas.getContext('2d');
  2598. ctx.drawImage(img, 0, 0, w2, h2);
  2599. var tmp = canvas.toDataURL();
  2600. // Uses new image if smaller
  2601. if (tmp.length < data.length)
  2602. {
  2603. // Checks if the image is empty by comparing
  2604. // with an empty image of the same size
  2605. var canvas2 = document.createElement('canvas');
  2606. canvas2.width = w2;
  2607. canvas2.height = h2;
  2608. var tmp2 = canvas2.toDataURL();
  2609. if (tmp !== tmp2)
  2610. {
  2611. data = tmp;
  2612. w = w2;
  2613. h = h2;
  2614. }
  2615. }
  2616. }
  2617. }
  2618. catch (e)
  2619. {
  2620. // ignores image scaling errors
  2621. }
  2622. }
  2623. fn(data, w, h);
  2624. };
  2625. /**
  2626. * Initializes CRC table.
  2627. */
  2628. (function()
  2629. {
  2630. EditorUi.prototype.crcTable = [];
  2631. for (var n = 0; n < 256; n++)
  2632. {
  2633. var c = n;
  2634. for (var k = 0; k < 8; k++)
  2635. {
  2636. if ((c & 1) == 1)
  2637. {
  2638. c = 0xedb88320 ^ (c >>> 1);
  2639. }
  2640. else
  2641. {
  2642. c >>>= 1;
  2643. }
  2644. EditorUi.prototype.crcTable[n] = c;
  2645. }
  2646. }
  2647. EditorUi.prototype.updateCRC = function(crc, data, off, len)
  2648. {
  2649. var c = crc;
  2650. for (var n = 0; n < len; n++)
  2651. {
  2652. c = EditorUi.prototype.crcTable[(c ^ data[off + n]) & 0xff] ^ (c >>> 8);
  2653. }
  2654. return c;
  2655. };
  2656. })();
  2657. /**
  2658. * Adds the given text to the compressed or non-compressed text chunk.
  2659. */
  2660. EditorUi.prototype.writeGraphModelToPng = function(data, type, key, value, error)
  2661. {
  2662. var base64 = data.substring(data.indexOf(',') + 1);
  2663. var f = (window.atob) ? atob(base64) : Base64.decode(base64, true);
  2664. var pos = 0;
  2665. function fread(d, count)
  2666. {
  2667. var start = pos;
  2668. pos += count;
  2669. return d.substring(start, pos);
  2670. };
  2671. // Reads unsigned long 32 bit big endian
  2672. function _freadint(d)
  2673. {
  2674. var bytes = fread(d, 4);
  2675. return bytes.charCodeAt(3) + (bytes.charCodeAt(2) << 8) +
  2676. (bytes.charCodeAt(1) << 16) + (bytes.charCodeAt(0) << 24);
  2677. };
  2678. function writeInt(num)
  2679. {
  2680. return String.fromCharCode((num >> 24) & 0x000000ff, (num >> 16) & 0x000000ff,
  2681. (num >> 8) & 0x000000ff, num & 0x000000ff);
  2682. };
  2683. // Checks signature
  2684. if (fread(f,8) != String.fromCharCode(137) + 'PNG' + String.fromCharCode(13, 10, 26, 10))
  2685. {
  2686. if (error != null)
  2687. {
  2688. error();
  2689. }
  2690. return;
  2691. }
  2692. // Reads header chunk
  2693. fread(f,4);
  2694. if (fread(f,4) != 'IHDR')
  2695. {
  2696. if (error != null)
  2697. {
  2698. error();
  2699. }
  2700. return;
  2701. }
  2702. fread(f, 17);
  2703. var result = f.substring(0, pos);
  2704. do
  2705. {
  2706. var n = _freadint(f);
  2707. var chunk = fread(f,4);
  2708. if (chunk == 'IDAT')
  2709. {
  2710. result = f.substring(0, pos - 8);
  2711. var crc = 0xffffffff;
  2712. crc = this.updateCRC(crc, type, 0, 4);
  2713. crc = this.updateCRC(crc, value, 0, value.length);
  2714. result += writeInt(key.length + value.length + 1 + ((type == 'zTXt') ? 1 : 0)) +
  2715. type + key + String.fromCharCode(0) +
  2716. ((type == 'zTXt') ? String.fromCharCode(0) : '') +
  2717. value + writeInt(crc ^ 0xffffffff);
  2718. result += f.substring(pos - 8, f.length);
  2719. break;
  2720. }
  2721. result += f.substring(pos - 8, pos - 4 + n);
  2722. value = fread(f,n);
  2723. fread(f,4);
  2724. }
  2725. while (n);
  2726. return 'data:image/png;base64,' + ((window.btoa) ? btoa(result) : Base64.encode(result, true));
  2727. }
  2728. /**
  2729. * Extracts the XML from the compressed or non-compressed text chunk.
  2730. */
  2731. EditorUi.prototype.extractGraphModelFromPng = function(data)
  2732. {
  2733. var result = null;
  2734. try
  2735. {
  2736. var base64 = data.substring(data.indexOf(',') + 1);
  2737. // Workaround for invalid character error in Safari
  2738. var binary = (window.atob && !mxClient.IS_SF) ? atob(base64) : Base64.decode(base64, true);
  2739. EditorUi.parsePng(binary, mxUtils.bind(this, function(pos, type, length)
  2740. {
  2741. var value = binary.substring(pos + 8, pos + 8 + length);
  2742. if (type == 'zTXt')
  2743. {
  2744. var idx = value.indexOf(String.fromCharCode(0));
  2745. if (value.substring(0, idx) == 'mxGraphModel')
  2746. {
  2747. // Workaround for Java URL Encoder using + for spaces, which isn't compatible with JS
  2748. var xmlData = this.editor.graph.bytesToString(pako.inflateRaw(
  2749. value.substring(idx + 2))).replace(/\+/g,' ');
  2750. if (xmlData != null && xmlData.length > 0)
  2751. {
  2752. result = xmlData;
  2753. }
  2754. }
  2755. }
  2756. // Uncompressed section is normally not used
  2757. else if (type == 'tEXt')
  2758. {
  2759. var vals = value.split(String.fromCharCode(0));
  2760. if (vals.length > 1 && vals[0] == 'mxGraphModel')
  2761. {
  2762. result = vals[1];
  2763. }
  2764. }
  2765. if (result != null || type == 'IDAT')
  2766. {
  2767. // Stops processing the file as our text chunks
  2768. // are always placed before the data section
  2769. return true;
  2770. }
  2771. }));
  2772. }
  2773. catch (e)
  2774. {
  2775. // ignores decoding errors
  2776. }
  2777. if (result != null && result.charAt(0) == '%')
  2778. {
  2779. result = decodeURIComponent(result);
  2780. }
  2781. // Workaround for double encoded content
  2782. if (result != null && result.charAt(0) == '%')
  2783. {
  2784. result = decodeURIComponent(result);
  2785. }
  2786. return result;
  2787. };
  2788. /**
  2789. * Loads the image from the given URI.
  2790. *
  2791. * @param {number} dx X-coordinate of the translation.
  2792. * @param {number} dy Y-coordinate of the translation.
  2793. */
  2794. EditorUi.prototype.loadImage = function(uri, onload, onerror)
  2795. {
  2796. var img = new Image();
  2797. img.onload = function()
  2798. {
  2799. onload(img);
  2800. }
  2801. if (onerror != null)
  2802. {
  2803. img.onerror = onerror;
  2804. }
  2805. img.src = uri;
  2806. };
  2807. // Initializes the user interface
  2808. var editorUiInit = EditorUi.prototype.init;
  2809. EditorUi.prototype.init = function()
  2810. {
  2811. editorUiInit.apply(this, arguments);
  2812. var graph = this.editor.graph;
  2813. var ui = this;
  2814. if (mxClient.IS_SVG)
  2815. {
  2816. // LATER: Add shadow for labels in graph.container (eg. math, NO_FO), scaling
  2817. this.editor.addSvgShadow(graph.view.canvas.ownerSVGElement, null, true);
  2818. }
  2819. /**
  2820. * Specifies the default filename.
  2821. */
  2822. this.defaultFilename = mxResources.get('untitledDiagram');
  2823. /**
  2824. * Adds placeholder for %page% and %pagenumber%
  2825. */
  2826. var graphGetGlobalVariable = graph.getGlobalVariable;
  2827. graph.getGlobalVariable = function(name)
  2828. {
  2829. if (name == 'page' && ui.currentPage != null)
  2830. {
  2831. return ui.currentPage.getName();
  2832. }
  2833. else if (name == 'pagenumber' && ui.currentPage != null && ui.pages != null)
  2834. {
  2835. return mxUtils.indexOf(ui.pages, ui.currentPage) + 1;
  2836. }
  2837. return graphGetGlobalVariable.apply(this, arguments);
  2838. };
  2839. /**
  2840. * Overrides editor filename.
  2841. */
  2842. this.editor.getOrCreateFilename = function()
  2843. {
  2844. var filename = ui.defaultFilename;
  2845. var file = ui.getCurrentFile();
  2846. if (file != null)
  2847. {
  2848. filename = (file.getTitle() != null) ? file.getTitle() : filename;
  2849. }
  2850. return filename;
  2851. };
  2852. // Disables print action for standalone apps on iOS
  2853. // because there is no way to close the new window
  2854. // LATER: Use iframe for print, disable preview
  2855. var printAction = this.actions.get('print');
  2856. printAction.setEnabled(!mxClient.IS_IOS || !navigator.standalone);
  2857. printAction.visible = printAction.isEnabled();
  2858. // Scales pages/graph to fit available size
  2859. if (!this.editor.chromeless)
  2860. {
  2861. // Defines additional hotkeys
  2862. this.keyHandler.bindAction(67, true, 'copyStyle', true); // Ctrl+Shift+C
  2863. this.keyHandler.bindAction(86, true, 'pasteStyle', true); // Ctrl+Shift+V
  2864. this.keyHandler.bindAction(77, true, 'editGeometry', true); // Ctrl+Shift+M
  2865. this.keyHandler.bindAction(88, true, 'insertText', true); // Ctrl+Shift+X
  2866. this.keyHandler.bindAction(75, true, 'insertRectangle'); // Ctrl+K
  2867. this.keyHandler.bindAction(75, true, 'insertEllipse', true); // Ctrl+Shift+K
  2868. // Handles copy paste of images from clipboard
  2869. if (!mxClient.IS_IE)
  2870. {
  2871. graph.container.addEventListener('paste', mxUtils.bind(this, function(evt)
  2872. {
  2873. var graph = this.editor.graph;
  2874. if (!mxEvent.isConsumed(evt) && !graph.isEditing())
  2875. {
  2876. try
  2877. {
  2878. var data = (evt.clipboardData || evt.originalEvent.clipboardData);
  2879. var containsText = false;
  2880. // Workaround for asynchronous paste event processing in textInput
  2881. // is to ignore this event if it contains text/html/rtf (see below).
  2882. // NOTE: Image is not pasted into textInput so can't listen there.
  2883. for (var i = 0; i < data.types.length; i++)
  2884. {
  2885. if (data.types[i].substring(0, 5) === 'text/')
  2886. {
  2887. containsText = true;
  2888. break;
  2889. }
  2890. }
  2891. if (!containsText)
  2892. {
  2893. var items = data.items;
  2894. for (index in items)
  2895. {
  2896. var item = items[index];
  2897. if (item.kind === 'file')
  2898. {
  2899. // LATER: Fix sanitizeHtml to allow for data URIs as image in labels
  2900. // if (graph.isEditing())
  2901. // {
  2902. // this.importFiles([item.getAsFile()], 0, 0, this.maxImageSize, function(data, mimeType, x, y, w, h)
  2903. // {
  2904. // // Inserts image into current text box
  2905. // graph.insertImage(data, w, h);
  2906. // }, function()
  2907. // {
  2908. // // No post processing
  2909. // }, function(file)
  2910. // {
  2911. // // Handles only images
  2912. // return file.type.substring(0, 6) == 'image/';
  2913. // }, function(queue)
  2914. // {
  2915. // // Invokes elements of queue in order
  2916. // for (var i = 0; i < queue.length; i++)
  2917. // {
  2918. // queue[i]();
  2919. // }
  2920. // });
  2921. // }
  2922. // else
  2923. {
  2924. var pt = this.editor.graph.getInsertPoint();
  2925. this.importFiles([item.getAsFile()], pt.x, pt.y, this.maxImageSize);
  2926. mxEvent.consume(evt);
  2927. }
  2928. break;
  2929. }
  2930. }
  2931. }
  2932. }
  2933. catch (e)
  2934. {
  2935. // ignore
  2936. }
  2937. }
  2938. }), false);
  2939. }
  2940. // Focused but invisible textarea during control or meta key events
  2941. var textInput = document.createElement('div');
  2942. textInput.style.position = 'absolute';
  2943. textInput.style.whiteSpace = 'nowrap';
  2944. textInput.style.overflow = 'hidden';
  2945. textInput.style.display = 'block';
  2946. textInput.contentEditable = true;
  2947. mxUtils.setOpacity(textInput, 0);
  2948. textInput.style.width = '1px';
  2949. textInput.style.height = '1px';
  2950. textInput.innerHTML = '&nbsp;';
  2951. var restoreFocus = false;
  2952. // Disables built-in cut, copy and paste shortcuts
  2953. this.keyHandler.bindControlKey(88, null);
  2954. this.keyHandler.bindControlKey(67, null);
  2955. this.keyHandler.bindControlKey(86, null);
  2956. // Shows a textare when control/cmd is pressed to handle native clipboard actions
  2957. mxEvent.addListener(document, 'keydown', mxUtils.bind(this, function(evt)
  2958. {
  2959. // No dialog visible
  2960. var source = mxEvent.getSource(evt);
  2961. if (graph.container != null && graph.isEnabled() && !graph.isMouseDown && !graph.isEditing() &&
  2962. this.dialog == null && source.nodeName != 'INPUT' && source.nodeName != 'TEXTAREA')
  2963. {
  2964. if (evt.keyCode == 224 /* FF */ || (!mxClient.IS_MAC && evt.keyCode == 17 /* Control */) ||
  2965. (mxClient.IS_MAC && evt.keyCode == 91 /* Meta */))
  2966. {
  2967. // Cannot use parentNode for check in IE
  2968. if (!restoreFocus)
  2969. {
  2970. // Avoid autoscroll but allow handling of all pass-through ctrl shortcuts
  2971. textInput.style.left = (graph.container.scrollLeft + 10) + 'px';
  2972. textInput.style.top = (graph.container.scrollTop + 10) + 'px';
  2973. graph.container.appendChild(textInput);
  2974. restoreFocus = true;
  2975. // Workaround for selected document content in quirks mode
  2976. if (mxClient.IS_QUIRKS)
  2977. {
  2978. window.setTimeout(function()
  2979. {
  2980. textInput.focus();
  2981. document.execCommand('selectAll', false, null);
  2982. }, 0);
  2983. }
  2984. else
  2985. {
  2986. textInput.focus();
  2987. document.execCommand('selectAll', false, null);
  2988. }
  2989. }
  2990. }
  2991. }
  2992. }));
  2993. // Clears input and restores focus and selection
  2994. function clearInput()
  2995. {
  2996. window.setTimeout(function()
  2997. {
  2998. textInput.innerHTML = '&nbsp;';
  2999. textInput.focus();
  3000. document.execCommand('selectAll', false, null);
  3001. }, 0);
  3002. };
  3003. mxEvent.addListener(document, 'keyup', mxUtils.bind(this, function(evt)
  3004. {
  3005. // Workaround for asynchronous event read invalid in IE quirks mode
  3006. var keyCode = evt.keyCode;
  3007. // Asynchronous workaround for scroll to origin after paste if the
  3008. // Ctrl-key is not pressed for long enough in FF on Windows
  3009. window.setTimeout(mxUtils.bind(this, function()
  3010. {
  3011. if (restoreFocus && (keyCode == 224 /* FF */ || keyCode == 17 /* Control */ ||
  3012. keyCode == 91 /* Meta */))
  3013. {
  3014. restoreFocus = false;
  3015. if (!graph.isEditing() && this.dialog == null && graph.container != null)
  3016. {
  3017. graph.container.focus();
  3018. }
  3019. textInput.parentNode.removeChild(textInput);
  3020. }
  3021. }), 0);
  3022. }));
  3023. mxEvent.addListener(textInput, 'copy', mxUtils.bind(this, function(evt)
  3024. {
  3025. if (graph.isEnabled())
  3026. {
  3027. mxClipboard.copy(graph);
  3028. this.copyCells(textInput);
  3029. clearInput();
  3030. }
  3031. }));
  3032. mxEvent.addListener(textInput, 'cut', mxUtils.bind(this, function(evt)
  3033. {
  3034. if (graph.isEnabled())
  3035. {
  3036. this.copyCells(textInput, true);
  3037. clearInput();
  3038. }
  3039. }));
  3040. mxEvent.addListener(textInput, 'paste', mxUtils.bind(this, function(evt)
  3041. {
  3042. if (graph.isEnabled() && !graph.isCellLocked(graph.getDefaultParent()))
  3043. {
  3044. textInput.innerHTML = '&nbsp;';
  3045. textInput.focus();
  3046. window.setTimeout(mxUtils.bind(this, function()
  3047. {
  3048. this.pasteCells(evt, textInput);
  3049. textInput.innerHTML = '&nbsp;';
  3050. }), 0);
  3051. }
  3052. }), true);
  3053. // Needed for IE11
  3054. var isSelectionAllowed2 = this.isSelectionAllowed;
  3055. this.isSelectionAllowed = function(evt)
  3056. {
  3057. if (mxEvent.getSource(evt) == textInput)
  3058. {
  3059. return true;
  3060. }
  3061. return isSelectionAllowed2.apply(this, arguments);
  3062. };
  3063. };
  3064. var y = Math.max(document.body.clientHeight || 0, document.documentElement.clientHeight || 0) / 2;
  3065. var x = document.body.clientWidth / 2 - 2;
  3066. // Holds the x-coordinate of the point
  3067. this.spinner = this.createSpinner(x, y, 24);
  3068. // Installs drag and drop handler for rich text editor
  3069. if (Graph.fileSupport)
  3070. {
  3071. this.editor.graph.addListener(mxEvent.EDITING_STARTED, mxUtils.bind(this, function(evt)
  3072. {
  3073. // Setup the dnd listeners
  3074. var graph = this.editor.graph;
  3075. var textElt = graph.cellEditor.text2;
  3076. var dropElt = null;
  3077. if (textElt != null)
  3078. {
  3079. mxEvent.addListener(textElt, 'dragleave', function(evt)
  3080. {
  3081. if (dropElt != null)
  3082. {
  3083. dropElt.parentNode.removeChild(dropElt);
  3084. dropElt = null;
  3085. }
  3086. evt.stopPropagation();
  3087. evt.preventDefault();
  3088. });
  3089. mxEvent.addListener(textElt, 'dragover', mxUtils.bind(this, function(evt)
  3090. {
  3091. // IE 10 does not implement pointer-events so it can't have a drop highlight
  3092. if (dropElt == null && (!mxClient.IS_IE || document.documentMode > 10))
  3093. {
  3094. dropElt = this.highlightElement(textElt);
  3095. }
  3096. evt.stopPropagation();
  3097. evt.preventDefault();
  3098. }));
  3099. mxEvent.addListener(textElt, 'drop', mxUtils.bind(this, function(evt)
  3100. {
  3101. if (dropElt != null)
  3102. {
  3103. dropElt.parentNode.removeChild(dropElt);
  3104. dropElt = null;
  3105. }
  3106. if (evt.dataTransfer.files.length > 0)
  3107. {
  3108. this.importFiles(evt.dataTransfer.files, 0, 0, this.maxImageSize, function(data, mimeType, x, y, w, h)
  3109. {
  3110. // Inserts image into current text box
  3111. graph.insertImage(data, w, h);
  3112. }, function()
  3113. {
  3114. // No post processing
  3115. }, function(file)
  3116. {
  3117. // Handles only images
  3118. return file.type.substring(0, 6) == 'image/';
  3119. }, function(queue)
  3120. {
  3121. // Invokes elements of queue in order
  3122. for (var i = 0; i < queue.length; i++)
  3123. {
  3124. queue[i]();
  3125. }
  3126. }, !mxEvent.isControlDown(evt));
  3127. }
  3128. else if (mxUtils.indexOf(evt.dataTransfer.types, 'text/uri-list') >= 0)
  3129. {
  3130. var uri = evt.dataTransfer.getData('text/uri-list');
  3131. if ((/\.(gif|jpg|jpeg|tiff|png|svg)$/i).test(uri))
  3132. {
  3133. this.loadImage(decodeURIComponent(uri), mxUtils.bind(this, function(img)
  3134. {
  3135. var w = Math.max(1, img.width);
  3136. var h = Math.max(1, img.height);
  3137. var maxSize = this.maxImageSize;
  3138. var s = Math.min(1, Math.min(maxSize / Math.max(1, w)), maxSize / Math.max(1, h));
  3139. graph.insertImage(decodeURIComponent(uri), w * s, h * s);
  3140. }));
  3141. }
  3142. else
  3143. {
  3144. document.execCommand('insertHTML', false, evt.dataTransfer.getData('text/plain'));
  3145. }
  3146. }
  3147. else
  3148. {
  3149. if (mxUtils.indexOf(evt.dataTransfer.types, 'text/html') >= 0)
  3150. {
  3151. document.execCommand('insertHTML', false, evt.dataTransfer.getData('text/html'));
  3152. }
  3153. else if (mxUtils.indexOf(evt.dataTransfer.types, 'text/plain') >= 0)
  3154. {
  3155. document.execCommand('insertHTML', false, evt.dataTransfer.getData('text/plain'));
  3156. }
  3157. }
  3158. evt.stopPropagation();
  3159. evt.preventDefault();
  3160. }));
  3161. }
  3162. }));
  3163. }
  3164. // Adds an element to edit the style in the footer in test mode
  3165. if (urlParams['test'] == '1')
  3166. {
  3167. var footer = document.getElementById('geFooter');
  3168. if (footer != null)
  3169. {
  3170. this.styleInput = document.createElement('input');
  3171. this.styleInput.setAttribute('type', 'text');
  3172. this.styleInput.style.position = 'absolute';
  3173. this.styleInput.style.top = '14px';
  3174. this.styleInput.style.left = '2px';
  3175. // Workaround for ignore right CSS property in FF
  3176. this.styleInput.style.width = '98%';
  3177. this.styleInput.style.visibility = 'hidden';
  3178. this.styleInput.style.opacity = '0.9';
  3179. mxEvent.addListener(this.styleInput, 'change', mxUtils.bind(this, function()
  3180. {
  3181. this.editor.graph.getModel().setStyle(this.editor.graph.getSelectionCell(), this.styleInput.value);
  3182. }));
  3183. footer.appendChild(this.styleInput);
  3184. this.editor.graph.getSelectionModel().addListener(mxEvent.CHANGE, mxUtils.bind(this, function(sender, evt)
  3185. {
  3186. if (this.editor.graph.getSelectionCount() > 0)
  3187. {
  3188. var cell = this.editor.graph.getSelectionCell();
  3189. var style = this.editor.graph.getModel().getStyle(cell);
  3190. this.styleInput.value = style || '';
  3191. this.styleInput.style.visibility = 'visible';
  3192. } else
  3193. {
  3194. this.styleInput.style.visibility = 'hidden';
  3195. }
  3196. }));
  3197. }
  3198. var isSelectionAllowed = this.isSelectionAllowed;
  3199. this.isSelectionAllowed = function(evt)
  3200. {
  3201. if (mxEvent.getSource(evt) == this.styleInput)
  3202. {
  3203. return true;
  3204. }
  3205. return isSelectionAllowed.apply(this, arguments);
  3206. };
  3207. }
  3208. // Removes info text in page
  3209. var info = document.getElementById('geInfo');
  3210. if (info != null)
  3211. {
  3212. info.parentNode.removeChild(info);
  3213. }
  3214. // Installs drag and drop handler for files
  3215. // Enables dropping files
  3216. if (Graph.fileSupport)
  3217. {
  3218. // Setup the dnd listeners
  3219. var dropElt = null;
  3220. mxEvent.addListener(graph.container, 'dragleave', function(evt)
  3221. {
  3222. if (graph.isEnabled())
  3223. {
  3224. if (dropElt != null)
  3225. {
  3226. dropElt.parentNode.removeChild(dropElt);
  3227. dropElt = null;
  3228. }
  3229. evt.stopPropagation();
  3230. evt.preventDefault();
  3231. }
  3232. });
  3233. mxEvent.addListener(graph.container, 'dragover', mxUtils.bind(this, function(evt)
  3234. {
  3235. // IE 10 does not implement pointer-events so it can't have a drop highlight
  3236. if (dropElt == null && (!mxClient.IS_IE || document.documentMode > 10))
  3237. {
  3238. dropElt = this.highlightElement(graph.container);
  3239. }
  3240. if (this.sidebar != null)
  3241. {
  3242. this.sidebar.hideTooltip();
  3243. }
  3244. evt.stopPropagation();
  3245. evt.preventDefault();
  3246. }));
  3247. mxEvent.addListener(graph.container, 'drop', mxUtils.bind(this, function(evt)
  3248. {
  3249. if (dropElt != null)
  3250. {
  3251. dropElt.parentNode.removeChild(dropElt);
  3252. dropElt = null;
  3253. }
  3254. if (graph.isEnabled())
  3255. {
  3256. var pt = mxUtils.convertPoint(graph.container, mxEvent.getClientX(evt), mxEvent.getClientY(evt));
  3257. var tr = graph.view.translate;
  3258. var scale = graph.view.scale;
  3259. var x = pt.x / scale - tr.x;
  3260. var y = pt.y / scale - tr.y;
  3261. if (mxEvent.isAltDown(evt))
  3262. {
  3263. x = 0;
  3264. y = 0;
  3265. }
  3266. if (evt.dataTransfer.files.length > 0)
  3267. {
  3268. this.importFiles(evt.dataTransfer.files, x, y, this.maxImageSize, null, null,
  3269. null, null, !mxEvent.isControlDown(evt) && !mxEvent.isShiftDown(evt));
  3270. }
  3271. else
  3272. {
  3273. var uri = (mxUtils.indexOf(evt.dataTransfer.types, 'text/uri-list') >= 0) ?
  3274. evt.dataTransfer.getData('text/uri-list') : null;
  3275. var data = this.extractGraphModelFromEvent(evt);
  3276. if (data != null)
  3277. {
  3278. graph.setSelectionCells(this.importXml(data, x, y, true));
  3279. }
  3280. else if (mxUtils.indexOf(evt.dataTransfer.types, 'text/html') >= 0)
  3281. {
  3282. var html = evt.dataTransfer.getData('text/html');
  3283. var div = document.createElement('div');
  3284. div.innerHTML = html;
  3285. // The default is based on the extension
  3286. var asImage = null;
  3287. // Extracts single image
  3288. var imgs = div.getElementsByTagName('img');
  3289. if (imgs != null && imgs.length == 1)
  3290. {
  3291. html = imgs[0].getAttribute('src');
  3292. // Handles special case where the src attribute has no valid extension
  3293. // in which case the text would be inserted as text with a link
  3294. if (!(/\.(gif|jpg|jpeg|tiff|png|svg)$/i).test(html))
  3295. {
  3296. asImage = true;
  3297. }
  3298. }
  3299. else
  3300. {
  3301. // Extracts single link
  3302. var a = div.getElementsByTagName('a');
  3303. if (a != null && a.length == 1)
  3304. {
  3305. html = a[0].getAttribute('href');
  3306. }
  3307. }
  3308. graph.setSelectionCells(this.insertTextAt(html, x, y, true, asImage));
  3309. }
  3310. else if (uri != null && (/\.(gif|jpg|jpeg|tiff|png|svg)$/i).test(uri))
  3311. {
  3312. this.loadImage(decodeURIComponent(uri), mxUtils.bind(this, function(img)
  3313. {
  3314. var w = Math.max(1, img.width);
  3315. var h = Math.max(1, img.height);
  3316. var maxSize = this.maxImageSize;
  3317. var s = Math.min(1, Math.min(maxSize / Math.max(1, w)), maxSize / Math.max(1, h));
  3318. graph.setSelectionCell(graph.insertVertex(null, null, '', x, y, w * s, h * s,
  3319. 'shape=image;verticalLabelPosition=bottom;labelBackgroundColor=#ffffff;' +
  3320. 'verticalAlign=top;aspect=fixed;image=' + uri + ';'));
  3321. }), mxUtils.bind(this, function(img)
  3322. {
  3323. graph.setSelectionCells(this.insertTextAt(uri, x, y, true));
  3324. }));
  3325. }
  3326. else if (mxUtils.indexOf(evt.dataTransfer.types, 'text/plain') >= 0)
  3327. {
  3328. graph.setSelectionCells(this.insertTextAt(evt.dataTransfer.getData('text/plain'), x, y, true));
  3329. }
  3330. }
  3331. }
  3332. evt.stopPropagation();
  3333. evt.preventDefault();
  3334. }), false);
  3335. }
  3336. this.initPages();
  3337. // Embedded mode
  3338. if (urlParams['embed'] == '1')
  3339. {
  3340. this.initializeEmbedMode();
  3341. }
  3342. };
  3343. /**
  3344. * Creates the format panel and adds overrides.
  3345. */
  3346. EditorUi.prototype.copyCells = function(elt, removeCells)
  3347. {
  3348. var graph = this.editor.graph;
  3349. if (!graph.isSelectionEmpty())
  3350. {
  3351. var cells = mxUtils.sortCells(graph.model.getTopmostCells(graph.getSelectionCells()));
  3352. // LATER: Add span with XML in data attribute
  3353. // var span = document.createElement('span');
  3354. // span.setAttribute('data-jgraph-type', 'application/vnd.jgraph.xml');
  3355. // span.setAttribute('data-jgraph-content', mxUtils.getXml(graph.encodeCells(clones)));
  3356. // Fixes cross-platform clipboard UTF8 issues by encoding as URI
  3357. var xml = mxUtils.getXml(this.editor.graph.encodeCells(cells));
  3358. mxUtils.setTextContent(elt, encodeURIComponent(xml));
  3359. if (removeCells)
  3360. {
  3361. graph.removeCells(cells, false);
  3362. graph.lastPasteXml = null;
  3363. }
  3364. else
  3365. {
  3366. graph.lastPasteXml = xml;
  3367. graph.pasteCounter = 0;
  3368. }
  3369. elt.focus();
  3370. document.execCommand('selectAll', false, null);
  3371. }
  3372. else
  3373. {
  3374. // Disables copy on focused element
  3375. elt.innerHTML = '';
  3376. }
  3377. };
  3378. /**
  3379. * Creates the format panel and adds overrides.
  3380. */
  3381. EditorUi.prototype.pasteCells = function(evt, elt)
  3382. {
  3383. if (!mxEvent.isConsumed(evt))
  3384. {
  3385. var graph = this.editor.graph;
  3386. var xml = mxUtils.trim((mxClient.IS_QUIRKS || document.documentMode == 8) ?
  3387. mxUtils.getTextContent(elt) : elt.textContent);
  3388. var compat = false;
  3389. // Workaround for junk after XML in VM
  3390. try
  3391. {
  3392. var idx = xml.lastIndexOf('%3E');
  3393. if (idx < xml.length - 3)
  3394. {
  3395. xml = xml.substring(0, idx + 3);
  3396. }
  3397. }
  3398. catch (e)
  3399. {
  3400. // ignore
  3401. }
  3402. // Checks for embedded XML content
  3403. try
  3404. {
  3405. var spans = elt.getElementsByTagName('span');
  3406. var tmp = (spans != null && spans.length > 0) ?
  3407. mxUtils.trim(decodeURIComponent(spans[0].textContent)) :
  3408. decodeURIComponent(xml);
  3409. if (this.isCompatibleString(tmp))
  3410. {
  3411. compat = true;
  3412. xml = tmp;
  3413. }
  3414. }
  3415. catch (e)
  3416. {
  3417. // ignore
  3418. }
  3419. if (graph.lastPasteXml == xml)
  3420. {
  3421. graph.pasteCounter++;
  3422. }
  3423. else
  3424. {
  3425. graph.lastPasteXml = xml;
  3426. graph.pasteCounter = 0;
  3427. }
  3428. var dx = graph.pasteCounter * graph.gridSize;
  3429. if (xml != null && xml.length > 0)
  3430. {
  3431. if (compat || this.isCompatibleString(xml))
  3432. {
  3433. graph.setSelectionCells(this.importXml(xml, dx, dx));
  3434. }
  3435. else
  3436. {
  3437. var pt = graph.getInsertPoint();
  3438. graph.setSelectionCells(this.insertTextAt(xml, pt.x + dx, pt.y + dx, true));
  3439. }
  3440. if (!graph.isSelectionEmpty())
  3441. {
  3442. graph.scrollCellToVisible(graph.getSelectionCell());
  3443. if (this.hoverIcons != null)
  3444. {
  3445. this.hoverIcons.update(graph.view.getState(graph.getSelectionCell()));
  3446. }
  3447. try
  3448. {
  3449. mxEvent.consume(evt);
  3450. }
  3451. catch (e)
  3452. {
  3453. // ignore event no longer exists in async handler in IE8-
  3454. }
  3455. }
  3456. }
  3457. }
  3458. };
  3459. /**
  3460. * Creates the format panel and adds overrides.
  3461. */
  3462. var editorUiCreateFormat = EditorUi.prototype.createFormat;
  3463. EditorUi.prototype.createFormat = function(container)
  3464. {
  3465. /**
  3466. * Overrides for Format sidebar.
  3467. */
  3468. var formatInit = Format.prototype.init;
  3469. Format.prototype.init = function()
  3470. {
  3471. formatInit.apply(this, arguments);
  3472. var ui = this.editorUi;
  3473. ui.editor.addListener('fileLoaded', this.update);
  3474. };
  3475. var formatRefresh = Format.prototype.refresh;
  3476. Format.prototype.refresh = function()
  3477. {
  3478. var ui = this.editorUi;
  3479. if (ui.getCurrentFile() != null || urlParams['embed'] == '1')
  3480. {
  3481. formatRefresh.apply(this, arguments);
  3482. }
  3483. else
  3484. {
  3485. this.clear();
  3486. }
  3487. };
  3488. /**
  3489. * Adds autosave and math typesetting options.
  3490. */
  3491. var diagramFormatPanelAddOptions = DiagramFormatPanel.prototype.addOptions;
  3492. DiagramFormatPanel.prototype.addOptions = function(div)
  3493. {
  3494. div = diagramFormatPanelAddOptions.apply(this, arguments);
  3495. var ui = this.editorUi;
  3496. var editor = ui.editor;
  3497. var graph = editor.graph;
  3498. if (graph.isEnabled())
  3499. {
  3500. var file = ui.getCurrentFile();
  3501. if (file != null && file.isAutosaveOptional())
  3502. {
  3503. var opt = this.createOption(mxResources.get('autosave'), function()
  3504. {
  3505. return ui.editor.autosave;
  3506. }, function(checked)
  3507. {
  3508. ui.editor.setAutosave(checked);
  3509. },
  3510. {
  3511. install: function(apply)
  3512. {
  3513. this.listener = function()
  3514. {
  3515. apply(ui.editor.autosave);
  3516. };
  3517. ui.editor.addListener('autosaveChanged', this.listener);
  3518. },
  3519. destroy: function()
  3520. {
  3521. ui.editor.removeListener(this.listener);
  3522. }
  3523. });
  3524. div.appendChild(opt);
  3525. }
  3526. }
  3527. return div;
  3528. };
  3529. /**
  3530. * Adds predefiend styles.
  3531. */
  3532. var StyleFormatPanelInit = StyleFormatPanel.prototype.init;
  3533. StyleFormatPanel.prototype.init = function()
  3534. {
  3535. // TODO: Update sstate in Format
  3536. var sstate = this.format.createSelectionState();
  3537. if (sstate.style.shape != 'image')
  3538. {
  3539. this.container.appendChild(this.addStyles(this.createPanel()));
  3540. }
  3541. StyleFormatPanelInit.apply(this, arguments);
  3542. };
  3543. /**
  3544. * Overridden to add copy and paste style.
  3545. */
  3546. var styleFormatPanelAddStyleOps = StyleFormatPanel.prototype.addStyleOps;
  3547. StyleFormatPanel.prototype.addStyleOps = function(div)
  3548. {
  3549. var btn = mxUtils.button(mxResources.get('copyStyle'), mxUtils.bind(this, function(evt)
  3550. {
  3551. this.editorUi.actions.get('copyStyle').funct();
  3552. }));
  3553. btn.setAttribute('title', mxResources.get('copyStyle') + ' (' + this.editorUi.actions.get('copyStyle').shortcut + ')');
  3554. btn.style.marginBottom = '2px';
  3555. btn.style.width = '100px';
  3556. btn.style.marginRight = '2px';
  3557. div.appendChild(btn);
  3558. var btn = mxUtils.button(mxResources.get('pasteStyle'), mxUtils.bind(this, function(evt)
  3559. {
  3560. this.editorUi.actions.get('pasteStyle').funct();
  3561. }));
  3562. btn.setAttribute('title', mxResources.get('pasteStyle') + ' (' + this.editorUi.actions.get('pasteStyle').shortcut + ')');
  3563. btn.style.marginBottom = '2px';
  3564. btn.style.width = '100px';
  3565. div.appendChild(btn);
  3566. mxUtils.br(div);
  3567. return styleFormatPanelAddStyleOps.apply(this, arguments);
  3568. };
  3569. /**
  3570. * Creates the buttons for the predefined styles.
  3571. */
  3572. StyleFormatPanel.prototype.addStyles = function(div)
  3573. {
  3574. var graph = this.editorUi.editor.graph;
  3575. div.style.paddingBottom = '4px';
  3576. var stylenames = ['plain-gray', 'plain-blue', 'plain-green', 'plain-orange',
  3577. 'plain-yellow', 'plain-red', 'plain-purple', null];
  3578. var colorsets = [null, {fill: '#f5f5f5', stroke: '#666666'},
  3579. {fill: '#dae8fc', stroke: '#6c8ebf'}, {fill: '#d5e8d4', stroke: '#82b366'},
  3580. {fill: '#ffe6cc', stroke: '#d79b00'}, {fill: '#fff2cc', stroke: '#d6b656'},
  3581. {fill: '#f8cecc', stroke: '#b85450'}, {fill: '#e1d5e7', stroke: '#9673a6'}];
  3582. function addButton(colorset)
  3583. {
  3584. var btn = mxUtils.button('', function(evt)
  3585. {
  3586. graph.getModel().beginUpdate();
  3587. try
  3588. {
  3589. var cells = graph.getSelectionCells();
  3590. for (var i = 0; i < cells.length; i++)
  3591. {
  3592. var style = graph.getModel().getStyle(cells[i]);
  3593. for (var j = 0; j < stylenames.length; j++)
  3594. {
  3595. style = mxUtils.removeStylename(style, stylenames[j]);
  3596. }
  3597. if (colorset != null)
  3598. {
  3599. style = mxUtils.setStyle(style, mxConstants.STYLE_FILLCOLOR, colorset['fill']);
  3600. style = mxUtils.setStyle(style, mxConstants.STYLE_STROKECOLOR, colorset['stroke']);
  3601. style = mxUtils.setStyle(style, mxConstants.STYLE_GRADIENTCOLOR, colorset['gradient']);
  3602. }
  3603. else
  3604. {
  3605. style = mxUtils.setStyle(style, mxConstants.STYLE_FILLCOLOR, '#ffffff');
  3606. style = mxUtils.setStyle(style, mxConstants.STYLE_STROKECOLOR, '#000000');
  3607. style = mxUtils.setStyle(style, mxConstants.STYLE_GRADIENTCOLOR, null);
  3608. }
  3609. graph.getModel().setStyle(cells[i], style);
  3610. }
  3611. }
  3612. finally
  3613. {
  3614. graph.getModel().endUpdate();
  3615. }
  3616. })
  3617. btn.style.width = '44px';
  3618. btn.style.height = '30px';
  3619. btn.style.margin = '1px 8px 6px 0px';
  3620. if (colorset != null)
  3621. {
  3622. btn.style.backgroundColor = colorset['fill'];
  3623. btn.style.border = '1px solid ' + colorset['stroke'];
  3624. }
  3625. else
  3626. {
  3627. btn.style.backgroundColor = '#ffffff';
  3628. btn.style.border = '1px solid #000000';
  3629. }
  3630. div.appendChild(btn);
  3631. };
  3632. for (var i = 0; i < colorsets.length; i++)
  3633. {
  3634. if (i > 0 && mxUtils.mod(i, 4) == 0)
  3635. {
  3636. mxUtils.br(div);
  3637. }
  3638. addButton(colorsets[i]);
  3639. }
  3640. return div;
  3641. };
  3642. return editorUiCreateFormat.apply(this, arguments);
  3643. };
  3644. // Overridden to add edit shape option
  3645. if (window.StyleFormatPanel != null)
  3646. {
  3647. StyleFormatPanel.prototype.addEditOps = function(div)
  3648. {
  3649. var ss = this.format.getSelectionState();
  3650. var btn = null;
  3651. if (this.editorUi.editor.graph.getSelectionCount() == 1)
  3652. {
  3653. btn = mxUtils.button(mxResources.get('editStyle'), mxUtils.bind(this, function(evt)
  3654. {
  3655. this.editorUi.actions.get('editStyle').funct();
  3656. }));
  3657. btn.setAttribute('title', mxResources.get('editStyle') + ' (' + this.editorUi.actions.get('editStyle').shortcut + ')');
  3658. btn.style.width = '202px';
  3659. btn.style.marginBottom = '2px';
  3660. div.appendChild(btn);
  3661. }
  3662. var graph = this.editorUi.editor.graph;
  3663. var state = graph.view.getState(graph.getSelectionCell());
  3664. if (graph.getSelectionCount() == 1 && state != null && state.shape != null && state.shape.stencil != null)
  3665. {
  3666. var btn2 = mxUtils.button(mxResources.get('editShape'), mxUtils.bind(this, function(evt)
  3667. {
  3668. this.editorUi.actions.get('editShape').funct();
  3669. }));
  3670. btn2.setAttribute('title', mxResources.get('editShape'));
  3671. btn2.style.marginBottom = '2px';
  3672. if (btn == null)
  3673. {
  3674. btn2.style.width = '202px';
  3675. }
  3676. else
  3677. {
  3678. btn.style.width = '100px';
  3679. btn2.style.width = '100px';
  3680. btn2.style.marginLeft = '2px';
  3681. }
  3682. div.appendChild(btn2);
  3683. }
  3684. else if (ss.image)
  3685. {
  3686. var btn2 = mxUtils.button(mxResources.get('editImage'), mxUtils.bind(this, function(evt)
  3687. {
  3688. this.editorUi.actions.get('image').funct();
  3689. }));
  3690. btn2.setAttribute('title', mxResources.get('editImage'));
  3691. btn2.style.marginBottom = '2px';
  3692. if (btn == null)
  3693. {
  3694. btn2.style.width = '202px';
  3695. }
  3696. else
  3697. {
  3698. btn.style.width = '100px';
  3699. btn2.style.width = '100px';
  3700. btn2.style.marginLeft = '2px';
  3701. }
  3702. div.appendChild(btn2);
  3703. }
  3704. return div;
  3705. };
  3706. }
  3707. /**
  3708. * Adds a file drop handler for opening local files.
  3709. */
  3710. EditorUi.prototype.addFileDropHandler = function(elts)
  3711. {
  3712. // Installs drag and drop handler for files
  3713. if (Graph.fileSupport)
  3714. {
  3715. var dropElt = null;
  3716. for (var i = 0; i < elts.length; i++)
  3717. {
  3718. // Setup the dnd listeners
  3719. mxEvent.addListener(elts[i], 'dragleave', function(evt)
  3720. {
  3721. if (dropElt != null)
  3722. {
  3723. dropElt.parentNode.removeChild(dropElt);
  3724. dropElt = null;
  3725. }
  3726. evt.stopPropagation();
  3727. evt.preventDefault();
  3728. });
  3729. mxEvent.addListener(elts[i], 'dragover', mxUtils.bind(this, function(evt)
  3730. {
  3731. // IE 10 does not implement pointer-events so it can't have a drop highlight
  3732. if (dropElt == null && (!mxClient.IS_IE || (document.documentMode > 10 && document.documentMode < 12)))
  3733. {
  3734. dropElt = this.highlightElement();
  3735. }
  3736. evt.stopPropagation();
  3737. evt.preventDefault();
  3738. }));
  3739. mxEvent.addListener(elts[i], 'drop', mxUtils.bind(this, function(evt)
  3740. {
  3741. if (dropElt != null)
  3742. {
  3743. dropElt.parentNode.removeChild(dropElt);
  3744. dropElt = null;
  3745. }
  3746. if (evt.dataTransfer.files.length > 0)
  3747. {
  3748. this.hideDialog();
  3749. this.openFiles(evt.dataTransfer.files);
  3750. }
  3751. else
  3752. {
  3753. // Handles open special files via text drag and drop
  3754. var data = this.extractGraphModelFromEvent(evt);
  3755. // Tries additional and async parsing of text content such as HTML, Gliffy data
  3756. if (data == null)
  3757. {
  3758. var provider = (evt.dataTransfer != null) ? evt.dataTransfer : evt.clipboardData;
  3759. if (provider != null)
  3760. {
  3761. if (document.documentMode == 10 || document.documentMode == 11)
  3762. {
  3763. data = provider.getData('Text');
  3764. }
  3765. else
  3766. {
  3767. var data = null;
  3768. if (mxUtils.indexOf(provider.types, 'text/uri-list') >= 0)
  3769. {
  3770. var data = evt.dataTransfer.getData('text/uri-list');
  3771. }
  3772. else
  3773. {
  3774. data = (mxUtils.indexOf(provider.types, 'text/html') >= 0) ? provider.getData('text/html') : null;
  3775. }
  3776. if (data != null && data.length > 0)
  3777. {
  3778. var div = document.createElement('div');
  3779. div.innerHTML = data;
  3780. // Extracts single image
  3781. var imgs = div.getElementsByTagName('img');
  3782. if (imgs.length > 0)
  3783. {
  3784. data = imgs[0].getAttribute('src');
  3785. }
  3786. }
  3787. else if (mxUtils.indexOf(provider.types, 'text/plain') >= 0)
  3788. {
  3789. data = provider.getData('text/plain');
  3790. }
  3791. }
  3792. if (data != null)
  3793. {
  3794. // Checks for embedded XML in PNG
  3795. if (data.substring(0, 22) == 'data:image/png;base64,')
  3796. {
  3797. var xml = this.extractGraphModelFromPng(data);
  3798. if (xml != null && xml.length > 0)
  3799. {
  3800. this.openLocalFile(xml);
  3801. }
  3802. }
  3803. if (!this.isOffline() && this.isRemoteFileFormat(data))
  3804. {
  3805. new mxXmlRequest(OPEN_URL, 'format=xml&data=' + encodeURIComponent(data)).send(mxUtils.bind(this, function(req)
  3806. {
  3807. if (req.getStatus() == 200)
  3808. {
  3809. this.openLocalFile(req.getText());
  3810. }
  3811. }));
  3812. }
  3813. else if (/^https?:\/\//.test(data))
  3814. {
  3815. var url = this.getUrl(window.location.pathname + '?url=' + encodeURIComponent(data));
  3816. if (this.getCurrentFile() == null)
  3817. {
  3818. window.location.href = url;
  3819. }
  3820. else
  3821. {
  3822. window.openWindow(url);
  3823. }
  3824. }
  3825. }
  3826. }
  3827. }
  3828. else
  3829. {
  3830. this.openLocalFile(data);
  3831. }
  3832. }
  3833. evt.stopPropagation();
  3834. evt.preventDefault();
  3835. }));
  3836. }
  3837. }
  3838. };
  3839. /**
  3840. * Highlights the given element
  3841. */
  3842. EditorUi.prototype.highlightElement = function(elt)
  3843. {
  3844. var x = 0;
  3845. var y = 0;
  3846. var w = 0;
  3847. var h = 0;
  3848. if (elt == null)
  3849. {
  3850. var b = document.body;
  3851. var d = document.documentElement;
  3852. w = (b.clientWidth || d.clientWidth) - 3;
  3853. h = Math.max(b.clientHeight || 0, d.clientHeight) - 3;
  3854. }
  3855. else
  3856. {
  3857. x = elt.offsetTop;
  3858. y = elt.offsetLeft;
  3859. w = elt.clientWidth;
  3860. h = elt.clientHeight;
  3861. }
  3862. var hl = document.createElement('div');
  3863. hl.style.zIndex = mxPopupMenu.prototype.zIndex + 2;
  3864. hl.style.border = '3px dotted rgb(254, 137, 12)';
  3865. hl.style.pointerEvents = 'none';
  3866. hl.style.position = 'absolute';
  3867. hl.style.top = x + 'px';
  3868. hl.style.left = y + 'px';
  3869. hl.style.width = Math.max(0, w - 3) + 'px';
  3870. hl.style.height = Math.max(0, h - 3) + 'px';
  3871. if (elt != null && elt.parentNode == this.editor.graph.container)
  3872. {
  3873. this.editor.graph.container.appendChild(hl);
  3874. }
  3875. else
  3876. {
  3877. document.body.appendChild(hl);
  3878. }
  3879. return hl;
  3880. };
  3881. /**
  3882. * Highlights the given element
  3883. */
  3884. EditorUi.prototype.stringToCells = function(xml)
  3885. {
  3886. var doc = mxUtils.parseXml(xml);
  3887. var node = this.editor.extractGraphModel(doc.documentElement);
  3888. var cells = [];
  3889. if (node != null)
  3890. {
  3891. var codec = new mxCodec(node.ownerDocument);
  3892. var model = new mxGraphModel();
  3893. codec.decode(node, model);
  3894. var parent = model.getChildAt(model.getRoot(), 0);
  3895. for (var j = 0; j < model.getChildCount(parent); j++)
  3896. {
  3897. cells.push(model.getChildAt(parent, j));
  3898. }
  3899. }
  3900. return cells;
  3901. };
  3902. /**
  3903. * Opens the given files in the editor.
  3904. */
  3905. EditorUi.prototype.openFiles = function(files)
  3906. {
  3907. if (this.spinner.spin(document.body, mxResources.get('loading')))
  3908. {
  3909. for (var i = 0; i < files.length; i++)
  3910. {
  3911. (mxUtils.bind(this, function(file)
  3912. {
  3913. var reader = new FileReader();
  3914. reader.onload = mxUtils.bind(this, function(e)
  3915. {
  3916. var data = e.target.result;
  3917. var name = file.name;
  3918. if (name != null && name.length > 0)
  3919. {
  3920. if (/(\.png)$/i.test(name))
  3921. {
  3922. name = name.substring(0, name.length - 4) + '.xml';
  3923. }
  3924. if (Graph.fileSupport && !this.isOffline() && new XMLHttpRequest().upload &&
  3925. this.isRemoteFileFormat(data, name))
  3926. {
  3927. var dot = name.lastIndexOf('.');
  3928. if (dot >= 0)
  3929. {
  3930. name = name.substring(0, name.lastIndexOf('.')) + '.xml';
  3931. }
  3932. else
  3933. {
  3934. name = name + '.xml';
  3935. }
  3936. this.parseFile(file, mxUtils.bind(this, function(xhr)
  3937. {
  3938. if (xhr.readyState == 4)
  3939. {
  3940. this.spinner.stop();
  3941. if (xhr.status == 200)
  3942. {
  3943. this.openLocalFile(xhr.responseText, name);
  3944. }
  3945. else
  3946. {
  3947. this.handleError({message: mxResources.get((xhr.status == 413) ?
  3948. 'drawingTooLarge' : 'invalidOrMissingFile')},
  3949. mxResources.get('errorLoadingFile'));
  3950. }
  3951. }
  3952. }));
  3953. }
  3954. else if (e.target.result.substring(0, 10) == '<mxlibrary')
  3955. {
  3956. this.spinner.stop();
  3957. try
  3958. {
  3959. this.loadLibrary(new LocalLibrary(this, e.target.result, file.name));
  3960. }
  3961. catch (e)
  3962. {
  3963. this.handleError(e, mxResources.get('errorLoadingFile'));
  3964. }
  3965. }
  3966. else
  3967. {
  3968. if (file.type.substring(0, 9) == 'image/png')
  3969. {
  3970. data = this.extractGraphModelFromPng(data);
  3971. }
  3972. this.spinner.stop();
  3973. this.openLocalFile(data, name);
  3974. }
  3975. }
  3976. });
  3977. reader.onerror = mxUtils.bind(this, function(e)
  3978. {
  3979. this.spinner.stop();
  3980. this.handleError(e);
  3981. window.openFile = null;
  3982. });
  3983. if (file.type.substring(0, 5) === 'image' && file.type.substring(0, 9) !== 'image/svg')
  3984. {
  3985. reader.readAsDataURL(file);
  3986. }
  3987. else
  3988. {
  3989. reader.readAsText(file);
  3990. }
  3991. }))(files[i]);
  3992. }
  3993. }
  3994. };
  3995. /**
  3996. * Shows the layers dialog if the graph has more than one layer.
  3997. */
  3998. EditorUi.prototype.openLocalFile = function(data, name)
  3999. {
  4000. var fn = mxUtils.bind(this, function()
  4001. {
  4002. window.openFile = null;
  4003. if (name == null && this.getCurrentFile() != null && this.isDiagramEmpty())
  4004. {
  4005. var doc = mxUtils.parseXml(data);
  4006. if (doc != null)
  4007. {
  4008. this.editor.setGraphXml(doc.documentElement);
  4009. this.editor.graph.selectAll();
  4010. }
  4011. }
  4012. else
  4013. {
  4014. this.fileLoaded(new LocalFile(this, data, name || this.defaultFilename));
  4015. }
  4016. });
  4017. if (data != null && data.length > 0)
  4018. {
  4019. if (this.getCurrentFile() != null && !this.isDiagramEmpty())
  4020. {
  4021. window.openFile = new OpenFile(function()
  4022. {
  4023. window.openFile = null;
  4024. });
  4025. window.openFile.setData(data, name);
  4026. window.openWindow(this.getUrl(), null, fn);
  4027. }
  4028. else
  4029. {
  4030. fn();
  4031. }
  4032. }
  4033. };
  4034. /**
  4035. * Shows the layers dialog if the graph has more than one layer.
  4036. */
  4037. EditorUi.prototype.initializeEmbedMode = function()
  4038. {
  4039. this.diagramContainer.style.visibility = 'hidden';
  4040. this.formatContainer.style.visibility = 'hidden';
  4041. this.editor.graph.setEnabled(false);
  4042. var parent = window.opener || window.parent;
  4043. if (parent != window)
  4044. {
  4045. if (urlParams['spin'] != '1' || this.spinner.spin(document.body, mxResources.get('loading')))
  4046. {
  4047. this.installMessageHandler(mxUtils.bind(this, function(xml, evt, modified)
  4048. {
  4049. this.spinner.stop();
  4050. this.addEmbedButtons();
  4051. this.diagramContainer.style.visibility = '';
  4052. this.formatContainer.style.visibility = '';
  4053. this.editor.graph.setEnabled(true);
  4054. if (xml != null && xml.length > 0)
  4055. {
  4056. var doc = mxUtils.parseXml(xml);
  4057. this.editor.setGraphXml(doc.documentElement);
  4058. this.showLayersDialog();
  4059. }
  4060. else
  4061. {
  4062. this.editor.graph.model.clear();
  4063. this.editor.fireEvent(new mxEventObject('resetGraphView'));
  4064. }
  4065. this.editor.undoManager.clear();
  4066. this.editor.modified = (modified != null) ? modified : false;
  4067. this.updateUi();
  4068. if (this.format != null)
  4069. {
  4070. this.format.refresh();
  4071. }
  4072. }));
  4073. }
  4074. }
  4075. };
  4076. /**
  4077. * Shows the layers dialog if the graph has more than one layer.
  4078. */
  4079. EditorUi.prototype.showLayersDialog = function()
  4080. {
  4081. if (this.editor.graph.getModel().getChildCount(this.editor.graph.getModel().getRoot()) > 1)
  4082. {
  4083. if (this.actions.layersWindow == null)
  4084. {
  4085. this.actions.get('layers').funct();
  4086. }
  4087. else
  4088. {
  4089. this.actions.layersWindow.window.setVisible(true);
  4090. }
  4091. }
  4092. };
  4093. /**
  4094. * Adds the buttons for embedded mode.
  4095. */
  4096. EditorUi.prototype.createLoadMessage = function(eventName)
  4097. {
  4098. var graph = this.editor.graph;
  4099. return {event: eventName, pageVisible: graph.pageVisible, translate: graph.view.translate,
  4100. scale: graph.view.scale, page: graph.view.getBackgroundPageBounds(), bounds: graph.getGraphBounds()};
  4101. };
  4102. /**
  4103. * Adds the buttons for embedded mode.
  4104. */
  4105. EditorUi.prototype.installMessageHandler = function(fn)
  4106. {
  4107. var autosave = false;
  4108. var updateStatus = mxUtils.bind(this, function(sender, eventObject)
  4109. {
  4110. if (urlParams['modified'] != null)
  4111. {
  4112. if (urlParams['modified'] == '0')
  4113. {
  4114. this.editor.setStatus('');
  4115. }
  4116. else
  4117. {
  4118. this.editor.setStatus(mxResources.get(urlParams['modified']));
  4119. }
  4120. }
  4121. });
  4122. this.editor.graph.model.addListener(mxEvent.CHANGE, updateStatus);
  4123. // Receives XML message from opener and puts it into the graph
  4124. mxEvent.addListener(window, 'message', mxUtils.bind(this, function(evt)
  4125. {
  4126. var data = evt.data;
  4127. if (urlParams['proto'] == 'json')
  4128. {
  4129. data = JSON.parse(data);
  4130. if (data.action == 'dialog')
  4131. {
  4132. this.showError((data.titleKey != null) ? mxResources.get(data.titleKey) : data.title,
  4133. (data.messageKey != null) ? mxResources.get(data.messageKey) : data.message,
  4134. (data.buttonKey != null) ? mxResources.get(data.buttonKey) : data.button);
  4135. if (data.modified != null)
  4136. {
  4137. this.editor.modified = data.modified;
  4138. }
  4139. return;
  4140. }
  4141. else if (data.action == 'template')
  4142. {
  4143. this.spinner.stop();
  4144. var dlg = new NewDialog(this, false, false, mxUtils.bind(this, function(xml)
  4145. {
  4146. // LATER: Add autosave option in template message
  4147. fn(xml || '', evt, xml != null);
  4148. }));
  4149. this.showDialog(dlg.container, 620, 440, true, true, mxUtils.bind(this, function(cancel)
  4150. {
  4151. if (cancel)
  4152. {
  4153. this.actions.get('exit').funct();
  4154. }
  4155. }));
  4156. dlg.init();
  4157. return;
  4158. }
  4159. else if (data.action == 'status')
  4160. {
  4161. if (data.messageKey != null)
  4162. {
  4163. this.editor.setStatus(mxResources.get(data.messageKey));
  4164. }
  4165. else if (data.message != null)
  4166. {
  4167. this.editor.setStatus(data.message);
  4168. }
  4169. if (data.modified != null)
  4170. {
  4171. this.editor.modified = data.modified;
  4172. }
  4173. return;
  4174. }
  4175. else if (data.action == 'spinner')
  4176. {
  4177. var msg = (data.messageKey != null) ? mxResources.get(data.messageKey) : data.message;
  4178. if (data.show != null && !data.show)
  4179. {
  4180. this.spinner.stop();
  4181. }
  4182. else
  4183. {
  4184. this.spinner.spin(document.body, msg)
  4185. }
  4186. return;
  4187. }
  4188. else if (data.action == 'export')
  4189. {
  4190. if (data.format == 'png' || data.format == 'xmlpng')
  4191. {
  4192. if ((data.spin == null && data.spinKey == null) || this.spinner.spin(document.body,
  4193. (data.spinKey != null) ? mxResources.get(data.spinKey) : data.spin))
  4194. {
  4195. var xml = (data.xml != null) ? data.xml : this.getFileData(true);
  4196. this.editor.graph.setEnabled(false);
  4197. var postDataBack = mxUtils.bind(this, function(bin)
  4198. {
  4199. var msg = this.createLoadMessage('export');
  4200. msg.format = data.format;
  4201. msg.xml = encodeURIComponent(xml);
  4202. msg.data = 'data:image/png;base64,' + bin;
  4203. parent.postMessage(JSON.stringify(msg), '*');
  4204. this.editor.graph.setEnabled(true);
  4205. });
  4206. if (this.isExportToCanvas())
  4207. {
  4208. this.exportToCanvas(mxUtils.bind(this, function(canvas)
  4209. {
  4210. var uri = canvas.toDataURL('image/png');
  4211. if (data.format == 'xmlpng')
  4212. {
  4213. uri = this.writeGraphModelToPng(uri, 'zTXt', 'mxGraphModel',
  4214. atob(this.editor.graph.compress(xml)));
  4215. }
  4216. postDataBack(uri.substring(uri.lastIndexOf(',') + 1));
  4217. }));
  4218. }
  4219. else
  4220. {
  4221. // Data from server is base64 encoded to avoid binary XHR
  4222. // Double encoding for XML arg is needed for UTF8 encoding
  4223. var req = new mxXmlRequest(EXPORT_URL, 'format=png&embedXml=' +
  4224. ((data.format == 'xmlpng') ? '1' : '0') + '&base64=1&xml=' +
  4225. encodeURIComponent(encodeURIComponent(xml)));
  4226. req.send(mxUtils.bind(this, function(req)
  4227. {
  4228. this.editor.graph.setEnabled(true);
  4229. this.spinner.stop();
  4230. if (req.getStatus() == 200)
  4231. {
  4232. postDataBack(req.getText());
  4233. }
  4234. }), mxUtils.bind(this, function()
  4235. {
  4236. this.spinner.stop();
  4237. }));
  4238. }
  4239. }
  4240. }
  4241. else
  4242. {
  4243. // SVG is generated from graph so parse optional XML
  4244. if (data.xml != null && data.xml.length > 0)
  4245. {
  4246. var doc = mxUtils.parseXml(data.xml);
  4247. this.editor.setGraphXml(doc.documentElement);
  4248. }
  4249. var msg = this.createLoadMessage('export');
  4250. if (data.format == 'html' || data.format == 'html2')
  4251. {
  4252. var xml = this.editor.getGraphXml();
  4253. msg.data = (data.format == 'html2') ? this.getHtml2(xml, this.editor.graph) :
  4254. this.getHtml(xml, this.editor.graph);
  4255. msg.xml = mxUtils.getXml(xml);
  4256. msg.format = data.format;
  4257. }
  4258. else
  4259. {
  4260. // Creates a preview with no alt text for unsupported browsers
  4261. mxSvgCanvas2D.prototype.foAltText = null;
  4262. var bg = this.editor.graph.background;
  4263. if (bg == mxConstants.NONE)
  4264. {
  4265. bg = null;
  4266. }
  4267. msg.xml = mxUtils.getXml(this.editor.getGraphXml());
  4268. msg.format = 'svg';
  4269. if (data.embedImages || data.embedImages == null)
  4270. {
  4271. if ((data.spin == null && data.spinKey == null) || this.spinner.spin(document.body,
  4272. (data.spinKey != null) ? mxResources.get(data.spinKey) : data.spin))
  4273. {
  4274. this.editor.graph.setEnabled(false);
  4275. if (data.format == 'xmlsvg')
  4276. {
  4277. this.getEmbeddedSvg(msg.xml, this.editor.graph, null, true, mxUtils.bind(this, function(svg)
  4278. {
  4279. this.editor.graph.setEnabled(true);
  4280. this.spinner.stop();
  4281. msg.data = this.createSvgDataUri(svg);
  4282. parent.postMessage(JSON.stringify(msg), '*');
  4283. }));
  4284. }
  4285. else
  4286. {
  4287. this.convertImages(this.editor.graph.getSvg(bg), mxUtils.bind(this, function(svgRoot)
  4288. {
  4289. this.editor.graph.setEnabled(true);
  4290. this.spinner.stop();
  4291. msg.data = this.createSvgDataUri(mxUtils.getXml(svgRoot));
  4292. parent.postMessage(JSON.stringify(msg), '*');
  4293. }));
  4294. }
  4295. }
  4296. return;
  4297. }
  4298. else
  4299. {
  4300. var svg = (data.format == 'xmlsvg') ? this.getEmbeddedSvg(mxUtils.getXml(this.editor.getGraphXml()),
  4301. this.editor.graph, null, true) : mxUtils.getXml(this.editor.graph.getSvg(bg));
  4302. msg.data = this.createSvgDataUri(svg);
  4303. }
  4304. }
  4305. parent.postMessage(JSON.stringify(msg), '*');
  4306. }
  4307. return;
  4308. }
  4309. else if (data.action == 'load')
  4310. {
  4311. autosave = data.autosave == 1;
  4312. if (data.modified != null && urlParams['modified'] == null)
  4313. {
  4314. urlParams['modified'] = data.modified;
  4315. }
  4316. if (data.saveAndExit != null && urlParams['saveAndExit'] == null)
  4317. {
  4318. urlParams['saveAndExit'] = data.saveAndExit;
  4319. }
  4320. if (data.xmlpng != null)
  4321. {
  4322. data = this.extractGraphModelFromPng(data.xmlpng);
  4323. }
  4324. else
  4325. {
  4326. data = data.xml;
  4327. }
  4328. }
  4329. else
  4330. {
  4331. // Unknown message
  4332. data = null;
  4333. }
  4334. }
  4335. if (data != null && data.charAt(0) != '<')
  4336. {
  4337. try
  4338. {
  4339. if (data.substring(0, 26) == 'data:image/svg+xml;base64,')
  4340. {
  4341. data = atob(data.substring(26));
  4342. }
  4343. else if (data.substring(0, 24) == 'data:image/svg+xml;utf8,')
  4344. {
  4345. data = data.substring(24);
  4346. }
  4347. if (data != null)
  4348. {
  4349. if (data.charAt(0) == '%')
  4350. {
  4351. data = decodeURIComponent(data);
  4352. }
  4353. else if (data.charAt(0) != '<')
  4354. {
  4355. data = this.editor.graph.decompress(data);
  4356. }
  4357. }
  4358. }
  4359. catch (e)
  4360. {
  4361. // ignore compression errors and use empty data
  4362. }
  4363. }
  4364. fn(data, evt);
  4365. if (urlParams['modified'] != null)
  4366. {
  4367. this.editor.setStatus('');
  4368. }
  4369. if (autosave)
  4370. {
  4371. var changeListener = mxUtils.bind(this, function(sender, eventObject)
  4372. {
  4373. var data = mxUtils.getXml(this.editor.getGraphXml());
  4374. var msg = this.createLoadMessage('autosave');
  4375. msg.xml = data;
  4376. data = JSON.stringify(msg);
  4377. var parent = window.opener || window.parent;
  4378. parent.postMessage(data, '*');
  4379. });
  4380. this.editor.graph.model.addListener(mxEvent.CHANGE, changeListener);
  4381. // Some options trigger autosave
  4382. this.addListener('pageFormatChanged', changeListener);
  4383. this.addListener('backgroundColorChanged', changeListener);
  4384. this.addListener('backgroundImageChanged', changeListener);
  4385. this.addListener('foldingEnabledChanged', changeListener);
  4386. this.addListener('mathEnabledChanged', changeListener);
  4387. this.addListener('gridEnabledChanged', changeListener);
  4388. this.addListener('guidesEnabledChanged', changeListener);
  4389. this.addListener('pageViewChanged', changeListener);
  4390. }
  4391. // Sends the bounds of the graph to the host after parsing
  4392. if (urlParams['returnbounds'] == '1' || urlParams['proto'] == 'json')
  4393. {
  4394. parent.postMessage(JSON.stringify(this.createLoadMessage('load')), '*');
  4395. }
  4396. }));
  4397. // Requests data from the sender. This is a workaround for not allowing
  4398. // the opener to listen for the onload event if not in the same origin.
  4399. var parent = window.opener || window.parent;
  4400. var msg = (urlParams['proto'] == 'json') ? JSON.stringify({event: 'init'}) : (urlParams['ready'] || 'ready');
  4401. parent.postMessage(msg, '*');
  4402. };
  4403. /**
  4404. * Adds the buttons for embedded mode.
  4405. */
  4406. EditorUi.prototype.addEmbedButtons = function()
  4407. {
  4408. if (this.menubar != null)
  4409. {
  4410. var div = document.createElement('div');
  4411. div.style.display = 'inline-block';
  4412. div.style.position = 'absolute';
  4413. div.style.paddingTop = (uiTheme == 'atlas') ? '2px' : '3px';
  4414. div.style.paddingLeft = '8px';
  4415. div.style.paddingBottom = '2px';
  4416. var button = document.createElement('button');
  4417. mxUtils.write(button, mxResources.get('save'));
  4418. button.className = 'geBigButton';
  4419. button.style.fontSize = '12px';
  4420. button.style.padding = '4px 6px 4px 6px';
  4421. button.style.borderRadius = '3px';
  4422. mxEvent.addListener(button, 'click', mxUtils.bind(this, function()
  4423. {
  4424. this.actions.get('save').funct();
  4425. }));
  4426. div.appendChild(button);
  4427. if (urlParams['saveAndExit'] == '1')
  4428. {
  4429. button = document.createElement('a');
  4430. mxUtils.write(button, mxResources.get('saveAndExit'));
  4431. button.style.fontSize = '12px';
  4432. button.style.marginLeft = '6px';
  4433. button.style.padding = '4px';
  4434. button.style.cursor = 'pointer';
  4435. mxEvent.addListener(button, 'click', mxUtils.bind(this, function()
  4436. {
  4437. this.actions.get('saveAndExit').funct();
  4438. }));
  4439. div.appendChild(button);
  4440. }
  4441. button = document.createElement('a');
  4442. mxUtils.write(button, mxResources.get('exit'));
  4443. button.style.fontSize = '12px';
  4444. button.style.marginLeft = '6px';
  4445. button.style.marginRight = '20px';
  4446. button.style.padding = '4px';
  4447. button.style.cursor = 'pointer';
  4448. mxEvent.addListener(button, 'click', mxUtils.bind(this, function()
  4449. {
  4450. this.actions.get('exit').funct();
  4451. }));
  4452. div.appendChild(button);
  4453. this.toolbar.container.appendChild(div);
  4454. this.toolbar.staticElements.push(div);
  4455. div.style.right = (uiTheme != 'atlas') ? '52px' : '42px';
  4456. }
  4457. };
  4458. /**
  4459. * Translates this point by the given vector.
  4460. *
  4461. * @param {number} dx X-coordinate of the translation.
  4462. * @param {number} dy Y-coordinate of the translation.
  4463. */
  4464. EditorUi.prototype.getSearch = function(exclude)
  4465. {
  4466. var result = '';
  4467. if (urlParams['offline'] != '1' && urlParams['demo'] != '1' && exclude != null && window.location.search.length > 0)
  4468. {
  4469. var amp = '?';
  4470. for (var key in urlParams)
  4471. {
  4472. if (mxUtils.indexOf(exclude, key) < 0 && urlParams[key] != null)
  4473. {
  4474. result += amp + key + '=' + urlParams[key];
  4475. amp = '&';
  4476. }
  4477. }
  4478. }
  4479. else
  4480. {
  4481. result = window.location.search;
  4482. }
  4483. return result;
  4484. };
  4485. /**
  4486. * Returns the URL for a copy of this editor with no state.
  4487. */
  4488. EditorUi.prototype.getUrl = function(pathname)
  4489. {
  4490. var href = (pathname != null) ? pathname : window.location.pathname;
  4491. var parms = (href.indexOf('?') > 0) ? 1 : 0;
  4492. if (urlParams['offline'] == '1')
  4493. {
  4494. href += window.location.search;
  4495. }
  4496. else
  4497. {
  4498. var ignored = ['tmp', 'libs', 'clibs', 'state', 'fileId', 'code', 'share', 'notitle',
  4499. 'url', 'embed', 'client', 'create', 'title', 'splash'];
  4500. // Removes template URL parameter for new blank diagram
  4501. for (var key in urlParams)
  4502. {
  4503. if (mxUtils.indexOf(ignored, key) < 0)
  4504. {
  4505. if (parms == 0)
  4506. {
  4507. href += '?';
  4508. }
  4509. else
  4510. {
  4511. href += '&';
  4512. }
  4513. if (urlParams[key] != null)
  4514. {
  4515. href += key + '=' + urlParams[key];
  4516. parms++;
  4517. }
  4518. }
  4519. }
  4520. }
  4521. return href;
  4522. };
  4523. /**
  4524. * Overrides createOutline
  4525. */
  4526. var editorUiCreateOutline = EditorUi.prototype.createOutline;
  4527. EditorUi.prototype.createOutline = function(wnd)
  4528. {
  4529. var outline = editorUiCreateOutline.apply(this, arguments);
  4530. var graph = this.editor.graph;
  4531. var outlineGetSourceGraphBounds = outline.getSourceGraphBounds;
  4532. outline.getSourceGraphBounds = function()
  4533. {
  4534. if (mxUtils.hasScrollbars(graph.container) && graph.pageVisible && this.source.minimumGraphSize != null)
  4535. {
  4536. var pb = this.source.getPagePadding();
  4537. var s = this.source.view.scale;
  4538. var result = new mxRectangle(0, 0, Math.ceil(this.source.minimumGraphSize.width - 2 * pb.x / s),
  4539. Math.ceil(this.source.minimumGraphSize.height - 2 * pb.y / s));
  4540. return result;
  4541. }
  4542. return outlineGetSourceGraphBounds.apply(this, arguments);
  4543. };
  4544. var outlineGetSourceContainerSize = outline.getSourceContainerSize;
  4545. outline.getSourceContainerSize = function()
  4546. {
  4547. if (mxUtils.hasScrollbars(graph.container) && this.source.minimumGraphSize != null)
  4548. {
  4549. var pad = this.source.getPagePadding();
  4550. var s = this.source.view.scale;
  4551. return new mxRectangle(0, 0, Math.ceil(this.source.minimumGraphSize.width * s - 2 * pad.x),
  4552. Math.ceil(this.source.minimumGraphSize.height * s - 2 * pad.y));
  4553. }
  4554. return outlineGetSourceContainerSize.apply(this, arguments);
  4555. };
  4556. outline.getOutlineOffset = function(scale)
  4557. {
  4558. if (mxUtils.hasScrollbars(graph.container) && this.source.minimumGraphSize != null)
  4559. {
  4560. var pb = this.source.getPagePadding();
  4561. var dx = Math.max(0, (outline.outline.container.clientWidth / scale - (this.source.minimumGraphSize.width - 2 * pb.x)) / 2);
  4562. var dy = Math.max(0, (outline.outline.container.clientHeight / scale - (this.source.minimumGraphSize.height - 2 * pb.y)) / 2);
  4563. // Why is vertical offset negative relative to dy
  4564. return new mxPoint(Math.round(dx - pb.x), Math.round(dy - pb.y - 5 / scale));
  4565. }
  4566. return new mxPoint(8 / scale, 8 / scale);
  4567. };
  4568. var outlineInit = outline.init;
  4569. outline.init = function()
  4570. {
  4571. outlineInit.apply(this, arguments);
  4572. // Problem: Need to override a function in the view but the view is created
  4573. // with the graph so a refresh of the page is needed to see this change.
  4574. outline.outline.view.getBackgroundPageBounds = function()
  4575. {
  4576. var layout = graph.getPageLayout();
  4577. var page = graph.getPageSize();
  4578. return new mxRectangle(this.scale * (this.translate.x + layout.x * page.width),
  4579. this.scale * (this.translate.y + layout.y * page.height),
  4580. this.scale * layout.width * page.width,
  4581. this.scale * layout.height * page.height);
  4582. };
  4583. outline.outline.view.validateBackgroundPage();
  4584. };
  4585. this.editor.addListener('pageSelected', function(sender, evt)
  4586. {
  4587. var change = evt.getProperty('change');
  4588. var graph = outline.source;
  4589. var g = outline.outline;
  4590. g.pageScale = graph.pageScale;
  4591. g.pageFormat = graph.pageFormat;
  4592. g.background = graph.background;
  4593. g.pageVisible = graph.pageVisible;
  4594. g.background = graph.background;
  4595. var current = mxUtils.getCurrentStyle(graph.container);
  4596. g.container.style.backgroundColor = current.backgroundColor;
  4597. if (graph.view.backgroundPageShape != null && g.view.backgroundPageShape != null)
  4598. {
  4599. g.view.backgroundPageShape.fill = graph.view.backgroundPageShape.fill;
  4600. }
  4601. outline.outline.view.clear(change.previousPage.root, true);
  4602. outline.outline.view.validate();
  4603. });
  4604. return outline;
  4605. };
  4606. /**
  4607. * Updates action states depending on the selection.
  4608. */
  4609. var editorUiUpdateActionStates = EditorUi.prototype.updateActionStates;
  4610. EditorUi.prototype.updateActionStates = function()
  4611. {
  4612. editorUiUpdateActionStates.apply(this, arguments);
  4613. var graph = this.editor.graph;
  4614. var file = this.getCurrentFile();
  4615. var active = (file != null && file.isEditable()) || urlParams['embed'] == '1';
  4616. this.actions.get('pageSetup').setEnabled(active);
  4617. this.actions.get('autosave').setEnabled(file != null && file.isEditable() && file.isAutosaveOptional());
  4618. this.actions.get('guides').setEnabled(active);
  4619. this.actions.get('shadowVisible').setEnabled(active);
  4620. this.actions.get('connectionArrows').setEnabled(active);
  4621. this.actions.get('connectionPoints').setEnabled(active);
  4622. this.actions.get('copyStyle').setEnabled(active && !graph.isSelectionEmpty());
  4623. this.actions.get('pasteStyle').setEnabled(active && !graph.isSelectionEmpty());
  4624. this.actions.get('editGeometry').setEnabled(graph.getModel().isVertex(graph.getSelectionCell()));
  4625. this.actions.get('createShape').setEnabled(active);
  4626. this.actions.get('createRevision').setEnabled(active);
  4627. this.actions.get('moveToFolder').setEnabled(file != null);
  4628. this.actions.get('makeCopy').setEnabled(file != null && !file.isRestricted());
  4629. this.actions.get('editDiagram').setEnabled(urlParams['embed'] == '1' ||
  4630. (file != null && !file.isRestricted()));
  4631. this.actions.get('imgur').setEnabled(file != null && !file.isRestricted());
  4632. this.actions.get('github').setEnabled(file != null && !file.isRestricted());
  4633. var state = graph.view.getState(graph.getSelectionCell());
  4634. this.actions.get('editShape').setEnabled(active && state != null && state.shape != null && state.shape.stencil != null);
  4635. };
  4636. /**
  4637. * Changes the default stylename so that it matches the old named style
  4638. * if one was specified in the XML.
  4639. */
  4640. Graph.prototype.defaultThemeName = 'default-style2';
  4641. /**
  4642. * Contains the last XML that was pasted.
  4643. */
  4644. Graph.prototype.lastPasteXml = null;
  4645. /**
  4646. * Contains the number of times the last XML was pasted.
  4647. */
  4648. Graph.prototype.pasteCounter = 0;
  4649. // Experimental edge mode
  4650. Graph.prototype.edgeMode = urlParams['edge'] != 'move';
  4651. /**
  4652. * Sets default style (used in editor.get/setGraphXml below)
  4653. */
  4654. var graphLoadStylesheet = Graph.prototype.loadStylesheet;
  4655. Graph.prototype.loadStylesheet = function()
  4656. {
  4657. graphLoadStylesheet.apply(this, arguments);
  4658. this.currentStyle = 'default-style2';
  4659. };
  4660. /**
  4661. * Graph Overrides
  4662. */
  4663. Graph.prototype.defaultScrollbars = urlParams['sb'] != '0';
  4664. /**
  4665. * Specifies if the page should be visible for new files. Default is true.
  4666. */
  4667. Graph.prototype.defaultPageVisible = urlParams['pv'] != '0';
  4668. /**
  4669. * Loads the stylesheet for this graph.
  4670. */
  4671. Graph.prototype.setShadowVisible = function(value, fireEvent)
  4672. {
  4673. if (mxClient.IS_SVG)
  4674. {
  4675. fireEvent = (fireEvent != null) ? fireEvent : true;
  4676. this.shadowVisible = value;
  4677. if (this.shadowVisible)
  4678. {
  4679. this.view.getDrawPane().setAttribute('filter', 'url(#dropShadow)');
  4680. }
  4681. else
  4682. {
  4683. this.view.getDrawPane().removeAttribute('filter');
  4684. }
  4685. if (fireEvent)
  4686. {
  4687. this.fireEvent(new mxEventObject('shadowVisibleChanged'));
  4688. }
  4689. }
  4690. };
  4691. /**
  4692. * Adds rack child layout style.
  4693. */
  4694. var graphInit = Graph.prototype.init;
  4695. Graph.prototype.init = function()
  4696. {
  4697. graphInit.apply(this, arguments);
  4698. // Override insert location for current mouse point
  4699. var mouseEvent = null;
  4700. function setMouseEvent(evt)
  4701. {
  4702. mouseEvent = evt;
  4703. // Workaround for member not found in IE8-
  4704. if (mxClient.IS_QUIRKS || document.documentMode == 7 || document.documentMode == 8)
  4705. {
  4706. mouseEvent = mxUtils.clone(evt);
  4707. }
  4708. };
  4709. mxEvent.addListener(this.container, 'mouseenter', setMouseEvent);
  4710. mxEvent.addListener(this.container, 'mousemove', setMouseEvent);
  4711. mxEvent.addListener(this.container, 'mouseleave', function(evt)
  4712. {
  4713. mouseEvent = null;
  4714. });
  4715. // Extends getInsertPoint to use the current mouse location
  4716. this.isMouseInsertPoint = function()
  4717. {
  4718. return mouseEvent != null;
  4719. };
  4720. var getInsertPoint = this.getInsertPoint;
  4721. this.getInsertPoint = function()
  4722. {
  4723. if (mouseEvent != null)
  4724. {
  4725. return this.getPointForEvent(mouseEvent);
  4726. }
  4727. return getInsertPoint.apply(this, arguments);
  4728. };
  4729. var layoutManagerGetLayout = this.layoutManager.getLayout;
  4730. this.layoutManager.getLayout = function(cell)
  4731. {
  4732. var state = this.graph.view.getState(cell);
  4733. var style = (state != null) ? state.style : this.graph.getCellStyle(cell);
  4734. // mxRackContainer may be undefined as it is dynamically loaded at render time
  4735. if (typeof(mxRackContainer) != 'undefined' && style['childLayout'] == 'rack')
  4736. {
  4737. var rackLayout = new mxStackLayout(this.graph, false);
  4738. rackLayout.setChildGeometry = function(child, geo)
  4739. {
  4740. var unitSize = 20;
  4741. geo.height = Math.max(geo.height, unitSize);
  4742. if (geo.height / unitSize > 1)
  4743. {
  4744. var mod = geo.height % unitSize;
  4745. geo.height += mod > unitSize / 2 ? (unitSize - mod) : -mod;
  4746. }
  4747. this.graph.getModel().setGeometry(child, geo);
  4748. };
  4749. rackLayout.fill = true;
  4750. rackLayout.unitSize = mxRackContainer.unitSize | 20;
  4751. rackLayout.marginLeft = style['marginLeft'] || 0;
  4752. rackLayout.marginRight = style['marginRight'] || 0;
  4753. rackLayout.marginTop = style['marginTop'] || 0;
  4754. rackLayout.marginBottom = style['marginBottom'] || 0;
  4755. rackLayout.resizeParent = false;
  4756. return rackLayout;
  4757. }
  4758. return layoutManagerGetLayout.apply(this, arguments);
  4759. }
  4760. };
  4761. /**
  4762. * Specifies special libraries that are loaded via dynamic JS.
  4763. *
  4764. *************************************************************
  4765. * IMPORTANT: Add all special cases in EmbedServlet.java and *
  4766. * jgraphcms/js/Graph.js lines 102 ff. *
  4767. *************************************************************
  4768. */
  4769. mxStencilRegistry.libraries['arrows2'] = [SHAPES_PATH + '/mxArrows.js'];
  4770. mxStencilRegistry.libraries['bpmn'] = [SHAPES_PATH + '/bpmn/mxBpmnShape2.js', STENCIL_PATH + '/bpmn.xml'];
  4771. mxStencilRegistry.libraries['er'] = [SHAPES_PATH + '/er/mxER.js'];
  4772. mxStencilRegistry.libraries['ios'] = [SHAPES_PATH + '/mockup/mxMockupiOS.js'];
  4773. mxStencilRegistry.libraries['rackGeneral'] = [SHAPES_PATH + '/rack/mxRack.js', STENCIL_PATH + '/rack/general.xml'];
  4774. mxStencilRegistry.libraries['rackF5'] = [STENCIL_PATH + '/rack/f5.xml'];
  4775. mxStencilRegistry.libraries['lean_mapping'] = [SHAPES_PATH + '/mxLeanMap.js', STENCIL_PATH + '/lean_mapping.xml'];
  4776. mxStencilRegistry.libraries['basic'] = [SHAPES_PATH + '/mxBasic.js', STENCIL_PATH + '/basic.xml'];
  4777. mxStencilRegistry.libraries['ios7icons'] = [STENCIL_PATH + '/ios7/icons.xml'];
  4778. mxStencilRegistry.libraries['ios7ui'] = [SHAPES_PATH + '/ios7/mxIOS7Ui.js', STENCIL_PATH + '/ios7/misc.xml'];
  4779. mxStencilRegistry.libraries['android'] = [SHAPES_PATH + '/mxAndroid.js', STENCIL_PATH + '/android/android.xml'];
  4780. mxStencilRegistry.libraries['eeLogicGates'] = [STENCIL_PATH + '/electrical/logic_gates.xml'];
  4781. mxStencilRegistry.libraries['eeResistors'] = [STENCIL_PATH + '/electrical/resistors.xml'];
  4782. mxStencilRegistry.libraries['eeCapacitors'] = [STENCIL_PATH + '/electrical/capacitors.xml'];
  4783. mxStencilRegistry.libraries['eeInductors'] = [STENCIL_PATH + '/electrical/inductors.xml'];
  4784. mxStencilRegistry.libraries['eeSwitchesRelays'] = [STENCIL_PATH + '/electrical/switchesRelays.xml', STENCIL_PATH + '/electrical/electro-mechanical.xml'];
  4785. mxStencilRegistry.libraries['eeDiodes'] = [STENCIL_PATH + '/electrical/diodes.xml'];
  4786. mxStencilRegistry.libraries['eeSources'] = [STENCIL_PATH + '/electrical/signal_sources.xml'];
  4787. mxStencilRegistry.libraries['eeTransistors'] = [STENCIL_PATH + '/electrical/mosfets1.xml', STENCIL_PATH + '/electrical/mosfets2.xml', STENCIL_PATH + '/electrical/transistors.xml'];
  4788. mxStencilRegistry.libraries['eeMisc'] = [STENCIL_PATH + '/electrical/electro-mechanical.xml', STENCIL_PATH + '/electrical/miscellaneous.xml'];
  4789. mxStencilRegistry.libraries['eeAudio'] = [STENCIL_PATH + '/electrical/radio.xml'];
  4790. mxStencilRegistry.libraries['eePlcLadder'] = [STENCIL_PATH + '/electrical/plc_ladder.xml'];
  4791. mxStencilRegistry.libraries['eeAbstract'] = [STENCIL_PATH + '/electrical/abstract.xml', STENCIL_PATH + '/electrical/logic_gates.xml'];
  4792. mxStencilRegistry.libraries['eeOptical'] = [STENCIL_PATH + '/electrical/opto_electronics.xml'];
  4793. mxStencilRegistry.libraries['eeVacuumTubes'] = [STENCIL_PATH + '/electrical/vacuum_tubes.xml'];
  4794. mxStencilRegistry.libraries['eeWaveforms'] = [STENCIL_PATH + '/electrical/waveforms.xml'];
  4795. mxStencilRegistry.libraries['eeInstruments'] = [STENCIL_PATH + '/electrical/instruments.xml'];
  4796. mxStencilRegistry.libraries['mscae/cloud'] = [STENCIL_PATH + '/mscae/cloud.xml'];
  4797. mxStencilRegistry.libraries['mockup/buttons'] = [SHAPES_PATH + '/mockup/mxMockupButtons.js'];
  4798. mxStencilRegistry.libraries['mockup/containers'] = [SHAPES_PATH + '/mockup/mxMockupContainers.js'];
  4799. mxStencilRegistry.libraries['mockup/forms'] = [SHAPES_PATH + '/mockup/mxMockupForms.js'];
  4800. mxStencilRegistry.libraries['mockup/graphics'] = [SHAPES_PATH + '/mockup/mxMockupGraphics.js', STENCIL_PATH + '/mockup/misc.xml'];
  4801. mxStencilRegistry.libraries['mockup/markup'] = [SHAPES_PATH + '/mockup/mxMockupMarkup.js'];
  4802. mxStencilRegistry.libraries['mockup/misc'] = [SHAPES_PATH + '/mockup/mxMockupMisc.js', STENCIL_PATH + '/mockup/misc.xml'];
  4803. mxStencilRegistry.libraries['mockup/navigation'] = [SHAPES_PATH + '/mockup/mxMockupNavigation.js', STENCIL_PATH + '/mockup/misc.xml'];
  4804. mxStencilRegistry.libraries['mockup/text'] = [SHAPES_PATH + '/mockup/mxMockupText.js'];
  4805. // Required to avoid 404 for mockup.xml since naming of mxgraph.mockup.anchor does not contain
  4806. // buttons even though it is defined in the mxMockupButtons.js file. This could only be fixed
  4807. // with aliases for existing shapes or aliases for basenames, but this is essentially the same.
  4808. mxStencilRegistry.libraries['mockup'] = [SHAPES_PATH + '/mockup/mxMockupButtons.js'];
  4809. mxStencilRegistry.libraries['pid2inst'] = [SHAPES_PATH + '/pid2/mxPidInstruments.js'];
  4810. mxStencilRegistry.libraries['pid2misc'] = [SHAPES_PATH + '/pid2/mxPidMisc.js', STENCIL_PATH + '/pid/misc.xml'];
  4811. mxStencilRegistry.libraries['pid2valves'] = [SHAPES_PATH + '/pid2/mxPidValves.js'];
  4812. mxStencilRegistry.libraries['pidFlowSensors'] = [STENCIL_PATH + '/pid/flow_sensors.xml'];
  4813. mxStencilRegistry.libraries['floorplan'] = [SHAPES_PATH + '/mxFloorplan.js', STENCIL_PATH + '/floorplan.xml'];
  4814. mxStencilRegistry.libraries['bootstrap'] = [SHAPES_PATH + '/mxBootstrap.js', STENCIL_PATH + '/bootstrap.xml'];
  4815. mxStencilRegistry.libraries['gmdl'] = [SHAPES_PATH + '/mxGmdl.js', STENCIL_PATH + '/gmdl.xml'];
  4816. mxStencilRegistry.libraries['cabinets'] = [SHAPES_PATH + '/mxCabinets.js', STENCIL_PATH + '/cabinets.xml'];
  4817. mxStencilRegistry.libraries['citrix'] = [STENCIL_PATH + '/citrix.xml'];
  4818. mxStencilRegistry.libraries['archimate'] = [SHAPES_PATH + '/mxArchiMate.js'];
  4819. mxStencilRegistry.libraries['archimate3'] = [SHAPES_PATH + '/mxArchiMate3.js'];
  4820. mxStencilRegistry.libraries['sysml'] = [SHAPES_PATH + '/mxSysML.js'];
  4821. mxStencilRegistry.libraries['eip'] = [SHAPES_PATH + '/mxEip.js', STENCIL_PATH + '/eip.xml'];
  4822. mxStencilRegistry.libraries['networks'] = [SHAPES_PATH + '/mxNetworks.js', STENCIL_PATH + '/networks.xml'];
  4823. mxStencilRegistry.libraries['aws3d'] = [SHAPES_PATH + '/mxAWS3D.js', STENCIL_PATH + '/aws3d.xml'];
  4824. // Triggers dynamic loading for markers
  4825. mxMarker.getPackageForType = function(type)
  4826. {
  4827. var name = null;
  4828. if (type != null && type.length > 0)
  4829. {
  4830. if (type.substring(0, 2) == 'ER')
  4831. {
  4832. name = 'mxgraph.er';
  4833. }
  4834. else if (type.substring(0, 5) == 'sysML')
  4835. {
  4836. name = 'mxgraph.sysml';
  4837. }
  4838. }
  4839. return name;
  4840. };
  4841. var mxMarkerCreateMarker = mxMarker.createMarker;
  4842. mxMarker.createMarker = function(canvas, shape, type, pe, unitX, unitY, size, source, sw, filled)
  4843. {
  4844. if (type != null)
  4845. {
  4846. var f = mxMarker.markers[type];
  4847. if (f == null)
  4848. {
  4849. var name = this.getPackageForType(type);
  4850. if (name != null)
  4851. {
  4852. mxStencilRegistry.getStencil(name);
  4853. }
  4854. }
  4855. }
  4856. return mxMarkerCreateMarker.apply(this, arguments);
  4857. };
  4858. })();