App.js 181 KB

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