App.js 213 KB

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