App.js 208 KB

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