App.js 185 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036303730383039304030413042304330443045304630473048304930503051305230533054305530563057305830593060306130623063306430653066306730683069307030713072307330743075307630773078307930803081308230833084308530863087308830893090309130923093309430953096309730983099310031013102310331043105310631073108310931103111311231133114311531163117311831193120312131223123312431253126312731283129313031313132313331343135313631373138313931403141314231433144314531463147314831493150315131523153315431553156315731583159316031613162316331643165316631673168316931703171317231733174317531763177317831793180318131823183318431853186318731883189319031913192319331943195319631973198319932003201320232033204320532063207320832093210321132123213321432153216321732183219322032213222322332243225322632273228322932303231323232333234323532363237323832393240324132423243324432453246324732483249325032513252325332543255325632573258325932603261326232633264326532663267326832693270327132723273327432753276327732783279328032813282328332843285328632873288328932903291329232933294329532963297329832993300330133023303330433053306330733083309331033113312331333143315331633173318331933203321332233233324332533263327332833293330333133323333333433353336333733383339334033413342334333443345334633473348334933503351335233533354335533563357335833593360336133623363336433653366336733683369337033713372337333743375337633773378337933803381338233833384338533863387338833893390339133923393339433953396339733983399340034013402340334043405340634073408340934103411341234133414341534163417341834193420342134223423342434253426342734283429343034313432343334343435343634373438343934403441344234433444344534463447344834493450345134523453345434553456345734583459346034613462346334643465346634673468346934703471347234733474347534763477347834793480348134823483348434853486348734883489349034913492349334943495349634973498349935003501350235033504350535063507350835093510351135123513351435153516351735183519352035213522352335243525352635273528352935303531353235333534353535363537353835393540354135423543354435453546354735483549355035513552355335543555355635573558355935603561356235633564356535663567356835693570357135723573357435753576357735783579358035813582358335843585358635873588358935903591359235933594359535963597359835993600360136023603360436053606360736083609361036113612361336143615361636173618361936203621362236233624362536263627362836293630363136323633363436353636363736383639364036413642364336443645364636473648364936503651365236533654365536563657365836593660366136623663366436653666366736683669367036713672367336743675367636773678367936803681368236833684368536863687368836893690369136923693369436953696369736983699370037013702370337043705370637073708370937103711371237133714371537163717371837193720372137223723372437253726372737283729373037313732373337343735373637373738373937403741374237433744374537463747374837493750375137523753375437553756375737583759376037613762376337643765376637673768376937703771377237733774377537763777377837793780378137823783378437853786378737883789379037913792379337943795379637973798379938003801380238033804380538063807380838093810381138123813381438153816381738183819382038213822382338243825382638273828382938303831383238333834383538363837383838393840384138423843384438453846384738483849385038513852385338543855385638573858385938603861386238633864386538663867386838693870387138723873387438753876387738783879388038813882388338843885388638873888388938903891389238933894389538963897389838993900390139023903390439053906390739083909391039113912391339143915391639173918391939203921392239233924392539263927392839293930393139323933393439353936393739383939394039413942394339443945394639473948394939503951395239533954395539563957395839593960396139623963396439653966396739683969397039713972397339743975397639773978397939803981398239833984398539863987398839893990399139923993399439953996399739983999400040014002400340044005400640074008400940104011401240134014401540164017401840194020402140224023402440254026402740284029403040314032403340344035403640374038403940404041404240434044404540464047404840494050405140524053405440554056405740584059406040614062406340644065406640674068406940704071407240734074407540764077407840794080408140824083408440854086408740884089409040914092409340944095409640974098409941004101410241034104410541064107410841094110411141124113411441154116411741184119412041214122412341244125412641274128412941304131413241334134413541364137413841394140414141424143414441454146414741484149415041514152415341544155415641574158415941604161416241634164416541664167416841694170417141724173417441754176417741784179418041814182418341844185418641874188418941904191419241934194419541964197419841994200420142024203420442054206420742084209421042114212421342144215421642174218421942204221422242234224422542264227422842294230423142324233423442354236423742384239424042414242424342444245424642474248424942504251425242534254425542564257425842594260426142624263426442654266426742684269427042714272427342744275427642774278427942804281428242834284428542864287428842894290429142924293429442954296429742984299430043014302430343044305430643074308430943104311431243134314431543164317431843194320432143224323432443254326432743284329433043314332433343344335433643374338433943404341434243434344434543464347434843494350435143524353435443554356435743584359436043614362436343644365436643674368436943704371437243734374437543764377437843794380438143824383438443854386438743884389439043914392439343944395439643974398439944004401440244034404440544064407440844094410441144124413441444154416441744184419442044214422442344244425442644274428442944304431443244334434443544364437443844394440444144424443444444454446444744484449445044514452445344544455445644574458445944604461446244634464446544664467446844694470447144724473447444754476447744784479448044814482448344844485448644874488448944904491449244934494449544964497449844994500450145024503450445054506450745084509451045114512451345144515451645174518451945204521452245234524452545264527452845294530453145324533453445354536453745384539454045414542454345444545454645474548454945504551455245534554455545564557455845594560456145624563456445654566456745684569457045714572457345744575457645774578457945804581458245834584458545864587458845894590459145924593459445954596459745984599460046014602460346044605460646074608460946104611461246134614461546164617461846194620462146224623462446254626462746284629463046314632463346344635463646374638463946404641464246434644464546464647464846494650465146524653465446554656465746584659466046614662466346644665466646674668466946704671467246734674467546764677467846794680468146824683468446854686468746884689469046914692469346944695469646974698469947004701470247034704470547064707470847094710471147124713471447154716471747184719472047214722472347244725472647274728472947304731473247334734473547364737473847394740474147424743474447454746474747484749475047514752475347544755475647574758475947604761476247634764476547664767476847694770477147724773477447754776477747784779478047814782478347844785478647874788478947904791479247934794479547964797479847994800480148024803480448054806480748084809481048114812481348144815481648174818481948204821482248234824482548264827482848294830483148324833483448354836483748384839484048414842484348444845484648474848484948504851485248534854485548564857485848594860486148624863486448654866486748684869487048714872487348744875487648774878487948804881488248834884488548864887488848894890489148924893489448954896489748984899490049014902490349044905490649074908490949104911491249134914491549164917491849194920492149224923492449254926492749284929493049314932493349344935493649374938493949404941494249434944494549464947494849494950495149524953495449554956495749584959496049614962496349644965496649674968496949704971497249734974497549764977497849794980498149824983498449854986498749884989499049914992499349944995499649974998499950005001500250035004500550065007500850095010501150125013501450155016501750185019502050215022502350245025502650275028502950305031503250335034503550365037503850395040504150425043504450455046504750485049505050515052505350545055505650575058505950605061506250635064506550665067506850695070507150725073507450755076507750785079508050815082508350845085508650875088508950905091509250935094509550965097509850995100510151025103510451055106510751085109511051115112511351145115511651175118511951205121512251235124512551265127512851295130513151325133513451355136513751385139514051415142514351445145514651475148514951505151515251535154515551565157515851595160516151625163516451655166516751685169517051715172517351745175517651775178517951805181518251835184518551865187518851895190519151925193519451955196519751985199520052015202520352045205520652075208520952105211521252135214521552165217521852195220522152225223522452255226522752285229523052315232523352345235523652375238523952405241524252435244524552465247524852495250525152525253525452555256525752585259526052615262526352645265526652675268526952705271527252735274527552765277527852795280528152825283528452855286528752885289529052915292529352945295529652975298529953005301530253035304530553065307530853095310531153125313531453155316531753185319532053215322532353245325532653275328532953305331533253335334533553365337533853395340534153425343534453455346534753485349535053515352535353545355535653575358535953605361536253635364536553665367536853695370537153725373537453755376537753785379538053815382538353845385538653875388538953905391539253935394539553965397539853995400540154025403540454055406540754085409541054115412541354145415541654175418541954205421542254235424542554265427542854295430543154325433543454355436543754385439544054415442544354445445544654475448544954505451545254535454545554565457545854595460546154625463546454655466546754685469547054715472547354745475547654775478547954805481548254835484548554865487548854895490549154925493549454955496549754985499550055015502550355045505550655075508550955105511551255135514551555165517551855195520552155225523552455255526552755285529553055315532553355345535553655375538553955405541554255435544554555465547554855495550555155525553555455555556555755585559556055615562556355645565556655675568556955705571557255735574557555765577557855795580558155825583558455855586558755885589559055915592559355945595559655975598559956005601560256035604560556065607560856095610561156125613561456155616561756185619562056215622562356245625562656275628562956305631563256335634563556365637563856395640564156425643564456455646564756485649565056515652565356545655565656575658565956605661566256635664566556665667566856695670567156725673567456755676567756785679568056815682568356845685568656875688568956905691569256935694569556965697569856995700570157025703570457055706570757085709571057115712571357145715571657175718571957205721572257235724572557265727572857295730573157325733573457355736573757385739574057415742574357445745574657475748574957505751575257535754575557565757575857595760576157625763576457655766576757685769577057715772577357745775577657775778577957805781578257835784578557865787578857895790579157925793579457955796579757985799580058015802580358045805580658075808580958105811581258135814581558165817581858195820582158225823582458255826582758285829583058315832583358345835583658375838583958405841584258435844584558465847584858495850585158525853585458555856585758585859586058615862586358645865586658675868586958705871587258735874587558765877587858795880588158825883588458855886588758885889589058915892589358945895589658975898589959005901590259035904590559065907590859095910591159125913591459155916591759185919592059215922592359245925592659275928592959305931593259335934593559365937593859395940594159425943594459455946594759485949595059515952595359545955595659575958595959605961596259635964596559665967596859695970597159725973597459755976597759785979598059815982598359845985598659875988598959905991599259935994599559965997599859996000600160026003600460056006600760086009601060116012601360146015601660176018601960206021602260236024602560266027602860296030603160326033603460356036603760386039604060416042604360446045604660476048604960506051605260536054605560566057605860596060606160626063606460656066606760686069607060716072607360746075607660776078607960806081608260836084608560866087608860896090609160926093609460956096609760986099610061016102610361046105610661076108610961106111611261136114611561166117611861196120612161226123612461256126612761286129613061316132613361346135613661376138613961406141614261436144614561466147614861496150615161526153615461556156615761586159616061616162616361646165616661676168616961706171617261736174617561766177617861796180618161826183618461856186618761886189619061916192619361946195619661976198619962006201620262036204620562066207620862096210621162126213621462156216621762186219622062216222622362246225622662276228622962306231623262336234623562366237623862396240624162426243624462456246624762486249625062516252625362546255625662576258625962606261626262636264626562666267626862696270627162726273627462756276627762786279628062816282628362846285628662876288628962906291629262936294629562966297629862996300630163026303630463056306630763086309631063116312631363146315631663176318631963206321632263236324632563266327632863296330633163326333633463356336633763386339634063416342634363446345634663476348634963506351635263536354635563566357635863596360636163626363636463656366636763686369637063716372637363746375637663776378637963806381638263836384638563866387638863896390639163926393639463956396639763986399640064016402640364046405640664076408640964106411641264136414641564166417641864196420642164226423642464256426642764286429643064316432643364346435643664376438643964406441644264436444644564466447644864496450645164526453645464556456645764586459646064616462646364646465646664676468646964706471647264736474647564766477647864796480648164826483648464856486648764886489649064916492649364946495649664976498649965006501650265036504650565066507650865096510651165126513651465156516651765186519652065216522652365246525652665276528652965306531653265336534653565366537653865396540654165426543654465456546654765486549655065516552655365546555655665576558655965606561656265636564656565666567656865696570657165726573657465756576657765786579658065816582658365846585658665876588658965906591659265936594659565966597659865996600660166026603660466056606660766086609661066116612661366146615661666176618661966206621662266236624662566266627662866296630663166326633663466356636663766386639664066416642664366446645664666476648664966506651665266536654665566566657665866596660666166626663666466656666666766686669667066716672667366746675667666776678667966806681668266836684668566866687668866896690669166926693669466956696669766986699670067016702670367046705670667076708670967106711671267136714671567166717671867196720672167226723672467256726672767286729673067316732673367346735673667376738673967406741674267436744674567466747674867496750675167526753675467556756675767586759676067616762676367646765676667676768676967706771677267736774677567766777677867796780678167826783678467856786678767886789679067916792679367946795679667976798679968006801680268036804680568066807680868096810681168126813681468156816681768186819682068216822682368246825682668276828682968306831683268336834683568366837683868396840684168426843684468456846684768486849685068516852685368546855685668576858685968606861686268636864686568666867686868696870687168726873687468756876687768786879688068816882688368846885688668876888688968906891689268936894
  1. /**
  2. * Copyright (c) 2006-2020, JGraph Ltd
  3. * Copyright (c) 2006-2020, draw.io AG
  4. */
  5. /**
  6. * Constructs a new point for the optional x and y coordinates. If no
  7. * coordinates are given, then the default values for <x> and <y> are used.
  8. * @constructor
  9. * @class Implements a basic 2D point. Known subclassers = {@link mxRectangle}.
  10. * @param {number} x X-coordinate of the point.
  11. * @param {number} y Y-coordinate of the point.
  12. */
  13. App = function(editor, container, lightbox)
  14. {
  15. EditorUi.call(this, editor, container, (lightbox != null) ? lightbox :
  16. (urlParams['lightbox'] == '1' || (uiTheme == 'min' &&
  17. urlParams['chrome'] != '0')));
  18. // Logs unloading of window with modifications for Google Drive file
  19. if (!mxClient.IS_CHROMEAPP && !EditorUi.isElectronApp)
  20. {
  21. window.onunload = mxUtils.bind(this, function()
  22. {
  23. var file = this.getCurrentFile();
  24. if (file != null && file.isModified())
  25. {
  26. var evt = {category: 'DISCARD-FILE-' + file.getHash(),
  27. action: ((file.savingFile) ? 'saving' : '') +
  28. ((file.savingFile && file.savingFileTime != null) ? '_' +
  29. Math.round((Date.now() - file.savingFileTime.getTime()) / 1000) : '') +
  30. ((file.saveLevel != null) ? ('-sl_' + file.saveLevel) : '') +
  31. '-age_' + ((file.ageStart != null) ? Math.round((Date.now() - file.ageStart.getTime()) / 1000) : 'x') +
  32. ((this.editor.autosave) ? '' : '-nosave') +
  33. ((file.isAutosave()) ? '' : '-noauto') +
  34. '-open_' + ((file.opened != null) ? Math.round((Date.now() - file.opened.getTime()) / 1000) : 'x') +
  35. '-save_' + ((file.lastSaved != null) ? Math.round((Date.now() - file.lastSaved.getTime()) / 1000) : 'x') +
  36. '-change_' + ((file.lastChanged != null) ? Math.round((Date.now() - file.lastChanged.getTime()) / 1000) : 'x') +
  37. '-alive_' + Math.round((Date.now() - App.startTime.getTime()) / 1000),
  38. label: (file.sync != null) ? ('client_' + file.sync.clientId) : 'nosync'};
  39. if (file.constructor == DriveFile && file.desc != null && this.drive != null)
  40. {
  41. evt.label += ((this.drive.user != null) ? ('-user_' + this.drive.user.id) : '-nouser') + '-rev_' +
  42. file.desc.headRevisionId + '-mod_' + file.desc.modifiedDate + '-size_' + file.getSize() +
  43. '-mime_' + file.desc.mimeType;
  44. }
  45. EditorUi.logEvent(evt);
  46. }
  47. });
  48. }
  49. // Logs changes to autosave
  50. this.editor.addListener('autosaveChanged', mxUtils.bind(this, function()
  51. {
  52. var file = this.getCurrentFile();
  53. if (file != null)
  54. {
  55. EditorUi.logEvent({category: ((this.editor.autosave) ? 'ON' : 'OFF') +
  56. '-AUTOSAVE-FILE-' + file.getHash(), action: 'changed',
  57. label: 'autosave_' + ((this.editor.autosave) ? 'on' : 'off')});
  58. }
  59. }));
  60. // Pre-fetches images
  61. if (mxClient.IS_SVG)
  62. {
  63. mxGraph.prototype.warningImage.src = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAE7SURBVHjaYvz//z8DJQAggBjwGXDuHMP/tWuD/uPTCxBAOA0AaQRK/f/+XeJ/cbHlf1wGAAQQTgPu3QNLgfHSpZo4DQAIIKwGwGyH4e/fFbG6AiQJEEAs2Ew2NFzH8OOHBMO6dT/A/KCg7wxGRh+wuhQggDBcALMdFIAcHBxgDGJjcwVIIUAAYbhAUXEdVos4OO4DXcGBIQ4QQCguQPY7sgtgAYruCpAgQACx4LJdU1OCwctLEcyWlLwPJF+AXQE0EMUBAAEEdwF6yMOiD4RRY0QT7gqQAEAAseDzu6XldYYPH9DD4joQa8L5AAEENgWb7SBcXa0JDQMBrK4AcQACiAlfyOMCEFdAnAYQQEz4FLa0XGf4/v0H0IIPONUABBAjyBmMjIwMS5cK/L927QORbtBkaG29DtYLEGAAH6f7oq3Zc+kAAAAASUVORK5CYII=';
  64. }
  65. else
  66. {
  67. var img = new Image();
  68. img.src = mxGraph.prototype.warningImage.src;
  69. }
  70. // Global helper method to deal with popup blockers
  71. window.openWindow = mxUtils.bind(this, function(url, pre, fallback)
  72. {
  73. var wnd = null;
  74. try
  75. {
  76. wnd = window.open(url);
  77. }
  78. catch (e)
  79. {
  80. // ignore
  81. }
  82. if (wnd == null || wnd === undefined)
  83. {
  84. this.showDialog(new PopupDialog(this, url, pre, fallback).container, 320, 140, true, true);
  85. }
  86. else if (pre != null)
  87. {
  88. pre();
  89. }
  90. });
  91. // Initial state for toolbar items is disabled
  92. this.updateDocumentTitle();
  93. this.updateUi();
  94. // Global helper method to display error messages
  95. window.showOpenAlert = mxUtils.bind(this, function(message)
  96. {
  97. // Cancel must be called before showing error message
  98. if (window.openFile != null)
  99. {
  100. window.openFile.cancel(true);
  101. }
  102. this.handleError(message);
  103. });
  104. // Handles opening files via drag and drop
  105. if (!this.editor.chromeless || this.editor.editable)
  106. {
  107. this.addFileDropHandler([document]);
  108. }
  109. // Process the queue for waiting plugins
  110. if (App.DrawPlugins != null)
  111. {
  112. for (var i = 0; i < App.DrawPlugins.length; i++)
  113. {
  114. try
  115. {
  116. App.DrawPlugins[i](this);
  117. }
  118. catch (e)
  119. {
  120. if (window.console != null)
  121. {
  122. console.log('Plugin Error:', e, App.DrawPlugins[i]);
  123. }
  124. }
  125. finally
  126. {
  127. App.embedModePluginsCount--;
  128. this.initializeEmbedMode();
  129. }
  130. }
  131. // Installs global callback for plugins
  132. window.Draw.loadPlugin = mxUtils.bind(this, function(callback)
  133. {
  134. try
  135. {
  136. callback(this);
  137. }
  138. finally
  139. {
  140. App.embedModePluginsCount--;
  141. this.initializeEmbedMode();
  142. }
  143. });
  144. //Set a timeout in case a plugin doesn't load quickly or doesn't load at all
  145. setTimeout(mxUtils.bind(this, function()
  146. {
  147. //Force finish loading if its not yet called
  148. if (App.embedModePluginsCount > 0)
  149. {
  150. App.embedModePluginsCount = 0;
  151. this.initializeEmbedMode();
  152. }
  153. }), 5000); //5 sec timeout
  154. }
  155. this.load();
  156. };
  157. /**
  158. * Timeout error
  159. */
  160. App.ERROR_TIMEOUT = 'timeout';
  161. /**
  162. * Busy error
  163. */
  164. App.ERROR_BUSY = 'busy';
  165. /**
  166. * Unknown error
  167. */
  168. App.ERROR_UNKNOWN = 'unknown';
  169. /**
  170. * Google drive mode
  171. */
  172. App.MODE_GOOGLE = 'google';
  173. /**
  174. * Dropbox mode
  175. */
  176. App.MODE_DROPBOX = 'dropbox';
  177. /**
  178. * OneDrive Mode
  179. */
  180. App.MODE_ONEDRIVE = 'onedrive';
  181. /**
  182. * Github Mode
  183. */
  184. App.MODE_GITHUB = 'github';
  185. /**
  186. * Gitlab mode
  187. */
  188. App.MODE_GITLAB = 'gitlab';
  189. /**
  190. * Device Mode
  191. */
  192. App.MODE_DEVICE = 'device';
  193. /**
  194. * Browser Mode
  195. */
  196. App.MODE_BROWSER = 'browser';
  197. /**
  198. * Trello App Mode
  199. */
  200. App.MODE_TRELLO = 'trello';
  201. /**
  202. * Embed App Mode
  203. */
  204. App.MODE_EMBED = 'embed';
  205. /**
  206. * Sets the delay for autosave in milliseconds. Default is 2000.
  207. */
  208. App.DROPBOX_APPKEY = 'libwls2fa9szdji';
  209. /**
  210. * Sets URL to load the Dropbox SDK from
  211. */
  212. App.DROPBOX_URL = window.DRAWIO_BASE_URL + '/js/dropbox/Dropbox-sdk.min.js';
  213. /**
  214. * Sets URL to load the Dropbox dropins JS from.
  215. */
  216. App.DROPINS_URL = 'https://www.dropbox.com/static/api/2/dropins.js';
  217. /**
  218. * OneDrive Client JS (file/folder picker). This is a slightly modified version to allow using accessTokens
  219. * But it doesn't work for IE11, so we fallback to the original one
  220. */
  221. App.ONEDRIVE_URL = mxClient.IS_IE11? 'https://js.live.net/v7.2/OneDrive.js' : window.DRAWIO_BASE_URL + '/js/onedrive/OneDrive.js';
  222. /**
  223. * Trello URL
  224. */
  225. App.TRELLO_URL = 'https://api.trello.com/1/client.js';
  226. /**
  227. * Trello JQuery dependency
  228. */
  229. App.TRELLO_JQUERY_URL = 'https://code.jquery.com/jquery-1.7.1.min.js';
  230. /**
  231. * Specifies the key for the pusher project.
  232. */
  233. App.PUSHER_KEY = '1e756b07a690c5bdb054';
  234. /**
  235. * Specifies the key for the pusher project.
  236. */
  237. App.PUSHER_CLUSTER = 'eu';
  238. /**
  239. * Specifies the URL for the pusher API.
  240. */
  241. App.PUSHER_URL = 'https://js.pusher.com/4.3/pusher.min.js';
  242. /**
  243. * Google APIs to load. The realtime API is needed to notify collaborators of conversion
  244. * of the realtime files, but after Dec 11 it's read-only and hence no longer needed.
  245. */
  246. App.GOOGLE_APIS = 'drive-share';
  247. /**
  248. * Function: authorize
  249. *
  250. * Authorizes the client, gets the userId and calls <open>.
  251. */
  252. App.startTime = new Date();
  253. /**
  254. * Defines plugin IDs for loading via p URL parameter. Update the table at
  255. * https://desk.draw.io/solution/articles/16000042546
  256. */
  257. App.pluginRegistry = {'4xAKTrabTpTzahoLthkwPNUn': '/plugins/explore.js',
  258. 'ex': '/plugins/explore.js', 'p1': '/plugins/p1.js',
  259. 'ac': '/plugins/connect.js', 'acj': '/plugins/connectJira.js',
  260. 'ac148': '/plugins/cConf-1-4-8.js', 'ac148cmnt': '/plugins/cConf-comments.js',
  261. 'voice': '/plugins/voice.js',
  262. 'tips': '/plugins/tooltips.js', 'svgdata': '/plugins/svgdata.js',
  263. 'electron': 'plugins/electron.js',
  264. 'number': '/plugins/number.js', 'sql': '/plugins/sql.js',
  265. 'props': '/plugins/props.js', 'text': '/plugins/text.js',
  266. 'anim': '/plugins/animation.js', 'update': '/plugins/update.js',
  267. 'trees': '/plugins/trees/trees.js', 'import': '/plugins/import.js',
  268. 'replay': '/plugins/replay.js', 'anon': '/plugins/anonymize.js',
  269. 'tr': '/plugins/trello.js', 'f5': '/plugins/rackF5.js',
  270. 'tickets': '/plugins/tickets.js', 'flow': '/plugins/flow.js',
  271. 'webcola': '/plugins/webcola/webcola.js', 'rnd': '/plugins/random.js',
  272. 'page': '/plugins/page.js', 'gd': '/plugins/googledrive.js',
  273. 'tags': '/plugins/tags.js'};
  274. App.publicPlugin = [
  275. 'ex',
  276. 'voice',
  277. 'tips',
  278. 'svgdata',
  279. 'number',
  280. 'sql',
  281. 'props',
  282. 'text',
  283. 'anim',
  284. 'update',
  285. 'trees',
  286. // 'import',
  287. 'replay',
  288. 'anon',
  289. 'tickets',
  290. 'flow',
  291. 'webcola',
  292. // 'rnd', 'page', 'gd',
  293. 'tags'
  294. ];
  295. /**
  296. * Function: authorize
  297. *
  298. * Authorizes the client, gets the userId and calls <open>.
  299. */
  300. App.getStoredMode = function()
  301. {
  302. var mode = null;
  303. if (mode == null && isLocalStorage)
  304. {
  305. mode = localStorage.getItem('.mode');
  306. }
  307. if (mode == null && typeof(Storage) != 'undefined')
  308. {
  309. var cookies = document.cookie.split(";");
  310. for (var i = 0; i < cookies.length; i++)
  311. {
  312. // Removes spaces around cookie
  313. var cookie = mxUtils.trim(cookies[i]);
  314. if (cookie.substring(0, 5) == 'MODE=')
  315. {
  316. mode = cookie.substring(5);
  317. break;
  318. }
  319. }
  320. if (mode != null && isLocalStorage)
  321. {
  322. // Moves to local storage
  323. var expiry = new Date();
  324. expiry.setYear(expiry.getFullYear() - 1);
  325. document.cookie = 'MODE=; expires=' + expiry.toUTCString();
  326. localStorage.setItem('.mode', mode);
  327. }
  328. }
  329. return mode;
  330. };
  331. /**
  332. * Static Application initializer executed at load-time.
  333. */
  334. (function()
  335. {
  336. if (!mxClient.IS_CHROMEAPP)
  337. {
  338. if (urlParams['offline'] != '1')
  339. {
  340. // Switches to dropbox mode for db.draw.io
  341. if (window.location.hostname == 'db.draw.io' && urlParams['mode'] == null)
  342. {
  343. urlParams['mode'] = 'dropbox';
  344. }
  345. App.mode = urlParams['mode'];
  346. }
  347. if (App.mode == null)
  348. {
  349. // Stored mode overrides preferred mode
  350. App.mode = App.getStoredMode();
  351. }
  352. /**
  353. * Lazy loading backends.
  354. */
  355. if (window.mxscript != null)
  356. {
  357. // Loads gapi for all browsers but IE8 and below if not disabled or if enabled and in embed mode
  358. if (urlParams['embed'] != '1')
  359. {
  360. if (typeof window.DriveClient === 'function')
  361. {
  362. if (urlParams['gapi'] != '0' && isSvgBrowser &&
  363. (document.documentMode == null || document.documentMode >= 10))
  364. {
  365. // Immediately loads client
  366. if (App.mode == App.MODE_GOOGLE || (urlParams['state'] != null &&
  367. window.location.hash == '') || (window.location.hash != null &&
  368. window.location.hash.substring(0, 2) == '#G'))
  369. {
  370. mxscript('https://apis.google.com/js/api.js');
  371. }
  372. // Keeps lazy loading for fallback to authenticated Google file if not public in loadFile
  373. else if (urlParams['chrome'] == '0' && (window.location.hash == null ||
  374. window.location.hash.substring(0, 45) !== '#Uhttps%3A%2F%2Fdrive.google.com%2Fuc%3Fid%3D'))
  375. {
  376. // Disables loading of client
  377. window.DriveClient = null;
  378. }
  379. }
  380. else
  381. {
  382. // Disables loading of client
  383. window.DriveClient = null;
  384. }
  385. }
  386. // Loads dropbox for all browsers but IE8 and below (no CORS) if not disabled or if enabled and in embed mode
  387. // KNOWN: Picker does not work in IE11 (https://dropbox.zendesk.com/requests/1650781)
  388. if (typeof window.DropboxClient === 'function')
  389. {
  390. if (urlParams['db'] != '0' && isSvgBrowser &&
  391. (document.documentMode == null || document.documentMode > 9))
  392. {
  393. // Immediately loads client
  394. if (App.mode == App.MODE_DROPBOX || (window.location.hash != null &&
  395. window.location.hash.substring(0, 2) == '#D'))
  396. {
  397. mxscript(App.DROPBOX_URL, function()
  398. {
  399. // Must load this after the dropbox SDK since they use the same namespace
  400. mxscript(App.DROPINS_URL, null, 'dropboxjs', App.DROPBOX_APPKEY);
  401. });
  402. }
  403. else if (urlParams['chrome'] == '0')
  404. {
  405. window.DropboxClient = null;
  406. }
  407. }
  408. else
  409. {
  410. // Disables loading of client
  411. window.DropboxClient = null;
  412. }
  413. }
  414. // Loads OneDrive for all browsers but IE6/IOS if not disabled or if enabled and in embed mode
  415. if (typeof window.OneDriveClient === 'function')
  416. {
  417. if (urlParams['od'] != '0' && (navigator.userAgent == null ||
  418. navigator.userAgent.indexOf('MSIE') < 0 || document.documentMode >= 10))
  419. {
  420. // Immediately loads client
  421. if (App.mode == App.MODE_ONEDRIVE || (window.location.hash != null &&
  422. window.location.hash.substring(0, 2) == '#W'))
  423. {
  424. mxscript(App.ONEDRIVE_URL);
  425. }
  426. else if (urlParams['chrome'] == '0')
  427. {
  428. window.OneDriveClient = null;
  429. }
  430. }
  431. else
  432. {
  433. // Disables loading of client
  434. window.OneDriveClient = null;
  435. }
  436. }
  437. // Loads Trello for all browsers but < IE10 if not disabled or if enabled and in embed mode
  438. if (typeof window.TrelloClient === 'function')
  439. {
  440. if (urlParams['tr'] != '0' && isSvgBrowser &&
  441. (document.documentMode == null || document.documentMode >= 10))
  442. {
  443. // Immediately loads client
  444. if (App.mode == App.MODE_TRELLO || (window.location.hash != null &&
  445. window.location.hash.substring(0, 2) == '#T'))
  446. {
  447. mxscript(App.TRELLO_JQUERY_URL, function()
  448. {
  449. mxscript(App.TRELLO_URL);
  450. });
  451. }
  452. else if (urlParams['chrome'] == '0')
  453. {
  454. window.TrelloClient = null;
  455. }
  456. }
  457. else
  458. {
  459. // Disables loading of client
  460. window.TrelloClient = null;
  461. }
  462. }
  463. }
  464. // Loads JSON for older browsers
  465. if (typeof(JSON) == 'undefined')
  466. {
  467. mxscript('js/json/json2.min.js');
  468. }
  469. }
  470. }
  471. })();
  472. /**
  473. * Program flow starts here.
  474. *
  475. * Optional callback is called with the app instance.
  476. */
  477. App.main = function(callback, createUi)
  478. {
  479. // Logs uncaught errors
  480. window.onerror = function(message, url, linenumber, colno, err)
  481. {
  482. EditorUi.logError('Global: ' + ((message != null) ? message : ''),
  483. url, linenumber, colno, err, null, true);
  484. };
  485. // Removes info text in embed mode
  486. if (urlParams['embed'] == '1' || urlParams['lightbox'] == '1')
  487. {
  488. var geInfo = document.getElementById('geInfo');
  489. if (geInfo != null)
  490. {
  491. geInfo.parentNode.removeChild(geInfo);
  492. }
  493. }
  494. // Redirects to the latest AWS icons
  495. if (document.referrer != null && urlParams['libs'] == 'aws3' &&
  496. document.referrer.substring(0, 42) == 'https://aws.amazon.com/architecture/icons/')
  497. {
  498. urlParams['libs'] = 'aws4';
  499. }
  500. if (window.mxscript != null)
  501. {
  502. // Checks for script content changes to avoid CSP errors in production
  503. if (urlParams['dev'] == '1' && CryptoJS != null)
  504. {
  505. var scripts = document.getElementsByTagName('script');
  506. // Checks bootstrap script
  507. if (scripts != null && scripts.length > 0)
  508. {
  509. var content = mxUtils.getTextContent(scripts[0]);
  510. if (CryptoJS.MD5(content).toString() != '6954d575e382bca7fc2090bf81ad77cf')
  511. {
  512. console.log('Change bootstrap script MD5 in the previous line:', CryptoJS.MD5(content).toString());
  513. alert('[Dev] Bootstrap script change requires update of CSP');
  514. }
  515. }
  516. // Checks main script
  517. if (scripts != null && scripts.length > 1)
  518. {
  519. var content = mxUtils.getTextContent(scripts[1]);
  520. if (CryptoJS.MD5(content).toString() != 'd41d8cd98f00b204e9800998ecf8427e')
  521. {
  522. console.log('Change main script MD5 in the previous line:', CryptoJS.MD5(content).toString());
  523. alert('[Dev] Main script change requires update of CSP');
  524. }
  525. }
  526. }
  527. // Runs as progressive web app if service workers are supported
  528. try
  529. {
  530. if ('serviceWorker' in navigator && (/.*\.diagrams\.net$/.test(window.location.hostname) ||
  531. /.*\.draw\.io$/.test(window.location.hostname) || urlParams['offline'] == '1'))
  532. {
  533. // Removes PWA cache on www.draw.io to force use of new domain via redirect
  534. if (urlParams['offline'] == '0' || /www\.draw\.io$/.test(window.location.hostname) ||
  535. (urlParams['offline'] != '1' && urlParams['dev'] == '1'))
  536. {
  537. navigator.serviceWorker.getRegistrations().then(function(registrations)
  538. {
  539. for(var i = 0; i < registrations.length; i++)
  540. {
  541. registrations[i].unregister();
  542. }
  543. });
  544. }
  545. else
  546. {
  547. mxscript('js/shapes.min.js', function()
  548. {
  549. mxscript('js/stencils.min.js', function()
  550. {
  551. mxscript('js/extensions.min.js');
  552. });
  553. });
  554. mxStencilRegistry.allowEval = false;
  555. // Use the window load event to keep the page load performant
  556. window.addEventListener('load', function()
  557. {
  558. navigator.serviceWorker.register('/service-worker.js');
  559. });
  560. }
  561. }
  562. }
  563. catch (e)
  564. {
  565. if (window.console != null)
  566. {
  567. console.error(e);
  568. }
  569. }
  570. // Loads Pusher API
  571. if (('ArrayBuffer' in window) && !mxClient.IS_CHROMEAPP && !EditorUi.isElectronApp &&
  572. DrawioFile.SYNC == 'auto' && (urlParams['embed'] != '1' || urlParams['embedRT'] == '1') && urlParams['local'] != '1' &&
  573. (urlParams['chrome'] != '0' || urlParams['rt'] == '1') &&
  574. urlParams['stealth'] != '1' && urlParams['offline'] != '1')
  575. {
  576. // TODO: Check if async loading is fast enough
  577. mxscript(App.PUSHER_URL);
  578. }
  579. // Loads plugins
  580. if (urlParams['plugins'] != '0' && urlParams['offline'] != '1')
  581. {
  582. // mxSettings is not yet initialized in configure mode, redirect parameter
  583. // to p URL parameter in caller for plugins in embed mode
  584. var plugins = (mxSettings.settings != null) ? mxSettings.getPlugins() : null;
  585. // Configured plugins in embed mode with configure=1 URL should be loaded so we
  586. // look ahead here and parse the config to fetch the list of custom plugins
  587. if (mxSettings.settings == null && isLocalStorage && typeof(JSON) !== 'undefined')
  588. {
  589. try
  590. {
  591. var temp = JSON.parse(localStorage.getItem(mxSettings.key));
  592. if (temp != null)
  593. {
  594. plugins = temp.plugins;
  595. }
  596. }
  597. catch (e)
  598. {
  599. // ignore
  600. }
  601. }
  602. var temp = urlParams['p'];
  603. App.initPluginCallback();
  604. if (temp != null)
  605. {
  606. // Mapping from key to URL in App.plugins
  607. App.loadPlugins(temp.split(';'));
  608. }
  609. if (plugins != null && plugins.length > 0 && urlParams['plugins'] != '0')
  610. {
  611. // Loading plugins inside the asynchronous block below stops the page from loading so a
  612. // hardcoded message for the warning dialog is used since the resources are loadd below
  613. var warning = 'The page has requested to load the following plugin(s):\n \n {1}\n \n Would you like to load these plugin(s) now?\n \n NOTE : Only allow plugins to run if you fully understand the security implications of doing so.\n';
  614. var tmp = window.location.protocol + '//' + window.location.host;
  615. var local = true;
  616. for (var i = 0; i < plugins.length && local; i++)
  617. {
  618. if (plugins[i].charAt(0) != '/' && plugins[i].substring(0, tmp.length) != tmp)
  619. {
  620. local = false;
  621. }
  622. }
  623. if (local || mxUtils.confirm(mxResources.replacePlaceholders(warning, [plugins.join('\n')]).replace(/\\n/g, '\n')))
  624. {
  625. for (var i = 0; i < plugins.length; i++)
  626. {
  627. try
  628. {
  629. if (App.pluginsLoaded[plugins[i]] == null)
  630. {
  631. App.pluginsLoaded[plugins[i]] = true;
  632. App.embedModePluginsCount++;
  633. if (plugins[i].charAt(0) == '/')
  634. {
  635. plugins[i] = PLUGINS_BASE_PATH + plugins[i];
  636. }
  637. mxscript(plugins[i]);
  638. }
  639. }
  640. catch (e)
  641. {
  642. // ignore
  643. }
  644. }
  645. }
  646. }
  647. }
  648. // Loads gapi for all browsers but IE8 and below if not disabled or if enabled and in embed mode
  649. // Special case: Cannot load in asynchronous code below
  650. if (typeof window.DriveClient === 'function' &&
  651. (typeof gapi === 'undefined' && (((urlParams['embed'] != '1' && urlParams['gapi'] != '0') ||
  652. (urlParams['embed'] == '1' && urlParams['gapi'] == '1')) && isSvgBrowser &&
  653. isLocalStorage && (document.documentMode == null || document.documentMode >= 10))))
  654. {
  655. mxscript('https://apis.google.com/js/api.js?onload=DrawGapiClientCallback', null, null, null, mxClient.IS_SVG);
  656. }
  657. // Disables client
  658. else if (typeof window.gapi === 'undefined')
  659. {
  660. window.DriveClient = null;
  661. }
  662. }
  663. /**
  664. * Asynchronous MathJax extension.
  665. */
  666. if (urlParams['math'] != '0')
  667. {
  668. Editor.initMath();
  669. }
  670. function doLoad(bundle)
  671. {
  672. // Prefetches asynchronous requests so that below code runs synchronous
  673. // Loading the correct bundle (one file) via the fallback system in mxResources. The stylesheet
  674. // is compiled into JS in the build process and is only needed for local development.
  675. mxUtils.getAll((urlParams['dev'] != '1') ? [bundle] : [bundle,
  676. STYLE_PATH + '/default.xml', STYLE_PATH + '/dark-default.xml'], function(xhr)
  677. {
  678. // Adds bundle text to resources
  679. mxResources.parse(xhr[0].getText());
  680. // Configuration mode
  681. if (isLocalStorage && localStorage != null && window.location.hash != null &&
  682. window.location.hash.substring(0, 9) == '#_CONFIG_')
  683. {
  684. try
  685. {
  686. var trustedPlugins = {};
  687. for (var key in App.pluginRegistry)
  688. {
  689. trustedPlugins[App.pluginRegistry[key]] = true;
  690. }
  691. // Only allows trusted plugins
  692. function checkPlugins(plugins)
  693. {
  694. if (plugins != null)
  695. {
  696. for (var i = 0; i < plugins.length; i++)
  697. {
  698. if (!trustedPlugins[plugins[i]])
  699. {
  700. throw new Error(mxResources.get('invalidInput') + ' "' + plugins[i]) + '"';
  701. }
  702. }
  703. }
  704. return true;
  705. };
  706. var value = JSON.parse(Graph.decompress(window.location.hash.substring(9)));
  707. if (value != null && checkPlugins(value.plugins))
  708. {
  709. EditorUi.debug('Setting configuration', JSON.stringify(value));
  710. if (confirm(mxResources.get('configLinkWarn')) &&
  711. confirm(mxResources.get('configLinkConfirm')))
  712. {
  713. localStorage.setItem('.configuration', JSON.stringify(value));
  714. window.location.hash = '';
  715. window.location.reload();
  716. }
  717. }
  718. window.location.hash = '';
  719. }
  720. catch (e)
  721. {
  722. window.location.hash = '';
  723. alert(e);
  724. }
  725. }
  726. // Prepares themes with mapping from old default-style to old XML file
  727. if (xhr.length > 2)
  728. {
  729. Graph.prototype.defaultThemes['default-style2'] = xhr[1].getDocumentElement();
  730. Graph.prototype.defaultThemes['darkTheme'] = xhr[2].getDocumentElement();
  731. }
  732. // Main
  733. var ui = (createUi != null) ? createUi() : new App(new Editor(
  734. urlParams['chrome'] == '0' || uiTheme == 'min',
  735. null, null, null, urlParams['chrome'] != '0'));
  736. if (window.mxscript != null)
  737. {
  738. // Loads dropbox for all browsers but IE8 and below (no CORS) if not disabled or if enabled and in embed mode
  739. // KNOWN: Picker does not work in IE11 (https://dropbox.zendesk.com/requests/1650781)
  740. if (typeof window.DropboxClient === 'function' &&
  741. (window.Dropbox == null && window.DrawDropboxClientCallback != null &&
  742. (((urlParams['embed'] != '1' && urlParams['db'] != '0') ||
  743. (urlParams['embed'] == '1' && urlParams['db'] == '1')) &&
  744. isSvgBrowser && (document.documentMode == null || document.documentMode > 9))))
  745. {
  746. mxscript(App.DROPBOX_URL, function()
  747. {
  748. // Must load this after the dropbox SDK since they use the same namespace
  749. mxscript(App.DROPINS_URL, function()
  750. {
  751. DrawDropboxClientCallback();
  752. }, 'dropboxjs', App.DROPBOX_APPKEY);
  753. });
  754. }
  755. // Disables client
  756. else if (typeof window.Dropbox === 'undefined' || typeof window.Dropbox.choose === 'undefined')
  757. {
  758. window.DropboxClient = null;
  759. }
  760. // Loads OneDrive for all browsers but IE6/IOS if not disabled or if enabled and in embed mode
  761. if (typeof window.OneDriveClient === 'function' &&
  762. (typeof OneDrive === 'undefined' && window.DrawOneDriveClientCallback != null &&
  763. (((urlParams['embed'] != '1' && urlParams['od'] != '0') || (urlParams['embed'] == '1' &&
  764. urlParams['od'] == '1')) && (navigator.userAgent == null ||
  765. navigator.userAgent.indexOf('MSIE') < 0 || document.documentMode >= 10))))
  766. {
  767. mxscript(App.ONEDRIVE_URL, window.DrawOneDriveClientCallback);
  768. }
  769. // Disables client
  770. else if (typeof window.OneDrive === 'undefined')
  771. {
  772. window.OneDriveClient = null;
  773. }
  774. // Loads Trello for all browsers but < IE10 if not disabled or if enabled and in embed mode
  775. if (typeof window.TrelloClient === 'function' &&
  776. (typeof window.Trello === 'undefined' && window.DrawTrelloClientCallback != null &&
  777. (((urlParams['embed'] != '1' && urlParams['tr'] != '0') || (urlParams['embed'] == '1' &&
  778. urlParams['tr'] == '1')) && (navigator.userAgent == null ||
  779. navigator.userAgent.indexOf('MSIE') < 0 || document.documentMode >= 10))))
  780. {
  781. mxscript(App.TRELLO_JQUERY_URL, function()
  782. {
  783. // Must load this after the dropbox SDK since they use the same namespace
  784. mxscript(App.TRELLO_URL, function()
  785. {
  786. DrawTrelloClientCallback();
  787. });
  788. });
  789. }
  790. // Disables client
  791. else if (typeof window.Trello === 'undefined')
  792. {
  793. window.TrelloClient = null;
  794. }
  795. }
  796. if (callback != null)
  797. {
  798. callback(ui);
  799. }
  800. /**
  801. * For developers only
  802. */
  803. if (urlParams['chrome'] != '0' && urlParams['test'] == '1')
  804. {
  805. EditorUi.debug('App.start', [ui, (new Date().getTime() - t0.getTime()) + 'ms']);
  806. if (urlParams['export'] != null)
  807. {
  808. EditorUi.debug('Export:', EXPORT_URL);
  809. }
  810. }
  811. }, function(xhr)
  812. {
  813. var st = document.getElementById('geStatus');
  814. if (st != null)
  815. {
  816. st.innerHTML = 'Error loading page. <a>Please try refreshing.</a>';
  817. // Tries reload with default resources in case any language resources were not available
  818. st.getElementsByTagName('a')[0].onclick = function()
  819. {
  820. mxLanguage = 'en';
  821. doLoad(mxResources.getDefaultBundle(RESOURCE_BASE, mxLanguage) ||
  822. mxResources.getSpecialBundle(RESOURCE_BASE, mxLanguage));
  823. };
  824. }
  825. });
  826. };
  827. function doMain()
  828. {
  829. // Optional override for autosaveDelay and defaultEdgeLength
  830. try
  831. {
  832. if (mxSettings.settings != null)
  833. {
  834. if (mxSettings.settings.autosaveDelay != null)
  835. {
  836. var val = parseInt(mxSettings.settings.autosaveDelay);
  837. if (!isNaN(val) && val > 0)
  838. {
  839. DrawioFile.prototype.autosaveDelay = val;
  840. EditorUi.debug('Setting autosaveDelay', val);
  841. }
  842. else
  843. {
  844. EditorUi.debug('Invalid autosaveDelay', val);
  845. }
  846. }
  847. if (mxSettings.settings.defaultEdgeLength != null)
  848. {
  849. var val = parseInt(mxSettings.settings.defaultEdgeLength);
  850. if (!isNaN(val) && val > 0)
  851. {
  852. Graph.prototype.defaultEdgeLength = val;
  853. EditorUi.debug('Using defaultEdgeLength', val);
  854. }
  855. else
  856. {
  857. EditorUi.debug('Invalid defaultEdgeLength', val);
  858. }
  859. }
  860. }
  861. }
  862. catch (e)
  863. {
  864. if (window.console != null)
  865. {
  866. console.error(e);
  867. }
  868. }
  869. // Adds required resources (disables loading of fallback properties, this can only
  870. // be used if we know that all keys are defined in the language specific file)
  871. mxResources.loadDefaultBundle = false;
  872. doLoad(mxResources.getDefaultBundle(RESOURCE_BASE, mxLanguage) ||
  873. mxResources.getSpecialBundle(RESOURCE_BASE, mxLanguage));
  874. };
  875. // Sends load event if configuration is requested and waits for configure message
  876. if (urlParams['configure'] == '1')
  877. {
  878. var op = window.opener || window.parent;
  879. var configHandler = function(evt)
  880. {
  881. if (evt.source == op)
  882. {
  883. try
  884. {
  885. var data = JSON.parse(evt.data);
  886. if (data != null && data.action == 'configure')
  887. {
  888. mxEvent.removeListener(window, 'message', configHandler);
  889. Editor.configure(data.config, true);
  890. mxSettings.load();
  891. doMain();
  892. }
  893. }
  894. catch (e)
  895. {
  896. if (window.console != null)
  897. {
  898. console.log('Error in configure message: ' + e, evt.data);
  899. }
  900. }
  901. }
  902. };
  903. // Receives XML message from opener and puts it into the graph
  904. mxEvent.addListener(window, 'message', configHandler);
  905. op.postMessage(JSON.stringify({event: 'configure'}), '*');
  906. }
  907. else
  908. {
  909. if (Editor.config == null)
  910. {
  911. // Loads configuration from global scope or local storage
  912. if (window.DRAWIO_CONFIG != null)
  913. {
  914. try
  915. {
  916. EditorUi.debug('Using global configuration', window.DRAWIO_CONFIG);
  917. Editor.configure(window.DRAWIO_CONFIG);
  918. mxSettings.load();
  919. }
  920. catch (e)
  921. {
  922. if (window.console != null)
  923. {
  924. console.error(e);
  925. }
  926. }
  927. }
  928. // Loads configuration from local storage
  929. if (isLocalStorage && localStorage != null && urlParams['embed'] != '1')
  930. {
  931. var configData = localStorage.getItem('.configuration');
  932. if (configData != null)
  933. {
  934. try
  935. {
  936. configData = JSON.parse(configData);
  937. if (configData != null)
  938. {
  939. EditorUi.debug('Using local configuration', configData);
  940. Editor.configure(configData);
  941. mxSettings.load();
  942. }
  943. }
  944. catch (e)
  945. {
  946. if (window.console != null)
  947. {
  948. console.error(e);
  949. }
  950. }
  951. }
  952. }
  953. }
  954. doMain();
  955. }
  956. };
  957. //Extends EditorUi
  958. mxUtils.extend(App, EditorUi);
  959. /**
  960. * Executes the first step for connecting to Google Drive.
  961. */
  962. App.prototype.defaultUserPicture = 'https://lh3.googleusercontent.com/-HIzvXUy6QUY/AAAAAAAAAAI/AAAAAAAAAAA/giuR7PQyjEk/photo.jpg?sz=64';
  963. /**
  964. *
  965. */
  966. App.prototype.shareImage = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAA2RpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMC1jMDYwIDYxLjEzNDc3NywgMjAxMC8wMi8xMi0xNzozMjowMCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD0ieG1wLmRpZDowOTgwMTE3NDA3MjA2ODExODhDNkFGMDBEQkQ0RTgwOSIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDoxMjU2NzdEMTcwRDIxMUUxQjc0MDkxRDhCNUQzOEFGRCIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDoxMjU2NzdEMDcwRDIxMUUxQjc0MDkxRDhCNUQzOEFGRCIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ1M1IFdpbmRvd3MiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDowNjgwMTE3NDA3MjA2ODExODcxRkM4MUY1OTFDMjQ5OCIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDowNzgwMTE3NDA3MjA2ODExODhDNkFGMDBEQkQ0RTgwOSIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PrM/fs0AAADgSURBVHjaYmDAA/7//88MwgzkAKDGFiD+BsQ/QWxSNaf9RwN37twpI8WAS+gGfP78+RpQSoRYA36iG/D379+vQClNdLVMOMz4gi7w79+/n0CKg1gD9qELvH379hzIHGK9oA508ieY8//8+fO5rq4uFCilRKwL1JmYmNhhHEZGRiZ+fn6Q2meEbDYG4u3/cYCfP38uA7kOm0ZOIJ7zn0jw48ePPiDFhmzArv8kgi9fvuwB+w5qwH9ykjswbFSZyM4sEMDPBDTlL5BxkFSd7969OwZ2BZKYGhDzkmjOJ4AAAwBhpRqGnEFb8QAAAABJRU5ErkJggg==';
  967. /**
  968. *
  969. */
  970. App.prototype.chevronUpImage = (!mxClient.IS_SVG) ? IMAGE_PATH + '/chevron-up.png' : 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMC1jMDYwIDYxLjEzNDc3NywgMjAxMC8wMi8xMi0xNzozMjowMCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNSBNYWNpbnRvc2giIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6NDg2NEE3NUY1MUVBMTFFM0I3MUVEMTc0N0YyOUI4QzEiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6NDg2NEE3NjA1MUVBMTFFM0I3MUVEMTc0N0YyOUI4QzEiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDo0ODY0QTc1RDUxRUExMUUzQjcxRUQxNzQ3RjI5QjhDMSIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDo0ODY0QTc1RTUxRUExMUUzQjcxRUQxNzQ3RjI5QjhDMSIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/Pg+qUokAAAAMUExURQAAANnZ2b+/v////5bgre4AAAAEdFJOU////wBAKqn0AAAAL0lEQVR42mJgRgMMRAswMKAKMDDARBjg8lARBoR6KImkH0wTbygT6YaS4DmAAAMAYPkClOEDDD0AAAAASUVORK5CYII=';
  971. /**
  972. *
  973. */
  974. App.prototype.chevronDownImage = (!mxClient.IS_SVG) ? IMAGE_PATH + '/chevron-down.png' : 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMC1jMDYwIDYxLjEzNDc3NywgMjAxMC8wMi8xMi0xNzozMjowMCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNSBNYWNpbnRvc2giIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6NDg2NEE3NUI1MUVBMTFFM0I3MUVEMTc0N0YyOUI4QzEiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6NDg2NEE3NUM1MUVBMTFFM0I3MUVEMTc0N0YyOUI4QzEiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDo0ODY0QTc1OTUxRUExMUUzQjcxRUQxNzQ3RjI5QjhDMSIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDo0ODY0QTc1QTUxRUExMUUzQjcxRUQxNzQ3RjI5QjhDMSIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PsCtve8AAAAMUExURQAAANnZ2b+/v////5bgre4AAAAEdFJOU////wBAKqn0AAAALUlEQVR42mJgRgMMRAkwQEXBNAOcBSPhclB1cNVwfcxI+vEZykSpoSR6DiDAAF23ApT99bZ+AAAAAElFTkSuQmCC';
  975. /**
  976. *
  977. */
  978. App.prototype.formatShowImage = (!mxClient.IS_SVG) ? IMAGE_PATH + '/format-show.png' : 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMC1jMDYwIDYxLjEzNDc3NywgMjAxMC8wMi8xMi0xNzozMjowMCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNSBNYWNpbnRvc2giIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6ODdCREY5REY1NkQ3MTFFNTkyNjNEMTA5NjgwODUyRTgiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6ODdCREY5RTA1NkQ3MTFFNTkyNjNEMTA5NjgwODUyRTgiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDo4N0JERjlERDU2RDcxMUU1OTI2M0QxMDk2ODA4NTJFOCIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDo4N0JERjlERTU2RDcxMUU1OTI2M0QxMDk2ODA4NTJFOCIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PlnMQ/8AAAAJUExURQAAAP///3FxcTfTiAsAAAACdFJOU/8A5bcwSgAAACFJREFUeNpiYEQDDEQJMMABTAAixcQ00ALoDiPRcwABBgB6DADly9Yx8wAAAABJRU5ErkJggg==';
  979. /**
  980. *
  981. */
  982. App.prototype.formatHideImage = (!mxClient.IS_SVG) ? IMAGE_PATH + '/format-hide.png' : 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMC1jMDYwIDYxLjEzNDc3NywgMjAxMC8wMi8xMi0xNzozMjowMCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNSBNYWNpbnRvc2giIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6ODdCREY5REI1NkQ3MTFFNTkyNjNEMTA5NjgwODUyRTgiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6ODdCREY5REM1NkQ3MTFFNTkyNjNEMTA5NjgwODUyRTgiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDo4N0JERjlEOTU2RDcxMUU1OTI2M0QxMDk2ODA4NTJFOCIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDo4N0JERjlEQTU2RDcxMUU1OTI2M0QxMDk2ODA4NTJFOCIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PqjT9SMAAAAGUExURQAAAP///6XZn90AAAACdFJOU/8A5bcwSgAAAB9JREFUeNpiYEQDDEQJMMABTAAmNdAC6A4j0XMAAQYAcbwA1Xvj1CgAAAAASUVORK5CYII=';
  983. /**
  984. *
  985. */
  986. App.prototype.fullscreenImage = (!mxClient.IS_SVG) ? IMAGE_PATH + '/fullscreen.png' : 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAQMAAAAlPW0iAAAABlBMVEUAAAAAAAClZ7nPAAAAAXRSTlMAQObYZgAAABpJREFUCNdjgAAbGxAy4AEh5gNwBBGByoIBAIueBd12TUjqAAAAAElFTkSuQmCC';
  987. /**
  988. * Interval to show dialog for unsaved data if autosave is on.
  989. * Default is 300000 (5 minutes).
  990. */
  991. App.prototype.warnInterval = 300000;
  992. /**
  993. *
  994. */
  995. App.prototype.compactMode = false;
  996. /**
  997. *
  998. */
  999. App.prototype.fullscreenMode = false;
  1000. /**
  1001. * Overriden UI settings depending on mode.
  1002. */
  1003. if (urlParams['embed'] != '1')
  1004. {
  1005. App.prototype.menubarHeight = 64;
  1006. }
  1007. else
  1008. {
  1009. App.prototype.footerHeight = 0;
  1010. }
  1011. /**
  1012. * Queue for loading plugins and wait for UI instance
  1013. */
  1014. App.initPluginCallback = function()
  1015. {
  1016. if (App.DrawPlugins == null)
  1017. {
  1018. // Workaround for need to load plugins now but wait for UI instance
  1019. App.DrawPlugins = [];
  1020. // Global entry point for plugins is Draw.loadPlugin. This is the only
  1021. // long-term supported solution for access to the EditorUi instance.
  1022. window.Draw = new Object();
  1023. window.Draw.loadPlugin = function(callback)
  1024. {
  1025. App.DrawPlugins.push(callback);
  1026. };
  1027. }
  1028. };
  1029. /**
  1030. *
  1031. */
  1032. App.pluginsLoaded = {};
  1033. App.embedModePluginsCount = 0;
  1034. /**
  1035. * Queue for loading plugins and wait for UI instance
  1036. */
  1037. App.loadPlugins = function(plugins, useInclude)
  1038. {
  1039. EditorUi.debug('Loading plugins', plugins);
  1040. for (var i = 0; i < plugins.length; i++)
  1041. {
  1042. if (plugins[i] != null && plugins[i].length > 0)
  1043. {
  1044. try
  1045. {
  1046. var url = PLUGINS_BASE_PATH + App.pluginRegistry[plugins[i]];
  1047. if (url != null)
  1048. {
  1049. if (App.pluginsLoaded[url] == null)
  1050. {
  1051. App.pluginsLoaded[url] = true;
  1052. App.embedModePluginsCount++;
  1053. if (typeof window.drawDevUrl === 'undefined')
  1054. {
  1055. if (useInclude)
  1056. {
  1057. mxinclude(url);
  1058. }
  1059. else
  1060. {
  1061. mxscript(url);
  1062. }
  1063. }
  1064. else
  1065. {
  1066. if (useInclude)
  1067. {
  1068. mxinclude(url);
  1069. }
  1070. else
  1071. {
  1072. mxscript(drawDevUrl + url);
  1073. }
  1074. }
  1075. }
  1076. }
  1077. else if (window.console != null)
  1078. {
  1079. console.log('Unknown plugin:', plugins[i]);
  1080. }
  1081. }
  1082. catch (e)
  1083. {
  1084. if (window.console != null)
  1085. {
  1086. console.log('Error loading plugin:', plugins[i], e);
  1087. }
  1088. }
  1089. }
  1090. }
  1091. };
  1092. /**
  1093. * Delay embed mode initialization until all plugins are loaded
  1094. */
  1095. App.prototype.initializeEmbedMode = function()
  1096. {
  1097. if (urlParams['embed'] == '1')
  1098. {
  1099. if (window.location.hostname == 'app.diagrams.net')
  1100. {
  1101. this.showBanner('EmbedDeprecationFooter', 'app.diagrams.net will stop working for embed mode. Please use embed.diagrams.net.');
  1102. }
  1103. if (App.embedModePluginsCount > 0 || this.initEmbedDone)
  1104. {
  1105. return; //Wait for plugins to load, or this is a duplicate call due to timeout
  1106. }
  1107. else
  1108. {
  1109. this.initEmbedDone = true;
  1110. }
  1111. EditorUi.prototype.initializeEmbedMode.apply(this, arguments);
  1112. }
  1113. };
  1114. /**
  1115. * TODO: Define viewer protocol and implement new viewer style toolbar
  1116. */
  1117. App.prototype.initializeViewerMode = function()
  1118. {
  1119. var parent = window.opener || window.parent;
  1120. if (parent != null)
  1121. {
  1122. this.editor.graph.addListener(mxEvent.SIZE, mxUtils.bind(this, function()
  1123. {
  1124. parent.postMessage(JSON.stringify(this.createLoadMessage('size')), '*');
  1125. }));
  1126. }
  1127. };
  1128. /**
  1129. * Translates this point by the given vector.
  1130. *
  1131. * @param {number} dx X-coordinate of the translation.
  1132. * @param {number} dy Y-coordinate of the translation.
  1133. */
  1134. App.prototype.init = function()
  1135. {
  1136. EditorUi.prototype.init.apply(this, arguments);
  1137. /**
  1138. * Specifies the default filename.
  1139. */
  1140. this.defaultLibraryName = mxResources.get('untitledLibrary');
  1141. /**
  1142. * Holds the listener for description changes.
  1143. */
  1144. this.descriptorChangedListener = mxUtils.bind(this, this.descriptorChanged);
  1145. /**
  1146. * Creates github client.
  1147. */
  1148. this.gitHub = (!mxClient.IS_IE || document.documentMode == 10 ||
  1149. mxClient.IS_IE11 || mxClient.IS_EDGE) &&
  1150. (urlParams['gh'] != '0' && (urlParams['embed'] != '1' ||
  1151. urlParams['gh'] == '1')) ? new GitHubClient(this) : null;
  1152. if (this.gitHub != null)
  1153. {
  1154. this.gitHub.addListener('userChanged', mxUtils.bind(this, function()
  1155. {
  1156. this.updateUserElement();
  1157. this.restoreLibraries();
  1158. }))
  1159. }
  1160. /**
  1161. * Creates gitlab client.
  1162. */
  1163. this.gitLab = (!mxClient.IS_IE || document.documentMode == 10 ||
  1164. mxClient.IS_IE11 || mxClient.IS_EDGE) &&
  1165. (urlParams['gl'] != '0' && (urlParams['embed'] != '1' ||
  1166. urlParams['gl'] == '1')) ? new GitLabClient(this) : null;
  1167. if (this.gitLab != null)
  1168. {
  1169. this.gitLab.addListener('userChanged', mxUtils.bind(this, function()
  1170. {
  1171. this.updateUserElement();
  1172. this.restoreLibraries();
  1173. }));
  1174. }
  1175. /**
  1176. * Lazy-loading for individual backends
  1177. */
  1178. if (urlParams['embed'] != '1' || urlParams['od'] == '1')
  1179. {
  1180. /**
  1181. * Creates onedrive client if all required libraries are available.
  1182. */
  1183. var initOneDriveClient = mxUtils.bind(this, function()
  1184. {
  1185. if (typeof OneDrive !== 'undefined')
  1186. {
  1187. /**
  1188. * Holds the x-coordinate of the point.
  1189. */
  1190. this.oneDrive = new OneDriveClient(this);
  1191. this.oneDrive.addListener('userChanged', mxUtils.bind(this, function()
  1192. {
  1193. this.updateUserElement();
  1194. this.restoreLibraries();
  1195. }));
  1196. // Notifies listeners of new client
  1197. this.fireEvent(new mxEventObject('clientLoaded', 'client', this.oneDrive));
  1198. }
  1199. else if (window.DrawOneDriveClientCallback == null)
  1200. {
  1201. window.DrawOneDriveClientCallback = initOneDriveClient;
  1202. }
  1203. });
  1204. initOneDriveClient();
  1205. }
  1206. /**
  1207. * Lazy-loading for Trello
  1208. */
  1209. if (urlParams['embed'] != '1' || urlParams['tr'] == '1')
  1210. {
  1211. /**
  1212. * Creates Trello client if all required libraries are available.
  1213. */
  1214. var initTrelloClient = mxUtils.bind(this, function()
  1215. {
  1216. if (typeof window.Trello !== 'undefined')
  1217. {
  1218. try
  1219. {
  1220. this.trello = new TrelloClient(this);
  1221. //TODO we have no user info from Trello so we don't set a user
  1222. this.trello.addListener('userChanged', mxUtils.bind(this, function()
  1223. {
  1224. this.updateUserElement();
  1225. this.restoreLibraries();
  1226. }));
  1227. // Notifies listeners of new client
  1228. this.fireEvent(new mxEventObject('clientLoaded', 'client', this.trello));
  1229. }
  1230. catch (e)
  1231. {
  1232. if (window.console != null)
  1233. {
  1234. console.error(e);
  1235. }
  1236. }
  1237. }
  1238. else if (window.DrawTrelloClientCallback == null)
  1239. {
  1240. window.DrawTrelloClientCallback = initTrelloClient;
  1241. }
  1242. });
  1243. initTrelloClient();
  1244. }
  1245. /**
  1246. * Creates drive client with all required libraries are available.
  1247. */
  1248. if (urlParams['embed'] != '1' || urlParams['gapi'] == '1')
  1249. {
  1250. var initDriveClient = mxUtils.bind(this, function()
  1251. {
  1252. /**
  1253. * Creates google drive client if all required libraries are available.
  1254. */
  1255. if (typeof gapi !== 'undefined')
  1256. {
  1257. var doInit = mxUtils.bind(this, function()
  1258. {
  1259. this.drive = new DriveClient(this);
  1260. this.drive.addListener('userChanged', mxUtils.bind(this, function()
  1261. {
  1262. this.updateUserElement();
  1263. this.restoreLibraries();
  1264. this.checkLicense();
  1265. }))
  1266. // Notifies listeners of new client
  1267. this.fireEvent(new mxEventObject('clientLoaded', 'client', this.drive));
  1268. });
  1269. if (window.DrawGapiClientCallback != null)
  1270. {
  1271. gapi.load(((urlParams['picker'] != '0') ? 'picker,': '') + App.GOOGLE_APIS, doInit);
  1272. /**
  1273. * Clears any callbacks.
  1274. */
  1275. window.DrawGapiClientCallback = null;
  1276. }
  1277. else
  1278. {
  1279. doInit();
  1280. }
  1281. }
  1282. else if (window.DrawGapiClientCallback == null)
  1283. {
  1284. window.DrawGapiClientCallback = initDriveClient;
  1285. }
  1286. });
  1287. initDriveClient();
  1288. }
  1289. if (urlParams['embed'] != '1' || urlParams['db'] == '1')
  1290. {
  1291. /**
  1292. * Creates dropbox client if all required libraries are available.
  1293. */
  1294. var initDropboxClient = mxUtils.bind(this, function()
  1295. {
  1296. if (typeof Dropbox === 'function' && typeof Dropbox.choose !== 'undefined')
  1297. {
  1298. /**
  1299. * Clears dropbox client callback.
  1300. */
  1301. window.DrawDropboxClientCallback = null;
  1302. /**
  1303. * Holds the x-coordinate of the point.
  1304. */
  1305. try
  1306. {
  1307. this.dropbox = new DropboxClient(this);
  1308. this.dropbox.addListener('userChanged', mxUtils.bind(this, function()
  1309. {
  1310. this.updateUserElement();
  1311. this.restoreLibraries();
  1312. }));
  1313. // Notifies listeners of new client
  1314. this.fireEvent(new mxEventObject('clientLoaded', 'client', this.dropbox));
  1315. }
  1316. catch (e)
  1317. {
  1318. if (window.console != null)
  1319. {
  1320. console.error(e);
  1321. }
  1322. }
  1323. }
  1324. else if (window.DrawDropboxClientCallback == null)
  1325. {
  1326. window.DrawDropboxClientCallback = initDropboxClient;
  1327. }
  1328. });
  1329. initDropboxClient();
  1330. }
  1331. if (urlParams['embed'] != '1')
  1332. {
  1333. /**
  1334. * Holds the background element.
  1335. */
  1336. this.bg = this.createBackground();
  1337. document.body.appendChild(this.bg);
  1338. this.diagramContainer.style.visibility = 'hidden';
  1339. this.formatContainer.style.visibility = 'hidden';
  1340. this.hsplit.style.display = 'none';
  1341. this.sidebarContainer.style.display = 'none';
  1342. this.sidebarFooterContainer.style.display = 'none';
  1343. // Sets the initial mode
  1344. if (urlParams['local'] == '1')
  1345. {
  1346. this.setMode(App.MODE_DEVICE);
  1347. }
  1348. else
  1349. {
  1350. this.mode = App.mode;
  1351. }
  1352. // Add to Home Screen dialog for mobile devices
  1353. if ('serviceWorker' in navigator && !this.editor.isChromelessView() &&
  1354. (mxClient.IS_ANDROID || mxClient.IS_IOS))
  1355. {
  1356. window.addEventListener('beforeinstallprompt', mxUtils.bind(this, function(e)
  1357. {
  1358. this.showBanner('AddToHomeScreenFooter', mxResources.get('installApp'), function()
  1359. {
  1360. e.prompt();
  1361. });
  1362. }));
  1363. }
  1364. if (!mxClient.IS_CHROMEAPP && !EditorUi.isElectronApp && !this.isOffline() &&
  1365. !mxClient.IS_ANDROID && !mxClient.IS_IOS && urlParams['open'] == null &&
  1366. (!this.editor.chromeless || this.editor.editable))
  1367. {
  1368. this.editor.addListener('fileLoaded', mxUtils.bind(this, function()
  1369. {
  1370. var file = this.getCurrentFile();
  1371. var mode = (file != null) ? file.getMode() : null;
  1372. if (mode == App.MODE_DEVICE || mode == App.MODE_BROWSER)
  1373. {
  1374. this.showDownloadDesktopBanner();
  1375. }
  1376. }));
  1377. }
  1378. if (!mxClient.IS_CHROMEAPP && !EditorUi.isElectronApp && urlParams['embed'] != '1' && DrawioFile.SYNC == 'auto' &&
  1379. urlParams['local'] != '1' && urlParams['stealth'] != '1' && !this.isOffline() &&
  1380. (!this.editor.chromeless || this.editor.editable))
  1381. {
  1382. // Checks if the cache is alive
  1383. var acceptResponse = true;
  1384. var timeoutThread = window.setTimeout(mxUtils.bind(this, function()
  1385. {
  1386. acceptResponse = false;
  1387. // Switches to manual sync if cache cannot be reached
  1388. DrawioFile.SYNC = 'manual';
  1389. var file = this.getCurrentFile();
  1390. if (file != null && file.sync != null)
  1391. {
  1392. file.sync.destroy();
  1393. file.sync = null;
  1394. var status = mxUtils.htmlEntities(mxResources.get('timeout'));
  1395. this.editor.setStatus('<div title="'+ status +
  1396. '" class="geStatusAlert" style="overflow:hidden;">' + status +
  1397. '</div>');
  1398. }
  1399. EditorUi.logEvent({category: 'TIMEOUT-CACHE-CHECK', action: 'timeout', label: 408});
  1400. }), Editor.cacheTimeout);
  1401. var t0 = new Date().getTime();
  1402. mxUtils.get(EditorUi.cacheUrl + '?alive', mxUtils.bind(this, function(req)
  1403. {
  1404. window.clearTimeout(timeoutThread);
  1405. }));
  1406. }
  1407. }
  1408. else if (this.menubar != null)
  1409. {
  1410. this.menubar.container.style.paddingTop = '0px';
  1411. }
  1412. this.updateHeader();
  1413. if (this.menubar != null)
  1414. {
  1415. this.buttonContainer = document.createElement('div');
  1416. this.buttonContainer.style.display = 'inline-block';
  1417. this.buttonContainer.style.paddingRight = '48px';
  1418. this.buttonContainer.style.position = 'absolute';
  1419. this.buttonContainer.style.right = '0px';
  1420. this.menubar.container.appendChild(this.buttonContainer);
  1421. }
  1422. if (uiTheme == 'atlas' && this.menubar != null)
  1423. {
  1424. if (this.toggleElement != null)
  1425. {
  1426. this.toggleElement.click();
  1427. this.toggleElement.style.display = 'none';
  1428. }
  1429. this.icon = document.createElement('img');
  1430. this.icon.setAttribute('src', IMAGE_PATH + '/logo-flat-small.png');
  1431. this.icon.setAttribute('title', mxResources.get('draw.io'));
  1432. this.icon.style.padding = '6px';
  1433. this.icon.style.cursor = 'pointer';
  1434. mxEvent.addListener(this.icon, 'click', mxUtils.bind(this, function(evt)
  1435. {
  1436. this.appIconClicked(evt);
  1437. }));
  1438. if (mxClient.IS_QUIRKS)
  1439. {
  1440. this.icon.style.marginTop = '12px';
  1441. }
  1442. this.menubar.container.insertBefore(this.icon, this.menubar.container.firstChild);
  1443. }
  1444. if (this.editor.graph.isViewer())
  1445. {
  1446. this.initializeViewerMode();
  1447. }
  1448. };
  1449. /**
  1450. * Schedules a sanity check.
  1451. */
  1452. App.prototype.scheduleSanityCheck = function()
  1453. {
  1454. if (!mxClient.IS_CHROMEAPP && !EditorUi.isElectronApp &&
  1455. this.sanityCheckThread == null)
  1456. {
  1457. this.sanityCheckThread = window.setTimeout(mxUtils.bind(this, function()
  1458. {
  1459. this.sanityCheckThread = null;
  1460. this.sanityCheck();
  1461. }), this.warnInterval);
  1462. }
  1463. };
  1464. /**
  1465. * Stops sanity checks.
  1466. */
  1467. App.prototype.stopSanityCheck = function()
  1468. {
  1469. if (this.sanityCheckThread != null)
  1470. {
  1471. window.clearTimeout(this.sanityCheckThread);
  1472. this.sanityCheckThread = null;
  1473. }
  1474. };
  1475. /**
  1476. * Shows a warning after some time with unsaved changes and autosave.
  1477. */
  1478. App.prototype.sanityCheck = function()
  1479. {
  1480. var file = this.getCurrentFile();
  1481. if (file != null && file.isModified() && file.isAutosave() && file.isOverdue())
  1482. {
  1483. var evt = {category: 'WARN-FILE-' + file.getHash(),
  1484. action: ((file.savingFile) ? 'saving' : '') +
  1485. ((file.savingFile && file.savingFileTime != null) ? '_' +
  1486. Math.round((Date.now() - file.savingFileTime.getTime()) / 1000) : '') +
  1487. ((file.saveLevel != null) ? ('-sl_' + file.saveLevel) : '') +
  1488. '-age_' + ((file.ageStart != null) ? Math.round((Date.now() - file.ageStart.getTime()) / 1000) : 'x') +
  1489. ((this.editor.autosave) ? '' : '-nosave') +
  1490. ((file.isAutosave()) ? '' : '-noauto') +
  1491. '-open_' + ((file.opened != null) ? Math.round((Date.now() - file.opened.getTime()) / 1000) : 'x') +
  1492. '-save_' + ((file.lastSaved != null) ? Math.round((Date.now() - file.lastSaved.getTime()) / 1000) : 'x') +
  1493. '-change_' + ((file.lastChanged != null) ? Math.round((Date.now() - file.lastChanged.getTime()) / 1000) : 'x')+
  1494. '-alive_' + Math.round((Date.now() - App.startTime.getTime()) / 1000),
  1495. label: (file.sync != null) ? ('client_' + file.sync.clientId) : 'nosync'};
  1496. if (file.constructor == DriveFile && file.desc != null && this.drive != null)
  1497. {
  1498. evt.label += ((this.drive.user != null) ? ('-user_' + this.drive.user.id) : '-nouser') + '-rev_' +
  1499. file.desc.headRevisionId + '-mod_' + file.desc.modifiedDate + '-size_' + file.getSize() +
  1500. '-mime_' + file.desc.mimeType;
  1501. }
  1502. EditorUi.logEvent(evt);
  1503. var msg = mxResources.get('ensureDataSaved');
  1504. if (file.lastSaved != null)
  1505. {
  1506. var str = this.timeSince(file.lastSaved);
  1507. if (str == null)
  1508. {
  1509. str = mxResources.get('lessThanAMinute');
  1510. }
  1511. msg = mxResources.get('lastSaved', [str]);
  1512. }
  1513. // Resets possible stale state
  1514. this.spinner.stop();
  1515. this.showError(mxResources.get('unsavedChanges'), msg, mxResources.get('ignore'),
  1516. mxUtils.bind(this, function()
  1517. {
  1518. this.hideDialog();
  1519. }), null, mxResources.get('save'), mxUtils.bind(this, function()
  1520. {
  1521. this.stopSanityCheck();
  1522. this.actions.get((this.mode == null || !file.isEditable()) ?
  1523. 'saveAs' : 'save').funct();
  1524. }), null, null, 360, 120, null, mxUtils.bind(this, function()
  1525. {
  1526. this.scheduleSanityCheck();
  1527. }));
  1528. }
  1529. };
  1530. /**
  1531. * Returns true if the current domain is for the new drive app.
  1532. */
  1533. App.prototype.isDriveDomain = function()
  1534. {
  1535. return urlParams['drive'] != '0' &&
  1536. (window.location.hostname == 'test.draw.io' ||
  1537. window.location.hostname == 'www.draw.io' ||
  1538. window.location.hostname == 'drive.draw.io' ||
  1539. window.location.hostname == 'app.diagrams.net' ||
  1540. window.location.hostname == 'jgraph.github.io');
  1541. };
  1542. /**
  1543. * Returns the pusher instance for notifications. Creates the instance of none exists.
  1544. */
  1545. App.prototype.getPusher = function()
  1546. {
  1547. if (this.pusher == null && typeof window.Pusher === 'function')
  1548. {
  1549. this.pusher = new Pusher(App.PUSHER_KEY,
  1550. {
  1551. cluster: App.PUSHER_CLUSTER,
  1552. encrypted: true
  1553. });
  1554. }
  1555. return this.pusher;
  1556. };
  1557. /**
  1558. * Shows a footer to download the desktop version once per session.
  1559. */
  1560. App.prototype.showNameChangeBanner = function()
  1561. {
  1562. this.showBanner('DiagramsFooter', 'draw.io is now diagrams.net', mxUtils.bind(this, function()
  1563. {
  1564. this.openLink('https://www.diagrams.net/blog/move-diagrams-net');
  1565. }));
  1566. };
  1567. /**
  1568. * Shows a footer to download the desktop version once per session.
  1569. */
  1570. App.prototype.showDownloadDesktopBanner = function()
  1571. {
  1572. var link = 'https://get.diagrams.net/';
  1573. if (this.showBanner('DesktopFooter', mxResources.get('downloadDesktop'), mxUtils.bind(this, function()
  1574. {
  1575. this.openLink(link);
  1576. })))
  1577. {
  1578. // Downloads installer for macOS and Windows
  1579. mxUtils.get('https://api.github.com/repos/jgraph/drawio-desktop/releases/latest', mxUtils.bind(this, function(req)
  1580. {
  1581. try
  1582. {
  1583. var rel = JSON.parse(req.getText());
  1584. if (rel != null)
  1585. {
  1586. if (rel.tag_name != null && rel.name != null && rel.html_url != null)
  1587. {
  1588. if (mxClient.IS_MAC)
  1589. {
  1590. link = 'https://github.com/jgraph/drawio-desktop/releases/download/' +
  1591. rel.tag_name + '/draw.io-' + rel.name + '.dmg';
  1592. }
  1593. else if (mxClient.IS_WIN)
  1594. {
  1595. link = 'https://github.com/jgraph/drawio-desktop/releases/download/' +
  1596. rel.tag_name + '/draw.io-' + rel.name + '-windows-installer.exe';
  1597. }
  1598. }
  1599. }
  1600. }
  1601. catch (e)
  1602. {
  1603. // ignore
  1604. }
  1605. }));
  1606. }
  1607. };
  1608. /**
  1609. *
  1610. */
  1611. App.prototype.checkLicense = function()
  1612. {
  1613. var driveUser = this.drive.getUser();
  1614. var email = ((urlParams['dev'] == '1') ? urlParams['lic'] : null) ||
  1615. ((driveUser != null) ? driveUser.email : null);
  1616. if (!this.isOffline() && !this.editor.chromeless && email != null)
  1617. {
  1618. // Anonymises the local part of the email address
  1619. var at = email.lastIndexOf('@');
  1620. var domain = email;
  1621. if (at >= 0)
  1622. {
  1623. domain = email.substring(at + 1);
  1624. email = Editor.crc32(email.substring(0, at)) + '@' + domain;
  1625. }
  1626. // Timestamp is workaround for cached response in certain environments
  1627. mxUtils.post('/license', 'domain=' + encodeURIComponent(domain) + '&email=' + encodeURIComponent(email) +
  1628. '&lc=' + encodeURIComponent(driveUser.locale) + '&ts=' + new Date().getTime(),
  1629. mxUtils.bind(this, function(req)
  1630. {
  1631. try
  1632. {
  1633. if (req.getStatus() >= 200 && req.getStatus() <= 299)
  1634. {
  1635. var value = req.getText();
  1636. if (value.length > 0)
  1637. {
  1638. var lic = JSON.parse(value);
  1639. if (lic != null)
  1640. {
  1641. this.handleLicense(lic, domain);
  1642. }
  1643. }
  1644. }
  1645. }
  1646. catch (e)
  1647. {
  1648. // ignore
  1649. }
  1650. }));
  1651. }
  1652. };
  1653. /**
  1654. * Returns true if the current domain is for the new drive app.
  1655. */
  1656. App.prototype.handleLicense = function(lic, domain)
  1657. {
  1658. if (lic != null && lic.plugins != null)
  1659. {
  1660. App.loadPlugins(lic.plugins.split(';'), true);
  1661. }
  1662. };
  1663. /**
  1664. *
  1665. */
  1666. App.prototype.getEditBlankXml = function()
  1667. {
  1668. var file = this.getCurrentFile();
  1669. if (file != null && this.editor.isChromelessView() && this.editor.graph.isLightboxView())
  1670. {
  1671. return file.getData();
  1672. }
  1673. else
  1674. {
  1675. return this.getFileData(true);
  1676. }
  1677. };
  1678. /**
  1679. * Updates action states depending on the selection.
  1680. */
  1681. App.prototype.updateActionStates = function()
  1682. {
  1683. EditorUi.prototype.updateActionStates.apply(this, arguments);
  1684. this.actions.get('revisionHistory').setEnabled(this.isRevisionHistoryEnabled());
  1685. };
  1686. /**
  1687. * Adds the specified entry to the recent file list in local storage
  1688. */
  1689. App.prototype.addRecent = function(entry)
  1690. {
  1691. if (isLocalStorage && localStorage != null)
  1692. {
  1693. var recent = this.getRecent();
  1694. if (recent == null)
  1695. {
  1696. recent = [];
  1697. }
  1698. else
  1699. {
  1700. for (var i = 0; i < recent.length; i++)
  1701. {
  1702. if (recent[i].id == entry.id)
  1703. {
  1704. recent.splice(i, 1);
  1705. }
  1706. }
  1707. }
  1708. if (recent != null)
  1709. {
  1710. recent.unshift(entry);
  1711. recent = recent.slice(0, 10);
  1712. localStorage.setItem('.recent', JSON.stringify(recent));
  1713. }
  1714. }
  1715. };
  1716. /**
  1717. * Returns the recent file list from local storage
  1718. */
  1719. App.prototype.getRecent = function()
  1720. {
  1721. if (isLocalStorage && localStorage != null)
  1722. {
  1723. try
  1724. {
  1725. var recent = localStorage.getItem('.recent');
  1726. if (recent != null)
  1727. {
  1728. return JSON.parse(recent);
  1729. }
  1730. }
  1731. catch (e)
  1732. {
  1733. // ignore
  1734. }
  1735. return null;
  1736. }
  1737. };
  1738. /**
  1739. * Clears the recent file list in local storage
  1740. */
  1741. App.prototype.resetRecent = function(entry)
  1742. {
  1743. if (isLocalStorage && localStorage != null)
  1744. {
  1745. try
  1746. {
  1747. localStorage.removeItem('.recent');
  1748. }
  1749. catch (e)
  1750. {
  1751. // ignore
  1752. }
  1753. }
  1754. };
  1755. /**
  1756. * Sets the onbeforeunload for the application
  1757. */
  1758. App.prototype.onBeforeUnload = function()
  1759. {
  1760. if (urlParams['embed'] == '1' && this.editor.modified)
  1761. {
  1762. return mxResources.get('allChangesLost');
  1763. }
  1764. else
  1765. {
  1766. var file = this.getCurrentFile();
  1767. if (file != null)
  1768. {
  1769. // KNOWN: Message is ignored by most browsers
  1770. if (file.constructor == LocalFile && file.getHash() == '' && !file.isModified() &&
  1771. urlParams['nowarn'] != '1' && !this.isDiagramEmpty() && urlParams['url'] == null &&
  1772. !this.editor.isChromelessView() && file.fileHandle == null)
  1773. {
  1774. return mxResources.get('ensureDataSaved');
  1775. }
  1776. else if (file.isModified())
  1777. {
  1778. return mxResources.get('allChangesLost');
  1779. }
  1780. else
  1781. {
  1782. file.close(true);
  1783. }
  1784. }
  1785. }
  1786. };
  1787. /**
  1788. * Translates this point by the given vector.
  1789. *
  1790. * @param {number} dx X-coordinate of the translation.
  1791. * @param {number} dy Y-coordinate of the translation.
  1792. */
  1793. App.prototype.updateDocumentTitle = function()
  1794. {
  1795. if (!this.editor.graph.isLightboxView())
  1796. {
  1797. var title = this.editor.appName;
  1798. var file = this.getCurrentFile();
  1799. if (this.isOfflineApp())
  1800. {
  1801. title += ' app';
  1802. }
  1803. if (file != null)
  1804. {
  1805. var filename = (file.getTitle() != null) ? file.getTitle() : this.defaultFilename;
  1806. title = filename + ' - ' + title;
  1807. }
  1808. if (document.title != title)
  1809. {
  1810. document.title = title;
  1811. var graph = this.editor.graph;
  1812. graph.invalidateDescendantsWithPlaceholders(graph.model.getRoot());
  1813. graph.view.validate();
  1814. }
  1815. }
  1816. };
  1817. /**
  1818. * Returns a thumbnail of the current file.
  1819. */
  1820. App.prototype.getThumbnail = function(width, fn)
  1821. {
  1822. var result = false;
  1823. try
  1824. {
  1825. var acceptResponse = true;
  1826. var timeoutThread = window.setTimeout(mxUtils.bind(this, function()
  1827. {
  1828. acceptResponse = false;
  1829. fn(null);
  1830. }), this.timeout);
  1831. var success = mxUtils.bind(this, function(canvas)
  1832. {
  1833. window.clearTimeout(timeoutThread);
  1834. if (acceptResponse)
  1835. {
  1836. fn(canvas);
  1837. }
  1838. });
  1839. if (this.thumbImageCache == null)
  1840. {
  1841. this.thumbImageCache = new Object();
  1842. }
  1843. var graph = this.editor.graph;
  1844. // Exports PNG for first page while other page is visible by creating a graph
  1845. // LATER: Add caching for the graph or SVG while not on first page
  1846. // To avoid refresh during save dark theme uses separate graph instance
  1847. var darkTheme = graph.themes != null && graph.defaultThemeName == 'darkTheme';
  1848. if (darkTheme || (this.pages != null && this.currentPage != this.pages[0]))
  1849. {
  1850. var graphGetGlobalVariable = graph.getGlobalVariable;
  1851. graph = this.createTemporaryGraph((darkTheme) ? graph.getDefaultStylesheet() : graph.getStylesheet());
  1852. var page = this.pages[0];
  1853. // Avoids override of stylesheet in getSvg for dark mode
  1854. if (darkTheme)
  1855. {
  1856. graph.defaultThemeName = 'default';
  1857. }
  1858. graph.getGlobalVariable = function(name)
  1859. {
  1860. if (name == 'page')
  1861. {
  1862. return page.getName();
  1863. }
  1864. else if (name == 'pagenumber')
  1865. {
  1866. return 1;
  1867. }
  1868. return graphGetGlobalVariable.apply(this, arguments);
  1869. };
  1870. graph.getGlobalVariable = graphGetGlobalVariable;
  1871. document.body.appendChild(graph.container);
  1872. graph.model.setRoot(page.root);
  1873. }
  1874. // Uses client-side canvas export
  1875. if (mxClient.IS_CHROMEAPP || this.useCanvasForExport)
  1876. {
  1877. this.editor.exportToCanvas(mxUtils.bind(this, function(canvas)
  1878. {
  1879. try
  1880. {
  1881. // Removes temporary graph from DOM
  1882. if (graph != this.editor.graph && graph.container.parentNode != null)
  1883. {
  1884. graph.container.parentNode.removeChild(graph.container);
  1885. }
  1886. }
  1887. catch (e)
  1888. {
  1889. canvas = null;
  1890. }
  1891. success(canvas);
  1892. }), width, this.thumbImageCache, '#ffffff', function()
  1893. {
  1894. // Continues with null in error case
  1895. success();
  1896. }, null, null, null, null, null, null, graph);
  1897. result = true;
  1898. }
  1899. else if (this.canvasSupported && this.getCurrentFile() != null)
  1900. {
  1901. var canvas = document.createElement('canvas');
  1902. var bounds = graph.getGraphBounds();
  1903. var scale = width / bounds.width;
  1904. // Limits scale to 1 or 2 * width / height
  1905. scale = Math.min(1, Math.min((width * 3) / (bounds.height * 4), scale));
  1906. var x0 = Math.floor(bounds.x);
  1907. var y0 = Math.floor(bounds.y);
  1908. canvas.setAttribute('width', Math.ceil(scale * (bounds.width + 4)));
  1909. canvas.setAttribute('height', Math.ceil(scale * (bounds.height + 4)));
  1910. var ctx = canvas.getContext('2d');
  1911. // Configures the canvas
  1912. ctx.scale(scale, scale);
  1913. ctx.translate(-x0, -y0);
  1914. // Paint white background instead of transparent
  1915. var bg = graph.background;
  1916. if (bg == null || bg == '' || bg == mxConstants.NONE)
  1917. {
  1918. bg = '#ffffff';
  1919. }
  1920. // Paints background
  1921. ctx.save();
  1922. ctx.fillStyle = bg;
  1923. ctx.fillRect(x0, y0, Math.ceil(bounds.width + 4), Math.ceil(bounds.height + 4));
  1924. ctx.restore();
  1925. var htmlCanvas = new mxJsCanvas(canvas);
  1926. // NOTE: htmlCanvas passed into async canvas is only used for image
  1927. // and canvas caching (canvas caching not used in this case as we do
  1928. // not render text). To reuse that cache via the thumbImageCache we
  1929. // pass that into the async canvas and override the image cache in
  1930. // the newly created html canvas with that of the thumbImageCache.
  1931. // LATER: Is clear thumbImageCache needed if file changes?
  1932. var asynCanvas = new mxAsyncCanvas(this.thumbImageCache);
  1933. htmlCanvas.images = this.thumbImageCache.images;
  1934. // Render graph
  1935. var imgExport = new mxImageExport();
  1936. imgExport.drawShape = function(state, canvas)
  1937. {
  1938. if (state.shape instanceof mxShape && state.shape.checkBounds())
  1939. {
  1940. canvas.save();
  1941. canvas.translate(0.5, 0.5);
  1942. state.shape.paint(canvas);
  1943. canvas.translate(-0.5, -0.5);
  1944. canvas.restore();
  1945. }
  1946. };
  1947. imgExport.drawText = function(state, canvas)
  1948. {
  1949. // No text output for thumbnails
  1950. };
  1951. imgExport.drawState(graph.getView().getState(graph.model.root), asynCanvas);
  1952. asynCanvas.finish(mxUtils.bind(this, function()
  1953. {
  1954. try
  1955. {
  1956. imgExport.drawState(graph.getView().getState(graph.model.root), htmlCanvas);
  1957. // Removes temporary graph from DOM
  1958. if (graph != this.editor.graph && graph.container.parentNode != null)
  1959. {
  1960. graph.container.parentNode.removeChild(graph.container);
  1961. }
  1962. }
  1963. catch (e)
  1964. {
  1965. canvas = null;
  1966. }
  1967. success(canvas);
  1968. }));
  1969. result = true;
  1970. }
  1971. }
  1972. catch (e)
  1973. {
  1974. result = false;
  1975. // Removes temporary graph from DOM
  1976. if (graph != null && graph != this.editor.graph && graph.container.parentNode != null)
  1977. {
  1978. graph.container.parentNode.removeChild(graph.container);
  1979. }
  1980. }
  1981. return result;
  1982. };
  1983. /**
  1984. * Translates this point by the given vector.
  1985. *
  1986. * @param {number} dx X-coordinate of the translation.
  1987. * @param {number} dy Y-coordinate of the translation.
  1988. */
  1989. App.prototype.createBackground = function()
  1990. {
  1991. var bg = this.createDiv('background');
  1992. bg.style.position = 'absolute';
  1993. bg.style.background = 'white';
  1994. bg.style.left = '0px';
  1995. bg.style.top = '0px';
  1996. bg.style.bottom = '0px';
  1997. bg.style.right = '0px';
  1998. mxUtils.setOpacity(bg, 100);
  1999. if (mxClient.IS_QUIRKS)
  2000. {
  2001. new mxDivResizer(bg);
  2002. }
  2003. return bg;
  2004. };
  2005. /**
  2006. * Translates this point by the given vector.
  2007. *
  2008. * @param {number} dx X-coordinate of the translation.
  2009. * @param {number} dy Y-coordinate of the translation.
  2010. */
  2011. (function()
  2012. {
  2013. var editorUiSetMode = EditorUi.prototype.setMode;
  2014. App.prototype.setMode = function(mode, remember)
  2015. {
  2016. editorUiSetMode.apply(this, arguments);
  2017. // Note: UseLocalStorage affects the file dialogs
  2018. // and should not be modified if mode is undefined
  2019. if (this.mode != null)
  2020. {
  2021. Editor.useLocalStorage = this.mode == App.MODE_BROWSER;
  2022. }
  2023. if (this.appIcon != null)
  2024. {
  2025. var file = this.getCurrentFile();
  2026. mode = (file != null) ? file.getMode() : mode;
  2027. if (mode == App.MODE_GOOGLE)
  2028. {
  2029. this.appIcon.setAttribute('title', mxResources.get('openIt', [mxResources.get('googleDrive')]));
  2030. this.appIcon.style.cursor = 'pointer';
  2031. }
  2032. else if (mode == App.MODE_DROPBOX)
  2033. {
  2034. this.appIcon.setAttribute('title', mxResources.get('openIt', [mxResources.get('dropbox')]));
  2035. this.appIcon.style.cursor = 'pointer';
  2036. }
  2037. else if (mode == App.MODE_ONEDRIVE)
  2038. {
  2039. this.appIcon.setAttribute('title', mxResources.get('openIt', [mxResources.get('oneDrive')]));
  2040. this.appIcon.style.cursor = 'pointer';
  2041. }
  2042. else
  2043. {
  2044. this.appIcon.removeAttribute('title');
  2045. this.appIcon.style.cursor = (mode == App.MODE_DEVICE) ? 'pointer' : 'default';
  2046. }
  2047. }
  2048. if (remember)
  2049. {
  2050. try
  2051. {
  2052. if (isLocalStorage)
  2053. {
  2054. localStorage.setItem('.mode', mode);
  2055. }
  2056. else if (typeof(Storage) != 'undefined')
  2057. {
  2058. var expiry = new Date();
  2059. expiry.setYear(expiry.getFullYear() + 1);
  2060. document.cookie = 'MODE=' + mode + '; expires=' + expiry.toUTCString();
  2061. }
  2062. }
  2063. catch (e)
  2064. {
  2065. // ignore possible access denied
  2066. }
  2067. }
  2068. };
  2069. })();
  2070. /**
  2071. * Function: authorize
  2072. *
  2073. * Authorizes the client, gets the userId and calls <open>.
  2074. */
  2075. App.prototype.appIconClicked = function(evt)
  2076. {
  2077. if (mxEvent.isAltDown(evt))
  2078. {
  2079. this.showSplash(true);
  2080. }
  2081. else
  2082. {
  2083. var file = this.getCurrentFile();
  2084. var mode = (file != null) ? file.getMode() : null;
  2085. if (mode == App.MODE_GOOGLE)
  2086. {
  2087. if (file != null && file.desc != null && file.desc.parents != null &&
  2088. file.desc.parents.length > 0 && !mxEvent.isShiftDown(evt))
  2089. {
  2090. // Opens containing folder
  2091. this.openLink('https://drive.google.com/drive/folders/' + file.desc.parents[0].id);
  2092. }
  2093. else if (file != null && file.getId() != null)
  2094. {
  2095. this.openLink('https://drive.google.com/open?id=' + file.getId());
  2096. }
  2097. else
  2098. {
  2099. this.openLink('https://drive.google.com/?authuser=0');
  2100. }
  2101. }
  2102. else if (mode == App.MODE_ONEDRIVE)
  2103. {
  2104. if (file != null && file.meta != null && file.meta.webUrl != null)
  2105. {
  2106. var url = file.meta.webUrl;
  2107. var name = encodeURIComponent(file.meta.name);
  2108. if (url.substring(url.length - name.length, url.length) == name)
  2109. {
  2110. url = url.substring(0, url.length - name.length);
  2111. }
  2112. this.openLink(url);
  2113. }
  2114. else
  2115. {
  2116. this.openLink('https://onedrive.live.com/');
  2117. }
  2118. }
  2119. else if (mode == App.MODE_DROPBOX)
  2120. {
  2121. if (file != null && file.stat != null && file.stat.path_display != null)
  2122. {
  2123. var url = 'https://www.dropbox.com/home/Apps/drawio' + file.stat.path_display;
  2124. if (!mxEvent.isShiftDown(evt))
  2125. {
  2126. url = url.substring(0, url.length - file.stat.name.length);
  2127. }
  2128. this.openLink(url);
  2129. }
  2130. else
  2131. {
  2132. this.openLink('https://www.dropbox.com/');
  2133. }
  2134. }
  2135. else if (mode == App.MODE_TRELLO)
  2136. {
  2137. this.openLink('https://trello.com/');
  2138. }
  2139. else if (mode == App.MODE_GITHUB)
  2140. {
  2141. if (file != null && file.constructor == GitHubFile)
  2142. {
  2143. this.openLink(file.meta.html_url);
  2144. }
  2145. else
  2146. {
  2147. this.openLink('https://github.com/');
  2148. }
  2149. }
  2150. else if (mode == App.MODE_GITLAB)
  2151. {
  2152. if (file != null && file.constructor == GitLabFile)
  2153. {
  2154. this.openLink(file.meta.html_url);
  2155. }
  2156. else
  2157. {
  2158. this.openLink(DRAWIO_GITLAB_URL);
  2159. }
  2160. }
  2161. else if (mode == App.MODE_DEVICE)
  2162. {
  2163. this.openLink('https://get.draw.io/');
  2164. }
  2165. }
  2166. mxEvent.consume(evt);
  2167. };
  2168. /**
  2169. * Function: authorize
  2170. *
  2171. * Authorizes the client, gets the userId and calls <open>.
  2172. */
  2173. App.prototype.clearMode = function()
  2174. {
  2175. if (isLocalStorage)
  2176. {
  2177. localStorage.removeItem('.mode');
  2178. }
  2179. else if (typeof(Storage) != 'undefined')
  2180. {
  2181. var expiry = new Date();
  2182. expiry.setYear(expiry.getFullYear() - 1);
  2183. document.cookie = 'MODE=; expires=' + expiry.toUTCString();
  2184. }
  2185. };
  2186. /**
  2187. * Translates this point by the given vector.
  2188. *
  2189. * @param {number} dx X-coordinate of the translation.
  2190. * @param {number} dy Y-coordinate of the translation.
  2191. */
  2192. App.prototype.getDiagramId = function()
  2193. {
  2194. var id = window.location.hash;
  2195. // Strips the hash sign
  2196. if (id != null && id.length > 0)
  2197. {
  2198. id = id.substring(1);
  2199. }
  2200. // Workaround for Trello client appending data after hash
  2201. if (id != null && id.length > 1 && id.charAt(0) == 'T')
  2202. {
  2203. var idx = id.indexOf('#');
  2204. if (idx > 0)
  2205. {
  2206. id = id.substring(0, idx);
  2207. }
  2208. }
  2209. return id;
  2210. };
  2211. /**
  2212. * Opens any file specified in the URL parameters.
  2213. */
  2214. App.prototype.open = function()
  2215. {
  2216. // Cross-domain window access is not allowed in FF, so if we
  2217. // were opened from another domain then this will fail.
  2218. try
  2219. {
  2220. // If the create URL param is used in embed mode then
  2221. // we try to open the XML from window.opener[value].
  2222. // Use this for embedding via tab to bypass the timing
  2223. // issues when passing messages without onload event.
  2224. if (window.opener != null)
  2225. {
  2226. var value = urlParams['create'];
  2227. if (value != null)
  2228. {
  2229. value = decodeURIComponent(value);
  2230. }
  2231. if (value != null && value.length > 0 && value.substring(0, 7) != 'http://' &&
  2232. value.substring(0, 8) != 'https://')
  2233. {
  2234. var doc = mxUtils.parseXml(window.opener[value]);
  2235. this.editor.setGraphXml(doc.documentElement);
  2236. }
  2237. else if (window.opener.openFile != null)
  2238. {
  2239. window.opener.openFile.setConsumer(mxUtils.bind(this, function(xml, filename, temp)
  2240. {
  2241. this.spinner.stop();
  2242. if (filename == null)
  2243. {
  2244. var title = urlParams['title'];
  2245. temp = true;
  2246. if (title != null)
  2247. {
  2248. filename = decodeURIComponent(title);
  2249. }
  2250. else
  2251. {
  2252. filename = this.defaultFilename;
  2253. }
  2254. }
  2255. // Replaces PNG with XML extension
  2256. var dot = (!this.useCanvasForExport) ? filename.substring(filename.length - 4) == '.png' : -1;
  2257. if (dot > 0)
  2258. {
  2259. filename = filename.substring(0, filename.length - 4) + '.drawio';
  2260. }
  2261. this.fileLoaded((mxClient.IS_IOS) ?
  2262. new StorageFile(this, xml, filename) :
  2263. new LocalFile(this, xml, filename, temp));
  2264. }));
  2265. }
  2266. }
  2267. }
  2268. catch(e)
  2269. {
  2270. // ignore
  2271. }
  2272. };
  2273. App.prototype.loadGapi = function(then)
  2274. {
  2275. if (typeof gapi !== 'undefined')
  2276. {
  2277. gapi.load(((urlParams['picker'] != '0') ? 'picker,': '') + App.GOOGLE_APIS, then);
  2278. }
  2279. };
  2280. /**
  2281. * Main function. Program starts here.
  2282. *
  2283. * @param {number} dx X-coordinate of the translation.
  2284. * @param {number} dy Y-coordinate of the translation.
  2285. */
  2286. App.prototype.load = function()
  2287. {
  2288. // Checks if we're running in embedded mode
  2289. if (urlParams['embed'] != '1')
  2290. {
  2291. if (this.spinner.spin(document.body, mxResources.get('starting')))
  2292. {
  2293. try
  2294. {
  2295. this.stateArg = (urlParams['state'] != null && this.drive != null) ? JSON.parse(decodeURIComponent(urlParams['state'])) : null;
  2296. }
  2297. catch (e)
  2298. {
  2299. // ignores invalid state args
  2300. }
  2301. this.editor.graph.setEnabled(this.getCurrentFile() != null);
  2302. // Passes the userId from the state parameter to the client
  2303. if ((window.location.hash == null || window.location.hash.length == 0) &&
  2304. this.drive != null && this.stateArg != null && this.stateArg.userId != null)
  2305. {
  2306. this.drive.setUserId(this.stateArg.userId);
  2307. }
  2308. // Legacy support for fileId parameter which is moved to the hash tag
  2309. if (urlParams['fileId'] != null)
  2310. {
  2311. window.location.hash = 'G' + urlParams['fileId'];
  2312. window.location.search = this.getSearch(['fileId']);
  2313. }
  2314. else
  2315. {
  2316. // Asynchronous or disabled loading of client
  2317. if (this.drive == null)
  2318. {
  2319. if (this.mode == App.MODE_GOOGLE)
  2320. {
  2321. this.mode = null;
  2322. }
  2323. this.start();
  2324. }
  2325. else
  2326. {
  2327. this.loadGapi(mxUtils.bind(this, function()
  2328. {
  2329. this.start();
  2330. }));
  2331. }
  2332. }
  2333. }
  2334. }
  2335. else
  2336. {
  2337. this.restoreLibraries();
  2338. if (urlParams['gapi'] == '1')
  2339. {
  2340. this.loadGapi(function() {});
  2341. }
  2342. }
  2343. };
  2344. /**
  2345. * Adds the listener for automatically saving the diagram for local changes.
  2346. */
  2347. App.prototype.showRefreshDialog = function(title, message)
  2348. {
  2349. if (!this.showingRefreshDialog)
  2350. {
  2351. this.showingRefreshDialog = true;
  2352. this.showError(title || mxResources.get('externalChanges'),
  2353. message || mxResources.get('redirectToNewApp'),
  2354. mxResources.get('refresh'), mxUtils.bind(this, function()
  2355. {
  2356. var file = this.getCurrentFile();
  2357. if (file != null)
  2358. {
  2359. file.setModified(false);
  2360. }
  2361. this.spinner.spin(document.body, mxResources.get('connecting'));
  2362. this.editor.graph.setEnabled(false);
  2363. window.location.reload();
  2364. }), null, null, null, null, null, 340, 180);
  2365. // Adds important notice to dialog
  2366. if (this.dialog != null && this.dialog.container != null)
  2367. {
  2368. var alert = this.createRealtimeNotice();
  2369. alert.style.left = '0';
  2370. alert.style.right = '0';
  2371. alert.style.borderRadius = '0';
  2372. alert.style.borderLeftStyle = 'none';
  2373. alert.style.borderRightStyle = 'none';
  2374. alert.style.marginBottom = '26px';
  2375. alert.style.padding = '8px 0 8px 0';
  2376. this.dialog.container.appendChild(alert);
  2377. }
  2378. }
  2379. };
  2380. /**
  2381. * Called in start after the spinner stops.
  2382. */
  2383. App.prototype.showAlert = function(message)
  2384. {
  2385. if (message != null && message.length > 0)
  2386. {
  2387. var div = document.createElement('div');
  2388. div.className = 'geAlert';
  2389. div.style.zIndex = 2e9;
  2390. div.style.left = '50%';
  2391. div.style.top = '-100%';
  2392. mxUtils.setPrefixedStyle(div.style, 'transform', 'translate(-50%,0%)');
  2393. mxUtils.setPrefixedStyle(div.style, 'transition', 'all 1s ease');
  2394. div.innerHTML = message;
  2395. var close = document.createElement('a');
  2396. close.className = 'geAlertLink';
  2397. close.style.textAlign = 'right';
  2398. close.style.marginTop = '20px';
  2399. close.style.display = 'block';
  2400. close.setAttribute('title', mxResources.get('close'));
  2401. close.innerHTML = mxResources.get('close');
  2402. div.appendChild(close);
  2403. mxEvent.addListener(close, 'click', function(evt)
  2404. {
  2405. if (div.parentNode != null)
  2406. {
  2407. div.parentNode.removeChild(div);
  2408. mxEvent.consume(evt);
  2409. }
  2410. });
  2411. document.body.appendChild(div);
  2412. // Delayed to get smoother animation after DOM rendering
  2413. window.setTimeout(function()
  2414. {
  2415. div.style.top = '30px';
  2416. }, 10);
  2417. // Fades out the alert after 15 secs
  2418. window.setTimeout(function()
  2419. {
  2420. mxUtils.setPrefixedStyle(div.style, 'transition', 'all 2s ease');
  2421. div.style.opacity = '0';
  2422. window.setTimeout(function()
  2423. {
  2424. if (div.parentNode != null)
  2425. {
  2426. div.parentNode.removeChild(div);
  2427. }
  2428. }, 2000);
  2429. }, 15000);
  2430. }
  2431. };
  2432. /**
  2433. * Translates this point by the given vector.
  2434. *
  2435. * @param {number} dx X-coordinate of the translation.
  2436. * @param {number} dy Y-coordinate of the translation.
  2437. */
  2438. App.prototype.start = function()
  2439. {
  2440. if (this.bg != null && this.bg.parentNode != null)
  2441. {
  2442. this.bg.parentNode.removeChild(this.bg);
  2443. }
  2444. this.restoreLibraries();
  2445. this.spinner.stop();
  2446. try
  2447. {
  2448. // Handles all errors
  2449. var ui = this;
  2450. window.onerror = function(message, url, linenumber, colno, err)
  2451. {
  2452. EditorUi.logError('Uncaught: ' + ((message != null) ? message : ''),
  2453. url, linenumber, colno, err, null, true);
  2454. ui.handleError({message: message}, mxResources.get('unknownError'),
  2455. null, null, null, null, true);
  2456. };
  2457. // Listens to changes of the hash if not in embed or client mode
  2458. if (urlParams['client'] != '1' && urlParams['embed'] != '1')
  2459. {
  2460. // Installs listener to claim current draft if there is one
  2461. try
  2462. {
  2463. if (isLocalStorage)
  2464. {
  2465. window.addEventListener('storage', mxUtils.bind(this, function(evt)
  2466. {
  2467. var file = this.getCurrentFile();
  2468. EditorUi.debug('storage event', evt, file);
  2469. if (file != null && evt.key == '.draft-alive-check' && evt.newValue != null && file.draftId != null)
  2470. {
  2471. this.draftAliveCheck = evt.newValue;
  2472. file.saveDraft();
  2473. }
  2474. }));
  2475. }
  2476. if (!mxClient.IS_CHROMEAPP && !EditorUi.isElectronApp && !this.isOfflineApp() &&
  2477. urlParams['open'] == null && /www\.draw\.io$/.test(window.location.hostname) &&
  2478. (!this.editor.chromeless || this.editor.editable))
  2479. {
  2480. this.showNameChangeBanner();
  2481. }
  2482. }
  2483. catch (e)
  2484. {
  2485. // ignore
  2486. }
  2487. // KNOWN: Does not work in quirks mode
  2488. mxEvent.addListener(window, 'hashchange', mxUtils.bind(this, function(evt)
  2489. {
  2490. try
  2491. {
  2492. this.hideDialog();
  2493. var id = this.getDiagramId();
  2494. var file = this.getCurrentFile();
  2495. if (file == null || file.getHash() != id)
  2496. {
  2497. this.loadFile(id, true);
  2498. }
  2499. }
  2500. catch (e)
  2501. {
  2502. // Workaround for possible scrollWidth of null in Dialog ctor
  2503. if (document.body != null)
  2504. {
  2505. this.handleError(e, mxResources.get('errorLoadingFile'), mxUtils.bind(this, function()
  2506. {
  2507. var file = this.getCurrentFile();
  2508. window.location.hash = (file != null) ? file.getHash() : '';
  2509. }));
  2510. }
  2511. }
  2512. }));
  2513. }
  2514. // Redirects old url URL parameter to new #U format
  2515. if ((window.location.hash == null || window.location.hash.length <= 1) && urlParams['url'] != null)
  2516. {
  2517. this.loadFile('U' + urlParams['url'], true);
  2518. }
  2519. else if (this.getCurrentFile() == null)
  2520. {
  2521. var done = mxUtils.bind(this, function()
  2522. {
  2523. // Starts in client mode and waits for data
  2524. if (urlParams['client'] == '1' && (window.location.hash == null ||
  2525. window.location.hash.length == 0 || window.location.hash.substring(0, 2) == '#P'))
  2526. {
  2527. var doLoadFile = mxUtils.bind(this, function(xml)
  2528. {
  2529. // Extracts graph model from PNG
  2530. if (xml.substring(0, 22) == 'data:image/png;base64,')
  2531. {
  2532. xml = this.extractGraphModelFromPng(xml);
  2533. }
  2534. var title = urlParams['title'];
  2535. if (title != null)
  2536. {
  2537. title = decodeURIComponent(title);
  2538. }
  2539. else
  2540. {
  2541. title = this.defaultFilename;
  2542. }
  2543. var file = new LocalFile(this, xml, title, true);
  2544. if (window.location.hash != null && window.location.hash.substring(0, 2) == '#P')
  2545. {
  2546. file.getHash = function()
  2547. {
  2548. return window.location.hash.substring(1);
  2549. };
  2550. }
  2551. this.fileLoaded(file);
  2552. this.getCurrentFile().setModified(!this.editor.chromeless);
  2553. });
  2554. var parent = window.opener || window.parent;
  2555. if (parent != window)
  2556. {
  2557. var value = urlParams['create'];
  2558. if (value != null)
  2559. {
  2560. doLoadFile(parent[decodeURIComponent(value)]);
  2561. }
  2562. else
  2563. {
  2564. value = urlParams['data'];
  2565. if (value != null)
  2566. {
  2567. doLoadFile(decodeURIComponent(value));
  2568. }
  2569. else
  2570. {
  2571. this.installMessageHandler(mxUtils.bind(this, function(xml, evt)
  2572. {
  2573. // Ignores messages from other windows
  2574. if (evt.source == parent)
  2575. {
  2576. doLoadFile(xml);
  2577. }
  2578. }));
  2579. }
  2580. }
  2581. }
  2582. }
  2583. // Checks if no earlier loading errors are showing
  2584. else if (this.dialog == null)
  2585. {
  2586. if (urlParams['demo'] == '1')
  2587. {
  2588. var prev = Editor.useLocalStorage;
  2589. this.createFile(this.defaultFilename, null, null, null, null, null, null, true);
  2590. Editor.useLocalStorage = prev;
  2591. }
  2592. else
  2593. {
  2594. var waiting = false;
  2595. // Checks if we're waiting for some asynchronous file to be loaded
  2596. // Cross-domain window access is not allowed in FF, so if we
  2597. // were opened from another domain then this will fail.
  2598. try
  2599. {
  2600. waiting = window.opener != null && window.opener.openFile != null;
  2601. }
  2602. catch(e)
  2603. {
  2604. // ignore
  2605. }
  2606. if (waiting)
  2607. {
  2608. // Spinner is stopped in App.open
  2609. this.spinner.spin(document.body, mxResources.get('loading'))
  2610. }
  2611. else
  2612. {
  2613. var id = this.getDiagramId();
  2614. if (EditorUi.enableDrafts && (urlParams['mode'] == null || EditorUi.isElectronApp) &&
  2615. this.getServiceName() == 'draw.io' && (id == null || id.length == 0) &&
  2616. !this.editor.isChromelessView())
  2617. {
  2618. this.checkDrafts();
  2619. }
  2620. else if (id != null && id.length > 0)
  2621. {
  2622. this.loadFile(id, null, null, mxUtils.bind(this, function()
  2623. {
  2624. var temp = decodeURIComponent(urlParams['viewbox'] || '');
  2625. if (temp != '')
  2626. {
  2627. try
  2628. {
  2629. var bounds = JSON.parse(temp);
  2630. this.editor.graph.fitWindow(bounds, bounds.border);
  2631. }
  2632. catch (e)
  2633. {
  2634. // Ignore invalid viewport
  2635. console.error(e);
  2636. }
  2637. }
  2638. }));
  2639. }
  2640. else if (urlParams['splash'] != '0')
  2641. {
  2642. this.loadFile();
  2643. }
  2644. else
  2645. {
  2646. this.createFile(this.defaultFilename, this.getFileData(), null, null, null, null, null, true);
  2647. }
  2648. }
  2649. }
  2650. }
  2651. });
  2652. var value = decodeURIComponent(urlParams['create'] || '');
  2653. if ((window.location.hash == null || window.location.hash.length <= 1) &&
  2654. value != null && value.length > 0 && this.spinner.spin(document.body, mxResources.get('loading')))
  2655. {
  2656. var reconnect = mxUtils.bind(this, function()
  2657. {
  2658. // Removes URL parameter and reloads the page
  2659. if (this.spinner.spin(document.body, mxResources.get('reconnecting')))
  2660. {
  2661. window.location.search = this.getSearch(['create', 'title']);
  2662. };
  2663. });
  2664. var showCreateDialog = mxUtils.bind(this, function(xml)
  2665. {
  2666. this.spinner.stop();
  2667. // Resets mode for dialog - local file is only for preview
  2668. if (urlParams['splash'] != '0')
  2669. {
  2670. this.fileLoaded(new LocalFile(this, xml, null));
  2671. this.editor.graph.setEnabled(false);
  2672. this.mode = urlParams['mode'];
  2673. var title = urlParams['title'];
  2674. if (title != null)
  2675. {
  2676. title = decodeURIComponent(title);
  2677. }
  2678. else
  2679. {
  2680. title = this.defaultFilename;
  2681. }
  2682. var serviceCount = this.getServiceCount(true);
  2683. if (isLocalStorage)
  2684. {
  2685. serviceCount++;
  2686. }
  2687. var rowLimit = (serviceCount <= 4) ? 2 : (serviceCount > 6 ? 4 : 3);
  2688. var dlg = new CreateDialog(this, title, mxUtils.bind(this, function(filename, mode)
  2689. {
  2690. if (mode == null)
  2691. {
  2692. this.hideDialog();
  2693. var prev = Editor.useLocalStorage;
  2694. this.createFile((filename.length > 0) ? filename : this.defaultFilename,
  2695. this.getFileData(), null, null, null, true, null, true);
  2696. Editor.useLocalStorage = prev;
  2697. }
  2698. else
  2699. {
  2700. this.pickFolder(mode, mxUtils.bind(this, function(folderId)
  2701. {
  2702. this.createFile(filename, this.getFileData(true),
  2703. null, mode, null, true, folderId);
  2704. }));
  2705. }
  2706. }), null, null, null, null, urlParams['browser'] == '1',
  2707. null, null, true, rowLimit, null, null, null,
  2708. this.editor.fileExtensions);
  2709. this.showDialog(dlg.container, 400, (serviceCount > rowLimit) ? 390 : 270,
  2710. true, false, mxUtils.bind(this, function(cancel)
  2711. {
  2712. if (cancel && this.getCurrentFile() == null)
  2713. {
  2714. this.showSplash();
  2715. }
  2716. }));
  2717. dlg.init();
  2718. }
  2719. });
  2720. value = decodeURIComponent(value);
  2721. if (value.substring(0, 7) != 'http://' && value.substring(0, 8) != 'https://')
  2722. {
  2723. // Cross-domain window access is not allowed in FF, so if we
  2724. // were opened from another domain then this will fail.
  2725. try
  2726. {
  2727. if (window.opener != null && window.opener[value] != null)
  2728. {
  2729. showCreateDialog(window.opener[value]);
  2730. }
  2731. else
  2732. {
  2733. this.handleError(null, mxResources.get('errorLoadingFile'));
  2734. }
  2735. }
  2736. catch (e)
  2737. {
  2738. this.handleError(e, mxResources.get('errorLoadingFile'));
  2739. }
  2740. }
  2741. else
  2742. {
  2743. this.loadTemplate(value, function(text)
  2744. {
  2745. showCreateDialog(text);
  2746. }, mxUtils.bind(this, function()
  2747. {
  2748. this.handleError(null, mxResources.get('errorLoadingFile'), reconnect);
  2749. }));
  2750. }
  2751. }
  2752. else
  2753. {
  2754. // Passes the fileId from the state parameter to the hash tag and reloads
  2755. // the page without the state parameter
  2756. if ((window.location.hash == null || window.location.hash.length <= 1) &&
  2757. urlParams['state'] != null && this.stateArg != null && this.stateArg.action == 'open')
  2758. {
  2759. if (this.stateArg.ids != null)
  2760. {
  2761. if (window.history && window.history.replaceState)
  2762. {
  2763. // Removes state URL parameter without reloading the page
  2764. window.history.replaceState(null, null, window.location.pathname +
  2765. this.getSearch(['state']));
  2766. }
  2767. window.location.hash = 'G' + this.stateArg.ids[0];
  2768. }
  2769. }
  2770. else if ((window.location.hash == null || window.location.hash.length <= 1) &&
  2771. this.drive != null && this.stateArg != null && this.stateArg.action == 'create')
  2772. {
  2773. if (window.history && window.history.replaceState)
  2774. {
  2775. // Removes state URL parameter without reloading the page
  2776. window.history.replaceState(null, null, window.location.pathname +
  2777. this.getSearch(['state']));
  2778. }
  2779. this.setMode(App.MODE_GOOGLE);
  2780. this.actions.get('new').funct();
  2781. }
  2782. else
  2783. {
  2784. // Removes open URL parameter. Hash is also updated in Init to load client.
  2785. if (urlParams['open'] != null && window.history && window.history.replaceState)
  2786. {
  2787. window.history.replaceState(null, null, window.location.pathname +
  2788. this.getSearch(['open']));
  2789. window.location.hash = urlParams['open'];
  2790. }
  2791. done();
  2792. }
  2793. }
  2794. }
  2795. }
  2796. catch (e)
  2797. {
  2798. this.handleError(e);
  2799. }
  2800. };
  2801. /**
  2802. * Checks for orphaned drafts.
  2803. */
  2804. App.prototype.loadDraft = function(xml, success)
  2805. {
  2806. this.createFile(this.defaultFilename, xml, null, null, mxUtils.bind(this, function()
  2807. {
  2808. window.setTimeout(mxUtils.bind(this, function()
  2809. {
  2810. var file = this.getCurrentFile();
  2811. if (file != null)
  2812. {
  2813. file.fileChanged();
  2814. if (success != null)
  2815. {
  2816. success();
  2817. }
  2818. }
  2819. }), 0);
  2820. }), null, null, true);
  2821. };
  2822. /**
  2823. * Checks for orphaned drafts.
  2824. */
  2825. App.prototype.checkDrafts = function()
  2826. {
  2827. try
  2828. {
  2829. // Triggers storage event for other windows to mark active drafts
  2830. var guid = Editor.guid();
  2831. localStorage.setItem('.draft-alive-check', guid);
  2832. window.setTimeout(mxUtils.bind(this, function()
  2833. {
  2834. localStorage.removeItem('.draft-alive-check');
  2835. this.getDatabaseItems(mxUtils.bind(this, function(items)
  2836. {
  2837. // Collects orphaned drafts
  2838. var drafts = [];
  2839. for (var i = 0; i < items.length; i++)
  2840. {
  2841. try
  2842. {
  2843. var key = items[i].key;
  2844. if (key != null && key.substring(0, 7) == '.draft_')
  2845. {
  2846. var obj = JSON.parse(items[i].data);
  2847. if (obj != null && obj.type == 'draft' && obj.aliveCheck != guid)
  2848. {
  2849. obj.key = key;
  2850. drafts.push(obj);
  2851. }
  2852. }
  2853. }
  2854. catch (e)
  2855. {
  2856. // ignore
  2857. }
  2858. }
  2859. if (drafts.length == 1)
  2860. {
  2861. this.loadDraft(drafts[0].data, mxUtils.bind(this, function()
  2862. {
  2863. this.removeDatabaseItem(drafts[0].key);
  2864. }));
  2865. }
  2866. else if (drafts.length > 1)
  2867. {
  2868. var ts = new Date(drafts[0].modified);
  2869. var dlg = new DraftDialog(this, (drafts.length > 1) ? mxResources.get('selectDraft') :
  2870. mxResources.get('draftFound', [ts.toLocaleDateString() + ' ' + ts.toLocaleTimeString()]),
  2871. (drafts.length > 1) ? null : drafts[0].data, mxUtils.bind(this, function(index)
  2872. {
  2873. this.hideDialog();
  2874. index = (index != '') ? index : 0;
  2875. this.loadDraft(drafts[index].data, mxUtils.bind(this, function()
  2876. {
  2877. this.removeDatabaseItem(drafts[index].key);
  2878. }));
  2879. }), mxUtils.bind(this, function(index, success)
  2880. {
  2881. index = (index != '') ? index : 0;
  2882. // Discard draft
  2883. this.confirm(mxResources.get('areYouSure'), null, mxUtils.bind(this, function()
  2884. {
  2885. this.removeDatabaseItem(drafts[index].key);
  2886. if (success != null)
  2887. {
  2888. success();
  2889. }
  2890. }), mxResources.get('no'), mxResources.get('yes'));
  2891. }), null, null, null, (drafts.length > 1) ? drafts : null);
  2892. this.showDialog(dlg.container, 640, 480, true, false, mxUtils.bind(this, function(cancel)
  2893. {
  2894. if (urlParams['splash'] != '0')
  2895. {
  2896. this.loadFile();
  2897. }
  2898. else
  2899. {
  2900. this.createFile(this.defaultFilename, this.getFileData(), null, null, null, null, null, true);
  2901. }
  2902. }));
  2903. dlg.init();
  2904. }
  2905. else if (urlParams['splash'] != '0')
  2906. {
  2907. this.loadFile();
  2908. }
  2909. else
  2910. {
  2911. this.createFile(this.defaultFilename, this.getFileData(), null, null, null, null, null, true);
  2912. }
  2913. }), mxUtils.bind(this, function()
  2914. {
  2915. if (urlParams['splash'] != '0')
  2916. {
  2917. this.loadFile();
  2918. }
  2919. else
  2920. {
  2921. this.createFile(this.defaultFilename, this.getFileData(), null, null, null, null, null, true);
  2922. }
  2923. }));
  2924. }), 0);
  2925. }
  2926. catch (e)
  2927. {
  2928. // ignore
  2929. }
  2930. };
  2931. /**
  2932. * Translates this point by the given vector.
  2933. *
  2934. * @param {number} dx X-coordinate of the translation.
  2935. * @param {number} dy Y-coordinate of the translation.
  2936. */
  2937. App.prototype.showSplash = function(force)
  2938. {
  2939. var serviceCount = this.getServiceCount(true);
  2940. var showSecondDialog = mxUtils.bind(this, function()
  2941. {
  2942. var dlg = new SplashDialog(this);
  2943. this.showDialog(dlg.container, 340, (mxClient.IS_CHROMEAPP || EditorUi.isElectronApp) ? 200 : 230, true, true,
  2944. mxUtils.bind(this, function(cancel)
  2945. {
  2946. // Creates a blank diagram if the dialog is closed
  2947. if (cancel && !mxClient.IS_CHROMEAPP)
  2948. {
  2949. var prev = Editor.useLocalStorage;
  2950. this.createFile(this.defaultFilename + (EditorUi.isElectronApp? '.drawio' : ''), null, null, null, null, null, null,
  2951. urlParams['local'] != '1');
  2952. Editor.useLocalStorage = prev;
  2953. }
  2954. }), true);
  2955. if (!mxClient.IS_CHROMEAPP && !EditorUi.isElectronApp && !this.isOfflineApp() &&
  2956. !mxClient.IS_ANDROID && !mxClient.IS_IOS &&
  2957. this.mode == App.MODE_DEVICE)
  2958. {
  2959. this.showDownloadDesktopBanner();
  2960. }
  2961. });
  2962. if (this.editor.isChromelessView())
  2963. {
  2964. this.handleError({message: mxResources.get('noFileSelected')},
  2965. mxResources.get('errorLoadingFile'), mxUtils.bind(this, function()
  2966. {
  2967. this.showSplash();
  2968. }));
  2969. }
  2970. else if (!mxClient.IS_CHROMEAPP && (this.mode == null || force))
  2971. {
  2972. var rowLimit = (serviceCount == 4) ? 2 : 3;
  2973. var dlg = new StorageDialog(this, mxUtils.bind(this, function()
  2974. {
  2975. this.hideDialog();
  2976. showSecondDialog();
  2977. }), rowLimit);
  2978. this.showDialog(dlg.container, (rowLimit < 3) ? 200 : 300,
  2979. ((serviceCount > 3) ? 320 : 210), true, false);
  2980. }
  2981. else if (urlParams['create'] == null)
  2982. {
  2983. showSecondDialog();
  2984. }
  2985. };
  2986. /**
  2987. * Translates this point by the given vector.
  2988. *
  2989. * @param {number} dx X-coordinate of the translation.
  2990. * @param {number} dy Y-coordinate of the translation.
  2991. */
  2992. App.prototype.addLanguageMenu = function(elt, addLabel)
  2993. {
  2994. var img = null;
  2995. var langMenu = this.menus.get('language');
  2996. if (langMenu != null)
  2997. {
  2998. img = document.createElement('div');
  2999. img.setAttribute('title', mxResources.get('language'));
  3000. img.className = 'geIcon geSprite geSprite-globe';
  3001. img.style.position = 'absolute';
  3002. img.style.cursor = 'pointer';
  3003. img.style.bottom = '20px';
  3004. img.style.right = '20px';
  3005. if (addLabel)
  3006. {
  3007. img.style.direction = 'rtl';
  3008. img.style.textAlign = 'right';
  3009. img.style.right = '24px';
  3010. var label = document.createElement('span');
  3011. label.style.display = 'inline-block';
  3012. label.style.fontSize = '12px';
  3013. label.style.margin = '5px 24px 0 0';
  3014. label.style.color = 'gray';
  3015. label.style.userSelect = 'none';
  3016. mxUtils.write(label, mxResources.get('language'));
  3017. img.appendChild(label);
  3018. }
  3019. mxEvent.addListener(img, 'click', mxUtils.bind(this, function(evt)
  3020. {
  3021. this.editor.graph.popupMenuHandler.hideMenu();
  3022. var menu = new mxPopupMenu(this.menus.get('language').funct);
  3023. menu.div.className += ' geMenubarMenu';
  3024. menu.smartSeparators = true;
  3025. menu.showDisabled = true;
  3026. menu.autoExpand = true;
  3027. // Disables autoexpand and destroys menu when hidden
  3028. menu.hideMenu = mxUtils.bind(this, function()
  3029. {
  3030. mxPopupMenu.prototype.hideMenu.apply(menu, arguments);
  3031. menu.destroy();
  3032. });
  3033. var offset = mxUtils.getOffset(img);
  3034. menu.popup(offset.x, offset.y + img.offsetHeight, null, evt);
  3035. // Allows hiding by clicking on document
  3036. this.setCurrentMenu(menu);
  3037. }));
  3038. elt.appendChild(img);
  3039. }
  3040. return img;
  3041. };
  3042. /**
  3043. * Loads the given file handle as a local file.
  3044. */
  3045. App.prototype.loadFileSystemEntry = function(fileHandle, success, error)
  3046. {
  3047. error = (error != null) ? error : mxUtils.bind(this, function(e)
  3048. {
  3049. this.handleError(e);
  3050. });
  3051. fileHandle.getFile().then(mxUtils.bind(this, function(file)
  3052. {
  3053. var reader = new FileReader();
  3054. reader.onload = mxUtils.bind(this, function(e)
  3055. {
  3056. try
  3057. {
  3058. if (success != null)
  3059. {
  3060. var data = e.target.result;
  3061. if (file.type.substring(0, 6) == 'image/')
  3062. {
  3063. data = this.extractGraphModelFromPng(data);
  3064. }
  3065. success(new LocalFile(this, data, file.name, null, fileHandle, file));
  3066. }
  3067. else
  3068. {
  3069. this.openFileHandle(e.target.result, file.name, file, false, fileHandle);
  3070. }
  3071. }
  3072. catch(e)
  3073. {
  3074. error(e);
  3075. }
  3076. });
  3077. reader.onerror = error;
  3078. if ((file.type.substring(0, 5) === 'image' ||
  3079. file.type === 'application/pdf') &&
  3080. file.type.substring(0, 9) !== 'image/svg')
  3081. {
  3082. reader.readAsDataURL(file);
  3083. }
  3084. else
  3085. {
  3086. reader.readAsText(file);
  3087. }
  3088. }), error);
  3089. };
  3090. /**
  3091. * Loads the given file handle as a local file.
  3092. */
  3093. App.prototype.createFileSystemOptions = function(name)
  3094. {
  3095. var ext = [];
  3096. var temp = null;
  3097. if (name != null)
  3098. {
  3099. var idx = name.lastIndexOf('.');
  3100. if (idx > 0)
  3101. {
  3102. temp = name.substring(idx + 1);
  3103. }
  3104. }
  3105. for (var i = 0; i < this.editor.diagramFileTypes.length; i++)
  3106. {
  3107. var obj = {description: mxResources.get(this.editor.diagramFileTypes[i].description) +
  3108. ((mxClient.IS_MAC) ? ' (.' + this.editor.diagramFileTypes[i].extension + ')' : ''),
  3109. extensions: [this.editor.diagramFileTypes[i].extension]};
  3110. if (this.editor.diagramFileTypes[i].extension == temp)
  3111. {
  3112. ext.splice(0, 0, obj);
  3113. }
  3114. else
  3115. {
  3116. ext.push(obj);
  3117. }
  3118. }
  3119. // TODO: Specify default filename
  3120. return {type: 'save-file', accepts: ext, fileName: name};
  3121. };
  3122. /**
  3123. * Loads the given file handle as a local file.
  3124. */
  3125. App.prototype.chooseFileSystemEntries = function(success, error, opts)
  3126. {
  3127. error = (error != null) ? error : mxUtils.bind(this, function(e)
  3128. {
  3129. if (e.name != 'AbortError')
  3130. {
  3131. this.handleError(e);
  3132. }
  3133. });
  3134. opts = (opts != null) ? opts : this.createFileSystemOptions();
  3135. // LATER: Specify default name via options
  3136. window.chooseFileSystemEntries(opts).then(mxUtils.bind(this, function(fileHandle)
  3137. {
  3138. fileHandle.getFile().then(mxUtils.bind(this, function(desc)
  3139. {
  3140. success(fileHandle, desc);
  3141. }), error);
  3142. }), error);
  3143. };
  3144. /**
  3145. * Translates this point by the given vector.
  3146. *
  3147. * @param {number} dx X-coordinate of the translation.
  3148. * @param {number} dy Y-coordinate of the translation.
  3149. */
  3150. App.prototype.pickFile = function(mode)
  3151. {
  3152. try
  3153. {
  3154. mode = (mode != null) ? mode : this.mode;
  3155. if (mode == App.MODE_GOOGLE)
  3156. {
  3157. if (this.drive != null && typeof(google) != 'undefined' && typeof(google.picker) != 'undefined')
  3158. {
  3159. this.drive.pickFile();
  3160. }
  3161. else
  3162. {
  3163. this.openLink('https://drive.google.com');
  3164. }
  3165. }
  3166. else
  3167. {
  3168. var peer = this.getPeerForMode(mode);
  3169. if (peer != null)
  3170. {
  3171. peer.pickFile();
  3172. }
  3173. else if (mode == App.MODE_DEVICE && 'chooseFileSystemEntries' in window)
  3174. {
  3175. window.chooseFileSystemEntries().then(mxUtils.bind(this, function(fileHandle)
  3176. {
  3177. if (this.spinner.spin(document.body, mxResources.get('loading')))
  3178. {
  3179. this.loadFileSystemEntry(fileHandle);
  3180. }
  3181. }), mxUtils.bind(this, function(e)
  3182. {
  3183. if (e.name != 'AbortError')
  3184. {
  3185. this.handleError(e);
  3186. }
  3187. }));
  3188. }
  3189. else if (mode == App.MODE_DEVICE && Graph.fileSupport)
  3190. {
  3191. if (this.openFileInputElt == null)
  3192. {
  3193. var input = document.createElement('input');
  3194. input.setAttribute('type', 'file');
  3195. mxEvent.addListener(input, 'change', mxUtils.bind(this, function()
  3196. {
  3197. if (input.files != null)
  3198. {
  3199. this.openFiles(input.files);
  3200. // Resets input to force change event for
  3201. // same file (type reset required for IE)
  3202. input.type = '';
  3203. input.type = 'file';
  3204. input.value = '';
  3205. }
  3206. }));
  3207. input.style.display = 'none';
  3208. document.body.appendChild(input);
  3209. this.openFileInputElt = input;
  3210. }
  3211. this.openFileInputElt.click();
  3212. }
  3213. else
  3214. {
  3215. this.hideDialog();
  3216. window.openNew = this.getCurrentFile() != null && !this.isDiagramEmpty();
  3217. window.baseUrl = this.getUrl();
  3218. window.openKey = 'open';
  3219. window.listBrowserFiles = mxUtils.bind(this, function(success, error)
  3220. {
  3221. StorageFile.listFiles(this, 'F', success, error);
  3222. });
  3223. window.openBrowserFile = mxUtils.bind(this, function(title, success, error)
  3224. {
  3225. StorageFile.getFileContent(this, title, success, error);
  3226. });
  3227. window.deleteBrowserFile = mxUtils.bind(this, function(title, success, error)
  3228. {
  3229. StorageFile.deleteFile(this, title, success, error);
  3230. });
  3231. var prevValue = Editor.useLocalStorage;
  3232. Editor.useLocalStorage = (mode == App.MODE_BROWSER);
  3233. this.openFile();
  3234. // Installs local handler for opened files in same window
  3235. window.openFile.setConsumer(mxUtils.bind(this, function(xml, filename)
  3236. {
  3237. // Replaces PNG with XML extension
  3238. var dot = !this.useCanvasForExport && filename.substring(filename.length - 4) == '.png';
  3239. if (dot)
  3240. {
  3241. filename = filename.substring(0, filename.length - 4) + '.drawio';
  3242. }
  3243. this.fileLoaded((mode == App.MODE_BROWSER) ?
  3244. new StorageFile(this, xml, filename) :
  3245. new LocalFile(this, xml, filename));
  3246. }));
  3247. // Extends dialog close to show splash screen
  3248. var dlg = this.dialog;
  3249. var dlgClose = dlg.close;
  3250. this.dialog.close = mxUtils.bind(this, function(cancel)
  3251. {
  3252. Editor.useLocalStorage = prevValue;
  3253. dlgClose.apply(dlg, arguments);
  3254. if (this.getCurrentFile() == null)
  3255. {
  3256. this.showSplash();
  3257. }
  3258. });
  3259. }
  3260. }
  3261. }
  3262. catch (e)
  3263. {
  3264. this.handleError(e);
  3265. }
  3266. };
  3267. /**
  3268. * Translates this point by the given vector.
  3269. *
  3270. * @param {number} dx X-coordinate of the translation.
  3271. * @param {number} dy Y-coordinate of the translation.
  3272. */
  3273. App.prototype.pickLibrary = function(mode)
  3274. {
  3275. mode = (mode != null) ? mode : this.mode;
  3276. if (mode == App.MODE_GOOGLE || mode == App.MODE_DROPBOX || mode == App.MODE_ONEDRIVE ||
  3277. mode == App.MODE_GITHUB || mode == App.MODE_GITLAB || mode == App.MODE_TRELLO)
  3278. {
  3279. var peer = (mode == App.MODE_GOOGLE) ? this.drive :
  3280. ((mode == App.MODE_ONEDRIVE) ? this.oneDrive :
  3281. ((mode == App.MODE_GITHUB) ? this.gitHub :
  3282. ((mode == App.MODE_GITLAB) ? this.gitLab :
  3283. ((mode == App.MODE_TRELLO) ? this.trello :
  3284. this.dropbox))));
  3285. if (peer != null)
  3286. {
  3287. peer.pickLibrary(mxUtils.bind(this, function(id, optionalFile)
  3288. {
  3289. if (optionalFile != null)
  3290. {
  3291. try
  3292. {
  3293. this.loadLibrary(optionalFile);
  3294. }
  3295. catch (e)
  3296. {
  3297. this.handleError(e, mxResources.get('errorLoadingFile'));
  3298. }
  3299. }
  3300. else
  3301. {
  3302. if (this.spinner.spin(document.body, mxResources.get('loading')))
  3303. {
  3304. peer.getLibrary(id, mxUtils.bind(this, function(file)
  3305. {
  3306. this.spinner.stop();
  3307. try
  3308. {
  3309. this.loadLibrary(file);
  3310. }
  3311. catch (e)
  3312. {
  3313. this.handleError(e, mxResources.get('errorLoadingFile'));
  3314. }
  3315. }), mxUtils.bind(this, function(resp)
  3316. {
  3317. this.handleError(resp, (resp != null) ? mxResources.get('errorLoadingFile') : null);
  3318. }));
  3319. }
  3320. }
  3321. }));
  3322. }
  3323. }
  3324. else if (mode == App.MODE_DEVICE && Graph.fileSupport)
  3325. {
  3326. if (this.libFileInputElt == null)
  3327. {
  3328. var input = document.createElement('input');
  3329. input.setAttribute('type', 'file');
  3330. mxEvent.addListener(input, 'change', mxUtils.bind(this, function()
  3331. {
  3332. if (input.files != null)
  3333. {
  3334. for (var i = 0; i < input.files.length; i++)
  3335. {
  3336. (mxUtils.bind(this, function(file)
  3337. {
  3338. var reader = new FileReader();
  3339. reader.onload = mxUtils.bind(this, function(e)
  3340. {
  3341. try
  3342. {
  3343. this.loadLibrary(new LocalLibrary(this, e.target.result, file.name));
  3344. }
  3345. catch (e)
  3346. {
  3347. this.handleError(e, mxResources.get('errorLoadingFile'));
  3348. }
  3349. });
  3350. reader.readAsText(file);
  3351. }))(input.files[i]);
  3352. }
  3353. // Resets input to force change event for same file (type reset required for IE)
  3354. input.type = '';
  3355. input.type = 'file';
  3356. input.value = '';
  3357. }
  3358. }));
  3359. input.style.display = 'none';
  3360. document.body.appendChild(input);
  3361. this.libFileInputElt = input;
  3362. }
  3363. this.libFileInputElt.click();
  3364. }
  3365. else
  3366. {
  3367. window.openNew = false;
  3368. window.openKey = 'open';
  3369. window.listBrowserFiles = mxUtils.bind(this, function(success, error)
  3370. {
  3371. StorageFile.listFiles(this, 'L', success, error);
  3372. });
  3373. window.openBrowserFile = mxUtils.bind(this, function(title, success, error)
  3374. {
  3375. StorageFile.getFileContent(this, title, success, error);
  3376. });
  3377. window.deleteBrowserFile = mxUtils.bind(this, function(title, success, error)
  3378. {
  3379. StorageFile.deleteFile(this, title, success, error);
  3380. });
  3381. var prevValue = Editor.useLocalStorage;
  3382. Editor.useLocalStorage = mode == App.MODE_BROWSER;
  3383. // Closes dialog after open
  3384. window.openFile = new OpenFile(mxUtils.bind(this, function(cancel)
  3385. {
  3386. this.hideDialog(cancel);
  3387. }));
  3388. window.openFile.setConsumer(mxUtils.bind(this, function(xml, filename)
  3389. {
  3390. try
  3391. {
  3392. this.loadLibrary((mode == App.MODE_BROWSER) ? new StorageLibrary(this, xml, filename) :
  3393. new LocalLibrary(this, xml, filename));
  3394. }
  3395. catch (e)
  3396. {
  3397. this.handleError(e, mxResources.get('errorLoadingFile'));
  3398. }
  3399. }));
  3400. // Removes openFile if dialog is closed
  3401. this.showDialog(new OpenDialog(this).container, (Editor.useLocalStorage) ? 640 : 360,
  3402. (Editor.useLocalStorage) ? 480 : 220, true, true, function()
  3403. {
  3404. Editor.useLocalStorage = prevValue;
  3405. window.openFile = null;
  3406. });
  3407. }
  3408. };
  3409. /**
  3410. * Translates this point by the given vector.
  3411. *
  3412. * @param {number} dx X-coordinate of the translation.
  3413. * @param {number} dy Y-coordinate of the translation.
  3414. */
  3415. App.prototype.saveLibrary = function(name, images, file, mode, noSpin, noReload, fn)
  3416. {
  3417. try
  3418. {
  3419. mode = (mode != null) ? mode : this.mode;
  3420. noSpin = (noSpin != null) ? noSpin : false;
  3421. noReload = (noReload != null) ? noReload : false;
  3422. var xml = this.createLibraryDataFromImages(images);
  3423. var error = mxUtils.bind(this, function(resp)
  3424. {
  3425. this.spinner.stop();
  3426. if (fn != null)
  3427. {
  3428. fn();
  3429. }
  3430. this.handleError(resp, (resp != null) ? mxResources.get('errorSavingFile') : null);
  3431. });
  3432. // Handles special case for local libraries
  3433. if (file == null && mode == App.MODE_DEVICE)
  3434. {
  3435. file = new LocalLibrary(this, xml, name);
  3436. }
  3437. if (file == null)
  3438. {
  3439. this.pickFolder(mode, mxUtils.bind(this, function(folderId)
  3440. {
  3441. if (mode == App.MODE_GOOGLE && this.drive != null && this.spinner.spin(document.body, mxResources.get('inserting')))
  3442. {
  3443. this.drive.insertFile(name, xml, folderId, mxUtils.bind(this, function(newFile)
  3444. {
  3445. this.spinner.stop();
  3446. this.hideDialog(true);
  3447. this.libraryLoaded(newFile, images);
  3448. }), error, this.drive.libraryMimeType);
  3449. }
  3450. else if (mode == App.MODE_GITHUB && this.gitHub != null && this.spinner.spin(document.body, mxResources.get('inserting')))
  3451. {
  3452. this.gitHub.insertLibrary(name, xml, mxUtils.bind(this, function(newFile)
  3453. {
  3454. this.spinner.stop();
  3455. this.hideDialog(true);
  3456. this.libraryLoaded(newFile, images);
  3457. }), error, folderId);
  3458. }
  3459. else if (mode == App.MODE_GITLAB && this.gitLab != null && this.spinner.spin(document.body, mxResources.get('inserting')))
  3460. {
  3461. this.gitLab.insertLibrary(name, xml, mxUtils.bind(this, function(newFile)
  3462. {
  3463. this.spinner.stop();
  3464. this.hideDialog(true);
  3465. this.libraryLoaded(newFile, images);
  3466. }), error, folderId);
  3467. }
  3468. else if (mode == App.MODE_TRELLO && this.trello != null && this.spinner.spin(document.body, mxResources.get('inserting')))
  3469. {
  3470. this.trello.insertLibrary(name, xml, mxUtils.bind(this, function(newFile)
  3471. {
  3472. this.spinner.stop();
  3473. this.hideDialog(true);
  3474. this.libraryLoaded(newFile, images);
  3475. }), error, folderId);
  3476. }
  3477. else if (mode == App.MODE_DROPBOX && this.dropbox != null && this.spinner.spin(document.body, mxResources.get('inserting')))
  3478. {
  3479. this.dropbox.insertLibrary(name, xml, mxUtils.bind(this, function(newFile)
  3480. {
  3481. this.spinner.stop();
  3482. this.hideDialog(true);
  3483. this.libraryLoaded(newFile, images);
  3484. }), error, folderId);
  3485. }
  3486. else if (mode == App.MODE_ONEDRIVE && this.oneDrive != null && this.spinner.spin(document.body, mxResources.get('inserting')))
  3487. {
  3488. this.oneDrive.insertLibrary(name, xml, mxUtils.bind(this, function(newFile)
  3489. {
  3490. this.spinner.stop();
  3491. this.hideDialog(true);
  3492. this.libraryLoaded(newFile, images);
  3493. }), error, folderId);
  3494. }
  3495. else if (mode == App.MODE_BROWSER)
  3496. {
  3497. var fn = mxUtils.bind(this, function()
  3498. {
  3499. var file = new StorageLibrary(this, xml, name);
  3500. // Inserts data into local storage
  3501. file.saveFile(name, false, mxUtils.bind(this, function()
  3502. {
  3503. this.hideDialog(true);
  3504. this.libraryLoaded(file, images);
  3505. }), error);
  3506. });
  3507. if (localStorage.getItem(name) == null)
  3508. {
  3509. fn();
  3510. }
  3511. else
  3512. {
  3513. this.confirm(mxResources.get('replaceIt', [name]), fn);
  3514. }
  3515. }
  3516. else
  3517. {
  3518. this.handleError({message: mxResources.get('serviceUnavailableOrBlocked')});
  3519. }
  3520. }));
  3521. }
  3522. else if (noSpin || this.spinner.spin(document.body, mxResources.get('saving')))
  3523. {
  3524. file.setData(xml);
  3525. var doSave = mxUtils.bind(this, function()
  3526. {
  3527. file.save(true, mxUtils.bind(this, function(resp)
  3528. {
  3529. this.spinner.stop();
  3530. this.hideDialog(true);
  3531. if (!noReload)
  3532. {
  3533. this.libraryLoaded(file, images);
  3534. }
  3535. if (fn != null)
  3536. {
  3537. fn();
  3538. }
  3539. }), error);
  3540. });
  3541. if (name != file.getTitle())
  3542. {
  3543. var oldHash = file.getHash();
  3544. file.rename(name, mxUtils.bind(this, function(resp)
  3545. {
  3546. // Change hash in stored settings
  3547. if (file.constructor != LocalLibrary && oldHash != file.getHash())
  3548. {
  3549. mxSettings.removeCustomLibrary(oldHash);
  3550. mxSettings.addCustomLibrary(file.getHash());
  3551. }
  3552. // Workaround for library files changing hash so
  3553. // the old library cannot be removed from the
  3554. // sidebar using the updated file in libraryLoaded
  3555. this.removeLibrarySidebar(oldHash);
  3556. doSave();
  3557. }), error)
  3558. }
  3559. else
  3560. {
  3561. doSave();
  3562. }
  3563. }
  3564. }
  3565. catch (e)
  3566. {
  3567. this.handleError(e);
  3568. }
  3569. };
  3570. /**
  3571. * Adds the label menu items to the given menu and parent.
  3572. */
  3573. App.prototype.saveFile = function(forceDialog, success)
  3574. {
  3575. var file = this.getCurrentFile();
  3576. if (file != null)
  3577. {
  3578. // FIXME: Invoke for local files
  3579. var done = mxUtils.bind(this, function()
  3580. {
  3581. if (EditorUi.enableDrafts)
  3582. {
  3583. file.removeDraft();
  3584. }
  3585. if (this.getCurrentFile() != file && !file.isModified())
  3586. {
  3587. // Workaround for possible status update while save as dialog is showing
  3588. // is to show no saved status for device files
  3589. if (file.getMode() != App.MODE_DEVICE)
  3590. {
  3591. this.editor.setStatus(mxUtils.htmlEntities(mxResources.get('allChangesSaved')));
  3592. }
  3593. else
  3594. {
  3595. this.editor.setStatus('');
  3596. }
  3597. }
  3598. if (success != null)
  3599. {
  3600. success();
  3601. }
  3602. });
  3603. if (!forceDialog && file.getTitle() != null && this.mode != null)
  3604. {
  3605. this.save(file.getTitle(), done);
  3606. }
  3607. else if (file != null && file.constructor == LocalFile && file.fileHandle != null)
  3608. {
  3609. this.chooseFileSystemEntries(mxUtils.bind(this, function(fileHandle, desc)
  3610. {
  3611. file.fileHandle = fileHandle;
  3612. file.title = desc.name;
  3613. file.desc = desc;
  3614. this.save(desc.name, done);
  3615. }), null, this.createFileSystemOptions(file.getTitle()));
  3616. }
  3617. else
  3618. {
  3619. var filename = (file.getTitle() != null) ? file.getTitle() : this.defaultFilename;
  3620. var allowTab = !mxClient.IS_IOS || !navigator.standalone;
  3621. var prev = this.mode;
  3622. var serviceCount = this.getServiceCount(true);
  3623. if (isLocalStorage)
  3624. {
  3625. serviceCount++;
  3626. }
  3627. var rowLimit = (serviceCount <= 4) ? 2 : (serviceCount > 6 ? 4 : 3);
  3628. var dlg = new CreateDialog(this, filename, mxUtils.bind(this, function(name, mode, input)
  3629. {
  3630. if (name != null && name.length > 0)
  3631. {
  3632. // Handles special case where PDF export is detected
  3633. if (/(\.pdf)$/i.test(name))
  3634. {
  3635. this.confirm(mxResources.get('didYouMeanToExportToPdf'), mxUtils.bind(this, function()
  3636. {
  3637. this.hideDialog();
  3638. this.actions.get('exportPdf').funct();
  3639. }), mxUtils.bind(this, function()
  3640. {
  3641. input.value = name.split('.').slice(0, -1).join('.');
  3642. input.focus();
  3643. if (mxClient.IS_GC || mxClient.IS_FF || document.documentMode >= 5 || mxClient.IS_QUIRKS)
  3644. {
  3645. input.select();
  3646. }
  3647. else
  3648. {
  3649. document.execCommand('selectAll', false, null);
  3650. }
  3651. }), mxResources.get('yes'), mxResources.get('no'));
  3652. }
  3653. else
  3654. {
  3655. this.hideDialog();
  3656. if (prev == null && mode == App.MODE_DEVICE)
  3657. {
  3658. if (file != null && 'chooseFileSystemEntries' in window)
  3659. {
  3660. this.chooseFileSystemEntries(mxUtils.bind(this, function(fileHandle, desc)
  3661. {
  3662. file.fileHandle = fileHandle;
  3663. file.mode = App.MODE_DEVICE;
  3664. file.title = desc.name;
  3665. file.desc = desc;
  3666. this.setMode(App.MODE_DEVICE);
  3667. this.save(desc.name, done);
  3668. }), mxUtils.bind(this, function(e)
  3669. {
  3670. if (e.name != 'AbortError')
  3671. {
  3672. this.handleError(e);
  3673. }
  3674. }), this.createFileSystemOptions(name));
  3675. }
  3676. else
  3677. {
  3678. this.setMode(App.MODE_DEVICE);
  3679. this.save(name, done);
  3680. }
  3681. }
  3682. else if (mode == 'download')
  3683. {
  3684. var tmp = new LocalFile(this, null, name);
  3685. tmp.save();
  3686. }
  3687. else if (mode == '_blank')
  3688. {
  3689. window.openFile = new OpenFile(function()
  3690. {
  3691. window.openFile = null;
  3692. });
  3693. // Do not use a filename to use undefined mode
  3694. window.openFile.setData(this.getFileData(true));
  3695. this.openLink(this.getUrl(window.location.pathname), null, true);
  3696. }
  3697. else if (prev != mode)
  3698. {
  3699. this.pickFolder(mode, mxUtils.bind(this, function(folderId)
  3700. {
  3701. this.createFile(name, this.getFileData(/(\.xml)$/i.test(name) ||
  3702. name.indexOf('.') < 0 || /(\.drawio)$/i.test(name),
  3703. /(\.svg)$/i.test(name), /(\.html)$/i.test(name)),
  3704. null, mode, done, this.mode == null, folderId);
  3705. }));
  3706. }
  3707. else if (mode != null)
  3708. {
  3709. this.save(name, done);
  3710. }
  3711. }
  3712. }
  3713. }), mxUtils.bind(this, function()
  3714. {
  3715. this.hideDialog();
  3716. }), mxResources.get('saveAs'), mxResources.get('download'), null, null, allowTab,
  3717. null, true, rowLimit, null, null, null, this.editor.fileExtensions, false);
  3718. this.showDialog(dlg.container, 400, (serviceCount > rowLimit) ? 390 : 270, true, true);
  3719. dlg.init();
  3720. }
  3721. }
  3722. };
  3723. /**
  3724. * Translates this point by the given vector.
  3725. *
  3726. * @param {number} dx X-coordinate of the translation.
  3727. * @param {number} dy Y-coordinate of the translation.
  3728. */
  3729. App.prototype.loadTemplate = function(url, onload, onerror, templateFilename, asLibrary)
  3730. {
  3731. var base64 = false;
  3732. var realUrl = url;
  3733. if (!this.editor.isCorsEnabledForUrl(realUrl))
  3734. {
  3735. // Always uses base64 response to check magic numbers for file type
  3736. var nocache = 't=' + new Date().getTime();
  3737. realUrl = PROXY_URL + '?url=' + encodeURIComponent(url) + '&base64=1&' + nocache;
  3738. base64 = true;
  3739. }
  3740. var filterFn = (templateFilename != null) ? templateFilename : url;
  3741. this.editor.loadUrl(realUrl, mxUtils.bind(this, function(responseData)
  3742. {
  3743. try
  3744. {
  3745. var data = (!base64) ? responseData : ((window.atob && !mxClient.IS_IE && !mxClient.IS_IE11) ?
  3746. atob(responseData) : Base64.decode(responseData));
  3747. var isVisioFilename = /(\.v(dx|sdx?))($|\?)/i.test(filterFn) ||
  3748. /(\.vs(x|sx?))($|\?)/i.test(filterFn);
  3749. if (isVisioFilename || this.isVisioData(data))
  3750. {
  3751. // Adds filename to control converter code
  3752. if (!isVisioFilename)
  3753. {
  3754. if (asLibrary)
  3755. {
  3756. filterFn = this.isRemoteVisioData(data) ? 'raw.vss' : 'raw.vssx';
  3757. }
  3758. else
  3759. {
  3760. filterFn = this.isRemoteVisioData(data) ? 'raw.vsd' : 'raw.vsdx';
  3761. }
  3762. }
  3763. this.importVisio(this.base64ToBlob(responseData.substring(responseData.indexOf(',') + 1)), function(xml)
  3764. {
  3765. onload(xml);
  3766. }, onerror, filterFn);
  3767. }
  3768. else if (!this.isOffline() && new XMLHttpRequest().upload && this.isRemoteFileFormat(data, filterFn))
  3769. {
  3770. // Asynchronous parsing via server
  3771. this.parseFile(new Blob([data], {type: 'application/octet-stream'}), mxUtils.bind(this, function(xhr)
  3772. {
  3773. if (xhr.readyState == 4 && xhr.status >= 200 && xhr.status <= 299 &&
  3774. xhr.responseText.substring(0, 13) == '<mxGraphModel')
  3775. {
  3776. onload(xhr.responseText);
  3777. }
  3778. }), url);
  3779. }
  3780. else if (this.isLucidChartData(data))
  3781. {
  3782. this.convertLucidChart(data, mxUtils.bind(this, function(xml)
  3783. {
  3784. onload(xml);
  3785. }), mxUtils.bind(this, function(e)
  3786. {
  3787. onerror(e);
  3788. }));
  3789. }
  3790. else
  3791. {
  3792. if (/(\.png)($|\?)/i.test(filterFn) || this.isPngData(data))
  3793. {
  3794. data = this.extractGraphModelFromPng(responseData);
  3795. }
  3796. onload(data);
  3797. }
  3798. }
  3799. catch (e)
  3800. {
  3801. onerror(e);
  3802. }
  3803. }), onerror, /(\.png)($|\?)/i.test(filterFn) || /(\.v(dx|sdx?))($|\?)/i.test(filterFn) ||
  3804. /(\.vs(x|sx?))($|\?)/i.test(filterFn), null, null, base64);
  3805. };
  3806. /**
  3807. * Translates this point by the given vector.
  3808. *
  3809. * @param {number} dx X-coordinate of the translation.
  3810. * @param {number} dy Y-coordinate of the translation.
  3811. */
  3812. App.prototype.getPeerForMode = function(mode)
  3813. {
  3814. if (mode == App.MODE_GOOGLE)
  3815. {
  3816. return this.drive;
  3817. }
  3818. else if (mode == App.MODE_GITHUB)
  3819. {
  3820. return this.gitHub;
  3821. }
  3822. else if (mode == App.MODE_GITLAB)
  3823. {
  3824. return this.gitLab;
  3825. }
  3826. else if (mode == App.MODE_DROPBOX)
  3827. {
  3828. return this.dropbox;
  3829. }
  3830. else if (mode == App.MODE_ONEDRIVE)
  3831. {
  3832. return this.oneDrive;
  3833. }
  3834. else if (mode == App.MODE_TRELLO)
  3835. {
  3836. return this.trello;
  3837. }
  3838. else
  3839. {
  3840. return null;
  3841. }
  3842. };
  3843. /**
  3844. * Translates this point by the given vector.
  3845. *
  3846. * @param {number} dx X-coordinate of the translation.
  3847. * @param {number} dy Y-coordinate of the translation.
  3848. */
  3849. App.prototype.createFile = function(title, data, libs, mode, done, replace, folderId, tempFile, clibs)
  3850. {
  3851. mode = (tempFile) ? null : ((mode != null) ? mode : this.mode);
  3852. if (title != null && this.spinner.spin(document.body, mxResources.get('inserting')))
  3853. {
  3854. data = (data != null) ? data : this.emptyDiagramXml;
  3855. var complete = mxUtils.bind(this, function()
  3856. {
  3857. this.spinner.stop();
  3858. });
  3859. var error = mxUtils.bind(this, function(resp)
  3860. {
  3861. complete();
  3862. if (resp == null && this.getCurrentFile() == null && this.dialog == null)
  3863. {
  3864. this.showSplash();
  3865. }
  3866. else if (resp != null)
  3867. {
  3868. this.handleError(resp);
  3869. }
  3870. });
  3871. try
  3872. {
  3873. if (mode == App.MODE_GOOGLE && this.drive != null)
  3874. {
  3875. if (folderId == null && this.stateArg != null && this.stateArg.folderId != null)
  3876. {
  3877. folderId = this.stateArg.folderId;
  3878. }
  3879. this.drive.insertFile(title, data, folderId, mxUtils.bind(this, function(file)
  3880. {
  3881. complete();
  3882. this.fileCreated(file, libs, replace, done, clibs);
  3883. }), error);
  3884. }
  3885. else if (mode == App.MODE_GITHUB && this.gitHub != null)
  3886. {
  3887. this.gitHub.insertFile(title, data, mxUtils.bind(this, function(file)
  3888. {
  3889. complete();
  3890. this.fileCreated(file, libs, replace, done, clibs);
  3891. }), error, false, folderId);
  3892. }
  3893. else if (mode == App.MODE_GITLAB && this.gitLab != null)
  3894. {
  3895. this.gitLab.insertFile(title, data, mxUtils.bind(this, function(file)
  3896. {
  3897. complete();
  3898. this.fileCreated(file, libs, replace, done, clibs);
  3899. }), error, false, folderId);
  3900. }
  3901. else if (mode == App.MODE_TRELLO && this.trello != null)
  3902. {
  3903. this.trello.insertFile(title, data, mxUtils.bind(this, function(file)
  3904. {
  3905. complete();
  3906. this.fileCreated(file, libs, replace, done, clibs);
  3907. }), error, false, folderId);
  3908. }
  3909. else if (mode == App.MODE_DROPBOX && this.dropbox != null)
  3910. {
  3911. this.dropbox.insertFile(title, data, mxUtils.bind(this, function(file)
  3912. {
  3913. complete();
  3914. this.fileCreated(file, libs, replace, done, clibs);
  3915. }), error);
  3916. }
  3917. else if (mode == App.MODE_ONEDRIVE && this.oneDrive != null)
  3918. {
  3919. this.oneDrive.insertFile(title, data, mxUtils.bind(this, function(file)
  3920. {
  3921. complete();
  3922. this.fileCreated(file, libs, replace, done, clibs);
  3923. }), error, false, folderId);
  3924. }
  3925. else if (mode == App.MODE_BROWSER)
  3926. {
  3927. StorageFile.insertFile(this, title, data, mxUtils.bind(this, function(file)
  3928. {
  3929. complete();
  3930. this.fileCreated(file, libs, replace, done, clibs);
  3931. }), error);
  3932. }
  3933. else if (!tempFile && mode == App.MODE_DEVICE && 'chooseFileSystemEntries' in window)
  3934. {
  3935. complete();
  3936. this.chooseFileSystemEntries(mxUtils.bind(this, function(fileHandle, desc)
  3937. {
  3938. var file = new LocalFile(this, data, desc.name, null, fileHandle, desc);
  3939. file.saveFile(desc.name, false, mxUtils.bind(this, function()
  3940. {
  3941. this.fileCreated(file, libs, replace, done, clibs);
  3942. }), error, true);
  3943. }), mxUtils.bind(this, function(e)
  3944. {
  3945. if (e.name != 'AbortError')
  3946. {
  3947. error(e);
  3948. }
  3949. }), this.createFileSystemOptions(title));
  3950. }
  3951. else
  3952. {
  3953. complete();
  3954. this.fileCreated(new LocalFile(this, data, title, mode == null), libs, replace, done, clibs);
  3955. }
  3956. }
  3957. catch (e)
  3958. {
  3959. complete();
  3960. this.handleError(e);
  3961. }
  3962. }
  3963. };
  3964. /**
  3965. * Translates this point by the given vector.
  3966. *
  3967. * @param {number} dx X-coordinate of the translation.
  3968. * @param {number} dy Y-coordinate of the translation.
  3969. */
  3970. App.prototype.fileCreated = function(file, libs, replace, done, clibs)
  3971. {
  3972. var url = window.location.pathname;
  3973. if (libs != null && libs.length > 0)
  3974. {
  3975. url += '?libs=' + libs;
  3976. }
  3977. if (clibs != null && clibs.length > 0)
  3978. {
  3979. url += '?clibs=' + clibs;
  3980. }
  3981. url = this.getUrl(url);
  3982. // Always opens a new tab for local files to avoid losing changes
  3983. if (file.getMode() != App.MODE_DEVICE)
  3984. {
  3985. url += '#' + file.getHash();
  3986. }
  3987. // Makes sure to produce consistent output with finalized files via createFileData this needs
  3988. // to save the file again since it needs the newly created file ID for redirecting in HTML
  3989. if (this.spinner.spin(document.body, mxResources.get('inserting')))
  3990. {
  3991. var data = file.getData();
  3992. var dataNode = (data.length > 0) ? this.editor.extractGraphModel(
  3993. mxUtils.parseXml(data).documentElement, true) : null;
  3994. var redirect = window.location.protocol + '//' + window.location.hostname + url;
  3995. var node = dataNode;
  3996. var graph = null;
  3997. // Handles special case where SVG files need a rendered graph to be saved
  3998. if (dataNode != null && /\.svg$/i.test(file.getTitle()))
  3999. {
  4000. graph = this.createTemporaryGraph(this.editor.graph.getStylesheet());
  4001. document.body.appendChild(graph.container);
  4002. node = this.decodeNodeIntoGraph(node, graph);
  4003. }
  4004. file.setData(this.createFileData(dataNode, graph, file, redirect));
  4005. if (graph != null)
  4006. {
  4007. graph.container.parentNode.removeChild(graph.container);
  4008. }
  4009. var complete = mxUtils.bind(this, function()
  4010. {
  4011. this.spinner.stop();
  4012. });
  4013. var fn = mxUtils.bind(this, function()
  4014. {
  4015. complete();
  4016. var currentFile = this.getCurrentFile();
  4017. if (replace == null && currentFile != null)
  4018. {
  4019. replace = !currentFile.isModified() && currentFile.getMode() == null;
  4020. }
  4021. var fn3 = mxUtils.bind(this, function()
  4022. {
  4023. window.openFile = null;
  4024. this.fileLoaded(file);
  4025. if (replace)
  4026. {
  4027. file.addAllSavedStatus();
  4028. }
  4029. if (libs != null)
  4030. {
  4031. this.sidebar.showEntries(libs);
  4032. }
  4033. if (clibs != null)
  4034. {
  4035. var temp = [];
  4036. var tokens = clibs.split(';');
  4037. for (var i = 0; i < tokens.length; i++)
  4038. {
  4039. temp.push(decodeURIComponent(tokens[i]));
  4040. }
  4041. this.loadLibraries(temp);
  4042. }
  4043. });
  4044. var fn2 = mxUtils.bind(this, function()
  4045. {
  4046. if (replace || currentFile == null || !currentFile.isModified())
  4047. {
  4048. fn3();
  4049. }
  4050. else
  4051. {
  4052. this.confirm(mxResources.get('allChangesLost'), null, fn3,
  4053. mxResources.get('cancel'), mxResources.get('discardChanges'));
  4054. }
  4055. });
  4056. if (done != null)
  4057. {
  4058. done();
  4059. }
  4060. // Opens the file in a new window
  4061. if (replace != null && !replace)
  4062. {
  4063. // Opens local file in a new window
  4064. if (file.constructor == LocalFile)
  4065. {
  4066. window.openFile = new OpenFile(function()
  4067. {
  4068. window.openFile = null;
  4069. });
  4070. window.openFile.setData(file.getData(), file.getTitle(), file.getMode() == null);
  4071. }
  4072. if (done != null)
  4073. {
  4074. done();
  4075. }
  4076. window.openWindow(url, null, fn2);
  4077. }
  4078. else
  4079. {
  4080. fn2();
  4081. }
  4082. });
  4083. // Updates data in memory for local files
  4084. if (file.constructor == LocalFile)
  4085. {
  4086. fn();
  4087. }
  4088. else
  4089. {
  4090. file.saveFile(file.getTitle(), false, mxUtils.bind(this, function()
  4091. {
  4092. fn();
  4093. }), mxUtils.bind(this, function(resp)
  4094. {
  4095. complete();
  4096. this.handleError(resp);
  4097. }));
  4098. }
  4099. }
  4100. };
  4101. /**
  4102. * Translates this point by the given vector.
  4103. *
  4104. * @param {number} dx X-coordinate of the translation.
  4105. * @param {number} dy Y-coordinate of the translation.
  4106. */
  4107. App.prototype.loadFile = function(id, sameWindow, file, success, force)
  4108. {
  4109. this.hideDialog();
  4110. var fn2 = mxUtils.bind(this, function()
  4111. {
  4112. if (id == null || id.length == 0)
  4113. {
  4114. this.editor.setStatus('');
  4115. this.fileLoaded(null);
  4116. }
  4117. else if (this.spinner.spin(document.body, mxResources.get('loading')))
  4118. {
  4119. // Handles files from localStorage
  4120. if (id.charAt(0) == 'L')
  4121. {
  4122. this.spinner.stop();
  4123. if (!isLocalStorage)
  4124. {
  4125. this.handleError({message: mxResources.get('serviceUnavailableOrBlocked')}, mxResources.get('errorLoadingFile'), mxUtils.bind(this, function()
  4126. {
  4127. var tempFile = this.getCurrentFile();
  4128. window.location.hash = (tempFile != null) ? tempFile.getHash() : '';
  4129. }));
  4130. }
  4131. else
  4132. {
  4133. var error = mxUtils.bind(this, function (e)
  4134. {
  4135. this.handleError(e, mxResources.get('errorLoadingFile'), mxUtils.bind(this, function()
  4136. {
  4137. var tempFile = this.getCurrentFile();
  4138. window.location.hash = (tempFile != null) ? tempFile.getHash() : '';
  4139. }));
  4140. });
  4141. id = decodeURIComponent(id.substring(1));
  4142. StorageFile.getFileContent(this, id, mxUtils.bind(this, function(data)
  4143. {
  4144. if (data != null)
  4145. {
  4146. this.fileLoaded(new StorageFile(this, data, id));
  4147. if (success != null)
  4148. {
  4149. success();
  4150. }
  4151. }
  4152. else
  4153. {
  4154. error({message: mxResources.get('fileNotFound')});
  4155. }
  4156. }), error);
  4157. }
  4158. }
  4159. else if (file != null)
  4160. {
  4161. // File already loaded
  4162. this.spinner.stop();
  4163. this.fileLoaded(file);
  4164. if (success != null)
  4165. {
  4166. success();
  4167. }
  4168. }
  4169. else if (id.charAt(0) == 'S')
  4170. {
  4171. this.spinner.stop();
  4172. try
  4173. {
  4174. this.loadDescriptor(JSON.parse(Graph.decompress(id.substring(1))),
  4175. success, mxUtils.bind(this, function(e)
  4176. {
  4177. this.handleError(e, mxResources.get('errorLoadingFile'));
  4178. }));
  4179. }
  4180. catch (e)
  4181. {
  4182. this.handleError(e, mxResources.get('errorLoadingFile'));
  4183. }
  4184. }
  4185. else if (id.charAt(0) == 'R')
  4186. {
  4187. // Raw file encoded into URL
  4188. this.spinner.stop();
  4189. var data = decodeURIComponent(id.substring(1));
  4190. if (data.charAt(0) != '<')
  4191. {
  4192. data = Graph.decompress(data);
  4193. }
  4194. var tempFile = new LocalFile(this, data, (urlParams['title'] != null) ?
  4195. decodeURIComponent(urlParams['title']) : this.defaultFilename, true);
  4196. tempFile.getHash = function()
  4197. {
  4198. return id;
  4199. };
  4200. this.fileLoaded(tempFile);
  4201. if (success != null)
  4202. {
  4203. success();
  4204. }
  4205. }
  4206. else if (id.charAt(0) == 'E') // Embed file
  4207. {
  4208. //Currently we only reload current file. Id is not used!
  4209. var currentFile = this.getCurrentFile();
  4210. if (currentFile == null)
  4211. {
  4212. this.handleError({message: mxResources.get('serviceUnavailableOrBlocked')}, mxResources.get('errorLoadingFile'));
  4213. }
  4214. else
  4215. {
  4216. this.remoteInvoke('getDraftFileContent', null, null, mxUtils.bind(this, function(data, desc)
  4217. {
  4218. this.spinner.stop();
  4219. this.fileLoaded(new EmbedFile(this, data, desc));
  4220. if (success != null)
  4221. {
  4222. success();
  4223. }
  4224. }), mxUtils.bind(this, function()
  4225. {
  4226. this.handleError({message: mxResources.get('serviceUnavailableOrBlocked')}, mxResources.get('errorLoadingFile'));
  4227. }));
  4228. }
  4229. }
  4230. else if (id.charAt(0) == 'U')
  4231. {
  4232. var url = decodeURIComponent(id.substring(1));
  4233. var doFallback = mxUtils.bind(this, function()
  4234. {
  4235. // Fallback for non-public Google Drive files
  4236. if (url.substring(0, 31) == 'https://drive.google.com/uc?id=' &&
  4237. (this.drive != null || typeof window.DriveClient === 'function'))
  4238. {
  4239. this.hideDialog();
  4240. var fallback = mxUtils.bind(this, function()
  4241. {
  4242. this.spinner.stop();
  4243. if (this.drive != null)
  4244. {
  4245. var tempId = url.substring(31, url.lastIndexOf('&ex'));
  4246. this.loadFile('G' + tempId, sameWindow, null, mxUtils.bind(this, function()
  4247. {
  4248. var currentFile = this.getCurrentFile();
  4249. if (currentFile != null && this.editor.chromeless && !this.editor.editable)
  4250. {
  4251. currentFile.getHash = function()
  4252. {
  4253. return 'G' + tempId;
  4254. };
  4255. window.location.hash = '#' + currentFile.getHash();
  4256. }
  4257. if (success != null)
  4258. {
  4259. success();
  4260. }
  4261. }));
  4262. return true;
  4263. }
  4264. else
  4265. {
  4266. return false;
  4267. }
  4268. });
  4269. if (!fallback() && this.spinner.spin(document.body, mxResources.get('loading')))
  4270. {
  4271. this.addListener('clientLoaded', fallback);
  4272. }
  4273. return true;
  4274. }
  4275. else
  4276. {
  4277. return false;
  4278. }
  4279. });
  4280. this.loadTemplate(url, mxUtils.bind(this, function(text)
  4281. {
  4282. this.spinner.stop();
  4283. if (text != null && text.length > 0)
  4284. {
  4285. var filename = this.defaultFilename;
  4286. // Tries to find name from URL with valid extensions
  4287. if (urlParams['title'] == null && urlParams['notitle'] != '1')
  4288. {
  4289. var tmp = url;
  4290. var dot = url.lastIndexOf('.');
  4291. var slash = tmp.lastIndexOf('/');
  4292. if (dot > slash && slash > 0)
  4293. {
  4294. tmp = tmp.substring(slash + 1, dot);
  4295. var ext = url.substring(dot);
  4296. if (!this.useCanvasForExport && ext == '.png')
  4297. {
  4298. ext = '.drawio';
  4299. }
  4300. if (ext === '.svg' || ext === '.xml' ||
  4301. ext === '.html' || ext === '.png' ||
  4302. ext === '.drawio')
  4303. {
  4304. filename = tmp + ext;
  4305. }
  4306. }
  4307. }
  4308. var tempFile = new LocalFile(this, text, (urlParams['title'] != null) ?
  4309. decodeURIComponent(urlParams['title']) : filename, true);
  4310. tempFile.getHash = function()
  4311. {
  4312. return id;
  4313. };
  4314. if (!this.fileLoaded(tempFile, true) && !doFallback())
  4315. {
  4316. this.handleError({message: mxResources.get('fileNotFound')},
  4317. mxResources.get('errorLoadingFile'));
  4318. }
  4319. }
  4320. else if (!doFallback())
  4321. {
  4322. this.handleError({message: mxResources.get('fileNotFound')},
  4323. mxResources.get('errorLoadingFile'));
  4324. }
  4325. }), mxUtils.bind(this, function()
  4326. {
  4327. if (!doFallback())
  4328. {
  4329. this.spinner.stop();
  4330. this.handleError({message: mxResources.get('fileNotFound')},
  4331. mxResources.get('errorLoadingFile'));
  4332. }
  4333. }), (urlParams['template-filename'] != null) ?
  4334. decodeURIComponent(urlParams['template-filename']) : null);
  4335. }
  4336. else
  4337. {
  4338. // Google Drive files are handled as default file types
  4339. var peer = null;
  4340. if (id.charAt(0) == 'G')
  4341. {
  4342. peer = this.drive;
  4343. }
  4344. else if (id.charAt(0) == 'D')
  4345. {
  4346. peer = this.dropbox;
  4347. }
  4348. else if (id.charAt(0) == 'W')
  4349. {
  4350. peer = this.oneDrive;
  4351. }
  4352. else if (id.charAt(0) == 'H')
  4353. {
  4354. peer = this.gitHub;
  4355. }
  4356. else if (id.charAt(0) == 'A')
  4357. {
  4358. peer = this.gitLab;
  4359. }
  4360. else if (id.charAt(0) == 'T')
  4361. {
  4362. peer = this.trello;
  4363. }
  4364. if (peer == null)
  4365. {
  4366. this.handleError({message: mxResources.get('serviceUnavailableOrBlocked')}, mxResources.get('errorLoadingFile'), mxUtils.bind(this, function()
  4367. {
  4368. var currentFile = this.getCurrentFile();
  4369. window.location.hash = (currentFile != null) ? currentFile.getHash() : '';
  4370. }));
  4371. }
  4372. else
  4373. {
  4374. var peerChar = id.charAt(0);
  4375. id = decodeURIComponent(id.substring(1));
  4376. peer.getFile(id, mxUtils.bind(this, function(file)
  4377. {
  4378. this.spinner.stop();
  4379. this.fileLoaded(file);
  4380. var currentFile = this.getCurrentFile();
  4381. if (currentFile == null)
  4382. {
  4383. window.location.hash = '';
  4384. this.showSplash();
  4385. }
  4386. else if (this.editor.chromeless && !this.editor.editable)
  4387. {
  4388. // Keeps ID even for converted files in chromeless mode for refresh to work
  4389. currentFile.getHash = function()
  4390. {
  4391. return peerChar + id;
  4392. };
  4393. window.location.hash = '#' + currentFile.getHash();
  4394. }
  4395. else if (file == currentFile && file.getMode() == null)
  4396. {
  4397. // Shows a warning if a copy was opened which happens
  4398. // eg. for .png files in IE as they cannot be written
  4399. var status = mxResources.get('copyCreated');
  4400. this.editor.setStatus('<div title="'+ status + '" class="geStatusAlert" style="overflow:hidden;">' + status + '</div>');
  4401. }
  4402. if (success != null)
  4403. {
  4404. success();
  4405. }
  4406. }), mxUtils.bind(this, function(resp)
  4407. {
  4408. // Makes sure the file does not save the invalid UI model and overwrites anything important
  4409. if (window.console != null && resp != null)
  4410. {
  4411. console.log('error in loadFile:', id, resp);
  4412. }
  4413. this.handleError(resp, (resp != null) ? mxResources.get('errorLoadingFile') : null, mxUtils.bind(this, function()
  4414. {
  4415. var currentFile = this.getCurrentFile();
  4416. if (currentFile == null)
  4417. {
  4418. window.location.hash = '';
  4419. this.showSplash();
  4420. }
  4421. else
  4422. {
  4423. window.location.hash = '#' + currentFile.getHash();
  4424. }
  4425. }), null, null, '#' + peerChar + id);
  4426. }));
  4427. }
  4428. }
  4429. }
  4430. });
  4431. var currentFile = this.getCurrentFile();
  4432. var fn = mxUtils.bind(this, function()
  4433. {
  4434. if (force || currentFile == null || !currentFile.isModified())
  4435. {
  4436. fn2();
  4437. }
  4438. else
  4439. {
  4440. this.confirm(mxResources.get('allChangesLost'), mxUtils.bind(this, function()
  4441. {
  4442. if (currentFile != null)
  4443. {
  4444. window.location.hash = currentFile.getHash();
  4445. }
  4446. }), fn2, mxResources.get('cancel'), mxResources.get('discardChanges'));
  4447. }
  4448. });
  4449. if (id == null || id.length == 0)
  4450. {
  4451. fn();
  4452. }
  4453. else if (currentFile != null && !sameWindow)
  4454. {
  4455. this.showDialog(new PopupDialog(this, this.getUrl() + '#' + id,
  4456. null, fn).container, 320, 140, true, true);
  4457. }
  4458. else
  4459. {
  4460. fn();
  4461. }
  4462. };
  4463. /**
  4464. * Translates this point by the given vector.
  4465. *
  4466. * @param {number} dx X-coordinate of the translation.
  4467. * @param {number} dy Y-coordinate of the translation.
  4468. */
  4469. App.prototype.getLibraryStorageHint = function(file)
  4470. {
  4471. var tip = file.getTitle();
  4472. if (file.constructor != LocalLibrary)
  4473. {
  4474. tip += '\n' + file.getHash();
  4475. }
  4476. if (file.constructor == DriveLibrary)
  4477. {
  4478. tip += ' (' + mxResources.get('googleDrive') + ')';
  4479. }
  4480. else if (file.constructor == GitHubLibrary)
  4481. {
  4482. tip += ' (' + mxResources.get('github') + ')';
  4483. }
  4484. else if (file.constructor == TrelloLibrary)
  4485. {
  4486. tip += ' (' + mxResources.get('trello') + ')';
  4487. }
  4488. else if (file.constructor == DropboxLibrary)
  4489. {
  4490. tip += ' (' + mxResources.get('dropbox') + ')';
  4491. }
  4492. else if (file.constructor == OneDriveLibrary)
  4493. {
  4494. tip += ' (' + mxResources.get('oneDrive') + ')';
  4495. }
  4496. else if (file.constructor == StorageLibrary)
  4497. {
  4498. tip += ' (' + mxResources.get('browser') + ')';
  4499. }
  4500. else if (file.constructor == LocalLibrary)
  4501. {
  4502. tip += ' (' + mxResources.get('device') + ')';
  4503. }
  4504. return tip;
  4505. };
  4506. /**
  4507. * Updates action states depending on the selection.
  4508. */
  4509. App.prototype.restoreLibraries = function()
  4510. {
  4511. this.loadLibraries(mxSettings.getCustomLibraries(), mxUtils.bind(this, function()
  4512. {
  4513. this.loadLibraries((urlParams['clibs'] || '').split(';'));
  4514. }));
  4515. };
  4516. /**
  4517. * Updates action states depending on the selection.
  4518. */
  4519. App.prototype.loadLibraries = function(libs, done)
  4520. {
  4521. if (this.sidebar != null)
  4522. {
  4523. if (this.pendingLibraries == null)
  4524. {
  4525. this.pendingLibraries = new Object();
  4526. }
  4527. // Ignores this library next time
  4528. var ignore = mxUtils.bind(this, function(id, keep)
  4529. {
  4530. if (!keep)
  4531. {
  4532. mxSettings.removeCustomLibrary(id);
  4533. }
  4534. delete this.pendingLibraries[id];
  4535. });
  4536. var waiting = 0;
  4537. var files = [];
  4538. // Loads in order of libs array
  4539. var checkDone = mxUtils.bind(this, function()
  4540. {
  4541. if (waiting == 0)
  4542. {
  4543. if (libs != null)
  4544. {
  4545. for (var i = libs.length - 1; i >= 0; i--)
  4546. {
  4547. if (files[i] != null)
  4548. {
  4549. this.loadLibrary(files[i]);
  4550. }
  4551. }
  4552. }
  4553. if (done != null)
  4554. {
  4555. done();
  4556. }
  4557. }
  4558. });
  4559. if (libs != null)
  4560. {
  4561. for (var i = 0; i < libs.length; i++)
  4562. {
  4563. var name = encodeURIComponent(decodeURIComponent(libs[i]));
  4564. (mxUtils.bind(this, function(id, index)
  4565. {
  4566. if (id != null && id.length > 0 && this.pendingLibraries[id] == null &&
  4567. this.sidebar.palettes[id] == null)
  4568. {
  4569. // Waits for all libraries to load
  4570. waiting++;
  4571. var onload = mxUtils.bind(this, function(file)
  4572. {
  4573. delete this.pendingLibraries[id];
  4574. files[index] = file;
  4575. waiting--;
  4576. checkDone();
  4577. });
  4578. var onerror = mxUtils.bind(this, function(keep)
  4579. {
  4580. ignore(id, keep);
  4581. waiting--;
  4582. checkDone();
  4583. });
  4584. this.pendingLibraries[id] = true;
  4585. var service = id.substring(0, 1);
  4586. if (service == 'L')
  4587. {
  4588. if (isLocalStorage || mxClient.IS_CHROMEAPP)
  4589. {
  4590. // Make asynchronous for barrier to work
  4591. window.setTimeout(mxUtils.bind(this, function()
  4592. {
  4593. try
  4594. {
  4595. var name = decodeURIComponent(id.substring(1));
  4596. StorageFile.getFileContent(this, name, mxUtils.bind(this, function(xml)
  4597. {
  4598. if (name == '.scratchpad' && xml == null)
  4599. {
  4600. xml = this.emptyLibraryXml;
  4601. }
  4602. if (xml != null)
  4603. {
  4604. onload(new StorageLibrary(this, xml, name));
  4605. }
  4606. else
  4607. {
  4608. onerror();
  4609. }
  4610. }), onerror);
  4611. }
  4612. catch (e)
  4613. {
  4614. onerror();
  4615. }
  4616. }), 0);
  4617. }
  4618. }
  4619. else if (service == 'U')
  4620. {
  4621. var url = decodeURIComponent(id.substring(1));
  4622. if (!this.isOffline())
  4623. {
  4624. this.loadTemplate(url, mxUtils.bind(this, function(text)
  4625. {
  4626. if (text != null && text.length > 0)
  4627. {
  4628. // LATER: Convert mxfile to mxlibrary using code from libraryLoaded
  4629. onload(new UrlLibrary(this, text, url));
  4630. }
  4631. else
  4632. {
  4633. onerror();
  4634. }
  4635. }), function()
  4636. {
  4637. onerror();
  4638. }, null, true);
  4639. }
  4640. }
  4641. else if (service == 'R')
  4642. {
  4643. var libDesc = decodeURIComponent(id.substring(1));
  4644. if (!this.isOffline())
  4645. {
  4646. try
  4647. {
  4648. libDesc = JSON.parse(libDesc);
  4649. var libObj = {
  4650. id: libDesc[0],
  4651. title: libDesc[1],
  4652. downloadUrl: libDesc[2]
  4653. }
  4654. this.remoteInvoke('getFileContent', [libObj.downloadUrl], null, mxUtils.bind(this, function(libContent)
  4655. {
  4656. try
  4657. {
  4658. onload(new RemoteLibrary(this, libContent, libObj));
  4659. }
  4660. catch (e)
  4661. {
  4662. onerror();
  4663. }
  4664. }), function()
  4665. {
  4666. onerror();
  4667. });
  4668. }
  4669. catch (e)
  4670. {
  4671. onerror();
  4672. }
  4673. }
  4674. }
  4675. else if (service == 'S' && this.loadDesktopLib != null)
  4676. {
  4677. try
  4678. {
  4679. this.loadDesktopLib(decodeURIComponent(id.substring(1)), function(desktopLib)
  4680. {
  4681. onload(desktopLib);
  4682. }, onerror);
  4683. }
  4684. catch (e)
  4685. {
  4686. onerror();
  4687. }
  4688. }
  4689. else
  4690. {
  4691. var peer = null;
  4692. if (service == 'G')
  4693. {
  4694. if (this.drive != null && this.drive.user != null)
  4695. {
  4696. peer = this.drive;
  4697. }
  4698. }
  4699. else if (service == 'H')
  4700. {
  4701. if (this.gitHub != null && this.gitHub.getUser() != null)
  4702. {
  4703. peer = this.gitHub;
  4704. }
  4705. }
  4706. else if (service == 'T')
  4707. {
  4708. if (this.trello != null && this.trello.isAuthorized())
  4709. {
  4710. peer = this.trello;
  4711. }
  4712. }
  4713. else if (service == 'D')
  4714. {
  4715. if (this.dropbox != null && this.dropbox.getUser() != null)
  4716. {
  4717. peer = this.dropbox;
  4718. }
  4719. }
  4720. else if (service == 'W')
  4721. {
  4722. if (this.oneDrive != null && this.oneDrive.getUser() != null)
  4723. {
  4724. peer = this.oneDrive;
  4725. }
  4726. }
  4727. if (peer != null)
  4728. {
  4729. peer.getLibrary(decodeURIComponent(id.substring(1)), mxUtils.bind(this, function(file)
  4730. {
  4731. try
  4732. {
  4733. onload(file);
  4734. }
  4735. catch (e)
  4736. {
  4737. onerror();
  4738. }
  4739. }), function(resp)
  4740. {
  4741. onerror();
  4742. });
  4743. }
  4744. else
  4745. {
  4746. onerror(true);
  4747. }
  4748. }
  4749. }
  4750. }))(name, i);
  4751. }
  4752. checkDone();
  4753. }
  4754. else
  4755. {
  4756. checkDone();
  4757. }
  4758. }
  4759. };
  4760. /**
  4761. * Translates this point by the given vector.
  4762. *
  4763. * @param {number} dx X-coordinate of the translation.
  4764. * @param {number} dy Y-coordinate of the translation.
  4765. */
  4766. App.prototype.updateButtonContainer = function()
  4767. {
  4768. if (this.buttonContainer != null)
  4769. {
  4770. var file = this.getCurrentFile();
  4771. // Comments
  4772. if (this.commentsSupported())
  4773. {
  4774. if (this.commentButton == null)
  4775. {
  4776. this.commentButton = document.createElement('a');
  4777. this.commentButton.setAttribute('title', mxResources.get('comments'));
  4778. this.commentButton.className = 'geToolbarButton';
  4779. this.commentButton.style.cssText = 'display:inline-block;position:relative;box-sizing:border-box;' +
  4780. 'margin-right:4px;float:left;cursor:pointer;width:24px;height:24px;background-size:24px 24px;' +
  4781. 'background-position:center center;background-repeat:no-repeat;background-image:' +
  4782. 'url(' + Editor.commentImage + ');';
  4783. if (uiTheme == 'atlas')
  4784. {
  4785. this.commentButton.style.marginRight = '10px';
  4786. this.commentButton.style.marginTop = '-3px';
  4787. }
  4788. else if (uiTheme == 'min')
  4789. {
  4790. this.commentButton.style.marginTop = '1px';
  4791. }
  4792. else
  4793. {
  4794. this.commentButton.style.marginTop = '-5px';
  4795. }
  4796. mxEvent.addListener(this.commentButton, 'click', mxUtils.bind(this, function()
  4797. {
  4798. this.actions.get('comments').funct();
  4799. }));
  4800. this.buttonContainer.appendChild(this.commentButton);
  4801. if (uiTheme == 'dark' || uiTheme == 'atlas')
  4802. {
  4803. this.commentButton.style.filter = 'invert(100%)';
  4804. }
  4805. }
  4806. }
  4807. else if (this.commentButton != null)
  4808. {
  4809. this.commentButton.parentNode.removeChild(this.commentButton);
  4810. this.commentButton = null;
  4811. }
  4812. // Share
  4813. if (urlParams['embed'] != '1' && this.getServiceName() == 'draw.io' &&
  4814. !mxClient.IS_CHROMEAPP && !EditorUi.isElectronApp &&
  4815. !this.isOfflineApp() && file != null)
  4816. {
  4817. if (this.shareButton == null)
  4818. {
  4819. this.shareButton = document.createElement('div');
  4820. this.shareButton.className = 'geBtn gePrimaryBtn';
  4821. this.shareButton.style.display = 'inline-block';
  4822. this.shareButton.style.backgroundColor = '#F2931E';
  4823. this.shareButton.style.borderColor = '#F08705';
  4824. this.shareButton.style.backgroundImage = 'none';
  4825. this.shareButton.style.padding = '2px 10px 0 10px';
  4826. this.shareButton.style.marginTop = '-10px';
  4827. this.shareButton.style.height = '28px';
  4828. this.shareButton.style.lineHeight = '28px';
  4829. this.shareButton.style.minWidth = '0px';
  4830. this.shareButton.style.cssFloat = 'right';
  4831. this.shareButton.setAttribute('title', mxResources.get('share'));
  4832. var icon = document.createElement('img');
  4833. icon.setAttribute('src', this.shareImage);
  4834. icon.setAttribute('align', 'absmiddle');
  4835. icon.style.marginRight = '4px';
  4836. icon.style.marginTop = '-3px';
  4837. this.shareButton.appendChild(icon);
  4838. if (uiTheme != 'dark' && uiTheme != 'atlas')
  4839. {
  4840. this.shareButton.style.color = 'black';
  4841. icon.style.filter = 'invert(100%)';
  4842. }
  4843. mxUtils.write(this.shareButton, mxResources.get('share'));
  4844. mxEvent.addListener(this.shareButton, 'click', mxUtils.bind(this, function()
  4845. {
  4846. this.actions.get('share').funct();
  4847. }));
  4848. this.buttonContainer.appendChild(this.shareButton);
  4849. }
  4850. }
  4851. else if (this.shareButton != null)
  4852. {
  4853. this.shareButton.parentNode.removeChild(this.shareButton);
  4854. this.shareButton = null;
  4855. }
  4856. }
  4857. };
  4858. /**
  4859. * Translates this point by the given vector.
  4860. *
  4861. * @param {number} dx X-coordinate of the translation.
  4862. * @param {number} dy Y-coordinate of the translation.
  4863. */
  4864. App.prototype.save = function(name, done)
  4865. {
  4866. var file = this.getCurrentFile();
  4867. if (file != null && this.spinner.spin(document.body, mxResources.get('saving')))
  4868. {
  4869. this.editor.setStatus('');
  4870. if (this.editor.graph.isEditing())
  4871. {
  4872. this.editor.graph.stopEditing();
  4873. }
  4874. var success = mxUtils.bind(this, function()
  4875. {
  4876. file.handleFileSuccess(true);
  4877. if (done != null)
  4878. {
  4879. done();
  4880. }
  4881. });
  4882. var error = mxUtils.bind(this, function(err)
  4883. {
  4884. if (file.isModified())
  4885. {
  4886. Editor.addRetryToError(err, mxUtils.bind(this, function()
  4887. {
  4888. this.save(name, done);
  4889. }));
  4890. }
  4891. file.handleFileError(err, true);
  4892. });
  4893. try
  4894. {
  4895. if (name == file.getTitle())
  4896. {
  4897. file.save(true, success, error);
  4898. }
  4899. else
  4900. {
  4901. file.saveAs(name, success, error)
  4902. }
  4903. }
  4904. catch (err)
  4905. {
  4906. error(err);
  4907. }
  4908. }
  4909. };
  4910. /**
  4911. * Invokes callback with null if mode does not support folder or not null
  4912. * if a valid folder was chosen for a mode that supports it. No callback
  4913. * is made if no folder was chosen for a mode that supports it.
  4914. */
  4915. App.prototype.pickFolder = function(mode, fn, enabled, direct, force)
  4916. {
  4917. enabled = (enabled != null) ? enabled : true;
  4918. var resume = this.spinner.pause();
  4919. if (enabled && mode == App.MODE_GOOGLE && this.drive != null)
  4920. {
  4921. // Shows a save dialog
  4922. this.drive.pickFolder(mxUtils.bind(this, function(evt)
  4923. {
  4924. resume();
  4925. if (evt.action == google.picker.Action.PICKED)
  4926. {
  4927. var folderId = null;
  4928. if (evt.docs != null && evt.docs.length > 0 && evt.docs[0].type == 'folder')
  4929. {
  4930. folderId = evt.docs[0].id;
  4931. }
  4932. fn(folderId);
  4933. }
  4934. }), force);
  4935. }
  4936. else if (enabled && mode == App.MODE_ONEDRIVE && this.oneDrive != null)
  4937. {
  4938. this.oneDrive.pickFolder(mxUtils.bind(this, function(files)
  4939. {
  4940. var folderId = null;
  4941. resume();
  4942. if (files != null && files.value != null && files.value.length > 0)
  4943. {
  4944. folderId = OneDriveFile.prototype.getIdOf(files.value[0]);
  4945. fn(folderId);
  4946. }
  4947. }), direct);
  4948. }
  4949. else if (enabled && mode == App.MODE_GITHUB && this.gitHub != null)
  4950. {
  4951. this.gitHub.pickFolder(mxUtils.bind(this, function(folderPath)
  4952. {
  4953. resume();
  4954. fn(folderPath);
  4955. }));
  4956. }
  4957. else if (enabled && mode == App.MODE_GITLAB && this.gitLab != null)
  4958. {
  4959. this.gitLab.pickFolder(mxUtils.bind(this, function(folderPath)
  4960. {
  4961. resume();
  4962. fn(folderPath);
  4963. }));
  4964. }
  4965. else if (enabled && mode == App.MODE_TRELLO && this.trello != null)
  4966. {
  4967. this.trello.pickFolder(mxUtils.bind(this, function(cardId)
  4968. {
  4969. resume();
  4970. fn(cardId);
  4971. }));
  4972. }
  4973. else
  4974. {
  4975. EditorUi.prototype.pickFolder.apply(this, arguments);
  4976. }
  4977. };
  4978. /**
  4979. *
  4980. */
  4981. App.prototype.exportFile = function(data, filename, mimeType, base64Encoded, mode, folderId)
  4982. {
  4983. if (mode == App.MODE_DROPBOX)
  4984. {
  4985. if (this.dropbox != null && this.spinner.spin(document.body, mxResources.get('saving')))
  4986. {
  4987. // LATER: Add folder picker
  4988. this.dropbox.insertFile(filename, (base64Encoded) ? this.base64ToBlob(data, mimeType) :
  4989. data, mxUtils.bind(this, function()
  4990. {
  4991. this.spinner.stop();
  4992. }), mxUtils.bind(this, function(resp)
  4993. {
  4994. this.spinner.stop();
  4995. this.handleError(resp);
  4996. }));
  4997. }
  4998. }
  4999. else if (mode == App.MODE_GOOGLE)
  5000. {
  5001. if (this.drive != null && this.spinner.spin(document.body, mxResources.get('saving')))
  5002. {
  5003. this.drive.insertFile(filename, data, folderId, mxUtils.bind(this, function(resp)
  5004. {
  5005. // TODO: Add callback with url param for clickable status message
  5006. // "File exported. Click here to open folder."
  5007. // this.editor.setStatus('<div class="geStatusMessage" style="cursor:pointer;">' +
  5008. // mxResources.get('saved') + '</div>');
  5009. //
  5010. // // Installs click handler for opening
  5011. // if (this.statusContainer != null)
  5012. // {
  5013. // var links = this.statusContainer.getElementsByTagName('div');
  5014. //
  5015. // if (links.length > 0)
  5016. // {
  5017. // mxEvent.addListener(links[0], 'click', mxUtils.bind(this, function()
  5018. // {
  5019. // if (resp != null && resp.id != null)
  5020. // {
  5021. // window.open('https://drive.google.com/open?id=' + resp.id);
  5022. // }
  5023. // }));
  5024. // }
  5025. // }
  5026. this.spinner.stop();
  5027. }), mxUtils.bind(this, function(resp)
  5028. {
  5029. this.spinner.stop();
  5030. this.handleError(resp);
  5031. }), mimeType, base64Encoded);
  5032. }
  5033. }
  5034. else if (mode == App.MODE_ONEDRIVE)
  5035. {
  5036. if (this.oneDrive != null && this.spinner.spin(document.body, mxResources.get('saving')))
  5037. {
  5038. // KNOWN: OneDrive does not show .svg extension
  5039. this.oneDrive.insertFile(filename, (base64Encoded) ? this.base64ToBlob(data, mimeType) :
  5040. data, mxUtils.bind(this, function()
  5041. {
  5042. this.spinner.stop();
  5043. }), mxUtils.bind(this, function(resp)
  5044. {
  5045. this.spinner.stop();
  5046. this.handleError(resp);
  5047. }), false, folderId);
  5048. }
  5049. }
  5050. else if (mode == App.MODE_GITHUB)
  5051. {
  5052. if (this.gitHub != null && this.spinner.spin(document.body, mxResources.get('saving')))
  5053. {
  5054. // Must insert file as library to force the file to be written
  5055. this.gitHub.insertFile(filename, data, mxUtils.bind(this, function()
  5056. {
  5057. this.spinner.stop();
  5058. }), mxUtils.bind(this, function(resp)
  5059. {
  5060. this.spinner.stop();
  5061. this.handleError(resp);
  5062. }), true, folderId, base64Encoded);
  5063. }
  5064. }
  5065. else if (mode == App.MODE_TRELLO)
  5066. {
  5067. if (this.trello != null && this.spinner.spin(document.body, mxResources.get('saving')))
  5068. {
  5069. this.trello.insertFile(filename, (base64Encoded) ? this.base64ToBlob(data, mimeType) :
  5070. data, mxUtils.bind(this, function()
  5071. {
  5072. this.spinner.stop();
  5073. }), mxUtils.bind(this, function(resp)
  5074. {
  5075. this.spinner.stop();
  5076. this.handleError(resp);
  5077. }), false, folderId);
  5078. }
  5079. }
  5080. else if (mode == App.MODE_BROWSER)
  5081. {
  5082. var fn = mxUtils.bind(this, function()
  5083. {
  5084. localStorage.setItem(filename, data);
  5085. });
  5086. if (localStorage.getItem(filename) == null)
  5087. {
  5088. fn();
  5089. }
  5090. else
  5091. {
  5092. this.confirm(mxResources.get('replaceIt', [filename]), fn);
  5093. }
  5094. }
  5095. };
  5096. /**
  5097. * Translates this point by the given vector.
  5098. *
  5099. * @param {number} dx X-coordinate of the translation.
  5100. * @param {number} dy Y-coordinate of the translation.
  5101. */
  5102. App.prototype.descriptorChanged = function()
  5103. {
  5104. var file = this.getCurrentFile();
  5105. if (file != null)
  5106. {
  5107. if (this.fname != null)
  5108. {
  5109. this.fnameWrapper.style.display = 'block';
  5110. this.fname.innerHTML = '';
  5111. var filename = (file.getTitle() != null) ? file.getTitle() : this.defaultFilename;
  5112. mxUtils.write(this.fname, filename);
  5113. this.fname.setAttribute('title', filename + ' - ' + mxResources.get('rename'));
  5114. }
  5115. var graph = this.editor.graph;
  5116. var editable = file.isEditable() && !file.invalidChecksum;
  5117. if (graph.isEnabled() && !editable)
  5118. {
  5119. graph.reset();
  5120. }
  5121. graph.setEnabled(editable);
  5122. // Ignores title and hash for revisions
  5123. if (urlParams['rev'] == null)
  5124. {
  5125. this.updateDocumentTitle();
  5126. var newHash = file.getHash();
  5127. if (newHash.length > 0)
  5128. {
  5129. window.location.hash = newHash;
  5130. }
  5131. else if (window.location.hash.length > 0)
  5132. {
  5133. window.location.hash = '';
  5134. }
  5135. }
  5136. }
  5137. this.updateUi();
  5138. if (this.format != null && this.editor.graph.isSelectionEmpty())
  5139. {
  5140. this.format.refresh();
  5141. }
  5142. };
  5143. /**
  5144. * Adds the listener for automatically saving the diagram for local changes.
  5145. */
  5146. App.prototype.showAuthDialog = function(peer, showRememberOption, fn, closeFn)
  5147. {
  5148. var resume = this.spinner.pause();
  5149. this.showDialog(new AuthDialog(this, peer, showRememberOption, mxUtils.bind(this, function(remember)
  5150. {
  5151. try
  5152. {
  5153. if (fn != null)
  5154. {
  5155. fn(remember, mxUtils.bind(this, function()
  5156. {
  5157. this.hideDialog();
  5158. resume();
  5159. }));
  5160. }
  5161. }
  5162. catch (e)
  5163. {
  5164. this.editor.setStatus(mxUtils.htmlEntities(e.message));
  5165. }
  5166. })).container, 300, (showRememberOption) ? 180 : 140, true, true, mxUtils.bind(this, function(cancel)
  5167. {
  5168. if (closeFn != null)
  5169. {
  5170. closeFn();
  5171. }
  5172. if (cancel && this.getCurrentFile() == null && this.dialog == null)
  5173. {
  5174. this.showSplash();
  5175. }
  5176. }));
  5177. };
  5178. /**
  5179. * Checks if the client is authorized and calls the next step. The optional
  5180. * readXml argument is used for import. Default is false. The optional
  5181. * readLibrary argument is used for reading libraries. Default is false.
  5182. */
  5183. App.prototype.convertFile = function(url, filename, mimeType, extension, success, error, executeRequest, headers)
  5184. {
  5185. var name = filename;
  5186. // SVG file extensions are valid and needed for image import
  5187. if (!/\.svg$/i.test(name))
  5188. {
  5189. name = name.substring(0, filename.lastIndexOf('.')) + extension;
  5190. }
  5191. var gitHubUrl = false;
  5192. if (this.gitHub != null && url.substring(0, this.gitHub.baseUrl.length) == this.gitHub.baseUrl)
  5193. {
  5194. gitHubUrl = true;
  5195. }
  5196. // Workaround for wrong binary response with VSD(X) & VDX files
  5197. if (/\.v(dx|sdx?)$/i.test(filename) && Graph.fileSupport && new XMLHttpRequest().upload &&
  5198. typeof new XMLHttpRequest().responseType === 'string')
  5199. {
  5200. var req = new XMLHttpRequest();
  5201. req.open('GET', url, true);
  5202. if (!gitHubUrl)
  5203. {
  5204. req.responseType = 'blob';
  5205. }
  5206. if (headers)
  5207. {
  5208. for (var key in headers)
  5209. {
  5210. req.setRequestHeader(key, headers[key]);
  5211. }
  5212. }
  5213. req.onload = mxUtils.bind(this, function()
  5214. {
  5215. if (req.status >= 200 && req.status <= 299)
  5216. {
  5217. var blob = null;
  5218. if (gitHubUrl)
  5219. {
  5220. var file = JSON.parse(req.responseText);
  5221. blob = this.base64ToBlob(file.content, 'application/octet-stream');
  5222. }
  5223. else
  5224. {
  5225. blob = new Blob([req.response], {type: 'application/octet-stream'});
  5226. }
  5227. this.importVisio(blob, mxUtils.bind(this, function(xml)
  5228. {
  5229. success(new LocalFile(this, xml, name, true));
  5230. }), error, filename)
  5231. }
  5232. else if (error != null)
  5233. {
  5234. error({message: mxResources.get('errorLoadingFile')});
  5235. }
  5236. });
  5237. req.onerror = error;
  5238. req.send();
  5239. }
  5240. else
  5241. {
  5242. var handleData = mxUtils.bind(this, function(data)
  5243. {
  5244. try
  5245. {
  5246. if (/\.pdf$/i.test(filename))
  5247. {
  5248. var temp = Editor.extractGraphModelFromPdf(data);
  5249. if (temp != null && temp.length > 0)
  5250. {
  5251. success(new LocalFile(this, temp, name, true));
  5252. }
  5253. }
  5254. else if (/\.png$/i.test(filename))
  5255. {
  5256. var temp = this.extractGraphModelFromPng(data);
  5257. if (temp != null)
  5258. {
  5259. success(new LocalFile(this, temp, name, true));
  5260. }
  5261. else
  5262. {
  5263. success(new LocalFile(this, data, filename, true));
  5264. }
  5265. }
  5266. else if (Graph.fileSupport && new XMLHttpRequest().upload && this.isRemoteFileFormat(data, url))
  5267. {
  5268. this.parseFile(new Blob([data], {type: 'application/octet-stream'}), mxUtils.bind(this, function(xhr)
  5269. {
  5270. if (xhr.readyState == 4)
  5271. {
  5272. if (xhr.status >= 200 && xhr.status <= 299)
  5273. {
  5274. success(new LocalFile(this, xhr.responseText, name, true));
  5275. }
  5276. else if (error != null)
  5277. {
  5278. error({message: mxResources.get('errorLoadingFile')});
  5279. }
  5280. }
  5281. }), filename);
  5282. }
  5283. else
  5284. {
  5285. success(new LocalFile(this, data, name, true));
  5286. }
  5287. }
  5288. catch (e)
  5289. {
  5290. if (error != null)
  5291. {
  5292. error(e);
  5293. }
  5294. }
  5295. });
  5296. var binary = /\.png$/i.test(filename) || /\.jpe?g$/i.test(filename) ||
  5297. /\.pdf$/i.test(filename) || (mimeType != null &&
  5298. mimeType.substring(0, 6) == 'image/');
  5299. // NOTE: Cannot force non-binary request via loadUrl so needs separate
  5300. // code as decoding twice on content with binary data did not work
  5301. if (gitHubUrl)
  5302. {
  5303. mxUtils.get(url, mxUtils.bind(this, function(req)
  5304. {
  5305. if (req.getStatus() >= 200 && req.getStatus() <= 299)
  5306. {
  5307. if (success != null)
  5308. {
  5309. var file = JSON.parse(req.getText());
  5310. var data = file.content;
  5311. if (file.encoding === 'base64')
  5312. {
  5313. if (/\.png$/i.test(filename))
  5314. {
  5315. data = 'data:image/png;base64,' + data;
  5316. }
  5317. else if (/\.pdf$/i.test(filename))
  5318. {
  5319. data = 'data:application/pdf;base64,' + data;
  5320. }
  5321. else
  5322. {
  5323. // Workaround for character encoding issues in IE10/11
  5324. data = (window.atob && !mxClient.IS_IE && !mxClient.IS_IE11) ? atob(data) : Base64.decode(data);
  5325. }
  5326. }
  5327. handleData(data);
  5328. }
  5329. }
  5330. else if (error != null)
  5331. {
  5332. error({code: App.ERROR_UNKNOWN});
  5333. }
  5334. }), function()
  5335. {
  5336. if (error != null)
  5337. {
  5338. error({code: App.ERROR_UNKNOWN});
  5339. }
  5340. }, false, this.timeout, function()
  5341. {
  5342. if (error != null)
  5343. {
  5344. error({code: App.ERROR_TIMEOUT, retry: fn});
  5345. }
  5346. }, headers);
  5347. }
  5348. else if (executeRequest != null)
  5349. {
  5350. executeRequest(url, handleData, error, binary);
  5351. }
  5352. else
  5353. {
  5354. this.editor.loadUrl(url, handleData, error, binary, null, null, null, headers);
  5355. }
  5356. }
  5357. };
  5358. /**
  5359. * Adds the listener for automatically saving the diagram for local changes.
  5360. */
  5361. App.prototype.updateHeader = function()
  5362. {
  5363. if (this.menubar != null)
  5364. {
  5365. this.appIcon = document.createElement('a');
  5366. this.appIcon.style.display = 'block';
  5367. this.appIcon.style.position = 'absolute';
  5368. this.appIcon.style.width = '32px';
  5369. this.appIcon.style.height = (this.menubarHeight - 28) + 'px';
  5370. this.appIcon.style.margin = '14px 0px 8px 16px';
  5371. this.appIcon.style.opacity = '0.85';
  5372. this.appIcon.style.borderRadius = '3px';
  5373. if (uiTheme != 'dark')
  5374. {
  5375. this.appIcon.style.backgroundColor = '#f08705';
  5376. }
  5377. mxEvent.disableContextMenu(this.appIcon);
  5378. mxEvent.addListener(this.appIcon, 'click', mxUtils.bind(this, function(evt)
  5379. {
  5380. this.appIconClicked(evt);
  5381. }));
  5382. // LATER: Use Alpha image loader in IE6
  5383. // NOTE: This uses the diagram bit of the old logo as it looks better in this case
  5384. //this.appIcon.style.filter = 'progid:DXImageTransform.Microsoft.AlphaImageLoader(src=' + IMAGE_PATH + '/logo-white.png,sizingMethod=\'scale\')';
  5385. var logo = (!mxClient.IS_SVG) ? 'url(\'' + IMAGE_PATH + '/logo-white.png\')' :
  5386. ((uiTheme == 'dark') ? 'url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+CjxzdmcKICAgeG1sbnM6ZGM9Imh0dHA6Ly9wdXJsLm9yZy9kYy9lbGVtZW50cy8xLjEvIgogICB4bWxuczpjYz0iaHR0cDovL2NyZWF0aXZlY29tbW9ucy5vcmcvbnMjIgogICB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiCiAgIHhtbG5zOnN2Zz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciCiAgIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIKICAgeG1sOnNwYWNlPSJwcmVzZXJ2ZSIKICAgZW5hYmxlLWJhY2tncm91bmQ9Im5ldyAwIDAgMzA2LjE4NSAxMjAuMjk2IgogICB2aWV3Qm94PSIyNCAyNiA2OCA2OCIKICAgeT0iMHB4IgogICB4PSIwcHgiCiAgIHZlcnNpb249IjEuMSI+CiAgIAkgPGc+PGxpbmUKICAgICAgIHkyPSI3Mi4zOTQiCiAgICAgICB4Mj0iNDEuMDYxIgogICAgICAgeTE9IjQzLjM4NCIKICAgICAgIHgxPSI1OC4wNjkiCiAgICAgICBzdHJva2UtbWl0ZXJsaW1pdD0iMTAiCiAgICAgICBzdHJva2Utd2lkdGg9IjMuNTUyOCIKICAgICAgIHN0cm9rZT0iI0ZGRkZGRiIKICAgICAgIGZpbGw9Im5vbmUiIC8+PGxpbmUKICAgICAgIHkyPSI3Mi4zOTQiCiAgICAgICB4Mj0iNzUuMDc2IgogICAgICAgeTE9IjQzLjM4NCIKICAgICAgIHgxPSI1OC4wNjgiCiAgICAgICBzdHJva2UtbWl0ZXJsaW1pdD0iMTAiCiAgICAgICBzdHJva2Utd2lkdGg9IjMuNTAwOCIKICAgICAgIHN0cm9rZT0iI0ZGRkZGRiIKICAgICAgIGZpbGw9Im5vbmUiIC8+PGc+PHBhdGgKICAgICAgICAgZD0iTTUyLjc3Myw3Ny4wODRjMCwxLjk1NC0xLjU5OSwzLjU1My0zLjU1MywzLjU1M0gzNi45OTljLTEuOTU0LDAtMy41NTMtMS41OTktMy41NTMtMy41NTN2LTkuMzc5ICAgIGMwLTEuOTU0LDEuNTk5LTMuNTUzLDMuNTUzLTMuNTUzaDEyLjIyMmMxLjk1NCwwLDMuNTUzLDEuNTk5LDMuNTUzLDMuNTUzVjc3LjA4NHoiCiAgICAgICAgIGZpbGw9IiNGRkZGRkYiIC8+PC9nPjxnCiAgICAgICBpZD0iZzM0MTkiPjxwYXRoCiAgICAgICAgIGQ9Ik02Ny43NjIsNDguMDc0YzAsMS45NTQtMS41OTksMy41NTMtMy41NTMsMy41NTNINTEuOTg4Yy0xLjk1NCwwLTMuNTUzLTEuNTk5LTMuNTUzLTMuNTUzdi05LjM3OSAgICBjMC0xLjk1NCwxLjU5OS0zLjU1MywzLjU1My0zLjU1M0g2NC4yMWMxLjk1NCwwLDMuNTUzLDEuNTk5LDMuNTUzLDMuNTUzVjQ4LjA3NHoiCiAgICAgICAgIGZpbGw9IiNGRkZGRkYiIC8+PC9nPjxnPjxwYXRoCiAgICAgICAgIGQ9Ik04Mi43NTIsNzcuMDg0YzAsMS45NTQtMS41OTksMy41NTMtMy41NTMsMy41NTNINjYuOTc3Yy0xLjk1NCwwLTMuNTUzLTEuNTk5LTMuNTUzLTMuNTUzdi05LjM3OSAgICBjMC0xLjk1NCwxLjU5OS0zLjU1MywzLjU1My0zLjU1M2gxMi4yMjJjMS45NTQsMCwzLjU1MywxLjU5OSwzLjU1MywzLjU1M1Y3Ny4wODR6IgogICAgICAgICBmaWxsPSIjRkZGRkZGIiAvPjwvZz48L2c+PC9zdmc+)' :
  5387. 'url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4KPHN2ZyB2ZXJzaW9uPSIxLjEiIGlkPSJFYmVuZV8xIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB4PSIwcHgiIHk9IjBweCIKCSB2aWV3Qm94PSIwIDAgMjI1IDIyNSIgc3R5bGU9ImVuYWJsZS1iYWNrZ3JvdW5kOm5ldyAwIDAgMjI1IDIyNTsiIHhtbDpzcGFjZT0icHJlc2VydmUiPgo8c3R5bGUgdHlwZT0idGV4dC9jc3MiPgoJLnN0MXtmaWxsOiNERjZDMEM7fQoJLnN0MntmaWxsOiNGRkZGRkY7fQo8L3N0eWxlPgo8cGF0aCBjbGFzcz0ic3QxIiBkPSJNMjI1LDIxNS40YzAsNS4zLTQuMyw5LjYtOS41LDkuNmwwLDBINzcuMWwtNDQuOC00NS41TDYwLjIsMTM0bDgyLjctMTAyLjdsODIuMSw4NC41VjIxNS40eiIvPgo8cGF0aCBjbGFzcz0ic3QyIiBkPSJNMTg0LjYsMTI1LjhoLTIzLjdsLTI1LTQyLjdjNS43LTEuMiw5LjgtNi4yLDkuNy0xMlYzOWMwLTYuOC01LjQtMTIuMy0xMi4yLTEyLjNoLTAuMUg5MS42CgljLTYuOCwwLTEyLjMsNS40LTEyLjMsMTIuMlYzOXYzMi4xYzAsNS44LDQsMTAuOCw5LjcsMTJsLTI1LDQyLjdINDAuNGMtNi44LDAtMTIuMyw1LjQtMTIuMywxMi4ydjAuMXYzMi4xCgljMCw2LjgsNS40LDEyLjMsMTIuMiwxMi4zaDAuMWg0MS43YzYuOCwwLDEyLjMtNS40LDEyLjMtMTIuMnYtMC4xdi0zMi4xYzAtNi44LTUuNC0xMi4zLTEyLjItMTIuM2gtMC4xaC00bDI0LjgtNDIuNGgxOS4zCglsMjQuOSw0Mi40SDE0M2MtNi44LDAtMTIuMyw1LjQtMTIuMywxMi4ydjAuMXYzMi4xYzAsNi44LDUuNCwxMi4zLDEyLjIsMTIuM2gwLjFoNDEuN2M2LjgsMCwxMi4zLTUuNCwxMi4zLTEyLjJ2LTAuMXYtMzIuMQoJYzAtNi44LTUuNC0xMi4zLTEyLjItMTIuM0MxODQuNywxMjUuOCwxODQuNywxMjUuOCwxODQuNiwxMjUuOHoiLz4KPC9zdmc+Cg==)');
  5388. this.appIcon.style.backgroundImage = logo;
  5389. this.appIcon.style.backgroundPosition = 'center center';
  5390. this.appIcon.style.backgroundSize = '100% 100%';
  5391. this.appIcon.style.backgroundRepeat = 'no-repeat';
  5392. mxUtils.setPrefixedStyle(this.appIcon.style, 'transition', 'all 125ms linear');
  5393. mxEvent.addListener(this.appIcon, 'mouseover', mxUtils.bind(this, function()
  5394. {
  5395. var file = this.getCurrentFile();
  5396. if (file != null)
  5397. {
  5398. var mode = file.getMode();
  5399. if (mode == App.MODE_GOOGLE)
  5400. {
  5401. this.appIcon.style.backgroundImage = 'url(' + IMAGE_PATH + '/google-drive-logo-white.svg)';
  5402. this.appIcon.style.backgroundSize = '70% 70%';
  5403. }
  5404. else if (mode == App.MODE_DROPBOX)
  5405. {
  5406. this.appIcon.style.backgroundImage = 'url(' + IMAGE_PATH + '/dropbox-logo-white.svg)';
  5407. this.appIcon.style.backgroundSize = '70% 70%';
  5408. }
  5409. else if (mode == App.MODE_ONEDRIVE)
  5410. {
  5411. this.appIcon.style.backgroundImage = 'url(' + IMAGE_PATH + '/onedrive-logo-white.svg)';
  5412. this.appIcon.style.backgroundSize = '70% 70%';
  5413. }
  5414. else if (mode == App.MODE_GITHUB)
  5415. {
  5416. this.appIcon.style.backgroundImage = 'url(' + IMAGE_PATH + '/github-logo-white.svg)';
  5417. this.appIcon.style.backgroundSize = '70% 70%';
  5418. }
  5419. else if (mode == App.MODE_GITLAB)
  5420. {
  5421. this.appIcon.style.backgroundImage = 'url(' + IMAGE_PATH + '/gitlab-logo-white.svg)';
  5422. this.appIcon.style.backgroundSize = '100% 100%';
  5423. }
  5424. else if (mode == App.MODE_TRELLO)
  5425. {
  5426. this.appIcon.style.backgroundImage = 'url(' + IMAGE_PATH + '/trello-logo-white-orange.svg)';
  5427. this.appIcon.style.backgroundSize = '70% 70%';
  5428. }
  5429. }
  5430. }));
  5431. mxEvent.addListener(this.appIcon, 'mouseout', mxUtils.bind(this, function()
  5432. {
  5433. this.appIcon.style.backgroundImage = logo;
  5434. this.appIcon.style.backgroundSize = '90% 90%';
  5435. }));
  5436. if (urlParams['embed'] != '1')
  5437. {
  5438. this.menubarContainer.appendChild(this.appIcon);
  5439. }
  5440. this.fnameWrapper = document.createElement('div');
  5441. this.fnameWrapper.style.position = 'absolute';
  5442. this.fnameWrapper.style.right = '120px';
  5443. this.fnameWrapper.style.left = '60px';
  5444. this.fnameWrapper.style.top = '9px';
  5445. this.fnameWrapper.style.height = '26px';
  5446. this.fnameWrapper.style.display = 'none';
  5447. this.fnameWrapper.style.overflow = 'hidden';
  5448. this.fnameWrapper.style.textOverflow = 'ellipsis';
  5449. this.fname = document.createElement('a');
  5450. this.fname.setAttribute('title', mxResources.get('rename'));
  5451. this.fname.className = 'geItem';
  5452. this.fname.style.padding = '2px 8px 2px 8px';
  5453. this.fname.style.display = 'inline';
  5454. this.fname.style.fontSize = '18px';
  5455. this.fname.style.whiteSpace = 'nowrap';
  5456. // Prevents focus
  5457. mxEvent.addListener(this.fname, (mxClient.IS_POINTER) ? 'pointerdown' : 'mousedown',
  5458. mxUtils.bind(this, function(evt)
  5459. {
  5460. evt.preventDefault();
  5461. }));
  5462. mxEvent.addListener(this.fname, 'click', mxUtils.bind(this, function(evt)
  5463. {
  5464. var file = this.getCurrentFile();
  5465. if (file != null && file.isRenamable())
  5466. {
  5467. if (this.editor.graph.isEditing())
  5468. {
  5469. this.editor.graph.stopEditing();
  5470. }
  5471. this.actions.get('rename').funct();
  5472. }
  5473. mxEvent.consume(evt);
  5474. }));
  5475. this.fnameWrapper.appendChild(this.fname);
  5476. if (urlParams['embed'] != '1')
  5477. {
  5478. this.menubarContainer.appendChild(this.fnameWrapper);
  5479. this.menubar.container.style.position = 'absolute';
  5480. this.menubar.container.style.paddingLeft = '59px';
  5481. this.toolbar.container.style.paddingLeft = '16px';
  5482. this.menubar.container.style.boxSizing = 'border-box';
  5483. this.menubar.container.style.top = '34px';
  5484. }
  5485. /**
  5486. * Adds format panel toggle.
  5487. */
  5488. this.toggleFormatElement = document.createElement('a');
  5489. this.toggleFormatElement.setAttribute('title', mxResources.get('formatPanel') + ' (' + Editor.ctrlKey + '+Shift+P)');
  5490. this.toggleFormatElement.style.position = 'absolute';
  5491. this.toggleFormatElement.style.display = 'inline-block';
  5492. this.toggleFormatElement.style.top = (uiTheme == 'atlas') ? '8px' : '6px';
  5493. this.toggleFormatElement.style.right = (uiTheme != 'atlas' && urlParams['embed'] != '1') ? '30px' : '10px';
  5494. this.toggleFormatElement.style.padding = '2px';
  5495. this.toggleFormatElement.style.fontSize = '14px';
  5496. this.toggleFormatElement.className = (uiTheme != 'atlas') ? 'geButton' : '';
  5497. this.toggleFormatElement.style.width = '16px';
  5498. this.toggleFormatElement.style.height = '16px';
  5499. this.toggleFormatElement.style.backgroundPosition = '50% 50%';
  5500. this.toggleFormatElement.style.backgroundRepeat = 'no-repeat';
  5501. this.toolbarContainer.appendChild(this.toggleFormatElement);
  5502. if (uiTheme == 'dark')
  5503. {
  5504. this.toggleFormatElement.style.filter = 'invert(100%)';
  5505. }
  5506. // Prevents focus
  5507. mxEvent.addListener(this.toggleFormatElement, (mxClient.IS_POINTER) ? 'pointerdown' : 'mousedown',
  5508. mxUtils.bind(this, function(evt)
  5509. {
  5510. evt.preventDefault();
  5511. }));
  5512. mxEvent.addListener(this.toggleFormatElement, 'click', mxUtils.bind(this, function(evt)
  5513. {
  5514. this.actions.get('formatPanel').funct();
  5515. mxEvent.consume(evt);
  5516. }));
  5517. var toggleFormatPanel = mxUtils.bind(this, function()
  5518. {
  5519. if (this.formatWidth > 0)
  5520. {
  5521. this.toggleFormatElement.style.backgroundImage = 'url(\'' + this.formatShowImage + '\')';
  5522. }
  5523. else
  5524. {
  5525. this.toggleFormatElement.style.backgroundImage = 'url(\'' + this.formatHideImage + '\')';
  5526. }
  5527. });
  5528. this.addListener('formatWidthChanged', toggleFormatPanel);
  5529. toggleFormatPanel();
  5530. this.fullscreenElement = document.createElement('a');
  5531. this.fullscreenElement.setAttribute('title', mxResources.get('fullscreen'));
  5532. this.fullscreenElement.style.position = 'absolute';
  5533. this.fullscreenElement.style.display = 'inline-block';
  5534. this.fullscreenElement.style.top = (uiTheme == 'atlas') ? '8px' : '6px';
  5535. this.fullscreenElement.style.right = (uiTheme != 'atlas' && urlParams['embed'] != '1') ? '50px' : '30px';
  5536. this.fullscreenElement.style.padding = '2px';
  5537. this.fullscreenElement.style.fontSize = '14px';
  5538. this.fullscreenElement.className = (uiTheme != 'atlas') ? 'geButton' : '';
  5539. this.fullscreenElement.style.width = '16px';
  5540. this.fullscreenElement.style.height = '16px';
  5541. this.fullscreenElement.style.backgroundPosition = '50% 50%';
  5542. this.fullscreenElement.style.backgroundRepeat = 'no-repeat';
  5543. this.fullscreenElement.style.backgroundImage = 'url(\'' + this.fullscreenImage + '\')';
  5544. this.toolbarContainer.appendChild(this.fullscreenElement);
  5545. // Prevents focus
  5546. mxEvent.addListener(this.fullscreenElement, (mxClient.IS_POINTER) ? 'pointerdown' : 'mousedown',
  5547. mxUtils.bind(this, function(evt)
  5548. {
  5549. evt.preventDefault();
  5550. }));
  5551. // Some style changes in Atlas theme
  5552. if (uiTheme == 'atlas')
  5553. {
  5554. mxUtils.setOpacity(this.toggleFormatElement, 70);
  5555. mxUtils.setOpacity(this.fullscreenElement, 70);
  5556. }
  5557. var initialPosition = this.hsplitPosition;
  5558. if (uiTheme == 'dark')
  5559. {
  5560. this.fullscreenElement.style.filter = 'invert(100%)';
  5561. }
  5562. mxEvent.addListener(this.fullscreenElement, 'click', mxUtils.bind(this, function(evt)
  5563. {
  5564. var visible = this.fullscreenMode;
  5565. if (uiTheme != 'atlas' && urlParams['embed'] != '1')
  5566. {
  5567. this.toggleCompactMode(visible);
  5568. }
  5569. if (!visible)
  5570. {
  5571. initialPosition = this.hsplitPosition;
  5572. }
  5573. this.hsplitPosition = (visible) ? initialPosition : 0;
  5574. this.toggleFormatPanel(visible);
  5575. this.fullscreenMode = !visible;
  5576. mxEvent.consume(evt);
  5577. }));
  5578. /**
  5579. * Adds compact UI toggle.
  5580. */
  5581. if (urlParams['embed'] != '1')
  5582. {
  5583. this.toggleElement = document.createElement('a');
  5584. this.toggleElement.setAttribute('title', mxResources.get('collapseExpand'));
  5585. this.toggleElement.className = 'geButton';
  5586. this.toggleElement.style.position = 'absolute';
  5587. this.toggleElement.style.display = 'inline-block';
  5588. this.toggleElement.style.width = '16px';
  5589. this.toggleElement.style.height = '16px';
  5590. this.toggleElement.style.color = '#666';
  5591. this.toggleElement.style.top = (uiTheme == 'atlas') ? '8px' : '6px';
  5592. this.toggleElement.style.right = '10px';
  5593. this.toggleElement.style.padding = '2px';
  5594. this.toggleElement.style.fontSize = '14px';
  5595. this.toggleElement.style.textDecoration = 'none';
  5596. this.toggleElement.style.backgroundImage = 'url(\'' + this.chevronUpImage + '\')';
  5597. this.toggleElement.style.backgroundPosition = '50% 50%';
  5598. this.toggleElement.style.backgroundRepeat = 'no-repeat';
  5599. if (uiTheme == 'dark')
  5600. {
  5601. this.toggleElement.style.filter = 'invert(100%)';
  5602. }
  5603. // Prevents focus
  5604. mxEvent.addListener(this.toggleElement, (mxClient.IS_POINTER) ? 'pointerdown' : 'mousedown',
  5605. mxUtils.bind(this, function(evt)
  5606. {
  5607. evt.preventDefault();
  5608. }));
  5609. // Toggles compact mode
  5610. mxEvent.addListener(this.toggleElement, 'click', mxUtils.bind(this, function(evt)
  5611. {
  5612. this.toggleCompactMode();
  5613. mxEvent.consume(evt);
  5614. }));
  5615. if (uiTheme != 'atlas')
  5616. {
  5617. this.toolbarContainer.appendChild(this.toggleElement);
  5618. }
  5619. // Enable compact mode for small screens except for Firefox where the height is wrong
  5620. if (!mxClient.IS_FF && screen.height <= 740 && typeof this.toggleElement.click !== 'undefined')
  5621. {
  5622. window.setTimeout(mxUtils.bind(this, function()
  5623. {
  5624. this.toggleElement.click();
  5625. }), 0);
  5626. }
  5627. }
  5628. }
  5629. };
  5630. /**
  5631. * Adds the listener for automatically saving the diagram for local changes.
  5632. */
  5633. App.prototype.toggleCompactMode = function(visible)
  5634. {
  5635. visible = (visible != null) ? visible : this.compactMode;
  5636. if (visible)
  5637. {
  5638. this.menubar.container.style.position = 'absolute';
  5639. this.menubar.container.style.paddingLeft = '59px';
  5640. this.menubar.container.style.paddingTop = '';
  5641. this.menubar.container.style.paddingBottom = '';
  5642. this.menubar.container.style.top = '34px';
  5643. this.toolbar.container.style.paddingLeft = '16px';
  5644. this.buttonContainer.style.visibility = 'visible';
  5645. this.appIcon.style.display = 'block';
  5646. this.fnameWrapper.style.display = 'block';
  5647. this.fnameWrapper.style.visibility = 'visible';
  5648. this.menubarHeight = App.prototype.menubarHeight;
  5649. this.refresh();
  5650. this.toggleElement.style.backgroundImage = 'url(\'' + this.chevronUpImage + '\')';
  5651. }
  5652. else
  5653. {
  5654. this.menubar.container.style.position = 'relative';
  5655. this.menubar.container.style.paddingLeft = '4px';
  5656. this.menubar.container.style.paddingTop = '0px';
  5657. this.menubar.container.style.paddingBottom = '0px';
  5658. this.menubar.container.style.top = '0px';
  5659. this.toolbar.container.style.paddingLeft = '8px';
  5660. this.buttonContainer.style.visibility = 'hidden';
  5661. this.appIcon.style.display = 'none';
  5662. this.fnameWrapper.style.display = 'none';
  5663. this.fnameWrapper.style.visibility = 'hidden';
  5664. this.menubarHeight = EditorUi.prototype.menubarHeight;
  5665. this.refresh();
  5666. this.toggleElement.style.backgroundImage = 'url(\'' + this.chevronDownImage + '\')';
  5667. }
  5668. this.compactMode = !visible;
  5669. };
  5670. /**
  5671. * Adds the listener for automatically saving the diagram for local changes.
  5672. */
  5673. App.prototype.updateUserElement = function()
  5674. {
  5675. if ((this.drive == null || this.drive.getUser() == null) &&
  5676. (this.oneDrive == null || this.oneDrive.getUser() == null) &&
  5677. (this.dropbox == null || this.dropbox.getUser() == null) &&
  5678. (this.gitHub == null || this.gitHub.getUser() == null) &&
  5679. (this.gitLab == null || this.gitLab.getUser() == null) &&
  5680. (this.trello == null || !this.trello.isAuthorized())) //TODO Trello no user issue
  5681. {
  5682. if (this.userElement != null)
  5683. {
  5684. this.userElement.parentNode.removeChild(this.userElement);
  5685. this.userElement = null;
  5686. }
  5687. }
  5688. else
  5689. {
  5690. if (this.userElement == null)
  5691. {
  5692. this.userElement = document.createElement('a');
  5693. this.userElement.className = 'geItem';
  5694. this.userElement.style.position = 'absolute';
  5695. this.userElement.style.fontSize = '8pt';
  5696. this.userElement.style.top = (uiTheme == 'atlas') ? '8px' : '2px';
  5697. this.userElement.style.right = '30px';
  5698. this.userElement.style.margin = '4px';
  5699. this.userElement.style.padding = '2px';
  5700. this.userElement.style.paddingRight = '16px';
  5701. this.userElement.style.verticalAlign = 'middle';
  5702. this.userElement.style.backgroundImage = 'url(' + IMAGE_PATH + '/expanded.gif)';
  5703. this.userElement.style.backgroundPosition = '100% 60%';
  5704. this.userElement.style.backgroundRepeat = 'no-repeat';
  5705. this.menubarContainer.appendChild(this.userElement);
  5706. // Prevents focus
  5707. mxEvent.addListener(this.userElement, (mxClient.IS_POINTER) ? 'pointerdown' : 'mousedown',
  5708. mxUtils.bind(this, function(evt)
  5709. {
  5710. evt.preventDefault();
  5711. }));
  5712. mxEvent.addListener(this.userElement, 'click', mxUtils.bind(this, function(evt)
  5713. {
  5714. if (this.userPanel == null)
  5715. {
  5716. var div = document.createElement('div');
  5717. div.className = 'geDialog';
  5718. div.style.position = 'absolute';
  5719. div.style.top = (this.userElement.clientTop + this.userElement.clientHeight + 6) + 'px';
  5720. div.style.right = '36px';
  5721. div.style.padding = '0px';
  5722. div.style.cursor = 'default';
  5723. this.userPanel = div;
  5724. }
  5725. if (this.userPanel.parentNode != null)
  5726. {
  5727. this.userPanel.parentNode.removeChild(this.userPanel);
  5728. }
  5729. else
  5730. {
  5731. var connected = false;
  5732. this.userPanel.innerHTML = '';
  5733. var img = document.createElement('img');
  5734. img.setAttribute('src', Dialog.prototype.closeImage);
  5735. img.setAttribute('title', mxResources.get('close'));
  5736. img.className = 'geDialogClose';
  5737. img.style.top = '8px';
  5738. img.style.right = '8px';
  5739. mxEvent.addListener(img, 'click', mxUtils.bind(this, function()
  5740. {
  5741. if (this.userPanel.parentNode != null)
  5742. {
  5743. this.userPanel.parentNode.removeChild(this.userPanel);
  5744. }
  5745. }));
  5746. this.userPanel.appendChild(img);
  5747. if (this.drive != null)
  5748. {
  5749. var driveUsers = this.drive.getUsersList();
  5750. if (driveUsers.length > 0)
  5751. {
  5752. // LATER: Cannot change user while file is open since close will not work with new
  5753. // credentials and closing the file using fileLoaded(null) will show splash dialog.
  5754. var closeFile = mxUtils.bind(this, function(callback, spinnerMsg)
  5755. {
  5756. var file = this.getCurrentFile();
  5757. if (file != null && file.constructor == DriveFile)
  5758. {
  5759. this.spinner.spin(document.body, spinnerMsg);
  5760. // file.close();
  5761. this.fileLoaded(null);
  5762. // LATER: Use callback to wait for thumbnail update
  5763. window.setTimeout(mxUtils.bind(this, function()
  5764. {
  5765. this.spinner.stop();
  5766. callback();
  5767. }), 2000);
  5768. }
  5769. else
  5770. {
  5771. callback();
  5772. }
  5773. });
  5774. var createUserRow = mxUtils.bind(this, function (user)
  5775. {
  5776. var tr = document.createElement('tr');
  5777. tr.style.cssText = user.isCurrent? '' : 'background-color: whitesmoke; cursor: pointer';
  5778. tr.setAttribute('title', 'User ID: ' + user.id);
  5779. tr.innerHTML = '<td valign="middle" style="height: 59px;width: 66px;' +
  5780. (user.isCurrent? '' : 'border-top: 1px solid rgb(224, 224, 224);') + '">' +
  5781. '<img width="50" height="50" style="margin: 4px 8px 0 8px;border-radius:50%;" src="' +
  5782. ((user.pictureUrl != null) ? user.pictureUrl : this.defaultUserPicture) + '"/>' +
  5783. '</td><td valign="middle" style="white-space:nowrap;' +
  5784. ((user.pictureUrl != null) ? 'padding-top:4px;' : '') +
  5785. (user.isCurrent? '' : 'border-top: 1px solid rgb(224, 224, 224);') +
  5786. '">' + mxUtils.htmlEntities(user.displayName) + '<br>' +
  5787. '<small style="color:gray;">' + mxUtils.htmlEntities(user.email) +
  5788. '</small><div style="margin-top:4px;"><i>' +
  5789. mxResources.get('googleDrive') + '</i></div>';
  5790. if (!user.isCurrent)
  5791. {
  5792. mxEvent.addListener(tr, 'click', mxUtils.bind(this, function(evt)
  5793. {
  5794. closeFile(mxUtils.bind(this, function()
  5795. {
  5796. this.stateArg = null;
  5797. this.drive.setUser(user);
  5798. this.drive.authorize(true, mxUtils.bind(this, function()
  5799. {
  5800. this.setMode(App.MODE_GOOGLE);
  5801. this.hideDialog();
  5802. this.showSplash();
  5803. }), mxUtils.bind(this, function(resp)
  5804. {
  5805. this.handleError(resp);
  5806. }), true); //Remember is true since add account imply keeping that account
  5807. }), mxResources.get('closingFile') + '...');
  5808. mxEvent.consume(evt);
  5809. }));
  5810. }
  5811. return tr;
  5812. });
  5813. connected = true;
  5814. var driveUserTable = document.createElement('table');
  5815. driveUserTable.style.cssText ='font-size:10pt;padding: 20px 0 0 0;min-width: 300px;border-spacing: 0;';
  5816. for (var i = 0; i < driveUsers.length; i++)
  5817. {
  5818. driveUserTable.appendChild(createUserRow(driveUsers[i]));
  5819. }
  5820. this.userPanel.appendChild(driveUserTable);
  5821. var div = document.createElement('div');
  5822. div.style.textAlign = 'left';
  5823. div.style.padding = '8px';
  5824. div.style.whiteSpace = 'nowrap';
  5825. div.style.borderTop = '1px solid rgb(224, 224, 224)';
  5826. var btn = mxUtils.button(mxResources.get('signOut'), mxUtils.bind(this, function()
  5827. {
  5828. this.confirm(mxResources.get('areYouSure'), mxUtils.bind(this, function()
  5829. {
  5830. closeFile(mxUtils.bind(this, function()
  5831. {
  5832. this.stateArg = null;
  5833. this.drive.logout();
  5834. this.setMode(App.MODE_GOOGLE);
  5835. this.hideDialog();
  5836. this.showSplash();
  5837. }), mxResources.get('signOut'));
  5838. }));
  5839. }));
  5840. btn.className = 'geBtn';
  5841. btn.style.float = 'right';
  5842. div.appendChild(btn);
  5843. var btn = mxUtils.button(mxResources.get('addAccount'), mxUtils.bind(this, function()
  5844. {
  5845. var authWin = this.drive.createAuthWin();
  5846. //FIXME This doean't work to set focus back to main window until closing the file is done
  5847. authWin.blur();
  5848. window.focus();
  5849. closeFile(mxUtils.bind(this, function()
  5850. {
  5851. this.stateArg = null;
  5852. this.drive.authorize(false, mxUtils.bind(this, function()
  5853. {
  5854. this.setMode(App.MODE_GOOGLE);
  5855. this.hideDialog();
  5856. this.showSplash();
  5857. }), mxUtils.bind(this, function(resp)
  5858. {
  5859. this.handleError(resp);
  5860. }), true, authWin); //Remember is true since add account imply keeping that account
  5861. }), mxResources.get('closingFile') + '...');
  5862. }));
  5863. btn.className = 'geBtn';
  5864. btn.style.margin = '0px';
  5865. div.appendChild(btn);
  5866. this.userPanel.appendChild(div);
  5867. }
  5868. }
  5869. var addUser = mxUtils.bind(this, function(user, logo, logout, label)
  5870. {
  5871. if (user != null)
  5872. {
  5873. if (connected)
  5874. {
  5875. this.userPanel.appendChild(document.createElement('hr'));
  5876. }
  5877. connected = true;
  5878. var userTable = document.createElement('table');
  5879. userTable.style.cssText = 'font-size:10pt;padding:' + (connected? '10' : '20') + 'px 20px 10px 10px;';
  5880. userTable.innerHTML += '<tr><td valign="top">' +
  5881. ((logo != null) ? '<img style="margin-right:6px;" src="' + logo + '" width="40" height="40"/></td>' : '') +
  5882. '<td valign="middle" style="white-space:nowrap;">' + mxUtils.htmlEntities(user.displayName) +
  5883. ((user.email != null) ? '<br><small style="color:gray;">' + mxUtils.htmlEntities(user.email) + '</small>' : '') +
  5884. ((label != null) ? '<div style="margin-top:4px;"><i>' + mxUtils.htmlEntities(label) + '</i></div>' : '') +
  5885. '</td></tr>';
  5886. this.userPanel.appendChild(userTable);
  5887. var div = document.createElement('div');
  5888. div.style.textAlign = 'center';
  5889. div.style.paddingBottom = '12px';
  5890. div.style.whiteSpace = 'nowrap';
  5891. if (logout != null)
  5892. {
  5893. var btn = mxUtils.button(mxResources.get('signOut'), logout);
  5894. btn.className = 'geBtn';
  5895. div.appendChild(btn);
  5896. }
  5897. this.userPanel.appendChild(div);
  5898. }
  5899. });
  5900. if (this.dropbox != null)
  5901. {
  5902. addUser(this.dropbox.getUser(), IMAGE_PATH + '/dropbox-logo.svg', mxUtils.bind(this, function()
  5903. {
  5904. var file = this.getCurrentFile();
  5905. if (file != null && file.constructor == DropboxFile)
  5906. {
  5907. var doLogout = mxUtils.bind(this, function()
  5908. {
  5909. this.dropbox.logout();
  5910. window.location.hash = '';
  5911. });
  5912. if (!file.isModified())
  5913. {
  5914. doLogout();
  5915. }
  5916. else
  5917. {
  5918. this.confirm(mxResources.get('allChangesLost'), null, doLogout,
  5919. mxResources.get('cancel'), mxResources.get('discardChanges'));
  5920. }
  5921. }
  5922. else
  5923. {
  5924. this.dropbox.logout();
  5925. }
  5926. }), mxResources.get('dropbox'));
  5927. }
  5928. if (this.oneDrive != null)
  5929. {
  5930. addUser(this.oneDrive.getUser(), IMAGE_PATH + '/onedrive-logo.svg', mxUtils.bind(this, function()
  5931. {
  5932. var file = this.getCurrentFile();
  5933. if (file != null && file.constructor == OneDriveFile)
  5934. {
  5935. var doLogout = mxUtils.bind(this, function()
  5936. {
  5937. this.oneDrive.logout();
  5938. window.location.hash = '';
  5939. });
  5940. if (!file.isModified())
  5941. {
  5942. doLogout();
  5943. }
  5944. else
  5945. {
  5946. this.confirm(mxResources.get('allChangesLost'), null, doLogout,
  5947. mxResources.get('cancel'), mxResources.get('discardChanges'));
  5948. }
  5949. }
  5950. else
  5951. {
  5952. this.oneDrive.logout();
  5953. }
  5954. }), mxResources.get('oneDrive'));
  5955. }
  5956. if (this.gitHub != null)
  5957. {
  5958. addUser(this.gitHub.getUser(), IMAGE_PATH + '/github-logo.svg', mxUtils.bind(this, function()
  5959. {
  5960. var file = this.getCurrentFile();
  5961. if (file != null && file.constructor == GitHubFile)
  5962. {
  5963. var doLogout = mxUtils.bind(this, function()
  5964. {
  5965. this.gitHub.logout();
  5966. window.location.hash = '';
  5967. });
  5968. if (!file.isModified())
  5969. {
  5970. doLogout();
  5971. }
  5972. else
  5973. {
  5974. this.confirm(mxResources.get('allChangesLost'), null, doLogout,
  5975. mxResources.get('cancel'), mxResources.get('discardChanges'));
  5976. }
  5977. }
  5978. else
  5979. {
  5980. this.gitHub.logout();
  5981. }
  5982. }), mxResources.get('github'));
  5983. }
  5984. if (this.gitLab != null)
  5985. {
  5986. addUser(this.gitLab.getUser(), IMAGE_PATH + '/gitlab-logo.svg', mxUtils.bind(this, function()
  5987. {
  5988. var file = this.getCurrentFile();
  5989. if (file != null && file.constructor == GitLabFile)
  5990. {
  5991. var doLogout = mxUtils.bind(this, function()
  5992. {
  5993. this.gitLab.logout();
  5994. window.location.hash = '';
  5995. });
  5996. if (!file.isModified())
  5997. {
  5998. doLogout();
  5999. }
  6000. else
  6001. {
  6002. this.confirm(mxResources.get('allChangesLost'), null, doLogout,
  6003. mxResources.get('cancel'), mxResources.get('discardChanges'));
  6004. }
  6005. }
  6006. else
  6007. {
  6008. this.gitLab.logout();
  6009. }
  6010. }), mxResources.get('gitlab'));
  6011. }
  6012. //TODO We have no user info from Trello, how we can create a user?
  6013. if (this.trello != null)
  6014. {
  6015. addUser(this.trello.getUser(), IMAGE_PATH + '/trello-logo.svg', mxUtils.bind(this, function()
  6016. {
  6017. var file = this.getCurrentFile();
  6018. if (file != null && file.constructor == TrelloFile)
  6019. {
  6020. var doLogout = mxUtils.bind(this, function()
  6021. {
  6022. this.trello.logout();
  6023. window.location.hash = '';
  6024. });
  6025. if (!file.isModified())
  6026. {
  6027. doLogout();
  6028. }
  6029. else
  6030. {
  6031. this.confirm(mxResources.get('allChangesLost'), null, doLogout,
  6032. mxResources.get('cancel'), mxResources.get('discardChanges'));
  6033. }
  6034. }
  6035. else
  6036. {
  6037. this.trello.logout();
  6038. }
  6039. }), mxResources.get('trello'));
  6040. }
  6041. if (!connected)
  6042. {
  6043. var div = document.createElement('div');
  6044. div.style.textAlign = 'center';
  6045. div.style.padding = '20px 20px 10px 10px';
  6046. div.innerHTML = mxResources.get('notConnected');
  6047. this.userPanel.appendChild(div);
  6048. }
  6049. var div = document.createElement('div');
  6050. div.style.textAlign = 'center';
  6051. div.style.padding = '12px';
  6052. div.style.background = 'whiteSmoke';
  6053. div.style.borderTop = '1px solid #e0e0e0';
  6054. div.style.whiteSpace = 'nowrap';
  6055. var btn = mxUtils.button(mxResources.get('close'), mxUtils.bind(this, function()
  6056. {
  6057. if (!mxEvent.isConsumed(evt) && this.userPanel != null && this.userPanel.parentNode != null)
  6058. {
  6059. this.userPanel.parentNode.removeChild(this.userPanel);
  6060. }
  6061. }));
  6062. btn.className = 'geBtn';
  6063. div.appendChild(btn);
  6064. this.userPanel.appendChild(div);
  6065. document.body.appendChild(this.userPanel);
  6066. }
  6067. mxEvent.consume(evt);
  6068. }));
  6069. mxEvent.addListener(document.body, 'click', mxUtils.bind(this, function(evt)
  6070. {
  6071. if (!mxEvent.isConsumed(evt) && this.userPanel != null && this.userPanel.parentNode != null)
  6072. {
  6073. this.userPanel.parentNode.removeChild(this.userPanel);
  6074. }
  6075. }));
  6076. }
  6077. var user = null;
  6078. if (this.drive != null && this.drive.getUser() != null)
  6079. {
  6080. user = this.drive.getUser();
  6081. }
  6082. else if (this.oneDrive != null && this.oneDrive.getUser() != null)
  6083. {
  6084. user = this.oneDrive.getUser();
  6085. }
  6086. else if (this.dropbox != null && this.dropbox.getUser() != null)
  6087. {
  6088. user = this.dropbox.getUser();
  6089. }
  6090. else if (this.gitHub != null && this.gitHub.getUser() != null)
  6091. {
  6092. user = this.gitHub.getUser();
  6093. }
  6094. else if (this.gitLab != null && this.gitLab.getUser() != null)
  6095. {
  6096. user = this.gitLab.getUser();
  6097. }
  6098. //TODO Trello no user issue
  6099. if (user != null)
  6100. {
  6101. this.userElement.innerHTML = '';
  6102. if (screen.width > 560)
  6103. {
  6104. mxUtils.write(this.userElement, user.displayName);
  6105. this.userElement.style.display = 'block';
  6106. }
  6107. }
  6108. else
  6109. {
  6110. this.userElement.style.display = 'none';
  6111. }
  6112. }
  6113. };
  6114. //TODO Use this function to get the currently logged in user
  6115. App.prototype.getCurrentUser = function()
  6116. {
  6117. var user = null;
  6118. if (this.drive != null && this.drive.getUser() != null)
  6119. {
  6120. user = this.drive.getUser();
  6121. }
  6122. else if (this.oneDrive != null && this.oneDrive.getUser() != null)
  6123. {
  6124. user = this.oneDrive.getUser();
  6125. }
  6126. else if (this.dropbox != null && this.dropbox.getUser() != null)
  6127. {
  6128. user = this.dropbox.getUser();
  6129. }
  6130. else if (this.gitHub != null && this.gitHub.getUser() != null)
  6131. {
  6132. user = this.gitHub.getUser();
  6133. }
  6134. //TODO Trello no user issue
  6135. return user;
  6136. }
  6137. /**
  6138. * Override depends on mxSettings which is not defined in the minified viewer.
  6139. */
  6140. var editorResetGraph = Editor.prototype.resetGraph;
  6141. Editor.prototype.resetGraph = function()
  6142. {
  6143. editorResetGraph.apply(this, arguments);
  6144. // Overrides default with persisted value
  6145. this.graph.pageFormat = mxSettings.getPageFormat();
  6146. };