EditorUi.js 543 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891289228932894289528962897289828992900290129022903290429052906290729082909291029112912291329142915291629172918291929202921292229232924292529262927292829292930293129322933293429352936293729382939294029412942294329442945294629472948294929502951295229532954295529562957295829592960296129622963296429652966296729682969297029712972297329742975297629772978297929802981298229832984298529862987298829892990299129922993299429952996299729982999300030013002300330043005300630073008300930103011301230133014301530163017301830193020302130223023302430253026302730283029303030313032303330343035303630373038303930403041304230433044304530463047304830493050305130523053305430553056305730583059306030613062306330643065306630673068306930703071307230733074307530763077307830793080308130823083308430853086308730883089309030913092309330943095309630973098309931003101310231033104310531063107310831093110311131123113311431153116311731183119312031213122312331243125312631273128312931303131313231333134313531363137313831393140314131423143314431453146314731483149315031513152315331543155315631573158315931603161316231633164316531663167316831693170317131723173317431753176317731783179318031813182318331843185318631873188318931903191319231933194319531963197319831993200320132023203320432053206320732083209321032113212321332143215321632173218321932203221322232233224322532263227322832293230323132323233323432353236323732383239324032413242324332443245324632473248324932503251325232533254325532563257325832593260326132623263326432653266326732683269327032713272327332743275327632773278327932803281328232833284328532863287328832893290329132923293329432953296329732983299330033013302330333043305330633073308330933103311331233133314331533163317331833193320332133223323332433253326332733283329333033313332333333343335333633373338333933403341334233433344334533463347334833493350335133523353335433553356335733583359336033613362336333643365336633673368336933703371337233733374337533763377337833793380338133823383338433853386338733883389339033913392339333943395339633973398339934003401340234033404340534063407340834093410341134123413341434153416341734183419342034213422342334243425342634273428342934303431343234333434343534363437343834393440344134423443344434453446344734483449345034513452345334543455345634573458345934603461346234633464346534663467346834693470347134723473347434753476347734783479348034813482348334843485348634873488348934903491349234933494349534963497349834993500350135023503350435053506350735083509351035113512351335143515351635173518351935203521352235233524352535263527352835293530353135323533353435353536353735383539354035413542354335443545354635473548354935503551355235533554355535563557355835593560356135623563356435653566356735683569357035713572357335743575357635773578357935803581358235833584358535863587358835893590359135923593359435953596359735983599360036013602360336043605360636073608360936103611361236133614361536163617361836193620362136223623362436253626362736283629363036313632363336343635363636373638363936403641364236433644364536463647364836493650365136523653365436553656365736583659366036613662366336643665366636673668366936703671367236733674367536763677367836793680368136823683368436853686368736883689369036913692369336943695369636973698369937003701370237033704370537063707370837093710371137123713371437153716371737183719372037213722372337243725372637273728372937303731373237333734373537363737373837393740374137423743374437453746374737483749375037513752375337543755375637573758375937603761376237633764376537663767376837693770377137723773377437753776377737783779378037813782378337843785378637873788378937903791379237933794379537963797379837993800380138023803380438053806380738083809381038113812381338143815381638173818381938203821382238233824382538263827382838293830383138323833383438353836383738383839384038413842384338443845384638473848384938503851385238533854385538563857385838593860386138623863386438653866386738683869387038713872387338743875387638773878387938803881388238833884388538863887388838893890389138923893389438953896389738983899390039013902390339043905390639073908390939103911391239133914391539163917391839193920392139223923392439253926392739283929393039313932393339343935393639373938393939403941394239433944394539463947394839493950395139523953395439553956395739583959396039613962396339643965396639673968396939703971397239733974397539763977397839793980398139823983398439853986398739883989399039913992399339943995399639973998399940004001400240034004400540064007400840094010401140124013401440154016401740184019402040214022402340244025402640274028402940304031403240334034403540364037403840394040404140424043404440454046404740484049405040514052405340544055405640574058405940604061406240634064406540664067406840694070407140724073407440754076407740784079408040814082408340844085408640874088408940904091409240934094409540964097409840994100410141024103410441054106410741084109411041114112411341144115411641174118411941204121412241234124412541264127412841294130413141324133413441354136413741384139414041414142414341444145414641474148414941504151415241534154415541564157415841594160416141624163416441654166416741684169417041714172417341744175417641774178417941804181418241834184418541864187418841894190419141924193419441954196419741984199420042014202420342044205420642074208420942104211421242134214421542164217421842194220422142224223422442254226422742284229423042314232423342344235423642374238423942404241424242434244424542464247424842494250425142524253425442554256425742584259426042614262426342644265426642674268426942704271427242734274427542764277427842794280428142824283428442854286428742884289429042914292429342944295429642974298429943004301430243034304430543064307430843094310431143124313431443154316431743184319432043214322432343244325432643274328432943304331433243334334433543364337433843394340434143424343434443454346434743484349435043514352435343544355435643574358435943604361436243634364436543664367436843694370437143724373437443754376437743784379438043814382438343844385438643874388438943904391439243934394439543964397439843994400440144024403440444054406440744084409441044114412441344144415441644174418441944204421442244234424442544264427442844294430443144324433443444354436443744384439444044414442444344444445444644474448444944504451445244534454445544564457445844594460446144624463446444654466446744684469447044714472447344744475447644774478447944804481448244834484448544864487448844894490449144924493449444954496449744984499450045014502450345044505450645074508450945104511451245134514451545164517451845194520452145224523452445254526452745284529453045314532453345344535453645374538453945404541454245434544454545464547454845494550455145524553455445554556455745584559456045614562456345644565456645674568456945704571457245734574457545764577457845794580458145824583458445854586458745884589459045914592459345944595459645974598459946004601460246034604460546064607460846094610461146124613461446154616461746184619462046214622462346244625462646274628462946304631463246334634463546364637463846394640464146424643464446454646464746484649465046514652465346544655465646574658465946604661466246634664466546664667466846694670467146724673467446754676467746784679468046814682468346844685468646874688468946904691469246934694469546964697469846994700470147024703470447054706470747084709471047114712471347144715471647174718471947204721472247234724472547264727472847294730473147324733473447354736473747384739474047414742474347444745474647474748474947504751475247534754475547564757475847594760476147624763476447654766476747684769477047714772477347744775477647774778477947804781478247834784478547864787478847894790479147924793479447954796479747984799480048014802480348044805480648074808480948104811481248134814481548164817481848194820482148224823482448254826482748284829483048314832483348344835483648374838483948404841484248434844484548464847484848494850485148524853485448554856485748584859486048614862486348644865486648674868486948704871487248734874487548764877487848794880488148824883488448854886488748884889489048914892489348944895489648974898489949004901490249034904490549064907490849094910491149124913491449154916491749184919492049214922492349244925492649274928492949304931493249334934493549364937493849394940494149424943494449454946494749484949495049514952495349544955495649574958495949604961496249634964496549664967496849694970497149724973497449754976497749784979498049814982498349844985498649874988498949904991499249934994499549964997499849995000500150025003500450055006500750085009501050115012501350145015501650175018501950205021502250235024502550265027502850295030503150325033503450355036503750385039504050415042504350445045504650475048504950505051505250535054505550565057505850595060506150625063506450655066506750685069507050715072507350745075507650775078507950805081508250835084508550865087508850895090509150925093509450955096509750985099510051015102510351045105510651075108510951105111511251135114511551165117511851195120512151225123512451255126512751285129513051315132513351345135513651375138513951405141514251435144514551465147514851495150515151525153515451555156515751585159516051615162516351645165516651675168516951705171517251735174517551765177517851795180518151825183518451855186518751885189519051915192519351945195519651975198519952005201520252035204520552065207520852095210521152125213521452155216521752185219522052215222522352245225522652275228522952305231523252335234523552365237523852395240524152425243524452455246524752485249525052515252525352545255525652575258525952605261526252635264526552665267526852695270527152725273527452755276527752785279528052815282528352845285528652875288528952905291529252935294529552965297529852995300530153025303530453055306530753085309531053115312531353145315531653175318531953205321532253235324532553265327532853295330533153325333533453355336533753385339534053415342534353445345534653475348534953505351535253535354535553565357535853595360536153625363536453655366536753685369537053715372537353745375537653775378537953805381538253835384538553865387538853895390539153925393539453955396539753985399540054015402540354045405540654075408540954105411541254135414541554165417541854195420542154225423542454255426542754285429543054315432543354345435543654375438543954405441544254435444544554465447544854495450545154525453545454555456545754585459546054615462546354645465546654675468546954705471547254735474547554765477547854795480548154825483548454855486548754885489549054915492549354945495549654975498549955005501550255035504550555065507550855095510551155125513551455155516551755185519552055215522552355245525552655275528552955305531553255335534553555365537553855395540554155425543554455455546554755485549555055515552555355545555555655575558555955605561556255635564556555665567556855695570557155725573557455755576557755785579558055815582558355845585558655875588558955905591559255935594559555965597559855995600560156025603560456055606560756085609561056115612561356145615561656175618561956205621562256235624562556265627562856295630563156325633563456355636563756385639564056415642564356445645564656475648564956505651565256535654565556565657565856595660566156625663566456655666566756685669567056715672567356745675567656775678567956805681568256835684568556865687568856895690569156925693569456955696569756985699570057015702570357045705570657075708570957105711571257135714571557165717571857195720572157225723572457255726572757285729573057315732573357345735573657375738573957405741574257435744574557465747574857495750575157525753575457555756575757585759576057615762576357645765576657675768576957705771577257735774577557765777577857795780578157825783578457855786578757885789579057915792579357945795579657975798579958005801580258035804580558065807580858095810581158125813581458155816581758185819582058215822582358245825582658275828582958305831583258335834583558365837583858395840584158425843584458455846584758485849585058515852585358545855585658575858585958605861586258635864586558665867586858695870587158725873587458755876587758785879588058815882588358845885588658875888588958905891589258935894589558965897589858995900590159025903590459055906590759085909591059115912591359145915591659175918591959205921592259235924592559265927592859295930593159325933593459355936593759385939594059415942594359445945594659475948594959505951595259535954595559565957595859595960596159625963596459655966596759685969597059715972597359745975597659775978597959805981598259835984598559865987598859895990599159925993599459955996599759985999600060016002600360046005600660076008600960106011601260136014601560166017601860196020602160226023602460256026602760286029603060316032603360346035603660376038603960406041604260436044604560466047604860496050605160526053605460556056605760586059606060616062606360646065606660676068606960706071607260736074607560766077607860796080608160826083608460856086608760886089609060916092609360946095609660976098609961006101610261036104610561066107610861096110611161126113611461156116611761186119612061216122612361246125612661276128612961306131613261336134613561366137613861396140614161426143614461456146614761486149615061516152615361546155615661576158615961606161616261636164616561666167616861696170617161726173617461756176617761786179618061816182618361846185618661876188618961906191619261936194619561966197619861996200620162026203620462056206620762086209621062116212621362146215621662176218621962206221622262236224622562266227622862296230623162326233623462356236623762386239624062416242624362446245624662476248624962506251625262536254625562566257625862596260626162626263626462656266626762686269627062716272627362746275627662776278627962806281628262836284628562866287628862896290629162926293629462956296629762986299630063016302630363046305630663076308630963106311631263136314631563166317631863196320632163226323632463256326632763286329633063316332633363346335633663376338633963406341634263436344634563466347634863496350635163526353635463556356635763586359636063616362636363646365636663676368636963706371637263736374637563766377637863796380638163826383638463856386638763886389639063916392639363946395639663976398639964006401640264036404640564066407640864096410641164126413641464156416641764186419642064216422642364246425642664276428642964306431643264336434643564366437643864396440644164426443644464456446644764486449645064516452645364546455645664576458645964606461646264636464646564666467646864696470647164726473647464756476647764786479648064816482648364846485648664876488648964906491649264936494649564966497649864996500650165026503650465056506650765086509651065116512651365146515651665176518651965206521652265236524652565266527652865296530653165326533653465356536653765386539654065416542654365446545654665476548654965506551655265536554655565566557655865596560656165626563656465656566656765686569657065716572657365746575657665776578657965806581658265836584658565866587658865896590659165926593659465956596659765986599660066016602660366046605660666076608660966106611661266136614661566166617661866196620662166226623662466256626662766286629663066316632663366346635663666376638663966406641664266436644664566466647664866496650665166526653665466556656665766586659666066616662666366646665666666676668666966706671667266736674667566766677667866796680668166826683668466856686668766886689669066916692669366946695669666976698669967006701670267036704670567066707670867096710671167126713671467156716671767186719672067216722672367246725672667276728672967306731673267336734673567366737673867396740674167426743674467456746674767486749675067516752675367546755675667576758675967606761676267636764676567666767676867696770677167726773677467756776677767786779678067816782678367846785678667876788678967906791679267936794679567966797679867996800680168026803680468056806680768086809681068116812681368146815681668176818681968206821682268236824682568266827682868296830683168326833683468356836683768386839684068416842684368446845684668476848684968506851685268536854685568566857685868596860686168626863686468656866686768686869687068716872687368746875687668776878687968806881688268836884688568866887688868896890689168926893689468956896689768986899690069016902690369046905690669076908690969106911691269136914691569166917691869196920692169226923692469256926692769286929693069316932693369346935693669376938693969406941694269436944694569466947694869496950695169526953695469556956695769586959696069616962696369646965696669676968696969706971697269736974697569766977697869796980698169826983698469856986698769886989699069916992699369946995699669976998699970007001700270037004700570067007700870097010701170127013701470157016701770187019702070217022702370247025702670277028702970307031703270337034703570367037703870397040704170427043704470457046704770487049705070517052705370547055705670577058705970607061706270637064706570667067706870697070707170727073707470757076707770787079708070817082708370847085708670877088708970907091709270937094709570967097709870997100710171027103710471057106710771087109711071117112711371147115711671177118711971207121712271237124712571267127712871297130713171327133713471357136713771387139714071417142714371447145714671477148714971507151715271537154715571567157715871597160716171627163716471657166716771687169717071717172717371747175717671777178717971807181718271837184718571867187718871897190719171927193719471957196719771987199720072017202720372047205720672077208720972107211721272137214721572167217721872197220722172227223722472257226722772287229723072317232723372347235723672377238723972407241724272437244724572467247724872497250725172527253725472557256725772587259726072617262726372647265726672677268726972707271727272737274727572767277727872797280728172827283728472857286728772887289729072917292729372947295729672977298729973007301730273037304730573067307730873097310731173127313731473157316731773187319732073217322732373247325732673277328732973307331733273337334733573367337733873397340734173427343734473457346734773487349735073517352735373547355735673577358735973607361736273637364736573667367736873697370737173727373737473757376737773787379738073817382738373847385738673877388738973907391739273937394739573967397739873997400740174027403740474057406740774087409741074117412741374147415741674177418741974207421742274237424742574267427742874297430743174327433743474357436743774387439744074417442744374447445744674477448744974507451745274537454745574567457745874597460746174627463746474657466746774687469747074717472747374747475747674777478747974807481748274837484748574867487748874897490749174927493749474957496749774987499750075017502750375047505750675077508750975107511751275137514751575167517751875197520752175227523752475257526752775287529753075317532753375347535753675377538753975407541754275437544754575467547754875497550755175527553755475557556755775587559756075617562756375647565756675677568756975707571757275737574757575767577757875797580758175827583758475857586758775887589759075917592759375947595759675977598759976007601760276037604760576067607760876097610761176127613761476157616761776187619762076217622762376247625762676277628762976307631763276337634763576367637763876397640764176427643764476457646764776487649765076517652765376547655765676577658765976607661766276637664766576667667766876697670767176727673767476757676767776787679768076817682768376847685768676877688768976907691769276937694769576967697769876997700770177027703770477057706770777087709771077117712771377147715771677177718771977207721772277237724772577267727772877297730773177327733773477357736773777387739774077417742774377447745774677477748774977507751775277537754775577567757775877597760776177627763776477657766776777687769777077717772777377747775777677777778777977807781778277837784778577867787778877897790779177927793779477957796779777987799780078017802780378047805780678077808780978107811781278137814781578167817781878197820782178227823782478257826782778287829783078317832783378347835783678377838783978407841784278437844784578467847784878497850785178527853785478557856785778587859786078617862786378647865786678677868786978707871787278737874787578767877787878797880788178827883788478857886788778887889789078917892789378947895789678977898789979007901790279037904790579067907790879097910791179127913791479157916791779187919792079217922792379247925792679277928792979307931793279337934793579367937793879397940794179427943794479457946794779487949795079517952795379547955795679577958795979607961796279637964796579667967796879697970797179727973797479757976797779787979798079817982798379847985798679877988798979907991799279937994799579967997799879998000800180028003800480058006800780088009801080118012801380148015801680178018801980208021802280238024802580268027802880298030803180328033803480358036803780388039804080418042804380448045804680478048804980508051805280538054805580568057805880598060806180628063806480658066806780688069807080718072807380748075807680778078807980808081808280838084808580868087808880898090809180928093809480958096809780988099810081018102810381048105810681078108810981108111811281138114811581168117811881198120812181228123812481258126812781288129813081318132813381348135813681378138813981408141814281438144814581468147814881498150815181528153815481558156815781588159816081618162816381648165816681678168816981708171817281738174817581768177817881798180818181828183818481858186818781888189819081918192819381948195819681978198819982008201820282038204820582068207820882098210821182128213821482158216821782188219822082218222822382248225822682278228822982308231823282338234823582368237823882398240824182428243824482458246824782488249825082518252825382548255825682578258825982608261826282638264826582668267826882698270827182728273827482758276827782788279828082818282828382848285828682878288828982908291829282938294829582968297829882998300830183028303830483058306830783088309831083118312831383148315831683178318831983208321832283238324832583268327832883298330833183328333833483358336833783388339834083418342834383448345834683478348834983508351835283538354835583568357835883598360836183628363836483658366836783688369837083718372837383748375837683778378837983808381838283838384838583868387838883898390839183928393839483958396839783988399840084018402840384048405840684078408840984108411841284138414841584168417841884198420842184228423842484258426842784288429843084318432843384348435843684378438843984408441844284438444844584468447844884498450845184528453845484558456845784588459846084618462846384648465846684678468846984708471847284738474847584768477847884798480848184828483848484858486848784888489849084918492849384948495849684978498849985008501850285038504850585068507850885098510851185128513851485158516851785188519852085218522852385248525852685278528852985308531853285338534853585368537853885398540854185428543854485458546854785488549855085518552855385548555855685578558855985608561856285638564856585668567856885698570857185728573857485758576857785788579858085818582858385848585858685878588858985908591859285938594859585968597859885998600860186028603860486058606860786088609861086118612861386148615861686178618861986208621862286238624862586268627862886298630863186328633863486358636863786388639864086418642864386448645864686478648864986508651865286538654865586568657865886598660866186628663866486658666866786688669867086718672867386748675867686778678867986808681868286838684868586868687868886898690869186928693869486958696869786988699870087018702870387048705870687078708870987108711871287138714871587168717871887198720872187228723872487258726872787288729873087318732873387348735873687378738873987408741874287438744874587468747874887498750875187528753875487558756875787588759876087618762876387648765876687678768876987708771877287738774877587768777877887798780878187828783878487858786878787888789879087918792879387948795879687978798879988008801880288038804880588068807880888098810881188128813881488158816881788188819882088218822882388248825882688278828882988308831883288338834883588368837883888398840884188428843884488458846884788488849885088518852885388548855885688578858885988608861886288638864886588668867886888698870887188728873887488758876887788788879888088818882888388848885888688878888888988908891889288938894889588968897889888998900890189028903890489058906890789088909891089118912891389148915891689178918891989208921892289238924892589268927892889298930893189328933893489358936893789388939894089418942894389448945894689478948894989508951895289538954895589568957895889598960896189628963896489658966896789688969897089718972897389748975897689778978897989808981898289838984898589868987898889898990899189928993899489958996899789988999900090019002900390049005900690079008900990109011901290139014901590169017901890199020902190229023902490259026902790289029903090319032903390349035903690379038903990409041904290439044904590469047904890499050905190529053905490559056905790589059906090619062906390649065906690679068906990709071907290739074907590769077907890799080908190829083908490859086908790889089909090919092909390949095909690979098909991009101910291039104910591069107910891099110911191129113911491159116911791189119912091219122912391249125912691279128912991309131913291339134913591369137913891399140914191429143914491459146914791489149915091519152915391549155915691579158915991609161916291639164916591669167916891699170917191729173917491759176917791789179918091819182918391849185918691879188918991909191919291939194919591969197919891999200920192029203920492059206920792089209921092119212921392149215921692179218921992209221922292239224922592269227922892299230923192329233923492359236923792389239924092419242924392449245924692479248924992509251925292539254925592569257925892599260926192629263926492659266926792689269927092719272927392749275927692779278927992809281928292839284928592869287928892899290929192929293929492959296929792989299930093019302930393049305930693079308930993109311931293139314931593169317931893199320932193229323932493259326932793289329933093319332933393349335933693379338933993409341934293439344934593469347934893499350935193529353935493559356935793589359936093619362936393649365936693679368936993709371937293739374937593769377937893799380938193829383938493859386938793889389939093919392939393949395939693979398939994009401940294039404940594069407940894099410941194129413941494159416941794189419942094219422942394249425942694279428942994309431943294339434943594369437943894399440944194429443944494459446944794489449945094519452945394549455945694579458945994609461946294639464946594669467946894699470947194729473947494759476947794789479948094819482948394849485948694879488948994909491949294939494949594969497949894999500950195029503950495059506950795089509951095119512951395149515951695179518951995209521952295239524952595269527952895299530953195329533953495359536953795389539954095419542954395449545954695479548954995509551955295539554955595569557955895599560956195629563956495659566956795689569957095719572957395749575957695779578957995809581958295839584958595869587958895899590959195929593959495959596959795989599960096019602960396049605960696079608960996109611961296139614961596169617961896199620962196229623962496259626962796289629963096319632963396349635963696379638963996409641964296439644964596469647964896499650965196529653965496559656965796589659966096619662966396649665966696679668966996709671967296739674967596769677967896799680968196829683968496859686968796889689969096919692969396949695969696979698969997009701970297039704970597069707970897099710971197129713971497159716971797189719972097219722972397249725972697279728972997309731973297339734973597369737973897399740974197429743974497459746974797489749975097519752975397549755975697579758975997609761976297639764976597669767976897699770977197729773977497759776977797789779978097819782978397849785978697879788978997909791979297939794979597969797979897999800980198029803980498059806980798089809981098119812981398149815981698179818981998209821982298239824982598269827982898299830983198329833983498359836983798389839984098419842984398449845984698479848984998509851985298539854985598569857985898599860986198629863986498659866986798689869987098719872987398749875987698779878987998809881988298839884988598869887988898899890989198929893989498959896989798989899990099019902990399049905990699079908990999109911991299139914991599169917991899199920992199229923992499259926992799289929993099319932993399349935993699379938993999409941994299439944994599469947994899499950995199529953995499559956995799589959996099619962996399649965996699679968996999709971997299739974997599769977997899799980998199829983998499859986998799889989999099919992999399949995999699979998999910000100011000210003100041000510006100071000810009100101001110012100131001410015100161001710018100191002010021100221002310024100251002610027100281002910030100311003210033100341003510036100371003810039100401004110042100431004410045100461004710048100491005010051100521005310054100551005610057100581005910060100611006210063100641006510066100671006810069100701007110072100731007410075100761007710078100791008010081100821008310084100851008610087100881008910090100911009210093100941009510096100971009810099101001010110102101031010410105101061010710108101091011010111101121011310114101151011610117101181011910120101211012210123101241012510126101271012810129101301013110132101331013410135101361013710138101391014010141101421014310144101451014610147101481014910150101511015210153101541015510156101571015810159101601016110162101631016410165101661016710168101691017010171101721017310174101751017610177101781017910180101811018210183101841018510186101871018810189101901019110192101931019410195101961019710198101991020010201102021020310204102051020610207102081020910210102111021210213102141021510216102171021810219102201022110222102231022410225102261022710228102291023010231102321023310234102351023610237102381023910240102411024210243102441024510246102471024810249102501025110252102531025410255102561025710258102591026010261102621026310264102651026610267102681026910270102711027210273102741027510276102771027810279102801028110282102831028410285102861028710288102891029010291102921029310294102951029610297102981029910300103011030210303103041030510306103071030810309103101031110312103131031410315103161031710318103191032010321103221032310324103251032610327103281032910330103311033210333103341033510336103371033810339103401034110342103431034410345103461034710348103491035010351103521035310354103551035610357103581035910360103611036210363103641036510366103671036810369103701037110372103731037410375103761037710378103791038010381103821038310384103851038610387103881038910390103911039210393103941039510396103971039810399104001040110402104031040410405104061040710408104091041010411104121041310414104151041610417104181041910420104211042210423104241042510426104271042810429104301043110432104331043410435104361043710438104391044010441104421044310444104451044610447104481044910450104511045210453104541045510456104571045810459104601046110462104631046410465104661046710468104691047010471104721047310474104751047610477104781047910480104811048210483104841048510486104871048810489104901049110492104931049410495104961049710498104991050010501105021050310504105051050610507105081050910510105111051210513105141051510516105171051810519105201052110522105231052410525105261052710528105291053010531105321053310534105351053610537105381053910540105411054210543105441054510546105471054810549105501055110552105531055410555105561055710558105591056010561105621056310564105651056610567105681056910570105711057210573105741057510576105771057810579105801058110582105831058410585105861058710588105891059010591105921059310594105951059610597105981059910600106011060210603106041060510606106071060810609106101061110612106131061410615106161061710618106191062010621106221062310624106251062610627106281062910630106311063210633106341063510636106371063810639106401064110642106431064410645106461064710648106491065010651106521065310654106551065610657106581065910660106611066210663106641066510666106671066810669106701067110672106731067410675106761067710678106791068010681106821068310684106851068610687106881068910690106911069210693106941069510696106971069810699107001070110702107031070410705107061070710708107091071010711107121071310714107151071610717107181071910720107211072210723107241072510726107271072810729107301073110732107331073410735107361073710738107391074010741107421074310744107451074610747107481074910750107511075210753107541075510756107571075810759107601076110762107631076410765107661076710768107691077010771107721077310774107751077610777107781077910780107811078210783107841078510786107871078810789107901079110792107931079410795107961079710798107991080010801108021080310804108051080610807108081080910810108111081210813108141081510816108171081810819108201082110822108231082410825108261082710828108291083010831108321083310834108351083610837108381083910840108411084210843108441084510846108471084810849108501085110852108531085410855108561085710858108591086010861108621086310864108651086610867108681086910870108711087210873108741087510876108771087810879108801088110882108831088410885108861088710888108891089010891108921089310894108951089610897108981089910900109011090210903109041090510906109071090810909109101091110912109131091410915109161091710918109191092010921109221092310924109251092610927109281092910930109311093210933109341093510936109371093810939109401094110942109431094410945109461094710948109491095010951109521095310954109551095610957109581095910960109611096210963109641096510966109671096810969109701097110972109731097410975109761097710978109791098010981109821098310984109851098610987109881098910990109911099210993109941099510996109971099810999110001100111002110031100411005110061100711008110091101011011110121101311014110151101611017110181101911020110211102211023110241102511026110271102811029110301103111032110331103411035110361103711038110391104011041110421104311044110451104611047110481104911050110511105211053110541105511056110571105811059110601106111062110631106411065110661106711068110691107011071110721107311074110751107611077110781107911080110811108211083110841108511086110871108811089110901109111092110931109411095110961109711098110991110011101111021110311104111051110611107111081110911110111111111211113111141111511116111171111811119111201112111122111231112411125111261112711128111291113011131111321113311134111351113611137111381113911140111411114211143111441114511146111471114811149111501115111152111531115411155111561115711158111591116011161111621116311164111651116611167111681116911170111711117211173111741117511176111771117811179111801118111182111831118411185111861118711188111891119011191111921119311194111951119611197111981119911200112011120211203112041120511206112071120811209112101121111212112131121411215112161121711218112191122011221112221122311224112251122611227112281122911230112311123211233112341123511236112371123811239112401124111242112431124411245112461124711248112491125011251112521125311254112551125611257112581125911260112611126211263112641126511266112671126811269112701127111272112731127411275112761127711278112791128011281112821128311284112851128611287112881128911290112911129211293112941129511296112971129811299113001130111302113031130411305113061130711308113091131011311113121131311314113151131611317113181131911320113211132211323113241132511326113271132811329113301133111332113331133411335113361133711338113391134011341113421134311344113451134611347113481134911350113511135211353113541135511356113571135811359113601136111362113631136411365113661136711368113691137011371113721137311374113751137611377113781137911380113811138211383113841138511386113871138811389113901139111392113931139411395113961139711398113991140011401114021140311404114051140611407114081140911410114111141211413114141141511416114171141811419114201142111422114231142411425114261142711428114291143011431114321143311434114351143611437114381143911440114411144211443114441144511446114471144811449114501145111452114531145411455114561145711458114591146011461114621146311464114651146611467114681146911470114711147211473114741147511476114771147811479114801148111482114831148411485114861148711488114891149011491114921149311494114951149611497114981149911500115011150211503115041150511506115071150811509115101151111512115131151411515115161151711518115191152011521115221152311524115251152611527115281152911530115311153211533115341153511536115371153811539115401154111542115431154411545115461154711548115491155011551115521155311554115551155611557115581155911560115611156211563115641156511566115671156811569115701157111572115731157411575115761157711578115791158011581115821158311584115851158611587115881158911590115911159211593115941159511596115971159811599116001160111602116031160411605116061160711608116091161011611116121161311614116151161611617116181161911620116211162211623116241162511626116271162811629116301163111632116331163411635116361163711638116391164011641116421164311644116451164611647116481164911650116511165211653116541165511656116571165811659116601166111662116631166411665116661166711668116691167011671116721167311674116751167611677116781167911680116811168211683116841168511686116871168811689116901169111692116931169411695116961169711698116991170011701117021170311704117051170611707117081170911710117111171211713117141171511716117171171811719117201172111722117231172411725117261172711728117291173011731117321173311734117351173611737117381173911740117411174211743117441174511746117471174811749117501175111752117531175411755117561175711758117591176011761117621176311764117651176611767117681176911770117711177211773117741177511776117771177811779117801178111782117831178411785117861178711788117891179011791117921179311794117951179611797117981179911800118011180211803118041180511806118071180811809118101181111812118131181411815118161181711818118191182011821118221182311824118251182611827118281182911830118311183211833118341183511836118371183811839118401184111842118431184411845118461184711848118491185011851118521185311854118551185611857118581185911860118611186211863118641186511866118671186811869118701187111872118731187411875118761187711878118791188011881118821188311884118851188611887118881188911890118911189211893118941189511896118971189811899119001190111902119031190411905119061190711908119091191011911119121191311914119151191611917119181191911920119211192211923119241192511926119271192811929119301193111932119331193411935119361193711938119391194011941119421194311944119451194611947119481194911950119511195211953119541195511956119571195811959119601196111962119631196411965119661196711968119691197011971119721197311974119751197611977119781197911980119811198211983119841198511986119871198811989119901199111992119931199411995119961199711998119991200012001120021200312004120051200612007120081200912010120111201212013120141201512016120171201812019120201202112022120231202412025120261202712028120291203012031120321203312034120351203612037120381203912040120411204212043120441204512046120471204812049120501205112052120531205412055120561205712058120591206012061120621206312064120651206612067120681206912070120711207212073120741207512076120771207812079120801208112082120831208412085120861208712088120891209012091120921209312094120951209612097120981209912100121011210212103121041210512106121071210812109121101211112112121131211412115121161211712118121191212012121121221212312124121251212612127121281212912130121311213212133121341213512136121371213812139121401214112142121431214412145121461214712148121491215012151121521215312154121551215612157121581215912160121611216212163121641216512166121671216812169121701217112172121731217412175121761217712178121791218012181121821218312184121851218612187121881218912190121911219212193121941219512196121971219812199122001220112202122031220412205122061220712208122091221012211122121221312214122151221612217122181221912220122211222212223122241222512226122271222812229122301223112232122331223412235122361223712238122391224012241122421224312244122451224612247122481224912250122511225212253122541225512256122571225812259122601226112262122631226412265122661226712268122691227012271122721227312274122751227612277122781227912280122811228212283122841228512286122871228812289122901229112292122931229412295122961229712298122991230012301123021230312304123051230612307123081230912310123111231212313123141231512316123171231812319123201232112322123231232412325123261232712328123291233012331123321233312334123351233612337123381233912340123411234212343123441234512346123471234812349123501235112352123531235412355123561235712358123591236012361123621236312364123651236612367123681236912370123711237212373123741237512376123771237812379123801238112382123831238412385123861238712388123891239012391123921239312394123951239612397123981239912400124011240212403124041240512406124071240812409124101241112412124131241412415124161241712418124191242012421124221242312424124251242612427124281242912430124311243212433124341243512436124371243812439124401244112442124431244412445124461244712448124491245012451124521245312454124551245612457124581245912460124611246212463124641246512466124671246812469124701247112472124731247412475124761247712478124791248012481124821248312484124851248612487124881248912490124911249212493124941249512496124971249812499125001250112502125031250412505125061250712508125091251012511125121251312514125151251612517125181251912520125211252212523125241252512526125271252812529125301253112532125331253412535125361253712538125391254012541125421254312544125451254612547125481254912550125511255212553125541255512556125571255812559125601256112562125631256412565125661256712568125691257012571125721257312574125751257612577125781257912580125811258212583125841258512586125871258812589125901259112592125931259412595125961259712598125991260012601126021260312604126051260612607126081260912610126111261212613126141261512616126171261812619126201262112622126231262412625126261262712628126291263012631126321263312634126351263612637126381263912640126411264212643126441264512646126471264812649126501265112652126531265412655126561265712658126591266012661126621266312664126651266612667126681266912670126711267212673126741267512676126771267812679126801268112682126831268412685126861268712688126891269012691126921269312694126951269612697126981269912700127011270212703127041270512706127071270812709127101271112712127131271412715127161271712718127191272012721127221272312724127251272612727127281272912730127311273212733127341273512736127371273812739127401274112742127431274412745127461274712748127491275012751127521275312754127551275612757127581275912760127611276212763127641276512766127671276812769127701277112772127731277412775127761277712778127791278012781127821278312784127851278612787127881278912790127911279212793127941279512796127971279812799128001280112802128031280412805128061280712808128091281012811128121281312814128151281612817128181281912820128211282212823128241282512826128271282812829128301283112832128331283412835128361283712838128391284012841128421284312844128451284612847128481284912850128511285212853128541285512856128571285812859128601286112862128631286412865128661286712868128691287012871128721287312874128751287612877128781287912880128811288212883128841288512886128871288812889128901289112892128931289412895128961289712898128991290012901129021290312904129051290612907129081290912910129111291212913129141291512916129171291812919129201292112922129231292412925129261292712928129291293012931129321293312934129351293612937129381293912940129411294212943129441294512946129471294812949129501295112952129531295412955129561295712958129591296012961129621296312964129651296612967129681296912970129711297212973129741297512976129771297812979129801298112982129831298412985129861298712988129891299012991129921299312994129951299612997129981299913000130011300213003130041300513006130071300813009130101301113012130131301413015130161301713018130191302013021130221302313024130251302613027130281302913030130311303213033130341303513036130371303813039130401304113042130431304413045130461304713048130491305013051130521305313054130551305613057130581305913060130611306213063130641306513066130671306813069130701307113072130731307413075130761307713078130791308013081130821308313084130851308613087130881308913090130911309213093130941309513096130971309813099131001310113102131031310413105131061310713108131091311013111131121311313114131151311613117131181311913120131211312213123131241312513126131271312813129131301313113132131331313413135131361313713138131391314013141131421314313144131451314613147131481314913150131511315213153131541315513156131571315813159131601316113162131631316413165131661316713168131691317013171131721317313174131751317613177131781317913180131811318213183131841318513186131871318813189131901319113192131931319413195131961319713198131991320013201132021320313204132051320613207132081320913210132111321213213132141321513216132171321813219132201322113222132231322413225132261322713228132291323013231132321323313234132351323613237132381323913240132411324213243132441324513246132471324813249132501325113252132531325413255132561325713258132591326013261132621326313264132651326613267132681326913270132711327213273132741327513276132771327813279132801328113282132831328413285132861328713288132891329013291132921329313294132951329613297132981329913300133011330213303133041330513306133071330813309133101331113312133131331413315133161331713318133191332013321133221332313324133251332613327133281332913330133311333213333133341333513336133371333813339133401334113342133431334413345133461334713348133491335013351133521335313354133551335613357133581335913360133611336213363133641336513366133671336813369133701337113372133731337413375133761337713378133791338013381133821338313384133851338613387133881338913390133911339213393133941339513396133971339813399134001340113402134031340413405134061340713408134091341013411134121341313414134151341613417134181341913420134211342213423134241342513426134271342813429134301343113432134331343413435134361343713438134391344013441134421344313444134451344613447134481344913450134511345213453134541345513456134571345813459134601346113462134631346413465134661346713468134691347013471134721347313474134751347613477134781347913480134811348213483134841348513486134871348813489134901349113492134931349413495134961349713498134991350013501135021350313504135051350613507135081350913510135111351213513135141351513516135171351813519135201352113522135231352413525135261352713528135291353013531135321353313534135351353613537135381353913540135411354213543135441354513546135471354813549135501355113552135531355413555135561355713558135591356013561135621356313564135651356613567135681356913570135711357213573135741357513576135771357813579135801358113582135831358413585135861358713588135891359013591135921359313594135951359613597135981359913600136011360213603136041360513606136071360813609136101361113612136131361413615136161361713618136191362013621136221362313624136251362613627136281362913630136311363213633136341363513636136371363813639136401364113642136431364413645136461364713648136491365013651136521365313654136551365613657136581365913660136611366213663136641366513666136671366813669136701367113672136731367413675136761367713678136791368013681136821368313684136851368613687136881368913690136911369213693136941369513696136971369813699137001370113702137031370413705137061370713708137091371013711137121371313714137151371613717137181371913720137211372213723137241372513726137271372813729137301373113732137331373413735137361373713738137391374013741137421374313744137451374613747137481374913750137511375213753137541375513756137571375813759137601376113762137631376413765137661376713768137691377013771137721377313774137751377613777137781377913780137811378213783137841378513786137871378813789137901379113792137931379413795137961379713798137991380013801138021380313804138051380613807138081380913810138111381213813138141381513816138171381813819138201382113822138231382413825138261382713828138291383013831138321383313834138351383613837138381383913840138411384213843138441384513846138471384813849138501385113852138531385413855138561385713858138591386013861138621386313864138651386613867138681386913870138711387213873138741387513876138771387813879138801388113882138831388413885138861388713888138891389013891138921389313894138951389613897138981389913900139011390213903139041390513906139071390813909139101391113912139131391413915139161391713918139191392013921139221392313924139251392613927139281392913930139311393213933139341393513936139371393813939139401394113942139431394413945139461394713948139491395013951139521395313954139551395613957139581395913960139611396213963139641396513966139671396813969139701397113972139731397413975139761397713978139791398013981139821398313984139851398613987139881398913990139911399213993139941399513996139971399813999140001400114002140031400414005140061400714008140091401014011140121401314014140151401614017140181401914020140211402214023140241402514026140271402814029140301403114032140331403414035140361403714038140391404014041140421404314044140451404614047140481404914050140511405214053140541405514056140571405814059140601406114062140631406414065140661406714068140691407014071140721407314074140751407614077140781407914080140811408214083140841408514086140871408814089140901409114092140931409414095140961409714098140991410014101141021410314104141051410614107141081410914110141111411214113141141411514116141171411814119141201412114122141231412414125141261412714128141291413014131141321413314134141351413614137141381413914140141411414214143141441414514146141471414814149141501415114152141531415414155141561415714158141591416014161141621416314164141651416614167141681416914170141711417214173141741417514176141771417814179141801418114182141831418414185141861418714188141891419014191141921419314194141951419614197141981419914200142011420214203142041420514206142071420814209142101421114212142131421414215142161421714218142191422014221142221422314224142251422614227142281422914230142311423214233142341423514236142371423814239142401424114242142431424414245142461424714248142491425014251142521425314254142551425614257142581425914260142611426214263142641426514266142671426814269142701427114272142731427414275142761427714278142791428014281142821428314284142851428614287142881428914290142911429214293142941429514296142971429814299143001430114302143031430414305143061430714308143091431014311143121431314314143151431614317143181431914320143211432214323143241432514326143271432814329143301433114332143331433414335143361433714338143391434014341143421434314344143451434614347143481434914350143511435214353143541435514356143571435814359143601436114362143631436414365143661436714368143691437014371143721437314374143751437614377143781437914380143811438214383143841438514386143871438814389143901439114392143931439414395143961439714398143991440014401144021440314404144051440614407144081440914410144111441214413144141441514416144171441814419144201442114422144231442414425144261442714428144291443014431144321443314434144351443614437144381443914440144411444214443144441444514446144471444814449144501445114452144531445414455144561445714458144591446014461144621446314464144651446614467144681446914470144711447214473144741447514476144771447814479144801448114482144831448414485144861448714488144891449014491144921449314494144951449614497144981449914500145011450214503145041450514506145071450814509145101451114512145131451414515145161451714518145191452014521145221452314524145251452614527145281452914530145311453214533145341453514536145371453814539145401454114542145431454414545145461454714548145491455014551145521455314554145551455614557145581455914560145611456214563145641456514566145671456814569145701457114572145731457414575145761457714578145791458014581145821458314584145851458614587145881458914590145911459214593145941459514596145971459814599146001460114602146031460414605146061460714608146091461014611146121461314614146151461614617146181461914620146211462214623146241462514626146271462814629146301463114632146331463414635146361463714638146391464014641146421464314644146451464614647146481464914650146511465214653146541465514656146571465814659146601466114662146631466414665146661466714668146691467014671146721467314674146751467614677146781467914680146811468214683146841468514686146871468814689146901469114692146931469414695146961469714698146991470014701147021470314704147051470614707147081470914710147111471214713147141471514716147171471814719147201472114722147231472414725147261472714728147291473014731147321473314734147351473614737147381473914740147411474214743147441474514746147471474814749147501475114752147531475414755147561475714758147591476014761147621476314764147651476614767147681476914770147711477214773147741477514776147771477814779147801478114782147831478414785147861478714788147891479014791147921479314794147951479614797147981479914800148011480214803148041480514806148071480814809148101481114812148131481414815148161481714818148191482014821148221482314824148251482614827148281482914830148311483214833148341483514836148371483814839148401484114842148431484414845148461484714848148491485014851148521485314854148551485614857148581485914860148611486214863148641486514866148671486814869148701487114872148731487414875148761487714878148791488014881148821488314884148851488614887148881488914890148911489214893148941489514896148971489814899149001490114902149031490414905149061490714908149091491014911149121491314914149151491614917149181491914920149211492214923149241492514926149271492814929149301493114932149331493414935149361493714938149391494014941149421494314944149451494614947149481494914950149511495214953149541495514956149571495814959149601496114962149631496414965149661496714968149691497014971149721497314974149751497614977149781497914980149811498214983149841498514986149871498814989149901499114992149931499414995149961499714998149991500015001150021500315004150051500615007150081500915010150111501215013150141501515016150171501815019150201502115022150231502415025150261502715028150291503015031150321503315034150351503615037150381503915040150411504215043150441504515046150471504815049150501505115052150531505415055150561505715058150591506015061150621506315064150651506615067150681506915070150711507215073150741507515076150771507815079150801508115082150831508415085150861508715088150891509015091150921509315094150951509615097150981509915100151011510215103151041510515106151071510815109151101511115112151131511415115151161511715118151191512015121151221512315124151251512615127151281512915130151311513215133151341513515136151371513815139151401514115142151431514415145151461514715148151491515015151151521515315154151551515615157151581515915160151611516215163151641516515166151671516815169151701517115172151731517415175151761517715178151791518015181151821518315184151851518615187151881518915190151911519215193151941519515196151971519815199152001520115202152031520415205152061520715208152091521015211152121521315214152151521615217152181521915220152211522215223152241522515226152271522815229152301523115232152331523415235152361523715238152391524015241152421524315244152451524615247152481524915250152511525215253152541525515256152571525815259152601526115262152631526415265152661526715268152691527015271152721527315274152751527615277152781527915280152811528215283152841528515286152871528815289152901529115292152931529415295152961529715298152991530015301153021530315304153051530615307153081530915310153111531215313153141531515316153171531815319153201532115322153231532415325153261532715328153291533015331153321533315334153351533615337153381533915340153411534215343153441534515346153471534815349153501535115352153531535415355153561535715358153591536015361153621536315364153651536615367153681536915370153711537215373153741537515376153771537815379153801538115382153831538415385153861538715388153891539015391153921539315394153951539615397153981539915400154011540215403154041540515406154071540815409154101541115412154131541415415154161541715418154191542015421154221542315424154251542615427154281542915430154311543215433154341543515436154371543815439154401544115442154431544415445154461544715448154491545015451154521545315454154551545615457154581545915460154611546215463154641546515466154671546815469154701547115472154731547415475154761547715478154791548015481154821548315484154851548615487154881548915490154911549215493154941549515496154971549815499155001550115502155031550415505155061550715508155091551015511155121551315514155151551615517155181551915520155211552215523155241552515526155271552815529155301553115532155331553415535155361553715538155391554015541155421554315544155451554615547155481554915550155511555215553155541555515556155571555815559155601556115562155631556415565155661556715568155691557015571155721557315574155751557615577155781557915580155811558215583155841558515586155871558815589155901559115592155931559415595155961559715598155991560015601156021560315604156051560615607156081560915610156111561215613156141561515616156171561815619156201562115622156231562415625156261562715628156291563015631156321563315634156351563615637156381563915640156411564215643156441564515646156471564815649156501565115652156531565415655156561565715658156591566015661156621566315664156651566615667156681566915670156711567215673156741567515676156771567815679156801568115682156831568415685156861568715688156891569015691156921569315694156951569615697156981569915700157011570215703157041570515706157071570815709157101571115712157131571415715157161571715718157191572015721157221572315724157251572615727157281572915730157311573215733157341573515736157371573815739157401574115742157431574415745157461574715748157491575015751157521575315754157551575615757157581575915760157611576215763157641576515766157671576815769157701577115772157731577415775157761577715778157791578015781157821578315784157851578615787157881578915790157911579215793157941579515796157971579815799158001580115802158031580415805158061580715808158091581015811158121581315814158151581615817158181581915820158211582215823158241582515826158271582815829158301583115832158331583415835158361583715838158391584015841158421584315844158451584615847158481584915850158511585215853158541585515856158571585815859158601586115862158631586415865158661586715868158691587015871158721587315874158751587615877158781587915880158811588215883158841588515886158871588815889158901589115892158931589415895158961589715898158991590015901159021590315904159051590615907159081590915910159111591215913159141591515916159171591815919159201592115922159231592415925159261592715928159291593015931159321593315934159351593615937159381593915940159411594215943159441594515946159471594815949159501595115952159531595415955159561595715958159591596015961159621596315964159651596615967159681596915970159711597215973159741597515976159771597815979159801598115982159831598415985159861598715988159891599015991159921599315994159951599615997159981599916000160011600216003160041600516006160071600816009160101601116012160131601416015160161601716018160191602016021160221602316024160251602616027160281602916030160311603216033160341603516036160371603816039160401604116042160431604416045160461604716048160491605016051160521605316054160551605616057160581605916060160611606216063160641606516066160671606816069160701607116072160731607416075160761607716078160791608016081160821608316084160851608616087160881608916090160911609216093160941609516096160971609816099161001610116102161031610416105161061610716108161091611016111161121611316114161151611616117161181611916120161211612216123161241612516126161271612816129161301613116132161331613416135161361613716138161391614016141161421614316144161451614616147161481614916150161511615216153161541615516156161571615816159161601616116162161631616416165161661616716168161691617016171161721617316174161751617616177161781617916180161811618216183161841618516186161871618816189161901619116192161931619416195161961619716198161991620016201162021620316204162051620616207162081620916210162111621216213162141621516216162171621816219162201622116222162231622416225162261622716228162291623016231162321623316234162351623616237162381623916240162411624216243162441624516246162471624816249162501625116252162531625416255162561625716258162591626016261162621626316264162651626616267162681626916270162711627216273162741627516276162771627816279162801628116282162831628416285162861628716288162891629016291162921629316294162951629616297162981629916300163011630216303163041630516306163071630816309163101631116312163131631416315163161631716318163191632016321163221632316324163251632616327163281632916330163311633216333163341633516336163371633816339163401634116342163431634416345163461634716348163491635016351163521635316354163551635616357163581635916360163611636216363163641636516366163671636816369163701637116372163731637416375163761637716378163791638016381163821638316384163851638616387163881638916390163911639216393163941639516396163971639816399164001640116402164031640416405164061640716408164091641016411164121641316414164151641616417164181641916420164211642216423164241642516426164271642816429164301643116432164331643416435164361643716438164391644016441164421644316444164451644616447164481644916450164511645216453164541645516456164571645816459164601646116462164631646416465164661646716468164691647016471164721647316474164751647616477164781647916480164811648216483164841648516486164871648816489164901649116492164931649416495164961649716498164991650016501165021650316504165051650616507165081650916510165111651216513165141651516516165171651816519165201652116522165231652416525165261652716528165291653016531165321653316534165351653616537165381653916540165411654216543165441654516546165471654816549165501655116552165531655416555165561655716558165591656016561165621656316564165651656616567165681656916570165711657216573165741657516576165771657816579165801658116582165831658416585165861658716588165891659016591165921659316594165951659616597165981659916600166011660216603166041660516606166071660816609166101661116612166131661416615166161661716618166191662016621166221662316624166251662616627166281662916630166311663216633166341663516636166371663816639166401664116642166431664416645166461664716648166491665016651166521665316654166551665616657166581665916660166611666216663166641666516666166671666816669166701667116672166731667416675166761667716678166791668016681166821668316684166851668616687166881668916690166911669216693166941669516696166971669816699167001670116702167031670416705167061670716708167091671016711167121671316714167151671616717167181671916720167211672216723167241672516726167271672816729167301673116732167331673416735167361673716738167391674016741167421674316744167451674616747167481674916750167511675216753167541675516756167571675816759167601676116762167631676416765167661676716768167691677016771167721677316774167751677616777167781677916780167811678216783167841678516786167871678816789167901679116792167931679416795167961679716798167991680016801168021680316804168051680616807168081680916810168111681216813168141681516816168171681816819168201682116822168231682416825168261682716828168291683016831168321683316834168351683616837168381683916840168411684216843168441684516846168471684816849168501685116852168531685416855168561685716858168591686016861168621686316864168651686616867168681686916870168711687216873168741687516876168771687816879168801688116882168831688416885168861688716888168891689016891168921689316894168951689616897168981689916900169011690216903169041690516906169071690816909169101691116912169131691416915169161691716918169191692016921169221692316924169251692616927169281692916930169311693216933169341693516936169371693816939169401694116942169431694416945169461694716948169491695016951169521695316954169551695616957169581695916960169611696216963169641696516966169671696816969169701697116972169731697416975169761697716978169791698016981169821698316984169851698616987169881698916990169911699216993169941699516996169971699816999170001700117002170031700417005170061700717008170091701017011170121701317014170151701617017170181701917020170211702217023170241702517026170271702817029170301703117032170331703417035170361703717038170391704017041170421704317044170451704617047170481704917050170511705217053170541705517056170571705817059170601706117062170631706417065170661706717068170691707017071170721707317074170751707617077170781707917080170811708217083170841708517086170871708817089170901709117092170931709417095170961709717098170991710017101171021710317104171051710617107171081710917110171111711217113171141711517116171171711817119171201712117122171231712417125171261712717128171291713017131171321713317134171351713617137171381713917140171411714217143171441714517146171471714817149171501715117152171531715417155171561715717158171591716017161171621716317164171651716617167171681716917170171711717217173171741717517176171771717817179171801718117182171831718417185171861718717188171891719017191171921719317194171951719617197171981719917200172011720217203172041720517206172071720817209172101721117212172131721417215172161721717218172191722017221172221722317224172251722617227172281722917230172311723217233172341723517236172371723817239172401724117242172431724417245172461724717248172491725017251172521725317254172551725617257172581725917260172611726217263172641726517266172671726817269172701727117272172731727417275172761727717278172791728017281172821728317284172851728617287172881728917290172911729217293172941729517296172971729817299173001730117302173031730417305173061730717308173091731017311173121731317314173151731617317173181731917320173211732217323173241732517326173271732817329173301733117332173331733417335173361733717338173391734017341173421734317344173451734617347173481734917350173511735217353173541735517356173571735817359173601736117362173631736417365173661736717368173691737017371173721737317374173751737617377173781737917380173811738217383173841738517386173871738817389173901739117392173931739417395173961739717398173991740017401174021740317404174051740617407174081740917410174111741217413174141741517416174171741817419174201742117422174231742417425174261742717428174291743017431174321743317434174351743617437174381743917440174411744217443174441744517446174471744817449174501745117452174531745417455174561745717458174591746017461174621746317464174651746617467174681746917470174711747217473174741747517476174771747817479174801748117482174831748417485174861748717488174891749017491174921749317494174951749617497174981749917500175011750217503175041750517506175071750817509175101751117512175131751417515175161751717518175191752017521175221752317524175251752617527175281752917530175311753217533175341753517536175371753817539175401754117542175431754417545175461754717548175491755017551175521755317554175551755617557175581755917560175611756217563175641756517566175671756817569175701757117572175731757417575175761757717578175791758017581175821758317584175851758617587175881758917590175911759217593175941759517596175971759817599176001760117602176031760417605176061760717608176091761017611176121761317614176151761617617176181761917620176211762217623176241762517626176271762817629176301763117632176331763417635176361763717638176391764017641176421764317644176451764617647176481764917650176511765217653176541765517656176571765817659176601766117662176631766417665176661766717668176691767017671176721767317674176751767617677176781767917680176811768217683176841768517686176871768817689176901769117692176931769417695176961769717698176991770017701177021770317704177051770617707177081770917710177111771217713177141771517716177171771817719177201772117722177231772417725177261772717728177291773017731177321773317734177351773617737177381773917740177411774217743177441774517746177471774817749177501775117752177531775417755177561775717758177591776017761177621776317764177651776617767177681776917770177711777217773177741777517776177771777817779177801778117782177831778417785177861778717788177891779017791177921779317794177951779617797177981779917800178011780217803178041780517806178071780817809178101781117812178131781417815178161781717818178191782017821178221782317824178251782617827178281782917830178311783217833178341783517836178371783817839178401784117842178431784417845178461784717848178491785017851178521785317854178551785617857178581785917860178611786217863178641786517866178671786817869178701787117872178731787417875178761787717878178791788017881178821788317884178851788617887178881788917890178911789217893178941789517896178971789817899179001790117902179031790417905179061790717908179091791017911179121791317914179151791617917179181791917920179211792217923179241792517926179271792817929179301793117932179331793417935179361793717938179391794017941179421794317944179451794617947179481794917950179511795217953179541795517956179571795817959179601796117962179631796417965179661796717968179691797017971179721797317974179751797617977179781797917980179811798217983179841798517986179871798817989179901799117992179931799417995179961799717998179991800018001180021800318004180051800618007180081800918010180111801218013180141801518016180171801818019180201802118022180231802418025180261802718028180291803018031180321803318034180351803618037180381803918040180411804218043180441804518046180471804818049180501805118052180531805418055180561805718058180591806018061180621806318064180651806618067180681806918070180711807218073180741807518076180771807818079180801808118082180831808418085180861808718088180891809018091180921809318094180951809618097180981809918100181011810218103181041810518106181071810818109181101811118112181131811418115181161811718118181191812018121181221812318124181251812618127181281812918130181311813218133181341813518136181371813818139181401814118142181431814418145181461814718148181491815018151181521815318154181551815618157181581815918160181611816218163181641816518166181671816818169181701817118172181731817418175181761817718178181791818018181181821818318184181851818618187181881818918190181911819218193181941819518196181971819818199182001820118202182031820418205182061820718208182091821018211182121821318214182151821618217182181821918220182211822218223182241822518226182271822818229182301823118232182331823418235182361823718238182391824018241182421824318244182451824618247182481824918250182511825218253182541825518256182571825818259182601826118262182631826418265182661826718268182691827018271182721827318274182751827618277182781827918280182811828218283182841828518286182871828818289182901829118292182931829418295182961829718298182991830018301183021830318304183051830618307183081830918310183111831218313183141831518316183171831818319183201832118322183231832418325183261832718328183291833018331183321833318334183351833618337183381833918340183411834218343183441834518346183471834818349183501835118352183531835418355183561835718358183591836018361183621836318364183651836618367183681836918370183711837218373183741837518376183771837818379183801838118382183831838418385183861838718388183891839018391183921839318394183951839618397183981839918400184011840218403184041840518406184071840818409184101841118412184131841418415184161841718418184191842018421184221842318424184251842618427184281842918430184311843218433184341843518436184371843818439184401844118442184431844418445184461844718448184491845018451184521845318454184551845618457184581845918460184611846218463184641846518466184671846818469184701847118472184731847418475184761847718478184791848018481184821848318484184851848618487184881848918490184911849218493184941849518496184971849818499185001850118502185031850418505185061850718508185091851018511185121851318514185151851618517185181851918520185211852218523185241852518526185271852818529185301853118532185331853418535185361853718538185391854018541185421854318544185451854618547185481854918550185511855218553185541855518556185571855818559185601856118562185631856418565185661856718568185691857018571185721857318574185751857618577185781857918580185811858218583185841858518586185871858818589185901859118592185931859418595185961859718598185991860018601186021860318604186051860618607186081860918610186111861218613186141861518616186171861818619186201862118622186231862418625186261862718628186291863018631186321863318634186351863618637186381863918640186411864218643186441864518646186471864818649186501865118652186531865418655186561865718658186591866018661186621866318664186651866618667186681866918670186711867218673186741867518676186771867818679186801868118682186831868418685186861868718688186891869018691186921869318694186951869618697186981869918700187011870218703187041870518706187071870818709187101871118712187131871418715187161871718718187191872018721187221872318724187251872618727187281872918730187311873218733187341873518736187371873818739187401874118742187431874418745187461874718748187491875018751187521875318754187551875618757187581875918760187611876218763187641876518766187671876818769187701877118772187731877418775187761877718778187791878018781187821878318784187851878618787187881878918790187911879218793187941879518796187971879818799188001880118802188031880418805188061880718808188091881018811188121881318814188151881618817188181881918820188211882218823188241882518826188271882818829188301883118832188331883418835188361883718838188391884018841188421884318844188451884618847188481884918850188511885218853188541885518856188571885818859188601886118862188631886418865188661886718868188691887018871188721887318874188751887618877188781887918880188811888218883188841888518886188871888818889188901889118892188931889418895188961889718898188991890018901189021890318904189051890618907189081890918910189111891218913189141891518916189171891818919189201892118922189231892418925189261892718928189291893018931189321893318934189351893618937189381893918940189411894218943189441894518946189471894818949189501895118952189531895418955189561895718958189591896018961189621896318964189651896618967189681896918970189711897218973189741897518976189771897818979189801898118982189831898418985189861898718988189891899018991189921899318994189951899618997189981899919000190011900219003190041900519006190071900819009190101901119012190131901419015190161901719018190191902019021190221902319024190251902619027190281902919030190311903219033190341903519036190371903819039190401904119042190431904419045190461904719048190491905019051190521905319054190551905619057190581905919060190611906219063190641906519066190671906819069190701907119072190731907419075190761907719078190791908019081190821908319084190851908619087190881908919090190911909219093190941909519096190971909819099191001910119102191031910419105191061910719108191091911019111191121911319114191151911619117191181911919120191211912219123191241912519126191271912819129191301913119132191331913419135191361913719138191391914019141191421914319144191451914619147191481914919150191511915219153191541915519156191571915819159191601916119162191631916419165191661916719168191691917019171191721917319174191751917619177191781917919180191811918219183191841918519186191871918819189191901919119192191931919419195191961919719198191991920019201192021920319204192051920619207192081920919210192111921219213192141921519216192171921819219192201922119222192231922419225192261922719228192291923019231192321923319234192351923619237192381923919240192411924219243192441924519246192471924819249192501925119252192531925419255192561925719258192591926019261192621926319264192651926619267192681926919270192711927219273192741927519276192771927819279192801928119282192831928419285192861928719288192891929019291192921929319294192951929619297192981929919300193011930219303193041930519306193071930819309193101931119312193131931419315193161931719318193191932019321193221932319324193251932619327193281932919330193311933219333193341933519336193371933819339193401934119342193431934419345193461934719348193491935019351193521935319354193551935619357193581935919360193611936219363193641936519366193671936819369193701937119372193731937419375193761937719378193791938019381193821938319384193851938619387193881938919390193911939219393193941939519396193971939819399194001940119402194031940419405194061940719408194091941019411194121941319414194151941619417194181941919420194211942219423194241942519426194271942819429194301943119432194331943419435194361943719438194391944019441194421944319444194451944619447194481944919450194511945219453194541945519456194571945819459194601946119462194631946419465194661946719468194691947019471194721947319474194751947619477194781947919480194811948219483194841948519486194871948819489194901949119492194931949419495194961949719498194991950019501195021950319504195051950619507195081950919510195111951219513195141951519516195171951819519195201952119522195231952419525195261952719528195291953019531195321953319534195351953619537195381953919540195411954219543195441954519546195471954819549195501955119552195531955419555195561955719558195591956019561195621956319564195651956619567195681956919570195711957219573195741957519576195771957819579195801958119582195831958419585195861958719588195891959019591195921959319594195951959619597195981959919600196011960219603196041960519606196071960819609196101961119612196131961419615196161961719618196191962019621196221962319624196251962619627196281962919630196311963219633196341963519636196371963819639196401964119642196431964419645196461964719648196491965019651196521965319654196551965619657196581965919660196611966219663196641966519666196671966819669196701967119672196731967419675196761967719678196791968019681196821968319684196851968619687196881968919690196911969219693196941969519696196971969819699197001970119702197031970419705197061970719708197091971019711197121971319714197151971619717197181971919720197211972219723197241972519726197271972819729197301973119732197331973419735197361973719738197391974019741197421974319744197451974619747197481974919750197511975219753197541975519756197571975819759197601976119762197631976419765197661976719768197691977019771197721977319774197751977619777197781977919780197811978219783197841978519786197871978819789197901979119792197931979419795197961979719798197991980019801198021980319804198051980619807198081980919810198111981219813198141981519816198171981819819198201982119822198231982419825198261982719828198291983019831198321983319834198351983619837198381983919840198411984219843198441984519846198471984819849198501985119852198531985419855198561985719858198591986019861198621986319864198651986619867198681986919870198711987219873198741987519876198771987819879198801988119882198831988419885198861988719888198891989019891198921989319894198951989619897198981989919900199011990219903199041990519906199071990819909199101991119912199131991419915199161991719918199191992019921199221992319924199251992619927199281992919930199311993219933199341993519936199371993819939199401994119942199431994419945199461994719948199491995019951199521995319954199551995619957199581995919960199611996219963199641996519966199671996819969199701997119972199731997419975199761997719978199791998019981199821998319984199851998619987199881998919990199911999219993199941999519996199971999819999200002000120002200032000420005200062000720008200092001020011200122001320014200152001620017200182001920020200212002220023200242002520026200272002820029200302003120032200332003420035200362003720038200392004020041200422004320044200452004620047200482004920050200512005220053200542005520056200572005820059200602006120062200632006420065200662006720068200692007020071200722007320074200752007620077200782007920080200812008220083200842008520086200872008820089200902009120092200932009420095200962009720098200992010020101201022010320104201052010620107201082010920110201112011220113201142011520116201172011820119201202012120122201232012420125201262012720128201292013020131201322013320134201352013620137201382013920140201412014220143201442014520146201472014820149201502015120152201532015420155201562015720158201592016020161201622016320164201652016620167201682016920170201712017220173201742017520176201772017820179201802018120182201832018420185201862018720188201892019020191201922019320194201952019620197201982019920200202012020220203202042020520206202072020820209202102021120212202132021420215202162021720218202192022020221202222022320224202252022620227202282022920230202312023220233202342023520236202372023820239202402024120242202432024420245202462024720248202492025020251202522025320254202552025620257202582025920260202612026220263202642026520266202672026820269202702027120272202732027420275202762027720278202792028020281202822028320284202852028620287202882028920290202912029220293202942029520296202972029820299203002030120302203032030420305203062030720308203092031020311203122031320314203152031620317203182031920320203212032220323203242032520326203272032820329203302033120332203332033420335203362033720338203392034020341203422034320344203452034620347203482034920350203512035220353203542035520356203572035820359203602036120362203632036420365203662036720368203692037020371203722037320374203752037620377203782037920380203812038220383203842038520386203872038820389203902039120392203932039420395203962039720398203992040020401204022040320404204052040620407204082040920410204112041220413204142041520416204172041820419204202042120422204232042420425204262042720428204292043020431204322043320434204352043620437204382043920440204412044220443204442044520446204472044820449204502045120452204532045420455204562045720458204592046020461204622046320464204652046620467204682046920470204712047220473204742047520476204772047820479204802048120482204832048420485204862048720488204892049020491204922049320494204952049620497204982049920500205012050220503205042050520506205072050820509205102051120512205132051420515205162051720518205192052020521205222052320524205252052620527205282052920530205312053220533205342053520536205372053820539205402054120542205432054420545205462054720548205492055020551205522055320554205552055620557205582055920560205612056220563205642056520566205672056820569205702057120572205732057420575205762057720578205792058020581205822058320584205852058620587205882058920590205912059220593205942059520596205972059820599206002060120602206032060420605206062060720608206092061020611206122061320614206152061620617206182061920620206212062220623206242062520626206272062820629206302063120632206332063420635206362063720638206392064020641206422064320644206452064620647206482064920650206512065220653206542065520656206572065820659206602066120662206632066420665206662066720668206692067020671206722067320674206752067620677206782067920680206812068220683206842068520686206872068820689206902069120692206932069420695206962069720698206992070020701207022070320704207052070620707207082070920710207112071220713207142071520716207172071820719207202072120722207232072420725207262072720728207292073020731207322073320734207352073620737207382073920740207412074220743207442074520746207472074820749207502075120752207532075420755207562075720758207592076020761207622076320764207652076620767207682076920770207712077220773207742077520776207772077820779207802078120782207832078420785207862078720788207892079020791207922079320794207952079620797207982079920800208012080220803208042080520806208072080820809208102081120812208132081420815208162081720818208192082020821208222082320824208252082620827208282082920830208312083220833208342083520836208372083820839208402084120842208432084420845208462084720848208492085020851208522085320854208552085620857208582085920860208612086220863208642086520866208672086820869208702087120872208732087420875208762087720878208792088020881208822088320884208852088620887208882088920890208912089220893208942089520896208972089820899209002090120902209032090420905209062090720908209092091020911209122091320914
  1. /**
  2. * Copyright (c) 2006-2017, JGraph Ltd
  3. * Copyright (c) 2006-2017, Gaudenz Alder
  4. */
  5. (function()
  6. {
  7. /**
  8. * Version
  9. */
  10. EditorUi.VERSION = '@DRAWIO-VERSION@';
  11. /**
  12. * Overrides compact UI setting.
  13. */
  14. EditorUi.compactUi = Editor.currentTheme != 'atlas' || window.DRAWIO_PUBLIC_BUILD;
  15. /**
  16. * Overrides default grid color for dark mode
  17. */
  18. if (Editor.isDarkMode())
  19. {
  20. mxGraphView.prototype.gridColor = mxGraphView.prototype.defaultDarkGridColor;
  21. }
  22. /**
  23. * Switch to disable logging for mode and search terms.
  24. */
  25. EditorUi.enableLogging = urlParams['stealth'] != '1' && urlParams['lockdown'] != '1' &&
  26. (/.*\.draw\.io$/.test(window.location.hostname) ||
  27. /.*\.diagrams\.net$/.test(window.location.hostname)) &&
  28. window.location.hostname != 'https://preprod.diagrams.net/' &&
  29. window.location.hostname != 'support.draw.io';
  30. /**
  31. * Protocol and hostname to use for embedded files. Default is https://www.draw.io
  32. */
  33. EditorUi.drawHost = window.DRAWIO_BASE_URL;
  34. /**
  35. * Protocol and hostname to use for embedded files. Default is https://www.draw.io
  36. */
  37. EditorUi.lightboxHost = window.DRAWIO_LIGHTBOX_URL;
  38. /**
  39. * Switch to disable logging for mode and search terms.
  40. */
  41. EditorUi.lastErrorMessage = null;
  42. /**
  43. * Switch to disable logging for mode and search terms.
  44. */
  45. EditorUi.ignoredAnonymizedChars = '\n\t`~!@#$%^&*()_+{}|:"<>?-=[]\;\'.\/,\n\t';
  46. /**
  47. * Specifies the URL for the templates index file.
  48. */
  49. EditorUi.templateFile = TEMPLATE_PATH + '/index.xml';
  50. /**
  51. * Specifies the URL for the diffsync cache.
  52. */
  53. EditorUi.cacheUrl = window.REALTIME_URL;
  54. /**
  55. * Disables sync if no diffsync cache is defined.
  56. */
  57. if (EditorUi.cacheUrl == null && typeof DrawioFile !== 'undefined')
  58. {
  59. DrawioFile.SYNC = 'none'; // Disables real-time sync
  60. }
  61. /**
  62. * Cache timeout is 10 seconds.
  63. */
  64. Editor.cacheTimeout = 10000;
  65. /**
  66. * Switch to enable PlantUML in the insert from text dialog.
  67. * NOTE: This must also be enabled on the server-side.
  68. */
  69. EditorUi.enablePlantUml = EditorUi.enableLogging;
  70. /**
  71. * https://github.com/electron/electron/issues/2288
  72. */
  73. EditorUi.isElectronApp = window != null && window.process != null &&
  74. window.process.versions != null && window.process.versions['electron'] != null;
  75. /**
  76. * Shortcut for capability check.
  77. */
  78. EditorUi.nativeFileSupport = !mxClient.IS_OP && !EditorUi.isElectronApp &&
  79. urlParams['extAuth'] != '1' && 'showSaveFilePicker' in window &&
  80. 'showOpenFilePicker' in window;
  81. /**
  82. * Specifies if drafts should be saved in IndexedDB.
  83. */
  84. EditorUi.enableDrafts = !mxClient.IS_CHROMEAPP &&
  85. isLocalStorage && urlParams['drafts'] != '0';
  86. /**
  87. * Link for scratchpad help.
  88. */
  89. EditorUi.scratchpadHelpLink = 'https://www.drawio.com/doc/faq/scratchpad';
  90. /**
  91. * Specifies if the edit option should be shown in the HTML export dialog.
  92. */
  93. EditorUi.enableHtmlEditOption = true;
  94. /**
  95. * Default Mermaid config without using foreign objects in flowcharts.
  96. */
  97. EditorUi.mermaidDiagramTypes = ['flowchart', 'classDiagram', 'sequenceDiagram',
  98. 'stateDiagram', 'mindmap', 'graph', 'erDiagram', 'requirementDiagram',
  99. 'journey', 'gantt', 'pie', 'gitGraph'];
  100. /**
  101. * Default Mermaid config without using foreign objects in flowcharts.
  102. */
  103. EditorUi.defaultMermaidConfig = {
  104. theme:'neutral',
  105. arrowMarkerAbsolute:false,
  106. flowchart:
  107. {
  108. htmlLabels:false
  109. },
  110. sequence:
  111. {
  112. diagramMarginX:50,
  113. diagramMarginY:10,
  114. actorMargin:50,
  115. width:150,
  116. height:65,
  117. boxMargin:10,
  118. boxTextMargin:5,
  119. noteMargin:10,
  120. messageMargin:35,
  121. mirrorActors:true,
  122. bottomMarginAdj:1,
  123. useMaxWidth:true,
  124. rightAngles:false,
  125. showSequenceNumbers:false
  126. },
  127. gantt:{
  128. titleTopMargin:25,
  129. barHeight:20,
  130. barGap:4,
  131. topPadding:50,
  132. leftPadding:75,
  133. gridLineStartPadding:35,
  134. fontSize:11,
  135. fontFamily:'"Open-Sans", "sans-serif"',
  136. numberSectionStyles:4,
  137. axisFormat:'%Y-%m-%d'
  138. }
  139. };
  140. /**
  141. * Updates action states depending on the selection.
  142. */
  143. EditorUi.logError = function(message, url, linenumber, colno, err, severity, quiet)
  144. {
  145. if (message != null)
  146. {
  147. err = (err != null) ? err : new Error(message);
  148. err.stack = (err.stack != null) ? err.stack : '';
  149. severity = (severity != null) ? severity : ((message.indexOf('NetworkError') < 0 &&
  150. message.indexOf('SecurityError') < 0 && message.indexOf('NS_ERROR_FAILURE') < 0 &&
  151. message.indexOf('out of memory') < 0) ? 'SEVERE' : 'CONFIG');
  152. try
  153. {
  154. if (EditorUi.enableLogging && urlParams['dev'] != '1' &&
  155. message != EditorUi.lastErrorMessage && message.indexOf('extension:') < 0 &&
  156. message.indexOf('ResizeObserver loop completed with undelivered notifications') < 0 &&
  157. err.stack.indexOf('extension:') < 0 && err.stack.indexOf('<anonymous>:') < 0 &&
  158. err.stack.indexOf('/math/es5/') < 0)
  159. {
  160. EditorUi.lastErrorMessage = message;
  161. var img = new Image();
  162. var logDomain = window.DRAWIO_LOG_URL != null ?
  163. window.DRAWIO_LOG_URL : '';
  164. img.src = logDomain + '/log?severity=' + severity +
  165. '&v=' + encodeURIComponent(EditorUi.VERSION) +
  166. '&msg=clientError:' + encodeURIComponent(message) +
  167. ':url:' + encodeURIComponent(window.location.href) +
  168. ':lnum:' + encodeURIComponent(linenumber) +
  169. ((colno != null) ?
  170. ':colno:' + encodeURIComponent(colno) : '') +
  171. ((err.stack != '') ?
  172. '&stack=' + encodeURIComponent(err.stack) : '');
  173. }
  174. }
  175. catch (e)
  176. {
  177. // ignore
  178. }
  179. try
  180. {
  181. if (!quiet && window.console != null)
  182. {
  183. console.error(severity, message, url, linenumber, colno, err);
  184. }
  185. }
  186. catch (e)
  187. {
  188. // ignore
  189. }
  190. }
  191. };
  192. /**
  193. * Updates action states depending on the selection.
  194. */
  195. EditorUi.logEvent = function(data)
  196. {
  197. if (urlParams['dev'] == '1')
  198. {
  199. EditorUi.debug('logEvent', data);
  200. }
  201. else if (EditorUi.enableLogging)
  202. {
  203. try
  204. {
  205. var logDomain = window.DRAWIO_LOG_URL != null ? window.DRAWIO_LOG_URL : '';
  206. var img = new Image();
  207. img.src = logDomain + '/images/1x1.png?' +
  208. 'v=' + encodeURIComponent(EditorUi.VERSION) +
  209. ((data != null) ? '&data=' + encodeURIComponent(JSON.stringify(data)) : '');
  210. }
  211. catch (e)
  212. {
  213. // ignore
  214. }
  215. }
  216. };
  217. /**
  218. * Sending error reports.
  219. */
  220. EditorUi.sendReport = function(data, maxLength)
  221. {
  222. if (urlParams['dev'] == '1')
  223. {
  224. EditorUi.debug('sendReport', data);
  225. }
  226. else if (EditorUi.enableLogging)
  227. {
  228. try
  229. {
  230. maxLength = (maxLength != null) ? maxLength : 50000;
  231. if (data.length > maxLength)
  232. {
  233. data = data.substring(0, maxLength) + '\n...[SHORTENED]'
  234. }
  235. mxUtils.post('/email', 'version=' + encodeURIComponent(EditorUi.VERSION) +
  236. '&url=' + encodeURIComponent(window.location.href) +
  237. '&data=' + encodeURIComponent(data));
  238. }
  239. catch (e)
  240. {
  241. // ignore
  242. }
  243. }
  244. };
  245. /**
  246. * Adds the listener for automatically saving the diagram for local changes.
  247. */
  248. EditorUi.debug = function()
  249. {
  250. try
  251. {
  252. if (window.console != null && urlParams['test'] == '1')
  253. {
  254. var args = [new Date().toISOString()];
  255. for (var i = 0; i < arguments.length; i++)
  256. {
  257. args.push(arguments[i]);
  258. }
  259. console.log.apply(console, args);
  260. }
  261. }
  262. catch (e)
  263. {
  264. // ignore
  265. }
  266. };
  267. /**
  268. * Removes any values, styles and geometries from the given XML node.
  269. */
  270. EditorUi.removeChildNodes = function(node)
  271. {
  272. while (node.firstChild != null)
  273. {
  274. node.removeChild(node.firstChild);
  275. }
  276. };
  277. /**
  278. * Replaces SVG data URIs in images with the actual SVG for
  279. * the images to be supported in apps like Powerpoint.
  280. */
  281. EditorUi.embedSvgImages = function(root)
  282. {
  283. var temp = root.getElementsByTagName('image');
  284. // Clones array
  285. var imgs = [];
  286. for (var i = 0; i < temp.length; i++)
  287. {
  288. imgs.push(temp[i]);
  289. }
  290. // Replaces images
  291. for (var i = 0; i < imgs.length; i++)
  292. {
  293. EditorUi.replaceSvgImage(imgs[i]);
  294. }
  295. };
  296. /**
  297. * Replaces the given SVG image with an SVG subtree.
  298. */
  299. EditorUi.replaceSvgImage = function(node)
  300. {
  301. try
  302. {
  303. var href = null;
  304. // Workaround for missing namespace support
  305. if (node.getAttributeNS == null)
  306. {
  307. href = node.getAttribute('xlink:href');
  308. }
  309. else
  310. {
  311. href = node.getAttributeNS(mxConstants.NS_XLINK, 'href');
  312. }
  313. var svg = EditorUi.getSvgSubtree(href);
  314. // Checks nodeName as parsers can get foreignObjects
  315. // content to go before the SVG element
  316. if (svg != null && svg.nodeName == 'svg')
  317. {
  318. svg.setAttribute('x', node.getAttribute('x'));
  319. svg.setAttribute('y', node.getAttribute('y'));
  320. svg.setAttribute('width', node.getAttribute('width'));
  321. svg.setAttribute('height', node.getAttribute('height'));
  322. svg.style.fontFamily = 'initial';
  323. // Adds group with optional transform attribute
  324. var group = mxUtils.createElementNs(svg.ownerDocument,
  325. mxConstants.NS_SVG, 'g');
  326. group.appendChild(svg);
  327. if (node.hasAttribute('transform'))
  328. {
  329. group.setAttribute('transform', node.getAttribute('transform'));
  330. }
  331. node.parentNode.replaceChild(group, node);
  332. }
  333. }
  334. catch (e)
  335. {
  336. // ignore
  337. }
  338. };
  339. /**
  340. * Returns SVG with modified CSS rules that limit scope to subtree.
  341. */
  342. EditorUi.getSvgSubtree = function(href)
  343. {
  344. var data = Graph.getSvgFromDataUri(href);
  345. var svg = null;
  346. if (data != null)
  347. {
  348. svg = Graph.sanitizeNode(mxUtils.parseXml(data).documentElement);
  349. // Limits CSS rules to subtree
  350. var styles = svg.getElementsByTagName('style');
  351. if (styles.length > 0)
  352. {
  353. var id = 'svg-image-' + Editor.guid();
  354. svg.setAttribute('id', id);
  355. // Adds ID selector for all CSS rules to limit scope
  356. var doc = document.implementation.createHTMLDocument(''),
  357. styleElement = document.createElement('style');
  358. for (var j = 0; j < styles.length; j++)
  359. {
  360. styleElement.textContent = styles[j].textContent;
  361. doc.body.appendChild(styleElement);
  362. var modifiedCss = '';
  363. for (var k = 0; k < styleElement.sheet.cssRules.length; k++)
  364. {
  365. var rule = styleElement.sheet.cssRules[k];
  366. if (rule.selectorText != null)
  367. {
  368. var tokens = rule.selectorText.split(',');
  369. for (var l = 0; l < tokens.length; l++)
  370. {
  371. tokens[l] = '#' + id + ' ' + tokens[l];
  372. }
  373. rule.selectorText = tokens.join(',');
  374. }
  375. modifiedCss += rule.cssText + '\n';
  376. }
  377. styles[j].textContent = modifiedCss;
  378. }
  379. }
  380. // Removes data-cell-id attribute from all elements
  381. var elements = svg.getElementsByTagName('*');
  382. for (var i = 0; i < elements.length; i++)
  383. {
  384. elements[i].removeAttribute('data-cell-id');
  385. }
  386. }
  387. return svg;
  388. };
  389. /**
  390. * Contains the default XML for an empty diagram.
  391. */
  392. EditorUi.prototype.emptyDiagramXml = '<mxGraphModel><root><mxCell id="0"/><mxCell id="1" parent="0"/></root></mxGraphModel>';
  393. /**
  394. *
  395. */
  396. EditorUi.prototype.emptyLibraryXml = '<mxlibrary>[]</mxlibrary>';
  397. /**
  398. * Sets the delay for autosave in milliseconds. Default is 2000.
  399. */
  400. EditorUi.prototype.mode = null;
  401. /**
  402. * General timeout is 25 seconds.
  403. * LATER: Move to Editor
  404. */
  405. EditorUi.prototype.timeout = Editor.prototype.timeout;
  406. /**
  407. * Specifies the default custom shape style.
  408. */
  409. EditorUi.prototype.defaultCustomShapeStyle = 'shape=stencil(tZRtTsQgEEBPw1+DJR7AoN6DbWftpAgE0Ortd/jYRGq72R+YNE2YgTePloEJGWblgA18ZuKFDcMj5/Sm8boZq+BgjCX4pTyqk6ZlKROitwusOMXKQDODx5iy4pXxZ5qTHiFHawxB0JrQZH7lCabQ0Fr+XWC1/E8zcsT/gAi+Subo2/3Mh6d/oJb5nU1b5tW7r2knautaa3T+U32o7f7vZwpJkaNDLORJjcu7t59m2jXxqX9un+tt022acsfmoKaQZ+vhhswZtS6Ne/ThQGt0IV0N3Yyv6P3CeT9/tHO0XFI5cAE=);whiteSpace=wrap;html=1;';
  410. /**
  411. * Defines the maximum size for images.
  412. */
  413. EditorUi.prototype.maxBackgroundSize = 1600;
  414. /**
  415. * Defines the maximum size for images in px. Default is 1200.
  416. */
  417. EditorUi.prototype.maxImageSize = 1200;
  418. /**
  419. * Defines the maximum width for pasted text.
  420. * Use 0 to disable check.
  421. */
  422. EditorUi.prototype.maxTextWidth = 520;
  423. /**
  424. * Images above 100K should be resampled.
  425. */
  426. EditorUi.prototype.resampleThreshold = 100000;
  427. /**
  428. * Defines the maximum size for images in bytes. Default is 2 MB.
  429. */
  430. EditorUi.prototype.maxImageBytes = 2000000;
  431. /**
  432. * Maximum size for background images is 2.5 MB.
  433. */
  434. EditorUi.prototype.maxBackgroundBytes = 2500000;
  435. /**
  436. * Maximum size for text files in labels is 0.5 MB.
  437. */
  438. EditorUi.prototype.maxTextBytes = 500000;
  439. /**
  440. * Holds the current file.
  441. */
  442. EditorUi.prototype.currentFile = null;
  443. /**
  444. * Specifies if PDF export should be done via print dialog. Default is
  445. * false which uses the PhantomJS backend to create the PDF.
  446. */
  447. EditorUi.prototype.printPdfExport = false;
  448. /**
  449. * Specifies if PDF export with pages is enabled.
  450. */
  451. EditorUi.prototype.pdfPageExport = true;
  452. /**
  453. * Restores app defaults for UI
  454. */
  455. EditorUi.prototype.formatEnabled = urlParams['format'] != '0';
  456. /**
  457. * Whether template action should be shown in insert menu.
  458. */
  459. EditorUi.prototype.insertTemplateEnabled = true;
  460. /**
  461. * Restores app defaults for UI
  462. */
  463. EditorUi.prototype.closableScratchpad = true;
  464. /**
  465. * Restores app defaults for UI
  466. */
  467. EditorUi.prototype.embedExportBorder = 8;
  468. /**
  469. * Restores app defaults for UI
  470. */
  471. EditorUi.prototype.embedExportBackground = null;
  472. /**
  473. * Restores app defaults for UI
  474. */
  475. EditorUi.prototype.shareCursorPosition = true;
  476. /**
  477. * Restores app defaults for UI
  478. */
  479. EditorUi.prototype.showRemoteCursors = true;
  480. /**
  481. * Capability check for canvas export
  482. */
  483. (function()
  484. {
  485. EditorUi.prototype.useCanvasForExport = false;
  486. EditorUi.prototype.jpgSupported = false;
  487. EditorUi.prototype.webpSupported = false;
  488. // Checks if canvas is supported
  489. try
  490. {
  491. var cnv = document.createElement('canvas');
  492. EditorUi.prototype.canvasSupported = !!(cnv.getContext && cnv.getContext('2d'));
  493. }
  494. catch (e)
  495. {
  496. // ignore
  497. }
  498. try
  499. {
  500. var canvas = document.createElement('canvas');
  501. var img = new Image();
  502. // LATER: Capability check should not be async
  503. img.onload = function()
  504. {
  505. try
  506. {
  507. var ctx = canvas.getContext('2d');
  508. ctx.drawImage(img, 0, 0);
  509. // Works in Chrome, Firefox, Edge, Safari and Opera
  510. var result = canvas.toDataURL('image/png');
  511. EditorUi.prototype.useCanvasForExport = result != null && result.length > 6;
  512. }
  513. catch (e)
  514. {
  515. // ignore
  516. }
  517. };
  518. // Checks if SVG with foreignObject can be exported
  519. var svg = '<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="1px" height="1px" version="1.1"><foreignObject pointer-events="all" width="1" height="1"><div xmlns="http://www.w3.org/1999/xhtml"></div></foreignObject></svg>';
  520. img.src = 'data:image/svg+xml;base64,' + btoa(unescape(encodeURIComponent(svg)));
  521. }
  522. catch (e)
  523. {
  524. // ignore
  525. }
  526. // Checks for client-side JPG and WebP support
  527. try
  528. {
  529. var canvas = document.createElement('canvas');
  530. canvas.width = canvas.height = 1;
  531. var uri = canvas.toDataURL('image/jpeg');
  532. EditorUi.prototype.jpgSupported = (uri.match('image/jpeg') !== null);
  533. uri = canvas.toDataURL('image/webp');
  534. EditorUi.prototype.webpSupported = (uri.match('image/webp') !== null);
  535. }
  536. catch (e)
  537. {
  538. // ignore
  539. }
  540. })();
  541. /**
  542. * Hook for subclassers.
  543. */
  544. EditorUi.prototype.createButtonContainer = function()
  545. {
  546. var div = document.createElement('div');
  547. div.className = 'geButtonContainer';
  548. div.style.overflow = (urlParams['embed'] == '1') ? 'hidden' : '';
  549. return div;
  550. };
  551. /**
  552. * Hook for subclassers.
  553. */
  554. EditorUi.prototype.openLink = function(url, target, allowOpener)
  555. {
  556. // LATER: Replace this with direct calls to graph
  557. return this.editor.graph.openLink(url, target, allowOpener);
  558. };
  559. /**
  560. * Hook for subclassers.
  561. */
  562. EditorUi.prototype.showSplash = function(force) { };
  563. /**
  564. * Abstraction for local storage access.
  565. */
  566. EditorUi.prototype.getLocalData = function(key, fn)
  567. {
  568. fn(localStorage.getItem(key));
  569. };
  570. /**
  571. * Abstraction for local storage access.
  572. */
  573. EditorUi.prototype.setLocalData = function(key, data, fn)
  574. {
  575. localStorage.setItem(key, data);
  576. if (fn != null)
  577. {
  578. fn();
  579. }
  580. };
  581. /**
  582. * Abstraction for local storage access.
  583. */
  584. EditorUi.prototype.isLocked = function()
  585. {
  586. var file = this.getCurrentFile();
  587. return file != null && file.isLocked()
  588. };
  589. /**
  590. * Abstraction for local storage access.
  591. */
  592. EditorUi.prototype.removeLocalData = function(key, fn)
  593. {
  594. localStorage.removeItem(key)
  595. fn();
  596. };
  597. /**
  598. * Returns true if offline app, which isn't a defined thing
  599. */
  600. EditorUi.prototype.setShareCursorPosition = function(value)
  601. {
  602. this.shareCursorPosition = value;
  603. this.fireEvent(new mxEventObject('shareCursorPositionChanged'));
  604. };
  605. /**
  606. * Returns true if offline app, which isn't a defined thing
  607. */
  608. EditorUi.prototype.isShareCursorPosition = function()
  609. {
  610. return this.shareCursorPosition;
  611. };
  612. /**
  613. * Returns true if offline app, which isn't a defined thing
  614. */
  615. EditorUi.prototype.setShowRemoteCursors = function(value)
  616. {
  617. this.showRemoteCursors = value;
  618. this.fireEvent(new mxEventObject('showRemoteCursorsChanged'));
  619. };
  620. /**
  621. * Returns true if offline app, which isn't a defined thing
  622. */
  623. EditorUi.prototype.isShowRemoteCursors = function()
  624. {
  625. return this.showRemoteCursors;
  626. };
  627. /**
  628. * Returns true if offline app, which isn't a defined thing
  629. */
  630. EditorUi.prototype.setMathEnabled = function(value)
  631. {
  632. var graph = this.editor.graph;
  633. graph.mathEnabled = value;
  634. // Forces refresh of background image
  635. if (graph.view.backgroundImage != null)
  636. {
  637. graph.view.backgroundImage.destroy();
  638. graph.view.backgroundImage = null;
  639. }
  640. this.editor.updateGraphComponents();
  641. graph.refresh();
  642. graph.defaultMathEnabled = value;
  643. this.fireEvent(new mxEventObject('mathEnabledChanged'));
  644. };
  645. /**
  646. * Returns true if offline app, which isn't a defined thing
  647. */
  648. EditorUi.prototype.isMathEnabled = function(value)
  649. {
  650. return this.editor.graph.mathEnabled;
  651. };
  652. /**
  653. * Returns true if offline app, which isn't a defined thing
  654. */
  655. EditorUi.prototype.isStandaloneApp = function()
  656. {
  657. return mxClient.IS_CHROMEAPP || EditorUi.isElectronApp || this.isOfflineApp();
  658. };
  659. /**
  660. * Returns true if offline app, which isn't a defined thing
  661. */
  662. EditorUi.prototype.isOfflineApp = function()
  663. {
  664. return urlParams['offline'] == '1';
  665. };
  666. /**
  667. * Deprecated. Poorly defined, to be replaced with isExternalDataComms and other more granular flags.
  668. * Original idea was it returns true if no external comms allowed or possible
  669. */
  670. EditorUi.prototype.isOffline = function(ignoreStealth)
  671. {
  672. return this.isOfflineApp() || !navigator.onLine || (!ignoreStealth && (urlParams['stealth'] == '1' || urlParams['lockdown'] == '1'));
  673. };
  674. /**
  675. * Returns true if diagram data transmission other than save/load is allowed or possible..
  676. */
  677. EditorUi.prototype.isExternalDataComms = function()
  678. {
  679. return urlParams['offline'] != '1' && !this.isOffline() && !this.isOfflineApp();
  680. };
  681. /**
  682. * Translates this point by the given vector.
  683. *
  684. * @param {number} dx X-coordinate of the translation.
  685. * @param {number} dy Y-coordinate of the translation.
  686. */
  687. EditorUi.prototype.createSpinner = function(x, y, size)
  688. {
  689. var autoPosition = (x == null || y == null);
  690. size = (size != null) ? size : 24;
  691. var spinner = new Spinner({
  692. lines: 12, // The number of lines to draw
  693. length: size, // The length of each line
  694. width: Math.round(size / 3), // The line thickness
  695. radius: Math.round(size / 2), // The radius of the inner circle
  696. rotate: 0, // The rotation offset
  697. color: (Editor.isDarkMode()) ? '#c0c0c0' : '#000', // #rgb or #rrggbb
  698. speed: 1.5, // Rounds per second
  699. trail: 60, // Afterglow percentage
  700. shadow: false, // Whether to render a shadow
  701. hwaccel: false, // Whether to use hardware acceleration
  702. zIndex: 2e9 // The z-index (defaults to 2000000000)
  703. });
  704. // Extends spin method to include an optional label
  705. var defaultTimeout = this.timeout;
  706. var oldSpin = spinner.spin;
  707. var thread = null;
  708. var retry = null;
  709. var resume = mxUtils.bind(this, function(fn)
  710. {
  711. if (fn != null)
  712. {
  713. fn();
  714. }
  715. });
  716. spinner.spin = function(container, label, error, timeout)
  717. {
  718. timeout = (timeout != null) ? timeout : defaultTimeout;
  719. var result = false;
  720. if (!this.active)
  721. {
  722. var start = Date.now();
  723. if (error != null)
  724. {
  725. thread = window.setTimeout(function()
  726. {
  727. spinner.stop();
  728. thread = null;
  729. error({code: App.ERROR_TIMEOUT,
  730. message: mxResources.get('timeout'),
  731. retry: retry});
  732. }, timeout);
  733. }
  734. oldSpin.call(this, container);
  735. this.active = true;
  736. if (label != null)
  737. {
  738. if (autoPosition)
  739. {
  740. y = Math.max(document.body.clientHeight || 0, document.documentElement.clientHeight || 0) / 2;
  741. x = document.body.clientWidth / 2 - 2;
  742. }
  743. var status = document.createElement('div');
  744. status.className = 'geSpinnerStatus';
  745. status.style.position = 'absolute';
  746. status.style.whiteSpace = 'nowrap';
  747. status.style.background = '#4B4243';
  748. status.style.color = 'white';
  749. status.style.fontFamily = Editor.defaultHtmlFont;
  750. status.style.fontSize = '9pt';
  751. status.style.padding = '6px';
  752. status.style.paddingLeft = '10px';
  753. status.style.paddingRight = '10px';
  754. status.style.zIndex = 2e9;
  755. status.style.left = Math.max(0, x) + 'px';
  756. status.style.top = Math.max(0, y + 70) + 'px';
  757. mxUtils.setPrefixedStyle(status.style, 'borderRadius', '6px');
  758. mxUtils.setPrefixedStyle(status.style, 'transform', 'translate(-50%,-50%)');
  759. if (!Editor.isDarkMode())
  760. {
  761. mxUtils.setPrefixedStyle(status.style, 'boxShadow', '2px 2px 3px 0px #ddd');
  762. }
  763. if (label.substring(label.length - 3, label.length) != '...' &&
  764. label.charAt(label.length - 1) != '!')
  765. {
  766. label = label + '...';
  767. }
  768. status.innerHTML = mxUtils.htmlEntities(label);
  769. container.appendChild(status);
  770. spinner.status = status;
  771. }
  772. // Pause returns a function to resume the spinner
  773. this.pause = mxUtils.bind(this, function()
  774. {
  775. var fn = resume;
  776. if (this.active)
  777. {
  778. // Reduces timeout by used time
  779. timeout = Math.max(0, timeout - (Date.now() - start));
  780. fn = mxUtils.bind(this, function(continueFn)
  781. {
  782. this.spin(container, label, error, timeout);
  783. // Continue is called after spinner was paused and is
  784. // used as the new retry function from here on
  785. if (continueFn != null)
  786. {
  787. try
  788. {
  789. continueFn();
  790. retry = mxUtils.bind(this, function()
  791. {
  792. this.spin(container, label, error, timeout);
  793. try
  794. {
  795. continueFn();
  796. }
  797. catch (e)
  798. {
  799. if (error != null)
  800. {
  801. error(e);
  802. }
  803. }
  804. });
  805. }
  806. catch (e)
  807. {
  808. if (error != null)
  809. {
  810. error(e);
  811. }
  812. }
  813. }
  814. });
  815. }
  816. this.stop();
  817. return fn;
  818. });
  819. result = true;
  820. }
  821. return result;
  822. };
  823. // Extends stop method to remove the optional label
  824. var oldStop = spinner.stop;
  825. spinner.stop = function()
  826. {
  827. oldStop.call(this);
  828. if (this.active)
  829. {
  830. this.active = false;
  831. if (thread != null)
  832. {
  833. window.clearTimeout(thread);
  834. thread = null;
  835. }
  836. if (spinner.status != null && spinner.status.parentNode != null)
  837. {
  838. spinner.status.parentNode.removeChild(spinner.status);
  839. }
  840. spinner.status = null;
  841. }
  842. };
  843. spinner.pause = function()
  844. {
  845. return resume;
  846. };
  847. return spinner;
  848. };
  849. /**
  850. * Returns true if the given string contains a compatible graph model.
  851. */
  852. EditorUi.prototype.isCompatibleString = function(data)
  853. {
  854. try
  855. {
  856. var doc = mxUtils.parseXml(data);
  857. var node = this.editor.extractGraphModel(doc.documentElement, true);
  858. return node != null && node.getElementsByTagName('parsererror').length == 0;
  859. }
  860. catch (e)
  861. {
  862. // ignore
  863. }
  864. return false;
  865. };
  866. /**
  867. * Returns true if the given binary data is a Visio file.
  868. */
  869. EditorUi.isVisioFilename = function(filename)
  870. {
  871. return (/(\.v(dx|sdx?))($|\?)/i.test(filename) ||
  872. /(\.vs(x|sx?))($|\?)/i.test(filename));
  873. };
  874. /**
  875. * Returns true if the given binary data is a Visio file.
  876. */
  877. EditorUi.prototype.isVisioData = function(data)
  878. {
  879. return data.length > 8 && ((data.charCodeAt(0) == 0xD0 && data.charCodeAt(1) == 0xCF &&
  880. data.charCodeAt(2) == 0x11 && data.charCodeAt(3) == 0xE0 && data.charCodeAt(4) == 0xA1 && data.charCodeAt(5) == 0xB1 &&
  881. data.charCodeAt(6) == 0x1A && data.charCodeAt(7) == 0xE1) || (data.charCodeAt(0) == 0x50 && data.charCodeAt(1) == 0x4B &&
  882. data.charCodeAt(2) == 0x03 && data.charCodeAt(3) == 0x04) || (data.charCodeAt(0) == 0x50 && data.charCodeAt(1) == 0x4B &&
  883. data.charCodeAt(2) == 0x03 && data.charCodeAt(3) == 0x06));
  884. };
  885. /**
  886. * Returns true if the given binary data is a Visio file that requires remote conversion.
  887. * This code returns true for vss, vsd and vdx files.
  888. */
  889. EditorUi.prototype.isRemoteVisioData = function(data)
  890. {
  891. return data.length > 8 && ((data.charCodeAt(0) == 0xD0 && data.charCodeAt(1) == 0xCF &&
  892. data.charCodeAt(2) == 0x11 && data.charCodeAt(3) == 0xE0 && data.charCodeAt(4) == 0xA1 && data.charCodeAt(5) == 0xB1 &&
  893. data.charCodeAt(6) == 0x1A && data.charCodeAt(7) == 0xE1) || (data.charCodeAt(0) == 0x3C && data.charCodeAt(1) == 0x3F &&
  894. data.charCodeAt(2) == 0x78 && data.charCodeAt(3) == 0x6D && data.charCodeAt(3) == 0x6C));
  895. };
  896. /**
  897. * Adds keyboard shortcuts for page handling.
  898. */
  899. var editorUiCreateKeyHandler = EditorUi.prototype.createKeyHandler;
  900. EditorUi.prototype.createKeyHandler = function(editor)
  901. {
  902. var keyHandler = editorUiCreateKeyHandler.apply(this, arguments);
  903. if (!this.editor.chromeless || this.editor.editable)
  904. {
  905. var keyHandlerGetFunction = keyHandler.getFunction;
  906. var graph = this.editor.graph;
  907. var ui = this;
  908. keyHandler.getFunction = function(evt)
  909. {
  910. if (graph.isSelectionEmpty() && ui.pages != null && ui.pages.length > 0)
  911. {
  912. var idx = ui.getSelectedPageIndex();
  913. if (mxEvent.isShiftDown(evt))
  914. {
  915. if (evt.keyCode == 37)
  916. {
  917. return function()
  918. {
  919. if (idx > 0)
  920. {
  921. ui.movePage(idx, idx - 1);
  922. }
  923. };
  924. }
  925. else if (evt.keyCode == 38)
  926. {
  927. return function()
  928. {
  929. if (idx > 0)
  930. {
  931. ui.movePage(idx, 0);
  932. }
  933. };
  934. }
  935. else if (evt.keyCode == 39)
  936. {
  937. return function()
  938. {
  939. if (idx < ui.pages.length - 1)
  940. {
  941. ui.movePage(idx, idx + 1);
  942. }
  943. };
  944. }
  945. else if (evt.keyCode == 40)
  946. {
  947. return function()
  948. {
  949. if (idx < ui.pages.length - 1)
  950. {
  951. ui.movePage(idx, ui.pages.length - 1);
  952. }
  953. };
  954. }
  955. }
  956. else if (mxEvent.isControlDown(evt) || (mxClient.IS_MAC && mxEvent.isMetaDown(evt)))
  957. {
  958. if (evt.keyCode == 37)
  959. {
  960. return function()
  961. {
  962. if (idx > 0)
  963. {
  964. ui.selectNextPage(false);
  965. }
  966. };
  967. }
  968. else if (evt.keyCode == 38)
  969. {
  970. return function()
  971. {
  972. if (idx > 0)
  973. {
  974. ui.selectPage(ui.pages[0]);
  975. }
  976. };
  977. }
  978. else if (evt.keyCode == 39)
  979. {
  980. return function()
  981. {
  982. if (idx < ui.pages.length - 1)
  983. {
  984. ui.selectNextPage(true);
  985. }
  986. };
  987. }
  988. else if (evt.keyCode == 40)
  989. {
  990. return function()
  991. {
  992. if (idx < ui.pages.length - 1)
  993. {
  994. ui.selectPage(ui.pages[ui.pages.length - 1]);
  995. }
  996. };
  997. }
  998. }
  999. }
  1000. // Ignores normal keystrokes as shortcuts if cells are selected (eg. A/S/D/F)
  1001. if (evt.keyCode >= 65 && evt.keyCode <= 90 && !graph.isSelectionEmpty() &&
  1002. !mxEvent.isAltDown(evt) && !mxEvent.isShiftDown(evt) &&
  1003. !mxEvent.isControlDown(evt) && !(mxClient.IS_MAC && mxEvent.isMetaDown(evt)))
  1004. {
  1005. return null;
  1006. }
  1007. else
  1008. {
  1009. return keyHandlerGetFunction.apply(this, arguments);
  1010. }
  1011. };
  1012. }
  1013. return keyHandler;
  1014. };
  1015. /**
  1016. * Extracts the mxfile from the given HTML data from a data transfer event.
  1017. */
  1018. var editorUiExtractGraphModelFromHtml = EditorUi.prototype.extractGraphModelFromHtml;
  1019. EditorUi.prototype.extractGraphModelFromHtml = function(data)
  1020. {
  1021. var result = editorUiExtractGraphModelFromHtml.apply(this, arguments);
  1022. if (result == null)
  1023. {
  1024. try
  1025. {
  1026. var idx = data.indexOf('&lt;mxfile ');
  1027. if (idx >= 0)
  1028. {
  1029. var idx2 = data.lastIndexOf('&lt;/mxfile&gt;');
  1030. if (idx2 > idx)
  1031. {
  1032. result = data.substring(idx, idx2 + 15).replace(/&gt;/g, '>').
  1033. replace(/&lt;/g, '<').replace(/\\&quot;/g, '"').replace(/\n/g, '');
  1034. }
  1035. }
  1036. else
  1037. {
  1038. // Gets compressed data from mxgraph element in HTML document
  1039. var doc = mxUtils.parseXml(data);
  1040. var node = this.editor.extractGraphModel(doc.documentElement, this.pages != null ||
  1041. this.diagramContainer.style.visibility == 'hidden');
  1042. result = (node != null) ? mxUtils.getXml(node) : '';
  1043. }
  1044. }
  1045. catch (e)
  1046. {
  1047. // ignore
  1048. }
  1049. }
  1050. return result;
  1051. };
  1052. /**
  1053. * Workaround for malformed xhtml meta element bug 07.08.16. The trailing slash was missing causing
  1054. * reopen to fail trying to parse. Used in replaceFileData, setFileData and importFile.
  1055. */
  1056. EditorUi.prototype.validateFileData = function(data)
  1057. {
  1058. if (data != null && data.length > 0)
  1059. {
  1060. var index = data.indexOf('<meta charset="utf-8">');
  1061. if (index >= 0)
  1062. {
  1063. var replaceString = '<meta charset="utf-8"/>';
  1064. var replaceStrLen = replaceString.length;
  1065. data = data.slice(0, index) + replaceString + data.slice(index + replaceStrLen - 1, data.length);
  1066. }
  1067. data = Graph.zapGremlins(data);
  1068. }
  1069. return data;
  1070. };
  1071. /**
  1072. *
  1073. */
  1074. EditorUi.prototype.replaceFileData = function(data, patches)
  1075. {
  1076. EditorUi.debug('EditorUi.replaceFileData', [this],
  1077. 'data', [data], 'patches', patches);
  1078. data = this.validateFileData(data);
  1079. var node = (data != null && data.length > 0) ? mxUtils.parseXml(data).documentElement : null;
  1080. // Some nodes must be extracted here to find the mxfile node
  1081. // LATER: Remove duplicate call to extractGraphModel in overridden setGraphXml
  1082. var tmp = (node != null) ? this.editor.extractGraphModel(node, true) : null;
  1083. if (tmp != null)
  1084. {
  1085. node = tmp;
  1086. }
  1087. if (node != null)
  1088. {
  1089. var graph = this.editor.graph;
  1090. graph.model.beginUpdate();
  1091. try
  1092. {
  1093. var oldPages = (this.pages != null) ? this.pages.slice() : null;
  1094. var nodes = node.getElementsByTagName('diagram');
  1095. if (nodes.length > 1 || (nodes.length == 1 && nodes[0].hasAttribute('name')))
  1096. {
  1097. this.fileNode = node;
  1098. this.pages = (this.pages != null) ? this.pages : [];
  1099. // Wraps page nodes
  1100. for (var i = nodes.length - 1; i >= 0; i--)
  1101. {
  1102. var page = this.updatePageRoot(new DiagramPage(nodes[i]));
  1103. // Checks for invalid page names
  1104. if (page.getName() == null)
  1105. {
  1106. page.setName(mxResources.get('pageWithNumber', [i + 1]));
  1107. }
  1108. graph.model.execute(new ChangePage(this, page, (i == 0) ? page : null, 0));
  1109. }
  1110. }
  1111. else
  1112. {
  1113. // Creates tabbed file structure if enforced by URL
  1114. if (this.fileNode == null)
  1115. {
  1116. this.fileNode = node.ownerDocument.createElement('mxfile');
  1117. this.currentPage = new DiagramPage(node.ownerDocument.createElement('diagram'));
  1118. this.currentPage.setName(mxResources.get('pageWithNumber', [1]));
  1119. graph.model.execute(new ChangePage(this, this.currentPage, this.currentPage, 0));
  1120. }
  1121. // Avoids scroll offset when switching page
  1122. this.editor.setGraphXml(node);
  1123. // Avoids duplicate parsing of the XML stored in the node
  1124. if (this.currentPage != null)
  1125. {
  1126. this.currentPage.root = this.editor.graph.model.root;
  1127. graph.model.execute(new ChangePage(this, this.currentPage, this.currentPage, 0));
  1128. }
  1129. }
  1130. // Removes old pages
  1131. if (oldPages != null)
  1132. {
  1133. for (var i = 0; i < oldPages.length; i++)
  1134. {
  1135. graph.model.execute(new ChangePage(this, oldPages[i], null));
  1136. }
  1137. }
  1138. // Updates internal sync state for current file
  1139. var file = this.getCurrentFile();
  1140. if (file != null)
  1141. {
  1142. file.fileReplaced(patches);
  1143. }
  1144. }
  1145. finally
  1146. {
  1147. graph.model.endUpdate();
  1148. }
  1149. }
  1150. };
  1151. /**
  1152. * Translates this point by the given vector.
  1153. *
  1154. * @param {number} dx X-coordinate of the translation.
  1155. * @param {number} dy Y-coordinate of the translation.
  1156. */
  1157. EditorUi.prototype.createFileData = function(node, graph, file, url, forceXml, forceSvg, forceHtml,
  1158. embeddedCallback, ignoreSelection, compact, uncompressed, scale, border)
  1159. {
  1160. graph = (graph != null) ? graph : this.editor.graph;
  1161. forceXml = (forceXml != null) ? forceXml : false;
  1162. ignoreSelection = (ignoreSelection != null) ? ignoreSelection : true;
  1163. uncompressed = (uncompressed != null) ? uncompressed : !Editor.defaultCompressed;
  1164. var editLink = null;
  1165. var redirect = null;
  1166. if (file == null || file.getMode() == App.MODE_DEVICE || file.getMode() == App.MODE_BROWSER)
  1167. {
  1168. editLink = '_blank';
  1169. }
  1170. else
  1171. {
  1172. editLink = url;
  1173. redirect = editLink;
  1174. }
  1175. if (node == null)
  1176. {
  1177. return '';
  1178. }
  1179. else
  1180. {
  1181. var fileNode = node;
  1182. // Ignores case for possible HTML or XML nodes
  1183. if (fileNode.nodeName.toLowerCase() != 'mxfile')
  1184. {
  1185. if (uncompressed)
  1186. {
  1187. var diagramNode = node.ownerDocument.createElement('diagram');
  1188. diagramNode.setAttribute('name', mxResources.get('pageWithNumber', [1]));
  1189. diagramNode.setAttribute('id', Editor.guid());
  1190. diagramNode.appendChild(node);
  1191. fileNode = node.ownerDocument.createElement('mxfile');
  1192. fileNode.appendChild(diagramNode);
  1193. }
  1194. else
  1195. {
  1196. // Removes control chars in input for correct roundtrip check
  1197. var text = Graph.zapGremlins(mxUtils.getXml(node));
  1198. var data = Graph.compress(text);
  1199. // Fallback to plain XML for invalid compression
  1200. // TODO: Remove this fallback with active pages
  1201. if (Graph.decompress(data) != text)
  1202. {
  1203. return text;
  1204. }
  1205. else
  1206. {
  1207. var diagramNode = node.ownerDocument.createElement('diagram');
  1208. diagramNode.setAttribute('name', mxResources.get('pageWithNumber', [1]));
  1209. diagramNode.setAttribute('id', Editor.guid());
  1210. mxUtils.setTextContent(diagramNode, data);
  1211. fileNode = node.ownerDocument.createElement('mxfile');
  1212. fileNode.appendChild(diagramNode);
  1213. }
  1214. }
  1215. }
  1216. if (!compact)
  1217. {
  1218. // Removes old metadata
  1219. fileNode.removeAttribute('userAgent');
  1220. fileNode.removeAttribute('modified');
  1221. fileNode.removeAttribute('version');
  1222. fileNode.removeAttribute('editor');
  1223. fileNode.removeAttribute('pages');
  1224. fileNode.removeAttribute('type');
  1225. fileNode.removeAttribute('etag');
  1226. if (mxClient.IS_CHROMEAPP)
  1227. {
  1228. fileNode.setAttribute('host', 'Chrome');
  1229. }
  1230. else if (EditorUi.isElectronApp)
  1231. {
  1232. fileNode.setAttribute('host', 'Electron');
  1233. }
  1234. else
  1235. {
  1236. fileNode.setAttribute('host', window.location.hostname);
  1237. }
  1238. // Adds new metadata
  1239. fileNode.setAttribute('agent', (navigator.userAgent != null) ?
  1240. navigator.userAgent : navigator.appVersion);
  1241. fileNode.setAttribute('version', EditorUi.VERSION);
  1242. if (fileNode.getElementsByTagName('diagram').length > 1 && this.pages != null)
  1243. {
  1244. fileNode.setAttribute('pages', this.pages.length);
  1245. }
  1246. }
  1247. else
  1248. {
  1249. fileNode = fileNode.cloneNode(true);
  1250. fileNode.removeAttribute('modified');
  1251. fileNode.removeAttribute('host');
  1252. fileNode.removeAttribute('agent');
  1253. fileNode.removeAttribute('etag');
  1254. fileNode.removeAttribute('userAgent');
  1255. fileNode.removeAttribute('version');
  1256. fileNode.removeAttribute('editor');
  1257. fileNode.removeAttribute('type');
  1258. }
  1259. if (scale != null)
  1260. {
  1261. fileNode = fileNode.cloneNode(true);
  1262. fileNode.setAttribute('scale', scale);
  1263. }
  1264. if (border != null)
  1265. {
  1266. fileNode = fileNode.cloneNode(true);
  1267. fileNode.setAttribute('border', border);
  1268. }
  1269. var xml = (uncompressed) ? mxUtils.getPrettyXml(fileNode) : mxUtils.getXml(fileNode);
  1270. // Writes the file as an embedded HTML file
  1271. if (!forceSvg && !forceXml && (forceHtml || (file != null && /(\.html)$/i.test(file.getTitle()))))
  1272. {
  1273. xml = this.getHtml2(mxUtils.getXml(fileNode), graph, (file != null) ? file.getTitle() : null, editLink, redirect);
  1274. }
  1275. // Maps the XML data to the content attribute in the SVG node
  1276. else if (forceSvg || (!forceXml && file != null && /(\.svg)$/i.test(file.getTitle())))
  1277. {
  1278. if (file != null && (file.getMode() == App.MODE_DEVICE || file.getMode() == App.MODE_BROWSER))
  1279. {
  1280. url = null;
  1281. }
  1282. var props = this.getSvgFileProperties(fileNode);
  1283. xml = this.getEmbeddedSvg(xml, graph, url, null, embeddedCallback,
  1284. ignoreSelection, redirect, null, null, props.scale, props.border);
  1285. }
  1286. return xml;
  1287. }
  1288. };
  1289. /**
  1290. * Translates this point by the given vector.
  1291. *
  1292. * @param {number} dx X-coordinate of the translation.
  1293. * @param {number} dy Y-coordinate of the translation.
  1294. */
  1295. EditorUi.prototype.getXmlFileData = function(ignoreSelection, currentPage, uncompressed, resolveReferences)
  1296. {
  1297. ignoreSelection = (ignoreSelection != null) ? ignoreSelection : true;
  1298. currentPage = (currentPage != null) ? currentPage : false;
  1299. uncompressed = (uncompressed != null) ? uncompressed : !Editor.defaultCompressed;
  1300. // Generats graph model XML node for single page export
  1301. var node = this.editor.getGraphXml(ignoreSelection, resolveReferences);
  1302. if (ignoreSelection && this.fileNode != null && this.currentPage != null)
  1303. {
  1304. // Updates current page XML if selection is ignored
  1305. EditorUi.removeChildNodes(this.currentPage.node);
  1306. this.currentPage.node.appendChild(node);
  1307. // Creates a clone of the file node for processing
  1308. node = this.fileNode.cloneNode(false);
  1309. // Appends the node of the page and applies compression
  1310. function appendPage(pageNode)
  1311. {
  1312. var models = pageNode.getElementsByTagName('mxGraphModel');
  1313. var modelNode = (models.length > 0) ? models[0] : null;
  1314. var clone = pageNode;
  1315. if (modelNode == null && uncompressed)
  1316. {
  1317. var text = mxUtils.getNodeValue(pageNode);
  1318. if (text.length > 0)
  1319. {
  1320. var tmp = Graph.decompress(text);
  1321. if (tmp != null && tmp.length > 0)
  1322. {
  1323. clone = pageNode.cloneNode(false);
  1324. clone.appendChild(mxUtils.parseXml(tmp).documentElement);
  1325. }
  1326. }
  1327. }
  1328. else if (modelNode != null && !uncompressed)
  1329. {
  1330. clone = pageNode.cloneNode(false);
  1331. mxUtils.setTextContent(clone, Graph.compressNode(modelNode));
  1332. }
  1333. else
  1334. {
  1335. clone = pageNode.cloneNode(true);
  1336. }
  1337. node.appendChild(clone);
  1338. };
  1339. if (currentPage)
  1340. {
  1341. appendPage(this.currentPage.node);
  1342. }
  1343. else
  1344. {
  1345. // Restores order of pages
  1346. for (var i = 0; i < this.pages.length; i++)
  1347. {
  1348. var page = this.pages[i];
  1349. var currNode = page.node;
  1350. if (page != this.currentPage)
  1351. {
  1352. if (page.needsUpdate)
  1353. {
  1354. var enc = new mxCodec(mxUtils.createXmlDocument());
  1355. var temp = enc.encode(new mxGraphModel(page.root));
  1356. this.editor.graph.saveViewState(page.viewState,
  1357. temp, null, resolveReferences);
  1358. EditorUi.removeChildNodes(currNode);
  1359. currNode.appendChild(temp);
  1360. // Marks the page as up-to-date
  1361. delete page.needsUpdate;
  1362. }
  1363. else if (resolveReferences)
  1364. {
  1365. this.updatePageRoot(page);
  1366. // Forces update of background page image in offscreen page
  1367. if (page.viewState.backgroundImage != null)
  1368. {
  1369. if (page.viewState.backgroundImage.originalSrc != null)
  1370. {
  1371. page.viewState.backgroundImage = this.createImageForPageLink(
  1372. page.viewState.backgroundImage.originalSrc, page);
  1373. }
  1374. else if (Graph.isPageLink(page.viewState.backgroundImage.src))
  1375. {
  1376. page.viewState.backgroundImage = this.createImageForPageLink(
  1377. page.viewState.backgroundImage.src, page);
  1378. }
  1379. }
  1380. // Updates the page node
  1381. if (page.viewState.backgroundImage != null &&
  1382. page.viewState.backgroundImage.originalSrc != null)
  1383. {
  1384. var enc = new mxCodec(mxUtils.createXmlDocument());
  1385. var temp = enc.encode(new mxGraphModel(page.root));
  1386. this.editor.graph.saveViewState(page.viewState,
  1387. temp, null, resolveReferences);
  1388. currNode = currNode.cloneNode(false);
  1389. currNode.appendChild(temp);
  1390. }
  1391. }
  1392. }
  1393. appendPage(currNode);
  1394. }
  1395. }
  1396. }
  1397. return node;
  1398. };
  1399. /**
  1400. * Removes any values, styles and geometries from the given XML node.
  1401. */
  1402. EditorUi.prototype.anonymizeString = function(text, zeros)
  1403. {
  1404. var result = [];
  1405. for (var i = 0; i < text.length; i++)
  1406. {
  1407. var c = text.charAt(i);
  1408. if (EditorUi.ignoredAnonymizedChars.indexOf(c) >= 0)
  1409. {
  1410. result.push(c);
  1411. }
  1412. else if (!isNaN(parseInt(c)))
  1413. {
  1414. result.push((zeros) ? '0' : Math.round(Math.random() * 9));
  1415. }
  1416. else if (c.toLowerCase() != c)
  1417. {
  1418. result.push(String.fromCharCode(65 + Math.round(Math.random() * 25)));
  1419. }
  1420. else if (c.toUpperCase() != c)
  1421. {
  1422. result.push(String.fromCharCode(97 + Math.round(Math.random() * 25)));
  1423. }
  1424. else if (/\s/.test(c))
  1425. {
  1426. /* any whitespace */
  1427. result.push(' ');
  1428. }
  1429. else
  1430. {
  1431. result.push('?');
  1432. }
  1433. }
  1434. return result.join('');
  1435. };
  1436. /**
  1437. * Removes any values, styles and geometries from the given XML node.
  1438. */
  1439. EditorUi.prototype.anonymizePatch = function(patch)
  1440. {
  1441. if (patch[EditorUi.DIFF_INSERT] != null)
  1442. {
  1443. for (var i = 0; i < patch[EditorUi.DIFF_INSERT].length; i++)
  1444. {
  1445. try
  1446. {
  1447. var data = patch[EditorUi.DIFF_INSERT][i].data;
  1448. var doc = mxUtils.parseXml(data);
  1449. var clone = doc.documentElement.cloneNode(false);
  1450. if (clone.getAttribute('name') != null)
  1451. {
  1452. clone.setAttribute('name', this.anonymizeString(clone.getAttribute('name')));
  1453. }
  1454. patch[EditorUi.DIFF_INSERT][i].data = mxUtils.getXml(clone);
  1455. }
  1456. catch (e)
  1457. {
  1458. patch[EditorUi.DIFF_INSERT][i].data = e.message;
  1459. }
  1460. }
  1461. }
  1462. if (patch[EditorUi.DIFF_UPDATE] != null)
  1463. {
  1464. for (var pageId in patch[EditorUi.DIFF_UPDATE])
  1465. {
  1466. var diff = patch[EditorUi.DIFF_UPDATE][pageId];
  1467. if (diff.name != null)
  1468. {
  1469. diff.name = this.anonymizeString(diff.name);
  1470. }
  1471. if (diff.cells != null)
  1472. {
  1473. var anonymizeCellDiffs = mxUtils.bind(this, function(key)
  1474. {
  1475. var cellDiffs = diff.cells[key];
  1476. if (cellDiffs != null)
  1477. {
  1478. for (var cellId in cellDiffs)
  1479. {
  1480. if (cellDiffs[cellId].value != null)
  1481. {
  1482. cellDiffs[cellId].value = '[' +
  1483. cellDiffs[cellId].value.length + ']';
  1484. }
  1485. if (cellDiffs[cellId].xmlValue != null)
  1486. {
  1487. cellDiffs[cellId].xmlValue = '[' +
  1488. cellDiffs[cellId].xmlValue.length + ']';
  1489. }
  1490. if (cellDiffs[cellId].style != null)
  1491. {
  1492. cellDiffs[cellId].style = '[' +
  1493. cellDiffs[cellId].style.length + ']';
  1494. }
  1495. if (mxUtils.isEmptyObject(cellDiffs[cellId]))
  1496. {
  1497. delete cellDiffs[cellId];
  1498. }
  1499. }
  1500. if (mxUtils.isEmptyObject(cellDiffs))
  1501. {
  1502. delete diff.cells[key];
  1503. }
  1504. }
  1505. });
  1506. anonymizeCellDiffs(EditorUi.DIFF_INSERT);
  1507. anonymizeCellDiffs(EditorUi.DIFF_UPDATE);
  1508. if (mxUtils.isEmptyObject(diff.cells))
  1509. {
  1510. delete diff.cells;
  1511. }
  1512. }
  1513. if (mxUtils.isEmptyObject(diff))
  1514. {
  1515. delete patch[EditorUi.DIFF_UPDATE][pageId];
  1516. }
  1517. }
  1518. if (mxUtils.isEmptyObject(patch[EditorUi.DIFF_UPDATE]))
  1519. {
  1520. delete patch[EditorUi.DIFF_UPDATE];
  1521. }
  1522. }
  1523. return patch;
  1524. };
  1525. /**
  1526. * Removes any values, styles and geometries from the given XML node.
  1527. */
  1528. EditorUi.prototype.anonymizeAttributes = function(node, zeros)
  1529. {
  1530. if (node.attributes != null)
  1531. {
  1532. for (var i = 0; i < node.attributes.length; i++)
  1533. {
  1534. if (node.attributes[i].name != 'as')
  1535. {
  1536. node.setAttribute(node.attributes[i].name,
  1537. this.anonymizeString(node.attributes[i].value, zeros));
  1538. }
  1539. }
  1540. }
  1541. if (node.childNodes != null)
  1542. {
  1543. for (var i = 0; i < node.childNodes.length; i++)
  1544. {
  1545. this.anonymizeAttributes(node.childNodes[i], zeros);
  1546. }
  1547. }
  1548. };
  1549. /**
  1550. * Removes any values, styles and geometries from the given XML node.
  1551. */
  1552. EditorUi.prototype.anonymizeNode = function(node, zeros)
  1553. {
  1554. var nodes = node.getElementsByTagName('mxCell');
  1555. for (var i = 0; i < nodes.length; i++)
  1556. {
  1557. if (nodes[i].getAttribute('value') != null)
  1558. {
  1559. nodes[i].setAttribute('value', '[' + nodes[i].getAttribute('value').length + ']');
  1560. }
  1561. if (nodes[i].getAttribute('xmlValue') != null)
  1562. {
  1563. nodes[i].setAttribute('xmlValue', '[' + nodes[i].getAttribute('xmlValue').length + ']');
  1564. }
  1565. if (nodes[i].getAttribute('style') != null)
  1566. {
  1567. nodes[i].setAttribute('style', '[' + nodes[i].getAttribute('style').length + ']');
  1568. }
  1569. if (nodes[i].parentNode != null && nodes[i].parentNode.nodeName != 'root' &&
  1570. nodes[i].parentNode.parentNode != null)
  1571. {
  1572. nodes[i].setAttribute('id', nodes[i].parentNode.getAttribute('id'));
  1573. nodes[i].parentNode.parentNode.replaceChild(nodes[i], nodes[i].parentNode);
  1574. }
  1575. }
  1576. return node;
  1577. };
  1578. /**
  1579. * Translates this point by the given vector.
  1580. *
  1581. * @param {number} dx X-coordinate of the translation.
  1582. * @param {number} dy Y-coordinate of the translation.
  1583. */
  1584. EditorUi.prototype.synchronizeCurrentFile = function(forceReload)
  1585. {
  1586. var currentFile = this.getCurrentFile();
  1587. if (currentFile != null)
  1588. {
  1589. if (currentFile.savingFile)
  1590. {
  1591. this.handleError({message: mxResources.get('busy')});
  1592. }
  1593. else if (!forceReload && currentFile.invalidChecksum)
  1594. {
  1595. currentFile.handleFileError(null, true);
  1596. }
  1597. else if (this.spinner.spin(document.body, mxResources.get('updatingDocument')))
  1598. {
  1599. currentFile.clearAutosave();
  1600. this.editor.setStatus('');
  1601. if (forceReload)
  1602. {
  1603. currentFile.reloadFile(mxUtils.bind(this, function()
  1604. {
  1605. this.spinner.stop();
  1606. currentFile.handleFileSuccess(DrawioFile.SYNC == 'manual');
  1607. }), mxUtils.bind(this, function(err)
  1608. {
  1609. this.spinner.stop();
  1610. currentFile.handleFileError(err, true);
  1611. }));
  1612. }
  1613. else
  1614. {
  1615. currentFile.synchronizeFile(mxUtils.bind(this, function()
  1616. {
  1617. currentFile.handleFileSuccess(DrawioFile.SYNC == 'manual');
  1618. }), mxUtils.bind(this, function(err)
  1619. {
  1620. currentFile.handleFileError(err, true);
  1621. }));
  1622. }
  1623. }
  1624. }
  1625. };
  1626. /**
  1627. * Translates this point by the given vector.
  1628. *
  1629. * @param {number} dx X-coordinate of the translation.
  1630. * @param {number} dy Y-coordinate of the translation.
  1631. */
  1632. EditorUi.prototype.getFileData = function(forceXml, forceSvg, forceHtml, embeddedCallback,
  1633. ignoreSelection, currentPage, node, compact, file, uncompressed, resolveReferences,
  1634. scale, border)
  1635. {
  1636. ignoreSelection = (ignoreSelection != null) ? ignoreSelection : true;
  1637. currentPage = (currentPage != null) ? currentPage : false;
  1638. uncompressed = (uncompressed != null) ? uncompressed : !Editor.defaultCompressed;
  1639. var graph = this.editor.graph;
  1640. // Forces compression of embedded XML
  1641. if (forceSvg || (!forceXml && file != null && /(\.svg)$/i.test(file.getTitle())))
  1642. {
  1643. var darkTheme = graph.themes != null && graph.defaultThemeName == 'darkTheme';
  1644. // Exports SVG for first page while other page is visible by creating a graph
  1645. // LATER: Add caching for the graph or SVG while not on first page
  1646. // Dark mode requires a refresh that would destroy all handlers
  1647. // LATER: Use dark theme here to bypass refresh
  1648. if (darkTheme || (this.pages != null && this.currentPage != this.pages[0]))
  1649. {
  1650. var graphGetGlobalVariable = graph.getGlobalVariable;
  1651. graph = this.createTemporaryGraph(darkTheme ?
  1652. graph.getDefaultStylesheet() :
  1653. graph.getStylesheet());
  1654. graph.setBackgroundImage = this.editor.graph.setBackgroundImage;
  1655. graph.background = this.editor.graph.background;
  1656. var page = (this.pages != null) ? this.pages[0] : null;;
  1657. if (page == null || this.currentPage == page)
  1658. {
  1659. graph.setBackgroundImage(this.editor.graph.backgroundImage);
  1660. }
  1661. else if (page.viewState != null && page.viewState != null)
  1662. {
  1663. graph.setBackgroundImage(page.viewState.backgroundImage);
  1664. }
  1665. graph.getGlobalVariable = function(name)
  1666. {
  1667. if (name == 'page' && page != null)
  1668. {
  1669. return page.getName();
  1670. }
  1671. else if (name == 'pagenumber')
  1672. {
  1673. return 1;
  1674. }
  1675. return graphGetGlobalVariable.apply(this, arguments);
  1676. };
  1677. document.body.appendChild(graph.container);
  1678. if (page != null)
  1679. {
  1680. graph.model.setRoot(page.root);
  1681. }
  1682. }
  1683. }
  1684. node = (node != null) ? node : this.getXmlFileData(ignoreSelection,
  1685. currentPage, uncompressed, resolveReferences);
  1686. file = (file != null) ? file : this.getCurrentFile();
  1687. var result = this.createFileData(node, graph, file, window.location.href,
  1688. forceXml, forceSvg, forceHtml, embeddedCallback, ignoreSelection, compact,
  1689. uncompressed, scale, border);
  1690. // Removes temporary graph from DOM
  1691. if (graph != this.editor.graph)
  1692. {
  1693. graph.container.parentNode.removeChild(graph.container);
  1694. }
  1695. return result;
  1696. };
  1697. /**
  1698. *
  1699. */
  1700. EditorUi.prototype.getHtml = function(node, graph, title, editLink, redirect, ignoreSelection)
  1701. {
  1702. ignoreSelection = (ignoreSelection != null) ? ignoreSelection : true;
  1703. var bg = null;
  1704. var js = EditorUi.drawHost + '/js/embed-static.min.js';
  1705. // LATER: Merge common code with EmbedDialog
  1706. if (graph != null)
  1707. {
  1708. var bounds = (ignoreSelection) ? graph.getGraphBounds() :
  1709. graph.getBoundingBox(graph.getSelectionCells());
  1710. var scale = graph.view.scale;
  1711. var x0 = Math.floor(bounds.x / scale - graph.view.translate.x);
  1712. var y0 = Math.floor(bounds.y / scale - graph.view.translate.y);
  1713. bg = graph.background;
  1714. // Embed script only used if no redirect
  1715. if (redirect == null)
  1716. {
  1717. var s = this.getBasenames().join(';');
  1718. if (s.length > 0)
  1719. {
  1720. js = EditorUi.drawHost + '/embed.js?s=' + s;
  1721. }
  1722. }
  1723. // Adds embed attributes
  1724. node.setAttribute('x0', x0);
  1725. node.setAttribute('y0', y0);
  1726. }
  1727. if (node != null)
  1728. {
  1729. node.setAttribute('pan', '1');
  1730. node.setAttribute('zoom', '1');
  1731. node.setAttribute('resize', '0');
  1732. node.setAttribute('fit', '0');
  1733. node.setAttribute('border', '20');
  1734. // Hidden attributes
  1735. node.setAttribute('links', '1');
  1736. if (editLink != null)
  1737. {
  1738. node.setAttribute('edit', editLink);
  1739. }
  1740. }
  1741. // Makes XHTML compatible
  1742. if (redirect != null)
  1743. {
  1744. redirect = redirect.replace(/&/g, '&amp;');
  1745. }
  1746. // Removes control chars in input for correct roundtrip check
  1747. var text = (node != null) ? Graph.zapGremlins(mxUtils.getXml(node)) : '';
  1748. // Double compression for mxfile not fixed since it may cause imcompatibilites with
  1749. // embed clients that rely on this format. HTML files and export use getHtml2.
  1750. var data = Graph.compress(text);
  1751. // Fallback to URI encoded XML for invalid compression
  1752. if (Graph.decompress(data) != text)
  1753. {
  1754. data = encodeURIComponent(text);
  1755. }
  1756. var style = 'position:relative;overflow:auto;width:100%;';
  1757. return ((redirect == null) ? '<!--[if IE]><meta http-equiv="X-UA-Compatible" content="IE=5,IE=9" ><![endif]-->\n' : '') +
  1758. '<!DOCTYPE html>\n<html' + ((redirect != null) ? ' xmlns="http://www.w3.org/1999/xhtml">' : '>') +
  1759. '\n<head>\n' + ((redirect == null) ? ((title != null) ? '<title>' + mxUtils.htmlEntities(title) +
  1760. '</title>\n' : '') : '<title>draw.io</title>\n') +
  1761. ((redirect != null) ? '<meta http-equiv="refresh" content="0;URL=\'' + redirect + '\'"/>\n' : '') +
  1762. '</head>\n<body' +
  1763. (((redirect == null && bg != null && bg != mxConstants.NONE) ? ' style="background-color:' + bg + ';">' : '>')) +
  1764. '\n<div class="mxgraph" style="' + style + '">\n' +
  1765. '<div style="width:1px;height:1px;overflow:hidden;">' + data + '</div>\n</div>\n' +
  1766. ((redirect == null) ? '<script type="text/javascript" src="' + js + '"></script>' :
  1767. '<a style="position:absolute;top:50%;left:50%;margin-top:-128px;margin-left:-64px;" ' +
  1768. 'href="' + redirect + '" target="_blank"><img border="0" ' +
  1769. 'src="' + EditorUi.drawHost + '/images/drawlogo128.png"/></a>') +
  1770. '\n</body>\n</html>\n';
  1771. };
  1772. /**
  1773. * Same as above but using the new embed code.
  1774. */
  1775. EditorUi.prototype.getHtml2 = function(xml, graph, title, editLink, redirect)
  1776. {
  1777. var js = window.DRAWIO_VIEWER_URL || EditorUi.drawHost + '/js/viewer-static.min.js';
  1778. // Makes XHTML compatible
  1779. if (redirect != null)
  1780. {
  1781. redirect = redirect.replace(/&/g, '&amp;');
  1782. }
  1783. var data = {highlight: '#0000ff', nav: this.editor.graph.foldingEnabled, resize: true,
  1784. xml: Graph.zapGremlins(xml), toolbar: 'pages zoom layers lightbox'};
  1785. if (this.pages != null && this.currentPage != null)
  1786. {
  1787. data.page = mxUtils.indexOf(this.pages, this.currentPage);
  1788. }
  1789. var style = 'max-width:100%;border:1px solid transparent;';
  1790. return ((redirect == null) ? '<!--[if IE]><meta http-equiv="X-UA-Compatible" content="IE=5,IE=9" ><![endif]-->\n' : '') +
  1791. '<!DOCTYPE html>\n<html' + ((redirect != null) ? ' xmlns="http://www.w3.org/1999/xhtml">' : '>') +
  1792. '\n<head>\n' + ((redirect == null) ? ((title != null) ? '<title>' + mxUtils.htmlEntities(title) +
  1793. '</title>\n' : '') : '<title>draw.io</title>\n') +
  1794. ((redirect != null) ? '<meta http-equiv="refresh" content="0;URL=\'' + redirect + '\'"/>\n' : '') +
  1795. '<meta charset="utf-8"/>\n</head>\n<body>' +
  1796. '\n<div class="mxgraph" style="' + style + '" data-mxgraph="' + mxUtils.htmlEntities(JSON.stringify(data)) + '"></div>\n' +
  1797. ((redirect == null) ? '<script type="text/javascript" src="' + js + '"></script>' :
  1798. '<a style="position:absolute;top:50%;left:50%;margin-top:-128px;margin-left:-64px;" ' +
  1799. 'href="' + redirect + '" target="_blank"><img border="0" ' +
  1800. 'src="' + EditorUi.drawHost + '/images/drawlogo128.png"/></a>') +
  1801. '\n</body>\n</html>\n';
  1802. };
  1803. /**
  1804. *
  1805. */
  1806. EditorUi.prototype.setFileData = function(data)
  1807. {
  1808. data = this.validateFileData(data);
  1809. this.currentPage = null;
  1810. this.fileNode = null;
  1811. this.pages = null;
  1812. var node = (data != null && data.length > 0) ? mxUtils.parseXml(data).documentElement : null;
  1813. // Checks for parser errors
  1814. var cause = Editor.extractParserError(node, mxResources.get('invalidOrMissingFile'));
  1815. if (cause)
  1816. {
  1817. EditorUi.debug('EditorUi.setFileData ParserError', [this],
  1818. 'data', [data], 'node', [node], 'cause', [cause]);
  1819. throw new Error(mxResources.get('notADiagramFile') + ' (' + cause + ')');
  1820. }
  1821. else
  1822. {
  1823. // Some nodes must be extracted here to find the mxfile node
  1824. // LATER: Remove duplicate call to extractGraphModel in overridden setGraphXml
  1825. var tmp = (node != null) ? this.editor.extractGraphModel(node, true) : null;
  1826. if (tmp != null)
  1827. {
  1828. node = tmp;
  1829. }
  1830. if (node != null && node.nodeName == 'mxfile')
  1831. {
  1832. var nodes = node.getElementsByTagName('diagram');
  1833. // Checks for duplicate page IDs
  1834. var pages = {};
  1835. if (nodes.length > 0)
  1836. {
  1837. var hashObj = this.getHashObject();
  1838. var selectedPage = null;
  1839. this.fileNode = node;
  1840. this.pages = [];
  1841. // Wraps page nodes
  1842. for (var i = 0; i < nodes.length; i++)
  1843. {
  1844. // Adds page ID based on page order to match
  1845. // remote IDs given if IDs are missing here
  1846. if (nodes[i].getAttribute('id') == null)
  1847. {
  1848. nodes[i].setAttribute('id', i);
  1849. }
  1850. var page = new DiagramPage(nodes[i]);
  1851. // Checks for invalid page names
  1852. if (page.getName() == null)
  1853. {
  1854. page.setName(mxResources.get('pageWithNumber', [i + 1]));
  1855. }
  1856. this.pages.push(page);
  1857. if ((hashObj.pageId == null && urlParams['page-id'] != null &&
  1858. page.getId() == urlParams['page-id']) ||
  1859. (hashObj.pageId != null && page.getId() == hashObj.pageId))
  1860. {
  1861. selectedPage = page;
  1862. }
  1863. if (pages[page.getId()] == null)
  1864. {
  1865. pages[page.getId()] = page;
  1866. }
  1867. else
  1868. {
  1869. throw new Error(page.getId() + ': Duplicate page ID');
  1870. }
  1871. }
  1872. this.currentPage = (selectedPage != null) ? selectedPage :
  1873. this.pages[Math.max(0, Math.min(this.pages.length - 1, urlParams['page'] || 0))];
  1874. node = this.currentPage.node;
  1875. }
  1876. }
  1877. // Creates tabbed file structure if enforced by URL
  1878. if (this.fileNode == null && node != null)
  1879. {
  1880. this.fileNode = node.ownerDocument.createElement('mxfile');
  1881. this.currentPage = new DiagramPage(node.ownerDocument.createElement('diagram'));
  1882. this.currentPage.setName(mxResources.get('pageWithNumber', [1]));
  1883. this.pages = [this.currentPage];
  1884. }
  1885. // Avoids scroll offset when switching page
  1886. this.editor.setGraphXml(node);
  1887. // Avoids duplicate parsing of the XML stored in the node
  1888. if (this.currentPage != null)
  1889. {
  1890. this.currentPage.root = this.editor.graph.model.root;
  1891. // Resets initial modified state
  1892. this.currentPage.setDiagramModified(false);
  1893. // Scrolls to current page
  1894. this.scrollToPage();
  1895. }
  1896. if (urlParams['layer-ids'] != null)
  1897. {
  1898. try
  1899. {
  1900. var layerIds = decodeURIComponent(urlParams['layer-ids']).split(' ');
  1901. var layerIdsMap = {};
  1902. for (var i = 0; i < layerIds.length; i++)
  1903. {
  1904. layerIdsMap[layerIds[i]] = true;
  1905. }
  1906. var model = this.editor.graph.getModel();
  1907. var children = model.getChildren(model.root);
  1908. // handle layers visibility
  1909. for (var i = 0; i < children.length; i++)
  1910. {
  1911. var child = children[i];
  1912. model.setVisible(child, layerIdsMap[child.id] || false);
  1913. }
  1914. }
  1915. catch(e){} //ignore
  1916. }
  1917. }
  1918. };
  1919. /**
  1920. * Translates this point by the given vector.
  1921. *
  1922. * @param {number} dx X-coordinate of the translation.
  1923. * @param {number} dy Y-coordinate of the translation.
  1924. */
  1925. EditorUi.prototype.getBaseFilename = function(ignorePageName)
  1926. {
  1927. var file = this.getCurrentFile();
  1928. var basename = (file != null && file.getTitle() != null) ? file.getTitle() : this.defaultFilename;
  1929. if (/(\.xml)$/i.test(basename) || /(\.html)$/i.test(basename) ||
  1930. /(\.svg)$/i.test(basename) || /(\.png)$/i.test(basename))
  1931. {
  1932. basename = basename.substring(0, basename.lastIndexOf('.'));
  1933. }
  1934. if (/(\.drawio)$/i.test(basename))
  1935. {
  1936. basename = basename.substring(0, basename.lastIndexOf('.'));
  1937. }
  1938. if (!ignorePageName && this.pages != null && this.pages.length > 1 &&
  1939. this.currentPage != null && this.currentPage.node.getAttribute('name') != null &&
  1940. this.currentPage.getName().length > 0)
  1941. {
  1942. basename = basename + '-' + this.currentPage.getName();
  1943. }
  1944. return basename;
  1945. };
  1946. /**
  1947. * Translates this point by the given vector.
  1948. *
  1949. * @param {number} dx X-coordinate of the translation.
  1950. * @param {number} dy Y-coordinate of the translation.
  1951. */
  1952. EditorUi.prototype.downloadFile = function(format, uncompressed, addShadow, ignoreSelection,
  1953. currentPage, pageVisible, transparent, scale, border, grid, includeXml, pageRange, margin,
  1954. fit, sheetsAcross, sheetsDown)
  1955. {
  1956. try
  1957. {
  1958. ignoreSelection = (ignoreSelection != null) ? ignoreSelection : this.editor.graph.isSelectionEmpty();
  1959. var basename = this.getBaseFilename(!currentPage);
  1960. var filename = basename + ((format == 'xml' || (format == 'pdf' &&
  1961. includeXml)) ? '.drawio' : '') + '.' + format;
  1962. if (format == 'xml')
  1963. {
  1964. var data = Graph.xmlDeclaration +'\n' +
  1965. this.getFileData(true, null, null, null, ignoreSelection, currentPage,
  1966. null, null, null, uncompressed);
  1967. this.saveData(filename, format, data, 'text/xml');
  1968. }
  1969. else if (format == 'html')
  1970. {
  1971. var data = this.getHtml2(this.getFileData(true), this.editor.graph, basename);
  1972. this.saveData(filename, format, data, 'text/html');
  1973. }
  1974. else if ((format == 'svg' || format == 'xmlsvg') && this.spinner.spin(document.body, mxResources.get('export')))
  1975. {
  1976. var svg = null;
  1977. var saveSvg = mxUtils.bind(this, function(data)
  1978. {
  1979. if (data.length <= MAX_REQUEST_SIZE)
  1980. {
  1981. this.saveData(filename, 'svg', data, 'image/svg+xml');
  1982. }
  1983. else
  1984. {
  1985. this.handleError({message: mxResources.get('drawingTooLarge')}, mxResources.get('error'), mxUtils.bind(this, function()
  1986. {
  1987. mxUtils.popup(svg);
  1988. }));
  1989. }
  1990. });
  1991. if (format == 'svg')
  1992. {
  1993. var bg = this.editor.graph.background;
  1994. if (transparent || bg == mxConstants.NONE)
  1995. {
  1996. bg = null;
  1997. }
  1998. // Sets or disables alternate text for foreignObjects. Disabling is needed
  1999. // because PhantomJS seems to ignore switch statements and paint all text.
  2000. var svgRoot = this.editor.graph.getSvg(bg, null, null, null, null, ignoreSelection);
  2001. if (addShadow)
  2002. {
  2003. this.editor.graph.addSvgShadow(svgRoot);
  2004. }
  2005. // Embeds the images in the SVG output (async)
  2006. this.editor.convertImages(svgRoot, mxUtils.bind(this, mxUtils.bind(this, function(svgRoot2)
  2007. {
  2008. this.spinner.stop();
  2009. saveSvg(Graph.xmlDeclaration + '\n' + Graph.svgDoctype + '\n' + mxUtils.getXml(svgRoot2));
  2010. })));
  2011. }
  2012. else
  2013. {
  2014. filename = basename + '.svg';
  2015. svg = this.getFileData(false, true, null, mxUtils.bind(this, function(svg)
  2016. {
  2017. this.spinner.stop();
  2018. saveSvg(svg);
  2019. }), ignoreSelection);
  2020. }
  2021. }
  2022. else
  2023. {
  2024. var w, h;
  2025. if (format == 'xmlpng')
  2026. {
  2027. filename = basename + '.png';
  2028. }
  2029. else if (format == 'jpeg')
  2030. {
  2031. filename = basename + '.jpg';
  2032. }
  2033. this.saveRequest(filename, format, mxUtils.bind(this, function(newTitle, base64)
  2034. {
  2035. try
  2036. {
  2037. var req = this.createDownloadRequest(newTitle, format, ignoreSelection, base64,
  2038. transparent, currentPage, scale, border, grid, includeXml, pageRange, w, h,
  2039. !pageVisible, margin, fit, sheetsAcross, sheetsDown);
  2040. return req;
  2041. }
  2042. catch (e)
  2043. {
  2044. this.handleError(e);
  2045. }
  2046. }));
  2047. }
  2048. }
  2049. catch (e)
  2050. {
  2051. this.handleError(e);
  2052. }
  2053. };
  2054. // Note: Remember to adjust ElectronApp override when this function is modified
  2055. EditorUi.prototype.createDownloadRequest = function(filename, format, ignoreSelection, base64,
  2056. transparent, currentPage, scale, border, grid, includeXml, pageRange, w, h, crop, margin,
  2057. fit, sheetsAcross, sheetsDown)
  2058. {
  2059. var params = this.downloadRequestBuilder(filename, format, ignoreSelection, base64,
  2060. transparent, currentPage, scale, border, grid, includeXml, pageRange, w, h, crop,
  2061. margin, fit, sheetsAcross, sheetsDown);
  2062. var paramsStr = '';
  2063. for (var p in params)
  2064. {
  2065. var val = params[p];
  2066. if (val != null)
  2067. {
  2068. paramsStr += p + '=' + encodeURIComponent(val) + '&';
  2069. }
  2070. }
  2071. return new mxXmlRequest(EXPORT_URL, paramsStr);
  2072. };
  2073. /**
  2074. * Translates this point by the given vector.
  2075. *
  2076. * @param {number} dx X-coordinate of the translation.
  2077. * @param {number} dy Y-coordinate of the translation.
  2078. */
  2079. EditorUi.prototype.downloadRequestBuilder = function(filename, format, ignoreSelection, base64,
  2080. transparent, currentPage, scale, border, grid, includeXml, pageRange, w, h, crop, margin,
  2081. fit, sheetsAcross, sheetsDown)
  2082. {
  2083. var graph = this.editor.graph;
  2084. var bounds = graph.getGraphBounds();
  2085. // Exports only current page for images that does not contain file data, but for
  2086. // the other formats with XML included or pdf with all pages, we need to send the complete data and use
  2087. // the from/to URL parameters to specify the page to be exported.
  2088. var data = this.getFileData(true, null, null, null, ignoreSelection,
  2089. currentPage == false ? false : format != 'xmlpng', null, null,
  2090. null, !Editor.defaultCompressed, format == 'pdf');
  2091. var from = null, to = null, allPages = null;
  2092. if (!EditorUi.isElectronApp && (bounds.width * bounds.height > MAX_AREA || data.length > MAX_REQUEST_SIZE))
  2093. {
  2094. throw {message: mxResources.get('drawingTooLarge')};
  2095. }
  2096. var embed = (includeXml) ? '1' : '0';
  2097. if (format == 'pdf')
  2098. {
  2099. if (pageRange != null)
  2100. {
  2101. from = pageRange.from;
  2102. to = pageRange.to;
  2103. }
  2104. else if (currentPage == false)
  2105. {
  2106. allPages = '1';
  2107. }
  2108. }
  2109. if (format == 'xmlpng')
  2110. {
  2111. embed = '1';
  2112. format = 'png';
  2113. }
  2114. if (format == 'xmlpng' || format == 'svg')
  2115. {
  2116. // Finds the current page number
  2117. if (this.pages != null && this.currentPage != null)
  2118. {
  2119. for (var i = 0; i < this.pages.length; i++)
  2120. {
  2121. if (this.pages[i] == this.currentPage)
  2122. {
  2123. from = i;
  2124. break;
  2125. }
  2126. }
  2127. }
  2128. }
  2129. var bg = graph.background;
  2130. if ((format == 'png' || format == 'pdf' || format == 'svg') && transparent)
  2131. {
  2132. bg = mxConstants.NONE;
  2133. }
  2134. else if (!transparent && (bg == null || bg == mxConstants.NONE))
  2135. {
  2136. bg = '#ffffff';
  2137. }
  2138. var extras = {globalVars: graph.getExportVariables()};
  2139. if (grid)
  2140. {
  2141. extras.grid = {
  2142. size: graph.gridSize,
  2143. steps: graph.view.gridSteps,
  2144. color: (Editor.isDarkMode() && format == 'pdf') ?
  2145. mxGraphView.prototype.defaultGridColor : graph.view.gridColor
  2146. };
  2147. }
  2148. if (Graph.translateDiagram)
  2149. {
  2150. extras.diagramLanguage = Graph.diagramLanguage;
  2151. }
  2152. return {
  2153. format: format,
  2154. from: from,
  2155. to: to,
  2156. allPages: allPages,
  2157. bg: ((bg != null) ? bg : mxConstants.NONE),
  2158. base64: base64,
  2159. embedXml: embed,
  2160. xml: data,
  2161. filename: ((filename != null) ? filename : ''),
  2162. extras: JSON.stringify(extras),
  2163. scale: scale,
  2164. border: border,
  2165. pageMargin: margin,
  2166. w: (w && isFinite(w)? w : null),
  2167. h: (h && isFinite(h)? h : null),
  2168. crop: (crop != null && crop) ? '1' : '0',
  2169. fit: (fit != null && fit) ? '1' : '0',
  2170. sheetsAcross: sheetsAcross,
  2171. sheetsDown: sheetsDown
  2172. };
  2173. };
  2174. /**
  2175. * Translates this point by the given vector.
  2176. *
  2177. * @param {number} dx X-coordinate of the translation.
  2178. * @param {number} dy Y-coordinate of the translation.
  2179. */
  2180. EditorUi.prototype.setMode = function(mode, remember)
  2181. {
  2182. this.mode = mode;
  2183. };
  2184. /**
  2185. * Translates this point by the given vector.
  2186. *
  2187. * @param {number} dx X-coordinate of the translation.
  2188. * @param {number} dy Y-coordinate of the translation.
  2189. */
  2190. EditorUi.prototype.getDiagramId = function()
  2191. {
  2192. var id = window.location.hash;
  2193. // Strips the hash sign
  2194. if (id != null && id.length > 0)
  2195. {
  2196. id = id.substring(1);
  2197. }
  2198. // Removes additional parameters after trailing hash
  2199. if (id != null && id.length > 1)
  2200. {
  2201. var idx = id.indexOf('#');
  2202. if (idx >= 0)
  2203. {
  2204. id = id.substring(0, idx);
  2205. }
  2206. }
  2207. return id;
  2208. };
  2209. /**
  2210. * Translates this point by the given vector.
  2211. *
  2212. * @param {number} dx X-coordinate of the translation.
  2213. * @param {number} dy Y-coordinate of the translation.
  2214. */
  2215. EditorUi.prototype.getHashObject = function()
  2216. {
  2217. var id = window.location.hash;
  2218. var result = {};
  2219. if (id != null && id.length > 0)
  2220. {
  2221. var last = id.lastIndexOf('#');
  2222. if (last > 0)
  2223. {
  2224. var temp = decodeURIComponent(id.substring(last + 1));
  2225. try
  2226. {
  2227. result = JSON.parse(temp);
  2228. }
  2229. catch (e)
  2230. {
  2231. // ignore
  2232. }
  2233. }
  2234. }
  2235. return result;
  2236. };
  2237. /**
  2238. * Updates the hash object with the current page id.
  2239. */
  2240. EditorUi.prototype.updateHashObject = function()
  2241. {
  2242. if (this.currentFile != null && this.currentPage != null &&
  2243. this.currentFile.getHash() != '')
  2244. {
  2245. var obj = this.getHashObject();
  2246. obj.pageId = this.currentPage.getId();
  2247. this.setHashObject(obj);
  2248. }
  2249. else
  2250. {
  2251. this.setHashObject(null);
  2252. }
  2253. };
  2254. /**
  2255. * Translates this point by the given vector.
  2256. *
  2257. * @param {number} dx X-coordinate of the translation.
  2258. * @param {number} dy Y-coordinate of the translation.
  2259. */
  2260. EditorUi.prototype.setHashObject = function(obj)
  2261. {
  2262. if (Editor.enableHashObjects)
  2263. {
  2264. var id = window.location.hash;
  2265. if (id == null || id == '')
  2266. {
  2267. id = '#';
  2268. }
  2269. var last = id.lastIndexOf('#');
  2270. if (last > 0)
  2271. {
  2272. id = id.substring(0, last);
  2273. }
  2274. try
  2275. {
  2276. if (obj != null && !mxUtils.isEmptyObject(obj))
  2277. {
  2278. id = id + '#' + encodeURIComponent(JSON.stringify(obj));
  2279. }
  2280. }
  2281. catch (e)
  2282. {
  2283. // ignore
  2284. }
  2285. // Adds to browsing history if hash object changed
  2286. if (last > 0 && id.lastIndexOf('#') > 0)
  2287. {
  2288. window.location.hash = id;
  2289. }
  2290. else
  2291. {
  2292. window.location.replace(id);
  2293. }
  2294. }
  2295. };
  2296. /**
  2297. * Loads the given file descriptor. The descriptor may define the following properties:
  2298. *
  2299. * - url: The url to load the data from (proxy is used if CORS is not enabled)
  2300. * - data: The data to be inserted. If both, data and url are defined, then the data
  2301. * is preprendended to the data returned from the given URL.
  2302. * - format: Currently, only 'csv' is supported as an optional value. Default is XML.
  2303. * - update: Optional URL to fetch updates from (POST request with the page XML).
  2304. * - interval: Optional interval for fetching updates. Default is 60000 (60 seconds).
  2305. */
  2306. EditorUi.prototype.loadDescriptor = function(desc, success, error)
  2307. {
  2308. var hash = window.location.hash;
  2309. var loadData = mxUtils.bind(this, function(data)
  2310. {
  2311. var realData = (desc.data != null) ? desc.data : '';
  2312. if (data != null && data.length > 0)
  2313. {
  2314. if (realData.length > 0)
  2315. {
  2316. realData += '\n';
  2317. }
  2318. realData += data;
  2319. }
  2320. var xml = (desc.format != 'csv' && realData.length > 0) ? realData : this.emptyDiagramXml;
  2321. var tempFile = new LocalFile(this, xml, (urlParams['title'] != null) ?
  2322. decodeURIComponent(urlParams['title']) : this.defaultFilename, true);
  2323. tempFile.getHash = function()
  2324. {
  2325. return hash;
  2326. };
  2327. this.fileLoaded(tempFile);
  2328. if (desc.format == 'csv')
  2329. {
  2330. this.importCsv(realData, mxUtils.bind(this, function(cells)
  2331. {
  2332. this.editor.undoManager.clear();
  2333. this.editor.setModified(false);
  2334. this.editor.setStatus('');
  2335. }));
  2336. }
  2337. // Installs updates
  2338. if (desc.update != null)
  2339. {
  2340. var interval = (desc.interval != null) ? parseInt(desc.interval) : 60000;
  2341. var currentThread = null;
  2342. var doUpdate = mxUtils.bind(this, function()
  2343. {
  2344. var page = this.currentPage;
  2345. mxUtils.post(desc.update, 'xml=' + encodeURIComponent(
  2346. mxUtils.getXml(this.editor.getGraphXml())),
  2347. mxUtils.bind(this, function(req)
  2348. {
  2349. if (page === this.currentPage)
  2350. {
  2351. if (req.getStatus() >= 200 && req.getStatus() <= 300)
  2352. {
  2353. var doc = this.updateDiagram(req.getText());
  2354. schedule();
  2355. }
  2356. else
  2357. {
  2358. this.handleError({message: mxResources.get('error') + ' ' + req.getStatus()});
  2359. }
  2360. }
  2361. }), mxUtils.bind(this, function(err)
  2362. {
  2363. this.handleError(err);
  2364. }));
  2365. });
  2366. var schedule = mxUtils.bind(this, function()
  2367. {
  2368. window.clearTimeout(currentThread);
  2369. currentThread = window.setTimeout(doUpdate, interval);
  2370. });
  2371. this.editor.addListener('pageSelected', mxUtils.bind(this, function()
  2372. {
  2373. schedule();
  2374. doUpdate();
  2375. }));
  2376. schedule();
  2377. doUpdate();
  2378. }
  2379. if (success != null)
  2380. {
  2381. success();
  2382. }
  2383. });
  2384. if (desc.url != null && desc.url.length > 0)
  2385. {
  2386. var url = this.editor.getProxiedUrl(desc.url);
  2387. // LATER: Remove cache-control header
  2388. this.editor.loadUrl(url, mxUtils.bind(this, function(data)
  2389. {
  2390. loadData(data);
  2391. }), mxUtils.bind(this, function(err)
  2392. {
  2393. if (error != null)
  2394. {
  2395. error(err)
  2396. }
  2397. }));
  2398. }
  2399. else
  2400. {
  2401. loadData('');
  2402. }
  2403. };
  2404. /**
  2405. * Translates this point by the given vector.
  2406. *
  2407. * @param {number} dx X-coordinate of the translation.
  2408. * @param {number} dy Y-coordinate of the translation.
  2409. */
  2410. EditorUi.prototype.updateDiagram = function(xml)
  2411. {
  2412. var doc = null;
  2413. var ui = this;
  2414. function createOverlay(desc)
  2415. {
  2416. var overlay = new mxCellOverlay(desc.image || graph.warningImage,
  2417. desc.tooltip, desc.align, desc.valign, desc.offset);
  2418. // Installs a handler for clicks on the overlay
  2419. overlay.addListener(mxEvent.CLICK, function(sender, evt)
  2420. {
  2421. ui.alert(desc.tooltip);
  2422. });
  2423. return overlay;
  2424. };
  2425. if (xml != null && xml.length > 0)
  2426. {
  2427. doc = mxUtils.parseXml(xml);
  2428. var node = (doc != null) ? doc.documentElement : null;
  2429. if (node != null && node.nodeName == 'updates')
  2430. {
  2431. var graph = this.editor.graph;
  2432. var model = graph.getModel();
  2433. model.beginUpdate();
  2434. var fit = null;
  2435. try
  2436. {
  2437. node = node.firstChild;
  2438. while (node != null)
  2439. {
  2440. if (node.nodeName == 'update')
  2441. {
  2442. // Resolves the cell ID
  2443. var cell = model.getCell(node.getAttribute('id'));
  2444. if (cell != null)
  2445. {
  2446. // Changes the value
  2447. try
  2448. {
  2449. var value = node.getAttribute('value');
  2450. if (value != null)
  2451. {
  2452. var valueNode = mxUtils.parseXml(value).documentElement;
  2453. if (valueNode != null)
  2454. {
  2455. if (valueNode.getAttribute('replace-value') == '1')
  2456. {
  2457. model.setValue(cell, valueNode);
  2458. }
  2459. else
  2460. {
  2461. var attrs = valueNode.attributes;
  2462. for (var j = 0; j < attrs.length; j++)
  2463. {
  2464. graph.setAttributeForCell(cell, attrs[j].nodeName,
  2465. (attrs[j].nodeValue.length > 0) ? attrs[j].nodeValue : null);
  2466. }
  2467. }
  2468. }
  2469. }
  2470. }
  2471. catch (e)
  2472. {
  2473. if (window.console != null)
  2474. {
  2475. console.log('Error in value for ' + cell.id + ': ' + e);
  2476. }
  2477. }
  2478. // Changes the style
  2479. try
  2480. {
  2481. var style = node.getAttribute('style');
  2482. if (style != null)
  2483. {
  2484. graph.model.setStyle(cell, style);
  2485. }
  2486. }
  2487. catch (e)
  2488. {
  2489. if (window.console != null)
  2490. {
  2491. console.log('Error in style for ' + cell.id + ': ' + e);
  2492. }
  2493. }
  2494. // Adds or removes an overlay icon
  2495. try
  2496. {
  2497. var icon = node.getAttribute('icon');
  2498. if (icon != null)
  2499. {
  2500. var desc = (icon.length > 0) ? JSON.parse(icon) : null;
  2501. if (desc == null || !desc.append)
  2502. {
  2503. graph.removeCellOverlays(cell);
  2504. }
  2505. if (desc != null)
  2506. {
  2507. graph.addCellOverlay(cell, createOverlay(desc));
  2508. }
  2509. }
  2510. }
  2511. catch (e)
  2512. {
  2513. if (window.console != null)
  2514. {
  2515. console.log('Error in icon for ' + cell.id + ': ' + e);
  2516. }
  2517. }
  2518. // Replaces the geometry
  2519. try
  2520. {
  2521. var geo = node.getAttribute('geometry');
  2522. if (geo != null)
  2523. {
  2524. geo = JSON.parse(geo);
  2525. var curr = graph.getCellGeometry(cell);
  2526. if (curr != null)
  2527. {
  2528. curr = curr.clone();
  2529. // Partially overwrites geometry
  2530. for (key in geo)
  2531. {
  2532. var val = parseFloat(geo[key]);
  2533. if (key == 'dx')
  2534. {
  2535. curr.x += val;
  2536. }
  2537. else if (key == 'dy')
  2538. {
  2539. curr.y += val;
  2540. }
  2541. else if (key == 'dw')
  2542. {
  2543. curr.width += val;
  2544. }
  2545. else if (key == 'dh')
  2546. {
  2547. curr.height += val;
  2548. }
  2549. else
  2550. {
  2551. curr[key] = parseFloat(geo[key]);
  2552. }
  2553. }
  2554. graph.model.setGeometry(cell, curr);
  2555. }
  2556. }
  2557. }
  2558. catch (e)
  2559. {
  2560. if (window.console != null)
  2561. {
  2562. console.log('Error in icon for ' + cell.id + ': ' + e);
  2563. }
  2564. }
  2565. } // if cell != null
  2566. } // if node.nodeName == 'update
  2567. else if (node.nodeName == 'model')
  2568. {
  2569. // Finds first child element
  2570. var dataNode = node.firstChild;
  2571. while (dataNode != null && dataNode.nodeType != mxConstants.NODETYPE_ELEMENT)
  2572. {
  2573. dataNode = dataNode.nextSibling;
  2574. }
  2575. if (dataNode != null)
  2576. {
  2577. var dec = new mxCodec(node.firstChild);
  2578. dec.decode(dataNode, model);
  2579. }
  2580. }
  2581. else if (node.nodeName == 'view')
  2582. {
  2583. if (node.hasAttribute('scale'))
  2584. {
  2585. graph.view.scale = parseFloat(node.getAttribute('scale'));
  2586. }
  2587. if (node.hasAttribute('dx') || node.hasAttribute('dy'))
  2588. {
  2589. graph.view.translate = new mxPoint(parseFloat(node.getAttribute('dx') || 0),
  2590. parseFloat(node.getAttribute('dy') || 0));
  2591. }
  2592. }
  2593. else if (node.nodeName == 'fit')
  2594. {
  2595. if (node.hasAttribute('max-scale'))
  2596. {
  2597. fit = parseFloat(node.getAttribute('max-scale'));
  2598. }
  2599. else
  2600. {
  2601. fit = 1;
  2602. }
  2603. }
  2604. node = node.nextSibling;
  2605. } // end of while
  2606. }
  2607. finally
  2608. {
  2609. model.endUpdate();
  2610. }
  2611. if (fit != null && this.chromelessResize)
  2612. {
  2613. this.chromelessResize(true, fit);
  2614. }
  2615. }
  2616. }
  2617. return doc;
  2618. };
  2619. /**
  2620. * Constructs a filename for a copy of the given file.
  2621. */
  2622. EditorUi.prototype.getCopyFilename = function(file, timestamp)
  2623. {
  2624. var title = (file != null && file.getTitle() != null) ?
  2625. file.getTitle() : this.defaultFilename;
  2626. // Handles extension
  2627. var extension = '';
  2628. var dot = title.lastIndexOf('.');
  2629. if (dot >= 0)
  2630. {
  2631. extension = title.substring(dot);
  2632. title = title.substring(0, dot);
  2633. }
  2634. if (timestamp)
  2635. {
  2636. function getFormattedTime()
  2637. {
  2638. var today = new Date();
  2639. var y = today.getFullYear();
  2640. // JavaScript months are 0-based.
  2641. var m = today.getMonth() + 1;
  2642. var d = today.getDate();
  2643. var h = today.getHours();
  2644. var mi = today.getMinutes();
  2645. var s = today.getSeconds();
  2646. return y + "-" + m + "-" + d + "-" + h + "-" + mi + "-" + s;
  2647. }
  2648. var ts = new Date();
  2649. title += ' ' + getFormattedTime();
  2650. }
  2651. title = mxResources.get('copyOf', [title]) + extension;
  2652. return title;
  2653. };
  2654. /**
  2655. * Translates this point by the given vector.
  2656. *
  2657. * @param {number} dx X-coordinate of the translation.
  2658. * @param {number} dy Y-coordinate of the translation.
  2659. */
  2660. EditorUi.prototype.logIfModified = function(file, discarded)
  2661. {
  2662. try
  2663. {
  2664. if (file != null)
  2665. {
  2666. if (discarded)
  2667. {
  2668. EditorUi.debug('File.closed', [file]);
  2669. }
  2670. // if (file.isModified() && file.constructor == DriveFile &&
  2671. // file.desc != null && this.drive != null)
  2672. // {
  2673. // var evt = {category: 'WARN-CLOSE-MODIFIED-FILE-' + file.getHash(),
  2674. // action: (discarded ? 'DISCARDED' : 'WARNED') + ((file.savingFile) ? 'saving' : '') +
  2675. // ((file.savingFile && file.savingFileTime != null) ? '_' +
  2676. // Math.round((Date.now() - file.savingFileTime.getTime()) / 1000) : '') +
  2677. // ((file.saveLevel != null) ? ('-sl_' + file.saveLevel) : '') +
  2678. // '-age_' + ((file.ageStart != null) ? Math.round((Date.now() - file.ageStart.getTime()) / 1000) : 'x') +
  2679. // ((this.editor.autosave) ? '' : '-nosave') +
  2680. // ((file.isAutosave()) ? '' : '-noauto') +
  2681. // '-open_' + ((file.opened != null) ? Math.round((Date.now() - file.opened.getTime()) / 1000) : 'x') +
  2682. // '-save_' + ((file.lastSaved != null) ? Math.round((Date.now() - file.lastSaved.getTime()) / 1000) : 'x') +
  2683. // '-change_' + ((file.lastChanged != null) ? Math.round((Date.now() - file.lastChanged.getTime()) / 1000) : 'x') +
  2684. // '-alive_' + Math.round((Date.now() - App.startTime.getTime()) / 1000),
  2685. // label: ((file.sync != null) ? ('client_' + file.sync.clientId) : 'nosync') +
  2686. // ((this.drive.user != null) ? ('-user_' + this.drive.user.id) : '-nouser') + '-rev_' +
  2687. // file.desc.headRevisionId + '-mod_' + file.desc.modifiedDate + '-size_' + file.getSize() +
  2688. // '-mime_' + file.desc.mimeType};
  2689. // EditorUi.logEvent(evt);
  2690. // }
  2691. }
  2692. }
  2693. catch (e)
  2694. {
  2695. // ignore
  2696. }
  2697. };
  2698. /**
  2699. * Translates this point by the given vector.
  2700. *
  2701. * @param {number} dx X-coordinate of the translation.
  2702. * @param {number} dy Y-coordinate of the translation.
  2703. */
  2704. EditorUi.prototype.fileLoaded = function(file, noDialogs, success)
  2705. {
  2706. var oldFile = this.getCurrentFile();
  2707. this.fileLoadedError = null;
  2708. this.fileEditable = null;
  2709. this.setCurrentFile(null);
  2710. var result = false;
  2711. this.hideDialog();
  2712. if (oldFile != null)
  2713. {
  2714. oldFile.removeListener(this.descriptorChangedListener);
  2715. this.logIfModified(oldFile, true);
  2716. oldFile.close();
  2717. }
  2718. this.editor.graph.model.clear();
  2719. this.editor.undoManager.clear();
  2720. var noFile = mxUtils.bind(this, function()
  2721. {
  2722. this.setGraphEnabled(false);
  2723. this.setCurrentFile(null);
  2724. // Keeps initial title if no file existed before
  2725. if (oldFile != null)
  2726. {
  2727. this.updateDocumentTitle();
  2728. }
  2729. // File might have been loaded halfway
  2730. this.editor.graph.model.clear();
  2731. this.editor.undoManager.clear();
  2732. this.setBackgroundImage(null);
  2733. // Avoids empty hash with no value
  2734. if (!noDialogs && window.location.hash != null &&
  2735. window.location.hash.length > 0)
  2736. {
  2737. window.location.hash = '';
  2738. }
  2739. if (this.fname != null)
  2740. {
  2741. this.fnameWrapper.style.display = 'none';
  2742. this.fname.innerText = '';
  2743. this.fname.setAttribute('title', mxResources.get('rename'));
  2744. }
  2745. this.editor.setStatus('');
  2746. this.updateUi();
  2747. if (!noDialogs)
  2748. {
  2749. this.showSplash();
  2750. }
  2751. });
  2752. if (file != null)
  2753. {
  2754. try
  2755. {
  2756. // Workaround for delayed scroll repaint with min UI in Safari
  2757. if (mxClient.IS_SF && uiTheme == 'min')
  2758. {
  2759. this.diagramContainer.style.visibility = '';
  2760. }
  2761. // Order is significant, current file needed for correct
  2762. // file format for initial save after starting realtime
  2763. this.openingFile = true;
  2764. this.setCurrentFile(file);
  2765. file.addListener('descriptorChanged', this.descriptorChangedListener);
  2766. file.addListener('contentChanged', this.descriptorChangedListener);
  2767. file.open();
  2768. delete this.openingFile;
  2769. // DescriptorChanged updates the enabled state of the graph
  2770. this.setGraphEnabled(true);
  2771. this.setMode(file.getMode());
  2772. this.editor.graph.model.prefix = Editor.guid() + '-';
  2773. this.editor.undoManager.clear();
  2774. this.descriptorChanged();
  2775. this.updateUi();
  2776. // Realtime files have a valid status message
  2777. if (!file.isEditable())
  2778. {
  2779. this.editor.setStatus('<div class="geStatusBox" title="' +
  2780. mxUtils.htmlEntities(mxResources.get('readOnly')) + '">' +
  2781. mxUtils.htmlEntities(mxResources.get('readOnly')) + '</div>');
  2782. }
  2783. // Handles modified state after error of loading new file
  2784. else if (file.isModified())
  2785. {
  2786. file.addUnsavedStatus();
  2787. }
  2788. else
  2789. {
  2790. this.editor.setStatus('');
  2791. }
  2792. if (!this.editor.isChromelessView() || this.editor.editable)
  2793. {
  2794. this.editor.graph.selectUnlockedLayer();
  2795. this.showLayersDialog();
  2796. this.restoreLibraries();
  2797. // Workaround for no initial focus in FF
  2798. if (window.self !== window.top)
  2799. {
  2800. window.focus();
  2801. }
  2802. }
  2803. else if (this.editor.graph.isLightboxView())
  2804. {
  2805. this.lightboxFit();
  2806. }
  2807. if (this.chromelessResize)
  2808. {
  2809. this.chromelessResize();
  2810. }
  2811. if (success != null)
  2812. {
  2813. success();
  2814. }
  2815. this.editor.fireEvent(new mxEventObject('fileLoaded'));
  2816. result = true;
  2817. if (!this.isOffline() && file.getMode() != null)
  2818. {
  2819. var theme = (urlParams['sketch'] == '1') ? 'sketch' : uiTheme;
  2820. if (theme == null)
  2821. {
  2822. theme = 'default';
  2823. }
  2824. else if (theme == 'sketch' || theme == 'min')
  2825. {
  2826. theme += Editor.isDarkMode() ? '-dark' : '-light';
  2827. }
  2828. // EditorUi.logEvent({category: file.getMode().toUpperCase() + '-OPEN-FILE-' + file.getHash(),
  2829. // action: 'size_' + file.getSize(),
  2830. // label: 'autosave_' + ((this.editor.autosave) ? 'on' : 'off') + '_theme_' + theme});
  2831. }
  2832. EditorUi.debug('File.opened', [file]);
  2833. //Notify users that editing is disabled within mobile apps (mainly for MS Teams)
  2834. if (urlParams['viewerOnlyMsg'] == '1')
  2835. {
  2836. this.showAlert(mxResources.get('viewerOnlyMsg'));
  2837. }
  2838. if (this.editor.editable && this.mode == file.getMode() &&
  2839. file.getMode() != App.MODE_DEVICE && file.getMode() != null)
  2840. {
  2841. try
  2842. {
  2843. this.addRecent({id: file.getHash(), title: file.getTitle(), mode: file.getMode()});
  2844. }
  2845. catch (e)
  2846. {
  2847. // ignore
  2848. }
  2849. }
  2850. try
  2851. {
  2852. mxSettings.setOpenCounter(mxSettings.getOpenCounter() + 1);
  2853. mxSettings.save();
  2854. }
  2855. catch (e)
  2856. {
  2857. // ignore
  2858. }
  2859. }
  2860. catch (e)
  2861. {
  2862. this.fileLoadedError = e;
  2863. // Disconnects file from UI
  2864. if (file != null)
  2865. {
  2866. try
  2867. {
  2868. file.close();
  2869. }
  2870. catch (e2)
  2871. {
  2872. // ignore
  2873. }
  2874. }
  2875. // if (EditorUi.enableLogging && !this.isOffline())
  2876. // {
  2877. // try
  2878. // {
  2879. // EditorUi.logEvent({category: 'ERROR-LOAD-FILE-' +
  2880. // ((file != null) ? file.getHash() : 'none'),
  2881. // action: 'message_' + e.message,
  2882. // label: 'stack_' + e.stack});
  2883. // }
  2884. // catch (e)
  2885. // {
  2886. // // ignore
  2887. // }
  2888. // }
  2889. // Asynchronous handling of errors
  2890. var fn = mxUtils.bind(this, function()
  2891. {
  2892. // Removes URL parameter and reloads the page
  2893. if (urlParams['url'] != null && this.spinner.spin(document.body, mxResources.get('reconnecting')))
  2894. {
  2895. window.location.search = this.getSearch(['url']);
  2896. }
  2897. else if (oldFile != null)
  2898. {
  2899. if (!this.fileLoaded(oldFile))
  2900. {
  2901. noFile();
  2902. }
  2903. }
  2904. else
  2905. {
  2906. noFile();
  2907. }
  2908. });
  2909. if (!noDialogs)
  2910. {
  2911. this.handleError(e, mxResources.get('errorLoadingFile'), fn, true, null, null, true);
  2912. }
  2913. else
  2914. {
  2915. fn();
  2916. }
  2917. }
  2918. }
  2919. else
  2920. {
  2921. noFile();
  2922. }
  2923. return result;
  2924. };
  2925. /**
  2926. * Creates a hash value for the current file.
  2927. */
  2928. EditorUi.prototype.getHashValueForPages = function(pages, details)
  2929. {
  2930. // TODO: Avoid encoding to XML to make it faster
  2931. var hash = 0;
  2932. var model = new mxGraphModel();
  2933. var codec = new mxCodec();
  2934. if (details != null)
  2935. {
  2936. details.byteCount = 0;
  2937. details.attrCount = 0;
  2938. details.eltCount = 0;
  2939. details.nodeCount = 0;
  2940. }
  2941. for (var i = 0; i < pages.length; i++)
  2942. {
  2943. this.updatePageRoot(pages[i]);
  2944. var diagram = pages[i].node.cloneNode(false);
  2945. // FIXME: Check why names can be null in newer files
  2946. // ignore in hash and do not diff null names for now
  2947. diagram.removeAttribute('name');
  2948. // Model is only a holder for the root
  2949. model.root = pages[i].root;
  2950. var xmlNode = codec.encode(model);
  2951. this.editor.graph.saveViewState(pages[i].viewState, xmlNode, true);
  2952. // Local defaults may be different in files so ignore
  2953. xmlNode.removeAttribute('pageWidth');
  2954. xmlNode.removeAttribute('pageHeight');
  2955. diagram.appendChild(xmlNode);
  2956. if (details != null)
  2957. {
  2958. details.eltCount += diagram.getElementsByTagName('*').length;
  2959. details.nodeCount += diagram.getElementsByTagName('mxCell').length;
  2960. }
  2961. hash = ((hash << 5) - hash + this.hashValue(diagram, function(obj, key, value, isXml)
  2962. {
  2963. // Ignores JS machine rounding errors in known numeric attributes
  2964. // eg. 412.33333333333326 (Webkit/FF) == 412.33333333333325 (Edge/IE11)
  2965. if (isXml && (obj.nodeName == 'mxGeometry' || obj.nodeName == 'mxPoint') &&
  2966. (key == 'x' || key == 'y' || key == 'width' || key == 'height'))
  2967. {
  2968. return Math.round(value);
  2969. }
  2970. // Workaround for previous in patch written to mxCell in 10.0.23
  2971. else if (isXml && obj.nodeName == 'mxCell' && key == 'previous')
  2972. {
  2973. return null;
  2974. }
  2975. else
  2976. {
  2977. return value;
  2978. }
  2979. }, details)) << 0;
  2980. }
  2981. return hash;
  2982. };
  2983. /**
  2984. * Creates a hash value for the given object. Replacer returns the value of the
  2985. * property or attribute for the given object or XML node.
  2986. */
  2987. EditorUi.prototype.hashValue = function(obj, replacer, details)
  2988. {
  2989. var hash = 0;
  2990. // Checks for XML nodes
  2991. if (obj != null && typeof obj === 'object' && typeof obj.nodeType === 'number' &&
  2992. typeof obj.nodeName === 'string' && typeof obj.getAttribute === 'function')
  2993. {
  2994. if (obj.nodeName != null)
  2995. {
  2996. hash = hash ^ this.hashValue(obj.nodeName, replacer, details);
  2997. }
  2998. if (obj.attributes != null)
  2999. {
  3000. if (details != null)
  3001. {
  3002. details.attrCount += obj.attributes.length;
  3003. }
  3004. for (var i = 0; i < obj.attributes.length; i++)
  3005. {
  3006. var key = obj.attributes[i].name;
  3007. var value = (replacer != null) ? replacer(obj, key, obj.attributes[i].value, true) : obj.attributes[i].value;
  3008. if (value != null)
  3009. {
  3010. hash = hash ^ (this.hashValue(key, replacer, details) +
  3011. this.hashValue(value, replacer, details));
  3012. }
  3013. }
  3014. }
  3015. if (obj.childNodes != null)
  3016. {
  3017. for (var i = 0; i < obj.childNodes.length; i++)
  3018. {
  3019. hash = ((hash << 5) - hash + this.hashValue(
  3020. obj.childNodes[i], replacer, details)) << 0;
  3021. }
  3022. }
  3023. }
  3024. else if (obj != null && typeof obj !== 'function')
  3025. {
  3026. var str = String(obj);
  3027. var temp = 0;
  3028. if (details != null)
  3029. {
  3030. details.byteCount += str.length;
  3031. }
  3032. for (var i = 0; i < str.length; i++)
  3033. {
  3034. temp = ((temp << 5) - temp + str.charCodeAt(i)) << 0;
  3035. }
  3036. hash = hash ^ temp;
  3037. }
  3038. return hash;
  3039. };
  3040. /**
  3041. * Adds empty implementation
  3042. */
  3043. EditorUi.prototype.descriptorChanged = function()
  3044. {
  3045. // empty
  3046. };
  3047. /**
  3048. * Hook for subclassers.
  3049. */
  3050. EditorUi.prototype.restoreLibraries = function() { };
  3051. /**
  3052. * Hook for subclassers.
  3053. */
  3054. EditorUi.prototype.saveLibrary = function(name, images, file, mode, noSpin, noReload, fn) { };
  3055. /**
  3056. *
  3057. */
  3058. EditorUi.prototype.isScratchpadEnabled = function()
  3059. {
  3060. return isLocalStorage || mxClient.IS_CHROMEAPP;
  3061. };
  3062. /**
  3063. * Shows or hides the scratchpad library.
  3064. */
  3065. EditorUi.prototype.toggleScratchpad = function()
  3066. {
  3067. if (this.isScratchpadEnabled())
  3068. {
  3069. if (this.scratchpad == null)
  3070. {
  3071. StorageFile.getFileContent(this, '.scratchpad', mxUtils.bind(this, function(xml)
  3072. {
  3073. if (xml == null)
  3074. {
  3075. xml = this.emptyLibraryXml;
  3076. }
  3077. this.loadLibrary(new StorageLibrary(this, xml, '.scratchpad'));
  3078. }));
  3079. }
  3080. else
  3081. {
  3082. this.closeLibrary(this.scratchpad);
  3083. }
  3084. }
  3085. };
  3086. /**
  3087. * Translates this point by the given vector.
  3088. *
  3089. * @param {number} dx X-coordinate of the translation.
  3090. * @param {number} dy Y-coordinate of the translation.
  3091. */
  3092. EditorUi.prototype.createLibraryDataFromImages = function(images)
  3093. {
  3094. // Uncompresses existing entries for saving
  3095. if (!Editor.defaultCompressed)
  3096. {
  3097. for (var i = 0; i < images.length; i++)
  3098. {
  3099. if (images[i].xml != null && images[i].xml.charAt(0) != '<')
  3100. {
  3101. images[i].xml = mxUtils.trim(Graph.decompress(images[i].xml));
  3102. }
  3103. }
  3104. }
  3105. var doc = mxUtils.createXmlDocument();
  3106. var library = doc.createElement('mxlibrary');
  3107. mxUtils.setTextContent(library, JSON.stringify(images, null, 2));
  3108. doc.appendChild(library);
  3109. return mxUtils.getXml(doc, '\n');
  3110. };
  3111. /**
  3112. * Translates this point by the given vector.
  3113. *
  3114. * @param {number} dx X-coordinate of the translation.
  3115. * @param {number} dy Y-coordinate of the translation.
  3116. */
  3117. EditorUi.prototype.closeLibrary = function(file)
  3118. {
  3119. if (file != null)
  3120. {
  3121. this.removeLibrarySidebar(file.getHash());
  3122. if (file.constructor != LocalLibrary)
  3123. {
  3124. mxSettings.removeCustomLibrary(file.getHash());
  3125. }
  3126. if (file.title == '.scratchpad')
  3127. {
  3128. this.scratchpad = null;
  3129. }
  3130. }
  3131. };
  3132. /**
  3133. * Translates this point by the given vector.
  3134. *
  3135. * @param {number} dx X-coordinate of the translation.
  3136. * @param {number} dy Y-coordinate of the translation.
  3137. */
  3138. EditorUi.prototype.removeLibrarySidebar = function(id)
  3139. {
  3140. var elts = this.sidebar.palettes[id];
  3141. if (elts != null)
  3142. {
  3143. for (var i = 0; i < elts.length; i++)
  3144. {
  3145. elts[i].parentNode.removeChild(elts[i]);
  3146. }
  3147. delete this.sidebar.palettes[id];
  3148. }
  3149. };
  3150. /**
  3151. * Changes the position of the library in the sidebar.
  3152. */
  3153. EditorUi.prototype.repositionLibrary = function(nextChild, append)
  3154. {
  3155. var c = this.sidebar.getEntryContainer();
  3156. if (!this.sidebar.appendCustomLibraries ||
  3157. nextChild != null || !append)
  3158. {
  3159. if (nextChild == null)
  3160. {
  3161. var elts = this.sidebar.palettes['L.scratchpad'];
  3162. if (elts == null)
  3163. {
  3164. elts = this.sidebar.palettes['search'];
  3165. }
  3166. if (elts != null)
  3167. {
  3168. nextChild = elts[elts.length - 1].nextSibling;
  3169. }
  3170. }
  3171. nextChild = (nextChild != null) ? nextChild : c.firstChild.nextSibling.nextSibling;
  3172. var content = c.lastChild;
  3173. var title = content.previousSibling;
  3174. c.insertBefore(content, nextChild);
  3175. c.insertBefore(title, content);
  3176. }
  3177. };
  3178. /**
  3179. * Translates this point by the given vector.
  3180. *
  3181. * @param {number} dx X-coordinate of the translation.
  3182. * @param {number} dy Y-coordinate of the translation.
  3183. */
  3184. EditorUi.prototype.loadLibrary = function(file, expand)
  3185. {
  3186. var doc = mxUtils.parseXml(file.getData());
  3187. if (doc.documentElement.nodeName == 'mxlibrary')
  3188. {
  3189. var images = JSON.parse(mxUtils.getTextContent(doc.documentElement));
  3190. this.libraryLoaded(file, images, doc.documentElement.getAttribute('title'), expand);
  3191. }
  3192. else
  3193. {
  3194. throw {message: mxResources.get('notALibraryFile')};
  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. EditorUi.prototype.getLibraryStorageHint = function(file)
  3204. {
  3205. return '';
  3206. };
  3207. /**
  3208. * Translates this point by the given vector.
  3209. *
  3210. * @param {number} dx X-coordinate of the translation.
  3211. * @param {number} dy Y-coordinate of the translation.
  3212. */
  3213. EditorUi.prototype.showSidebar = function()
  3214. {
  3215. if (this.sidebarWindow != null)
  3216. {
  3217. this.sidebarWindow.window.setVisible(true);
  3218. }
  3219. else
  3220. {
  3221. this.toggleShapesPanel(true);
  3222. }
  3223. };
  3224. /**
  3225. * Translates this point by the given vector.
  3226. *
  3227. * @param {number} dx X-coordinate of the translation.
  3228. * @param {number} dy Y-coordinate of the translation.
  3229. */
  3230. EditorUi.prototype.libraryLoaded = function(file, images, optionalTitle, expand)
  3231. {
  3232. if (this.sidebar == null)
  3233. {
  3234. return;
  3235. }
  3236. if (file.constructor != LocalLibrary)
  3237. {
  3238. mxSettings.addCustomLibrary(file.getHash());
  3239. }
  3240. var library = null;
  3241. if (file.constructor != StorageLibrary || file.title != '.scratchpad')
  3242. {
  3243. if (this.openLibraries == null)
  3244. {
  3245. this.openLibraries = [];
  3246. }
  3247. // Removes existing entry for this file ID
  3248. for (var i = 0; i < this.openLibraries.length; i++)
  3249. {
  3250. if (this.openLibraries[i].file.getHash() == file.getHash())
  3251. {
  3252. mxUtils.remove(this.openLibraries[i], this.openLibraries);
  3253. break;
  3254. }
  3255. }
  3256. // Adds new entry to the array of open libraries
  3257. library = {file: file, images: images,
  3258. title: optionalTitle,
  3259. expand: expand};
  3260. this.openLibraries.push(library);
  3261. }
  3262. else
  3263. {
  3264. this.scratchpad = file;
  3265. }
  3266. var elts = this.sidebar.palettes[file.getHash()];
  3267. var nextSibling = (elts != null) ? elts[elts.length - 1].nextSibling : null;
  3268. // Removes existing sidebar entry for this library
  3269. this.removeLibrarySidebar(file.getHash());
  3270. var dropTarget = null;
  3271. var addImages = mxUtils.bind(this, function(imgs, content)
  3272. {
  3273. if (imgs.length == 0 && file.isEditable())
  3274. {
  3275. if (dropTarget == null)
  3276. {
  3277. dropTarget = document.createElement('div');
  3278. dropTarget.className = 'geDropTarget';
  3279. mxUtils.write(dropTarget, mxResources.get('dragElementsHere'));
  3280. }
  3281. content.appendChild(dropTarget);
  3282. }
  3283. else
  3284. {
  3285. this.addLibraryEntries(imgs, content);
  3286. }
  3287. });
  3288. // Adds entries to search index
  3289. // KNOWN: Existing entries are not replaced after edit of custom library
  3290. if (this.sidebar != null && images != null)
  3291. {
  3292. this.sidebar.addEntries(images);
  3293. }
  3294. // Adds new sidebar entry for this library
  3295. var tmp = optionalTitle;
  3296. if (tmp == null)
  3297. {
  3298. tmp = file.getTitle();
  3299. if (tmp != null && /(\.xml)$/i.test(tmp))
  3300. {
  3301. tmp = tmp.substring(0, tmp.lastIndexOf('.'));
  3302. }
  3303. }
  3304. var contentDiv = this.sidebar.addPalette(file.getHash(), tmp,
  3305. (expand != null) ? expand : true, mxUtils.bind(this, function(content)
  3306. {
  3307. addImages(images, content);
  3308. }));
  3309. if (library != null)
  3310. {
  3311. library.div = contentDiv;
  3312. }
  3313. this.repositionLibrary(nextSibling, file.getHash() != 'L.scratchpad');
  3314. // Adds tooltip for backend
  3315. var title = contentDiv.parentNode.previousSibling;
  3316. var tip = title.getAttribute('title');
  3317. if (tip != null && tip.length > 0 && file.title != '.scratchpad')
  3318. {
  3319. title.setAttribute('title', this.getLibraryStorageHint(file) + '\n' + tip);
  3320. }
  3321. var buttons = document.createElement('div');
  3322. buttons.style.backgroundColor = 'inherit';
  3323. title.style.position = 'relative';
  3324. var btn = document.createElement('img');
  3325. btn.className = 'geAdaptiveAsset';
  3326. btn.setAttribute('src', Editor.crossImage);
  3327. btn.setAttribute('title', mxResources.get('close'));
  3328. btn.setAttribute('valign', 'absmiddle');
  3329. btn.setAttribute('border', '0');
  3330. btn.style.position = 'relative';
  3331. btn.style.top = '2px';
  3332. btn.style.width = '14px';
  3333. btn.style.cursor = 'pointer';
  3334. btn.style.margin = '0 3px';
  3335. var saveBtn = null;
  3336. if (file.title != '.scratchpad' || this.closableScratchpad)
  3337. {
  3338. buttons.appendChild(btn);
  3339. mxEvent.addListener(btn, 'click', mxUtils.bind(this, function(evt)
  3340. {
  3341. // Workaround for close after any button click in IE8
  3342. if (!mxEvent.isConsumed(evt))
  3343. {
  3344. var fn = mxUtils.bind(this, function()
  3345. {
  3346. if (library != null)
  3347. {
  3348. mxUtils.remove(library, this.openLibraries);
  3349. }
  3350. this.closeLibrary(file);
  3351. });
  3352. if (saveBtn != null)
  3353. {
  3354. this.confirm(mxResources.get('allChangesLost'), null, fn,
  3355. mxResources.get('cancel'), mxResources.get('discardChanges'));
  3356. }
  3357. else
  3358. {
  3359. fn();
  3360. }
  3361. mxEvent.consume(evt);
  3362. }
  3363. }));
  3364. }
  3365. if (file.isEditable())
  3366. {
  3367. var graph = this.editor.graph;
  3368. var spinBtn = null;
  3369. var editLibrary = mxUtils.bind(this, function(evt)
  3370. {
  3371. this.showLibraryDialog(file.getTitle(), contentDiv, images, file, file.getMode());
  3372. mxEvent.consume(evt);
  3373. });
  3374. var saveLibrary = mxUtils.bind(this, function(evt)
  3375. {
  3376. file.setModified(true);
  3377. if (file.isAutosave())
  3378. {
  3379. if (spinBtn != null && spinBtn.parentNode != null)
  3380. {
  3381. spinBtn.parentNode.removeChild(spinBtn);
  3382. }
  3383. spinBtn = btn.cloneNode(false);
  3384. spinBtn.setAttribute('src', Editor.spinImage);
  3385. spinBtn.setAttribute('title', mxResources.get('saving'));
  3386. spinBtn.style.cursor = 'default';
  3387. spinBtn.style.marginRight = '2px';
  3388. spinBtn.style.marginTop = '-2px';
  3389. buttons.insertBefore(spinBtn, buttons.firstChild);
  3390. this.saveLibrary(file.getTitle(), images, file, file.getMode(), true, true, function()
  3391. {
  3392. if (spinBtn != null && spinBtn.parentNode != null)
  3393. {
  3394. spinBtn.parentNode.removeChild(spinBtn);
  3395. }
  3396. });
  3397. }
  3398. else if (saveBtn == null)
  3399. {
  3400. saveBtn = btn.cloneNode(false);
  3401. saveBtn.setAttribute('src', Editor.saveImage);
  3402. saveBtn.setAttribute('title', mxResources.get('save'));
  3403. buttons.insertBefore(saveBtn, buttons.firstChild);
  3404. mxEvent.addListener(saveBtn, 'click', mxUtils.bind(this, function(evt)
  3405. {
  3406. this.saveLibrary(file.getTitle(), images, file, file.getMode(),
  3407. file.constructor == LocalLibrary, true, function()
  3408. {
  3409. if (saveBtn != null && !file.isModified())
  3410. {
  3411. saveBtn.parentNode.removeChild(saveBtn);
  3412. saveBtn = null;
  3413. }
  3414. });
  3415. mxEvent.consume(evt);
  3416. }));
  3417. }
  3418. });
  3419. var addCells = mxUtils.bind(this, function(cells, bounds, evt, title)
  3420. {
  3421. cells = graph.cloneCells(mxUtils.sortCells(graph.model.getTopmostCells(cells)));
  3422. // Translates cells to origin
  3423. for (var i = 0; i < cells.length; i++)
  3424. {
  3425. var geo = graph.getCellGeometry(cells[i]);
  3426. if (geo != null)
  3427. {
  3428. geo.translate(-bounds.x, -bounds.y);
  3429. }
  3430. }
  3431. contentDiv.appendChild(this.sidebar.createVertexTemplateFromCells(
  3432. cells, bounds.width, bounds.height, title || '', true, null, false));
  3433. var xml = mxUtils.getXml(this.editor.graph.encodeCells(cells));
  3434. if (Editor.defaultCompressed)
  3435. {
  3436. xml = Graph.compress(xml);
  3437. }
  3438. var entry = {xml: xml, w: bounds.width, h: bounds.height};
  3439. if (title != null)
  3440. {
  3441. entry.title = title;
  3442. }
  3443. images.push(entry);
  3444. saveLibrary(evt);
  3445. if (dropTarget != null && dropTarget.parentNode != null && images.length > 0)
  3446. {
  3447. dropTarget.parentNode.removeChild(dropTarget);
  3448. dropTarget = null;
  3449. }
  3450. });
  3451. var addSelection = mxUtils.bind(this, function(evt)
  3452. {
  3453. if (!graph.isSelectionEmpty())
  3454. {
  3455. var cells = graph.getSelectionCells();
  3456. var bounds = graph.view.getBounds(cells);
  3457. if (bounds != null && bounds.width > 0 && bounds.height > 0)
  3458. {
  3459. var s = graph.view.scale;
  3460. bounds.x /= s;
  3461. bounds.y /= s;
  3462. bounds.width /= s;
  3463. bounds.height /= s;
  3464. bounds.x -= graph.view.translate.x;
  3465. bounds.y -= graph.view.translate.y;
  3466. addCells(cells, bounds);
  3467. }
  3468. else
  3469. {
  3470. this.showError(mxResources.get('error'), mxResources.get('invalidSel'), mxResources.get('ok'));
  3471. }
  3472. }
  3473. else if (graph.getRubberband().isActive())
  3474. {
  3475. graph.getRubberband().execute(evt);
  3476. graph.getRubberband().reset();
  3477. }
  3478. else
  3479. {
  3480. this.showError(mxResources.get('error'), mxResources.get('nothingIsSelected'), mxResources.get('ok'));
  3481. }
  3482. mxEvent.consume(evt);
  3483. });
  3484. // Adds drop handler from graph
  3485. mxEvent.addGestureListeners(contentDiv, function(){}, mxUtils.bind(this, function(evt)
  3486. {
  3487. if (graph.isMouseDown && graph.panningManager != null && graph.graphHandler.first != null)
  3488. {
  3489. graph.graphHandler.suspend();
  3490. if (graph.graphHandler.hint != null)
  3491. {
  3492. graph.graphHandler.hint.style.visibility = 'hidden';
  3493. }
  3494. contentDiv.style.backgroundColor = (Editor.isDarkMode()) ? '#000000' : '#fefefe';
  3495. contentDiv.style.cursor = 'copy';
  3496. graph.panningManager.stop();
  3497. graph.autoScroll = false;
  3498. mxEvent.consume(evt);
  3499. }
  3500. }), mxUtils.bind(this, function(evt)
  3501. {
  3502. if (graph.isMouseDown && graph.panningManager != null && graph.graphHandler != null)
  3503. {
  3504. contentDiv.style.backgroundColor = '';
  3505. contentDiv.style.cursor = 'default';
  3506. this.sidebar.showTooltips = true;
  3507. graph.panningManager.stop();
  3508. graph.graphHandler.reset();
  3509. graph.isMouseDown = false;
  3510. graph.autoScroll = true;
  3511. addSelection(evt);
  3512. mxEvent.consume(evt);
  3513. }
  3514. }));
  3515. // Handles mouse leaving the library and restoring move
  3516. mxEvent.addListener(contentDiv, 'mouseleave', mxUtils.bind(this, function(evt)
  3517. {
  3518. if (graph.isMouseDown && graph.graphHandler.first != null)
  3519. {
  3520. graph.graphHandler.resume();
  3521. if (graph.graphHandler.hint != null)
  3522. {
  3523. graph.graphHandler.hint.style.visibility = 'visible';
  3524. }
  3525. contentDiv.style.backgroundColor = '';
  3526. contentDiv.style.cursor = '';
  3527. graph.autoScroll = true;
  3528. }
  3529. }));
  3530. // Adds drop handler from filesystem
  3531. if (Graph.fileSupport)
  3532. {
  3533. mxEvent.addListener(contentDiv, 'dragover', mxUtils.bind(this, function(evt)
  3534. {
  3535. contentDiv.style.backgroundColor = (Editor.isDarkMode()) ? '#000000' : '#fefefe';
  3536. evt.dataTransfer.dropEffect = 'copy';
  3537. contentDiv.style.cursor = 'copy';
  3538. this.sidebar.hideTooltip();
  3539. evt.stopPropagation();
  3540. evt.preventDefault();
  3541. }));
  3542. mxEvent.addListener(contentDiv, 'drop', mxUtils.bind(this, function(evt)
  3543. {
  3544. contentDiv.style.cursor = '';
  3545. contentDiv.style.backgroundColor = '';
  3546. if (evt.dataTransfer.files.length > 0)
  3547. {
  3548. this.importFiles(evt.dataTransfer.files, 0, 0, this.maxImageSize, mxUtils.bind(this, function(data, mimeType, x, y, w, h, img, doneFn, file)
  3549. {
  3550. if (data != null && mimeType.substring(0, 6) == 'image/')
  3551. {
  3552. var style = 'shape=image;verticalLabelPosition=bottom;verticalAlign=top;imageAspect=0;aspect=fixed;image=' +
  3553. this.convertDataUri(data);
  3554. var cells = [new mxCell('', new mxGeometry(0, 0, w, h), style)];
  3555. cells[0].vertex = true;
  3556. addCells(cells, new mxRectangle(0, 0, w, h), evt, (mxEvent.isAltDown(evt)) ? null : img.substring(0, img.lastIndexOf('.')).replace(/_/g, ' '));
  3557. if (dropTarget != null && dropTarget.parentNode != null && images.length > 0)
  3558. {
  3559. dropTarget.parentNode.removeChild(dropTarget);
  3560. dropTarget = null;
  3561. }
  3562. }
  3563. else
  3564. {
  3565. var done = false;
  3566. var doImport = mxUtils.bind(this, function(theData, theMimeType)
  3567. {
  3568. if (theData != null && theMimeType == 'application/pdf')
  3569. {
  3570. var xml = Editor.extractGraphModelFromPdf(theData);
  3571. if (xml != null && xml.length > 0)
  3572. {
  3573. theMimeType = 'text/xml';
  3574. theData = xml;
  3575. }
  3576. }
  3577. if (theData != null) //Try to parse the file as xml (can be a library or mxfile). Otherwise, an error will be shown
  3578. {
  3579. var doc = mxUtils.parseXml(theData);
  3580. if (doc.documentElement.nodeName == 'mxlibrary')
  3581. {
  3582. try
  3583. {
  3584. var temp = JSON.parse(mxUtils.getTextContent(doc.documentElement));
  3585. addImages(temp, contentDiv);
  3586. images = images.concat(temp);
  3587. saveLibrary(evt);
  3588. this.spinner.stop();
  3589. done = true;
  3590. }
  3591. catch (e)
  3592. {
  3593. // ignore
  3594. }
  3595. }
  3596. else if (doc.documentElement.nodeName == 'mxfile')
  3597. {
  3598. try
  3599. {
  3600. var pages = doc.documentElement.getElementsByTagName('diagram');
  3601. for (var i = 0; i < pages.length; i++)
  3602. {
  3603. var cells = this.stringToCells(Editor.getDiagramNodeXml(pages[i]));
  3604. var size = this.editor.graph.getBoundingBoxFromGeometry(cells);
  3605. if (size != null)
  3606. {
  3607. addCells(cells, new mxRectangle(0, 0, size.width, size.height), evt);
  3608. }
  3609. }
  3610. done = true;
  3611. }
  3612. catch (e)
  3613. {
  3614. if (window.console != null)
  3615. {
  3616. console.log('error in drop handler:', e);
  3617. }
  3618. }
  3619. }
  3620. }
  3621. if (!done)
  3622. {
  3623. this.spinner.stop();
  3624. this.handleError({message: mxResources.get('errorLoadingFile')})
  3625. }
  3626. if (dropTarget != null && dropTarget.parentNode != null && images.length > 0)
  3627. {
  3628. dropTarget.parentNode.removeChild(dropTarget);
  3629. dropTarget = null;
  3630. }
  3631. });
  3632. if (file != null && img != null && EditorUi.isVisioFilename(img))
  3633. {
  3634. this.importVisio(file, function(xml)
  3635. {
  3636. doImport(xml, 'text/xml');
  3637. }, null, img);
  3638. }
  3639. else if (new XMLHttpRequest().upload && this.isRemoteFileFormat(data, img) && file != null)
  3640. {
  3641. if (this.isExternalDataComms())
  3642. {
  3643. this.parseFile(file, mxUtils.bind(this, function(xhr)
  3644. {
  3645. if (xhr.readyState == 4)
  3646. {
  3647. this.spinner.stop();
  3648. if (xhr.status >= 200 && xhr.status <= 299)
  3649. {
  3650. doImport(xhr.responseText, 'text/xml');
  3651. }
  3652. else
  3653. {
  3654. this.handleError({message: mxResources.get((xhr.status == 413) ?
  3655. 'drawingTooLarge' : 'invalidOrMissingFile')},
  3656. mxResources.get('errorLoadingFile'));
  3657. }
  3658. }
  3659. }));
  3660. }
  3661. else
  3662. {
  3663. this.spinner.stop();
  3664. this.showError(mxResources.get('error'), mxResources.get('notInOffline'));
  3665. }
  3666. }
  3667. else
  3668. {
  3669. doImport(data, mimeType);
  3670. }
  3671. }
  3672. }));
  3673. }
  3674. evt.stopPropagation();
  3675. evt.preventDefault();
  3676. }));
  3677. mxEvent.addListener(contentDiv, 'dragleave', function(evt)
  3678. {
  3679. contentDiv.style.cursor = '';
  3680. contentDiv.style.backgroundColor = '';
  3681. evt.stopPropagation();
  3682. evt.preventDefault();
  3683. });
  3684. }
  3685. btn = btn.cloneNode(false);
  3686. btn.setAttribute('src', Editor.editImage);
  3687. btn.setAttribute('title', mxResources.get('edit'));
  3688. buttons.insertBefore(btn, buttons.firstChild);
  3689. mxEvent.addListener(btn, 'click', editLibrary);
  3690. var btn2 = btn.cloneNode(false);
  3691. btn2.setAttribute('src', Editor.plusImage);
  3692. btn2.setAttribute('title', mxResources.get('add'));
  3693. buttons.insertBefore(btn2, buttons.firstChild);
  3694. mxEvent.addListener(btn2, 'click', addSelection);
  3695. // Hack to add selection via context menu
  3696. if (file.title == '.scratchpad')
  3697. {
  3698. this.addSelectionToScratchpad = addSelection;
  3699. }
  3700. if (!this.isOffline() && file.title == '.scratchpad' &&
  3701. EditorUi.scratchpadHelpLink != null)
  3702. {
  3703. var link = this.createHelpIcon(EditorUi.scratchpadHelpLink);
  3704. var img = link.getElementsByTagName('img')[0];
  3705. link.style.marginRight = '1px';
  3706. img.style.marginTop = '-2px';
  3707. img.style.opacity = '';
  3708. buttons.insertBefore(link, buttons.firstChild);
  3709. }
  3710. }
  3711. title.appendChild(buttons);
  3712. this.editor.fireEvent(new mxEventObject('libraryLoaded'));
  3713. };
  3714. /**
  3715. * Adds the library entries to the given DOM node.
  3716. */
  3717. EditorUi.prototype.addLibraryEntries = function(imgs, content)
  3718. {
  3719. for (var i = 0; i < imgs.length; i++)
  3720. {
  3721. try
  3722. {
  3723. var img = imgs[i];
  3724. var data = img.data;
  3725. if (data != null)
  3726. {
  3727. data = (data.substring(0, 5) == 'data:') ? this.convertDataUri(data) : data;
  3728. var s = 'shape=image;verticalLabelPosition=bottom;verticalAlign=top;imageAspect=0;';
  3729. if (img.aspect == 'fixed')
  3730. {
  3731. s += 'aspect=fixed;'
  3732. }
  3733. s = s + 'image=' + data + ';';
  3734. if (img.style != null)
  3735. {
  3736. s += img.style;
  3737. }
  3738. content.appendChild(this.sidebar.createVertexTemplate(s,
  3739. img.w, img.h, '', img.title || '', false, null, true));
  3740. }
  3741. else if (img.xml != null)
  3742. {
  3743. var cells = this.stringToCells((img.xml.charAt(0) == '<') ?
  3744. img.xml : Graph.decompress(img.xml));
  3745. var title = (cells.length == 0) ? mxResources.get('drawingEmpty') :
  3746. ((img.title != null) ? img.title : '');
  3747. content.appendChild(this.sidebar.createVertexTemplateFromCells(
  3748. cells, img.w, img.h, title, true, null, true));
  3749. }
  3750. }
  3751. catch (e)
  3752. {
  3753. var title = (e.message != null) ? mxResources.get('error') +
  3754. ': ' + e.message : String(e);
  3755. var elt = this.sidebar.createVertexTemplateFromCells(null,
  3756. img.w, img.h, title, true, null, true);
  3757. content.appendChild(elt);
  3758. }
  3759. }
  3760. };
  3761. /**
  3762. * Extracts the resource for the current language from the given multi language
  3763. * resource object of the form {es: "...", de: "...", main: "..."} where the keys
  3764. * are country codes and main defines the fallback if no resource for the current
  3765. * country code exists.
  3766. */
  3767. EditorUi.prototype.getResource = function(obj)
  3768. {
  3769. return (obj != null) ? (obj[mxLanguage] || obj.main) : null;
  3770. };
  3771. /**
  3772. * EditorUi Overrides
  3773. */
  3774. EditorUi.prototype.footerHeight = 0;
  3775. if (urlParams['savesidebar'] == '1')
  3776. {
  3777. Sidebar.prototype.thumbWidth = 64;
  3778. Sidebar.prototype.thumbHeight = 64;
  3779. }
  3780. /**
  3781. * Programmatic settings for theme.
  3782. */
  3783. EditorUi.initTheme = function()
  3784. {
  3785. if (Editor.currentTheme == 'atlas' && !window.DRAWIO_PUBLIC_BUILD)
  3786. {
  3787. mxClient.link('stylesheet', STYLE_PATH + '/atlas.css');
  3788. if (typeof Toolbar !== 'undefined')
  3789. {
  3790. Toolbar.prototype.unselectedBackground = 'linear-gradient(rgb(255, 255, 255) 0px, rgb(242, 242, 242) 100%)';
  3791. Toolbar.prototype.selectedBackground = 'rgb(242, 242, 242)';
  3792. }
  3793. Editor.prototype.initialTopSpacing = 3;
  3794. EditorUi.prototype.menubarHeight = 41;
  3795. EditorUi.prototype.toolbarHeight = 38;
  3796. }
  3797. // Implements the sketch-min UI
  3798. if (Editor.currentTheme == 'sketch' && !window.DRAWIO_PUBLIC_BUILD)
  3799. {
  3800. Editor.configurationKey = '.sketch-configuration';
  3801. Editor.settingsKey = '.sketch-config';
  3802. }
  3803. };
  3804. EditorUi.initTheme();
  3805. /**
  3806. * Overrides image dialog to add image search and Google+.
  3807. */
  3808. EditorUi.prototype.showImageDialog = function(title, value, fn, ignoreExisting, convertDataUri, withCrop, initClipPath)
  3809. {
  3810. // KNOWN: IE+FF don't return keyboard focus after image dialog (calling focus doesn't help)
  3811. var dlg = new ImageDialog(this, title, value, fn, ignoreExisting, convertDataUri, withCrop, initClipPath);
  3812. this.showDialog(dlg.container, (Graph.fileSupport) ? 480 : 360, (Graph.fileSupport) ? 200 : 90, true, true);
  3813. dlg.init();
  3814. };
  3815. /**
  3816. * Overrides image dialog to add image search and Google+.
  3817. */
  3818. EditorUi.prototype.showLocalStorageDialog = function(title, key, buttons, elt, helpLink, applyFn)
  3819. {
  3820. var value = localStorage.getItem(key);
  3821. var dlg = new TextareaDialog(this, title, (value != null) ?
  3822. JSON.stringify(JSON.parse(value), null, 2) : '',
  3823. mxUtils.bind(this, function(newValue)
  3824. {
  3825. if (newValue != null)
  3826. {
  3827. try
  3828. {
  3829. if (applyFn != null)
  3830. {
  3831. applyFn(newValue);
  3832. }
  3833. if (newValue == value)
  3834. {
  3835. this.hideDialog();
  3836. }
  3837. else
  3838. {
  3839. if (newValue.length > 0)
  3840. {
  3841. var obj = JSON.parse(newValue);
  3842. localStorage.setItem(key, JSON.stringify(obj));
  3843. }
  3844. else
  3845. {
  3846. localStorage.removeItem(key);
  3847. }
  3848. this.hideDialog();
  3849. this.alert(mxResources.get('restartForChangeRequired'));
  3850. }
  3851. }
  3852. catch (e)
  3853. {
  3854. this.handleError(e);
  3855. }
  3856. }
  3857. }), null, mxResources.get('close'), null, null, null, true, null, null, helpLink, buttons, elt);
  3858. this.showDialog(dlg.container, 660, 480, true, false);
  3859. dlg.init();
  3860. };
  3861. /**
  3862. * Hides the current menu.
  3863. */
  3864. EditorUi.prototype.showBackgroundImageDialog = function(apply, img, color, showColor)
  3865. {
  3866. apply = (apply != null) ? apply : mxUtils.bind(this, function(image, failed, color, shadowVisible)
  3867. {
  3868. if (!failed)
  3869. {
  3870. var change = new ChangePageSetup(this, (showColor) ? color : null, image);
  3871. change.ignoreColor = !showColor;
  3872. if (shadowVisible != null && showColor)
  3873. {
  3874. change.shadowVisible = shadowVisible;
  3875. }
  3876. this.editor.graph.model.execute(change);
  3877. }
  3878. });
  3879. var dlg = new BackgroundImageDialog(this, apply, img, color, showColor);
  3880. this.showDialog(dlg.container, 400, (showColor) ? 240 : 220, true, true);
  3881. dlg.init();
  3882. };
  3883. /**
  3884. * Hides the current menu.
  3885. */
  3886. EditorUi.prototype.showLibraryDialog = function(name, sidebar, images, file, mode)
  3887. {
  3888. var dlg = new LibraryDialog(this, name, sidebar, images, file, mode);
  3889. this.showDialog(dlg.container, 640, 440, true, false, mxUtils.bind(this, function(cancel)
  3890. {
  3891. if (cancel && this.getCurrentFile() == null && urlParams['embed'] != '1')
  3892. {
  3893. this.showSplash();
  3894. }
  3895. }));
  3896. dlg.init();
  3897. };
  3898. /**
  3899. * Overridden to update after view state changes.
  3900. */
  3901. var editorUiCreateFormat = EditorUi.prototype.createFormat;
  3902. EditorUi.prototype.createFormat = function(container)
  3903. {
  3904. var format = editorUiCreateFormat.apply(this, arguments);
  3905. this.editor.graph.addListener('viewStateChanged', mxUtils.bind(this, function(evt)
  3906. {
  3907. if (this.editor.graph.isSelectionEmpty())
  3908. {
  3909. format.refresh();
  3910. }
  3911. }));
  3912. return format;
  3913. };
  3914. /**
  3915. * Translates this point by the given vector.
  3916. *
  3917. * @param {number} dx X-coordinate of the translation.
  3918. * @param {number} dy Y-coordinate of the translation.
  3919. */
  3920. EditorUi.prototype.handleError = function(resp, title, fn, invokeFnOnClose, notFoundMessage, fileHash, disableLogging)
  3921. {
  3922. var resume = (this.spinner != null && this.spinner.pause != null) ? this.spinner.pause() : function() {};
  3923. var e = (resp != null && resp.error != null) ? resp.error : resp;
  3924. // Logs errors and writes stack to console
  3925. if (resp != null && (urlParams['test'] == '1' || resp.stack != null) && resp.message != null)
  3926. {
  3927. try
  3928. {
  3929. if (!disableLogging)
  3930. {
  3931. EditorUi.logError('Caught: ' +
  3932. (resp.message == '' && resp.name != null) ? resp.name : resp.message,
  3933. resp.filename, resp.lineNumber, resp.columnNumber, resp, 'INFO');
  3934. }
  3935. if ((urlParams['test'] == '1' || disableLogging) &&
  3936. window.console != null)
  3937. {
  3938. console.error('EditorUi.handleError:', resp);
  3939. }
  3940. }
  3941. catch (e)
  3942. {
  3943. // ignore
  3944. }
  3945. }
  3946. if (e != null || title != null)
  3947. {
  3948. var msg = mxUtils.htmlEntities(mxResources.get('unknownError'), false);
  3949. var btn = mxResources.get('ok');
  3950. var retry = null;
  3951. title = (title != null) ? title : mxResources.get('error');
  3952. if (e != null)
  3953. {
  3954. if (e.retry != null)
  3955. {
  3956. btn = mxResources.get('cancel');
  3957. retry = function()
  3958. {
  3959. resume();
  3960. e.retry();
  3961. };
  3962. }
  3963. if (e.code == 404 || e.status == 404 || e.code == 403)
  3964. {
  3965. if (e.code == 403)
  3966. {
  3967. if (e.message != null)
  3968. {
  3969. msg = mxUtils.htmlEntities(e.message, false);
  3970. }
  3971. else
  3972. {
  3973. msg = mxUtils.htmlEntities(mxResources.get('accessDenied'), false);
  3974. }
  3975. }
  3976. else
  3977. {
  3978. msg = (notFoundMessage != null) ? notFoundMessage :
  3979. mxUtils.htmlEntities(mxResources.get('fileNotFoundOrDenied') +
  3980. ((this.drive != null && this.drive.user != null) ?
  3981. ' (' + this.drive.user.email+ ')' : ''), false);
  3982. }
  3983. var id = (notFoundMessage != null) ? null : ((fileHash != null) ? fileHash : window.location.hash);
  3984. // #U handles case where we tried to fallback to Google File and
  3985. // hash property still shows the public URL we tried to load
  3986. if (id != null && (id.substring(0, 2) == '#G' ||
  3987. id.substring(0, 45) == '#Uhttps%3A%2F%2Fdrive.google.com%2Fuc%3Fid%3D') &&
  3988. ((resp != null && resp.error != null && ((resp.error.errors != null &&
  3989. resp.error.errors.length > 0 && resp.error.errors[0].reason == 'fileAccess') ||
  3990. (resp.error.data != null && resp.error.data.length > 0 &&
  3991. resp.error.data[0].reason == 'fileAccess'))) ||
  3992. e.code == 404 || e.status == 404))
  3993. {
  3994. id = (id.substring(0, 2) == '#U') ? id.substring(45, id.lastIndexOf('%26ex')) : id.substring(2);
  3995. // Special case where the button must have a different label and function
  3996. this.showError(title, msg, mxResources.get('tryOpeningViaThisPage'), mxUtils.bind(this, function()
  3997. {
  3998. this.editor.graph.openLink('https://drive.google.com/open?id=' + id);
  3999. if (invokeFnOnClose != null)
  4000. {
  4001. invokeFnOnClose();
  4002. }
  4003. }), retry, mxResources.get('changeUser'), mxUtils.bind(this, function()
  4004. {
  4005. var driveUsers = this.drive.getUsersList();
  4006. var div = document.createElement('div');
  4007. var title = document.createElement('div');
  4008. title.style.marginBottom = '6px';
  4009. mxUtils.write(title, mxResources.get('changeUser') + ': ');
  4010. div.appendChild(title);
  4011. var usersSelect = document.createElement('select');
  4012. usersSelect.style.width = '100%';
  4013. //TODO This code is similar to Dialogs.js change user part in SplashDialog
  4014. function fillUsersSelect()
  4015. {
  4016. usersSelect.innerText = '';
  4017. for (var i = 0; i < driveUsers.length; i++)
  4018. {
  4019. var option = document.createElement('option');
  4020. mxUtils.write(option, driveUsers[i].displayName);
  4021. option.value = i;
  4022. usersSelect.appendChild(option);
  4023. //More info (email) about the user in a disabled option
  4024. option = document.createElement('option');
  4025. option.innerHTML = '&nbsp;&nbsp;&nbsp;';
  4026. mxUtils.write(option, '<' + driveUsers[i].email + '>');
  4027. option.setAttribute('disabled', 'disabled');
  4028. usersSelect.appendChild(option);
  4029. }
  4030. //Add account option
  4031. var option = document.createElement('option');
  4032. mxUtils.write(option, mxResources.get('addAccount'));
  4033. option.value = driveUsers.length;
  4034. usersSelect.appendChild(option);
  4035. }
  4036. fillUsersSelect();
  4037. mxEvent.addListener(usersSelect, 'change', mxUtils.bind(this, function()
  4038. {
  4039. var userIndex = usersSelect.value;
  4040. var existingAccount = driveUsers.length != userIndex;
  4041. if (existingAccount)
  4042. {
  4043. this.drive.setUser(driveUsers[userIndex]);
  4044. }
  4045. this.drive.authorize(existingAccount, mxUtils.bind(this, function()
  4046. {
  4047. if (!existingAccount)
  4048. {
  4049. driveUsers = this.drive.getUsersList();
  4050. fillUsersSelect();
  4051. }
  4052. }), mxUtils.bind(this, function(resp)
  4053. {
  4054. this.handleError(resp);
  4055. }), true);
  4056. }));
  4057. div.appendChild(usersSelect);
  4058. var dlg = new CustomDialog(this, div, mxUtils.bind(this, function()
  4059. {
  4060. this.loadFile(window.location.hash.substr(1), true);
  4061. }));
  4062. this.showDialog(dlg.container, 300, 100, true, true);
  4063. }), mxResources.get('cancel'), mxUtils.bind(this, function()
  4064. {
  4065. this.hideDialog();
  4066. if (fn != null)
  4067. {
  4068. fn();
  4069. }
  4070. }), 520, 150);
  4071. return;
  4072. }
  4073. }
  4074. if (e.message != null)
  4075. {
  4076. if (e.message == '' && e.name != null)
  4077. {
  4078. msg = mxUtils.htmlEntities(e.name, false);
  4079. }
  4080. else
  4081. {
  4082. msg = mxUtils.htmlEntities(e.message, false);
  4083. }
  4084. }
  4085. else if (e.response != null && e.response.error != null)
  4086. {
  4087. msg = mxUtils.htmlEntities(e.response.error, false);
  4088. }
  4089. else if (typeof window.App !== 'undefined')
  4090. {
  4091. if (e.code == App.ERROR_TIMEOUT)
  4092. {
  4093. msg = mxUtils.htmlEntities(mxResources.get('timeout'), false);
  4094. }
  4095. else if (e.code == App.ERROR_BUSY)
  4096. {
  4097. msg = mxUtils.htmlEntities(mxResources.get('busy'), false);
  4098. }
  4099. else if (typeof e === 'string' && e.length > 0)
  4100. {
  4101. msg = mxUtils.htmlEntities(e, false);
  4102. }
  4103. }
  4104. }
  4105. var btn3 = null;
  4106. var fn3 = null;
  4107. if (e != null && e.helpLink != null)
  4108. {
  4109. btn3 = mxResources.get('help');
  4110. fn3 = mxUtils.bind(this, function()
  4111. {
  4112. return this.editor.graph.openLink(e.helpLink);
  4113. });
  4114. }
  4115. else if (e != null && e.ownerEmail != null)
  4116. {
  4117. btn3 = mxResources.get('contactOwner');
  4118. msg += mxUtils.htmlEntities(' (' + btn3 + ': ' + e.ownerEmail + ')', false);
  4119. fn3 = mxUtils.bind(this, function()
  4120. {
  4121. return this.openLink('mailto:' + mxUtils.htmlEntities(e.ownerEmail));
  4122. });
  4123. }
  4124. this.showError(title, msg, btn, fn, retry, null, null, btn3, fn3,
  4125. null, null, null, (invokeFnOnClose) ? fn : null);
  4126. }
  4127. else if (fn != null)
  4128. {
  4129. fn();
  4130. }
  4131. };
  4132. /**
  4133. * Translates this point by the given vector.
  4134. *
  4135. * @param {number} dx X-coordinate of the translation.
  4136. * @param {number} dy Y-coordinate of the translation.
  4137. */
  4138. EditorUi.prototype.alert = function(msg, fn, optionalWidth)
  4139. {
  4140. var dlg = new ErrorDialog(this, null, msg, mxResources.get('ok'), fn);
  4141. this.showDialog(dlg.container, optionalWidth || 340, 100, true, false);
  4142. dlg.init();
  4143. };
  4144. /**
  4145. * Translates this point by the given vector.
  4146. *
  4147. * @param {number} dx X-coordinate of the translation.
  4148. * @param {number} dy Y-coordinate of the translation.
  4149. */
  4150. EditorUi.prototype.confirm = function(msg, okFn, cancelFn, okLabel, cancelLabel, closable)
  4151. {
  4152. msg = (msg != null) ? msg : '';
  4153. var resume = (this.spinner != null && this.spinner.pause != null) ? this.spinner.pause() : function() {};
  4154. var height = Math.min(220, Math.ceil(Math.max(1, msg.length) / 50) * 28);
  4155. var dlg = new ConfirmDialog(this, msg, function()
  4156. {
  4157. resume();
  4158. if (okFn != null)
  4159. {
  4160. okFn();
  4161. }
  4162. }, function()
  4163. {
  4164. resume();
  4165. if (cancelFn != null)
  4166. {
  4167. cancelFn();
  4168. }
  4169. }, okLabel, cancelLabel, null, null, null, null, height);
  4170. this.showDialog(dlg.container, 340, 46 + height, true, closable);
  4171. dlg.init();
  4172. };
  4173. /**
  4174. * Creates a popup banner.
  4175. */
  4176. EditorUi.prototype.showBanner = function(id, text, onclick, doNotShowAgainOnClose, small, positionCss, t1, t2, to)
  4177. {
  4178. var result = false;
  4179. if (!this.bannerShowing && !this['hideBanner' + id] &&
  4180. (!isLocalStorage || mxSettings.settings == null ||
  4181. mxSettings.settings['close' + id] == null))
  4182. {
  4183. positionCss = (positionCss != null) ? positionCss : 'bottom:10px;left:50%;';
  4184. t1 = (t1 != null) ? t1 : 'translate(-50%,120%)';
  4185. t2 = (t2 != null) ? t2 : 'translate(-50%,0%)';
  4186. var delay = (small) ? 500 : 1000;
  4187. var css = (!small) ? 'font-size:16px;padding:18px 34px 12px 20px;font-weight:bold;' :
  4188. 'padding:4px;border-radius:6px;font-size:11px;height:12px;font-weight:normal;';
  4189. var banner = document.createElement('div');
  4190. banner.style.cssText = 'position:absolute;' + positionCss + ';max-width:90%;white-space:nowrap;' +
  4191. 'cursor:pointer;z-index:' + mxPopupMenu.prototype.zIndex + ';' + css;
  4192. mxUtils.setPrefixedStyle(banner.style, 'transform', t1);
  4193. mxUtils.setPrefixedStyle(banner.style, 'transition', 'all ' + delay + 'ms ease');
  4194. banner.className = 'geBtn gePrimaryBtn' + ((small) ? ' geSmallBanner' : '');
  4195. if (!Editor.isDarkMode())
  4196. {
  4197. mxUtils.setPrefixedStyle(banner.style, 'box-shadow', '1px 1px 2px 0px #ddd');
  4198. }
  4199. if (to != null)
  4200. {
  4201. mxUtils.setPrefixedStyle(banner.style, 'transform-origin', to);
  4202. }
  4203. if (!small)
  4204. {
  4205. var logo = document.createElement('img');
  4206. logo.setAttribute('src', IMAGE_PATH + '/logo.png');
  4207. logo.setAttribute('border', '0');
  4208. logo.setAttribute('align', 'absmiddle');
  4209. logo.style.cssText = 'margin-top:-4px;margin-left:8px;'+
  4210. 'margin-right:12px;width:26px;height:26px;';
  4211. banner.appendChild(logo);
  4212. var img = document.createElement('img');
  4213. img.setAttribute('src', Dialog.prototype.closeImage);
  4214. img.setAttribute('title', mxResources.get((doNotShowAgainOnClose) ? 'doNotShowAgain' : 'close'));
  4215. img.setAttribute('border', '0');
  4216. img.style.cssText = ((small) ? 'right:6px;top:9px;' :
  4217. 'right:10px;top:12px;') + 'position:absolute;filter:invert(1);padding:6px;margin:-6px;cursor:default;';
  4218. banner.appendChild(img);
  4219. }
  4220. mxUtils.write(banner, text);
  4221. document.body.appendChild(banner);
  4222. this.bannerShowing = true;
  4223. var div = document.createElement('div');
  4224. div.style.cssText = 'display:flex;align-items:center;justify-content:center;' +
  4225. 'padding-top:6px;font-size:11px;text-align:center;font-weight:normal;';
  4226. var chk = document.createElement('input');
  4227. chk.setAttribute('type', 'checkbox');
  4228. chk.setAttribute('id', 'geDoNotShowAgainCheckbox');
  4229. chk.style.marginRight = '6px';
  4230. if (!doNotShowAgainOnClose)
  4231. {
  4232. div.appendChild(chk);
  4233. var label = document.createElement('label');
  4234. label.setAttribute('for', 'geDoNotShowAgainCheckbox');
  4235. mxUtils.write(label, mxResources.get('doNotShowAgain'));
  4236. div.appendChild(label);
  4237. banner.style.paddingBottom = (small) ? '16px' : '30px';
  4238. banner.appendChild(div);
  4239. }
  4240. var onclose = mxUtils.bind(this, function()
  4241. {
  4242. if (banner.parentNode != null)
  4243. {
  4244. banner.parentNode.removeChild(banner);
  4245. this.bannerShowing = false;
  4246. if (chk.checked || doNotShowAgainOnClose)
  4247. {
  4248. this['hideBanner' + id] = true;
  4249. if (isLocalStorage && mxSettings.settings != null)
  4250. {
  4251. mxSettings.settings['close' + id] = Date.now();
  4252. mxSettings.save();
  4253. }
  4254. }
  4255. }
  4256. });
  4257. if (img != null)
  4258. {
  4259. mxEvent.addListener(img, 'click', mxUtils.bind(this, function(e)
  4260. {
  4261. mxEvent.consume(e);
  4262. onclose();
  4263. }));
  4264. }
  4265. var hide = mxUtils.bind(this, function()
  4266. {
  4267. mxUtils.setPrefixedStyle(banner.style, 'transform', t1);
  4268. window.setTimeout(mxUtils.bind(this, function()
  4269. {
  4270. onclose();
  4271. }), delay);
  4272. });
  4273. mxEvent.addListener(banner, 'click', mxUtils.bind(this, function(e)
  4274. {
  4275. var source = mxEvent.getSource(e);
  4276. if (source != chk && source != label)
  4277. {
  4278. if (onclick != null)
  4279. {
  4280. onclick();
  4281. }
  4282. onclose();
  4283. mxEvent.consume(e);
  4284. }
  4285. else
  4286. {
  4287. hide();
  4288. }
  4289. }));
  4290. window.setTimeout(mxUtils.bind(this, function()
  4291. {
  4292. mxUtils.setPrefixedStyle(banner.style, 'transform', t2);
  4293. }), delay / 2);
  4294. window.setTimeout(hide, (small) ? 4000 : 30000);
  4295. result = true;
  4296. }
  4297. return result;
  4298. };
  4299. /**
  4300. * Translates this point by the given vector.
  4301. *
  4302. * @param {number} dx X-coordinate of the translation.
  4303. * @param {number} dy Y-coordinate of the translation.
  4304. */
  4305. EditorUi.prototype.setCurrentFile = function(file)
  4306. {
  4307. if (file != null)
  4308. {
  4309. file.opened = new Date();
  4310. }
  4311. this.currentFile = file;
  4312. };
  4313. /**
  4314. * Translates this point by the given vector.
  4315. *
  4316. * @param {number} dx X-coordinate of the translation.
  4317. * @param {number} dy Y-coordinate of the translation.
  4318. */
  4319. EditorUi.prototype.getCurrentFile = function()
  4320. {
  4321. return this.currentFile;
  4322. };
  4323. /**
  4324. * Handling for canvas export.
  4325. */
  4326. EditorUi.prototype.isExportToCanvas = function()
  4327. {
  4328. return this.editor.isExportToCanvas();
  4329. };
  4330. /**
  4331. *
  4332. */
  4333. EditorUi.prototype.createImageDataUri = function(canvas, xml, format, dpi)
  4334. {
  4335. var data = canvas.toDataURL('image/' + format);
  4336. // Checks for valid output
  4337. if (data != null && data.length > 6)
  4338. {
  4339. if (xml != null)
  4340. {
  4341. data = Editor.writeGraphModelToPng(data, 'tEXt', 'mxfile', encodeURIComponent(xml));
  4342. }
  4343. if (dpi > 0)
  4344. {
  4345. data = Editor.writeGraphModelToPng(data, 'pHYs', 'dpi', dpi);
  4346. }
  4347. }
  4348. else
  4349. {
  4350. throw {message: mxResources.get('unknownError')};
  4351. }
  4352. return data;
  4353. };
  4354. /**
  4355. *
  4356. */
  4357. EditorUi.prototype.saveCanvas = function(canvas, xml, format, ignorePageName, dpi)
  4358. {
  4359. var ext = ((format == 'jpeg') ? 'jpg' : format);
  4360. var filename = this.getBaseFilename(ignorePageName) +
  4361. ((xml != null) ? '.drawio' : '') + '.' + ext;
  4362. var data = this.createImageDataUri(canvas, xml, format, dpi);
  4363. this.saveData(filename, ext, data.substring(data.lastIndexOf(',') + 1), 'image/' + format, true);
  4364. };
  4365. /**
  4366. * Returns true if files should be saved using <saveLocalFile>.
  4367. */
  4368. EditorUi.prototype.isLocalFileSave = function()
  4369. {
  4370. return ((urlParams['save'] != 'remote' && (mxClient.IS_IE ||
  4371. (typeof window.Blob !== 'undefined' && typeof window.URL !== 'undefined')) &&
  4372. document.documentMode != 9 && document.documentMode != 8 &&
  4373. document.documentMode != 7) ||
  4374. this.isOfflineApp() || mxClient.IS_IOS);
  4375. };
  4376. /**
  4377. * Translates this point by the given vector.
  4378. *
  4379. * @param {number} dx X-coordinate of the translation.
  4380. * @param {number} dy Y-coordinate of the translation.
  4381. */
  4382. EditorUi.prototype.showTextDialog = function(title, text)
  4383. {
  4384. var dlg = new TextareaDialog(this, title, text, null, null, mxResources.get('close'));
  4385. this.showDialog(dlg.container, 620, 460, true, true, null, null, null, null, true);
  4386. dlg.init();
  4387. document.execCommand('selectall', false, null);
  4388. };
  4389. /**
  4390. * Translates this point by the given vector.
  4391. *
  4392. * @param {number} dx X-coordinate of the translation.
  4393. * @param {number} dy Y-coordinate of the translation.
  4394. */
  4395. EditorUi.prototype.doSaveLocalFile = function(data, filename, mimeType, base64Encoded, format, defaultExtension)
  4396. {
  4397. // Appends .drawio extension for XML files with no extension
  4398. // to avoid the browser to automatically append .xml instead
  4399. if (mimeType == 'text/xml' &&
  4400. !/(\.drawio)$/i.test(filename) &&
  4401. !/(\.xml)$/i.test(filename) &&
  4402. !/(\.svg)$/i.test(filename) &&
  4403. !/(\.html)$/i.test(filename))
  4404. {
  4405. defaultExtension = (defaultExtension != null) ? defaultExtension : 'drawio';
  4406. filename = filename + '.' + defaultExtension;
  4407. }
  4408. // Newer versions of IE
  4409. if (window.Blob && navigator.msSaveOrOpenBlob)
  4410. {
  4411. var blob = (base64Encoded) ?
  4412. this.base64ToBlob(data, mimeType) :
  4413. new Blob([data], {type: mimeType})
  4414. navigator.msSaveOrOpenBlob(blob, filename);
  4415. }
  4416. // Older versions of IE (binary not supported)
  4417. else if (mxClient.IS_IE)
  4418. {
  4419. var win = window.open('about:blank', '_blank');
  4420. if (win == null)
  4421. {
  4422. mxUtils.popup(data, true);
  4423. }
  4424. else
  4425. {
  4426. win.document.write(data);
  4427. win.document.close();
  4428. win.document.execCommand('SaveAs', true, filename);
  4429. win.close();
  4430. }
  4431. }
  4432. else if (mxClient.IS_IOS && this.isOffline())
  4433. {
  4434. // Workaround for "WebKitBlobResource error 1" in mobile Safari
  4435. if (!navigator.standalone && mimeType != null && mimeType.substring(0, 6) == 'image/')
  4436. {
  4437. this.openInNewWindow(data, mimeType, base64Encoded);
  4438. }
  4439. else
  4440. {
  4441. this.showTextDialog(filename + ':', data);
  4442. }
  4443. }
  4444. else
  4445. {
  4446. var a = document.createElement('a');
  4447. // Workaround for mxXmlRequest.simulate no longer working in PaleMoon
  4448. // if this is used (ie PNG export broken after XML export in PaleMoon)
  4449. // and for "WebKitBlobResource error 1" for all browsers on iOS.
  4450. var useDownload = (navigator.userAgent == null ||
  4451. navigator.userAgent.indexOf("PaleMoon/") < 0) &&
  4452. typeof a.download !== 'undefined';
  4453. // Workaround for Chromium 65 cross-domain anchor download issue
  4454. if (mxClient.IS_GC && navigator.userAgent != null)
  4455. {
  4456. var raw = navigator.userAgent.match(/Chrom(e|ium)\/([0-9]+)\./)
  4457. var vers = raw ? parseInt(raw[2], 10) : false;
  4458. useDownload = vers == 65 ? false : useDownload;
  4459. }
  4460. if (useDownload || this.isOffline())
  4461. {
  4462. a.href = URL.createObjectURL((base64Encoded) ?
  4463. this.base64ToBlob(data, mimeType) :
  4464. new Blob([data], {type: mimeType}));
  4465. if (useDownload)
  4466. {
  4467. a.download = filename;
  4468. }
  4469. else
  4470. {
  4471. // Workaround for same window in Safari
  4472. a.setAttribute('target', '_blank');
  4473. }
  4474. document.body.appendChild(a);
  4475. try
  4476. {
  4477. window.setTimeout(function()
  4478. {
  4479. URL.revokeObjectURL(a.href);
  4480. }, 20000);
  4481. a.click();
  4482. a.parentNode.removeChild(a);
  4483. }
  4484. catch (e)
  4485. {
  4486. // ignore
  4487. }
  4488. }
  4489. else
  4490. {
  4491. var req = this.createEchoRequest(data, filename, mimeType, base64Encoded, format);
  4492. req.simulate(document, '_blank');
  4493. }
  4494. }
  4495. };
  4496. /**
  4497. * Translates this point by the given vector.
  4498. *
  4499. * @param {number} dx X-coordinate of the translation.
  4500. * @param {number} dy Y-coordinate of the translation.
  4501. */
  4502. EditorUi.prototype.createEchoRequest = function(data, filename, mimeType, base64Encoded, format, base64Response)
  4503. {
  4504. var param = (typeof(pako) === 'undefined' || true) ? 'xml=' + encodeURIComponent(data) :
  4505. 'data=' + encodeURIComponent(Graph.compress(data));
  4506. return new mxXmlRequest(SAVE_URL, param +
  4507. ((mimeType != null) ? '&mime=' + mimeType : '') +
  4508. ((format != null) ? '&format=' + format : '') +
  4509. ((base64Response != null) ? '&base64=' + base64Response : '') +
  4510. ((filename != null) ? '&filename=' + encodeURIComponent(filename) : '') +
  4511. ((base64Encoded) ? '&binary=1' : ''));
  4512. };
  4513. /**
  4514. * Translates this point by the given vector.
  4515. *
  4516. * @param {number} dx X-coordinate of the translation.
  4517. * @param {number} dy Y-coordinate of the translation.
  4518. */
  4519. EditorUi.prototype.saveLocalFile = function(data, filename, mimeType, base64Encoded, format, allowBrowser, allowTab, defaultExtension)
  4520. {
  4521. allowBrowser = (allowBrowser != null) ? allowBrowser : false;
  4522. allowTab = (allowTab != null) ? allowTab : (format != 'vsdx') && (!mxClient.IS_IOS || !navigator.standalone);
  4523. var saveFunction = mxUtils.bind(this, function(newTitle, mode, input, folderId)
  4524. {
  4525. try
  4526. {
  4527. // Opens a new window
  4528. if (mode == '_blank')
  4529. {
  4530. if (mimeType != null && mimeType.substring(0, 6) == 'image/')
  4531. {
  4532. this.openInNewWindow(data, mimeType, base64Encoded);
  4533. }
  4534. else if (mimeType != null && mimeType.substring(0, 9) == 'text/html')
  4535. {
  4536. var dlg = new EmbedDialog(this, data);
  4537. this.showDialog(dlg.container, 450, 240, true, true);
  4538. dlg.init();
  4539. }
  4540. else
  4541. {
  4542. var win = window.open('about:blank');
  4543. if (win == null)
  4544. {
  4545. mxUtils.popup(data, true);
  4546. }
  4547. else
  4548. {
  4549. win.document.write('<pre>' + mxUtils.htmlEntities(data, false) + '</pre>');
  4550. win.document.close();
  4551. }
  4552. }
  4553. }
  4554. else if (mode == App.MODE_DEVICE || mode == 'download')
  4555. {
  4556. this.doSaveLocalFile(data, newTitle, mimeType, base64Encoded, null, defaultExtension);
  4557. }
  4558. else if (newTitle != null && newTitle.length > 0)
  4559. {
  4560. var saveFile = mxUtils.bind(this, function(folderId)
  4561. {
  4562. try
  4563. {
  4564. this.exportFile(data, newTitle, mimeType, base64Encoded, mode, folderId);
  4565. }
  4566. catch (e)
  4567. {
  4568. this.handleError(e);
  4569. }
  4570. });
  4571. if (folderId != null)
  4572. {
  4573. saveFile(folderId);
  4574. }
  4575. else
  4576. {
  4577. this.pickFolder(mode, saveFile);
  4578. }
  4579. }
  4580. }
  4581. catch (e)
  4582. {
  4583. this.handleError(e);
  4584. }
  4585. });
  4586. var disabled = [];
  4587. if (!allowBrowser)
  4588. {
  4589. disabled.push(App.MODE_BROWSER);
  4590. }
  4591. if (!allowTab)
  4592. {
  4593. disabled.push('_blank');
  4594. }
  4595. var dlg = new SaveDialog(this, filename, mxUtils.bind(this, function(input, mode, folderId)
  4596. {
  4597. saveFunction(input.value, mode, input, folderId);
  4598. this.hideDialog(null, null, dlg.container);
  4599. }), disabled, data, mimeType, base64Encoded);
  4600. this.showDialog(dlg.container, 420, 110, true, false, mxUtils.bind(this, function()
  4601. {
  4602. this.hideDialog();
  4603. }));
  4604. dlg.init();
  4605. };
  4606. /**
  4607. *
  4608. */
  4609. EditorUi.prototype.openInNewWindow = function(data, mimeType, base64Encoded)
  4610. {
  4611. var win = window.open('about:blank');
  4612. if (win == null || win.document == null)
  4613. {
  4614. mxUtils.popup(data, true);
  4615. }
  4616. else
  4617. {
  4618. if (mimeType == 'image/svg+xml' && !mxClient.IS_SVG)
  4619. {
  4620. win.document.write('<html><pre>' + mxUtils.htmlEntities(data, false) + '</pre></html>');
  4621. win.document.close();
  4622. }
  4623. else
  4624. {
  4625. if (mimeType == 'image/svg+xml' && !base64Encoded)
  4626. {
  4627. win.document.write('<html>'+ data + '</html>');
  4628. }
  4629. else
  4630. {
  4631. var temp = (base64Encoded) ? data : btoa(unescape(encodeURIComponent(data)));
  4632. win.document.write('<html><img style="max-width:100%;" src="data:' +
  4633. mimeType + ';base64,' + temp + '"/></html>');
  4634. }
  4635. win.document.close();
  4636. }
  4637. }
  4638. };
  4639. var editoUiAddChromelessToolbarItems = EditorUi.prototype.addChromelessToolbarItems;
  4640. /**
  4641. * Image export in viewer is only allowed for same domain or hosted environments
  4642. * but disabled to avoid cross domain image export in canvas which isn't allowed.
  4643. */
  4644. EditorUi.prototype.isChromelessImageExportEnabled = function()
  4645. {
  4646. return this.getServiceName() != 'draw.io' ||
  4647. /.*\.draw\.io$/.test(window.location.hostname) ||
  4648. /.*\.diagrams\.net$/.test(window.location.hostname);
  4649. };
  4650. /**
  4651. * Creates a temporary graph instance for rendering off-screen content.
  4652. */
  4653. EditorUi.prototype.addChromelessToolbarItems = function(addButton)
  4654. {
  4655. if (urlParams['tags'] != null)
  4656. {
  4657. this.tagsComponent = null;
  4658. this.tagsDialog = null;
  4659. var tagsButton = addButton(mxUtils.bind(this, function(evt)
  4660. {
  4661. if (this.tagsComponent == null)
  4662. {
  4663. this.tagsComponent = this.editor.graph.createTagsDialog(mxUtils.bind(this, function()
  4664. {
  4665. return this.tagsDialog != null;
  4666. }), true);
  4667. this.tagsComponent.div.getElementsByTagName('div')[0].style.position = '';
  4668. mxUtils.setPrefixedStyle(this.tagsComponent.div.style, 'borderRadius', '5px');
  4669. this.tagsComponent.div.className = 'geScrollable';
  4670. this.tagsComponent.div.style.maxHeight = '160px';
  4671. this.tagsComponent.div.style.maxWidth = '120px';
  4672. this.tagsComponent.div.style.padding = '4px';
  4673. this.tagsComponent.div.style.overflow = 'auto';
  4674. this.tagsComponent.div.style.height = 'auto';
  4675. this.tagsComponent.div.style.position = 'fixed';
  4676. this.tagsComponent.div.style.fontFamily = Editor.defaultHtmlFont;
  4677. if (!mxClient.IS_IE && !mxClient.IS_IE11)
  4678. {
  4679. this.tagsComponent.div.style.backgroundColor = '#000000';
  4680. this.tagsComponent.div.style.color = '#ffffff';
  4681. mxUtils.setOpacity(this.tagsComponent.div, 80);
  4682. }
  4683. else
  4684. {
  4685. this.tagsComponent.div.style.backgroundColor = '#ffffff';
  4686. this.tagsComponent.div.style.border = '2px solid black';
  4687. this.tagsComponent.div.style.color = '#000000';
  4688. }
  4689. }
  4690. if (this.tagsDialog != null)
  4691. {
  4692. this.tagsDialog.parentNode.removeChild(this.tagsDialog);
  4693. this.tagsDialog = null;
  4694. }
  4695. else
  4696. {
  4697. this.tagsDialog = this.tagsComponent.div;
  4698. mxEvent.addListener(this.tagsDialog, 'mouseleave', mxUtils.bind(this, function()
  4699. {
  4700. if (this.tagsDialog != null)
  4701. {
  4702. this.tagsDialog.parentNode.removeChild(this.tagsDialog);
  4703. this.tagsDialog = null;
  4704. }
  4705. }));
  4706. var r = tagsButton.getBoundingClientRect();
  4707. this.tagsDialog.style.left = r.left + 'px';
  4708. this.tagsDialog.style.bottom = parseInt(this.chromelessToolbar.style.bottom) +
  4709. this.chromelessToolbar.offsetHeight + 4 + 'px';
  4710. // Puts the dialog on top of the container z-index
  4711. var style = mxUtils.getCurrentStyle(this.editor.graph.container);
  4712. this.tagsDialog.style.zIndex = style.zIndex;
  4713. document.body.appendChild(this.tagsDialog);
  4714. this.tagsComponent.refresh();
  4715. this.editor.fireEvent(new mxEventObject('tagsDialogShown'));
  4716. }
  4717. mxEvent.consume(evt);
  4718. }), Editor.tagsImage, mxResources.get('tags'));
  4719. // Shows/hides tags button depending on content
  4720. var model = this.editor.graph.getModel();
  4721. model.addListener(mxEvent.CHANGE, mxUtils.bind(this, function()
  4722. {
  4723. var tags = this.editor.graph.getAllTags();
  4724. tagsButton.style.display = (tags.length > 0) ? '' : 'none';
  4725. }));
  4726. }
  4727. editoUiAddChromelessToolbarItems.apply(this, arguments);
  4728. this.editor.addListener('tagsDialogShown', mxUtils.bind(this, function()
  4729. {
  4730. if (this.layersDialog != null)
  4731. {
  4732. this.layersDialog.parentNode.removeChild(this.layersDialog);
  4733. this.layersDialog = null;
  4734. }
  4735. }));
  4736. this.editor.addListener('layersDialogShown', mxUtils.bind(this, function()
  4737. {
  4738. if (this.tagsDialog != null)
  4739. {
  4740. this.tagsDialog.parentNode.removeChild(this.tagsDialog);
  4741. this.tagsDialog = null;
  4742. }
  4743. }));
  4744. this.editor.addListener('pageSelected', mxUtils.bind(this, function()
  4745. {
  4746. if (this.tagsDialog != null)
  4747. {
  4748. this.tagsDialog.parentNode.removeChild(this.tagsDialog);
  4749. this.tagsDialog = null;
  4750. }
  4751. if (this.layersDialog != null)
  4752. {
  4753. this.layersDialog.parentNode.removeChild(this.layersDialog);
  4754. this.layersDialog = null;
  4755. }
  4756. }));
  4757. mxEvent.addListener(this.editor.graph.container, 'click', mxUtils.bind(this, function()
  4758. {
  4759. if (this.tagsDialog != null)
  4760. {
  4761. this.tagsDialog.parentNode.removeChild(this.tagsDialog);
  4762. this.tagsDialog = null;
  4763. }
  4764. if (this.layersDialog != null)
  4765. {
  4766. this.layersDialog.parentNode.removeChild(this.layersDialog);
  4767. this.layersDialog = null;
  4768. }
  4769. }));
  4770. if (this.isExportToCanvas() && this.isChromelessImageExportEnabled())
  4771. {
  4772. this.exportDialog = null;
  4773. var exportButton = addButton(mxUtils.bind(this, function(evt)
  4774. {
  4775. var clickHandler = mxUtils.bind(this, function()
  4776. {
  4777. mxEvent.removeListener(this.editor.graph.container, 'click', clickHandler);
  4778. if (this.exportDialog != null)
  4779. {
  4780. this.exportDialog.parentNode.removeChild(this.exportDialog);
  4781. this.exportDialog = null;
  4782. }
  4783. });
  4784. if (this.exportDialog != null)
  4785. {
  4786. clickHandler.apply(this);
  4787. }
  4788. else
  4789. {
  4790. this.exportDialog = document.createElement('div');
  4791. var r = exportButton.getBoundingClientRect();
  4792. mxUtils.setPrefixedStyle(this.exportDialog.style, 'borderRadius', '5px');
  4793. this.exportDialog.style.position = 'fixed';
  4794. this.exportDialog.style.textAlign = 'center';
  4795. this.exportDialog.style.fontFamily = Editor.defaultHtmlFont;
  4796. this.exportDialog.style.backgroundColor = '#000000';
  4797. this.exportDialog.style.width = '50px';
  4798. this.exportDialog.style.height = '50px';
  4799. this.exportDialog.style.padding = '4px 2px 4px 2px';
  4800. this.exportDialog.style.color = '#ffffff';
  4801. mxUtils.setOpacity(this.exportDialog, 80);
  4802. this.exportDialog.style.left = r.left + 'px';
  4803. this.exportDialog.style.bottom = parseInt(this.chromelessToolbar.style.bottom) +
  4804. this.chromelessToolbar.offsetHeight + 4 + 'px';
  4805. // Puts the dialog on top of the container z-index
  4806. var style = mxUtils.getCurrentStyle(this.editor.graph.container);
  4807. this.exportDialog.style.zIndex = style.zIndex;
  4808. var spinner = new Spinner({
  4809. lines: 8, // The number of lines to draw
  4810. length: 6, // The length of each line
  4811. width: 5, // The line thickness
  4812. radius: 6, // The radius of the inner circle
  4813. rotate: 0, // The rotation offset
  4814. color: '#fff', // #rgb or #rrggbb
  4815. speed: 1.5, // Rounds per second
  4816. trail: 60, // Afterglow percentage
  4817. shadow: false, // Whether to render a shadow
  4818. hwaccel: false, // Whether to use hardware acceleration
  4819. top: '28px',
  4820. zIndex: 2e9 // The z-index (defaults to 2000000000)
  4821. });
  4822. spinner.spin(this.exportDialog);
  4823. document.body.appendChild(this.exportDialog);
  4824. mxEvent.addListener(this.editor.graph.container, 'click', clickHandler);
  4825. this.editor.exportToCanvas(mxUtils.bind(this, function(canvas)
  4826. {
  4827. spinner.stop();
  4828. this.exportDialog.style.width = 'auto';
  4829. this.exportDialog.style.height = 'auto';
  4830. this.exportDialog.style.padding = '10px';
  4831. var data = this.createImageDataUri(canvas, null, 'png');
  4832. var img = document.createElement('img');
  4833. img.style.maxWidth = '140px';
  4834. img.style.maxHeight = '140px';
  4835. img.style.cursor = 'pointer';
  4836. img.style.backgroundColor = 'white';
  4837. img.setAttribute('title', mxResources.get('openInNewWindow'));
  4838. img.setAttribute('border', '0');
  4839. img.setAttribute('src', data);
  4840. this.exportDialog.appendChild(img);
  4841. mxEvent.addListener(img, 'click', mxUtils.bind(this, function()
  4842. {
  4843. this.openInNewWindow(data.substring(data.indexOf(',') + 1), 'image/png', true);
  4844. clickHandler.apply(this, arguments);
  4845. }));
  4846. }), null, this.thumbImageCache, null, mxUtils.bind(this, function(e)
  4847. {
  4848. spinner.stop();
  4849. if (this.exportDialog != null && this.exportDialog.parentNode != null)
  4850. {
  4851. this.exportDialog.parentNode.removeChild(this.exportDialog);
  4852. this.exportDialog = null;
  4853. }
  4854. this.handleError(e);
  4855. }), null, null, null, null, null, null, null, Editor.defaultBorder);
  4856. }
  4857. mxEvent.consume(evt);
  4858. }), Editor.cameraImage, mxResources.get('export'));
  4859. }
  4860. };
  4861. /**
  4862. * Translates this point by the given vector.
  4863. *
  4864. * @param {number} dx X-coordinate of the translation.
  4865. * @param {number} dy Y-coordinate of the translation.
  4866. */
  4867. EditorUi.prototype.saveData = function(filename, format, data, mime, base64Encoded)
  4868. {
  4869. if (this.isLocalFileSave())
  4870. {
  4871. this.saveLocalFile(data, filename, mime, base64Encoded, format);
  4872. }
  4873. else
  4874. {
  4875. this.saveRequest(filename, format, mxUtils.bind(this, function(newTitle, base64)
  4876. {
  4877. return this.createEchoRequest(data, newTitle, mime, base64Encoded, format, base64);
  4878. }), data, base64Encoded, mime);
  4879. }
  4880. };
  4881. /**
  4882. * Translates this point by the given vector.
  4883. *
  4884. * Last 3 argument are optional and must only be used if the data can be stored as is on the client
  4885. * side without requiring a server roundtrip.
  4886. *
  4887. * @param {number} dx X-coordinate of the translation.
  4888. * @param {number} dy Y-coordinate of the translation.
  4889. */
  4890. EditorUi.prototype.saveRequest = function(filename, format, fn, data, base64Encoded, mimeType, allowTab)
  4891. {
  4892. allowTab = (allowTab != null) ? allowTab : !mxClient.IS_IOS || !navigator.standalone;
  4893. var saveFunction = mxUtils.bind(this, function(newTitle, mode, input, folderId)
  4894. {
  4895. if (mode == '_blank' || newTitle != null && newTitle.length > 0)
  4896. {
  4897. var base64 = (mode == App.MODE_DEVICE || mode == 'download' || mode == null || mode == '_blank') ? '0' : '1';
  4898. var xhr = fn((mode == '_blank') ? null : newTitle, base64);
  4899. if (xhr != null)
  4900. {
  4901. if (mode == App.MODE_DEVICE || mode == 'download' || mode == '_blank')
  4902. {
  4903. xhr.simulate(document, '_blank');
  4904. }
  4905. else
  4906. {
  4907. var doSave = mxUtils.bind(this, function(folderId)
  4908. {
  4909. mimeType = (mimeType != null) ? mimeType : ((format == 'pdf') ?
  4910. 'application/pdf' : 'image/' + format);
  4911. // Workaround for no roundtrip required if data is available on client-side
  4912. // TODO: Refactor the saveData/saveRequest call chain for local data
  4913. if (data != null)
  4914. {
  4915. try
  4916. {
  4917. this.exportFile(data, newTitle, mimeType, true, mode, folderId);
  4918. }
  4919. catch (e)
  4920. {
  4921. this.handleError(e);
  4922. }
  4923. }
  4924. else if (this.spinner.spin(document.body, mxResources.get('saving')))
  4925. {
  4926. // LATER: Catch possible mixed content error
  4927. // see http://stackoverflow.com/questions/30646417/catching-mixed-content-error
  4928. xhr.send(mxUtils.bind(this, function()
  4929. {
  4930. this.spinner.stop();
  4931. if (xhr.getStatus() >= 200 && xhr.getStatus() <= 299)
  4932. {
  4933. try
  4934. {
  4935. this.exportFile(xhr.getText(), newTitle, mimeType, true, mode, folderId);
  4936. }
  4937. catch (e)
  4938. {
  4939. this.handleError(e);
  4940. }
  4941. }
  4942. else
  4943. {
  4944. this.handleError({message: mxResources.get('errorSavingFile')});
  4945. }
  4946. }), mxUtils.bind(this, function(resp)
  4947. {
  4948. this.spinner.stop();
  4949. this.handleError(resp);
  4950. }));
  4951. }
  4952. });
  4953. if (folderId != null)
  4954. {
  4955. doSave(folderId);
  4956. }
  4957. else
  4958. {
  4959. this.pickFolder(mode, doSave);
  4960. }
  4961. }
  4962. }
  4963. }
  4964. });
  4965. var disabled = [App.MODE_BROWSER];
  4966. if (!allowTab)
  4967. {
  4968. disabled.push('_blank');
  4969. }
  4970. var dlg = new SaveDialog(this, filename, mxUtils.bind(this, function(input, mode, folderId)
  4971. {
  4972. saveFunction(input.value, mode, input, folderId);
  4973. this.hideDialog(null, null, dlg.container);
  4974. }), disabled, null, 'application/pdf');
  4975. this.showDialog(dlg.container, 420, 110, true, false, mxUtils.bind(this, function()
  4976. {
  4977. this.hideDialog();
  4978. }));
  4979. dlg.init();
  4980. };
  4981. /**
  4982. * Returns whether or not any services should be shown in dialogs
  4983. */
  4984. EditorUi.prototype.isServices = function(count)
  4985. {
  4986. var noServices = 1; //(mxClient.IS_IOS) ? 0 : 1;
  4987. return count != noServices;
  4988. };
  4989. /**
  4990. *
  4991. */
  4992. EditorUi.prototype.getEditBlankXml = function()
  4993. {
  4994. return this.getFileData(true);
  4995. };
  4996. /**
  4997. * Hook for subclassers.
  4998. */
  4999. EditorUi.prototype.exportFile = function(data, filename, mimeType, base64Encoded, mode, folderId)
  5000. {
  5001. // do nothing
  5002. };
  5003. /**
  5004. * Hook for subclassers.
  5005. */
  5006. EditorUi.prototype.getServiceForName = function(name)
  5007. {
  5008. return null;
  5009. };
  5010. /**
  5011. * Hook for subclassers.
  5012. */
  5013. EditorUi.prototype.getTitleForService = function(name)
  5014. {
  5015. return mxResources.get(name);
  5016. };
  5017. /**
  5018. * Hook for subclassers.
  5019. */
  5020. EditorUi.prototype.pickFolder = function(mode, fn, enabled)
  5021. {
  5022. fn(null);
  5023. };
  5024. /**
  5025. *
  5026. */
  5027. EditorUi.prototype.exportSvg = function(scale, transparentBackground, ignoreSelection, addShadow,
  5028. editable, embedImages, border, noCrop, currentPage, linkTarget, theme, exportType,
  5029. embedFonts, saveFn)
  5030. {
  5031. if (this.spinner.spin(document.body, mxResources.get('exporting'), mxUtils.bind(this, function(err)
  5032. {
  5033. Editor.addRetryToError(err, mxUtils.bind(this, function()
  5034. {
  5035. this.exportSvg(scale, transparentBackground, ignoreSelection, addShadow,
  5036. editable, embedImages, border, noCrop, currentPage, linkTarget,
  5037. theme, exportType, embedFonts, saveFn);
  5038. }));
  5039. this.handleError(err);
  5040. })))
  5041. {
  5042. try
  5043. {
  5044. var selectionEmpty = this.editor.graph.isSelectionEmpty();
  5045. ignoreSelection = (ignoreSelection != null) ? ignoreSelection : selectionEmpty;
  5046. var bg = (transparentBackground) ? null : this.editor.graph.background;
  5047. if (bg == mxConstants.NONE)
  5048. {
  5049. bg = null;
  5050. }
  5051. // Handles special case where background is null but transparent is false
  5052. if (bg == null && transparentBackground == false)
  5053. {
  5054. bg = (theme == 'dark' && !Editor.enableCssDarkMode) ? Editor.darkColor : '#ffffff';
  5055. }
  5056. // Removes global foreignObject warning if image fallback is added for text
  5057. var graphAddForeignObjectWarning = this.editor.graph.addForeignObjectWarning;
  5058. if (Editor.foreignObjectImages)
  5059. {
  5060. this.editor.graph.addForeignObjectWarning = function() {};
  5061. }
  5062. var svgRoot = this.editor.graph.getSvg(bg, scale, border, noCrop, null,
  5063. ignoreSelection, null, null, (linkTarget == 'blank') ? '_blank' :
  5064. ((linkTarget == 'self') ? '_top' : null), null, !embedFonts,
  5065. theme, exportType);
  5066. this.editor.graph.addForeignObjectWarning = graphAddForeignObjectWarning;
  5067. if (addShadow)
  5068. {
  5069. this.editor.graph.addSvgShadow(svgRoot);
  5070. }
  5071. var filename = this.getBaseFilename() + ((editable) ? '.drawio' : '') + '.svg';
  5072. saveFn = (saveFn != null) ? saveFn : mxUtils.bind(this, function(svg)
  5073. {
  5074. if (this.isLocalFileSave() || svg.length <= MAX_REQUEST_SIZE)
  5075. {
  5076. this.saveData(filename, 'svg', svg, 'image/svg+xml');
  5077. }
  5078. else
  5079. {
  5080. this.handleError({message: mxResources.get('drawingTooLarge')},
  5081. mxResources.get('error'), mxUtils.bind(this, function()
  5082. {
  5083. mxUtils.popup(svg);
  5084. }));
  5085. }
  5086. });
  5087. var doSave = mxUtils.bind(this, function(svgRoot)
  5088. {
  5089. this.spinner.stop();
  5090. if (editable)
  5091. {
  5092. svgRoot.setAttribute('content', this.getFileData(true, null, null,
  5093. null, ignoreSelection, currentPage, null, null, null, null,
  5094. null, scale, border));
  5095. }
  5096. saveFn(Graph.xmlDeclaration + '\n' + ((editable) ?
  5097. Graph.svgFileComment + '\n' : '') +
  5098. Graph.svgDoctype + '\n' +
  5099. mxUtils.getXml(svgRoot));
  5100. });
  5101. // Adds CSS
  5102. if (this.editor.graph.mathEnabled)
  5103. {
  5104. this.editor.addMathCss(svgRoot);
  5105. }
  5106. // Replaces alternate content
  5107. var processResult = mxUtils.bind(this, function(svgRoot)
  5108. {
  5109. // Fixes ignored SVG data URIs for Office
  5110. if (Editor.replaceSvgDataUris && embedImages)
  5111. {
  5112. EditorUi.embedSvgImages(svgRoot);
  5113. }
  5114. // Improves foreignObject fallback for Office/Inkscape
  5115. if (Editor.foreignObjectImages && embedFonts)
  5116. {
  5117. this.replaceAlternateContent(svgRoot, theme, doSave);
  5118. }
  5119. else
  5120. {
  5121. doSave(svgRoot);
  5122. }
  5123. });
  5124. var done = mxUtils.bind(this, function(svgRoot)
  5125. {
  5126. if (embedImages && !this.isOffline() && this.canvasSupported)
  5127. {
  5128. // Caches images
  5129. if (this.thumbImageCache == null)
  5130. {
  5131. this.thumbImageCache = new Object();
  5132. }
  5133. this.editor.convertImages(svgRoot, processResult, this.thumbImageCache);
  5134. }
  5135. else
  5136. {
  5137. processResult(svgRoot);
  5138. }
  5139. });
  5140. if (embedFonts)
  5141. {
  5142. this.embedFonts(svgRoot, done);
  5143. }
  5144. else
  5145. {
  5146. this.editor.addFontCss(svgRoot);
  5147. done(svgRoot);
  5148. }
  5149. }
  5150. catch (e)
  5151. {
  5152. this.handleError(e);
  5153. }
  5154. }
  5155. };
  5156. /**
  5157. * Replaces SVG data URIs in images with the actual SVG for
  5158. * the images to be supported in apps like Powerpoint.
  5159. */
  5160. EditorUi.prototype.replaceAlternateContent = function(root, theme, callback)
  5161. {
  5162. // Collects CSS
  5163. var css = '';
  5164. var styles = root.getElementsByTagName('style');
  5165. for (var i = 0; i < styles.length; i++)
  5166. {
  5167. css += styles[i].innerHTML;
  5168. }
  5169. // Replaces alternate content with image of text
  5170. var switches = root.getElementsByTagName('switch');
  5171. counter = switches.length;
  5172. var done = mxUtils.bind(this, function()
  5173. {
  5174. counter--;
  5175. if (counter == 0)
  5176. {
  5177. callback(root);
  5178. }
  5179. });
  5180. // Caching for HTML size and images
  5181. var sizeCache = {};
  5182. var imgCache = {};
  5183. var convert = [];
  5184. function replaceElt(switchElt, x, y, size, src)
  5185. {
  5186. var node = mxUtils.createElementNs(root.ownerDocument,
  5187. mxConstants.NS_SVG, 'image');
  5188. node.setAttribute('x', x);
  5189. node.setAttribute('y', y + 0.5);
  5190. node.setAttribute('width', size.width);
  5191. node.setAttribute('height', size.height);
  5192. // Workaround for missing namespace support
  5193. if (node.setAttributeNS == null)
  5194. {
  5195. node.setAttribute('xlink:href', src);
  5196. }
  5197. else
  5198. {
  5199. node.setAttributeNS(mxConstants.NS_XLINK, 'xlink:href', src);
  5200. }
  5201. switchElt.replaceChild(node, switchElt.lastChild);
  5202. done();
  5203. };
  5204. // Create an image for the given foreignObject
  5205. for (var i = 0; i < switches.length; i++)
  5206. {
  5207. (mxUtils.bind(this, function(switchElt)
  5208. {
  5209. var divs = switchElt.getElementsByTagName('div');
  5210. if (divs.length > 0)
  5211. {
  5212. try
  5213. {
  5214. var temp = divs[0].cloneNode(true);
  5215. var x = parseInt(temp.style.marginLeft);
  5216. var y = parseInt(temp.style.paddingTop);
  5217. var measure = temp.getElementsByTagName('div')[1];
  5218. var rl = false;
  5219. var vertical = false;
  5220. var alignItems = temp.style.alignItems;
  5221. var justifyContent = temp.style.justifyContent;
  5222. if (temp.style.writingMode != null)
  5223. {
  5224. var writingMode = temp.style.writingMode;
  5225. vertical = writingMode.substring(0, 9) == 'vertical-';
  5226. rl = writingMode.substring(writingMode.length - 3) == '-rl';
  5227. }
  5228. var h1 = (!vertical && temp.style.height == '1px') ||
  5229. (vertical && temp.style.width == '1px');
  5230. var w1 = (!vertical && temp.style.width == '1px') ||
  5231. (vertical && temp.style.height == '1px');
  5232. if (h1)
  5233. {
  5234. if (vertical && rl)
  5235. {
  5236. temp.style.alignItems = 'unsafe flex-end';
  5237. }
  5238. else
  5239. {
  5240. temp.style.alignItems = 'unsafe flex-start';
  5241. }
  5242. }
  5243. if (w1)
  5244. {
  5245. temp.style.justifyContent = 'unsafe flex-start';
  5246. }
  5247. temp.style.paddingTop = '';
  5248. temp.style.marginLeft = '';
  5249. var html = temp.outerHTML;
  5250. var size = sizeCache[html];
  5251. if (size == null)
  5252. {
  5253. temp.style.position = 'absolute';
  5254. temp.style.visibility = 'hidden';
  5255. document.body.appendChild(temp);
  5256. var w = 1;
  5257. if (temp.style.width == '1px')
  5258. {
  5259. w = measure.offsetWidth;
  5260. }
  5261. else
  5262. {
  5263. w = parseInt(temp.style.width);
  5264. }
  5265. var h = 1;
  5266. if (temp.style.height == '1px')
  5267. {
  5268. h = measure.offsetHeight;
  5269. }
  5270. else
  5271. {
  5272. h = parseInt(temp.style.height);
  5273. }
  5274. document.body.removeChild(temp);
  5275. size = new mxRectangle(x, y, w, h);
  5276. sizeCache[html] = size;
  5277. }
  5278. if (temp.style.height == '1px')
  5279. {
  5280. if (alignItems == 'unsafe center')
  5281. {
  5282. y -= size.height / 2;
  5283. }
  5284. else if (alignItems == 'unsafe flex-end')
  5285. {
  5286. y -= size.height;
  5287. }
  5288. }
  5289. if (temp.style.width == '1px')
  5290. {
  5291. if ((!vertical && justifyContent == 'unsafe center') ||
  5292. (vertical && alignItems == 'unsafe center'))
  5293. {
  5294. x -= size.width / 2;
  5295. }
  5296. else if ((!vertical && justifyContent == 'unsafe flex-end') ||
  5297. (vertical && ((!rl && alignItems == 'unsafe flex-end') ||
  5298. (rl && alignItems == 'unsafe flex-start'))))
  5299. {
  5300. x -= size.width;
  5301. }
  5302. }
  5303. // Adds padding for font metrics
  5304. size = new mxRectangle(x, y, size.width, size.height +
  5305. (parseInt(measure.style.fontSize) / 4));
  5306. // Sequence of async conversion to images
  5307. convert.push(mxUtils.bind(this, function(next)
  5308. {
  5309. var cachedSrc = imgCache[html];
  5310. if (cachedSrc == null)
  5311. {
  5312. Graph.htmlToPng(html, size.width, size.height, mxUtils.bind(this, function(src)
  5313. {
  5314. replaceElt(switchElt, x, y, size, src);
  5315. imgCache[html] = src;
  5316. next();
  5317. }), css);
  5318. }
  5319. else
  5320. {
  5321. replaceElt(switchElt, x, y, size, cachedSrc);
  5322. next();
  5323. }
  5324. }));
  5325. }
  5326. catch (e)
  5327. {
  5328. done();
  5329. }
  5330. }
  5331. else
  5332. {
  5333. done();
  5334. }
  5335. }))(switches[i]);
  5336. }
  5337. if (counter == 0)
  5338. {
  5339. callback(root);
  5340. }
  5341. var i = 0;
  5342. function next()
  5343. {
  5344. if (i < convert.length)
  5345. {
  5346. convert[i++](next);
  5347. }
  5348. };
  5349. next();
  5350. };
  5351. /**
  5352. *
  5353. */
  5354. EditorUi.prototype.addRadiobox = function(div, radioGroupName, label, checked, disabled, disableNewline, visible)
  5355. {
  5356. return this.addCheckbox(div, label, checked, disabled, disableNewline, visible, true, radioGroupName);
  5357. };
  5358. /**
  5359. *
  5360. */
  5361. EditorUi.prototype.addCheckbox = function(div, label, checked, disabled, disableNewline, visible, asRadio, radioGroupName)
  5362. {
  5363. visible = (visible != null) ? visible : true;
  5364. var cb = document.createElement('input');
  5365. cb.style.marginRight = '8px';
  5366. cb.style.marginTop = '16px';
  5367. cb.setAttribute('type', asRadio? 'radio' : 'checkbox');
  5368. var id = 'geCheckbox-' + Editor.guid();
  5369. cb.id = id;
  5370. if (radioGroupName != null)
  5371. {
  5372. cb.setAttribute('name', radioGroupName);
  5373. }
  5374. if (checked)
  5375. {
  5376. cb.setAttribute('checked', 'checked');
  5377. cb.defaultChecked = true;
  5378. }
  5379. if (disabled)
  5380. {
  5381. cb.setAttribute('disabled', 'disabled');
  5382. }
  5383. if (visible)
  5384. {
  5385. div.appendChild(cb);
  5386. var lbl = document.createElement('label');
  5387. mxUtils.write(lbl, label);
  5388. lbl.setAttribute('for', id);
  5389. div.appendChild(lbl);
  5390. if (!disableNewline)
  5391. {
  5392. mxUtils.br(div);
  5393. }
  5394. }
  5395. return cb;
  5396. };
  5397. /**
  5398. *
  5399. */
  5400. EditorUi.prototype.addEditButton = function(div, lightbox)
  5401. {
  5402. var edit = this.addCheckbox(div, mxResources.get('edit') + ':', true, null, true);
  5403. edit.style.marginLeft = '24px';
  5404. var file = this.getCurrentFile();
  5405. var editUrl = '';
  5406. if (file != null && file.getMode() != App.MODE_DEVICE && file.getMode() != App.MODE_BROWSER)
  5407. {
  5408. editUrl = window.location.href;
  5409. }
  5410. var editSelect = document.createElement('select');
  5411. editSelect.style.maxWidth = '200px';
  5412. editSelect.style.width = 'auto';
  5413. editSelect.style.marginLeft = '8px';
  5414. editSelect.style.marginRight = '10px';
  5415. editSelect.className = 'geBtn';
  5416. var blankOption = document.createElement('option');
  5417. blankOption.setAttribute('value', 'blank');
  5418. mxUtils.write(blankOption, mxResources.get('makeCopy'));
  5419. editSelect.appendChild(blankOption);
  5420. var customOption = document.createElement('option');
  5421. customOption.setAttribute('value', 'custom');
  5422. mxUtils.write(customOption, mxResources.get('custom') + '...');
  5423. editSelect.appendChild(customOption);
  5424. div.appendChild(editSelect);
  5425. mxEvent.addListener(editSelect, 'change', mxUtils.bind(this, function()
  5426. {
  5427. if (editSelect.value == 'custom')
  5428. {
  5429. var dlg2 = new FilenameDialog(this, editUrl, mxResources.get('ok'), function(value)
  5430. {
  5431. if (value != null)
  5432. {
  5433. editUrl = value;
  5434. customOption.setAttribute('title', value);
  5435. }
  5436. else
  5437. {
  5438. editSelect.value = 'blank';
  5439. customOption.removeAttribute('title');
  5440. }
  5441. }, mxResources.get('url'), null, null, null, null, function()
  5442. {
  5443. editSelect.value = 'blank';
  5444. });
  5445. this.showDialog(dlg2.container, 300, 80, true, false);
  5446. dlg2.init();
  5447. }
  5448. }));
  5449. mxEvent.addListener(edit, 'change', mxUtils.bind(this, function()
  5450. {
  5451. if (edit.checked && (lightbox == null || lightbox.checked))
  5452. {
  5453. editSelect.removeAttribute('disabled');
  5454. }
  5455. else
  5456. {
  5457. editSelect.setAttribute('disabled', 'disabled');
  5458. }
  5459. }));
  5460. mxUtils.br(div);
  5461. return {
  5462. getLink: function()
  5463. {
  5464. return (edit.checked) ? ((editSelect.value === 'blank') ? '_blank' : editUrl) : null;
  5465. },
  5466. getEditInput: function()
  5467. {
  5468. return edit;
  5469. },
  5470. getEditSelect: function()
  5471. {
  5472. return editSelect;
  5473. }
  5474. };
  5475. }
  5476. /**
  5477. *
  5478. */
  5479. EditorUi.prototype.addLinkSection = function(div, showFrameOption)
  5480. {
  5481. mxUtils.write(div, mxResources.get('links') + ':');
  5482. var linkSelect = document.createElement('select');
  5483. linkSelect.style.width = '100px';
  5484. linkSelect.style.padding = '0px';
  5485. linkSelect.style.marginLeft = '8px';
  5486. linkSelect.style.marginRight = '10px';
  5487. linkSelect.className = 'geBtn';
  5488. var autoOption = document.createElement('option');
  5489. autoOption.setAttribute('value', 'auto');
  5490. mxUtils.write(autoOption, mxResources.get('automatic'));
  5491. linkSelect.appendChild(autoOption);
  5492. var blankOption = document.createElement('option');
  5493. blankOption.setAttribute('value', 'blank');
  5494. mxUtils.write(blankOption, mxResources.get('openInNewWindow'));
  5495. linkSelect.appendChild(blankOption);
  5496. var selfOption = document.createElement('option');
  5497. selfOption.setAttribute('value', 'self');
  5498. mxUtils.write(selfOption, mxResources.get('openInThisWindow'));
  5499. linkSelect.appendChild(selfOption);
  5500. if (showFrameOption)
  5501. {
  5502. var frameOption = document.createElement('option');
  5503. frameOption.setAttribute('value', 'frame');
  5504. mxUtils.write(frameOption, mxResources.get('openInThisWindow') +
  5505. ' (' + mxResources.get('iframe') + ')');
  5506. linkSelect.appendChild(frameOption);
  5507. }
  5508. div.appendChild(linkSelect);
  5509. mxUtils.write(div, mxResources.get('borderColor') + ':');
  5510. var linkColor = '#0000ff';
  5511. var linkButton = null;
  5512. function updateLinkColor()
  5513. {
  5514. var div = document.createElement('div');
  5515. div.style.width = '100%';
  5516. div.style.height = '100%';
  5517. div.style.boxSizing = 'border-box';
  5518. if (linkColor != null && linkColor != mxConstants.NONE)
  5519. {
  5520. div.style.border = '1px solid black';
  5521. div.style.backgroundColor = linkColor;
  5522. }
  5523. else
  5524. {
  5525. div.style.backgroundPosition = 'center center';
  5526. div.style.backgroundRepeat = 'no-repeat';
  5527. div.style.backgroundImage = 'url(\'' + Dialog.prototype.closeImage + '\')';
  5528. }
  5529. linkButton.innerText = '';
  5530. linkButton.appendChild(div);
  5531. };
  5532. linkButton = mxUtils.button('', mxUtils.bind(this, function(evt)
  5533. {
  5534. this.pickColor(linkColor || 'none', function(color)
  5535. {
  5536. linkColor = color;
  5537. updateLinkColor();
  5538. });
  5539. mxEvent.consume(evt);
  5540. }));
  5541. updateLinkColor();
  5542. linkButton.style.padding = (mxClient.IS_FF) ? '4px 2px 4px 2px' : '4px';
  5543. linkButton.style.marginLeft = '4px';
  5544. linkButton.style.height = '22px';
  5545. linkButton.style.width = '22px';
  5546. linkButton.style.position = 'relative';
  5547. linkButton.style.top = (mxClient.IS_IE || mxClient.IS_IE11 || mxClient.IS_EDGE) ? '6px' : '1px';
  5548. linkButton.className = 'geColorBtn';
  5549. div.appendChild(linkButton);
  5550. mxUtils.br(div);
  5551. return {
  5552. getColor: function()
  5553. {
  5554. return linkColor;
  5555. },
  5556. getTarget: function()
  5557. {
  5558. return linkSelect.value;
  5559. },
  5560. focus: function()
  5561. {
  5562. linkSelect.focus();
  5563. }
  5564. };
  5565. }
  5566. /**
  5567. *
  5568. */
  5569. EditorUi.prototype.createUrlParameters = function(linkTarget, linkColor, lightbox, editLink, layers, params)
  5570. {
  5571. params = (params != null) ? params : [];
  5572. if (lightbox)
  5573. {
  5574. params.push('lightbox=1');
  5575. if (linkTarget != 'auto')
  5576. {
  5577. params.push('target=' + linkTarget);
  5578. }
  5579. if (linkColor != null && linkColor != mxConstants.NONE)
  5580. {
  5581. params.push('highlight=' + ((linkColor.charAt(0) == '#') ?
  5582. linkColor.substring(1) : linkColor));
  5583. }
  5584. if (editLink != null && editLink.length > 0)
  5585. {
  5586. params.push('edit=' + encodeURIComponent(editLink));
  5587. }
  5588. if (layers)
  5589. {
  5590. params.push('layers=1');
  5591. }
  5592. if (this.editor.graph.foldingEnabled)
  5593. {
  5594. params.push('nav=1');
  5595. }
  5596. }
  5597. return params;
  5598. };
  5599. /**
  5600. *
  5601. */
  5602. EditorUi.prototype.createLink = function(linkTarget, linkColor, allPages, lightbox, editLink, layers,
  5603. url, ignoreFile, params, useOpenParameter, currentPage)
  5604. {
  5605. var file = this.getCurrentFile();
  5606. params = this.createUrlParameters(linkTarget, linkColor,
  5607. lightbox, editLink, layers, params);
  5608. var addTitle = true;
  5609. var data = '';
  5610. if (url != null)
  5611. {
  5612. data = '#U' + encodeURIComponent(url);
  5613. }
  5614. else
  5615. {
  5616. // Fallback to non-public URL for Drive files
  5617. if (!ignoreFile && file != null && file.getHash() != '')
  5618. {
  5619. data = '#' + file.getHash();
  5620. addTitle = false;
  5621. }
  5622. else
  5623. {
  5624. data = '#R' + encodeURIComponent((allPages) ?
  5625. this.getFileData(true, null, null, null, null, null, null, true, null, false) :
  5626. Graph.compress(mxUtils.getXml(this.editor.getGraphXml())))
  5627. }
  5628. }
  5629. if (addTitle && file != null && file.getTitle() != null && file.getTitle() != this.defaultFilename)
  5630. {
  5631. params.push('title=' + encodeURIComponent(file.getTitle()));
  5632. }
  5633. if (useOpenParameter && data.length > 1)
  5634. {
  5635. params.push('open=' + data.substring(1));
  5636. data = '';
  5637. }
  5638. if (currentPage && this.currentPage != null)
  5639. {
  5640. params.push('page-id=' + this.currentPage.getId());
  5641. }
  5642. // Uses current host for non-public GitLab and GitHub files
  5643. return ((lightbox && (url != null || (file != null &&
  5644. file.getMode() != App.MODE_GITHUB && file.getMode() != App.MODE_GITLAB))) ?
  5645. EditorUi.lightboxHost : (((mxClient.IS_CHROMEAPP || EditorUi.isElectronApp ||
  5646. !(/.*\.draw\.io$/.test(window.location.hostname))) ?
  5647. EditorUi.drawHost : 'https://' + window.location.host))) + '/' +
  5648. ((params.length > 0) ? '?' + params.join('&') : '') + data;
  5649. };
  5650. /**
  5651. *
  5652. */
  5653. EditorUi.prototype.createHtml = function(publicUrl, zoomEnabled, initialZoom, linkTarget,
  5654. linkColor, fit, allPages, layers, tags, lightbox, editLink, fn)
  5655. {
  5656. var s = this.getBasenames();
  5657. var data = {};
  5658. if (linkColor != '' && linkColor != mxConstants.NONE)
  5659. {
  5660. data.highlight = linkColor;
  5661. }
  5662. if (linkTarget !== 'auto')
  5663. {
  5664. data.target = linkTarget;
  5665. }
  5666. if (!lightbox)
  5667. {
  5668. data.lightbox = false;
  5669. }
  5670. data.nav = this.editor.graph.foldingEnabled;
  5671. var zoom = parseInt(initialZoom);
  5672. if (!isNaN(zoom) && zoom != 100)
  5673. {
  5674. data.zoom = zoom / 100;
  5675. }
  5676. var tb = [];
  5677. if (allPages)
  5678. {
  5679. tb.push('pages');
  5680. data.resize = true;
  5681. if (this.pages != null && this.currentPage != null)
  5682. {
  5683. data.page = mxUtils.indexOf(this.pages, this.currentPage);
  5684. }
  5685. }
  5686. if (zoomEnabled)
  5687. {
  5688. tb.push('zoom');
  5689. data.resize = true;
  5690. }
  5691. if (layers)
  5692. {
  5693. tb.push('layers');
  5694. }
  5695. if (tags)
  5696. {
  5697. tb.push('tags');
  5698. }
  5699. if (tb.length > 0)
  5700. {
  5701. if (lightbox)
  5702. {
  5703. tb.push('lightbox');
  5704. }
  5705. data.toolbar = tb.join(' ');
  5706. }
  5707. if (editLink != null && editLink.length > 0)
  5708. {
  5709. data.edit = editLink;
  5710. }
  5711. if (publicUrl != null)
  5712. {
  5713. data.url = publicUrl;
  5714. }
  5715. else
  5716. {
  5717. data.xml = this.getFileData(true, null, null, null, null, !allPages);
  5718. }
  5719. var value = '<div class="mxgraph" style="' +
  5720. ((fit) ? 'max-width:100%;' : '') +
  5721. ((tb != '') ? 'border:1px solid transparent;' : '') +
  5722. '" data-mxgraph="' + mxUtils.htmlEntities(JSON.stringify(data)) + '"></div>';
  5723. var fetchParam = (publicUrl != null) ? '&fetch=' + encodeURIComponent(publicUrl) : '';
  5724. var s2 = (fetchParam.length > 0) ? (((urlParams['dev'] == '1') ?
  5725. 'https://test.draw.io/embed2.js?dev=1' : EditorUi.lightboxHost + '/embed2.js?')) + fetchParam :
  5726. (((urlParams['dev'] == '1') ? 'https://test.draw.io/js/viewer-static.min.js' :
  5727. window.DRAWIO_VIEWER_URL ? window.DRAWIO_VIEWER_URL : EditorUi.lightboxHost + '/js/viewer-static.min.js'));
  5728. var src = '<script type="text/javascript" src="' + s2 + '"></script>';
  5729. fn(value, src);
  5730. };
  5731. /**
  5732. *
  5733. */
  5734. EditorUi.prototype.showHtmlDialog = function(btnLabel, helpLink, publicUrl, fn)
  5735. {
  5736. var div = document.createElement('div');
  5737. div.style.whiteSpace = 'nowrap';
  5738. var hd = document.createElement('h3');
  5739. mxUtils.write(hd, mxResources.get('html'));
  5740. hd.style.cssText = 'width:100%;text-align:center;margin-top:0px;margin-bottom:12px';
  5741. div.appendChild(hd);
  5742. var radioSection = document.createElement('div');
  5743. radioSection.style.cssText = 'border-bottom:1px solid lightGray;padding-bottom:8px;margin-bottom:12px;';
  5744. var publicUrlRadio = document.createElement('input');
  5745. publicUrlRadio.style.cssText = 'margin-right:8px;margin-top:8px;margin-bottom:8px;';
  5746. publicUrlRadio.setAttribute('value', 'url');
  5747. publicUrlRadio.setAttribute('type', 'radio');
  5748. publicUrlRadio.setAttribute('name', 'type-embedhtmldialog');
  5749. var copyRadio = publicUrlRadio.cloneNode(true);
  5750. copyRadio.setAttribute('value', 'copy');
  5751. radioSection.appendChild(copyRadio);
  5752. var span = document.createElement('span');
  5753. mxUtils.write(span, mxResources.get('includeCopyOfMyDiagram'));
  5754. radioSection.appendChild(span);
  5755. mxUtils.br(radioSection);
  5756. radioSection.appendChild(publicUrlRadio);
  5757. var span = document.createElement('span');
  5758. mxUtils.write(span, mxResources.get('publicDiagramUrl'));
  5759. radioSection.appendChild(span);
  5760. var file = this.getCurrentFile();
  5761. if (publicUrl == null && file != null && file.constructor == window.DriveFile)
  5762. {
  5763. var testLink = document.createElement('a');
  5764. testLink.style.paddingLeft = '12px';
  5765. testLink.style.color = 'gray';
  5766. testLink.style.cursor = 'pointer';
  5767. mxUtils.write(testLink, mxResources.get('share'));
  5768. radioSection.appendChild(testLink);
  5769. mxEvent.addListener(testLink, 'click', mxUtils.bind(this, function()
  5770. {
  5771. this.hideDialog();
  5772. this.drive.showPermissions(file.getId(), file);
  5773. }));
  5774. }
  5775. copyRadio.setAttribute('checked', 'checked');
  5776. if (publicUrl == null)
  5777. {
  5778. publicUrlRadio.setAttribute('disabled', 'disabled');
  5779. }
  5780. div.appendChild(radioSection);
  5781. var linkSection = this.addLinkSection(div);
  5782. var zoom = this.addCheckbox(div, mxResources.get('zoom'), true, null, true);
  5783. mxUtils.write(div, ':');
  5784. var zoomInput = document.createElement('input');
  5785. zoomInput.setAttribute('type', 'text');
  5786. zoomInput.style.marginRight = '16px';
  5787. zoomInput.style.width = '60px';
  5788. zoomInput.style.marginLeft = '4px';
  5789. zoomInput.style.marginRight = '12px';
  5790. zoomInput.value = '100%';
  5791. div.appendChild(zoomInput);
  5792. var fit = this.addCheckbox(div, mxResources.get('fit'), true);
  5793. var hasPages = this.pages != null && this.pages.length > 1;
  5794. var allPages = allPages = this.addCheckbox(div, mxResources.get('allPages'), hasPages, !hasPages);
  5795. var layers = this.addCheckbox(div, mxResources.get('layers'), true);
  5796. var tags = this.addCheckbox(div, mxResources.get('tags'), true);
  5797. var lightbox = this.addCheckbox(div, mxResources.get('lightbox'), true);
  5798. var editSection = null;
  5799. var h = 380;
  5800. if (EditorUi.enableHtmlEditOption)
  5801. {
  5802. editSection = this.addEditButton(div, lightbox);
  5803. var edit = editSection.getEditInput();
  5804. edit.style.marginBottom = '16px';
  5805. h += 50;
  5806. mxEvent.addListener(lightbox, 'change', function()
  5807. {
  5808. if (lightbox.checked)
  5809. {
  5810. edit.removeAttribute('disabled');
  5811. }
  5812. else
  5813. {
  5814. edit.setAttribute('disabled', 'disabled');
  5815. }
  5816. if (edit.checked && lightbox.checked)
  5817. {
  5818. editSection.getEditSelect().removeAttribute('disabled');
  5819. }
  5820. else
  5821. {
  5822. editSection.getEditSelect().setAttribute('disabled', 'disabled');
  5823. }
  5824. });
  5825. }
  5826. var dlg = new CustomDialog(this, div, mxUtils.bind(this, function()
  5827. {
  5828. fn((publicUrlRadio.checked) ? publicUrl : null, zoom.checked, zoomInput.value, linkSection.getTarget(),
  5829. linkSection.getColor(), fit.checked, allPages.checked, layers.checked, tags.checked,
  5830. lightbox.checked, (editSection != null) ? editSection.getLink() : null);
  5831. }), null, btnLabel, helpLink);
  5832. this.showDialog(dlg.container, 340, h, true, true);
  5833. copyRadio.focus();
  5834. };
  5835. /**
  5836. *
  5837. */
  5838. EditorUi.prototype.showPublishLinkDialog = function(title, width, height, showFrameOption,
  5839. helpLink, footer, publicUrl, file, fn)
  5840. {
  5841. var div = document.createElement('div');
  5842. div.style.whiteSpace = 'nowrap';
  5843. var hd = document.createElement('h3');
  5844. mxUtils.write(hd, title || mxResources.get('publish'));
  5845. hd.style.width = '100%';
  5846. hd.style.textAlign = 'center';
  5847. hd.style.marginTop = '2px';
  5848. hd.style.marginBottom = '20px';
  5849. div.appendChild(hd);
  5850. var linkSelect = document.createElement('select');
  5851. var dy = 30;
  5852. linkSelect.className = 'geBtn';
  5853. linkSelect.style.marginBottom = '8px';
  5854. linkSelect.style.marginLeft = '0px';
  5855. linkSelect.style.width = '100%';
  5856. linkSelect.style.boxSizing = 'border-box';
  5857. if (file == null || file.getHash() == '')
  5858. {
  5859. helpLink = (helpLink != null) ? helpLink : 'https://www.drawio.com/doc/faq/publish-diagram-as-link';
  5860. var makeCopy = document.createElement('option');
  5861. mxUtils.write(makeCopy, mxResources.get('makeCopy'));
  5862. makeCopy.setAttribute('value', 'copy');
  5863. linkSelect.appendChild(makeCopy);
  5864. }
  5865. var authRequired = document.createElement('option');
  5866. mxUtils.write(authRequired, mxResources.get('authorizationRequired'));
  5867. authRequired.setAttribute('value', 'auth');
  5868. linkSelect.appendChild(authRequired);
  5869. if (file == null || file.getHash() == '')
  5870. {
  5871. authRequired.setAttribute('disabled', 'disabled');
  5872. }
  5873. var publicLink = document.createElement('option');
  5874. publicLink.setAttribute('value', 'public');
  5875. linkSelect.appendChild(publicLink);
  5876. if (publicUrl != null)
  5877. {
  5878. mxUtils.write(publicLink, mxResources.get('publicDiagramUrl'));
  5879. publicLink.setAttribute('title', publicUrl);
  5880. linkSelect.value = 'public';
  5881. }
  5882. else
  5883. {
  5884. mxUtils.write(publicLink, mxResources.get('publicDiagramUrl') +
  5885. ' (' + mxResources.get('diagramIsNotPublic') + ')');
  5886. publicLink.setAttribute('disabled', 'disabled');
  5887. }
  5888. div.appendChild(linkSelect);
  5889. mxUtils.br(div);
  5890. linkSelect.focus();
  5891. var widthInput = null;
  5892. var heightInput = null;
  5893. if (width != null || height != null)
  5894. {
  5895. dy += 30;
  5896. mxUtils.write(div, mxResources.get('width') + ':');
  5897. widthInput = document.createElement('input');
  5898. widthInput.setAttribute('type', 'text');
  5899. widthInput.style.marginRight = '16px';
  5900. widthInput.style.width = '50px';
  5901. widthInput.style.marginLeft = '6px';
  5902. widthInput.style.marginRight = '16px';
  5903. widthInput.style.marginBottom = '10px';
  5904. widthInput.value = '100%';
  5905. div.appendChild(widthInput);
  5906. mxUtils.write(div, mxResources.get('height') + ':');
  5907. heightInput = document.createElement('input');
  5908. heightInput.setAttribute('type', 'text');
  5909. heightInput.style.width = '50px';
  5910. heightInput.style.marginLeft = '6px';
  5911. heightInput.style.marginBottom = '10px';
  5912. heightInput.value = height + 'px';
  5913. div.appendChild(heightInput);
  5914. mxUtils.br(div);
  5915. }
  5916. var linkSection = this.addLinkSection(div, showFrameOption);
  5917. var currentPage = null;
  5918. if (this.pages != null && this.currentPage != null &&
  5919. this.getPageIndex(this.currentPage) > 0)
  5920. {
  5921. dy += 30;
  5922. var name = (this.currentPage != null) ? this.currentPage.getName() : '';
  5923. if (name.length > 16)
  5924. {
  5925. name = name.substring(0, 16) + '...';
  5926. }
  5927. currentPage = this.addCheckbox(div, mxResources.get('selectedPage') + ': ' + name);
  5928. }
  5929. var lightbox = this.addCheckbox(div, mxResources.get('lightbox'),
  5930. true, null, null, !showFrameOption);
  5931. var editSection = this.addEditButton(div, lightbox);
  5932. var edit = editSection.getEditInput();
  5933. // Cannot disable lightbox in iframes
  5934. if (showFrameOption)
  5935. {
  5936. edit.style.marginLeft = lightbox.style.marginLeft;
  5937. lightbox.style.display = 'none';
  5938. dy -= 20;
  5939. }
  5940. var layers = this.addCheckbox(div, mxResources.get('layers'), true);
  5941. layers.style.marginLeft = edit.style.marginLeft;
  5942. layers.style.marginTop = '8px';
  5943. var tags = this.addCheckbox(div, mxResources.get('tags'), true);
  5944. tags.style.marginLeft = edit.style.marginLeft;
  5945. tags.style.marginBottom = '16px';
  5946. tags.style.marginTop = '16px';
  5947. mxEvent.addListener(lightbox, 'change', function()
  5948. {
  5949. if (lightbox.checked)
  5950. {
  5951. layers.removeAttribute('disabled');
  5952. edit.removeAttribute('disabled');
  5953. tags.removeAttribute('disabled');
  5954. }
  5955. else
  5956. {
  5957. layers.setAttribute('disabled', 'disabled');
  5958. edit.setAttribute('disabled', 'disabled');
  5959. tags.setAttribute('disabled', 'disabled');
  5960. }
  5961. if (edit.checked && lightbox.checked)
  5962. {
  5963. editSection.getEditSelect().removeAttribute('disabled');
  5964. }
  5965. else
  5966. {
  5967. editSection.getEditSelect().setAttribute('disabled', 'disabled');
  5968. }
  5969. });
  5970. var dlg = new CustomDialog(this, div, mxUtils.bind(this, function()
  5971. {
  5972. fn(linkSection.getTarget(), linkSection.getColor(),
  5973. (currentPage == null) ? false : currentPage.checked,
  5974. lightbox.checked, editSection.getLink(), layers.checked,
  5975. (widthInput != null) ? widthInput.value : null,
  5976. (heightInput != null) ? heightInput.value : null,
  5977. tags.checked, linkSelect.value);
  5978. }), null, mxResources.get('create'), helpLink, footer);
  5979. this.showDialog(dlg.container, 340, 296 + dy, true, true);
  5980. if (widthInput != null)
  5981. {
  5982. widthInput.focus();
  5983. if (mxClient.IS_GC || mxClient.IS_FF || document.documentMode >= 5)
  5984. {
  5985. widthInput.select();
  5986. }
  5987. else
  5988. {
  5989. document.execCommand('selectAll', false, null);
  5990. }
  5991. }
  5992. else (linkSelect.parentNode == null)
  5993. {
  5994. linkSection.focus();
  5995. }
  5996. };
  5997. /**
  5998. *
  5999. */
  6000. EditorUi.prototype.showRemoteExportDialog = function(btnLabel, helpLink, callback, hideInclude, showZoomBorder)
  6001. {
  6002. var div = document.createElement('div');
  6003. div.style.whiteSpace = 'nowrap';
  6004. var hd = document.createElement('h3');
  6005. mxUtils.write(hd, mxResources.get('image'));
  6006. hd.style.cssText = 'width:100%;text-align:center;margin-top:0px;margin-bottom:' + (showZoomBorder? '10' : '4') +'px';
  6007. div.appendChild(hd);
  6008. if (showZoomBorder)
  6009. {
  6010. mxUtils.write(div, mxResources.get('zoom') + ':');
  6011. var zoomInput = document.createElement('input');
  6012. zoomInput.setAttribute('type', 'text');
  6013. zoomInput.style.marginRight = '16px';
  6014. zoomInput.style.width = '60px';
  6015. zoomInput.style.marginLeft = '4px';
  6016. zoomInput.style.marginRight = '12px';
  6017. zoomInput.value = this.lastExportZoom || '100%';
  6018. div.appendChild(zoomInput);
  6019. mxUtils.write(div, mxResources.get('borderWidth') + ':');
  6020. var borderInput = document.createElement('input');
  6021. borderInput.setAttribute('type', 'text');
  6022. borderInput.style.marginRight = '16px';
  6023. borderInput.style.width = '60px';
  6024. borderInput.style.marginLeft = '4px';
  6025. borderInput.value = this.lastExportBorder || '0';
  6026. div.appendChild(borderInput);
  6027. mxUtils.br(div);
  6028. }
  6029. var selection = this.addCheckbox(div, mxResources.get('selectionOnly'), false,
  6030. this.editor.graph.isSelectionEmpty());
  6031. var include = (hideInclude) ? null : this.addCheckbox(div, mxResources.get('includeCopyOfMyDiagram'),
  6032. Editor.defaultIncludeDiagram);
  6033. var graph = this.editor.graph;
  6034. var transparent = (hideInclude) ? null : this.addCheckbox(div, mxResources.get('transparentBackground'),
  6035. graph.background == mxConstants.NONE || graph.background == null);
  6036. if (transparent != null)
  6037. {
  6038. transparent.style.marginBottom = '16px';
  6039. }
  6040. var dlg = new CustomDialog(this, div, mxUtils.bind(this, function()
  6041. {
  6042. var scale = parseInt(zoomInput.value) / 100 || 1;
  6043. var border = parseInt(borderInput.value) || 0;
  6044. callback(!selection.checked, (include != null) ? include.checked : false,
  6045. (transparent != null) ? transparent.checked : false, scale, border);
  6046. }), null, btnLabel, helpLink);
  6047. this.showDialog(dlg.container, 300, (showZoomBorder? 25 : 0) + (hideInclude ? 125 : 210), true, true);
  6048. };
  6049. /**
  6050. *
  6051. */
  6052. EditorUi.prototype.showExportDialog = function(title, embedOption, btnLabel, helpLink, callback,
  6053. cropOption, defaultInclude, format, exportOption)
  6054. {
  6055. defaultInclude = (defaultInclude != null) ? defaultInclude : Editor.defaultIncludeDiagram;
  6056. var div = document.createElement('div');
  6057. div.style.whiteSpace = 'nowrap';
  6058. var graph = this.editor.graph;
  6059. var height = (format == 'jpeg' || format == 'webp') ? 220 : 320;
  6060. var hd = document.createElement('h3');
  6061. mxUtils.write(hd, title);
  6062. hd.style.cssText = 'width:100%;text-align:center;margin-top:0px;margin-bottom:10px';
  6063. div.appendChild(hd);
  6064. mxUtils.write(div, mxResources.get('zoom') + ':');
  6065. var zoomInput = document.createElement('input');
  6066. zoomInput.setAttribute('type', 'text');
  6067. zoomInput.style.marginRight = '16px';
  6068. zoomInput.style.width = '60px';
  6069. zoomInput.style.marginLeft = '4px';
  6070. zoomInput.style.marginRight = '12px';
  6071. zoomInput.value = this.lastExportZoom || '100%';
  6072. div.appendChild(zoomInput);
  6073. mxUtils.write(div, mxResources.get('borderWidth') + ':');
  6074. var borderInput = document.createElement('input');
  6075. borderInput.setAttribute('type', 'text');
  6076. borderInput.style.marginRight = '16px';
  6077. borderInput.style.width = '60px';
  6078. borderInput.style.marginLeft = '4px';
  6079. borderInput.value = this.lastExportBorder || '0';
  6080. div.appendChild(borderInput);
  6081. mxUtils.br(div);
  6082. var selection = this.addCheckbox(div, mxResources.get('selectionOnly'),
  6083. false, graph.isSelectionEmpty());
  6084. var cb6 = document.createElement('input');
  6085. cb6.style.marginTop = '16px';
  6086. cb6.style.marginRight = '8px';
  6087. cb6.style.marginLeft = '24px';
  6088. cb6.setAttribute('disabled', 'disabled');
  6089. cb6.setAttribute('type', 'checkbox');
  6090. var exportSelect = document.createElement('select');
  6091. exportSelect.style.marginTop = '16px';
  6092. exportSelect.style.marginLeft = '8px';
  6093. var sizes = ['selectionOnly', 'diagram', 'page'];
  6094. var sizesOpt = {};
  6095. for (var i = 0; i < sizes.length; i++)
  6096. {
  6097. if (!graph.isSelectionEmpty() || sizes[i] != 'selectionOnly')
  6098. {
  6099. var opt = document.createElement('option');
  6100. mxUtils.write(opt, mxResources.get(sizes[i]));
  6101. opt.setAttribute('value', sizes[i]);
  6102. exportSelect.appendChild(opt);
  6103. sizesOpt[sizes[i]] = opt;
  6104. }
  6105. }
  6106. if (exportOption)
  6107. {
  6108. mxUtils.write(div, mxResources.get('size') + ':');
  6109. div.appendChild(exportSelect);
  6110. mxUtils.br(div);
  6111. height += 26;
  6112. mxEvent.addListener(exportSelect, 'change', function()
  6113. {
  6114. if (exportSelect.value == 'selectionOnly')
  6115. {
  6116. selection.checked = true;
  6117. }
  6118. });
  6119. }
  6120. else if (cropOption)
  6121. {
  6122. div.appendChild(cb6);
  6123. mxUtils.write(div, mxResources.get('crop'));
  6124. mxUtils.br(div);
  6125. height += 30;
  6126. mxEvent.addListener(selection, 'change', function()
  6127. {
  6128. if (selection.checked)
  6129. {
  6130. cb6.removeAttribute('disabled');
  6131. }
  6132. else
  6133. {
  6134. cb6.setAttribute('disabled', 'disabled');
  6135. }
  6136. });
  6137. }
  6138. if (graph.isSelectionEmpty())
  6139. {
  6140. if (exportOption)
  6141. {
  6142. selection.style.display = 'none';
  6143. selection.nextSibling.style.display = 'none';
  6144. selection.nextSibling.nextSibling.style.display = 'none';
  6145. height -= 30;
  6146. }
  6147. }
  6148. else
  6149. {
  6150. exportSelect.value = 'diagram';
  6151. cb6.setAttribute('checked', 'checked');
  6152. cb6.defaultChecked = true;
  6153. mxEvent.addListener(selection, 'change', function()
  6154. {
  6155. if (selection.checked)
  6156. {
  6157. exportSelect.value = 'selectionOnly';
  6158. }
  6159. else
  6160. {
  6161. exportSelect.value = 'diagram';
  6162. }
  6163. });
  6164. }
  6165. var defaultTransparent = false; /*graph.background == mxConstants.NONE || graph.background == null*/;
  6166. var transparent = this.addCheckbox(div, mxResources.get('transparentBackground'),
  6167. defaultTransparent, null, null, format != 'jpeg' && format != 'webp');
  6168. var themeSelect = null;
  6169. if (Editor.isDarkMode() || Editor.enableCssDarkMode)
  6170. {
  6171. var themeSelect = document.createElement('select');
  6172. themeSelect.style.maxWidth = '260px';
  6173. themeSelect.style.marginLeft = '8px';
  6174. themeSelect.style.marginTop = '16px';
  6175. var lightOption = document.createElement('option');
  6176. lightOption.setAttribute('value', 'light');
  6177. mxUtils.write(lightOption, mxResources.get('light'));
  6178. themeSelect.appendChild(lightOption);
  6179. var darkOption = document.createElement('option');
  6180. darkOption.setAttribute('value', 'dark');
  6181. mxUtils.write(darkOption, mxResources.get('dark'));
  6182. themeSelect.appendChild(darkOption);
  6183. if (Editor.enableCssDarkMode && format == 'svg')
  6184. {
  6185. var autoOption = document.createElement('option');
  6186. autoOption.setAttribute('value', 'auto');
  6187. mxUtils.write(autoOption, mxResources.get('automatic'));
  6188. themeSelect.appendChild(autoOption);
  6189. }
  6190. mxUtils.write(div, mxResources.get('appearance') + ':');
  6191. div.appendChild(themeSelect);
  6192. mxUtils.br(div);
  6193. if (Editor.isDarkMode() || Editor.cssDarkMode)
  6194. {
  6195. darkOption.setAttribute('selected', 'selected');
  6196. }
  6197. else
  6198. {
  6199. lightOption.setAttribute('selected', 'selected');
  6200. }
  6201. height += 26;
  6202. }
  6203. var shadow = this.addCheckbox(div, mxResources.get('shadow'), graph.shadowVisible);
  6204. var grid = null;
  6205. if (format == 'png' || format == 'jpeg' || format == 'webp')
  6206. {
  6207. grid = this.addCheckbox(div, mxResources.get('grid'), false,
  6208. this.isOffline() || !this.canvasSupported, false, true);
  6209. height += 30;
  6210. }
  6211. var include = this.addCheckbox(div, mxResources.get('includeCopyOfMyDiagram') + ':',
  6212. defaultInclude, null, null, format != 'jpeg' && format != 'webp');
  6213. var includeSelect = document.createElement('select');
  6214. includeSelect.style.maxWidth = '260px';
  6215. includeSelect.style.marginLeft = '28px';
  6216. if (format == 'png' || format == 'svg')
  6217. {
  6218. var includeAllPagesOption = document.createElement('option');
  6219. includeAllPagesOption.setAttribute('value', 'allPages');
  6220. mxUtils.write(includeAllPagesOption, mxResources.get('allPages'));
  6221. includeSelect.appendChild(includeAllPagesOption);
  6222. var includeCurrentPageOption = document.createElement('option');
  6223. includeCurrentPageOption.setAttribute('value', 'currentPage');
  6224. mxUtils.write(includeCurrentPageOption, mxResources.get('currentPage'));
  6225. includeSelect.appendChild(includeCurrentPageOption);
  6226. include.style.marginBottom = '12px';
  6227. includeSelect.style.marginBottom = '16px';
  6228. div.appendChild(includeSelect);
  6229. mxUtils.br(div);
  6230. height += 20;
  6231. if (this.lastEmbedInclude != null)
  6232. {
  6233. includeSelect.value = this.lastEmbedInclude;
  6234. }
  6235. function updateIncludeSelect()
  6236. {
  6237. if (include.checked)
  6238. {
  6239. includeSelect.removeAttribute('disabled');
  6240. }
  6241. else
  6242. {
  6243. includeSelect.setAttribute('disabled', 'disabled');
  6244. }
  6245. };
  6246. mxEvent.addListener(include, 'change', updateIncludeSelect);
  6247. updateIncludeSelect();
  6248. }
  6249. else
  6250. {
  6251. include.style.marginBottom = '16px';
  6252. }
  6253. var cb5 = document.createElement('input');
  6254. cb5.style.marginBottom = '16px';
  6255. cb5.style.marginRight = '8px';
  6256. cb5.setAttribute('type', 'checkbox');
  6257. var cb7 = document.createElement('input');
  6258. cb7.style.marginBottom = '16px';
  6259. cb7.style.marginRight = '8px';
  6260. cb7.setAttribute('type', 'checkbox');
  6261. if (embedOption)
  6262. {
  6263. cb5.checked = (this.lastEmbedImages != null) ?
  6264. this.lastEmbedImages : true;
  6265. div.appendChild(cb5);
  6266. mxUtils.write(div, mxResources.get('embedImages'));
  6267. mxUtils.br(div);
  6268. cb7.checked = (this.lastEmbedFonts != null) ?
  6269. this.lastEmbedImages : true;
  6270. div.appendChild(cb7);
  6271. mxUtils.write(div, mxResources.get('embedFonts'));
  6272. mxUtils.br(div);
  6273. height += 50;
  6274. }
  6275. var linkSelect = document.createElement('select');
  6276. linkSelect.style.maxWidth = '260px';
  6277. linkSelect.style.marginLeft = '8px';
  6278. var autoOption = document.createElement('option');
  6279. autoOption.setAttribute('value', 'auto');
  6280. mxUtils.write(autoOption, mxResources.get('automatic'));
  6281. linkSelect.appendChild(autoOption);
  6282. var blankOption = document.createElement('option');
  6283. blankOption.setAttribute('value', 'blank');
  6284. mxUtils.write(blankOption, mxResources.get('openInNewWindow'));
  6285. linkSelect.appendChild(blankOption);
  6286. var selfOption = document.createElement('option');
  6287. selfOption.setAttribute('value', 'self');
  6288. mxUtils.write(selfOption, mxResources.get('openInThisWindow'));
  6289. linkSelect.appendChild(selfOption);
  6290. //Inkscape doesn't support links from pdf to svg. Related to https://gitlab.com/inkscape/inbox/-/issues/583
  6291. var linkLost = document.createElement('div');
  6292. mxUtils.write(linkLost, mxResources.get('LinksLost'));
  6293. linkLost.style.margin = '7px';
  6294. linkLost.style.display = 'none';
  6295. if (format == 'svg')
  6296. {
  6297. mxUtils.write(div, mxResources.get('links') + ':');
  6298. div.appendChild(linkSelect);
  6299. div.appendChild(linkLost);
  6300. mxUtils.br(div);
  6301. mxUtils.br(div);
  6302. height += 50;
  6303. }
  6304. var dlg = new CustomDialog(this, div, mxUtils.bind(this, function()
  6305. {
  6306. this.lastExportBorder = borderInput.value;
  6307. this.lastExportZoom = zoomInput.value;
  6308. this.lastEmbedImages = cb5.checked;
  6309. this.lastEmbedFonts = cb7.checked;
  6310. this.lastEmbedInclude = includeSelect.value;
  6311. callback(zoomInput.value, transparent.checked, !selection.checked, shadow.checked,
  6312. include.checked, cb5.checked && embedOption, borderInput.value, cb6.checked,
  6313. (format == 'png' || format == 'svg') && includeSelect.value == 'currentPage',
  6314. linkSelect.value, (grid != null) ? grid.checked : null,
  6315. (themeSelect != null) ? themeSelect.value : null,
  6316. exportSelect.value, cb7.checked);
  6317. }), null, btnLabel, helpLink);
  6318. this.showDialog(dlg.container, 340, height, true, true, null, null, null, null, true);
  6319. zoomInput.focus();
  6320. if (mxClient.IS_GC || mxClient.IS_FF || document.documentMode >= 5)
  6321. {
  6322. zoomInput.select();
  6323. }
  6324. else
  6325. {
  6326. document.execCommand('selectAll', false, null);
  6327. }
  6328. };
  6329. /**
  6330. *
  6331. */
  6332. EditorUi.prototype.showEmbedImageDialog = function(fn, title, imageLabel, shadowEnabled, helpLink)
  6333. {
  6334. var div = document.createElement('div');
  6335. div.style.whiteSpace = 'nowrap';
  6336. var graph = this.editor.graph;
  6337. if (title != null)
  6338. {
  6339. var hd = document.createElement('h3');
  6340. mxUtils.write(hd, title);
  6341. hd.style.cssText = 'width:100%;text-align:center;margin-top:0px;margin-bottom:4px';
  6342. div.appendChild(hd);
  6343. }
  6344. var fit = this.addCheckbox(div, mxResources.get('fit'), true);
  6345. var shadow = this.addCheckbox(div, mxResources.get('shadow'),
  6346. graph.shadowVisible && shadowEnabled, !shadowEnabled);
  6347. var image = this.addCheckbox(div, imageLabel);
  6348. var lightbox = this.addCheckbox(div, mxResources.get('lightbox'), true);
  6349. var editSection = this.addEditButton(div, lightbox);
  6350. var edit = editSection.getEditInput();
  6351. var hasLayers = graph.model.getChildCount(graph.model.getRoot()) > 1;
  6352. var layers = this.addCheckbox(div, mxResources.get('layers'), hasLayers, !hasLayers);
  6353. layers.style.marginLeft = edit.style.marginLeft;
  6354. layers.style.marginBottom = '12px';
  6355. layers.style.marginTop = '8px';
  6356. mxEvent.addListener(lightbox, 'change', function()
  6357. {
  6358. if (lightbox.checked)
  6359. {
  6360. if (hasLayers)
  6361. {
  6362. layers.removeAttribute('disabled');
  6363. }
  6364. edit.removeAttribute('disabled');
  6365. }
  6366. else
  6367. {
  6368. layers.setAttribute('disabled', 'disabled');
  6369. edit.setAttribute('disabled', 'disabled');
  6370. }
  6371. if (edit.checked && lightbox.checked)
  6372. {
  6373. editSection.getEditSelect().removeAttribute('disabled');
  6374. }
  6375. else
  6376. {
  6377. editSection.getEditSelect().setAttribute('disabled', 'disabled');
  6378. }
  6379. });
  6380. var dlg = new CustomDialog(this, div, mxUtils.bind(this, function()
  6381. {
  6382. fn(fit.checked, shadow.checked, image.checked, lightbox.checked,
  6383. editSection.getLink(), layers.checked);
  6384. }), null, mxResources.get('embed'), helpLink);
  6385. this.showDialog(dlg.container, 280, 300, true, true);
  6386. };
  6387. /**
  6388. *
  6389. */
  6390. EditorUi.prototype.createEmbedImage = function(fit, shadow, retina, lightbox, edit, layers, fn, err)
  6391. {
  6392. var bounds = this.editor.graph.getGraphBounds();
  6393. var page = this.getSelectedPageIndex();
  6394. function doUpdate(dataUri)
  6395. {
  6396. var onclick = ' ';
  6397. var css = '';
  6398. // Adds double click handling
  6399. if (lightbox)
  6400. {
  6401. // KNOWN: Message passing does not seem to work in IE11
  6402. onclick = " onclick=\"(function(img){if(img.wnd!=null&&!img.wnd.closed){img.wnd.focus();}else{var r=function(evt){if(evt.data=='ready'&&evt.source==img.wnd){img.wnd.postMessage(decodeURIComponent(" +
  6403. "img.getAttribute('src')),'*');window.removeEventListener('message',r);}};window.addEventListener('message',r);img.wnd=window.open('" + EditorUi.lightboxHost + "/?client=1" +
  6404. ((page != null) ? ("&page=" + page) : "") +
  6405. ((edit) ? "&edit=_blank" : "") +
  6406. ((layers) ? '&layers=1' : '') + "');}})(this);\"";
  6407. css += 'cursor:pointer;';
  6408. }
  6409. if (fit)
  6410. {
  6411. css += 'max-width:100%;';
  6412. }
  6413. var atts = '';
  6414. if (retina)
  6415. {
  6416. atts = ' width="' + Math.round(bounds.width) + '" height="' + Math.round(bounds.height) + '"';
  6417. }
  6418. fn('<img src="' + dataUri + '"' + atts + ((css != '') ? ' style="' + css + '"' : '') + onclick + '/>');
  6419. };
  6420. if (this.isExportToCanvas())
  6421. {
  6422. this.editor.exportToCanvas(mxUtils.bind(this, function(canvas)
  6423. {
  6424. var xml = (lightbox) ? this.getFileData(true) : null;
  6425. var data = this.createImageDataUri(canvas, xml, 'png');
  6426. doUpdate(data);
  6427. }), null, null, null, mxUtils.bind(this, function(e)
  6428. {
  6429. err({message: mxResources.get('unknownError')});
  6430. }), null, true, (retina) ? 2 : 1, null, shadow, null, null, Editor.defaultBorder);
  6431. }
  6432. else
  6433. {
  6434. var data = this.getFileData(true);
  6435. if (bounds.width * bounds.height <= MAX_AREA && data.length <= MAX_REQUEST_SIZE)
  6436. {
  6437. var size = '';
  6438. if (retina)
  6439. {
  6440. size = '&w=' + Math.round(2 * bounds.width) +
  6441. '&h=' + Math.round(2 * bounds.height);
  6442. }
  6443. var embed = (lightbox) ? '1' : '0';
  6444. var req = new mxXmlRequest(EXPORT_URL, 'format=png' +
  6445. '&base64=1&embedXml=' + embed + size + '&xml=' +
  6446. encodeURIComponent(data));
  6447. // LATER: Updates on each change, add a delay
  6448. req.send(mxUtils.bind(this, function()
  6449. {
  6450. if (req.getStatus() >= 200 && req.getStatus() <= 299)
  6451. {
  6452. // Fixes possible "incorrect function" for select() on
  6453. // DOM node which is no longer in document with IE11
  6454. doUpdate('data:image/png;base64,' + req.getText());
  6455. }
  6456. else
  6457. {
  6458. err({message: mxResources.get('unknownError')});
  6459. }
  6460. }));
  6461. }
  6462. else
  6463. {
  6464. err({message: mxResources.get('drawingTooLarge')});
  6465. }
  6466. }
  6467. };
  6468. /**
  6469. *
  6470. */
  6471. EditorUi.prototype.createEmbedSvg = function(fit, shadow, image, lightbox, edit, layers, fn)
  6472. {
  6473. var svgRoot = this.editor.graph.getSvg(null, null, null,
  6474. null, null, null, null, null, null, null, !image);
  6475. // Keeps hashtag links on same page
  6476. var links = svgRoot.getElementsByTagName('a');
  6477. if (links != null)
  6478. {
  6479. for (var i = 0; i < links.length; i++)
  6480. {
  6481. var href = links[i].getAttribute('href');
  6482. if (href != null && href.charAt(0) == '#' &&
  6483. links[i].getAttribute('target') == '_blank')
  6484. {
  6485. links[i].removeAttribute('target');
  6486. }
  6487. }
  6488. }
  6489. if (lightbox)
  6490. {
  6491. svgRoot.setAttribute('content', this.getFileData(true));
  6492. }
  6493. // Adds shadow filter
  6494. if (shadow)
  6495. {
  6496. this.editor.graph.addSvgShadow(svgRoot);
  6497. }
  6498. // SVG inside image tag
  6499. if (image)
  6500. {
  6501. var onclick = ' ';
  6502. var css = '';
  6503. // Adds double click handling
  6504. if (lightbox)
  6505. {
  6506. // KNOWN: Message passing does not seem to work in IE11
  6507. onclick = "onclick=\"(function(img){if(img.wnd!=null&&!img.wnd.closed){img.wnd.focus();}else{var r=function(evt){if(evt.data=='ready'&&evt.source==img.wnd){img.wnd.postMessage(decodeURIComponent(" +
  6508. "img.getAttribute('src')),'*');window.removeEventListener('message',r);}};window.addEventListener('message',r);img.wnd=window.open('" + EditorUi.lightboxHost + "/?client=1" +
  6509. ((edit) ? "&edit=_blank" : "") + ((layers) ? '&layers=1' : '') + "');}})(this);\"";
  6510. css += 'cursor:pointer;';
  6511. }
  6512. if (fit)
  6513. {
  6514. css += 'max-width:100%;';
  6515. }
  6516. // Images inside IMG don't seem to work so embed them all
  6517. this.editor.convertImages(svgRoot, mxUtils.bind(this, function(svgRoot)
  6518. {
  6519. fn('<img src="' + Editor.createSvgDataUri(mxUtils.getXml(svgRoot)) + '"' +
  6520. ((css != '') ? ' style="' + css + '"' : '') + onclick + '/>');
  6521. }));
  6522. }
  6523. else
  6524. {
  6525. var css = '';
  6526. // Adds double click handling
  6527. if (lightbox)
  6528. {
  6529. var page = this.getSelectedPageIndex();
  6530. // KNOWN: Message passing does not seem to work in IE11
  6531. var js = "(function(svg){var src=window.event.target||window.event.srcElement;" +
  6532. // Ignores link events
  6533. "while (src!=null&&src.nodeName.toLowerCase()!='a'){src=src.parentNode;}if(src==null)" +
  6534. // Focus existing lightbox
  6535. "{if(svg.wnd!=null&&!svg.wnd.closed){svg.wnd.focus();}else{var r=function(evt){" +
  6536. // Message handling
  6537. "if(evt.data=='ready'&&evt.source==svg.wnd){svg.wnd.postMessage(decodeURIComponent(" +
  6538. "svg.getAttribute('content')),'*');window.removeEventListener('message',r);}};" +
  6539. "window.addEventListener('message',r);" +
  6540. // Opens lightbox window
  6541. "svg.wnd=window.open('" + EditorUi.lightboxHost + "/?client=1" +
  6542. ((page != null) ? ("&page=" + page) : "") +
  6543. ((edit) ? "&edit=_blank" : "") + ((layers) ? '&layers=1' : '') + "');}}})(this);";
  6544. svgRoot.setAttribute('onclick', js);
  6545. css += 'cursor:pointer;';
  6546. }
  6547. // Adds responsive size
  6548. if (fit)
  6549. {
  6550. var w = parseInt(svgRoot.getAttribute('width'));
  6551. var h = parseInt(svgRoot.getAttribute('height'));
  6552. svgRoot.setAttribute('viewBox', '-0.5 -0.5 ' + w + ' ' + h);
  6553. css += 'max-width:100%;max-height:' + h + 'px;';
  6554. svgRoot.removeAttribute('height');
  6555. }
  6556. if (css != '')
  6557. {
  6558. svgRoot.setAttribute('style', css);
  6559. }
  6560. // Adds CSS
  6561. this.editor.addFontCss(svgRoot);
  6562. if (this.editor.graph.mathEnabled)
  6563. {
  6564. this.editor.addMathCss(svgRoot);
  6565. }
  6566. fn(mxUtils.getXml(svgRoot));
  6567. }
  6568. };
  6569. /**
  6570. * Translates this point by the given vector.
  6571. *
  6572. * @param {number} dx X-coordinate of the translation.
  6573. * @param {number} dy Y-coordinate of the translation.
  6574. */
  6575. EditorUi.prototype.timeSince = function(date)
  6576. {
  6577. var seconds = Math.floor((new Date() - date) / 1000);
  6578. var interval = Math.floor(seconds / 31536000);
  6579. if (interval > 1)
  6580. {
  6581. return interval + ' ' + mxResources.get('years');
  6582. }
  6583. interval = Math.floor(seconds / 2592000);
  6584. if (interval > 1)
  6585. {
  6586. return interval + ' ' + mxResources.get('months');
  6587. }
  6588. interval = Math.floor(seconds / 86400);
  6589. if (interval > 1)
  6590. {
  6591. return interval + ' ' + mxResources.get('days');
  6592. }
  6593. interval = Math.floor(seconds / 3600);
  6594. if (interval > 1)
  6595. {
  6596. return interval + ' ' + mxResources.get('hours');
  6597. }
  6598. interval = Math.floor(seconds / 60);
  6599. if (interval > 1)
  6600. {
  6601. return interval + ' ' + mxResources.get('minutes');
  6602. }
  6603. if (interval == 1)
  6604. {
  6605. return interval + ' ' + mxResources.get('minute');
  6606. }
  6607. return null;
  6608. };
  6609. /**
  6610. *
  6611. */
  6612. EditorUi.prototype.decodeNodeIntoGraph = function(node, graph)
  6613. {
  6614. if (node != null)
  6615. {
  6616. var diagramNode = null;
  6617. if (node.nodeName == 'diagram')
  6618. {
  6619. diagramNode = node;
  6620. }
  6621. else if (node.nodeName == 'mxfile')
  6622. {
  6623. var diagrams = node.getElementsByTagName('diagram');
  6624. if (diagrams.length > 0)
  6625. {
  6626. diagramNode = diagrams[0];
  6627. var graphGetGlobalVariable = graph.getGlobalVariable;
  6628. graph.getGlobalVariable = function(name)
  6629. {
  6630. if (name == 'page')
  6631. {
  6632. return diagramNode.getAttribute('name') || mxResources.get('pageWithNumber', [1])
  6633. }
  6634. else if (name == 'pagenumber')
  6635. {
  6636. return 1;
  6637. }
  6638. return graphGetGlobalVariable.apply(this, arguments);
  6639. };
  6640. }
  6641. }
  6642. if (diagramNode != null)
  6643. {
  6644. node = Editor.parseDiagramNode(diagramNode);
  6645. }
  6646. }
  6647. // Hack to decode XML into temp graph via editor
  6648. var prev = this.editor.graph;
  6649. try
  6650. {
  6651. this.editor.graph = graph;
  6652. this.editor.setGraphXml(node);
  6653. }
  6654. catch (e)
  6655. {
  6656. // ignore
  6657. }
  6658. finally
  6659. {
  6660. this.editor.graph = prev;
  6661. }
  6662. return node;
  6663. };
  6664. /**
  6665. *
  6666. */
  6667. EditorUi.prototype.getSvgFileProperties = function(node)
  6668. {
  6669. return this.getPngFileProperties(node);
  6670. };
  6671. /**
  6672. *
  6673. */
  6674. EditorUi.prototype.getPngFileProperties = function(node)
  6675. {
  6676. var scale = 1;
  6677. var border = 0;
  6678. if (node != null)
  6679. {
  6680. if (node.hasAttribute('scale'))
  6681. {
  6682. var temp = parseFloat(node.getAttribute('scale'));
  6683. if (!isNaN(temp) && temp > 0)
  6684. {
  6685. scale = temp;
  6686. }
  6687. }
  6688. if (node.hasAttribute('border'))
  6689. {
  6690. var temp = parseInt(node.getAttribute('border'));
  6691. if (!isNaN(temp) && temp > 0)
  6692. {
  6693. border = temp;
  6694. }
  6695. }
  6696. }
  6697. return {scale: scale, border: border};
  6698. };
  6699. /**
  6700. *
  6701. */
  6702. EditorUi.prototype.getEmbeddedPng = function(success, error, optionalData, scale, border)
  6703. {
  6704. try
  6705. {
  6706. var graph = this.editor.graph;
  6707. var darkTheme = graph.themes != null && graph.defaultThemeName == 'darkTheme';
  6708. var diagramData = null;
  6709. // Exports PNG for given optional data
  6710. if (optionalData != null && optionalData.length > 0)
  6711. {
  6712. graph = this.createTemporaryGraph((darkTheme) ?
  6713. graph.getDefaultStylesheet() : graph.getStylesheet());
  6714. document.body.appendChild(graph.container);
  6715. this.decodeNodeIntoGraph(this.editor.extractGraphModel(
  6716. mxUtils.parseXml(optionalData).documentElement, true), graph);
  6717. diagramData = optionalData;
  6718. }
  6719. // Exports PNG for first page while other page is showing
  6720. else if (darkTheme || (this.pages != null && this.currentPage != this.pages[0]))
  6721. {
  6722. graph = this.createTemporaryGraph((darkTheme) ?
  6723. graph.getDefaultStylesheet() : graph.getStylesheet());
  6724. var graphGetGlobalVariable = graph.getGlobalVariable;
  6725. graph.setBackgroundImage = this.editor.graph.setBackgroundImage;
  6726. var page = this.pages[0];
  6727. if (this.currentPage == page)
  6728. {
  6729. graph.setBackgroundImage(this.editor.graph.backgroundImage);
  6730. }
  6731. else if (page.viewState != null && page.viewState != null)
  6732. {
  6733. graph.setBackgroundImage(page.viewState.backgroundImage);
  6734. }
  6735. graph.getGlobalVariable = function(name)
  6736. {
  6737. if (name == 'page')
  6738. {
  6739. return page.getName();
  6740. }
  6741. else if (name == 'pagenumber')
  6742. {
  6743. return 1;
  6744. }
  6745. return graphGetGlobalVariable.apply(this, arguments);
  6746. };
  6747. document.body.appendChild(graph.container);
  6748. graph.model.setRoot(page.root);
  6749. }
  6750. this.editor.exportToCanvas(mxUtils.bind(this, function(canvas)
  6751. {
  6752. try
  6753. {
  6754. if (diagramData == null)
  6755. {
  6756. diagramData = this.getFileData(true, null, null, null, null,
  6757. null, null, null, null, false);
  6758. }
  6759. var data = canvas.toDataURL('image/png');
  6760. data = Editor.writeGraphModelToPng(data,
  6761. 'tEXt', 'mxfile', encodeURIComponent(diagramData));
  6762. success(data.substring(data.lastIndexOf(',') + 1));
  6763. // Removes temporary graph from DOM
  6764. if (graph != this.editor.graph)
  6765. {
  6766. graph.container.parentNode.removeChild(graph.container);
  6767. }
  6768. }
  6769. catch (e)
  6770. {
  6771. if (error != null)
  6772. {
  6773. error(e);
  6774. }
  6775. }
  6776. }), null, null, null, mxUtils.bind(this, function(e)
  6777. {
  6778. if (error != null)
  6779. {
  6780. error(e);
  6781. }
  6782. }), null, null, scale, null, graph.shadowVisible, null,
  6783. graph, border, null, null, null, 'diagram', null);
  6784. }
  6785. catch (e)
  6786. {
  6787. if (error != null)
  6788. {
  6789. error(e);
  6790. }
  6791. }
  6792. }
  6793. /**
  6794. * Returns the SVG of the diagram with embedded XML. If a callback function is
  6795. * used, the images are converted to data URIs.
  6796. */
  6797. EditorUi.prototype.getEmbeddedSvg = function(xml, graph, url, noHeader, callback, ignoreSelection,
  6798. redirect, embedImages, background, scale, border, shadow, theme)
  6799. {
  6800. embedImages = (embedImages != null) ? embedImages : true;
  6801. border = (border != null) ? border : 0;
  6802. var bg = (background != null) ? background : graph.background;
  6803. if (bg == mxConstants.NONE)
  6804. {
  6805. bg = null;
  6806. }
  6807. // Sets or disables alternate text for foreignObjects. Disabling is needed
  6808. // because PhantomJS seems to ignore switch statements and paint all text.
  6809. var svgRoot = graph.getSvg(bg, scale, border, null, null, ignoreSelection, null,
  6810. null, null, graph.shadowVisible || shadow, null, theme, 'diagram');
  6811. if (graph.shadowVisible || shadow)
  6812. {
  6813. graph.addSvgShadow(svgRoot, null, null, border == 0);
  6814. }
  6815. if (xml != null)
  6816. {
  6817. svgRoot.setAttribute('content', xml);
  6818. }
  6819. if (url != null)
  6820. {
  6821. svgRoot.setAttribute('resource', url);
  6822. }
  6823. // LATER: Click on SVG content to start editing
  6824. // if (redirect != null)
  6825. // {
  6826. // // TODO: Ignore anchor tag source for click event
  6827. // svgRoot.setAttribute('style', 'cursor:pointer;');
  6828. // svgRoot.setAttribute('onclick', 'window.location.href=\'' + redirect + '\';');
  6829. // }
  6830. var done = mxUtils.bind(this, function(svgRoot)
  6831. {
  6832. var result = ((!noHeader) ? Graph.xmlDeclaration + '\n' + Graph.svgFileComment +
  6833. '\n' + Graph.svgDoctype + '\n' : '') + mxUtils.getXml(svgRoot);
  6834. if (callback != null)
  6835. {
  6836. callback(result);
  6837. }
  6838. return result;
  6839. });
  6840. // Adds CSS
  6841. if (graph.mathEnabled)
  6842. {
  6843. this.editor.addMathCss(svgRoot);
  6844. }
  6845. if (callback != null)
  6846. {
  6847. this.embedFonts(svgRoot, mxUtils.bind(this, function(svgRoot)
  6848. {
  6849. if (embedImages)
  6850. {
  6851. this.editor.convertImages(svgRoot, mxUtils.bind(this, function(svgRoot)
  6852. {
  6853. done(svgRoot);
  6854. }));
  6855. }
  6856. else
  6857. {
  6858. done(svgRoot);
  6859. }
  6860. }));
  6861. }
  6862. else
  6863. {
  6864. return done(svgRoot);
  6865. }
  6866. };
  6867. /**
  6868. * Embeds font CSS as data URIs into the given svgRoot.
  6869. */
  6870. EditorUi.prototype.embedFonts = function(svgRoot, callback)
  6871. {
  6872. this.editor.loadFonts(mxUtils.bind(this, function()
  6873. {
  6874. try
  6875. {
  6876. if (this.editor.resolvedFontCss != null)
  6877. {
  6878. this.editor.addFontCss(svgRoot, this.editor.resolvedFontCss);
  6879. }
  6880. this.editor.embedExtFonts(mxUtils.bind(this, function(extFontsEmbeddedCss)
  6881. {
  6882. try
  6883. {
  6884. if (extFontsEmbeddedCss != null)
  6885. {
  6886. this.editor.addFontCss(svgRoot, extFontsEmbeddedCss);
  6887. }
  6888. callback(svgRoot);
  6889. }
  6890. catch (e)
  6891. {
  6892. callback(svgRoot);
  6893. }
  6894. }));
  6895. }
  6896. catch (e)
  6897. {
  6898. callback(svgRoot);
  6899. }
  6900. }));
  6901. };
  6902. /**
  6903. *
  6904. */
  6905. EditorUi.prototype.exportImage = function(scale, transparentBackground, ignoreSelection, addShadow,
  6906. editable, border, noCrop, currentPage, format, grid, dpi, theme, exportType)
  6907. {
  6908. format = (format != null) ? format : 'png';
  6909. if (this.spinner.spin(document.body, mxResources.get('exporting'), mxUtils.bind(this, function(err)
  6910. {
  6911. Editor.addRetryToError(err, mxUtils.bind(this, function()
  6912. {
  6913. this.exportImage(scale, transparentBackground, ignoreSelection, addShadow,
  6914. editable, border, noCrop, currentPage, format, grid, dpi, theme, exportType);
  6915. }));
  6916. this.handleError(err);
  6917. })))
  6918. {
  6919. var selectionEmpty = this.editor.graph.isSelectionEmpty();
  6920. ignoreSelection = (ignoreSelection != null) ? ignoreSelection : selectionEmpty;
  6921. // Caches images
  6922. if (this.thumbImageCache == null)
  6923. {
  6924. this.thumbImageCache = new Object();
  6925. }
  6926. try
  6927. {
  6928. this.editor.exportToCanvas(mxUtils.bind(this, function(canvas)
  6929. {
  6930. this.spinner.stop();
  6931. try
  6932. {
  6933. this.saveCanvas(canvas, (editable) ? this.getFileData(true, null,
  6934. null, null, ignoreSelection, currentPage, null, null, null,
  6935. null, null, scale, border) : null, format,
  6936. (this.pages == null || this.pages.length == 0), dpi);
  6937. }
  6938. catch (e)
  6939. {
  6940. this.handleError(e);
  6941. }
  6942. }), null, this.thumbImageCache, null, mxUtils.bind(this, function(e)
  6943. {
  6944. this.spinner.stop();
  6945. this.handleError(e);
  6946. }), null, ignoreSelection, scale || 1, transparentBackground, addShadow,
  6947. null, null, border, noCrop, grid, theme, exportType);
  6948. }
  6949. catch (e)
  6950. {
  6951. this.spinner.stop();
  6952. this.handleError(e);
  6953. }
  6954. }
  6955. };
  6956. /**
  6957. /**
  6958. * Returns true if the given URL is known to have CORS headers.
  6959. */
  6960. EditorUi.prototype.isCorsEnabledForUrl = function(url)
  6961. {
  6962. return this.editor.isCorsEnabledForUrl(url);
  6963. };
  6964. /**
  6965. * Handling drag and drop and import.
  6966. */
  6967. /**
  6968. * Adds the local page IDs to the given mapping.
  6969. */
  6970. EditorUi.prototype.addLocalPagesToMapping = function(mapping)
  6971. {
  6972. mapping = (mapping != null) ? mapping : {};
  6973. if (this.pages != null)
  6974. {
  6975. for (var i = 0; i < this.pages.length; i++)
  6976. {
  6977. mapping[this.pages[i].getId()] = this.pages[i].getId();
  6978. }
  6979. }
  6980. return mapping;
  6981. };
  6982. /**
  6983. * Imports the given XML into the existing diagram.
  6984. */
  6985. EditorUi.prototype.importXml = function(xml, dx, dy, crop, noErrorHandling, addNewPage, applyDefaultStyles)
  6986. {
  6987. dx = (dx != null) ? dx : 0;
  6988. dy = (dy != null) ? dy : 0;
  6989. var cells = []
  6990. try
  6991. {
  6992. var graph = this.editor.graph;
  6993. if (xml != null && xml.length > 0)
  6994. {
  6995. // Adds pages
  6996. graph.model.beginUpdate();
  6997. try
  6998. {
  6999. var doc = mxUtils.parseXml(xml);
  7000. var mapping = {};
  7001. // Checks for mxfile with multiple pages
  7002. var node = this.editor.extractGraphModel(doc.documentElement, this.pages != null);
  7003. if (node != null && node.nodeName == 'mxfile' && this.pages != null)
  7004. {
  7005. var diagrams = node.getElementsByTagName('diagram');
  7006. if (diagrams.length == 1 && !addNewPage)
  7007. {
  7008. node = Editor.parseDiagramNode(diagrams[0]);
  7009. if (this.currentPage != null)
  7010. {
  7011. mapping[diagrams[0].getAttribute('id')] = this.currentPage.getId();
  7012. // Renames page if diagram has one blank page with default name
  7013. if (this.isBlankFile())
  7014. {
  7015. var name = diagrams[0].getAttribute('name');
  7016. if (name != null && name != '')
  7017. {
  7018. this.editor.graph.model.execute(new RenamePage(
  7019. this, this.currentPage, name));
  7020. }
  7021. }
  7022. }
  7023. }
  7024. else if (diagrams.length > 0)
  7025. {
  7026. var pages = [];
  7027. var i0 = 0;
  7028. // Adds first page to current page if current page is only page and empty
  7029. if (this.pages != null && this.pages.length == 1 && this.isDiagramEmpty())
  7030. {
  7031. mapping[diagrams[0].getAttribute('id')] = this.pages[0].getId();
  7032. var name = diagrams[0].getAttribute('name');
  7033. if (name != null && name != '')
  7034. {
  7035. this.editor.graph.model.execute(new RenamePage(
  7036. this, this.pages[0], name));
  7037. }
  7038. node = Editor.parseDiagramNode(diagrams[0]);
  7039. crop = false;
  7040. i0 = 1;
  7041. }
  7042. for (var i = i0; i < diagrams.length; i++)
  7043. {
  7044. // Imported pages must obtain a new ID and
  7045. // all links to pages must be updated below
  7046. var oldId = diagrams[i].getAttribute('id')
  7047. diagrams[i].removeAttribute('id');
  7048. var page = this.updatePageRoot(new DiagramPage(diagrams[i]));
  7049. mapping[oldId] = diagrams[i].getAttribute('id');
  7050. var index = this.pages.length;
  7051. // Checks for invalid page names
  7052. if (page.getName() == null)
  7053. {
  7054. page.setName(mxResources.get('pageWithNumber', [index + 1]));
  7055. }
  7056. graph.model.execute(new ChangePage(this, page, page, index, true));
  7057. pages.push(page);
  7058. }
  7059. this.updatePageLinks(mapping, pages);
  7060. }
  7061. }
  7062. if (node != null && node.nodeName === 'mxGraphModel')
  7063. {
  7064. cells = graph.importGraphModel(node, dx, dy, crop);
  7065. if (cells != null)
  7066. {
  7067. this.addLocalPagesToMapping(mapping);
  7068. for (var i = 0; i < cells.length; i++)
  7069. {
  7070. this.updatePageLinksForCell(mapping, cells[i]);
  7071. }
  7072. }
  7073. var bgImg = graph.parseBackgroundImage(node.getAttribute('backgroundImage'));
  7074. if (bgImg != null && bgImg.originalSrc != null)
  7075. {
  7076. this.updateBackgroundPageLink(mapping, bgImg);
  7077. var change = new ChangePageSetup(this, null, bgImg);
  7078. change.ignoreColor = true;
  7079. graph.model.execute(change);
  7080. }
  7081. }
  7082. if (applyDefaultStyles)
  7083. {
  7084. graph.pasteCellStyles(graph.includeDescendants(cells),
  7085. graph.defaultVertexStyle, graph.defaultEdgeStyle);
  7086. }
  7087. }
  7088. finally
  7089. {
  7090. graph.model.endUpdate();
  7091. }
  7092. }
  7093. }
  7094. catch (e)
  7095. {
  7096. if (!noErrorHandling)
  7097. {
  7098. this.handleError(e);
  7099. }
  7100. else
  7101. {
  7102. throw e;
  7103. }
  7104. }
  7105. return cells;
  7106. };
  7107. /**
  7108. * Updates links to pages in shapes and labels.
  7109. */
  7110. EditorUi.prototype.updatePageLinks = function(mapping, pages)
  7111. {
  7112. for (var i = 0; i < pages.length; i++)
  7113. {
  7114. this.updatePageLinksForCell(mapping, pages[i].root);
  7115. if (pages[i].viewState != null)
  7116. {
  7117. this.updateBackgroundPageLink(mapping, pages[i].viewState.backgroundImage);
  7118. }
  7119. }
  7120. };
  7121. /**
  7122. * Updates links to pages in shapes and labels.
  7123. */
  7124. EditorUi.prototype.updateBackgroundPageLink = function(mapping, obj)
  7125. {
  7126. try
  7127. {
  7128. if (obj != null && Graph.isPageLink(obj.originalSrc))
  7129. {
  7130. var newId = mapping[obj.originalSrc.substring(obj.originalSrc.indexOf(',') + 1)];
  7131. if (newId != null)
  7132. {
  7133. obj.originalSrc = 'data:page/id,' + newId;
  7134. }
  7135. }
  7136. }
  7137. catch (e)
  7138. {
  7139. // ignore background image
  7140. }
  7141. };
  7142. /**
  7143. * Updates links to pages in shapes and labels.
  7144. */
  7145. EditorUi.prototype.updatePageLinksForCell = function(mapping, cell)
  7146. {
  7147. var temp = document.createElement('div');
  7148. var graph = this.editor.graph;
  7149. var href = graph.getLinkForCell(cell);
  7150. if (href != null)
  7151. {
  7152. graph.setLinkForCell(cell, this.updatePageLink(mapping, href));
  7153. }
  7154. if (graph.isHtmlLabel(cell))
  7155. {
  7156. temp.innerHTML = Graph.sanitizeHtml(graph.getLabel(cell));
  7157. var links = temp.getElementsByTagName('a');
  7158. var changed = false;
  7159. for (var i = 0; i < links.length; i++)
  7160. {
  7161. href = links[i].getAttribute('href');
  7162. if (href != null)
  7163. {
  7164. links[i].setAttribute('href', this.updatePageLink(mapping, href));
  7165. changed = true;
  7166. }
  7167. }
  7168. if (changed)
  7169. {
  7170. graph.labelChanged(cell, temp.innerHTML);
  7171. }
  7172. }
  7173. for (var i = 0; i < graph.model.getChildCount(cell); i++)
  7174. {
  7175. this.updatePageLinksForCell(mapping, graph.model.getChildAt(cell, i));
  7176. }
  7177. };
  7178. /**
  7179. * Updates links to pages in shapes and labels.
  7180. */
  7181. EditorUi.prototype.updatePageLink = function(mapping, href)
  7182. {
  7183. if (Graph.isPageLink(href))
  7184. {
  7185. var newId = mapping[href.substring(href.indexOf(',') + 1)];
  7186. href = (newId != null) ? 'data:page/id,' + newId : null;
  7187. }
  7188. else if (href.substring(0, 17) == 'data:action/json,')
  7189. {
  7190. try
  7191. {
  7192. var link = JSON.parse(href.substring(17));
  7193. if (link.actions != null)
  7194. {
  7195. for (var i = 0; i < link.actions.length; i++)
  7196. {
  7197. var action = link.actions[i];
  7198. if (action.open != null && Graph.isPageLink(action.open))
  7199. {
  7200. var oldId = action.open.substring(action.open.indexOf(',') + 1);
  7201. var newId = mapping[oldId];
  7202. if (newId != null)
  7203. {
  7204. action.open = 'data:page/id,' + newId;
  7205. }
  7206. else if (this.getPageById(oldId) == null)
  7207. {
  7208. delete action.open;
  7209. }
  7210. }
  7211. }
  7212. href = 'data:action/json,' + JSON.stringify(link);
  7213. }
  7214. }
  7215. catch (e)
  7216. {
  7217. // Ignore
  7218. }
  7219. }
  7220. return href;
  7221. };
  7222. /**
  7223. * Returns true for VSD, VDX and VSS, VSX files.
  7224. */
  7225. EditorUi.prototype.isRemoteVisioFormat = function(filename)
  7226. {
  7227. return (/(\.v(sd|dx))($|\?)/i.test(filename) ||
  7228. /(\.vs(s|x))($|\?)/i.test(filename));
  7229. };
  7230. /**
  7231. * Imports the given Visio file
  7232. */
  7233. EditorUi.prototype.importVisio = function(file, done, error, filename, customParam)
  7234. {
  7235. var onerror = mxUtils.bind(this, function(e)
  7236. {
  7237. this.loadingExtensions = false;
  7238. if (error != null)
  7239. {
  7240. error(e);
  7241. }
  7242. else
  7243. {
  7244. this.handleError(e);
  7245. }
  7246. });
  7247. //A reduced version of this code is used in conf/jira plugins, review that code whenever this function is changed
  7248. this.createTimeout(null, mxUtils.bind(this, function(timeout)
  7249. {
  7250. filename = (filename != null) ? filename : file.name;
  7251. var handleError = mxUtils.bind(this, function(e)
  7252. {
  7253. if (timeout.clear())
  7254. {
  7255. onerror(e);
  7256. }
  7257. });
  7258. var delayed = mxUtils.bind(this, function()
  7259. {
  7260. this.loadingExtensions = false;
  7261. if (this.doImportVisio)
  7262. {
  7263. var remote = this.isRemoteVisioFormat(filename);
  7264. try
  7265. {
  7266. var ext = 'UNKNOWN-VISIO';
  7267. var dot = filename.lastIndexOf('.');
  7268. if (dot >= 0 && dot < filename.length)
  7269. {
  7270. ext = filename.substring(dot + 1).toUpperCase();
  7271. }
  7272. else
  7273. {
  7274. var slash = filename.lastIndexOf('/');
  7275. if (slash >= 0 && slash < filename.length)
  7276. {
  7277. filename = filename.substring(slash + 1);
  7278. }
  7279. }
  7280. // EditorUi.logEvent({category: ext + '-MS-IMPORT-FILE',
  7281. // action: 'filename_' + filename,
  7282. // label: (remote) ? 'remote' : 'local'});
  7283. }
  7284. catch (e)
  7285. {
  7286. // ignore
  7287. }
  7288. if (remote)
  7289. {
  7290. if (VSD_CONVERT_URL != null && !this.isOffline())
  7291. {
  7292. var formData = new FormData();
  7293. formData.append('file1', file, filename);
  7294. var xhr = new XMLHttpRequest();
  7295. xhr.open('POST', VSD_CONVERT_URL + (/(\.vss|\.vsx)$/.test(filename)? '?stencil=1' : ''));
  7296. xhr.responseType = 'blob';
  7297. this.addRemoteServiceSecurityCheck(xhr);
  7298. if (customParam != null)
  7299. {
  7300. xhr.setRequestHeader('x-convert-custom', customParam);
  7301. }
  7302. xhr.onreadystatechange = mxUtils.bind(this, function()
  7303. {
  7304. if (xhr.readyState == 4 && timeout.clear())
  7305. {
  7306. if (xhr.status >= 200 && xhr.status <= 299)
  7307. {
  7308. try
  7309. {
  7310. var resp = xhr.response;
  7311. if (resp.type == 'text/xml')
  7312. {
  7313. var reader = new FileReader();
  7314. reader.onload = mxUtils.bind(this, function(e)
  7315. {
  7316. try
  7317. {
  7318. done(e.target.result);
  7319. }
  7320. catch (e)
  7321. {
  7322. handleError({message: mxResources.get('errorLoadingFile')});
  7323. }
  7324. });
  7325. reader.readAsText(resp);
  7326. }
  7327. else
  7328. {
  7329. this.doImportVisio(resp, done, handleError, filename);
  7330. }
  7331. }
  7332. catch (e)
  7333. {
  7334. handleError(e);
  7335. }
  7336. }
  7337. else
  7338. {
  7339. try
  7340. {
  7341. if (xhr.responseType == '' || xhr.responseType == 'text')
  7342. {
  7343. handleError({message: xhr.responseText});
  7344. }
  7345. else
  7346. {
  7347. var reader = new FileReader();
  7348. reader.onload = function()
  7349. {
  7350. try
  7351. {
  7352. handleError({message: JSON.parse(reader.result).Message});
  7353. }
  7354. catch (e)
  7355. {
  7356. handleError(e);
  7357. }
  7358. }
  7359. reader.readAsText(xhr.response);
  7360. }
  7361. }
  7362. catch(e)
  7363. {
  7364. handleError({});
  7365. }
  7366. }
  7367. }
  7368. });
  7369. xhr.send(formData);
  7370. }
  7371. else
  7372. {
  7373. handleError({message: this.getServiceName() != 'draw.io'? mxResources.get('vsdNoConfig') :
  7374. mxResources.get('serviceUnavailableOrBlocked')});
  7375. }
  7376. }
  7377. else if (timeout.clear())
  7378. {
  7379. try
  7380. {
  7381. this.doImportVisio(file, done, handleError, filename);
  7382. }
  7383. catch (e)
  7384. {
  7385. handleError(e);
  7386. }
  7387. }
  7388. }
  7389. else
  7390. {
  7391. handleError({message: mxResources.get('serviceUnavailableOrBlocked')});
  7392. }
  7393. });
  7394. if (!this.doImportVisio && !this.loadingExtensions && !this.isOffline(true))
  7395. {
  7396. this.loadingExtensions = true;
  7397. mxscript(window.DRAWIO_SERVER_URL + 'js/extensions.min.js', delayed, null, null, null, handleError);
  7398. }
  7399. else
  7400. {
  7401. delayed();
  7402. }
  7403. }), onerror);
  7404. };
  7405. /**
  7406. * Imports the given GraphML (yEd) file
  7407. */
  7408. EditorUi.prototype.importGraphML = function(xmlData, done, error)
  7409. {
  7410. var onerror = mxUtils.bind(this, function(e)
  7411. {
  7412. this.loadingExtensions = false;
  7413. if (error != null)
  7414. {
  7415. error(e);
  7416. }
  7417. else
  7418. {
  7419. this.handleError(e);
  7420. }
  7421. });
  7422. this.createTimeout(null, mxUtils.bind(this, function(timeout)
  7423. {
  7424. var handleError = mxUtils.bind(this, function(e)
  7425. {
  7426. if (timeout.clear())
  7427. {
  7428. onerror(e);
  7429. }
  7430. });
  7431. var delayed = mxUtils.bind(this, function()
  7432. {
  7433. this.loadingExtensions = false;
  7434. if (timeout.clear())
  7435. {
  7436. if (this.doImportGraphML)
  7437. {
  7438. try
  7439. {
  7440. this.doImportGraphML(xmlData, done, onerror);
  7441. }
  7442. catch (e)
  7443. {
  7444. handleError(e);
  7445. }
  7446. }
  7447. else
  7448. {
  7449. handleError({message: mxResources.get('serviceUnavailableOrBlocked')});
  7450. }
  7451. }
  7452. });
  7453. if (!this.doImportGraphML && !this.loadingExtensions && !this.isOffline(true))
  7454. {
  7455. this.loadingExtensions = true;
  7456. mxscript(window.DRAWIO_SERVER_URL + 'js/extensions.min.js', delayed, null, null, null, handleError);
  7457. }
  7458. else
  7459. {
  7460. delayed();
  7461. }
  7462. }), onerror);
  7463. };
  7464. /**
  7465. * Export the diagram to VSDX
  7466. */
  7467. EditorUi.prototype.exportVisio = function(currentPage)
  7468. {
  7469. if (this.spinner.spin(document.body, mxResources.get('loading')))
  7470. {
  7471. var onerror = mxUtils.bind(this, function(e)
  7472. {
  7473. this.loadingExtensions = false;
  7474. this.handleError(e);
  7475. });
  7476. this.createTimeout(null, mxUtils.bind(this, function(timeout)
  7477. {
  7478. var handleError = mxUtils.bind(this, function(e)
  7479. {
  7480. if (timeout.clear())
  7481. {
  7482. onerror(e);
  7483. }
  7484. });
  7485. var delayed = mxUtils.bind(this, function()
  7486. {
  7487. this.loadingExtensions = false;
  7488. if (timeout.clear())
  7489. {
  7490. if (typeof VsdxExport !== 'undefined')
  7491. {
  7492. try
  7493. {
  7494. this.spinner.stop();
  7495. var expSuccess = new VsdxExport(this).exportCurrentDiagrams(currentPage);
  7496. if (!expSuccess)
  7497. {
  7498. handleError({message: mxResources.get('unknownError')});
  7499. }
  7500. }
  7501. catch (e)
  7502. {
  7503. handleError(e);
  7504. }
  7505. }
  7506. else
  7507. {
  7508. handleError({message: mxResources.get('serviceUnavailableOrBlocked')});
  7509. }
  7510. }
  7511. });
  7512. if (typeof VsdxExport === 'undefined' && !this.loadingExtensions && !this.isOffline(true))
  7513. {
  7514. this.loadingExtensions = true;
  7515. mxscript(window.DRAWIO_SERVER_URL + 'js/extensions.min.js', delayed, null, null, null, handleError);
  7516. }
  7517. else
  7518. {
  7519. // Async needed for showing spinner for longer exports
  7520. window.setTimeout(delayed, 0);
  7521. }
  7522. }), onerror);
  7523. }
  7524. };
  7525. /**
  7526. * Imports the given Lucidchart data.
  7527. */
  7528. EditorUi.prototype.convertLucidChart = function(data, success, error)
  7529. {
  7530. var onerror = mxUtils.bind(this, function(e)
  7531. {
  7532. this.loadingExtensions = false;
  7533. if (error != null)
  7534. {
  7535. error(e);
  7536. }
  7537. else
  7538. {
  7539. this.handleError(e);
  7540. }
  7541. });
  7542. this.createTimeout(null, mxUtils.bind(this, function(timeout)
  7543. {
  7544. var handleError = mxUtils.bind(this, function(e)
  7545. {
  7546. if (timeout.clear())
  7547. {
  7548. onerror(e);
  7549. }
  7550. });
  7551. var delayed = mxUtils.bind(this, function()
  7552. {
  7553. this.loadingExtensions = false;
  7554. if (timeout.clear())
  7555. {
  7556. // Checks for signature method
  7557. if (typeof window.LucidImporter !== 'undefined')
  7558. {
  7559. try
  7560. {
  7561. var obj = JSON.parse(data);
  7562. success(LucidImporter.importState(obj));
  7563. try
  7564. {
  7565. // EditorUi.logEvent({category: 'LUCIDCHART-IMPORT-FILE',
  7566. // action: 'size_' + data.length});
  7567. if (window.console != null && urlParams['test'] == '1')
  7568. {
  7569. var args = [new Date().toISOString(), 'convertLucidChart', obj];
  7570. if (obj.state != null)
  7571. {
  7572. args.push(JSON.parse(obj.state));
  7573. }
  7574. if (obj.svgThumbs != null)
  7575. {
  7576. for (var i = 0; i < obj.svgThumbs.length; i++)
  7577. {
  7578. args.push(Editor.createSvgDataUri(obj.svgThumbs[i]));
  7579. }
  7580. }
  7581. if (obj.thumb != null)
  7582. {
  7583. args.push(obj.thumb);
  7584. }
  7585. console.log.apply(console, args);
  7586. }
  7587. }
  7588. catch (e)
  7589. {
  7590. // ignore
  7591. }
  7592. }
  7593. catch (e)
  7594. {
  7595. if (window.console != null)
  7596. {
  7597. console.error(e);
  7598. }
  7599. handleError(e);
  7600. }
  7601. }
  7602. else
  7603. {
  7604. handleError({message: mxResources.get('serviceUnavailableOrBlocked')});
  7605. }
  7606. }
  7607. });
  7608. if (typeof window.LucidImporter === 'undefined' &&
  7609. !this.loadingExtensions && !this.isOffline(true))
  7610. {
  7611. this.loadingExtensions = true;
  7612. if (urlParams['dev'] == '1')
  7613. {
  7614. //Lucid org chart requires orgChart layout, in production, it is part of the extemsions.min.js
  7615. mxscript('js/diagramly/Extensions.js', function()
  7616. {
  7617. mxscript('js/orgchart/bridge.min.js', function()
  7618. {
  7619. mxscript('js/orgchart/bridge.collections.min.js', function()
  7620. {
  7621. mxscript('js/orgchart/OrgChart.Layout.min.js', function()
  7622. {
  7623. mxscript('js/orgchart/mxOrgChartLayout.js',
  7624. delayed, null, null, null, handleError);
  7625. }, null, null, null, handleError);
  7626. }, null, null, null, handleError);
  7627. }, null, null, null, handleError);
  7628. }, null, null, null, handleError);
  7629. }
  7630. else
  7631. {
  7632. mxscript(window.DRAWIO_SERVER_URL + 'js/extensions.min.js', delayed,
  7633. null, null, null, handleError);
  7634. }
  7635. }
  7636. else
  7637. {
  7638. // Async needed for selection
  7639. window.setTimeout(delayed, 0);
  7640. }
  7641. }), onerror);
  7642. };
  7643. /**
  7644. * Generates a Mermaid image.
  7645. */
  7646. EditorUi.prototype.createMermaidXml = function(input, config, data, w, h, prompt)
  7647. {
  7648. var graph = new Graph(document.createElement('div'));
  7649. var cell = graph.insertVertex(null, null, null, 0, 0, w, h,
  7650. 'shape=image;noLabel=1;verticalAlign=top;' +
  7651. 'imageAspect=1;image=' + data + ';')
  7652. graph.setAttributeForCell(cell, 'mermaidData', JSON.stringify(
  7653. {data: input, config: config}, null, 2));
  7654. if (prompt != null)
  7655. {
  7656. graph.setAttributeForCell(cell, 'templatePrompt', prompt);
  7657. }
  7658. var codec = new mxCodec();
  7659. var node = codec.encode(graph.getModel());
  7660. return mxUtils.getXml(node);
  7661. };
  7662. /**
  7663. * Generates a Mermaid image.
  7664. */
  7665. EditorUi.prototype.generateOpenAiMermaidDiagram = function(prompt, success, error)
  7666. {
  7667. var maxRetries = 3;
  7668. var retryCount = 0;
  7669. var fn = mxUtils.bind(this, function()
  7670. {
  7671. this.createTimeout(10000, mxUtils.bind(this, function(timeout)
  7672. {
  7673. // EditorUi.logEvent({category: 'OPENAI-DIAGRAM',
  7674. // action: 'generateOpenAiMermaidDiagram',
  7675. // label: prompt});
  7676. var url = 'https://www.draw.io/generate/v2';
  7677. var req = new mxXmlRequest(url, prompt, 'POST');
  7678. var handleError = mxUtils.bind(this, function(e)
  7679. {
  7680. if (timeout.clear())
  7681. {
  7682. error(e);
  7683. }
  7684. });
  7685. req.send(mxUtils.bind(this, function(req)
  7686. {
  7687. if (timeout.isAlive())
  7688. {
  7689. if (req.getStatus() >= 200 && req.getStatus() <= 299)
  7690. {
  7691. this.tryAndHandle(mxUtils.bind(this, function()
  7692. {
  7693. var result = mxUtils.trim(req.getText());
  7694. this.generateMermaidImage(result, null, mxUtils.bind(this, function(data, w, h)
  7695. {
  7696. this.tryAndHandle(mxUtils.bind(this, function()
  7697. {
  7698. if (timeout.clear())
  7699. {
  7700. EditorUi.debug('EditorUi.generateOpenAiMermaidDiagram',
  7701. '\nprompt:', prompt, '\nresult:', result);
  7702. success(result, data, w, h);
  7703. }
  7704. }), handleError);
  7705. }), handleError, mxUtils.bind(this, function(e)
  7706. {
  7707. if (retryCount++ < maxRetries)
  7708. {
  7709. if (timeout.clear())
  7710. {
  7711. fn();
  7712. }
  7713. }
  7714. else
  7715. {
  7716. handleError(e);
  7717. }
  7718. }));
  7719. }), handleError);
  7720. }
  7721. else
  7722. {
  7723. var e = {message: mxResources.get('error') + ' ' + req.getStatus()};
  7724. try
  7725. {
  7726. e = JSON.parse(req.getText());
  7727. e = e.error;
  7728. }
  7729. catch (e)
  7730. {
  7731. // ignore
  7732. }
  7733. handleError(e);
  7734. }
  7735. }
  7736. }), handleError);
  7737. }), error);
  7738. });
  7739. fn();
  7740. };
  7741. /**
  7742. * Generates a Mermaid image.
  7743. */
  7744. EditorUi.prototype.extractMermaidDeclaration = function(value)
  7745. {
  7746. value = mxUtils.trim(value);
  7747. // Removes occasional "o" on first line in response
  7748. if (value.substring(0, 3) == 'o\n\n')
  7749. {
  7750. value = value.substring(3);
  7751. }
  7752. // Various formats supported
  7753. var tokens = value.split('```');
  7754. tokens = (tokens.length > 1) ? tokens : value.split('<pre>');
  7755. tokens = (tokens.length > 1) ? tokens : value.split('~~~');
  7756. tokens = (tokens.length > 1) ? tokens : value.split('%%');
  7757. tokens = (tokens.length > 1) ? tokens : value.split('(Begins)');
  7758. var text = mxUtils.trim((tokens.length <= 1) ? value : tokens[1]);
  7759. var lines = text.split('\n');
  7760. // Removes occasional mermaid tag or other text on first line
  7761. var type = (lines.length > 1) ? lines[1] : null;
  7762. if (type != null)
  7763. {
  7764. var dash = type.indexOf('-');
  7765. if (dash > 0)
  7766. {
  7767. type = type.substring(0, dash);
  7768. }
  7769. }
  7770. if ((lines.length > 0 && mxUtils.trim(lines[0]) == 'mermaid') ||
  7771. (type != null && mxUtils.indexOf(
  7772. EditorUi.mermaidDiagramTypes, type) >= 0))
  7773. {
  7774. lines.shift();
  7775. text = mxUtils.trim(lines.join('\n'));
  7776. lines = text.split('\n');
  7777. }
  7778. // Validates diagram type on first line
  7779. type = lines[0].split(' ')[0].replace(/:$/, '');
  7780. var dash = type.indexOf('-');
  7781. if (dash > 0)
  7782. {
  7783. type = type.substring(0, dash);
  7784. }
  7785. try
  7786. {
  7787. if (type == 'mindmap' && lines.length > 2)
  7788. {
  7789. text = extractMermaidMindmap(lines);
  7790. }
  7791. }
  7792. catch (e)
  7793. {
  7794. // ignore
  7795. }
  7796. // TODO Is this too restrictive?
  7797. if (mxUtils.indexOf(EditorUi.mermaidDiagramTypes, type) < 0)
  7798. {
  7799. text = null;
  7800. }
  7801. return text;
  7802. };
  7803. /**
  7804. * Generates a Mermaid image.
  7805. */
  7806. EditorUi.prototype.generateMermaidImage = function(data, config, success, error, parseErrorHandler)
  7807. {
  7808. var onerror = mxUtils.bind(this, function(e)
  7809. {
  7810. this.loadingMermaid = false;
  7811. if (error != null)
  7812. {
  7813. error(e);
  7814. }
  7815. else
  7816. {
  7817. this.handleError(e);
  7818. }
  7819. });
  7820. var delayed = mxUtils.bind(this, function()
  7821. {
  7822. try
  7823. {
  7824. this.loadingMermaid = false;
  7825. config = (config != null) ? config : mxUtils.clone(EditorUi.defaultMermaidConfig);
  7826. config.securityLevel = 'strict';
  7827. config.startOnLoad = false;
  7828. // Math labels
  7829. if (typeof mxMermaidToDrawio !== 'undefined' && config.flowchart && data.indexOf('$$') >= 0)
  7830. {
  7831. config.flowchart.htmlLabels = true;
  7832. mxMermaidToDrawio.htmlLabels = true;
  7833. }
  7834. if (Editor.isDarkMode())
  7835. {
  7836. config.theme = 'dark';
  7837. }
  7838. var renderCallback = mxUtils.bind(this, function(svg)
  7839. {
  7840. try
  7841. {
  7842. // Workaround for namespace errors in SVG output for IE
  7843. if (mxClient.IS_IE || mxClient.IS_IE11)
  7844. {
  7845. svg = svg.replace(/ xmlns:\S*="http:\/\/www.w3.org\/XML\/1998\/namespace"/g, '').
  7846. replace(/ (NS xml|\S*):space="preserve"/g, ' xml:space="preserve"');
  7847. }
  7848. var doc = mxUtils.parseXml(svg);
  7849. var svgs = doc.getElementsByTagName('svg');
  7850. if (svgs.length > 0 && svgs[0].getAttribute('aria-roledescription') != 'error')
  7851. {
  7852. var w = parseFloat(svgs[0].getAttribute('width'));
  7853. var h = parseFloat(svgs[0].getAttribute('height'));
  7854. if (isNaN(w) || isNaN(h))
  7855. {
  7856. try
  7857. {
  7858. var viewBox = svgs[0].getAttribute('viewBox').split(/\s+/);
  7859. w = parseFloat(viewBox[2]);
  7860. h = parseFloat(viewBox[3]);
  7861. }
  7862. catch(e)
  7863. {
  7864. //Any size such that it shows up
  7865. w = w || 100;
  7866. h = h || 100;
  7867. }
  7868. }
  7869. success(this.convertDataUri(Editor.createSvgDataUri(svg)), w, h);
  7870. }
  7871. else
  7872. {
  7873. if (parseErrorHandler != null)
  7874. {
  7875. parseErrorHandler();
  7876. }
  7877. else
  7878. {
  7879. error({message: mxResources.get('invalidInput')});
  7880. }
  7881. }
  7882. }
  7883. catch (e)
  7884. {
  7885. error(e);
  7886. }
  7887. });
  7888. mermaid.mermaidAPI.initialize(config);
  7889. mermaid.mermaidAPI.render('geMermaidOutput-' + new Date().getTime(), data).then(function(result)
  7890. {
  7891. renderCallback(result.svg);
  7892. }).catch(function(e)
  7893. {
  7894. if (parseErrorHandler != null)
  7895. {
  7896. parseErrorHandler(e);
  7897. }
  7898. else
  7899. {
  7900. error(e);
  7901. }
  7902. });
  7903. }
  7904. catch (e)
  7905. {
  7906. error(e);
  7907. }
  7908. });
  7909. if (typeof mermaid === 'undefined' && !this.loadingMermaid && !this.isOffline(true))
  7910. {
  7911. this.loadingMermaid = true;
  7912. if (urlParams['dev'] == '1')
  7913. {
  7914. if (urlParams['mermaidToDrawioTest'] == '1')
  7915. {
  7916. mxMermaidToDrawio.addListener(mxUtils.bind(this, function(modelXml)
  7917. {
  7918. this.importXml(modelXml, null, null, null, null, null, true);
  7919. }));
  7920. }
  7921. mxscript('js/mermaid/mermaid.min.js', delayed,
  7922. null, null, null, onerror);
  7923. }
  7924. else
  7925. {
  7926. mxscript(window.DRAWIO_SERVER_URL + 'js/extensions.min.js', delayed,
  7927. null, null, null, onerror);
  7928. }
  7929. }
  7930. else
  7931. {
  7932. window.setTimeout(delayed, 0);
  7933. }
  7934. };
  7935. /**
  7936. * Generates a plant UML image. Possible types are svg, png and txt.
  7937. */
  7938. EditorUi.prototype.generatePlantUmlImage = function(data, type, success, error)
  7939. {
  7940. function encode64(data)
  7941. {
  7942. r = "";
  7943. for (i = 0; i < data.length; i += 3)
  7944. {
  7945. if (i + 2 == data.length)
  7946. {
  7947. r += append3bytes(data.charCodeAt(i), data.charCodeAt(i + 1), 0);
  7948. }
  7949. else if (i + 1 == data.length)
  7950. {
  7951. r += append3bytes(data.charCodeAt(i), 0, 0);
  7952. }
  7953. else
  7954. {
  7955. r += append3bytes(data.charCodeAt(i), data.charCodeAt(i + 1),
  7956. data.charCodeAt(i + 2));
  7957. }
  7958. }
  7959. return r;
  7960. }
  7961. function append3bytes(b1, b2, b3)
  7962. {
  7963. c1 = b1 >> 2;
  7964. c2 = ((b1 & 0x3) << 4) | (b2 >> 4);
  7965. c3 = ((b2 & 0xF) << 2) | (b3 >> 6);
  7966. c4 = b3 & 0x3F;
  7967. r = "";
  7968. r += encode6bit(c1 & 0x3F);
  7969. r += encode6bit(c2 & 0x3F);
  7970. r += encode6bit(c3 & 0x3F);
  7971. r += encode6bit(c4 & 0x3F);
  7972. return r;
  7973. }
  7974. function encode6bit(b)
  7975. {
  7976. if (b < 10)
  7977. {
  7978. return String.fromCharCode(48 + b);
  7979. }
  7980. b -= 10;
  7981. if (b < 26)
  7982. {
  7983. return String.fromCharCode(65 + b);
  7984. }
  7985. b -= 26;
  7986. if (b < 26)
  7987. {
  7988. return String.fromCharCode(97 + b);
  7989. }
  7990. b -= 26;
  7991. if (b == 0)
  7992. {
  7993. return '-';
  7994. }
  7995. if (b == 1)
  7996. {
  7997. return '_';
  7998. }
  7999. return '?';
  8000. }
  8001. // TODO: Remove unescape, use btoa for compatibility with graph.compress
  8002. function compress(s)
  8003. {
  8004. return encode64(Graph.arrayBufferToString(pako.deflateRaw(s)));
  8005. };
  8006. var plantUmlServerUrl = (type == 'txt') ? PLANT_URL + '/txt/' :
  8007. ((type == 'png') ? PLANT_URL + '/png/' : PLANT_URL + '/svg/');
  8008. var xhr = new XMLHttpRequest();
  8009. xhr.open('GET', plantUmlServerUrl + compress(data), true);
  8010. if (type != 'txt')
  8011. {
  8012. xhr.responseType = 'blob';
  8013. }
  8014. xhr.onload = function(e)
  8015. {
  8016. if (this.status >= 200 && this.status < 300)
  8017. {
  8018. if (type == 'txt')
  8019. {
  8020. success(this.response);
  8021. }
  8022. else
  8023. {
  8024. var reader = new FileReader();
  8025. reader.readAsDataURL(this.response);
  8026. reader.onloadend = function(e)
  8027. {
  8028. var img = new Image();
  8029. img.onload = function()
  8030. {
  8031. try
  8032. {
  8033. var w = img.width;
  8034. var h = img.height;
  8035. // Workaround for 0 image size in IE11
  8036. if (w == 0 && h == 0)
  8037. {
  8038. var data = reader.result;
  8039. var comma = data.indexOf(',');
  8040. var svgText = decodeURIComponent(escape(atob(data.substring(comma + 1))));
  8041. var root = mxUtils.parseXml(svgText);
  8042. var svgs = root.getElementsByTagName('svg');
  8043. if (svgs.length > 0)
  8044. {
  8045. w = parseFloat(svgs[0].getAttribute('width'));
  8046. h = parseFloat(svgs[0].getAttribute('height'));
  8047. }
  8048. }
  8049. success(reader.result, w, h);
  8050. }
  8051. catch (e)
  8052. {
  8053. error(e);
  8054. }
  8055. };
  8056. img.src = reader.result;
  8057. };
  8058. reader.onerror = function(e)
  8059. {
  8060. error(e);
  8061. };
  8062. }
  8063. }
  8064. else
  8065. {
  8066. error(e);
  8067. }
  8068. };
  8069. xhr.onerror = function(e)
  8070. {
  8071. error(e);
  8072. };
  8073. xhr.send();
  8074. };
  8075. /**
  8076. * Inserts the given text as a preformatted HTML text.
  8077. */
  8078. EditorUi.prototype.insertAsPreText = function(text, x, y)
  8079. {
  8080. var graph = this.editor.graph;
  8081. var cell = null;
  8082. graph.getModel().beginUpdate();
  8083. try
  8084. {
  8085. cell = graph.insertVertex(null, null, '<pre>' + text + '</pre>',
  8086. x, y, 1, 1, 'text;html=1;align=left;verticalAlign=top;');
  8087. graph.updateCellSize(cell, true);
  8088. }
  8089. finally
  8090. {
  8091. graph.getModel().endUpdate();
  8092. }
  8093. return cell;
  8094. };
  8095. /**
  8096. * Imports the given XML into the existing diagram.
  8097. * TODO: Make this function asynchronous
  8098. */
  8099. EditorUi.prototype.insertTextAt = function(text, dx, dy, html, asImage, crop, resizeImages, addNewPage)
  8100. {
  8101. crop = (crop != null) ? crop : true;
  8102. resizeImages = (resizeImages != null) ? resizeImages : true;
  8103. // Handles special case for Gliffy data which requires async server-side for parsing
  8104. if (text != null)
  8105. {
  8106. if (Graph.fileSupport && new XMLHttpRequest().upload && this.isRemoteFileFormat(text))
  8107. {
  8108. if (this.isOffline())
  8109. {
  8110. this.showError(mxResources.get('error'), mxResources.get('notInOffline'));
  8111. }
  8112. else
  8113. {
  8114. // Fixes possible parsing problems with ASCII 160 (non-breaking space)
  8115. this.parseFileData(text.replace(/\s+/g,' '), mxUtils.bind(this, function(xhr)
  8116. {
  8117. if (xhr.readyState == 4 && xhr.status >= 200 && xhr.status <= 299)
  8118. {
  8119. this.editor.graph.setSelectionCells(this.insertTextAt(
  8120. xhr.responseText, dx, dy, true));
  8121. }
  8122. }));
  8123. }
  8124. // Returns empty cells array as it is aysynchronous
  8125. return [];
  8126. }
  8127. // Handles special case of data URI which requires async loading for finding size
  8128. else if (text.substring(0, 5) == 'data:' || (!this.isOffline() &&
  8129. (asImage || (/\.(gif|jpg|jpeg|tiff|png|svg)$/i).test(text))))
  8130. {
  8131. var graph = this.editor.graph;
  8132. // Checks for embedded XML in PDF
  8133. if (text.substring(0, 28) == 'data:application/pdf;base64,')
  8134. {
  8135. var xml = Editor.extractGraphModelFromPdf(text);
  8136. if (xml != null && xml.length > 0)
  8137. {
  8138. return this.importXml(xml, dx, dy, crop, true, addNewPage);
  8139. }
  8140. }
  8141. // Checks for embedded XML in PNG
  8142. if (Editor.isPngDataUrl(text))
  8143. {
  8144. var xml = Editor.extractGraphModelFromPng(text);
  8145. if (xml != null && xml.length > 0)
  8146. {
  8147. return this.importXml(xml, dx, dy, crop, true, addNewPage);
  8148. }
  8149. }
  8150. // Tries to extract embedded XML from SVG data URI
  8151. if (text.substring(0, 19) == 'data:image/svg+xml;')
  8152. {
  8153. try
  8154. {
  8155. var xml = null;
  8156. if (text.substring(0, 26) == 'data:image/svg+xml;base64,')
  8157. {
  8158. xml = text.substring(text.indexOf(',') + 1);
  8159. xml = (window.atob && !mxClient.IS_SF) ? atob(xml) : Base64.decode(xml, true);
  8160. }
  8161. else
  8162. {
  8163. xml = decodeURIComponent(text.substring(text.indexOf(',') + 1));
  8164. }
  8165. var result = this.importXml(xml, dx, dy, crop, true, addNewPage);
  8166. if (result.length > 0)
  8167. {
  8168. return result;
  8169. }
  8170. }
  8171. catch (e)
  8172. {
  8173. // Ignore
  8174. }
  8175. }
  8176. this.loadImage(text, mxUtils.bind(this, function(img)
  8177. {
  8178. if (text.substring(0, 5) == 'data:')
  8179. {
  8180. this.resizeImage(img, text, mxUtils.bind(this, function(data2, w2, h2)
  8181. {
  8182. graph.setSelectionCell(graph.insertVertex(null, null, '', graph.snap(dx), graph.snap(dy),
  8183. w2, h2, 'shape=image;verticalLabelPosition=bottom;labelBackgroundColor=default;' +
  8184. 'verticalAlign=top;aspect=fixed;imageAspect=0;image=' + this.convertDataUri(data2) + ';'));
  8185. }), resizeImages, this.maxImageSize);
  8186. }
  8187. else
  8188. {
  8189. var s = Math.min(1, Math.min(this.maxImageSize / img.width, this.maxImageSize / img.height));
  8190. var w = Math.round(img.width * s);
  8191. var h = Math.round(img.height * s);
  8192. graph.setSelectionCell(graph.insertVertex(null, null, '', graph.snap(dx), graph.snap(dy),
  8193. w, h, 'shape=image;verticalLabelPosition=bottom;labelBackgroundColor=default;' +
  8194. 'verticalAlign=top;aspect=fixed;imageAspect=0;image=' + text + ';'));
  8195. }
  8196. }), mxUtils.bind(this, function()
  8197. {
  8198. var cell = null;
  8199. // Inserts invalid data URIs as text
  8200. graph.getModel().beginUpdate();
  8201. try
  8202. {
  8203. cell = graph.insertVertex(graph.getDefaultParent(), null, text,
  8204. graph.snap(dx), graph.snap(dy), 1, 1, 'text;' +
  8205. ((html) ? 'html=1;' : ''));
  8206. graph.updateCellSize(cell);
  8207. graph.fireEvent(new mxEventObject('textInserted', 'cells', [cell]));
  8208. }
  8209. finally
  8210. {
  8211. graph.getModel().endUpdate();
  8212. }
  8213. graph.setSelectionCell(cell);
  8214. }));
  8215. return [];
  8216. }
  8217. else
  8218. {
  8219. text = Graph.zapGremlins(mxUtils.trim(text));
  8220. if (this.isCompatibleString(text))
  8221. {
  8222. return this.importXml(text, dx, dy, crop, null, addNewPage);
  8223. }
  8224. else if (text.length > 0)
  8225. {
  8226. if (this.isLucidChartData(text))
  8227. {
  8228. this.convertLucidChart(text, mxUtils.bind(this, function(xml)
  8229. {
  8230. this.editor.graph.setSelectionCells(
  8231. this.importXml(xml, dx, dy, crop,
  8232. null, addNewPage));
  8233. }), mxUtils.bind(this, function(e)
  8234. {
  8235. this.handleError(e);
  8236. }));
  8237. }
  8238. else
  8239. {
  8240. var graph = this.editor.graph;
  8241. var cell = null;
  8242. graph.getModel().beginUpdate();
  8243. try
  8244. {
  8245. // Fires cellsInserted to apply the current style to the inserted text.
  8246. // This requires the value to be empty when the event is fired.
  8247. cell = graph.insertVertex(graph.getDefaultParent(), null, '',
  8248. graph.snap(dx), graph.snap(dy), 1, 1, 'text;whiteSpace=wrap;' +
  8249. ((html) ? 'html=1;' : ''));
  8250. graph.fireEvent(new mxEventObject('textInserted', 'cells', [cell]));
  8251. if (html)
  8252. {
  8253. text = graph.sanitizeHtml(text);
  8254. }
  8255. //TODO Refuse unsupported file types early as at this stage a lot of processing has beed done and time is wasted.
  8256. // For example, 5 MB PDF files is processed and then only 0.5 MB of meaningless text is added!
  8257. //Limit labels to maxTextBytes
  8258. if (text.length > this.maxTextBytes)
  8259. {
  8260. text = text.substring(0, this.maxTextBytes) + '...';
  8261. }
  8262. // Apply value and updates the cell size to fit the text block
  8263. cell.value = text;
  8264. graph.updateCellSize(cell);
  8265. // Adds wrapping for large text blocks
  8266. if (this.maxTextWidth > 0 && cell.geometry.width > this.maxTextWidth)
  8267. {
  8268. var size = graph.getPreferredSizeForCell(cell, this.maxTextWidth);
  8269. cell.geometry.width = size.width;
  8270. cell.geometry.height = size.height;
  8271. }
  8272. // See https://stackoverflow.com/questions/5717093/check-if-a-javascript-string-is-a-url
  8273. if (Graph.isLink(cell.value))
  8274. {
  8275. graph.setLinkForCell(cell, cell.value);
  8276. }
  8277. // Adds spacing
  8278. cell.geometry.width += graph.gridSize;
  8279. cell.geometry.height += graph.gridSize;
  8280. }
  8281. finally
  8282. {
  8283. graph.getModel().endUpdate();
  8284. }
  8285. return [cell];
  8286. }
  8287. }
  8288. }
  8289. }
  8290. return [];
  8291. };
  8292. /**
  8293. * Formats the given file size.
  8294. */
  8295. EditorUi.prototype.formatFileSize = function(size)
  8296. {
  8297. var units = [' kB', ' MB', ' GB', ' TB', 'PB', 'EB', 'ZB', 'YB'];
  8298. var i = -1;
  8299. do
  8300. {
  8301. size = size / 1024;
  8302. i++;
  8303. } while (size > 1024);
  8304. return Math.max(size, 0.1).toFixed(1) + units[i];
  8305. };
  8306. /**
  8307. * Imports the given XML into the existing diagram.
  8308. */
  8309. EditorUi.prototype.convertDataUri = function(uri)
  8310. {
  8311. // Handles special case of data URI which needs to be rewritten
  8312. // to be used in a cell style to remove the semicolon
  8313. if (uri.substring(0, 5) == 'data:')
  8314. {
  8315. var semi = uri.indexOf(';');
  8316. if (semi > 0)
  8317. {
  8318. uri = uri.substring(0, semi) + uri.substring(uri.indexOf(',', semi + 1));
  8319. }
  8320. }
  8321. return uri;
  8322. };
  8323. /**
  8324. * Returns true for Gliffy data.
  8325. */
  8326. EditorUi.prototype.isRemoteFileFormat = function(data, filename)
  8327. {
  8328. return /(\"contentType\":\s*\"application\/gliffy\+json\")/.test(data);
  8329. };
  8330. /**
  8331. * Returns true for Gliffy
  8332. */
  8333. EditorUi.prototype.isLucidChartData = function(data)
  8334. {
  8335. return data != null && (data.substring(0, 26) ==
  8336. '{"state":"{\\"Properties\\":' ||
  8337. data.substring(0, 14) == '{"Properties":');
  8338. };
  8339. /**
  8340. * Imports a local file from the device or local storage.
  8341. */
  8342. EditorUi.prototype.importLocalFile = function(device, noSplash)
  8343. {
  8344. if (device && Graph.fileSupport)
  8345. {
  8346. if (this.importFileInputElt == null)
  8347. {
  8348. var input = document.createElement('input');
  8349. input.setAttribute('type', 'file');
  8350. mxEvent.addListener(input, 'change', mxUtils.bind(this, function()
  8351. {
  8352. if (input.files != null)
  8353. {
  8354. // Using null for position will disable crop of input file
  8355. this.importFiles(input.files, null, null, this.maxImageSize);
  8356. // Resets input to force change event for same file (type reset required for IE)
  8357. input.type = '';
  8358. input.type = 'file';
  8359. input.value = '';
  8360. }
  8361. }));
  8362. input.style.display = 'none';
  8363. document.body.appendChild(input);
  8364. this.importFileInputElt = input;
  8365. }
  8366. this.importFileInputElt.click();
  8367. }
  8368. else
  8369. {
  8370. window.openNew = false;
  8371. window.openKey = 'import';
  8372. window.listBrowserFiles = mxUtils.bind(this, function(success, error)
  8373. {
  8374. StorageFile.listFiles(this, 'F', success, error);
  8375. });
  8376. window.openBrowserFile = mxUtils.bind(this, function(title, success, error)
  8377. {
  8378. StorageFile.getFileContent(this, title, success, error);
  8379. });
  8380. window.deleteBrowserFile = mxUtils.bind(this, function(title, success, error)
  8381. {
  8382. StorageFile.deleteFile(this, title, success, error);
  8383. });
  8384. if (!noSplash)
  8385. {
  8386. var prevValue = Editor.useLocalStorage;
  8387. Editor.useLocalStorage = !device;
  8388. }
  8389. // Closes dialog after open
  8390. window.openFile = new OpenFile(mxUtils.bind(this, function(cancel)
  8391. {
  8392. this.hideDialog(cancel);
  8393. }));
  8394. window.openFile.setConsumer(mxUtils.bind(this, function(xml, filename)
  8395. {
  8396. if (filename != null && Graph.fileSupport && /(\.v(dx|sdx?))($|\?)/i.test(filename))
  8397. {
  8398. // "Not a UTF 8 file" when opening VSDX in IE so this is never called
  8399. var file = new Blob([xml], {type: 'application/octet-stream'})
  8400. this.importVisio(file, mxUtils.bind(this, function(xml)
  8401. {
  8402. this.importXml(xml, 0, 0, true);
  8403. }), null, filename);
  8404. }
  8405. else
  8406. {
  8407. this.editor.graph.setSelectionCells(this.importXml(xml, 0, 0, true));
  8408. }
  8409. }));
  8410. // Removes openFile if dialog is closed
  8411. this.showDialog(new OpenDialog(this).container, (Editor.useLocalStorage) ? 640 : 360,
  8412. (Editor.useLocalStorage) ? 480 : 220, true, true, function()
  8413. {
  8414. window.openFile = null;
  8415. });
  8416. // Extends dialog close to show splash screen
  8417. if (!noSplash)
  8418. {
  8419. var dlg = this.dialog;
  8420. var dlgClose = dlg.close;
  8421. this.dialog.close = mxUtils.bind(this, function(cancel)
  8422. {
  8423. Editor.useLocalStorage = prevValue;
  8424. dlgClose.apply(dlg, arguments);
  8425. if (cancel && this.getCurrentFile() == null && urlParams['embed'] != '1')
  8426. {
  8427. this.showSplash();
  8428. }
  8429. });
  8430. }
  8431. }
  8432. };
  8433. /**
  8434. * Imports the given zip file.
  8435. */
  8436. EditorUi.prototype.importZipFile = function(file, success, onerror)
  8437. {
  8438. var ui = this;
  8439. var delayed = mxUtils.bind(this, function()
  8440. {
  8441. this.loadingExtensions = false;
  8442. if (typeof JSZip !== 'undefined')
  8443. {
  8444. JSZip.loadAsync(file).then(function(zip)
  8445. {
  8446. if (mxUtils.isEmptyObject(zip.files))
  8447. {
  8448. onerror();
  8449. }
  8450. else
  8451. {
  8452. var gliffyLatestVer = {version: 0};
  8453. var drawioFound = false;
  8454. zip.forEach(function (relativePath, zipEntry)
  8455. {
  8456. var name = zipEntry.name.toLowerCase();
  8457. if (name == 'diagram/diagram.xml') //draw.io zip format has the latest diagram version at diagram/diagram.xml
  8458. {
  8459. drawioFound = true;
  8460. zipEntry.async("string").then(function(str){
  8461. if (str.indexOf('<mxfile ') == 0)
  8462. {
  8463. success(str);
  8464. }
  8465. else
  8466. {
  8467. onerror();
  8468. }
  8469. });
  8470. }
  8471. else if (name.indexOf('versions/') == 0) //Gliffy zip format has the versions inside versions folder
  8472. {
  8473. var version = parseInt(name.substr(9)); //9 is the length of versions/
  8474. if (version > gliffyLatestVer.version)
  8475. {
  8476. gliffyLatestVer = {version: version, zipEntry: zipEntry}
  8477. }
  8478. }
  8479. });
  8480. if (gliffyLatestVer.version > 0)
  8481. {
  8482. gliffyLatestVer.zipEntry.async("string").then(function(data)
  8483. {
  8484. if (new XMLHttpRequest().upload && ui.isRemoteFileFormat(data, file.name))
  8485. {
  8486. if (ui.isOffline())
  8487. {
  8488. ui.showError(mxResources.get('error'), mxResources.get('notInOffline'), null, onerror);
  8489. }
  8490. else
  8491. {
  8492. ui.parseFileData(data, mxUtils.bind(this, function(xhr)
  8493. {
  8494. if (xhr.readyState == 4)
  8495. {
  8496. if (xhr.status >= 200 && xhr.status <= 299)
  8497. {
  8498. success(xhr.responseText);
  8499. }
  8500. else
  8501. {
  8502. onerror();
  8503. }
  8504. }
  8505. }), file.name);
  8506. }
  8507. }
  8508. else
  8509. {
  8510. onerror();
  8511. }
  8512. });
  8513. }
  8514. else if (!drawioFound)
  8515. {
  8516. onerror();
  8517. }
  8518. }
  8519. }, function (e) {
  8520. onerror(e);
  8521. });
  8522. }
  8523. else
  8524. {
  8525. onerror();
  8526. }
  8527. });
  8528. if (typeof JSZip === 'undefined' && !this.loadingExtensions && !this.isOffline(true))
  8529. {
  8530. this.loadingExtensions = true;
  8531. mxscript(window.DRAWIO_SERVER_URL + 'js/extensions.min.js', delayed,
  8532. null, null, null, onerror);
  8533. }
  8534. else
  8535. {
  8536. delayed();
  8537. }
  8538. };
  8539. /**
  8540. * Imports the given XML into the existing diagram.
  8541. */
  8542. EditorUi.prototype.importFile = function(data, mimeType, dx, dy, w, h, filename,
  8543. done, file, crop, ignoreEmbeddedXml, evt)
  8544. {
  8545. crop = (crop != null) ? crop : true;
  8546. var async = false;
  8547. var cells = null;
  8548. var handleResult = mxUtils.bind(this, function(xml)
  8549. {
  8550. var importedCells = null;
  8551. if (xml != null && xml.substring(0, 10) == '<mxlibrary')
  8552. {
  8553. this.loadLibrary(new LocalLibrary(this, xml, filename));
  8554. this.showSidebar();
  8555. }
  8556. else
  8557. {
  8558. importedCells = this.importXml(xml, dx, dy, crop, null,
  8559. (evt != null) ? mxEvent.isControlDown(evt) : null);
  8560. }
  8561. if (done != null)
  8562. {
  8563. done(importedCells);
  8564. }
  8565. });
  8566. if (mimeType.substring(0, 5) == 'image')
  8567. {
  8568. var containsModel = false;
  8569. if (mimeType.substring(0, 9) == 'image/png')
  8570. {
  8571. var xml = (ignoreEmbeddedXml) ? null : this.extractGraphModelFromPng(data);
  8572. if (xml != null && xml.length > 0)
  8573. {
  8574. cells = this.importXml(xml, dx, dy, crop, null, (evt != null) ?
  8575. mxEvent.isControlDown(evt) : null);
  8576. containsModel = true;
  8577. }
  8578. }
  8579. if (!containsModel)
  8580. {
  8581. var graph = this.editor.graph;
  8582. // Strips encoding bit (eg. ;base64,) for cell style
  8583. var semi = data.indexOf(';');
  8584. if (semi > 0)
  8585. {
  8586. data = data.substring(0, semi) + data.substring(data.indexOf(',', semi + 1));
  8587. }
  8588. if (crop && graph.isGridEnabled())
  8589. {
  8590. dx = graph.snap(dx);
  8591. dy = graph.snap(dy);
  8592. }
  8593. cells = [graph.insertVertex(null, null, '', dx, dy, w, h,
  8594. 'shape=image;verticalLabelPosition=bottom;labelBackgroundColor=default;' +
  8595. 'verticalAlign=top;aspect=fixed;imageAspect=0;image=' + data + ';')];
  8596. }
  8597. }
  8598. else if (/(\.*<graphml )/.test(data))
  8599. {
  8600. async = true;
  8601. this.importGraphML(data, handleResult);
  8602. }
  8603. else if (file != null && filename != null && EditorUi.isVisioFilename(filename))
  8604. {
  8605. // LATER: done and async are a hack before making this asynchronous
  8606. async = true;
  8607. this.importVisio(file, handleResult);
  8608. }
  8609. else if (new XMLHttpRequest().upload && this.isRemoteFileFormat(data, filename))
  8610. {
  8611. if (this.isOffline())
  8612. {
  8613. this.showError(mxResources.get('error'), mxResources.get('notInOffline'));
  8614. }
  8615. else
  8616. {
  8617. // LATER: done and async are a hack before making this asynchronous
  8618. async = true;
  8619. // Returns empty cells array as it is aysynchronous
  8620. var parseCallback = mxUtils.bind(this, function(xhr)
  8621. {
  8622. if (xhr.readyState == 4)
  8623. {
  8624. if (xhr.status >= 200 && xhr.status <= 299)
  8625. {
  8626. handleResult(xhr.responseText);
  8627. }
  8628. else if (done != null)
  8629. {
  8630. done(null);
  8631. this.showError(mxResources.get('error'), xhr.status == 413? mxResources.get('diagramTooLarge') :
  8632. mxResources.get('unknownError'));
  8633. }
  8634. }
  8635. });
  8636. if (data != null)
  8637. {
  8638. this.parseFileData(data, parseCallback, filename);
  8639. }
  8640. else
  8641. {
  8642. this.parseFile(file, parseCallback, filename);
  8643. }
  8644. }
  8645. }
  8646. else if (data.indexOf('PK') == 0 && file != null)
  8647. {
  8648. async = true;
  8649. this.importZipFile(file, handleResult, mxUtils.bind(this, function()
  8650. {
  8651. //If importing as a zip file failed, just insert as text
  8652. cells = this.insertTextAt(this.validateFileData(data), dx, dy, true, null, crop);
  8653. done(cells);
  8654. }));
  8655. }
  8656. else if (!/(\.v(sd|dx))($|\?)/i.test(filename) && !/(\.vs(s|x))($|\?)/i.test(filename))
  8657. {
  8658. cells = this.insertTextAt(this.validateFileData(data), dx, dy, true,
  8659. null, crop, null, (evt != null) ? mxEvent.isControlDown(evt) : null);
  8660. }
  8661. if (!async && done != null)
  8662. {
  8663. done(cells);
  8664. }
  8665. return cells;
  8666. };
  8667. /**
  8668. *
  8669. */
  8670. EditorUi.prototype.importFiles = function(files, x, y, maxSize, fn, resultFn, filterFn, barrierFn,
  8671. resizeDialog, maxBytes, resampleThreshold, ignoreEmbeddedXml, evt)
  8672. {
  8673. maxSize = (maxSize != null) ? maxSize : this.maxImageSize;
  8674. maxBytes = (maxBytes != null) ? maxBytes : this.maxImageBytes;
  8675. var crop = x != null && y != null;
  8676. var resizeImages = true;
  8677. x = (x != null) ? x : 0;
  8678. y = (y != null) ? y : 0;
  8679. // Checks if large images are imported
  8680. var largeImages = false;
  8681. if (!mxClient.IS_CHROMEAPP && files != null)
  8682. {
  8683. var thresh = resampleThreshold || this.resampleThreshold;
  8684. for (var i = 0; i < files.length; i++)
  8685. {
  8686. if (files[i].type.substring(0, 9) !== 'image/svg' &&
  8687. files[i].type.substring(0, 6) === 'image/' &&
  8688. files[i].size > thresh)
  8689. {
  8690. largeImages = true;
  8691. break;
  8692. }
  8693. }
  8694. }
  8695. var doImportFiles = mxUtils.bind(this, function()
  8696. {
  8697. var graph = this.editor.graph;
  8698. var gs = graph.gridSize;
  8699. fn = (fn != null) ? fn : mxUtils.bind(this, function(data, mimeType, x, y, w, h, filename, done, file)
  8700. {
  8701. try
  8702. {
  8703. if (data != null && data.substring(0, 10) == '<mxlibrary')
  8704. {
  8705. this.spinner.stop();
  8706. this.loadLibrary(new LocalLibrary(this, data, filename));
  8707. this.showSidebar();
  8708. return null;
  8709. }
  8710. else
  8711. {
  8712. // Drop on empty file ignores drop location
  8713. if (this.isCompatibleString(data) && files.length == 1 && evt != null &&
  8714. evt.type == 'drop' && this.isBlankFile() && !this.canUndo())
  8715. {
  8716. crop = false;
  8717. x = 0;
  8718. y = 0;
  8719. }
  8720. return this.importFile(data, mimeType, x, y, w, h, filename,
  8721. done, file, crop, ignoreEmbeddedXml, evt);
  8722. }
  8723. }
  8724. catch (e)
  8725. {
  8726. this.handleError(e);
  8727. return null;
  8728. }
  8729. });
  8730. resultFn = (resultFn != null) ? resultFn : mxUtils.bind(this, function(cells)
  8731. {
  8732. graph.setSelectionCells(cells);
  8733. });
  8734. if (this.spinner.spin(document.body, mxResources.get('loading')))
  8735. {
  8736. var count = files.length;
  8737. var remain = count;
  8738. var queue = [];
  8739. // Barrier waits for all files to be loaded asynchronously
  8740. var barrier = mxUtils.bind(this, function(index, fnc)
  8741. {
  8742. queue[index] = fnc;
  8743. if (--remain == 0)
  8744. {
  8745. this.spinner.stop();
  8746. if (barrierFn != null)
  8747. {
  8748. barrierFn(queue);
  8749. }
  8750. else
  8751. {
  8752. var cells = [];
  8753. graph.getModel().beginUpdate();
  8754. try
  8755. {
  8756. for (var j = 0; j < queue.length; j++)
  8757. {
  8758. var tmp = queue[j]();
  8759. if (tmp != null)
  8760. {
  8761. cells = cells.concat(tmp);
  8762. }
  8763. }
  8764. }
  8765. finally
  8766. {
  8767. graph.getModel().endUpdate();
  8768. }
  8769. }
  8770. resultFn(cells);
  8771. }
  8772. });
  8773. for (var i = 0; i < count; i++)
  8774. {
  8775. (mxUtils.bind(this, function(index)
  8776. {
  8777. var file = files[index];
  8778. if (file != null)
  8779. {
  8780. var reader = new FileReader();
  8781. reader.onload = mxUtils.bind(this, function(e)
  8782. {
  8783. if (filterFn == null || filterFn(file))
  8784. {
  8785. try
  8786. {
  8787. if (file.type.substring(0, 6) == 'image/')
  8788. {
  8789. if (file.type.substring(0, 9) == 'image/svg')
  8790. {
  8791. // Checks if SVG contains content attribute
  8792. var data = Graph.clipSvgDataUri(e.target.result);
  8793. var comma = data.indexOf(',');
  8794. var svgText = decodeURIComponent(escape(atob(data.substring(comma + 1))));
  8795. var root = mxUtils.parseXml(svgText);
  8796. var svgs = root.getElementsByTagName('svg');
  8797. if (svgs.length > 0)
  8798. {
  8799. var svgRoot = svgs[0];
  8800. var cont = (ignoreEmbeddedXml) ? null : svgRoot.getAttribute('content');
  8801. if (cont != null && cont.charAt(0) != '<' && cont.charAt(0) != '%')
  8802. {
  8803. cont = unescape((window.atob) ? atob(cont) : Base64.decode(cont, true));
  8804. }
  8805. if (cont != null && cont.charAt(0) == '%')
  8806. {
  8807. cont = decodeURIComponent(cont);
  8808. }
  8809. if (cont != null && (cont.substring(0, 8) === '<mxfile ' ||
  8810. cont.substring(0, 14) === '<mxGraphModel '))
  8811. {
  8812. barrier(index, mxUtils.bind(this, function()
  8813. {
  8814. return fn(cont, 'text/xml', x + index * gs, y + index * gs, 0, 0, file.name);
  8815. }));
  8816. }
  8817. else
  8818. {
  8819. // SVG needs special handling to add viewbox if missing and
  8820. // find initial size from SVG attributes (only for IE11)
  8821. barrier(index, mxUtils.bind(this, function()
  8822. {
  8823. try
  8824. {
  8825. // Parses SVG and find width and height
  8826. if (root != null)
  8827. {
  8828. var svgs = root.getElementsByTagName('svg');
  8829. if (svgs.length > 0)
  8830. {
  8831. var svgRoot = svgs[0];
  8832. var w = svgRoot.getAttribute('width');
  8833. var h = svgRoot.getAttribute('height');
  8834. if (w != null && w.charAt(w.length - 1) != '%')
  8835. {
  8836. w = parseFloat(w);
  8837. }
  8838. else
  8839. {
  8840. w = NaN;
  8841. }
  8842. if (h != null && h.charAt(h.length - 1) != '%')
  8843. {
  8844. h = parseFloat(h);
  8845. }
  8846. else
  8847. {
  8848. h = NaN;
  8849. }
  8850. // Check if viewBox attribute already exists
  8851. var vb = svgRoot.getAttribute('viewBox');
  8852. if (vb == null || vb.length == 0)
  8853. {
  8854. svgRoot.setAttribute('viewBox', '0 0 ' + w + ' ' + h);
  8855. }
  8856. // Uses width and height from viewbox for
  8857. // missing width and height attributes
  8858. else if (isNaN(w) || isNaN(h))
  8859. {
  8860. var tokens = vb.split(' ');
  8861. if (tokens.length > 3)
  8862. {
  8863. w = parseFloat(tokens[2]);
  8864. h = parseFloat(tokens[3]);
  8865. }
  8866. }
  8867. data = Editor.createSvgDataUri(mxUtils.getXml(svgRoot));
  8868. var s = Math.min(1, Math.min(maxSize / Math.max(1, w)), maxSize / Math.max(1, h));
  8869. var cells = fn(data, file.type, x + index * gs, y + index * gs, Math.max(
  8870. 1, Math.round(w * s)), Math.max(1, Math.round(h * s)), file.name);
  8871. // Hack to fix width and height asynchronously
  8872. if (cells != null && (isNaN(w) || isNaN(h)))
  8873. {
  8874. var img = new Image();
  8875. img.onload = mxUtils.bind(this, function()
  8876. {
  8877. w = Math.max(1, img.width);
  8878. h = Math.max(1, img.height);
  8879. cells[0].geometry.width = w;
  8880. cells[0].geometry.height = h;
  8881. svgRoot.setAttribute('viewBox', '0 0 ' + w + ' ' + h);
  8882. data = Editor.createSvgDataUri(mxUtils.getXml(svgRoot));
  8883. var semi = data.indexOf(';');
  8884. if (semi > 0)
  8885. {
  8886. data = data.substring(0, semi) + data.substring(data.indexOf(',', semi + 1));
  8887. }
  8888. graph.setCellStyles('image', data, [cells[0]]);
  8889. });
  8890. img.src = Editor.createSvgDataUri(mxUtils.getXml(svgRoot));
  8891. }
  8892. return cells;
  8893. }
  8894. }
  8895. }
  8896. catch (e)
  8897. {
  8898. // ignores any SVG parsing errors
  8899. }
  8900. return null;
  8901. }));
  8902. }
  8903. }
  8904. else
  8905. {
  8906. barrier(index, mxUtils.bind(this, function()
  8907. {
  8908. return null;
  8909. }));
  8910. }
  8911. }
  8912. else
  8913. {
  8914. // Checks if PNG+XML is available to bypass code below
  8915. var containsModel = false;
  8916. if (file.type == 'image/png')
  8917. {
  8918. var xml = (ignoreEmbeddedXml) ? null : this.extractGraphModelFromPng(e.target.result);
  8919. if (xml != null && xml.length > 0)
  8920. {
  8921. var img = new Image();
  8922. img.src = e.target.result;
  8923. barrier(index, mxUtils.bind(this, function()
  8924. {
  8925. return fn(xml, 'text/xml', x + index * gs, y + index * gs,
  8926. img.width, img.height, file.name);
  8927. }));
  8928. containsModel = true;
  8929. }
  8930. }
  8931. // Additional asynchronous step for finding image size
  8932. if (!containsModel)
  8933. {
  8934. // Cannot load local files in Chrome App
  8935. if (mxClient.IS_CHROMEAPP)
  8936. {
  8937. this.spinner.stop();
  8938. this.showError(mxResources.get('error'), mxResources.get('dragAndDropNotSupported'),
  8939. mxResources.get('cancel'), mxUtils.bind(this, function()
  8940. {
  8941. // Hides the dialog
  8942. }), null, mxResources.get('ok'), mxUtils.bind(this, function()
  8943. {
  8944. // Redirects to import function
  8945. this.actions.get('import').funct();
  8946. })
  8947. );
  8948. }
  8949. else
  8950. {
  8951. this.loadImage(e.target.result, mxUtils.bind(this, function(img)
  8952. {
  8953. this.resizeImage(img, e.target.result, mxUtils.bind(this, function(data2, w2, h2)
  8954. {
  8955. barrier(index, mxUtils.bind(this, function()
  8956. {
  8957. // Refuses to insert images above a certain size as they kill the app
  8958. if (data2 != null && data2.length < maxBytes)
  8959. {
  8960. var s = (!resizeImages || !this.isResampleImageSize(
  8961. file.size, resampleThreshold)) ? 1 :
  8962. Math.min(1, Math.min(maxSize / w2, maxSize / h2));
  8963. return fn(data2, file.type, x + index * gs, y + index * gs,
  8964. Math.round(w2 * s), Math.round(h2 * s), file.name);
  8965. }
  8966. else
  8967. {
  8968. this.handleError({message: mxResources.get('imageTooBig')});
  8969. return null;
  8970. }
  8971. }));
  8972. }), resizeImages, maxSize, resampleThreshold, file.size);
  8973. }), mxUtils.bind(this, function()
  8974. {
  8975. this.handleError({message: mxResources.get('invalidOrMissingFile')});
  8976. }));
  8977. }
  8978. }
  8979. }
  8980. }
  8981. else
  8982. {
  8983. var data = e.target.result;
  8984. fn(data, file.type, x + index * gs, y + index * gs, 240, 160, file.name, function(cells)
  8985. {
  8986. barrier(index, function()
  8987. {
  8988. return cells;
  8989. });
  8990. }, file);
  8991. }
  8992. }
  8993. catch (e)
  8994. {
  8995. // Ignores file parsing error
  8996. barrier(index, mxUtils.bind(this, function()
  8997. {
  8998. return null;
  8999. }));
  9000. if (window.console != null)
  9001. {
  9002. console.error(e, file);
  9003. }
  9004. }
  9005. }
  9006. else
  9007. {
  9008. // Ignores file and decrements counter
  9009. barrier(index, mxUtils.bind(this, function()
  9010. {
  9011. return null;
  9012. }));
  9013. }
  9014. });
  9015. // Handles special cases
  9016. if (EditorUi.isVisioFilename(file.name))
  9017. {
  9018. fn(null, file.type, x + index * gs, y + index * gs, 240, 160, file.name, function(cells)
  9019. {
  9020. barrier(index, function()
  9021. {
  9022. return cells;
  9023. });
  9024. }, file);
  9025. }
  9026. else if (file.type.substring(0, 5) == 'image' || file.type == 'application/pdf')
  9027. {
  9028. reader.readAsDataURL(file);
  9029. }
  9030. else
  9031. {
  9032. reader.readAsText(file);
  9033. }
  9034. }
  9035. }))(i);
  9036. }
  9037. }
  9038. });
  9039. if (largeImages)
  9040. {
  9041. // Workaround for lost files array in async code
  9042. var tmp = [];
  9043. for (var i = 0; i < files.length; i++)
  9044. {
  9045. tmp.push(files[i]);
  9046. }
  9047. files = tmp;
  9048. this.confirmImageResize(function(doResize)
  9049. {
  9050. resizeImages = doResize;
  9051. doImportFiles();
  9052. }, resizeDialog);
  9053. }
  9054. else
  9055. {
  9056. doImportFiles();
  9057. }
  9058. };
  9059. /**
  9060. * Returns true if the current file is a blank diagram.
  9061. */
  9062. EditorUi.prototype.isBlankFile = function()
  9063. {
  9064. return this.pages != null && this.pages.length == 1 &&
  9065. this.isDiagramEmpty() && this.currentPage.getName() ==
  9066. mxResources.get('pageWithNumber', [1]);
  9067. };
  9068. /**
  9069. * Parses the file using XHR2 via the server. File can be a blob or file object.
  9070. * Filename is an optional parameter for blobs (that do not have a filename).
  9071. */
  9072. EditorUi.prototype.confirmImageResize = function(fn, force)
  9073. {
  9074. force = (force != null) ? force : false;
  9075. var resume = (this.spinner != null && this.spinner.pause != null) ? this.spinner.pause() : function() {};
  9076. var resizeImages = (isLocalStorage || mxClient.IS_CHROMEAPP) ? mxSettings.getResizeImages() : null;
  9077. var wrapper = function(remember, resize)
  9078. {
  9079. if (remember || force)
  9080. {
  9081. mxSettings.setResizeImages((remember) ? resize : null);
  9082. mxSettings.save();
  9083. }
  9084. resume();
  9085. fn(resize);
  9086. };
  9087. if (resizeImages != null && !force)
  9088. {
  9089. wrapper(false, resizeImages);
  9090. }
  9091. else
  9092. {
  9093. this.showDialog(new ConfirmDialog(this, mxResources.get('resizeLargeImages'),
  9094. function(remember)
  9095. {
  9096. wrapper(remember, true);
  9097. },
  9098. function(remember)
  9099. {
  9100. wrapper(remember, false);
  9101. }, mxResources.get('resize'), mxResources.get('actualSize'),
  9102. '<img style="margin-top:8px;" src="' + Editor.loResImage + '"/>',
  9103. '<img style="margin-top:8px;" src="' + Editor.hiResImage + '"/>',
  9104. isLocalStorage || mxClient.IS_CHROMEAPP).container, 340,
  9105. (isLocalStorage || mxClient.IS_CHROMEAPP) ? 226 : 200, true, true);
  9106. }
  9107. };
  9108. /**
  9109. * Parses the file using XHR2 via the server. File can be a blob or file object.
  9110. * Filename is an optional parameter for blobs (that do not have a filename).
  9111. */
  9112. EditorUi.prototype.parseFile = function(file, fn, filename)
  9113. {
  9114. filename = (filename != null) ? filename : file.name;
  9115. var reader = new FileReader();
  9116. reader.onload = mxUtils.bind(this, function()
  9117. {
  9118. this.parseFileData(reader.result, fn, filename)
  9119. });
  9120. reader.readAsText(file);
  9121. };
  9122. //TODO Use this version of the function instead of creating a Blob then read it again
  9123. EditorUi.prototype.parseFileData = function(data, fn, filename)
  9124. {
  9125. var xhr = new XMLHttpRequest();
  9126. xhr.open('POST', OPEN_URL);
  9127. xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
  9128. xhr.onreadystatechange = function()
  9129. {
  9130. fn(xhr);
  9131. };
  9132. xhr.send('format=xml&filename=' + encodeURIComponent(filename) + '&data=' + encodeURIComponent(data));
  9133. try
  9134. {
  9135. EditorUi.logEvent({category: 'GLIFFY-IMPORT-FILE',
  9136. action: 'size_' + file.size});
  9137. }
  9138. catch (e)
  9139. {
  9140. // ignore
  9141. }
  9142. };
  9143. /**
  9144. *
  9145. */
  9146. EditorUi.prototype.isResampleImageSize = function(size, thresh)
  9147. {
  9148. thresh = (thresh != null) ? thresh : this.resampleThreshold;
  9149. return size > thresh;
  9150. };
  9151. /**
  9152. * Resizes the given image if <maxImageBytes> is not null.
  9153. */
  9154. EditorUi.prototype.resizeImage = function(img, data, fn, enabled, maxSize, thresh, fileSize)
  9155. {
  9156. maxSize = (maxSize != null) ? maxSize : this.maxImageSize;
  9157. var w = Math.max(1, img.width);
  9158. var h = Math.max(1, img.height);
  9159. var originalData = data;
  9160. if (enabled && this.isResampleImageSize((fileSize != null) ? fileSize : data.length, thresh))
  9161. {
  9162. try
  9163. {
  9164. var factor = Math.max(w / maxSize, h / maxSize);
  9165. if (factor > 1)
  9166. {
  9167. var w2 = Math.round(w / factor);
  9168. var h2 = Math.round(h / factor);
  9169. var canvas = document.createElement('canvas');
  9170. canvas.width = w2;
  9171. canvas.height = h2;
  9172. var ctx = canvas.getContext('2d');
  9173. ctx.drawImage(img, 0, 0, w2, h2);
  9174. var tmp = canvas.toDataURL();
  9175. // Uses new image if smaller
  9176. if (tmp.length < data.length)
  9177. {
  9178. // Checks if the image is empty by comparing
  9179. // with an empty image of the same size
  9180. var canvas2 = document.createElement('canvas');
  9181. canvas2.width = w2;
  9182. canvas2.height = h2;
  9183. var tmp2 = canvas2.toDataURL();
  9184. if (tmp !== tmp2)
  9185. {
  9186. data = tmp;
  9187. w = w2;
  9188. h = h2;
  9189. }
  9190. }
  9191. }
  9192. }
  9193. catch (e)
  9194. {
  9195. // ignores image scaling errors
  9196. }
  9197. }
  9198. if (enabled && originalData != data && maxSize > this.maxImageSize / 2 && data.length > this.maxImageBytes)
  9199. {
  9200. this.resizeImage(img, data, fn, enabled, maxSize / 1.5, thresh, fileSize);
  9201. }
  9202. else
  9203. {
  9204. fn(data, w, h);
  9205. }
  9206. };
  9207. /**
  9208. * Extracts the XML from the compressed or non-compressed text chunk.
  9209. */
  9210. EditorUi.prototype.extractGraphModelFromPng = function(data)
  9211. {
  9212. return Editor.extractGraphModelFromPng(data);
  9213. };
  9214. /**
  9215. * Loads the image from the given URI.
  9216. *
  9217. * @param {number} dx X-coordinate of the translation.
  9218. * @param {number} dy Y-coordinate of the translation.
  9219. */
  9220. EditorUi.prototype.loadImage = function(uri, onload, onerror)
  9221. {
  9222. try
  9223. {
  9224. var img = new Image();
  9225. img.onload = function()
  9226. {
  9227. img.width = (img.width > 0) ? img.width : 120;
  9228. img.height = (img.height > 0) ? img.height : 120;
  9229. onload(img);
  9230. };
  9231. if (onerror != null)
  9232. {
  9233. img.onerror = onerror;
  9234. };
  9235. img.src = uri;
  9236. }
  9237. catch (e)
  9238. {
  9239. if (onerror != null)
  9240. {
  9241. onerror(e);
  9242. }
  9243. else
  9244. {
  9245. throw e;
  9246. }
  9247. }
  9248. };
  9249. /**
  9250. * Returns the default value for sketch mode.
  9251. */
  9252. EditorUi.prototype.getDefaultSketchMode = function()
  9253. {
  9254. var defaultValue = urlParams['sketch'] == '1';
  9255. var roughParam = (urlParams['rough'] != null) ? urlParams['rough'] : defaultValue;
  9256. return roughParam != '0';
  9257. };
  9258. /**
  9259. * Overridden to set sketch mode before UI is created.
  9260. */
  9261. var editorUiCreateUi = EditorUi.prototype.createUi;
  9262. EditorUi.prototype.createUi = function()
  9263. {
  9264. if (Editor.isSettingsEnabled())
  9265. {
  9266. if (mxSettings.settings.sidebarTitles != null)
  9267. {
  9268. Sidebar.prototype.sidebarTitles = mxSettings.settings.sidebarTitles;
  9269. }
  9270. this.formatWidth = mxSettings.getFormatWidth();
  9271. if ((Editor.config == null || Editor.config.enableCssDarkMode == null) &&
  9272. mxSettings.settings.enableCssDarkMode != null)
  9273. {
  9274. Editor.enableCssDarkMode = mxSettings.settings.enableCssDarkMode;
  9275. }
  9276. }
  9277. editorUiCreateUi.apply(this, arguments);
  9278. if (Editor.isSettingsEnabled())
  9279. {
  9280. this.doSetSketchMode((mxSettings.settings.sketchMode != null && urlParams['rough'] == null &&
  9281. urlParams['sketch'] == null) ? mxSettings.settings.sketchMode : this.getDefaultSketchMode());
  9282. }
  9283. };
  9284. /**
  9285. * Initializes the UI.
  9286. */
  9287. var editorUiInit = EditorUi.prototype.init;
  9288. EditorUi.prototype.init = function()
  9289. {
  9290. mxStencilRegistry.allowEval = mxStencilRegistry.allowEval && !this.isOfflineApp();
  9291. var ui = this;
  9292. var graph = this.editor.graph;
  9293. var graphIsEnabled = graph.isEnabled;
  9294. graph.isEnabled = function()
  9295. {
  9296. return graphIsEnabled.apply(this, arguments) && !ui.isLocked();
  9297. };
  9298. // Stops panning while freehand is active
  9299. if (Graph.touchStyle)
  9300. {
  9301. graph.panningHandler.isPanningTrigger = function(me)
  9302. {
  9303. var evt = me.getEvent();
  9304. return (me.getState() == null && (!mxEvent.isMouseEvent(evt) &&
  9305. !graph.freehand.isDrawing())) ||
  9306. (mxEvent.isPopupTrigger(evt) && (me.getState() == null ||
  9307. mxEvent.isControlDown(evt) || mxEvent.isShiftDown(evt)));
  9308. };
  9309. }
  9310. // Starts editing PlantUML data
  9311. graph.cellEditor.editPlantUmlData = function(cell, trigger, data)
  9312. {
  9313. var obj = JSON.parse(data);
  9314. var dlg = new TextareaDialog(ui, mxResources.get('plantUml') + ':',
  9315. obj.data, function(text)
  9316. {
  9317. if (text != null)
  9318. {
  9319. if (ui.spinner.spin(document.body, mxResources.get('inserting')))
  9320. {
  9321. ui.generatePlantUmlImage(text, obj.format, function(data, w, h)
  9322. {
  9323. ui.spinner.stop();
  9324. graph.getModel().beginUpdate();
  9325. try
  9326. {
  9327. if (obj.format == 'txt')
  9328. {
  9329. graph.labelChanged(cell, '<pre>' + data + '</pre>');
  9330. graph.updateCellSize(cell, true);
  9331. }
  9332. else
  9333. {
  9334. graph.setCellStyles('image', ui.convertDataUri(data), [cell]);
  9335. var geo = graph.model.getGeometry(cell);
  9336. if (geo != null)
  9337. {
  9338. geo = geo.clone();
  9339. geo.width = w;
  9340. geo.height = h;
  9341. graph.cellsResized([cell], [geo], false);
  9342. }
  9343. }
  9344. graph.setAttributeForCell(cell, 'plantUmlData',
  9345. JSON.stringify({data: text, format: obj.format}));
  9346. }
  9347. finally
  9348. {
  9349. graph.getModel().endUpdate();
  9350. }
  9351. }, function(e)
  9352. {
  9353. ui.handleError(e);
  9354. });
  9355. }
  9356. }
  9357. }, null, null, 400, 220);
  9358. ui.showDialog(dlg.container, 420, 300, true, true);
  9359. dlg.init();
  9360. };
  9361. // Starts editing Mermaid data
  9362. graph.cellEditor.editMermaidData = function(cell, trigger, data)
  9363. {
  9364. var obj = JSON.parse(data);
  9365. var dlg = new TextareaDialog(ui, mxResources.get('mermaid') + ':',
  9366. obj.data, function(text)
  9367. {
  9368. if (text != null)
  9369. {
  9370. if (ui.spinner.spin(document.body, mxResources.get('inserting')))
  9371. {
  9372. ui.generateMermaidImage(text, obj.config, function(data, w, h)
  9373. {
  9374. ui.spinner.stop();
  9375. graph.getModel().beginUpdate();
  9376. try
  9377. {
  9378. graph.setCellStyles('image', data, [cell]);
  9379. var geo = graph.model.getGeometry(cell);
  9380. if (geo != null)
  9381. {
  9382. geo = geo.clone();
  9383. geo.width = Math.max(geo.width, w);
  9384. geo.height = Math.max(geo.height, h);
  9385. graph.cellsResized([cell], [geo], false);
  9386. }
  9387. graph.setAttributeForCell(cell, 'mermaidData',
  9388. JSON.stringify({data: text, config:
  9389. obj.config}, null, 2));
  9390. }
  9391. finally
  9392. {
  9393. graph.getModel().endUpdate();
  9394. }
  9395. }, function(e)
  9396. {
  9397. ui.handleError(e);
  9398. });
  9399. }
  9400. }
  9401. }, null, null, 400, 220);
  9402. ui.showDialog(dlg.container, 420, 300, true, true);
  9403. dlg.init();
  9404. };
  9405. // Overrides function to add editing for Plant UML.
  9406. var cellEditorStartEditing = graph.cellEditor.startEditing;
  9407. graph.cellEditor.startEditing = function(cell, trigger)
  9408. {
  9409. try
  9410. {
  9411. var data = this.graph.getAttributeForCell(cell, 'plantUmlData');
  9412. if (data != null)
  9413. {
  9414. this.editPlantUmlData(cell, trigger, data);
  9415. }
  9416. else
  9417. {
  9418. data = this.graph.getAttributeForCell(cell, 'mermaidData');
  9419. if (data != null && window.isMermaidEnabled)
  9420. {
  9421. this.editMermaidData(cell, trigger, data);
  9422. }
  9423. else
  9424. {
  9425. var style = graph.getCellStyle(cell);
  9426. if (mxUtils.getValue(style, 'metaEdit', '0') == '1')
  9427. {
  9428. ui.showDataDialog(cell);
  9429. }
  9430. else
  9431. {
  9432. cellEditorStartEditing.apply(this, arguments);
  9433. }
  9434. }
  9435. }
  9436. }
  9437. catch (e)
  9438. {
  9439. ui.handleError(e);
  9440. }
  9441. };
  9442. // Redirects custom link title via UI for page links
  9443. graph.getLinkTitle = function(href)
  9444. {
  9445. return ui.getLinkTitle(href);
  9446. };
  9447. // Redirects custom link via UI for page link handling
  9448. graph.customLinkClicked = function(link, associatedCell)
  9449. {
  9450. var done = false;
  9451. try
  9452. {
  9453. var bounds = this.getGraphBounds();
  9454. ui.handleCustomLink(link, associatedCell);
  9455. done = true;
  9456. if (ui.chromelessResize && !bounds.equals(this.getGraphBounds()))
  9457. {
  9458. ui.chromelessResize();
  9459. this.scrollCellToVisible(associatedCell);
  9460. }
  9461. }
  9462. catch (e)
  9463. {
  9464. ui.handleError(e);
  9465. }
  9466. return done;
  9467. };
  9468. // Parses background page references
  9469. var graphParseBackgroundImage = graph.parseBackgroundImage;
  9470. graph.parseBackgroundImage = function(json)
  9471. {
  9472. var result = graphParseBackgroundImage.apply(this, arguments);
  9473. if (result != null && result.src != null && Graph.isPageLink(result.src))
  9474. {
  9475. result = {originalSrc: result.src};
  9476. }
  9477. return result;
  9478. };
  9479. // Updates background page SVG
  9480. var graphSetBackgroundImage = graph.setBackgroundImage;
  9481. graph.setBackgroundImage = function(img)
  9482. {
  9483. if (img != null && img.originalSrc != null)
  9484. {
  9485. img = ui.createImageForPageLink(img.originalSrc, ui.currentPage, this);
  9486. }
  9487. graphSetBackgroundImage.apply(this, arguments);
  9488. };
  9489. // Updates background to update placeholders for page title
  9490. this.editor.addListener('pageRenamed', mxUtils.bind(this, function()
  9491. {
  9492. graph.refreshBackgroundImage();
  9493. }));
  9494. // Updates background to update placeholders for page number
  9495. this.editor.addListener('pageMoved', mxUtils.bind(this, function()
  9496. {
  9497. graph.refreshBackgroundImage();
  9498. }));
  9499. // Updates background image after remote changes to the referenced page
  9500. this.editor.addListener('pagesPatched', mxUtils.bind(this, function(sender, evt)
  9501. {
  9502. var ref = (graph.backgroundImage != null) ? graph.backgroundImage.originalSrc : null;
  9503. if (ref != null)
  9504. {
  9505. var comma = ref.indexOf(',');
  9506. if (comma > 0)
  9507. {
  9508. var id = ref.substring(comma + 1);
  9509. var patches = evt.getProperty('patches');
  9510. for (var i = 0; i < patches.length; i++)
  9511. {
  9512. if ((patches[i][EditorUi.DIFF_UPDATE] != null &&
  9513. patches[i][EditorUi.DIFF_UPDATE][id] != null) ||
  9514. (patches[i][EditorUi.DIFF_REMOVE] != null &&
  9515. mxUtils.indexOf(patches[i][EditorUi.DIFF_REMOVE], id) >= 0))
  9516. {
  9517. graph.refreshBackgroundImage();
  9518. break;
  9519. }
  9520. }
  9521. }
  9522. }
  9523. }));
  9524. // Restores background page reference in output data or
  9525. // replaces dark mode page image with normal mode image
  9526. var graphGetBackgroundImageObject = graph.getBackgroundImageObject;
  9527. graph.getBackgroundImageObject = function(obj, resolveReferences)
  9528. {
  9529. var result = graphGetBackgroundImageObject.apply(this, arguments);
  9530. if (result != null && result.originalSrc != null)
  9531. {
  9532. if (!resolveReferences)
  9533. {
  9534. result = {src: result.originalSrc};
  9535. }
  9536. else
  9537. {
  9538. var temp = this.stylesheet;
  9539. var tempFg = this.shapeForegroundColor;
  9540. var tempBg = this.shapeBackgroundColor;
  9541. if (this.themes != null && this.defaultThemeName == 'darkTheme')
  9542. {
  9543. this.stylesheet = this.getDefaultStylesheet();
  9544. this.shapeBackgroundColor = '#ffffff';
  9545. this.shapeForegroundColor = '#000000';
  9546. }
  9547. result = ui.createImageForPageLink(result.originalSrc, null, null, true);
  9548. this.shapeBackgroundColor = tempBg;
  9549. this.shapeForegroundColor = tempFg;
  9550. this.stylesheet = temp;
  9551. }
  9552. }
  9553. return result;
  9554. };
  9555. // Sets help link for placeholders
  9556. if (!this.isOffline() && typeof window.EditDataDialog !== 'undefined')
  9557. {
  9558. EditDataDialog.placeholderHelpLink = 'https://www.drawio.com/doc/faq/predefined-placeholders';
  9559. }
  9560. if (/viewer\.diagrams\.net$/.test(window.location.hostname) ||
  9561. /embed\.diagrams\.net$/.test(window.location.hostname))
  9562. {
  9563. this.editor.editBlankUrl = 'https://app.diagrams.net/';
  9564. }
  9565. // Passes dev mode to new window
  9566. var editorGetEditBlankUrl = ui.editor.getEditBlankUrl;
  9567. this.editor.getEditBlankUrl = function(params)
  9568. {
  9569. params = (params != null) ? params : '';
  9570. if (urlParams['dev'] == '1')
  9571. {
  9572. params += ((params.length > 0) ? '&' : '?') + 'dev=1';
  9573. }
  9574. return editorGetEditBlankUrl.apply(this, arguments);
  9575. };
  9576. // For chromeless mode and lightbox mode in viewer
  9577. // Must be overridden before supercall to be applied
  9578. // in case of chromeless initialization
  9579. var graphAddClickHandler = graph.addClickHandler;
  9580. graph.addClickHandler = function(highlight, beforeClick, onClick)
  9581. {
  9582. var tmp = beforeClick;
  9583. beforeClick = function(evt, href, cell)
  9584. {
  9585. if (href == null)
  9586. {
  9587. var source = mxEvent.getSource(evt);
  9588. if (source.nodeName.toLowerCase() == 'a')
  9589. {
  9590. href = source.getAttribute('href');
  9591. }
  9592. }
  9593. if (href != null && graph.isCustomLink(href) &&
  9594. (mxEvent.isTouchEvent(evt) ||
  9595. !mxEvent.isPopupTrigger(evt)) &&
  9596. graph.customLinkClicked(href, cell))
  9597. {
  9598. mxEvent.consume(evt);
  9599. }
  9600. if (tmp != null)
  9601. {
  9602. tmp(evt, href, cell);
  9603. }
  9604. };
  9605. // For some reason, local argument override is not enough in this case...
  9606. graphAddClickHandler.call(this, highlight, beforeClick, onClick);
  9607. };
  9608. editorUiInit.apply(this, arguments);
  9609. if (mxClient.IS_SVG)
  9610. {
  9611. // LATER: Add shadow for labels in graph.container (eg. math, NO_FO), scaling
  9612. this.editor.graph.addSvgShadow(graph.view.canvas.ownerSVGElement, null, true);
  9613. }
  9614. if (this.menus != null)
  9615. {
  9616. var menusAddPopupMenuItems = Menus.prototype.addPopupMenuItems;
  9617. // Inserts zoomIn/zoomOut into popup menu
  9618. this.menus.addPopupMenuItems = function(menu, cell, evt)
  9619. {
  9620. if (graph.isSelectionEmpty() && Editor.currentTheme == 'simple')
  9621. {
  9622. this.addMenuItems(menu, ['zoomIn', 'zoomOut', '-'], null, evt);
  9623. }
  9624. menusAddPopupMenuItems.apply(this, arguments);
  9625. // Shows add to scratchpad option
  9626. if (!graph.isSelectionEmpty() &&
  9627. ui.addSelectionToScratchpad != null)
  9628. {
  9629. this.addMenuItems(menu, ['-', 'addToScratchpad'], null, evt);
  9630. }
  9631. if (graph.isSelectionEmpty() && Editor.currentTheme == 'simple')
  9632. {
  9633. this.addMenuItems(menu, ['-', 'exitGroup', 'home'], null, evt);
  9634. }
  9635. };
  9636. var menusAddPopupMenuEditItems = Menus.prototype.addPopupMenuEditItems;
  9637. // Inserts copyAsImage into popup menu
  9638. this.menus.addPopupMenuEditItems = function(menu, cell, evt)
  9639. {
  9640. if (ui.editor.graph.isSelectionEmpty())
  9641. {
  9642. menusAddPopupMenuEditItems.apply(this, arguments);
  9643. ui.menus.addMenuItems(menu, ['copyAsImage'], null, evt);
  9644. }
  9645. else
  9646. {
  9647. if (this.isShowCellEditItems())
  9648. {
  9649. this.addPopupDeleteItem(menu, cell, evt);
  9650. }
  9651. else
  9652. {
  9653. this.addPopupMenuArrangeItems(menu, cell, evt);
  9654. }
  9655. this.addMenuItems(menu, ['-', 'cut', 'copy', 'copyAsImage',
  9656. 'duplicate', '-'], null, evt);
  9657. if (!this.isShowCellEditItems())
  9658. {
  9659. this.addPopupDeleteItem(menu, cell, evt);
  9660. }
  9661. this.addMenuItems(menu, ['lockUnlock', '-'], null, evt);
  9662. if (!this.isShowStyleItems())
  9663. {
  9664. if (graph.getSelectionCount() == 1 && !graph.isCellLocked(cell) &&
  9665. graph.isCellEditable(cell))
  9666. {
  9667. this.addSubmenu('editCell', menu, null, mxResources.get('edit'));
  9668. menu.addSeparator();
  9669. // Shows line submenu for edges
  9670. if (graph.getModel().isEdge(cell))
  9671. {
  9672. this.addSubmenu('line', menu);
  9673. var geo = graph.getModel().getGeometry(cell);
  9674. if (geo != null && geo.points != null && geo.points.length > 0)
  9675. {
  9676. this.addMenuItems(menu, ['clearWaypoints'], null, evt);
  9677. }
  9678. }
  9679. }
  9680. if (graph.getSelectionCount() == 1)
  9681. {
  9682. this.addMenuItems(menu, ['enterGroup'], null, evt);
  9683. }
  9684. // Shows table cell options
  9685. var ss = ui.getSelectionState();
  9686. if (ss.mergeCell != null)
  9687. {
  9688. var item = this.addMenuItem(menu, 'mergeCells');
  9689. }
  9690. else if (ss.style['colspan'] > 1 || ss.style['rowspan'] > 1)
  9691. {
  9692. var item = this.addMenuItem(menu, 'unmergeCells');
  9693. }
  9694. }
  9695. }
  9696. };
  9697. this.menus.isShowStyleItems = function()
  9698. {
  9699. return Editor.currentTheme != 'simple' &&
  9700. Editor.currentTheme != 'sketch' &&
  9701. Editor.currentTheme != 'min';
  9702. };
  9703. this.menus.isShowHistoryItems = function()
  9704. {
  9705. return Editor.currentTheme != 'simple';
  9706. };
  9707. this.menus.isShowArrangeItems = this.menus.isShowStyleItems;
  9708. this.menus.isShowCellEditItems = this.menus.isShowStyleItems;
  9709. }
  9710. // Specifies the default filename
  9711. this.defaultFilename = mxResources.get('untitledDiagram');
  9712. // Adds export for %page%, %pagenumber% and %pagecount% placeholders
  9713. var graphGetExportVariables = graph.getExportVariables;
  9714. graph.getExportVariables = function()
  9715. {
  9716. var vars = graphGetExportVariables.apply(this, arguments);
  9717. var file = ui.getCurrentFile();
  9718. if (file != null)
  9719. {
  9720. vars['filename'] = file.getTitle();
  9721. }
  9722. vars['pagecount'] = (ui.pages != null) ? ui.pages.length : 1;
  9723. vars['page'] = (ui.currentPage != null) ? ui.currentPage.getName() : '';
  9724. vars['pagenumber'] = (ui.pages != null && ui.currentPage != null) ?
  9725. mxUtils.indexOf(ui.pages, ui.currentPage) + 1 : 1;
  9726. return vars;
  9727. };
  9728. // Adds %page%, %pagenumber% and %pagecount% placeholders
  9729. var graphGetGlobalVariable = graph.getGlobalVariable;
  9730. graph.getGlobalVariable = function(name)
  9731. {
  9732. var file = ui.getCurrentFile();
  9733. if (name == 'filename' && file != null)
  9734. {
  9735. return file.getTitle();
  9736. }
  9737. else if (name == 'page' && ui.currentPage != null)
  9738. {
  9739. return ui.currentPage.getName();
  9740. }
  9741. else if (name == 'pagenumber')
  9742. {
  9743. if (ui.currentPage != null && ui.pages != null)
  9744. {
  9745. return mxUtils.indexOf(ui.pages, ui.currentPage) + 1;
  9746. }
  9747. else
  9748. {
  9749. return 1;
  9750. }
  9751. }
  9752. else if (name == 'pagecount')
  9753. {
  9754. return (ui.pages != null) ? ui.pages.length : 1;
  9755. }
  9756. return graphGetGlobalVariable.apply(this, arguments);
  9757. };
  9758. // Forces update of filename placeholder
  9759. var lastFilename = null;
  9760. var lastFile = null;
  9761. this.addListener('fileDescriptorChanged', mxUtils.bind(this, function()
  9762. {
  9763. var file = this.getCurrentFile();
  9764. var filename = (file != null && file.getTitle() != null) ?
  9765. file.getTitle() : this.defaultFilename;
  9766. if (lastFilename != filename && file == lastFile)
  9767. {
  9768. graph.invalidateDescendantsWithPlaceholders(
  9769. graph.model.getRoot());
  9770. graph.view.validate();
  9771. }
  9772. lastFilename = filename;
  9773. lastFile = file;
  9774. }));
  9775. var graphLabelLinkClicked = graph.labelLinkClicked;
  9776. graph.labelLinkClicked = function(state, elt, evt)
  9777. {
  9778. var href = elt.getAttribute('href');
  9779. if (href != null && graph.isCustomLink(href) &&
  9780. (mxEvent.isTouchEvent(evt) ||
  9781. !mxEvent.isPopupTrigger(evt)))
  9782. {
  9783. // Active links are moved to the hint
  9784. if (!graph.isEnabled() || (state != null && graph.isCellLocked(state.cell)))
  9785. {
  9786. graph.customLinkClicked(href);
  9787. // Resets rubberband after click on locked cell
  9788. graph.getRubberband().reset();
  9789. }
  9790. mxEvent.consume(evt);
  9791. }
  9792. else
  9793. {
  9794. graphLabelLinkClicked.apply(this, arguments);
  9795. }
  9796. };
  9797. // Overrides editor filename
  9798. this.editor.getOrCreateFilename = function()
  9799. {
  9800. var filename = ui.defaultFilename;
  9801. var file = ui.getCurrentFile();
  9802. if (file != null)
  9803. {
  9804. filename = (file.getTitle() != null) ? file.getTitle() : filename;
  9805. }
  9806. return filename;
  9807. };
  9808. // Disables print action for standalone apps on iOS
  9809. // because there is no way to close the new window
  9810. // LATER: Use iframe for print, disable preview
  9811. var printAction = this.actions.get('print');
  9812. printAction.setEnabled(!mxClient.IS_IOS || !navigator.standalone);
  9813. printAction.visible = printAction.isEnabled();
  9814. // Installs additional keyboard shortcuts for editor
  9815. if (!this.editor.chromeless || this.editor.editable)
  9816. {
  9817. // Defines additional hotkeys
  9818. this.keyHandler.bindAction(70, true, 'findReplace'); // Ctrl+F
  9819. this.keyHandler.bindAction(77, true, 'editGeometry', true); // Ctrl+Shift+M
  9820. this.keyHandler.bindAction(75, true, 'tags'); // Ctrl+K
  9821. this.keyHandler.bindAction(65, false, 'insertText'); // A
  9822. this.keyHandler.bindAction(83, false, 'insertNote'); // S
  9823. this.keyHandler.bindAction(68, false, 'insertRectangle'); // D
  9824. this.keyHandler.bindAction(70, false, 'insertEllipse'); // F
  9825. this.keyHandler.bindAction(76, false, 'insertLink'); // L
  9826. this.keyHandler.bindAction(82, false, 'insertRhombus'); // R
  9827. this.keyHandler.bindAction(67, false, 'insertEdge'); // C
  9828. this.keyHandler.bindAction(88, false, 'insertFreehand'); // X
  9829. this.keyHandler.bindAction(75, true, 'toggleShapes', true); // Ctrl+Shift+K
  9830. this.keyHandler.bindAction(54, true, 'convertDarkModeColors', true); // Ctrl+Shift+6
  9831. this.altShiftActions[81] = 'copyStyle'; // Alt+Shift+Q
  9832. this.altShiftActions[87] = 'pasteStyle'; // Alt+Shift+W
  9833. this.altShiftActions[83] = 'synchronize'; // Alt+Shift+S
  9834. if (urlParams['embedInline'] == '1')
  9835. {
  9836. graph.addListener(mxEvent.ESCAPE, function(sender, evt)
  9837. {
  9838. if (evt != null && graph.isEnabled() && !graph.isEditing() &&
  9839. evt.getProperty('event') != null)
  9840. {
  9841. ui.actions.get('exit').funct();
  9842. }
  9843. });
  9844. }
  9845. this.installImagePasteHandler();
  9846. this.installNativeClipboardHandler();
  9847. };
  9848. // Updates realtime state icon
  9849. this.addListener('realtimeStateChanged', mxUtils.bind(this, function()
  9850. {
  9851. this.updateUserElement();
  9852. }));
  9853. // Creates the spinner
  9854. this.spinner = this.createSpinner(null, null, 24);
  9855. // Installs drag and drop handler for rich text editor
  9856. if (Graph.fileSupport)
  9857. {
  9858. graph.addListener(mxEvent.EDITING_STARTED, mxUtils.bind(this, function(evt)
  9859. {
  9860. // Setup the dnd listeners
  9861. var textElt = graph.cellEditor.text2;
  9862. var dropElt = null;
  9863. if (textElt != null)
  9864. {
  9865. mxEvent.addListener(textElt, 'dragleave', function(evt)
  9866. {
  9867. if (dropElt != null)
  9868. {
  9869. dropElt.parentNode.removeChild(dropElt);
  9870. dropElt = null;
  9871. }
  9872. evt.stopPropagation();
  9873. evt.preventDefault();
  9874. });
  9875. mxEvent.addListener(textElt, 'dragover', mxUtils.bind(this, function(evt)
  9876. {
  9877. // IE 10 does not implement pointer-events so it can't have a drop highlight
  9878. if (dropElt == null && (!mxClient.IS_IE || document.documentMode > 10))
  9879. {
  9880. dropElt = this.highlightElement(textElt);
  9881. }
  9882. evt.stopPropagation();
  9883. evt.preventDefault();
  9884. }));
  9885. mxEvent.addListener(textElt, 'drop', mxUtils.bind(this, function(evt)
  9886. {
  9887. if (dropElt != null)
  9888. {
  9889. dropElt.parentNode.removeChild(dropElt);
  9890. dropElt = null;
  9891. }
  9892. if (evt.dataTransfer.files.length > 0)
  9893. {
  9894. this.importFiles(evt.dataTransfer.files, 0, 0, this.maxImageSize, function(data, mimeType, x, y, w, h)
  9895. {
  9896. // Inserts image into current text box
  9897. graph.insertImage(data, w, h);
  9898. }, function()
  9899. {
  9900. // No post processing
  9901. }, function(file)
  9902. {
  9903. // Handles only images
  9904. return file.type.substring(0, 6) == 'image/';
  9905. }, function(queue)
  9906. {
  9907. // Invokes elements of queue in order
  9908. for (var i = 0; i < queue.length; i++)
  9909. {
  9910. queue[i]();
  9911. }
  9912. }, mxEvent.isControlDown(evt));
  9913. }
  9914. else if (mxUtils.indexOf(evt.dataTransfer.types, 'text/uri-list') >= 0)
  9915. {
  9916. var uri = evt.dataTransfer.getData('text/uri-list');
  9917. if ((/\.(gif|jpg|jpeg|tiff|png|svg)$/i).test(uri))
  9918. {
  9919. this.loadImage(decodeURIComponent(uri), mxUtils.bind(this, function(img)
  9920. {
  9921. var w = Math.max(1, img.width);
  9922. var h = Math.max(1, img.height);
  9923. var maxSize = this.maxImageSize;
  9924. var s = Math.min(1, Math.min(maxSize / Math.max(1, w)), maxSize / Math.max(1, h));
  9925. graph.insertImage(decodeURIComponent(uri), w * s, h * s);
  9926. }));
  9927. }
  9928. else
  9929. {
  9930. document.execCommand('insertHTML', false, evt.dataTransfer.getData('text/plain'));
  9931. }
  9932. }
  9933. else
  9934. {
  9935. if (mxUtils.indexOf(evt.dataTransfer.types, 'text/html') >= 0)
  9936. {
  9937. document.execCommand('insertHTML', false, evt.dataTransfer.getData('text/html'));
  9938. }
  9939. else if (mxUtils.indexOf(evt.dataTransfer.types, 'text/plain') >= 0)
  9940. {
  9941. document.execCommand('insertHTML', false, evt.dataTransfer.getData('text/plain'));
  9942. }
  9943. }
  9944. evt.stopPropagation();
  9945. evt.preventDefault();
  9946. }));
  9947. }
  9948. }));
  9949. }
  9950. // Adding mxRuler to editor
  9951. if (Editor.isSettingsEnabled())
  9952. {
  9953. var view = this.editor.graph.view;
  9954. var unit = mxSettings.getUnit();
  9955. view.setUnit(unit);
  9956. // Updates page size unit (using mm instead of m)
  9957. Editor.pageSizeUnit = (unit == mxConstants.METERS) ?
  9958. mxConstants.MILLIMETERS : unit;
  9959. view.addListener('unitChanged', function(sender, evt)
  9960. {
  9961. var unit = evt.getProperty('unit');
  9962. mxSettings.setUnit(unit);
  9963. mxSettings.save();
  9964. // Updates page size unit (using mm instead of m)
  9965. Editor.pageSizeUnit = (unit == mxConstants.METERS) ?
  9966. mxConstants.MILLIMETERS : unit;
  9967. });
  9968. var showRuler = this.canvasSupported && document.documentMode != 9 &&
  9969. (urlParams['ruler'] == '1' || mxSettings.isRulerOn()) &&
  9970. (!this.editor.isChromelessView() || this.editor.editable);
  9971. this.ruler = (showRuler) ? new mxDualRuler(this, view.unit) : null;
  9972. this.refresh();
  9973. }
  9974. // Adds an element to edit the style in the footer in test mode
  9975. if (urlParams['styledev'] == '1')
  9976. {
  9977. var footer = document.getElementById('geFooter');
  9978. if (footer != null)
  9979. {
  9980. this.styleInput = document.createElement('input');
  9981. this.styleInput.setAttribute('type', 'text');
  9982. this.styleInput.style.position = 'absolute';
  9983. this.styleInput.style.top = '14px';
  9984. this.styleInput.style.left = '2px';
  9985. // Workaround for ignore right CSS property in FF
  9986. this.styleInput.style.width = '98%';
  9987. this.styleInput.style.visibility = 'hidden';
  9988. this.styleInput.style.opacity = '0.9';
  9989. mxEvent.addListener(this.styleInput, 'change', mxUtils.bind(this, function()
  9990. {
  9991. this.editor.graph.getModel().setStyle(this.editor.graph.getSelectionCell(), this.styleInput.value);
  9992. }));
  9993. footer.appendChild(this.styleInput);
  9994. this.editor.graph.getSelectionModel().addListener(mxEvent.CHANGE, mxUtils.bind(this, function(sender, evt)
  9995. {
  9996. if (this.editor.graph.getSelectionCount() > 0)
  9997. {
  9998. var cell = this.editor.graph.getSelectionCell();
  9999. var style = this.editor.graph.getModel().getStyle(cell);
  10000. this.styleInput.value = style || '';
  10001. this.styleInput.style.visibility = 'visible';
  10002. }
  10003. else
  10004. {
  10005. this.styleInput.style.visibility = 'hidden';
  10006. }
  10007. }));
  10008. }
  10009. var isSelectionAllowed = this.isSelectionAllowed;
  10010. this.isSelectionAllowed = function(evt)
  10011. {
  10012. if (mxEvent.getSource(evt) == this.styleInput)
  10013. {
  10014. return true;
  10015. }
  10016. return isSelectionAllowed.apply(this, arguments);
  10017. };
  10018. }
  10019. // Removes info text in page
  10020. var info = document.getElementById('geInfo');
  10021. if (info != null)
  10022. {
  10023. info.parentNode.removeChild(info);
  10024. }
  10025. // Installs drag and drop handler for files
  10026. // Enables dropping files
  10027. if (Graph.fileSupport && (!this.editor.chromeless || this.editor.editable))
  10028. {
  10029. // Setup the dnd listeners
  10030. var dropElt = null;
  10031. mxEvent.addListener(graph.container, 'dragleave', function(evt)
  10032. {
  10033. if (graph.isEnabled())
  10034. {
  10035. if (dropElt != null)
  10036. {
  10037. dropElt.parentNode.removeChild(dropElt);
  10038. dropElt = null;
  10039. }
  10040. evt.stopPropagation();
  10041. evt.preventDefault();
  10042. }
  10043. });
  10044. mxEvent.addListener(graph.container, 'dragover', mxUtils.bind(this, function(evt)
  10045. {
  10046. // IE 10 does not implement pointer-events so it can't have a drop highlight
  10047. if (dropElt == null && (!mxClient.IS_IE || document.documentMode > 10))
  10048. {
  10049. dropElt = this.highlightElement(graph.container);
  10050. }
  10051. if (this.sidebar != null)
  10052. {
  10053. this.sidebar.hideTooltip();
  10054. }
  10055. evt.stopPropagation();
  10056. evt.preventDefault();
  10057. }));
  10058. mxEvent.addListener(graph.container, 'drop', mxUtils.bind(this, function(evt)
  10059. {
  10060. if (dropElt != null)
  10061. {
  10062. dropElt.parentNode.removeChild(dropElt);
  10063. dropElt = null;
  10064. }
  10065. if (graph.isEnabled())
  10066. {
  10067. var pt = mxUtils.convertPoint(graph.container, mxEvent.getClientX(evt), mxEvent.getClientY(evt));
  10068. var files = evt.dataTransfer.files;
  10069. var tr = graph.view.translate;
  10070. var scale = graph.view.scale;
  10071. var x = pt.x / scale - tr.x;
  10072. var y = pt.y / scale - tr.y;
  10073. if (files.length > 0)
  10074. {
  10075. if (urlParams['embed'] != '1' && mxEvent.isShiftDown(evt))
  10076. {
  10077. // Closes current file if blank and no undoable changes
  10078. if (this.isBlankFile() && !this.canUndo() &&
  10079. this.getCurrentFile() != null)
  10080. {
  10081. this.fileLoaded(null);
  10082. }
  10083. this.openFiles(files, true);
  10084. }
  10085. else
  10086. {
  10087. if (mxEvent.isAltDown(evt))
  10088. {
  10089. x = null;
  10090. y = null;
  10091. }
  10092. this.importFiles(files, x, y, this.maxImageSize, null, null, null,
  10093. null, mxEvent.isControlDown(evt), null, null,
  10094. mxEvent.isShiftDown(evt), evt);
  10095. }
  10096. }
  10097. else
  10098. {
  10099. if (mxEvent.isAltDown(evt))
  10100. {
  10101. x = 0;
  10102. y = 0;
  10103. }
  10104. var uri = (mxUtils.indexOf(evt.dataTransfer.types, 'text/uri-list') >= 0) ?
  10105. evt.dataTransfer.getData('text/uri-list') : null;
  10106. var data = this.extractGraphModelFromEvent(evt, this.pages != null);
  10107. if (data != null)
  10108. {
  10109. graph.setSelectionCells(this.importXml(data, x, y, true));
  10110. }
  10111. else if (mxUtils.indexOf(evt.dataTransfer.types, 'text/html') >= 0)
  10112. {
  10113. var html = evt.dataTransfer.getData('text/html');
  10114. var div = document.createElement('div');
  10115. div.innerHTML = Graph.sanitizeHtml(html);
  10116. // The default is based on the extension
  10117. var asImage = null;
  10118. // Extracts single image
  10119. var imgs = div.getElementsByTagName('img');
  10120. if (imgs != null && imgs.length == 1)
  10121. {
  10122. html = imgs[0].getAttribute('src');
  10123. if (html == null)
  10124. {
  10125. html = imgs[0].getAttribute('srcset');
  10126. }
  10127. // Handles special case where the src attribute has no valid extension
  10128. // in which case the text would be inserted as text with a link
  10129. if (!(/\.(gif|jpg|jpeg|tiff|png|svg)$/i).test(html))
  10130. {
  10131. asImage = true;
  10132. }
  10133. }
  10134. else
  10135. {
  10136. // Extracts single link
  10137. var a = div.getElementsByTagName('a');
  10138. if (a != null && a.length == 1)
  10139. {
  10140. html = a[0].getAttribute('href');
  10141. }
  10142. else
  10143. {
  10144. // Extracts preformatted text
  10145. var pre = div.getElementsByTagName('pre');
  10146. if (pre != null && pre.length == 1)
  10147. {
  10148. html = mxUtils.getTextContent(pre[0]);
  10149. }
  10150. }
  10151. }
  10152. var resizeImages = true;
  10153. var doInsert = mxUtils.bind(this, function()
  10154. {
  10155. graph.setSelectionCells(this.insertTextAt(html, x, y, true,
  10156. asImage, null, resizeImages, mxEvent.isControlDown(evt)));
  10157. });
  10158. if (asImage && html != null && html.length > this.resampleThreshold)
  10159. {
  10160. this.confirmImageResize(function(doResize)
  10161. {
  10162. resizeImages = doResize;
  10163. doInsert();
  10164. }, mxEvent.isControlDown(evt));
  10165. }
  10166. else
  10167. {
  10168. doInsert();
  10169. }
  10170. }
  10171. else if (uri != null && (/\.(gif|jpg|jpeg|tiff|png|svg)$/i).test(uri))
  10172. {
  10173. this.loadImage(decodeURIComponent(uri), mxUtils.bind(this, function(img)
  10174. {
  10175. var w = Math.max(1, img.width);
  10176. var h = Math.max(1, img.height);
  10177. var maxSize = this.maxImageSize;
  10178. var s = Math.min(1, Math.min(maxSize / Math.max(1, w)), maxSize / Math.max(1, h));
  10179. graph.setSelectionCell(graph.insertVertex(null, null, '', x, y, w * s, h * s,
  10180. 'shape=image;verticalLabelPosition=bottom;labelBackgroundColor=default;' +
  10181. 'verticalAlign=top;aspect=fixed;imageAspect=0;image=' + uri + ';'));
  10182. }), mxUtils.bind(this, function(img)
  10183. {
  10184. graph.setSelectionCells(this.insertTextAt(uri, x, y, true));
  10185. }));
  10186. }
  10187. else if (mxUtils.indexOf(evt.dataTransfer.types, 'text/plain') >= 0)
  10188. {
  10189. graph.setSelectionCells(this.insertTextAt(evt.dataTransfer.getData('text/plain'), x, y, true));
  10190. }
  10191. }
  10192. }
  10193. evt.stopPropagation();
  10194. evt.preventDefault();
  10195. }), false);
  10196. }
  10197. this.initPages();
  10198. // Embedded mode
  10199. if (urlParams['embed'] == '1')
  10200. {
  10201. this.initializeEmbedMode();
  10202. }
  10203. var themeChangeListener = mxUtils.bind(this, function()
  10204. {
  10205. graph.refresh();
  10206. graph.view.validateBackground();
  10207. this.updateDocumentTitle();
  10208. this.updateTabContainer();
  10209. this.hideShapePicker();
  10210. });
  10211. this.addListener('darkModeChanged', themeChangeListener);
  10212. this.addListener('currentThemeChanged', mxUtils.bind(this, function()
  10213. {
  10214. if (this.sidebar != null)
  10215. {
  10216. this.sidebar.updateEntries();
  10217. }
  10218. this.updateButtonContainer();
  10219. this.updateDocumentTitle();
  10220. this.refresh();
  10221. }));
  10222. graph.addListener('enabledChanged', mxUtils.bind(this, function()
  10223. {
  10224. if (!graph.isEnabled())
  10225. {
  10226. this.hideShapePicker();
  10227. }
  10228. }));
  10229. // Overrides mxWindow.fit to allow for embedViewport
  10230. var ui = this;
  10231. mxWindow.prototype.fit = function()
  10232. {
  10233. if (!Editor.inlineFullscreen && ui.embedViewport != null)
  10234. {
  10235. var left = parseInt(this.div.offsetLeft);
  10236. var width = parseInt(this.div.offsetWidth);
  10237. var right = ui.embedViewport.x + ui.embedViewport.width;
  10238. var top = parseInt(this.div.offsetTop);
  10239. var height = parseInt(this.div.offsetHeight);
  10240. var bottom = ui.embedViewport.y + ui.embedViewport.height;
  10241. this.div.style.left = Math.max(ui.embedViewport.x, Math.min(left, right - width)) + 'px';
  10242. this.div.style.top = Math.max(ui.embedViewport.y, Math.min(top, bottom - height)) + 'px';
  10243. this.div.style.height = Math.min(ui.embedViewport.height, parseInt(this.div.style.height)) + 'px';
  10244. this.div.style.width = Math.min(ui.embedViewport.width, parseInt(this.div.style.width)) + 'px';
  10245. }
  10246. else
  10247. {
  10248. mxUtils.fit(this.div);
  10249. }
  10250. };
  10251. if (!this.editor.chromeless || this.editor.editable)
  10252. {
  10253. // Activates scheme in UI
  10254. if (Editor.currentTheme == 'simple' ||
  10255. Editor.currentTheme == 'sketch')
  10256. {
  10257. var theme = Editor.currentTheme;
  10258. Editor.currentTheme = '';
  10259. this.doSetCurrentTheme(theme, 0, mxUtils.bind(this, function()
  10260. {
  10261. if (urlParams['embedInline'] == '1')
  10262. {
  10263. // Inline embed mode must be initialized after setting current theme
  10264. this.initializeInlineEmbedMode();
  10265. }
  10266. else
  10267. {
  10268. // Initial state of format panel
  10269. var iw = window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth;
  10270. if (iw < Editor.smallScreenWidth)
  10271. {
  10272. this.toggleFormatPanel(false);
  10273. }
  10274. }
  10275. this.fireEvent(new mxEventObject('themeInitialized'));
  10276. }));
  10277. }
  10278. }
  10279. if (Editor.currentTheme != 'atlas')
  10280. {
  10281. if (!mxClient.IS_IE && !mxClient.IS_IE11 && urlParams['dark'] != '0' &&
  10282. Editor.currentTheme != 'atlas' && (urlParams['embed'] != '1' ||
  10283. urlParams['dark'] == '1' || urlParams['dark'] == 'auto' ||
  10284. Editor.currentTheme == 'dark' || urlParams['atlas'] == '1'))
  10285. {
  10286. var darkMode = false;
  10287. if (this.isAutoDarkModeSupported() && this.isAutoDarkMode())
  10288. {
  10289. darkMode = window.matchMedia('(prefers-color-scheme: dark)').matches;
  10290. }
  10291. else if (urlParams['dark'] == null && Editor.isSettingsEnabled() &&
  10292. mxSettings.settings.darkMode === true)
  10293. {
  10294. darkMode = true;
  10295. }
  10296. if (darkMode || urlParams['dark'] == '1' ||
  10297. Editor.currentTheme == 'dark')
  10298. {
  10299. if (urlParams['contrast'] != null)
  10300. {
  10301. Editor.enableCssDarkMode = urlParams['contrast'] != '0';
  10302. }
  10303. this.setDarkMode(true);
  10304. }
  10305. }
  10306. if (this.isAutoDarkModeSupported())
  10307. {
  10308. try
  10309. {
  10310. // Automatically updates theme when system setting changes
  10311. window.matchMedia('(prefers-color-scheme: dark)')
  10312. .addEventListener('change', mxUtils.bind(this, function (e)
  10313. {
  10314. if (this.isAutoDarkMode())
  10315. {
  10316. this.setDarkMode(e.matches);
  10317. }
  10318. }));
  10319. }
  10320. catch (e)
  10321. {
  10322. // Ignores object doesn't support addEventListener and disables auto dark mode
  10323. this.actions.get('autoMode').setEnabled(false);
  10324. }
  10325. }
  10326. else if (Editor.isSettingsEnabled() && mxSettings.settings.darkMode === true)
  10327. {
  10328. darkMode = true;
  10329. }
  10330. }
  10331. if (urlParams['high-contrast'] == '1')
  10332. {
  10333. this.setHighContrast(true);
  10334. }
  10335. else if (Editor.isSettingsEnabled() && !this.editor.graph.isLightboxView() &&
  10336. mxSettings.settings.highContrast != null)
  10337. {
  10338. this.setHighContrast(mxSettings.settings.highContrast);
  10339. }
  10340. this.installSettings();
  10341. // Animations
  10342. this.addListener('enableAnimationsChanged', mxUtils.bind(this, function(sender, evt)
  10343. {
  10344. graph.enableFlowAnimation = Editor.enableAnimations;
  10345. graph.refresh();
  10346. }));
  10347. graph.enableFlowAnimation = Editor.enableAnimations;
  10348. if (screen.width <= Editor.smallScreenWidth)
  10349. {
  10350. this.formatWidth = 0;
  10351. }
  10352. if (urlParams['prefetchFonts'] == '1')
  10353. {
  10354. ui.editor.loadFonts();
  10355. }
  10356. };
  10357. /**
  10358. * Adapts the UI elements when the window size changes.
  10359. */
  10360. var editorUiWindowResized = EditorUi.prototype.windowResized;
  10361. EditorUi.prototype.windowResized = function()
  10362. {
  10363. if (Editor.currentTheme == 'simple')
  10364. {
  10365. var iw = window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth;
  10366. var limit = Editor.smallScreenWidth;
  10367. if (this.lastWindowWidth != null && this.lastWindowWidth >= limit && iw < limit)
  10368. {
  10369. if (this.isFormatPanelVisible())
  10370. {
  10371. this.toggleFormatPanel(false);
  10372. }
  10373. }
  10374. else if (this.lastWindowWidth != null && this.lastWindowWidth < limit && iw >= limit)
  10375. {
  10376. if (!this.isFormatPanelVisible())
  10377. {
  10378. this.toggleFormatPanel(true);
  10379. }
  10380. }
  10381. this.lastWindowWidth = iw;
  10382. }
  10383. editorUiWindowResized.apply(this, arguments);
  10384. };
  10385. /**
  10386. * Initializes embed inline mode.
  10387. */
  10388. EditorUi.prototype.initializeInlineEmbedMode = function()
  10389. {
  10390. var footer = this.sketchFooterMenuElt;
  10391. var toolbar = this.sketchMainMenuElt;
  10392. var picker = this.sketchPickerMenuElt;
  10393. var graph = this.editor.graph;
  10394. picker.style.transform = '';
  10395. mxEvent.addGestureListeners(this.diagramContainer.parentNode, mxUtils.bind(this, function(evt)
  10396. {
  10397. if (mxEvent.getSource(evt) == this.diagramContainer.parentNode)
  10398. {
  10399. this.embedExitPoint = new mxPoint(
  10400. mxEvent.getClientX(evt),
  10401. mxEvent.getClientY(evt));
  10402. this.sendEmbeddedSvgExport();
  10403. }
  10404. }));
  10405. document.body.style.cursor = 'text';
  10406. var div = document.createElement('div');
  10407. div.style.position = 'absolute';
  10408. div.style.width = '10px';
  10409. div.style.height = '10px';
  10410. div.style.borderRadius = '5px';
  10411. div.style.border = '1px solid gray';
  10412. div.style.background = '#ffffff';
  10413. div.style.cursor = 'row-resize';
  10414. this.diagramContainer.parentNode.appendChild(div);
  10415. this.bottomResizer = div;
  10416. var x0 = null;
  10417. var y0 = null;
  10418. var w0 = null;
  10419. var h0 = null;
  10420. mxEvent.addGestureListeners(div, mxUtils.bind(this, function(evt)
  10421. {
  10422. h0 = parseInt(this.diagramContainer.style.height);
  10423. y0 = mxEvent.getClientY(evt);
  10424. graph.popupMenuHandler.hideMenu();
  10425. mxEvent.consume(evt);
  10426. }));
  10427. div = div.cloneNode(false);
  10428. div.style.cursor = 'col-resize';
  10429. this.diagramContainer.parentNode.appendChild(div);
  10430. this.rightResizer = div;
  10431. mxEvent.addGestureListeners(div, mxUtils.bind(this, function(evt)
  10432. {
  10433. w0 = parseInt(this.diagramContainer.style.width);
  10434. x0 = mxEvent.getClientX(evt);
  10435. graph.popupMenuHandler.hideMenu();
  10436. mxEvent.consume(evt);
  10437. }));
  10438. mxEvent.addGestureListeners(document.body, null, mxUtils.bind(this, function(evt)
  10439. {
  10440. var changed = false;
  10441. if (x0 != null)
  10442. {
  10443. this.diagramContainer.style.width = Math.max(20,
  10444. w0 + mxEvent.getClientX(evt) - x0) + 'px';
  10445. changed = true;
  10446. }
  10447. if (y0 != null)
  10448. {
  10449. this.diagramContainer.style.height = Math.max(20,
  10450. h0 + mxEvent.getClientY(evt) - y0) + 'px';
  10451. changed = true;
  10452. }
  10453. if (changed)
  10454. {
  10455. var parent = window.opener || window.parent;
  10456. parent.postMessage(JSON.stringify({
  10457. event: 'resize',
  10458. fullscreen: Editor.inlineFullscreen,
  10459. rect: this.diagramContainer.getBoundingClientRect()
  10460. }), '*');
  10461. this.inlineSizeChanged();
  10462. this.refresh();
  10463. }
  10464. }), function(evt)
  10465. {
  10466. if (x0 != null || y0 != null)
  10467. {
  10468. mxEvent.consume(evt);
  10469. }
  10470. x0 = null;
  10471. y0 = null;
  10472. });
  10473. document.body.style.backgroundColor = 'transparent';
  10474. this.diagramContainer.style.borderRadius = '4px';
  10475. this.bottomResizer.style.visibility = 'hidden';
  10476. this.rightResizer.style.visibility = 'hidden';
  10477. this.sketchMenubarElt.style.display = 'none';
  10478. toolbar.style.visibility = 'hidden';
  10479. footer.style.visibility = 'hidden';
  10480. picker.style.display = 'none';
  10481. this.addListener('editInlineStart', mxUtils.bind(this, function(evt)
  10482. {
  10483. this.inlineSizeChanged();
  10484. this.fitWindows();
  10485. }));
  10486. this.addListener('darkModeChanged', mxUtils.bind(this, function(evt)
  10487. {
  10488. if (!Editor.enableCssDarkMode)
  10489. {
  10490. this.inlineSizeChanged();
  10491. }
  10492. }));
  10493. this.addListener('editInlineStop', mxUtils.bind(this, function(evt)
  10494. {
  10495. this.diagramContainer.style.width = '10px';
  10496. this.diagramContainer.style.height = '10px';
  10497. this.diagramContainer.style.border = '';
  10498. this.bottomResizer.style.visibility = 'hidden';
  10499. this.rightResizer.style.visibility = 'hidden';
  10500. toolbar.style.visibility = 'hidden';
  10501. footer.style.visibility = 'hidden';
  10502. picker.style.display = 'none';
  10503. }));
  10504. // Overridden to avoid reset of scrollbars
  10505. this.windowResized = mxUtils.bind(this, function()
  10506. {
  10507. // do nothing
  10508. });
  10509. this.inlineSizeChanged();
  10510. };
  10511. /**
  10512. * Installs handler for pasting image from clipboard.
  10513. */
  10514. EditorUi.prototype.installImagePasteHandler = function()
  10515. {
  10516. if (!mxClient.IS_IE)
  10517. {
  10518. var graph = this.editor.graph;
  10519. graph.container.addEventListener('paste', mxUtils.bind(this, function(evt)
  10520. {
  10521. if (!mxEvent.isConsumed(evt))
  10522. {
  10523. try
  10524. {
  10525. var data = (evt.clipboardData || evt.originalEvent.clipboardData);
  10526. var containsText = false;
  10527. // Workaround for asynchronous paste event processing in textInput
  10528. // is to ignore this event if it contains text/html/rtf (see below).
  10529. // NOTE: Image is not pasted into textInput so can't listen there.
  10530. for (var i = 0; i < data.types.length; i++)
  10531. {
  10532. if (data.types[i].substring(0, 5) === 'text/')
  10533. {
  10534. containsText = true;
  10535. break;
  10536. }
  10537. }
  10538. if (!containsText)
  10539. {
  10540. var items = data.items;
  10541. for (index in items)
  10542. {
  10543. var item = items[index];
  10544. if (item.kind === 'file')
  10545. {
  10546. if (graph.isEditing())
  10547. {
  10548. this.importFiles([item.getAsFile()], 0, 0, this.maxImageSize, function(data, mimeType, x, y, w, h)
  10549. {
  10550. // Inserts image into current text box
  10551. graph.insertImage(data, w, h);
  10552. }, function()
  10553. {
  10554. // No post processing
  10555. }, function(file)
  10556. {
  10557. // Handles only images
  10558. return file.type.substring(0, 6) == 'image/';
  10559. }, function(queue)
  10560. {
  10561. // Invokes elements of queue in order
  10562. for (var i = 0; i < queue.length; i++)
  10563. {
  10564. queue[i]();
  10565. }
  10566. });
  10567. }
  10568. else
  10569. {
  10570. var pt = this.editor.graph.getInsertPoint();
  10571. this.importFiles([item.getAsFile()], pt.x, pt.y, this.maxImageSize);
  10572. mxEvent.consume(evt);
  10573. }
  10574. break;
  10575. }
  10576. }
  10577. }
  10578. }
  10579. catch (e)
  10580. {
  10581. // ignore
  10582. }
  10583. }
  10584. }), false);
  10585. }
  10586. };
  10587. /**
  10588. * Installs the native clipboard support.
  10589. */
  10590. EditorUi.prototype.installNativeClipboardHandler = function()
  10591. {
  10592. var graph = this.editor.graph;
  10593. // Focused but invisible textarea during control or meta key events
  10594. // LATER: Disable text rendering to avoid delay while keeping focus
  10595. var textInput = document.createElement('div');
  10596. textInput.setAttribute('autocomplete', 'off');
  10597. textInput.setAttribute('autocorrect', 'off');
  10598. textInput.setAttribute('autocapitalize', 'off');
  10599. textInput.setAttribute('spellcheck', 'false');
  10600. textInput.style.textRendering = 'optimizeSpeed';
  10601. textInput.style.fontFamily = 'monospace';
  10602. textInput.style.wordBreak = 'break-all';
  10603. textInput.style.background = 'transparent';
  10604. textInput.style.color = 'transparent';
  10605. textInput.style.position = 'absolute';
  10606. textInput.style.whiteSpace = 'nowrap';
  10607. textInput.style.overflow = 'hidden';
  10608. textInput.style.display = 'block';
  10609. textInput.style.fontSize = '1';
  10610. textInput.style.zIndex = '-1';
  10611. textInput.style.resize = 'none';
  10612. textInput.style.outline = 'none';
  10613. textInput.style.width = '1px';
  10614. textInput.style.height = '1px';
  10615. mxUtils.setOpacity(textInput, 0);
  10616. textInput.contentEditable = true;
  10617. textInput.innerHTML = '&nbsp;';
  10618. var restoreFocus = false;
  10619. // Disables built-in cut, copy and paste shortcuts
  10620. this.keyHandler.bindControlKey(88, null);
  10621. this.keyHandler.bindControlKey(67, null);
  10622. this.keyHandler.bindControlKey(86, null);
  10623. // Shows a textare when control/cmd is pressed to handle native clipboard actions
  10624. mxEvent.addListener(document, 'keydown', mxUtils.bind(this, function(evt)
  10625. {
  10626. // No dialog visible
  10627. var source = mxEvent.getSource(evt);
  10628. if (graph.container != null && graph.isEnabled() && !graph.isMouseDown && !graph.isEditing() &&
  10629. this.dialog == null && source.nodeName != 'INPUT' && source.nodeName != 'TEXTAREA' &&
  10630. source.contentEditable != 'true')
  10631. {
  10632. if (evt.keyCode == 224 /* FF */ || (!mxClient.IS_MAC && evt.keyCode == 17 /* Control */) ||
  10633. (mxClient.IS_MAC && (evt.keyCode == 91 || evt.keyCode == 93) /* Left/Right Meta */))
  10634. {
  10635. // Cannot use parentNode for check in IE
  10636. if (!restoreFocus)
  10637. {
  10638. // Avoid autoscroll but allow handling of all pass-through ctrl shortcuts
  10639. textInput.style.left = (graph.container.scrollLeft + 10) + 'px';
  10640. textInput.style.top = (graph.container.scrollTop + 10) + 'px';
  10641. var x0 = graph.container.scrollLeft;
  10642. var y0 = graph.container.scrollTop;
  10643. graph.container.appendChild(textInput);
  10644. restoreFocus = true;
  10645. textInput.focus();
  10646. document.execCommand('selectAll', false, null);
  10647. // Workaround for Safari 16 scroll after CMD key press
  10648. graph.container.scrollLeft = x0;
  10649. graph.container.scrollTop = y0;
  10650. }
  10651. }
  10652. }
  10653. }));
  10654. // Clears input and restores focus and selection
  10655. function clearInput()
  10656. {
  10657. window.setTimeout(function()
  10658. {
  10659. textInput.innerHTML = '&nbsp;';
  10660. textInput.focus();
  10661. document.execCommand('selectAll', false, null);
  10662. }, 0);
  10663. };
  10664. mxEvent.addListener(document, 'keyup', mxUtils.bind(this, function(evt)
  10665. {
  10666. // Workaround for asynchronous event read invalid in IE quirks mode
  10667. var keyCode = evt.keyCode;
  10668. // Asynchronous workaround for scroll to origin after paste if the
  10669. // Ctrl-key is not pressed for long enough in FF on Windows
  10670. window.setTimeout(mxUtils.bind(this, function()
  10671. {
  10672. if (restoreFocus && (keyCode == 224 /* FF */ || keyCode == 17 /* Control */ ||
  10673. keyCode == 91 /* MetaLeft */ || keyCode == 93 /* MetaRight */))
  10674. {
  10675. restoreFocus = false;
  10676. if (!graph.isEditing() && this.dialog == null && graph.container != null)
  10677. {
  10678. graph.container.focus();
  10679. }
  10680. textInput.parentNode.removeChild(textInput);
  10681. // Workaround for lost cursor in focused element
  10682. if (this.dialog == null)
  10683. {
  10684. mxUtils.clearSelection();
  10685. }
  10686. }
  10687. }), 0);
  10688. }));
  10689. mxEvent.addListener(textInput, 'copy', mxUtils.bind(this, function(evt)
  10690. {
  10691. if (graph.isEnabled())
  10692. {
  10693. try
  10694. {
  10695. mxClipboard.copy(graph);
  10696. this.copyCells(textInput);
  10697. clearInput();
  10698. }
  10699. catch (e)
  10700. {
  10701. this.handleError(e);
  10702. }
  10703. }
  10704. }));
  10705. mxEvent.addListener(textInput, 'cut', mxUtils.bind(this, function(evt)
  10706. {
  10707. if (graph.isEnabled())
  10708. {
  10709. try
  10710. {
  10711. mxClipboard.copy(graph);
  10712. this.copyCells(textInput, true);
  10713. clearInput();
  10714. }
  10715. catch (e)
  10716. {
  10717. this.handleError(e);
  10718. }
  10719. }
  10720. }));
  10721. mxEvent.addListener(textInput, 'paste', mxUtils.bind(this, function(evt)
  10722. {
  10723. if (graph.isEnabled() && !graph.isCellLocked(graph.getDefaultParent()))
  10724. {
  10725. try
  10726. {
  10727. textInput.innerHTML = '&nbsp;';
  10728. textInput.focus();
  10729. if (evt.clipboardData != null)
  10730. {
  10731. Graph.removePasteFormatting(textInput.firstChild);
  10732. this.pasteCells(evt, textInput, true, true);
  10733. }
  10734. if (!mxEvent.isConsumed(evt))
  10735. {
  10736. var x0 = graph.container.scrollLeft;
  10737. var y0 = graph.container.scrollTop;
  10738. window.setTimeout(mxUtils.bind(this, function()
  10739. {
  10740. try
  10741. {
  10742. // Workaround for Safari 16 scroll after paste
  10743. graph.container.scrollLeft = x0;
  10744. graph.container.scrollTop = y0;
  10745. Graph.removePasteFormatting(textInput.firstChild);
  10746. this.pasteCells(evt, textInput, false, true);
  10747. }
  10748. catch (e)
  10749. {
  10750. this.handleError(e);
  10751. }
  10752. }), 0);
  10753. }
  10754. }
  10755. catch (e)
  10756. {
  10757. this.handleError(e);
  10758. }
  10759. }
  10760. }), true);
  10761. // Needed for IE11
  10762. var isSelectionAllowed2 = this.isSelectionAllowed;
  10763. this.isSelectionAllowed = function(evt)
  10764. {
  10765. if (mxEvent.getSource(evt) == textInput)
  10766. {
  10767. return true;
  10768. }
  10769. return isSelectionAllowed2.apply(this, arguments);
  10770. };
  10771. };
  10772. /**
  10773. * Sets the current UI theme. Possible values are null, "kennedy" and "sketch".
  10774. */
  10775. EditorUi.prototype.setCurrentTheme = function(value, noRestart)
  10776. {
  10777. mxSettings.setUi(value);
  10778. noRestart = this.doSetCurrentTheme(value) || noRestart;
  10779. if (!noRestart)
  10780. {
  10781. this.alert(mxResources.get('restartForChangeRequired'));
  10782. }
  10783. };
  10784. /**
  10785. * Changes the current UI theme.
  10786. */
  10787. EditorUi.prototype.isDefaultTheme = function(theme)
  10788. {
  10789. return theme == '' || theme == 'dark' || theme == 'default' ||
  10790. theme == 'kennedy' || theme == null;
  10791. };
  10792. /**
  10793. * Changes the current UI theme.
  10794. */
  10795. EditorUi.prototype.doSetCurrentTheme = function(value, delay, post)
  10796. {
  10797. delay = (delay != null) ? delay : 150;
  10798. function isSimple(theme)
  10799. {
  10800. return theme == 'simple' || (delay == 0 &&
  10801. theme == 'sketch');
  10802. };
  10803. // From kennedy to simple or sketch or vice versa
  10804. var curr = Editor.currentTheme;
  10805. var transition = (isSimple(curr) && this.isDefaultTheme(value)) ||
  10806. (this.isDefaultTheme(curr) && isSimple(value));
  10807. var noRestart = transition && (value != 'sketch' && curr != 'sketch');
  10808. if (transition && !this.themeSwitching)
  10809. {
  10810. Editor.currentTheme = value;
  10811. this.themeSwitching = true;
  10812. mxUtils.setPrefixedStyle(this.container.style, 'transition',
  10813. 'all ' + delay + 'ms ease-in-out');
  10814. if (delay == 0)
  10815. {
  10816. this.container.style.opacity = '0';
  10817. }
  10818. window.setTimeout(mxUtils.bind(this, function()
  10819. {
  10820. var scrollState = this.saveScrollState();
  10821. this.editor.graph.stopEditing(false);
  10822. this.container.style.opacity = '0';
  10823. window.setTimeout(mxUtils.bind(this, function()
  10824. {
  10825. if (isSimple(curr) && this.isDefaultTheme(value))
  10826. {
  10827. this.menubarContainer.style.display = '';
  10828. this.toolbarContainer.style.display = 'block';
  10829. this.tabContainer.style.display = 'flex';
  10830. this.hsplit.style.display = 'block';
  10831. this.menubarHeight = App.prototype.menubarHeight;
  10832. }
  10833. else if (this.isDefaultTheme(curr) && isSimple(value))
  10834. {
  10835. this.menubarContainer.style.display = 'none';
  10836. this.toolbarContainer.style.display = 'none';
  10837. this.menubarHeight = 0;
  10838. if (value != 'simple')
  10839. {
  10840. this.tabContainer.style.display = 'none';
  10841. this.hsplit.style.display = 'none';
  10842. this.hsplitPosition = 0;
  10843. }
  10844. else
  10845. {
  10846. this.tabContainer.style.display = 'flex';
  10847. }
  10848. }
  10849. this.switchTheme(value);
  10850. window.setTimeout(mxUtils.bind(this, function()
  10851. {
  10852. this.fireEvent(new mxEventObject('currentThemeChanged'));
  10853. this.editor.fireEvent(new mxEventObject('statusChanged'));
  10854. this.editor.graph.refresh();
  10855. this.restoreScrollState(scrollState);
  10856. this.container.style.opacity = '';
  10857. window.setTimeout(mxUtils.bind(this, function()
  10858. {
  10859. mxUtils.setPrefixedStyle(this.container.style, 'transition', null);
  10860. delete this.themeSwitching;
  10861. if (isLocalStorage && isSimple(value))
  10862. {
  10863. this.setTabContainerVisible((mxSettings.settings.pages != null) ?
  10864. mxSettings.settings.pages : true);
  10865. }
  10866. if (post != null)
  10867. {
  10868. post();
  10869. }
  10870. }), delay);
  10871. }), delay);
  10872. }), delay);
  10873. }), 0);
  10874. }
  10875. return noRestart;
  10876. };
  10877. /**
  10878. * Saves scroll position
  10879. */
  10880. EditorUi.prototype.saveScrollState = function()
  10881. {
  10882. var t = this.editor.graph.view.translate;
  10883. var x = this.diagramContainer.scrollLeft;
  10884. var y = this.diagramContainer.scrollTop;
  10885. if (this.embedViewport != null)
  10886. {
  10887. if (!Editor.inlineFullscreen)
  10888. {
  10889. x += this.embedViewport.x;
  10890. y += this.embedViewport.y;
  10891. }
  10892. else
  10893. {
  10894. x -= this.embedViewport.x;
  10895. y -= this.embedViewport.y;
  10896. }
  10897. }
  10898. return {x: x, y: y, tx: t.x, ty: t.y};
  10899. };
  10900. /**
  10901. * Dynamic change of dark mode.
  10902. */
  10903. EditorUi.prototype.restoreScrollState = function(state)
  10904. {
  10905. var s = this.editor.graph.view.scale;
  10906. var t = this.editor.graph.view.translate;
  10907. this.diagramContainer.scrollLeft = state.x + (t.x - state.tx) * s;
  10908. this.diagramContainer.scrollTop = state.y + (t.y - state.ty) * s;
  10909. };
  10910. /**
  10911. * Overrides image dialog to add image search and Google+.
  10912. */
  10913. EditorUi.prototype.installStatusMinimizer = function(parent)
  10914. {
  10915. parent = (parent != null) ? parent : this.statusContainer.parentNode;
  10916. var visible = false;
  10917. mxEvent.addListener(parent, 'mouseenter', mxUtils.bind(this, function()
  10918. {
  10919. if (Editor.currentTheme == 'sketch' && this.editor.getStatus() != '')
  10920. {
  10921. this.statusContainer.style.display = 'inline-flex';
  10922. }
  10923. }));
  10924. mxEvent.addListener(parent, 'mouseleave', mxUtils.bind(this, function()
  10925. {
  10926. if (Editor.currentTheme == 'sketch' && !visible)
  10927. {
  10928. this.statusContainer.style.display = 'none';
  10929. }
  10930. }));
  10931. var statusChanged = mxUtils.bind(this, function()
  10932. {
  10933. if (Editor.currentTheme == 'sketch')
  10934. {
  10935. var elt = (this.statusContainer.firstChild != null &&
  10936. typeof this.statusContainer.firstChild.getAttribute === 'function') ?
  10937. this.statusContainer.firstChild : null;
  10938. visible = elt != null && elt.getAttribute('class') != null;
  10939. if (!visible && elt != null)
  10940. {
  10941. var title = elt.getAttribute('title');
  10942. var file = this.getCurrentFile();
  10943. var key = (file != null) ? file.savingStatusKey :
  10944. DrawioFile.prototype.savingStatusKey;
  10945. // Shows animated spinner while saving
  10946. if (title == mxResources.get(key) + '...')
  10947. {
  10948. this.statusContainer.innerHTML = '<div><img title="' + mxUtils.htmlEntities(
  10949. mxResources.get(key)) + '...' + '"src="' + Editor.tailSpin + '"></div>';
  10950. visible = true;
  10951. }
  10952. }
  10953. // Checks size of container without status
  10954. this.statusContainer.style.display = 'none';
  10955. var empty = parent.clientWidth <= 32;
  10956. // Hides container if empty and no status
  10957. parent.style.visibility = (empty && this.editor.getStatus() == '') ?
  10958. 'hidden' : '';
  10959. // Shows status if container empty or status relevant
  10960. if (empty || visible)
  10961. {
  10962. this.statusContainer.style.display = 'inline-flex';
  10963. visible = true;
  10964. }
  10965. }
  10966. else if (Editor.currentTheme == 'simple')
  10967. {
  10968. // Required for flex layout gaps to be applied correctly
  10969. this.statusContainer.style.display = 'inline-flex';
  10970. this.statusContainer.style.display = (this.statusContainer.clientWidth == 0)
  10971. ? 'none' : 'inline-flex';
  10972. }
  10973. else
  10974. {
  10975. this.statusContainer.style.display = 'inline-flex';
  10976. }
  10977. });
  10978. this.editor.addListener('statusChanged', statusChanged);
  10979. statusChanged();
  10980. };
  10981. /**
  10982. * Overrides image dialog to add image search and Google+.
  10983. */
  10984. EditorUi.prototype.switchTheme = function(value)
  10985. {
  10986. // Removes containers before destroying windows
  10987. if (this.isDefaultTheme(value))
  10988. {
  10989. // Format window
  10990. if (this.formatContainer != null)
  10991. {
  10992. this.formatContainer.style.left = '';
  10993. this.formatContainer.style.zIndex = '1';
  10994. this.formatContainer.style.border = '';
  10995. if (this.footerContainer != null)
  10996. {
  10997. if (this.footerContainer.parentNode !=
  10998. this.formatContainer.parentNode)
  10999. {
  11000. this.footerContainer.parentNode.insertBefore(
  11001. this.formatContainer, this.footerContainer);
  11002. }
  11003. }
  11004. // Shapes window
  11005. if (this.sidebarContainer != null)
  11006. {
  11007. if (this.formatContainer.parentNode !=
  11008. this.sidebarContainer.parentNode)
  11009. {
  11010. this.formatContainer.parentNode.insertBefore(
  11011. this.sidebarContainer, this.formatContainer);
  11012. }
  11013. }
  11014. }
  11015. }
  11016. this.destroyWindows();
  11017. this.updateUserElement();
  11018. this.updateDefaultStyles();
  11019. this.switchThemeConstants(value);
  11020. this.switchCssForTheme(value);
  11021. this.createWrapperForTheme(value);
  11022. this.createMainMenuForTheme(value);
  11023. this.createFooterMenuForTheme(value);
  11024. this.createPickerMenuForTheme(value);
  11025. this.createMenubarForTheme(value);
  11026. // TODO: Check what hides sidebarContainer
  11027. this.sidebarContainer.style.display = '';
  11028. if (value == 'sketch')
  11029. {
  11030. // Format window
  11031. this.createFormatWindow();
  11032. this.formatContainer.style.left = '0px';
  11033. this.formatContainer.style.top = '0px';
  11034. this.formatContainer.style.width = '';
  11035. this.formatContainer.style.zIndex = '';
  11036. this.formatContainer.style.border = 'none';
  11037. // Shapes window
  11038. var libs = Editor.enableCustomLibraries && (urlParams['embed'] != '1' ||
  11039. urlParams['libraries'] == '1');
  11040. this.createShapesWindow();
  11041. this.sidebarContainer.className = '';
  11042. this.sidebarContainer.style.position = 'absolute';
  11043. this.sidebarContainer.style.left = '0px';
  11044. this.sidebarContainer.style.top = '0px';
  11045. this.sidebarContainer.style.bottom = (libs) ? '32px' : '0px';
  11046. this.sidebarContainer.style.width = '100%';
  11047. }
  11048. // Format panel close button
  11049. if (this.format != null)
  11050. {
  11051. var closeButton = this.isDefaultTheme(value) || value == 'atlas';
  11052. if (this.format.showCloseButton != closeButton)
  11053. {
  11054. this.format.showCloseButton = closeButton;
  11055. this.format.refresh();
  11056. }
  11057. }
  11058. };
  11059. /**
  11060. * Overrides image dialog to add image search and Google+.
  11061. */
  11062. EditorUi.prototype.getWindows = function()
  11063. {
  11064. var wnd = [this.sidebarWindow, this.formatWindow, this.freehandWindow];
  11065. if (this.actions != null)
  11066. {
  11067. wnd = wnd.concat([this.actions.outlineWindow, this.actions.layersWindow]);
  11068. }
  11069. if (this.menus != null)
  11070. {
  11071. wnd = wnd.concat([this.menus.tagsWindow, this.menus.findWindow,
  11072. this.menus.findReplaceWindow, this.menus.commentsWindow]);
  11073. }
  11074. return wnd;
  11075. };
  11076. /**
  11077. * Overrides image dialog to add image search and Google+.
  11078. */
  11079. EditorUi.prototype.fitWindows = function()
  11080. {
  11081. var wnd = this.getWindows();
  11082. for (var i = 0; i < wnd.length; i++)
  11083. {
  11084. if (wnd[i] != null)
  11085. {
  11086. wnd[i].window.fit();
  11087. }
  11088. }
  11089. };
  11090. /**
  11091. * Overrides image dialog to add image search and Google+.
  11092. */
  11093. EditorUi.prototype.hideWindows = function()
  11094. {
  11095. var wnd = this.getWindows();
  11096. for (var i = 0; i < wnd.length; i++)
  11097. {
  11098. if (wnd[i] != null)
  11099. {
  11100. wnd[i].window.setVisible(false);
  11101. }
  11102. }
  11103. };
  11104. /**
  11105. * Overrides image dialog to add image search and Google+.
  11106. */
  11107. EditorUi.prototype.destroyWindows = function()
  11108. {
  11109. if (this.sidebarWindow != null)
  11110. {
  11111. this.sidebarWindow.destroy();
  11112. this.sidebarWindow = null;
  11113. }
  11114. if (this.formatWindow != null)
  11115. {
  11116. this.formatWindow.destroy();
  11117. this.formatWindow = null;
  11118. }
  11119. if (this.freehandWindow != null)
  11120. {
  11121. this.freehandWindow.destroy();
  11122. this.freehandWindow = null;
  11123. }
  11124. if (this.actions.outlineWindow != null)
  11125. {
  11126. this.actions.outlineWindow.destroy();
  11127. this.actions.outlineWindow = null;
  11128. }
  11129. if (this.actions.layersWindow != null)
  11130. {
  11131. this.actions.layersWindow.destroy();
  11132. this.actions.layersWindow = null;
  11133. }
  11134. if (this.menus != null)
  11135. {
  11136. if (this.menus.chatWindow != null)
  11137. {
  11138. this.menus.chatWindow.destroy();
  11139. this.menus.chatWindow = null;
  11140. }
  11141. if (this.menus.tagsWindow != null)
  11142. {
  11143. this.menus.tagsWindow.destroy();
  11144. this.menus.tagsWindow = null;
  11145. }
  11146. if (this.menus.findWindow != null)
  11147. {
  11148. this.menus.findWindow.destroy();
  11149. this.menus.findWindow = null;
  11150. }
  11151. if (this.menus.findReplaceWindow != null)
  11152. {
  11153. this.menus.findReplaceWindow.destroy();
  11154. this.menus.findReplaceWindow = null;
  11155. }
  11156. if (this.menus.commentsWindow != null)
  11157. {
  11158. this.menus.commentsWindow.destroy();
  11159. this.menus.commentsWindow = null;
  11160. }
  11161. }
  11162. };
  11163. /**
  11164. * Overrides image dialog to add image search and Google+.
  11165. */
  11166. EditorUi.prototype.switchThemeConstants = function(value)
  11167. {
  11168. var graph = this.editor.graph;
  11169. graph.defaultEdgeLength = Graph.prototype.defaultEdgeLength;
  11170. graph.defaultGridEnabled = Graph.prototype.defaultGridEnabled;
  11171. graph.defaultPageVisible = Graph.prototype.defaultPageVisible;
  11172. if (this.menus != null)
  11173. {
  11174. this.menus.autoPopup = value != 'simple' && value != 'sketch';
  11175. }
  11176. if (value == 'simple' || value == 'sketch')
  11177. {
  11178. Editor.fitWindowBorders = new mxRectangle(60, 30, 30, 30);
  11179. if (Editor.config == null || Editor.config.defaultEdgeLength == null)
  11180. {
  11181. graph.defaultEdgeLength = 120;
  11182. }
  11183. if (urlParams['grid'] == null)
  11184. {
  11185. graph.defaultGridEnabled = false;
  11186. }
  11187. if (urlParams['pv'] == null)
  11188. {
  11189. graph.defaultPageVisible = false;
  11190. }
  11191. }
  11192. else
  11193. {
  11194. Editor.fitWindowBorders = null;
  11195. }
  11196. };
  11197. /**
  11198. * Overrides image dialog to add image search and Google+.
  11199. */
  11200. EditorUi.prototype.switchCssForTheme = function(value)
  11201. {
  11202. if (this.sketchStyleElt != null)
  11203. {
  11204. this.sketchStyleElt.parentNode.removeChild(this.sketchStyleElt);
  11205. this.sketchStyleElt = null;
  11206. }
  11207. };
  11208. /**
  11209. * Overrides image dialog to add image search and Google+.
  11210. */
  11211. EditorUi.prototype.createWrapperForTheme = function(value)
  11212. {
  11213. if (this.sketchWrapperElt != null && this.sketchWrapperElt.parentNode != null)
  11214. {
  11215. this.tabContainer.parentNode.insertBefore(this.diagramContainer, this.tabContainer);
  11216. this.sketchWrapperElt.parentNode.removeChild(this.sketchWrapperElt);
  11217. }
  11218. };
  11219. /**
  11220. * Overrides image dialog to add image search and Google+.
  11221. */
  11222. EditorUi.prototype.createMainMenuForTheme = function(value)
  11223. {
  11224. // hook for subclassers
  11225. };
  11226. /**
  11227. * Overrides image dialog to add image search and Google+.
  11228. */
  11229. EditorUi.prototype.isPageMenuVisible = function()
  11230. {
  11231. return this.pages != null && (urlParams['pages'] != '0' ||
  11232. this.pages.length > 1 || Editor.pagesVisible);
  11233. };
  11234. /**
  11235. * Overrides image dialog to add image search and Google+.
  11236. */
  11237. EditorUi.prototype.createFooterMenuForTheme = function(value)
  11238. {
  11239. // hook for subclassers
  11240. };
  11241. /**
  11242. * Overrides image dialog to add image search and Google+.
  11243. */
  11244. EditorUi.prototype.createPickerMenuForTheme = function(value)
  11245. {
  11246. // hook for subclassers
  11247. };
  11248. /**
  11249. *
  11250. */
  11251. EditorUi.prototype.getNetworkStatus = function()
  11252. {
  11253. var status = null;
  11254. if (this.isOffline(true))
  11255. {
  11256. status = mxResources.get('offline');
  11257. }
  11258. else
  11259. {
  11260. var file = this.getCurrentFile();
  11261. if (file != null)
  11262. {
  11263. if (file.invalidChecksum)
  11264. {
  11265. status = mxResources.get('error') + ': ' +
  11266. mxResources.get('checksum');
  11267. }
  11268. else if (file.sync != null && (!file.sync.enabled ||
  11269. !file.sync.isRealtimeActive()) && this.getServiceName() != 'atlassian') // Atlassian app has pulling sync (no RT)
  11270. {
  11271. status = mxResources.get('realtimeCollaboration') +
  11272. ': ' + mxResources.get('disabled');
  11273. }
  11274. else if (file.sync != null && !file.sync.isConnected())
  11275. {
  11276. status = mxResources.get('notConnected');
  11277. }
  11278. else if (file.isRealtimeEnabled() &&
  11279. file.isRealtimeSupported() &&
  11280. file.getRealtimeState() > 1)
  11281. {
  11282. var err = file.getRealtimeError();
  11283. status = mxResources.get('realtimeCollaboration') + ': ' +
  11284. ((err != null && err.message != null) ?
  11285. err.message : mxResources.get('error'));
  11286. }
  11287. }
  11288. }
  11289. return status;
  11290. };
  11291. /**
  11292. * Overrides image dialog to add image search and Google+.
  11293. */
  11294. EditorUi.prototype.createMenubarForTheme = function(value)
  11295. {
  11296. if (this.statusContainer != null)
  11297. {
  11298. this.statusContainer.style.flexGrow = '';
  11299. this.statusContainer.style.flexShrink = '';
  11300. this.statusContainer.style.width = '';
  11301. this.statusContainer.style.marginTop = '';
  11302. this.statusContainer.style.justifyContent = '';
  11303. this.statusContainer.style.opacity = '';
  11304. this.menubar.container.appendChild(this.statusContainer);
  11305. }
  11306. if (this.userElement != null)
  11307. {
  11308. this.menubarContainer.appendChild(this.userElement);
  11309. }
  11310. var elt = this.menubar.langIcon;
  11311. if (elt != null)
  11312. {
  11313. elt.style.position = 'absolute';
  11314. elt.style.height = '18px';
  11315. elt.style.width = '18px';
  11316. elt.style.flexShrink = '';
  11317. elt.style.opacity = '';
  11318. this.menubarContainer.parentNode.insertBefore(elt,
  11319. this.menubarContainer);
  11320. }
  11321. if (this.buttonContainer != null)
  11322. {
  11323. this.buttonContainer.style.display = '';
  11324. this.buttonContainer.style.padding = '';
  11325. this.menubar.container.appendChild(this.buttonContainer);
  11326. }
  11327. };
  11328. /**
  11329. * Overrides image dialog to add image search and Google+.
  11330. */
  11331. EditorUi.prototype.createMenu = function(key, img, className, clickFn)
  11332. {
  11333. className = (className != null) ? className : 'geToolbarButton';
  11334. var menu = this.menus.get(key);
  11335. var elt = this.menubar.addMenu(mxResources.get(key), menu.funct, null, clickFn);
  11336. elt.className = className;
  11337. elt.style.display = 'inline-block';
  11338. elt.style.cursor = 'pointer';
  11339. elt.style.height = '24px';
  11340. elt.setAttribute('title', mxResources.get(key));
  11341. this.menus.menuCreated(menu, elt, className);
  11342. if (img != null)
  11343. {
  11344. elt.style.backgroundImage = 'url(' + img + ')';
  11345. elt.style.backgroundPosition = 'center center';
  11346. elt.style.backgroundRepeat = 'no-repeat';
  11347. elt.style.backgroundSize = '100% 100%';
  11348. elt.style.width = '24px';
  11349. elt.innerText = '';
  11350. }
  11351. return elt;
  11352. };
  11353. /**
  11354. * Create toolbar button.
  11355. */
  11356. EditorUi.prototype.createToolbarButton = function(img, title, fn, size)
  11357. {
  11358. size = (size != null) ? size : 24;
  11359. var btn = document.createElement('a');
  11360. btn.className = 'geToolbarButton geAdaptiveAsset';
  11361. btn.setAttribute('title', title);
  11362. btn.style.backgroundImage = 'url(' + img + ')';
  11363. btn.style.backgroundPosition = 'center center';
  11364. btn.style.backgroundRepeat = 'no-repeat';
  11365. btn.style.backgroundSize = '100% 100%';
  11366. btn.style.display = 'inline-block';
  11367. btn.style.cursor = 'pointer';
  11368. btn.style.marginLeft = '6px';
  11369. btn.style.width = size + 'px';
  11370. btn.style.height = size + 'px';
  11371. btn.style.flexShrink = '0';
  11372. btn.style.flexGrow = '0';
  11373. if (fn != null)
  11374. {
  11375. // Prevents focus
  11376. mxEvent.addListener(btn, (mxClient.IS_POINTER) ? 'pointerdown' : 'mousedown',
  11377. mxUtils.bind(this, function(evt)
  11378. {
  11379. evt.preventDefault();
  11380. }));
  11381. mxEvent.addListener(btn, 'click', function(evt)
  11382. {
  11383. if (btn.getAttribute('disabled') != 'disabled')
  11384. {
  11385. fn(evt);
  11386. }
  11387. mxEvent.consume(evt);
  11388. });
  11389. }
  11390. return btn;
  11391. };
  11392. /**
  11393. * Overrides image dialog to add image search and Google+.
  11394. */
  11395. EditorUi.prototype.createMenuItem = function(key, img, ignoreState)
  11396. {
  11397. var action = this.actions.get(key);
  11398. var fn = (action != null) ? action.funct : null;
  11399. var btn = this.createToolbarButton(img, mxResources.get(key) +
  11400. ((action != null && action.shortcut != null) ? ' (' +
  11401. action.shortcut + ')' : ''), fn);
  11402. if (action != null)
  11403. {
  11404. if (!ignoreState)
  11405. {
  11406. function updateState()
  11407. {
  11408. if (action.isEnabled())
  11409. {
  11410. btn.removeAttribute('disabled');
  11411. btn.style.cursor = 'pointer';
  11412. }
  11413. else
  11414. {
  11415. btn.setAttribute('disabled', 'disabled');
  11416. btn.style.cursor = 'default';
  11417. }
  11418. btn.style.opacity = (action.isEnabled()) ? '' : '0.2';
  11419. };
  11420. this.editor.graph.addListener('enabledChanged', updateState);
  11421. action.addListener('stateChanged', updateState);
  11422. updateState();
  11423. }
  11424. }
  11425. return btn;
  11426. };
  11427. /**
  11428. * Overrides image dialog to add image search and Google+.
  11429. */
  11430. EditorUi.prototype.createFormatWindow = function()
  11431. {
  11432. if (this.formatWindow == null)
  11433. {
  11434. var x = Math.max(10, this.diagramContainer.parentNode.clientWidth - 256);
  11435. var y = 60;
  11436. var h = (urlParams['embedInline'] == '1') ? 580 :
  11437. ((urlParams['sketch'] == '1') ? 580 : Math.min(566,
  11438. this.editor.graph.container.clientHeight - 10));
  11439. this.formatWindow = new WrapperWindow(this, mxResources.get('format'), x, y, 240, h,
  11440. mxUtils.bind(this, function(container)
  11441. {
  11442. container.appendChild(this.formatContainer);
  11443. }));
  11444. this.formatWindow.window.addListener(mxEvent.SHOW, mxUtils.bind(this, function()
  11445. {
  11446. this.formatWindow.window.fit();
  11447. }));
  11448. var toggleMinimized = this.formatWindow.window.toggleMinimized;
  11449. var w = 240;
  11450. this.formatWindow.window.toggleMinimized = function()
  11451. {
  11452. toggleMinimized.apply(this, arguments);
  11453. if (this.minimized)
  11454. {
  11455. w = parseInt(this.div.style.width);
  11456. this.div.style.width = '140px';
  11457. this.table.style.width = '140px';
  11458. this.div.style.left = (parseInt(this.div.style.left) + w - 140) + 'px';
  11459. }
  11460. else
  11461. {
  11462. this.div.style.width = w + 'px';
  11463. this.table.style.width = this.div.style.width;
  11464. this.div.style.left = (Math.max(0, parseInt(this.div.style.left) - w + 140)) + 'px';
  11465. }
  11466. this.fit();
  11467. };
  11468. mxEvent.addListener(this.formatWindow.window.title, 'dblclick', mxUtils.bind(this, function(evt)
  11469. {
  11470. if (mxEvent.getSource(evt) == this.formatWindow.window.title)
  11471. {
  11472. this.formatWindow.window.toggleMinimized();
  11473. }
  11474. }));
  11475. this.formatWindow.window.minimumSize = new mxRectangle(0, 0, 240, 80);
  11476. // Sets initial state for format window
  11477. if (Editor.currentTheme == 'sketch')
  11478. {
  11479. var iw = window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth;
  11480. var ih = window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight;
  11481. if (iw < 1200 || ih < 708)
  11482. {
  11483. this.formatWindow.window.toggleMinimized();
  11484. }
  11485. }
  11486. else
  11487. {
  11488. this.formatWindow.window.setVisible(false);
  11489. }
  11490. }
  11491. };
  11492. /**
  11493. *
  11494. */
  11495. var editorUiToggleFormatPanel = EditorUi.prototype.toggleFormatPanel;
  11496. EditorUi.prototype.toggleFormatPanel = function(visible)
  11497. {
  11498. var wnd = this.formatWindow;
  11499. if (wnd != null)
  11500. {
  11501. wnd.window.setVisible((visible != null) ? visible :
  11502. !this.isFormatPanelVisible());
  11503. }
  11504. else
  11505. {
  11506. editorUiToggleFormatPanel.apply(this, arguments);
  11507. }
  11508. };
  11509. /**
  11510. *
  11511. */
  11512. EditorUi.prototype.toggleShapesPanel = function(visible)
  11513. {
  11514. if (this.isShapesPanelVisible() != visible)
  11515. {
  11516. var size = EditorUi.prototype.hsplitPosition;
  11517. // On smaller screens this is set to 0
  11518. if (size == 0)
  11519. {
  11520. size = this.defaultSidebarWidth;
  11521. }
  11522. var x = this.hsplitPosition;
  11523. var doRefresh = mxUtils.bind(this, function()
  11524. {
  11525. this.hsplitPosition = tmp;
  11526. this.refresh();
  11527. this.diagramContainer.scrollLeft -= x - this.hsplitPosition;
  11528. });
  11529. var tmp = (visible) ? size : 0;
  11530. var delay = 0.3;
  11531. mxUtils.setPrefixedStyle(this.sidebarContainer.style, 'transform', (tmp == 0) ? 'translateX(0)' : 'translateX(-100%)');
  11532. if (tmp != 0)
  11533. {
  11534. doRefresh();
  11535. }
  11536. window.setTimeout(mxUtils.bind(this, function()
  11537. {
  11538. mxUtils.setPrefixedStyle(this.sidebarContainer.style, 'transform', (tmp == 0) ? 'translateX(-100%)' : 'translateX(0)');
  11539. mxUtils.setPrefixedStyle(this.sidebarContainer.style, 'transition', 'transform ' + delay + 's ease-in-out');
  11540. mxUtils.setPrefixedStyle(this.sidebarContainer.style, 'transform-origin', 'top left');
  11541. window.setTimeout(mxUtils.bind(this, function()
  11542. {
  11543. mxUtils.setPrefixedStyle(this.sidebarContainer.style, 'transition', null);
  11544. mxUtils.setPrefixedStyle(this.sidebarContainer.style, 'transform', null);
  11545. mxUtils.setPrefixedStyle(this.sidebarContainer.style, 'transform-origin', null);
  11546. if (tmp == 0)
  11547. {
  11548. doRefresh();
  11549. }
  11550. }), delay * 1000);
  11551. }), 10);
  11552. }
  11553. };
  11554. /**
  11555. *
  11556. */
  11557. EditorUi.prototype.isShapesPanelVisible = function()
  11558. {
  11559. return this.hsplitPosition > 0;
  11560. };
  11561. /**
  11562. *
  11563. */
  11564. var editorUiIsFormatPanelVisible = EditorUi.prototype.isFormatPanelVisible;
  11565. EditorUi.prototype.isFormatPanelVisible = function()
  11566. {
  11567. var wnd = this.formatWindow;
  11568. if (wnd != null)
  11569. {
  11570. return wnd.window.isVisible();
  11571. }
  11572. else
  11573. {
  11574. return editorUiIsFormatPanelVisible.apply(this, arguments);
  11575. }
  11576. };
  11577. var editorUiRefresh = EditorUi.prototype.refresh;
  11578. /**
  11579. * Changes refresh to only update the diagram container in sketch mode.
  11580. */
  11581. EditorUi.prototype.refresh = function(sizeDidChange)
  11582. {
  11583. if (this.sketchWrapperElt != null && this.sketchWrapperElt.parentNode != null)
  11584. {
  11585. sizeDidChange = (sizeDidChange != null) ? sizeDidChange : true;
  11586. if (urlParams['embedInline'] != '1')
  11587. {
  11588. var w = this.container.clientWidth;
  11589. var h = this.container.clientHeight;
  11590. var off = this.zeroOffset;
  11591. var x = off.x;
  11592. var y = off.y;
  11593. if (this.container == document.body)
  11594. {
  11595. w = document.body.clientWidth || document.documentElement.clientWidth;
  11596. h = document.documentElement.clientHeight;
  11597. }
  11598. off = this.getDiagramContainerOffset();
  11599. x = off.x;
  11600. y = off.y;
  11601. if (Editor.currentTheme == 'simple')
  11602. {
  11603. y += this.sketchMainMenuElt.offsetHeight;
  11604. }
  11605. this.diagramContainer.style.top = y + 'px';
  11606. this.diagramContainer.style.bottom = '0';
  11607. if (Editor.currentTheme == 'simple')
  11608. {
  11609. this.hsplit.style.top = this.sketchMainMenuElt.offsetHeight + 'px';
  11610. this.sidebarContainer.style.top = this.hsplit.style.top;
  11611. this.formatContainer.style.top = this.hsplit.style.top;
  11612. var iw = window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth;
  11613. var effHsplitPosition = Math.max(0, Math.min(iw - this.formatWidth,
  11614. Math.min(this.hsplitPosition, w - this.splitSize - 40)));
  11615. this.sidebarContainer.style.width = effHsplitPosition + 'px';
  11616. this.diagramContainer.style.left = (effHsplitPosition + x) + 'px';
  11617. this.tabContainer.style.left = effHsplitPosition + 'px';
  11618. this.tabContainer.style.right = this.formatWidth + 'px';
  11619. this.sketchMainMenuElt.style.left = '0px';
  11620. this.sketchMainMenuElt.style.right = '0px';
  11621. this.hsplit.style.left = effHsplitPosition + 'px';
  11622. this.diagramContainer.style.right = this.formatWidth + 'px';
  11623. this.formatContainer.style.width = this.formatWidth + 'px';
  11624. this.hsplit.style.bottom = this.tabContainer.offsetHeight + 'px';
  11625. this.diagramContainer.style.bottom = this.hsplit.style.bottom;
  11626. this.checkTabScrollerOverflow();
  11627. }
  11628. else
  11629. {
  11630. this.diagramContainer.style.left = off.x + 'px';
  11631. this.diagramContainer.style.right = '0';
  11632. }
  11633. }
  11634. if (sizeDidChange)
  11635. {
  11636. this.editor.graph.sizeDidChange();
  11637. }
  11638. }
  11639. else
  11640. {
  11641. editorUiRefresh.apply(this, arguments);
  11642. }
  11643. };
  11644. /**
  11645. *
  11646. */
  11647. EditorUi.prototype.createShapesPanel = function(container)
  11648. {
  11649. var css = 'position:absolute;border-width:1px;cusor:pointer;border-style:none;' +
  11650. 'height:24px;bottom:0px;text-align:center;padding:8px 6px 0 6px;border-top-style:solid;' +
  11651. 'width:50%;height:32px;box-sizing:border-box;font-size:11px;';
  11652. var addMenu = mxUtils.bind(this, function(id)
  11653. {
  11654. var elt = this.createMenu(id, null, 'geTitle');
  11655. elt.style.cssText = css;
  11656. container.appendChild(elt);
  11657. return elt;
  11658. });
  11659. if (Editor.enableCustomLibraries && (urlParams['embed'] != '1' || urlParams['libraries'] == '1'))
  11660. {
  11661. // Defined in native apps together with openLibrary
  11662. if (this.actions.get('newLibrary') != null)
  11663. {
  11664. var div = document.createElement('div');
  11665. div.style.cssText = css;
  11666. div.className = 'geTitle';
  11667. mxUtils.write(div, mxResources.get('newLibrary'));
  11668. container.appendChild(div);
  11669. mxEvent.addListener(div, 'click', this.actions.get('newLibrary').funct);
  11670. var div = div.cloneNode(false);
  11671. div.style.left = '50%';
  11672. div.style.borderLeftStyle = 'solid';
  11673. mxUtils.write(div, mxResources.get('openLibrary'));
  11674. container.appendChild(div);
  11675. mxEvent.addListener(div, 'click', this.actions.get('openLibrary').funct);
  11676. }
  11677. else
  11678. {
  11679. var elt = addMenu('newLibrary');
  11680. elt.style.fontSize = '11px';
  11681. elt.style.left = '0';
  11682. var elt = addMenu('openLibraryFrom');
  11683. elt.style.borderLeftStyle = 'solid';
  11684. elt.style.fontSize = '11px';
  11685. elt.style.left = '50%';
  11686. }
  11687. }
  11688. container.appendChild(this.sidebarContainer);
  11689. container.style.overflow = 'hidden';
  11690. };
  11691. /**
  11692. * Overrides image dialog to add image search and Google+.
  11693. */
  11694. EditorUi.prototype.createShapesWindow = function()
  11695. {
  11696. if (this.sidebarWindow == null)
  11697. {
  11698. var w = Math.min(this.diagramContainer.parentNode.clientWidth - 10, 218);
  11699. var h = (urlParams['embedInline'] == '1') ? 650 :
  11700. Math.min(this.diagramContainer.parentNode.clientHeight, 650);
  11701. var simpleTheme = Editor.currentTheme == 'simple' ||
  11702. Editor.currentTheme == 'sketch';
  11703. this.sidebarWindow = new WrapperWindow(this, mxResources.get('shapes'),
  11704. (simpleTheme && urlParams['embedInline'] != '1') ? 66 : 10,
  11705. (simpleTheme && urlParams['embedInline'] != '1') ?
  11706. Math.max(30, (this.diagramContainer.parentNode.clientHeight - h) / 2) : 56,
  11707. w - 6, h - 6, mxUtils.bind(this, function(container)
  11708. {
  11709. this.createShapesPanel(container);
  11710. }));
  11711. this.sidebarWindow.window.addListener(mxEvent.SHOW, mxUtils.bind(this, function()
  11712. {
  11713. this.sidebarWindow.window.fit();
  11714. }));
  11715. this.sidebarWindow.window.minimumSize = new mxRectangle(0, 0, 90, 90);
  11716. this.sidebarWindow.window.setVisible(false);
  11717. }
  11718. };
  11719. /**
  11720. * Overrides image dialog to add image search and Google+.
  11721. */
  11722. EditorUi.prototype.setSketchMode = function(value)
  11723. {
  11724. if (this.spinner.spin(document.body, mxResources.get('working') + '...'))
  11725. {
  11726. window.setTimeout(mxUtils.bind(this, function()
  11727. {
  11728. this.spinner.stop();
  11729. this.doSetSketchMode(value);
  11730. // Persist setting
  11731. if (urlParams['rough'] == null)
  11732. {
  11733. mxSettings.settings.sketchMode = value;
  11734. mxSettings.save();
  11735. }
  11736. this.fireEvent(new mxEventObject('sketchModeChanged'));
  11737. }), 0);
  11738. }
  11739. };
  11740. /**
  11741. * Dynamic change of dark mode.
  11742. */
  11743. Editor.createMinimalCss = function()
  11744. {
  11745. // Dark mode styles
  11746. return (Editor.isDarkMode() ?
  11747. 'html body .geMenubarContainer .geMenuItem .geMenuItem, html body .geMenubarContainer a.geMenuItem { color: #353535; }' +
  11748. 'html body .geToolbarContainer .geMenuItem, html body .geToolbarContainer .geToolbarButton, ' +
  11749. 'html body .geMenubarContainer .geMenuItem .geMenuItem, html body .geMenubarContainer a.geMenuItem,' +
  11750. 'html body .geMenubarContainer .geToolbarButton { filter: invert(1); }' +
  11751. 'html > body.geEditor > div > a.geItem { background-color: ' + Editor.darkColor + '; color: #cccccc; border-color: #505759; }' +
  11752. 'html body .mxCellEditor { color: #f0f0f0; }'
  11753. :
  11754. // Non-dark mode styles
  11755. 'div.diagramContainer button.gePrimaryBtn, .mxWindow button.gePrimaryBtn, .geDialog button.gePrimaryBtn, html body .gePrimaryBtn ' +
  11756. '{ background: #29b6f2 !important; color: #fff !important; border: none !important; box-shadow: none !important; }' +
  11757. 'html body .gePrimaryBtn:hover:not([disabled]) { background: #12a2e0 !important; }'
  11758. ) +
  11759. // End of custom styles
  11760. 'html body .geStatus { overflow: hidden; text-overflow: ellipsis; }' +
  11761. 'html body .geStatus > *:not([class]) { vertical-align:top; }' +
  11762. 'html > body > div > a.geItem { background-color: #ffffff; color: #707070; border-top: 1px solid lightgray; border-left: 1px solid lightgray; }' +
  11763. 'html body .mxWindow { z-index: 3; font-size: 12px; }' +
  11764. 'html body table.mxWindow { font-size: 12px; }' +
  11765. 'html body button.geBtn:active { opacity: 0.6; }' +
  11766. 'html body a.geMenuItem { opacity: 0.75; cursor: pointer; user-select: none; }' +
  11767. 'html body a.geMenuItem[disabled] { opacity: 0.2; }' +
  11768. 'html body a.geMenuItem[disabled]:active { opacity: 0.2; }' +
  11769. 'html body a.geMenuItem:active { opacity: 0.2; }' +
  11770. 'html body .geToolbarButton:active { opacity: 0.15; }' +
  11771. 'html body .geStatus:active { opacity: 0.5; }' +
  11772. '.geStatus > div { box-sizing: border-box; max-width: 100%; text-overflow: ellipsis; }' +
  11773. 'html table.mxPopupMenu tr.mxPopupMenuItemHover:active { opacity: 0.7; }' +
  11774. 'html body .mxWindow input[type="checkbox"] {padding: 0px; }' +
  11775. '.mxWindow button, .geDialog select, .mxWindow select { display:inline-block; }' +
  11776. 'html body .mxWindow .geColorBtn, html body .geDialog .geColorBtn { background: none; }' +
  11777. 'html body div.diagramContainer button:active, html body .mxWindow button:active, html body .geDialog button:active { opacity: 0.6; }' +
  11778. '.geBtn button { min-width:72px !important; }' +
  11779. 'div.geToolbarContainer a.geButton { margin:0px; padding: 0 2px 4px 2px; } ' +
  11780. 'html body div.geToolbarContainer a.geColorBtn { margin: 2px; } ' +
  11781. 'table.mxWindow td.mxWindowPane button.geColorBtn { padding:0px; box-sizing: border-box; }' +
  11782. 'html body .geMenuItem { font-size:14px; text-decoration: none; font-weight: normal; padding: 6px 10px 6px 10px; border: none; border-radius: 5px; color: #353535; box-shadow: inset 0 0 0 1px rgba(0,0,0,.11), inset 0 -1px 0 0 rgba(0,0,0,.08), 0 1px 2px 0 rgba(0,0,0,.04); ' + (EditorUi.isElectronApp? 'app-region: no-drag; ' : '') + '}' +
  11783. 'div.mxWindow td.mxWindowPane button { background-image: none; float: none; }' +
  11784. 'html div.geVerticalHandle { position:absolute;bottom:0px;left:50%;cursor:row-resize;width:11px;height:11px;background:white;margin-bottom:-6px; margin-left:-6px; border: none; border-radius: 6px; box-shadow: inset 0 0 0 1px rgba(0,0,0,.11), inset 0 -1px 0 0 rgba(0,0,0,.08), 0 1px 2px 0 rgba(0,0,0,.04); }' +
  11785. 'html div.mxRubberband { border:1px solid; border-color: #29b6f2 !important; background:rgba(41,182,242,0.4) !important; } ' +
  11786. 'html body div.mxPopupMenu { border-radius:5px; border:1px solid #c0c0c0; padding:5px 0 5px 0; box-shadow: 0px 4px 17px -4px rgba(96,96,96,1); } ' +
  11787. 'html table.mxPopupMenu td.mxPopupMenuItem { color: ' + (Editor.isDarkMode() ? '#cccccc' : '#353535') + '; font-size: 14px; padding-top: 4px; padding-bottom: 4px; }' +
  11788. 'html table.mxPopupMenu tr.mxPopupMenuItemHover { background-color: ' + (Editor.isDarkMode() ? '#000000' : '#29b6f2') + '; }' +
  11789. 'html tr.mxPopupMenuItemHover td.mxPopupMenuItem, html tr.mxPopupMenuItemHover td.mxPopupMenuItem span { color: ' + (Editor.isDarkMode() ? '#cccccc' : '#ffffff') + ' !important; }' +
  11790. 'html tr.mxPopupMenuItem, html td.mxPopupMenuItem { transition-property: none !important; }' +
  11791. 'html body td.mxWindowTitle { padding-right: 14px; }';
  11792. };
  11793. /**
  11794. * Sets dark mode and persists the setting.
  11795. */
  11796. EditorUi.prototype.setAndPersistDarkMode = function(value)
  11797. {
  11798. var actual = value;
  11799. if (value == 'auto')
  11800. {
  11801. actual = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches;
  11802. }
  11803. this.setDarkMode(actual);
  11804. mxSettings.settings.darkMode = value;
  11805. mxSettings.save();
  11806. var theme = mxSettings.getUi();
  11807. if (urlParams['ui'] == null && value != 'auto' && theme != 'atlas' &&
  11808. theme != 'min' && theme != 'sketch' && theme != 'simple')
  11809. {
  11810. this.setCurrentTheme((!value) ? 'kennedy' : 'dark', true);
  11811. }
  11812. };
  11813. /**
  11814. * Sets dark mode and persists the setting.
  11815. */
  11816. EditorUi.prototype.setAndPersistLanguage = function(value)
  11817. {
  11818. mxSettings.setLanguage(value);
  11819. mxSettings.save();
  11820. // Shows dialog in new language
  11821. mxClient.language = value;
  11822. mxResources.loadDefaultBundle = false;
  11823. mxResources.add(RESOURCE_BASE);
  11824. };
  11825. /**
  11826. * Sets dark mode and persists the setting.
  11827. */
  11828. EditorUi.prototype.setAndPersistHighContrast = function(value)
  11829. {
  11830. this.setHighContrast(value);
  11831. if (Editor.isSettingsEnabled())
  11832. {
  11833. mxSettings.settings.highContrast = value;
  11834. mxSettings.save();
  11835. }
  11836. };
  11837. /**
  11838. * Sets dark mode and persists the setting.
  11839. */
  11840. EditorUi.prototype.isHighContrast = function()
  11841. {
  11842. var highContrastStylesheet = document.getElementById('high-contrast-stylesheet');
  11843. if (highContrastStylesheet != null && window.matchMedia)
  11844. {
  11845. var media = highContrastStylesheet.getAttribute('media');
  11846. return media == 'all' || (media != 'not all' &&
  11847. window.matchMedia('(forced-colors: active)').matches);
  11848. }
  11849. else
  11850. {
  11851. return false;
  11852. }
  11853. };
  11854. /**
  11855. * Sets dark mode and persists the setting.
  11856. */
  11857. EditorUi.prototype.setHighContrast = function(value)
  11858. {
  11859. var highContrastStylesheet = document.getElementById('high-contrast-stylesheet');
  11860. if (highContrastStylesheet != null && window.matchMedia)
  11861. {
  11862. var match = window.matchMedia('(forced-colors: active)').matches;
  11863. var media = highContrastStylesheet.getAttribute('media');
  11864. var on = media == 'all' || (media != 'not all' && match);
  11865. if (on && !value)
  11866. {
  11867. highContrastStylesheet.setAttribute('media', (match) ?
  11868. 'not all' : '(forced-colors: active)');
  11869. }
  11870. else if (!on && value)
  11871. {
  11872. highContrastStylesheet.setAttribute('media', (match) ?
  11873. '(forced-colors: active)' : 'all');
  11874. }
  11875. }
  11876. };
  11877. /**
  11878. * Dynamic change of dark mode.
  11879. */
  11880. EditorUi.prototype.isRulerVisible = function()
  11881. {
  11882. return this.ruler != null;
  11883. };
  11884. /**
  11885. * Dynamic change of dark mode.
  11886. */
  11887. EditorUi.prototype.setRulerVisible = function(visible)
  11888. {
  11889. var before = this.getDiagramContainerOffset();
  11890. mxSettings.setRulerOn(visible);
  11891. mxSettings.save();
  11892. if (!visible && this.ruler != null)
  11893. {
  11894. this.ruler.destroy();
  11895. this.ruler = null;
  11896. }
  11897. else if (visible && this.ruler == null)
  11898. {
  11899. this.ruler = new mxDualRuler(this, this.editor.graph.view.unit);
  11900. }
  11901. this.refresh();
  11902. this.fireEvent(new mxEventObject('rulerVisibleChanged'));
  11903. var after = this.getDiagramContainerOffset();
  11904. this.diagramContainer.scrollLeft += after.x - before.x;
  11905. this.diagramContainer.scrollTop += after.x - before.x;
  11906. };
  11907. /**
  11908. * Returns true if automatic dark mode is supported.
  11909. */
  11910. EditorUi.prototype.isAutoDarkModeSupported = function()
  11911. {
  11912. return window.matchMedia != null;
  11913. };
  11914. /**
  11915. * Returns the current state of the dark mode.
  11916. */
  11917. EditorUi.prototype.isAutoDarkMode = function(ignoreUrl)
  11918. {
  11919. return (!ignoreUrl && urlParams['dark'] == 'auto') ||
  11920. (Editor.isSettingsEnabled() && (mxSettings.settings.darkMode == 'auto' ||
  11921. (this.getServiceName() == 'draw.io' && urlParams['embed'] != '1' &&
  11922. (!this.editor.chromeless || this.editor.editable) &&
  11923. mxSettings.settings.darkMode == null)));
  11924. };
  11925. /**
  11926. * Dynamic change of dark mode.
  11927. */
  11928. EditorUi.prototype.setCssDarkModeEnabled = function(value)
  11929. {
  11930. var prev = Editor.isDarkMode() || Editor.cssDarkMode;
  11931. if (prev)
  11932. {
  11933. this.setDarkMode(false);
  11934. }
  11935. Editor.enableCssDarkMode = value;
  11936. mxSettings.settings.enableCssDarkMode = Editor.enableCssDarkMode;
  11937. mxSettings.save();
  11938. this.setDarkMode(prev);
  11939. this.fireEvent(new mxEventObject('cssDarkModeEnabledChanged'));
  11940. };
  11941. /**
  11942. * Dynamic change of dark mode.
  11943. */
  11944. EditorUi.prototype.setDarkMode = function(value)
  11945. {
  11946. this.doSetDarkMode(value, mxUtils.bind(this, function()
  11947. {
  11948. this.fireEvent(new mxEventObject('darkModeChanged'));
  11949. }), mxUtils.bind(this, function(e)
  11950. {
  11951. if (window.console != null)
  11952. {
  11953. console.error(e);
  11954. }
  11955. this.editor.setStatus(e.message);
  11956. }));
  11957. };
  11958. /**
  11959. * Creates dark mode style node.
  11960. */
  11961. EditorUi.prototype.createDarkStyle = function()
  11962. {
  11963. var darkStyle = document.createElement('link');
  11964. darkStyle.setAttribute('rel', 'stylesheet');
  11965. darkStyle.setAttribute('href', STYLE_PATH + '/dark.css');
  11966. darkStyle.setAttribute('charset', 'UTF-8');
  11967. darkStyle.setAttribute('type', 'text/css');
  11968. return darkStyle;
  11969. };
  11970. // Sets instance graph stylesheet
  11971. EditorUi.setGraphDarkMode = function(graph, container, darkMode)
  11972. {
  11973. graph.view.defaultGridColor = darkMode ?
  11974. mxGraphView.prototype.defaultDarkGridColor : mxGraphView.prototype.defaultGridColor;
  11975. graph.view.gridColor = graph.view.defaultGridColor;
  11976. graph.defaultPageBackgroundColor = (urlParams['embedInline'] == '1') ? 'transparent' :
  11977. darkMode ? Editor.darkColor : '#ffffff';
  11978. graph.defaultPageBorderColor = darkMode ? '#000000' : '#ffffff';
  11979. graph.shapeBackgroundColor = darkMode ? Editor.darkColor : '#ffffff';
  11980. graph.shapeForegroundColor = darkMode ? Editor.lightColor : '#000000';
  11981. graph.defaultThemeName = darkMode ? 'darkTheme' : 'default-style2';
  11982. graph.graphHandler.previewColor = darkMode ? '#cccccc' : 'black';
  11983. mxGraphHandler.prototype.previewColor = graph.graphHandler.previewColor;
  11984. if (container != null)
  11985. {
  11986. container.style.backgroundColor = (urlParams['embedInline'] == '1') ? 'transparent' :
  11987. (darkMode ? Editor.darkColor : '#ffffff');
  11988. }
  11989. graph.loadStylesheet();
  11990. // Sets global vars
  11991. Graph.prototype.defaultPageBackgroundColor = graph.defaultPageBackgroundColor;
  11992. Graph.prototype.defaultPageBorderColor = graph.defaultPageBorderColor;
  11993. Graph.prototype.shapeBackgroundColor = graph.shapeBackgroundColor;
  11994. Graph.prototype.shapeForegroundColor = graph.shapeForegroundColor;
  11995. Graph.prototype.defaultThemeName = graph.defaultThemeName;
  11996. };
  11997. /**
  11998. * Dynamic change of dark mode.
  11999. */
  12000. EditorUi.prototype.setCssDarkMode = function(value)
  12001. {
  12002. var node = (mxUtils.isAncestorNode(document.body, this.container)) ?
  12003. this.container : this.editor.graph.container;
  12004. if (node != null)
  12005. {
  12006. if (value)
  12007. {
  12008. node.classList.add('geDarkMode');
  12009. }
  12010. else
  12011. {
  12012. node.classList.remove('geDarkMode');
  12013. }
  12014. }
  12015. };
  12016. /**
  12017. * Dynamic change of dark mode.
  12018. */
  12019. EditorUi.prototype.doSetDarkMode = function(value, success, error)
  12020. {
  12021. if (Editor.enableCssDarkMode)
  12022. {
  12023. this.setCssDarkMode(value);
  12024. Editor.cssDarkMode = value;
  12025. success();
  12026. }
  12027. else
  12028. {
  12029. var delayed = mxUtils.bind(this, function()
  12030. {
  12031. if (Editor.darkMode != value)
  12032. {
  12033. var graph = this.editor.graph;
  12034. Editor.darkMode = value;
  12035. // Sets instance vars and graph stylesheet
  12036. this.spinner.opts.color = Editor.isDarkMode() ? '#c0c0c0' : '#000';
  12037. EditorUi.setGraphDarkMode(graph, document.body, Editor.isDarkMode());
  12038. // Destroys windows with code for dark mode
  12039. if (this.actions.layersWindow != null)
  12040. {
  12041. var wasVisible = this.actions.layersWindow.window.isVisible();
  12042. this.actions.layersWindow.window.setVisible(false);
  12043. this.actions.layersWindow.destroy();
  12044. this.actions.layersWindow = null;
  12045. if (wasVisible)
  12046. {
  12047. window.setTimeout(this.actions.get('layers').funct, 0);
  12048. }
  12049. }
  12050. if (this.menus != null && this.menus.commentsWindow != null)
  12051. {
  12052. this.menus.commentsWindow.window.setVisible(false);
  12053. this.menus.commentsWindow.destroy();
  12054. this.menus.commentsWindow = null;
  12055. }
  12056. if (this.ruler != null)
  12057. {
  12058. this.ruler.updateStyle();
  12059. }
  12060. if (window.StyleFormatPanel != null)
  12061. {
  12062. StyleFormatPanel.prototype.defaultStrokeColor = Editor.isDarkMode() ? '#cccccc' : 'black';
  12063. }
  12064. if (window.Format != null)
  12065. {
  12066. Format.inactiveTabBackgroundColor = Editor.isDarkMode() ? '#000000' : '#e4e4e4';
  12067. }
  12068. mxConstants.DROP_TARGET_COLOR = Editor.isDarkMode() ? '#00ff00' : '#0000FF';
  12069. Editor.checkmarkImage = (Editor.isDarkMode() && mxClient.IS_SVG) ?
  12070. Editor.darkCheckmarkImage : Editor.lightCheckmarkImage;
  12071. // Updates CSS
  12072. if (this.sketchStyleElt != null)
  12073. {
  12074. this.sketchStyleElt.innerHTML = Editor.createMinimalCss();
  12075. }
  12076. else if (Editor.styleElt != null)
  12077. {
  12078. Editor.styleElt.innerHTML = Editor.createMinimalCss();
  12079. }
  12080. }
  12081. var node = (mxUtils.isAncestorNode(document.body, this.container)) ?
  12082. this.container : this.editor.graph.container;
  12083. if (node != null)
  12084. {
  12085. if (Editor.isDarkMode())
  12086. {
  12087. node.classList.add('geLegacyDarkMode');
  12088. }
  12089. else
  12090. {
  12091. node.classList.remove('geLegacyDarkMode');
  12092. }
  12093. }
  12094. // Adds or removes link to CSS
  12095. if (Editor.isDarkMode())
  12096. {
  12097. if (this.darkStyle.parentNode == null)
  12098. {
  12099. var head = document.getElementsByTagName('head')[0];
  12100. head.appendChild(this.darkStyle);
  12101. }
  12102. }
  12103. else if (this.darkStyle.parentNode != null)
  12104. {
  12105. this.darkStyle.parentNode.removeChild(this.darkStyle);
  12106. }
  12107. success();
  12108. });
  12109. if (this.darkStyle != null)
  12110. {
  12111. delayed();
  12112. }
  12113. else
  12114. {
  12115. var darkStyle = this.createDarkStyle();
  12116. this.createTimeout(null, mxUtils.bind(this, function(timeout)
  12117. {
  12118. darkStyle.onerror = mxUtils.bind(this, function(e)
  12119. {
  12120. if (timeout.clear())
  12121. {
  12122. error(new Error(mxResources.get('errorLoadingFile') +
  12123. ' ' + darkStyle.getAttribute('href')));
  12124. }
  12125. });
  12126. darkStyle.onload = mxUtils.bind(this, function()
  12127. {
  12128. if (timeout.clear())
  12129. {
  12130. this.darkStyle = darkStyle;
  12131. delayed();
  12132. }
  12133. });
  12134. var head = document.getElementsByTagName('head')[0];
  12135. head.appendChild(darkStyle);
  12136. }), mxUtils.bind(this, function()
  12137. {
  12138. error(new Error(mxResources.get('timeout') +
  12139. ' ' + darkStyle.getAttribute('href')));
  12140. }));
  12141. };
  12142. }
  12143. };
  12144. /**
  12145. * Changes Editor.pagesVisible.
  12146. */
  12147. EditorUi.prototype.setPagesVisible = function(value)
  12148. {
  12149. if (Editor.pagesVisible != value)
  12150. {
  12151. Editor.pagesVisible = value;
  12152. // Persist setting
  12153. mxSettings.settings.pagesVisible = value;
  12154. mxSettings.save();
  12155. this.fireEvent(new mxEventObject('pagesVisibleChanged'));
  12156. }
  12157. };
  12158. /**
  12159. * Changes Sidebar.sidebarTitles.
  12160. */
  12161. EditorUi.prototype.setSidebarTitles = function(value, remember)
  12162. {
  12163. if (this.sidebar.sidebarTitles != value)
  12164. {
  12165. this.sidebar.sidebarTitles = value;
  12166. this.sidebar.refresh();
  12167. // Persist setting
  12168. if (Editor.isSettingsEnabled() && remember)
  12169. {
  12170. mxSettings.settings.sidebarTitles = value;
  12171. mxSettings.save();
  12172. }
  12173. this.fireEvent(new mxEventObject('sidebarTitlesChanged'));
  12174. }
  12175. };
  12176. /**
  12177. * Dynamic change of dark mode.
  12178. */
  12179. EditorUi.prototype.setInlineFullscreen = function(value)
  12180. {
  12181. if (Editor.inlineFullscreen != value)
  12182. {
  12183. this.diagramContainer.setAttribute('data-scrollState',
  12184. JSON.stringify(this.saveScrollState()));
  12185. // Send request for fullscreen to parent
  12186. var parent = window.opener || window.parent;
  12187. parent.postMessage(JSON.stringify({
  12188. event: 'resize',
  12189. fullscreen: value,
  12190. rect: this.diagramContainer.getBoundingClientRect()
  12191. }), '*');
  12192. }
  12193. };
  12194. /**
  12195. * Invokes to update the UI after a size change in inline embed mode.
  12196. */
  12197. EditorUi.prototype.inlineSizeChanged = function()
  12198. {
  12199. var footer = this.sketchFooterMenuElt;
  12200. var toolbar = this.sketchMainMenuElt;
  12201. var picker = this.sketchPickerMenuElt;
  12202. var graph = this.editor.graph;
  12203. if (Editor.inlineFullscreen)
  12204. {
  12205. toolbar.style.left = '10px';
  12206. toolbar.style.top = '10px';
  12207. picker.style.left = '10px';
  12208. picker.style.top = '60px';
  12209. footer.style.top = '10px';
  12210. footer.style.right = '12px';
  12211. footer.style.left = '';
  12212. if (this.diagramContainer.getAttribute('data-bounds') == null)
  12213. {
  12214. this.diagramContainer.setAttribute('data-bounds', this.diagramContainer.style.top + ' ' +
  12215. this.diagramContainer.style.left + ' ' + this.diagramContainer.style.width + ' ' +
  12216. this.diagramContainer.style.height);
  12217. this.diagramContainer.style.top = '0px';
  12218. this.diagramContainer.style.left = '0px';
  12219. this.diagramContainer.style.bottom = '0px';
  12220. this.diagramContainer.style.right = '0px';
  12221. this.diagramContainer.style.width = '';
  12222. this.diagramContainer.style.height = '';
  12223. }
  12224. }
  12225. else
  12226. {
  12227. var bounds = this.diagramContainer.getAttribute('data-bounds');
  12228. if (bounds != null)
  12229. {
  12230. this.diagramContainer.removeAttribute('data-bounds');
  12231. var gb = graph.getGraphBounds();
  12232. var tokens = bounds.split(' ');
  12233. var ds = mxUtils.getDocumentSize();
  12234. this.diagramContainer.style.top = tokens[0];
  12235. this.diagramContainer.style.left = tokens[1];
  12236. var w = parseInt(tokens[2]);
  12237. var h = parseInt(tokens[3]);
  12238. w = Math.min((this.minInlineWidth != null) ? Math.max(
  12239. this.minInlineWidth, w) : w, ds.width - 80);
  12240. h = Math.min((this.minInlineHeight != null) ? Math.max(
  12241. this.minInlineHeight, h) : h, ds.height - 80);
  12242. this.diagramContainer.style.width = w + 'px';
  12243. this.diagramContainer.style.height = h + 'px';
  12244. this.diagramContainer.style.bottom = '';
  12245. this.diagramContainer.style.right = '';
  12246. var parent = window.opener || window.parent;
  12247. parent.postMessage(JSON.stringify({
  12248. event: 'resize',
  12249. rect: this.diagramContainer.getBoundingClientRect()
  12250. }), '*');
  12251. }
  12252. toolbar.style.left = this.diagramContainer.offsetLeft + 'px';
  12253. toolbar.style.top = (this.diagramContainer.offsetTop -
  12254. toolbar.offsetHeight - 4) + 'px';
  12255. picker.style.display = '';
  12256. picker.style.left = (this.diagramContainer.offsetLeft -
  12257. picker.offsetWidth - 4) + 'px';
  12258. picker.style.top = this.diagramContainer.offsetTop + 'px';
  12259. footer.style.left = (this.diagramContainer.offsetLeft +
  12260. this.diagramContainer.offsetWidth -
  12261. footer.offsetWidth) + 'px';
  12262. footer.style.top = toolbar.style.top;
  12263. footer.style.right = '';
  12264. this.bottomResizer.style.left = (this.diagramContainer.offsetLeft +
  12265. (this.diagramContainer.offsetWidth -
  12266. this.bottomResizer.offsetWidth) / 2) + 'px';
  12267. this.bottomResizer.style.top = (this.diagramContainer.offsetTop +
  12268. this.diagramContainer.offsetHeight -
  12269. this.bottomResizer.offsetHeight / 2 - 1) + 'px';
  12270. this.rightResizer.style.left = (this.diagramContainer.offsetLeft +
  12271. this.diagramContainer.offsetWidth -
  12272. this.rightResizer.offsetWidth / 2 - 1) + 'px';
  12273. this.rightResizer.style.top = (this.diagramContainer.offsetTop +
  12274. (this.diagramContainer.offsetHeight -
  12275. this.bottomResizer.offsetHeight) / 2) + 'px';
  12276. }
  12277. this.bottomResizer.style.visibility = (Editor.inlineFullscreen) ? 'hidden' : '';
  12278. this.rightResizer.style.visibility = this.bottomResizer.style.visibility;
  12279. toolbar.style.visibility = '';
  12280. footer.style.visibility = '';
  12281. };
  12282. /**
  12283. * Dynamic change of dark mode.
  12284. */
  12285. EditorUi.prototype.doSetSketchMode = function(value)
  12286. {
  12287. if (Editor.sketchMode != value)
  12288. {
  12289. Editor.sketchMode = value;
  12290. this.updateDefaultStyles();
  12291. }
  12292. };
  12293. /**
  12294. * Overrides image dialog to add image search and Google+.
  12295. */
  12296. EditorUi.prototype.updateDefaultStyles = function()
  12297. {
  12298. function setStyle(style, key, value)
  12299. {
  12300. style[key] = value;
  12301. };
  12302. var graph = this.editor.graph;
  12303. graph.defaultVertexStyle = mxUtils.clone(Graph.prototype.defaultVertexStyle);
  12304. graph.defaultEdgeStyle = mxUtils.clone(Graph.prototype.defaultEdgeStyle);
  12305. // Skipped if defaultVertexStyle or defaultEdgeStyle configured
  12306. if (Editor.config == null || (Editor.config.defaultVertexStyle == null &&
  12307. Editor.config.defaultEdgeStyle == null))
  12308. {
  12309. if (Editor.currentTheme == 'sketch')
  12310. {
  12311. graph.vertexFontSize = 20;
  12312. graph.edgeFontSize = graph.vertexFontSize - 4;
  12313. }
  12314. else if (Editor.currentTheme == 'simple')
  12315. {
  12316. graph.vertexFontSize = 16;
  12317. graph.edgeFontSize = graph.vertexFontSize - 4;
  12318. }
  12319. else
  12320. {
  12321. graph.vertexFontSize = Graph.prototype.vertexFontSize;
  12322. graph.edgeFontSize = Graph.prototype.edgeFontSize;
  12323. }
  12324. // Font size for edges is applied to all inserted edges
  12325. setStyle(graph.defaultEdgeStyle, 'fontSize', graph.edgeFontSize);
  12326. }
  12327. // Skipped if defaultEdgeStyle configured
  12328. if (Editor.config == null || Editor.config.defaultEdgeStyle == null)
  12329. {
  12330. if (Editor.currentTheme == 'simple')
  12331. {
  12332. setStyle(graph.defaultEdgeStyle, 'edgeStyle', 'none');
  12333. setStyle(graph.defaultEdgeStyle, 'curved', '1');
  12334. setStyle(graph.defaultEdgeStyle, 'rounded', '0');
  12335. setStyle(graph.defaultEdgeStyle, 'endSize', '8');
  12336. setStyle(graph.defaultEdgeStyle, 'startSize', '8');
  12337. }
  12338. else if (Editor.currentTheme == 'sketch')
  12339. {
  12340. setStyle(graph.defaultEdgeStyle, 'edgeStyle', 'none');
  12341. setStyle(graph.defaultEdgeStyle, 'curved', '1');
  12342. setStyle(graph.defaultEdgeStyle, 'rounded', '0');
  12343. setStyle(graph.defaultEdgeStyle, 'jettySize', 'auto');
  12344. setStyle(graph.defaultEdgeStyle, 'orthogonalLoop', '1');
  12345. setStyle(graph.defaultEdgeStyle, 'endArrow', 'open');
  12346. setStyle(graph.defaultEdgeStyle, 'endSize', '14');
  12347. setStyle(graph.defaultEdgeStyle, 'startSize', '14');
  12348. setStyle(graph.defaultEdgeStyle, 'sourcePerimeterSpacing', '8');
  12349. setStyle(graph.defaultEdgeStyle, 'targetPerimeterSpacing', '8');
  12350. }
  12351. }
  12352. // Skipped if defaultFonts configured
  12353. if (Editor.config == null || Editor.config.defaultFonts == null)
  12354. {
  12355. // Skipped if defaultFonts, defaultVertexStyle or defaultEdgeStyle configured
  12356. if (Editor.config == null || (Editor.config.defaultVertexStyle == null &&
  12357. Editor.config.defaultEdgeStyle == null))
  12358. {
  12359. if (Editor.currentTheme == 'sketch' || Editor.sketchMode)
  12360. {
  12361. setStyle(graph.defaultVertexStyle, 'fontFamily', Editor.sketchFontFamily);
  12362. setStyle(graph.defaultVertexStyle, 'fontSource', Editor.sketchFontSource);
  12363. setStyle(graph.defaultEdgeStyle, 'fontFamily', Editor.sketchFontFamily);
  12364. setStyle(graph.defaultEdgeStyle, 'fontSource', Editor.sketchFontSource);
  12365. }
  12366. if (Editor.sketchMode)
  12367. {
  12368. setStyle(graph.defaultVertexStyle, 'sketch', '1');
  12369. setStyle(graph.defaultVertexStyle, 'curveFitting', Editor.sketchDefaultCurveFitting);
  12370. setStyle(graph.defaultVertexStyle, 'jiggle', Editor.sketchDefaultJiggle);
  12371. setStyle(graph.defaultVertexStyle, 'hachureGap', '4');
  12372. setStyle(graph.defaultEdgeStyle, 'sketch', '1');
  12373. setStyle(graph.defaultEdgeStyle, 'curveFitting', Editor.sketchDefaultCurveFitting);
  12374. setStyle(graph.defaultEdgeStyle, 'jiggle', Editor.sketchDefaultJiggle);
  12375. setStyle(graph.defaultEdgeStyle, 'hachureGap', '4');
  12376. }
  12377. }
  12378. if (Editor.currentTheme == 'sketch')
  12379. {
  12380. this.menus.defaultFonts = Menus.prototype.defaultFonts.concat(Editor.sketchFonts);
  12381. }
  12382. else
  12383. {
  12384. this.menus.defaultFonts = Menus.prototype.defaultFonts;
  12385. }
  12386. }
  12387. graph.currentVertexStyle = mxUtils.clone(graph.defaultVertexStyle);
  12388. graph.currentEdgeStyle = mxUtils.clone(graph.defaultEdgeStyle);
  12389. this.clearDefaultStyle();
  12390. };
  12391. /**
  12392. *
  12393. */
  12394. EditorUi.prototype.getLinkTitle = function(href)
  12395. {
  12396. var title = Graph.prototype.getLinkTitle.apply(this, arguments);
  12397. if (Graph.isPageLink(href))
  12398. {
  12399. var comma = href.indexOf(',');
  12400. if (comma > 0)
  12401. {
  12402. var page = this.getPageById(href.substring(comma + 1));
  12403. if (page != null)
  12404. {
  12405. title = page.getName();
  12406. }
  12407. else
  12408. {
  12409. title = mxResources.get('pageNotFound');
  12410. }
  12411. }
  12412. }
  12413. else if (href.substring(0, 5) == 'data:')
  12414. {
  12415. title = this.getCustomLinkTitle(href);
  12416. }
  12417. return title;
  12418. };
  12419. /**
  12420. *
  12421. */
  12422. EditorUi.prototype.getCustomLinkTitle = function(href)
  12423. {
  12424. var result = mxResources.get('action');
  12425. if (href.substring(0, 17) == 'data:action/json,')
  12426. {
  12427. try
  12428. {
  12429. var link = JSON.parse(href.substring(17));
  12430. if (link != null && link.title != null)
  12431. {
  12432. result = link.title;
  12433. }
  12434. }
  12435. catch (e)
  12436. {
  12437. // ignore
  12438. }
  12439. }
  12440. return result;
  12441. };
  12442. /**
  12443. *
  12444. */
  12445. EditorUi.prototype.handleCustomLink = function(href, cell)
  12446. {
  12447. if (Graph.isPageLink(href))
  12448. {
  12449. var comma = href.indexOf(',');
  12450. var page = this.getPageById(href.substring(comma + 1));
  12451. if (page)
  12452. {
  12453. this.selectPage(page)
  12454. }
  12455. else
  12456. {
  12457. // Needs fallback for missing resource in case of viewer lightbox
  12458. throw new Error(mxResources.get('pageNotFound') || 'Page not found');
  12459. }
  12460. }
  12461. else
  12462. {
  12463. this.editor.graph.handleCustomLink(href, cell);
  12464. }
  12465. };
  12466. /**
  12467. * Creates the format panel and adds overrides.
  12468. */
  12469. EditorUi.prototype.installSettings = function()
  12470. {
  12471. if (Editor.isSettingsEnabled())
  12472. {
  12473. // Sets global switch for sketch mode
  12474. Editor.pagesVisible = mxSettings.settings.pagesVisible;
  12475. // Gets recent colors from settings
  12476. ColorDialog.recentColors = mxSettings.getRecentColors();
  12477. // Avoids overridden values for changes in
  12478. // multiple windows and updates shared values
  12479. if (isLocalStorage)
  12480. {
  12481. try
  12482. {
  12483. window.addEventListener('storage', mxUtils.bind(this, function(evt)
  12484. {
  12485. if (evt.key == mxSettings.key)
  12486. {
  12487. mxSettings.load();
  12488. // Updates values
  12489. ColorDialog.recentColors = mxSettings.getRecentColors();
  12490. this.menus.customFonts = mxSettings.getCustomFonts();
  12491. }
  12492. }), false);
  12493. }
  12494. catch (e)
  12495. {
  12496. // ignore
  12497. }
  12498. }
  12499. // Updates UI to reflect current edge style
  12500. this.fireEvent(new mxEventObject('styleChanged', 'keys', [], 'values', [], 'cells', []));
  12501. /**
  12502. * Persists custom fonts.
  12503. */
  12504. this.menus.customFonts = mxSettings.getCustomFonts();
  12505. this.addListener('customFontsChanged', mxUtils.bind(this, function(sender, evt)
  12506. {
  12507. mxSettings.setCustomFonts(this.menus.customFonts);
  12508. mxSettings.save();
  12509. }));
  12510. /**
  12511. * Persists copy on connect switch.
  12512. */
  12513. this.editor.graph.connectionHandler.setCreateTarget(mxSettings.isCreateTarget());
  12514. this.fireEvent(new mxEventObject('copyConnectChanged'));
  12515. this.addListener('copyConnectChanged', mxUtils.bind(this, function(sender, evt)
  12516. {
  12517. mxSettings.setCreateTarget(this.editor.graph.connectionHandler.isCreateTarget());
  12518. mxSettings.save();
  12519. }));
  12520. /**
  12521. * Persists default page format.
  12522. */
  12523. this.editor.graph.pageFormat = (this.editor.graph.defaultPageFormat != null) ?
  12524. this.editor.graph.defaultPageFormat : mxSettings.getPageFormat();
  12525. this.addListener('pageFormatChanged', mxUtils.bind(this, function(sender, evt)
  12526. {
  12527. mxSettings.setPageFormat(this.editor.graph.pageFormat);
  12528. mxSettings.save();
  12529. }));
  12530. /**
  12531. * Persists default grid color.
  12532. */
  12533. this.editor.graph.view.gridColor = mxSettings.getGridColor(Editor.isDarkMode());
  12534. this.editor.graph.view.defaultDarkGridColor = mxSettings.getGridColor(true);
  12535. this.editor.graph.view.defaultGridColor = mxSettings.getGridColor(false);
  12536. this.addListener('gridColorChanged', mxUtils.bind(this, function(sender, evt)
  12537. {
  12538. mxSettings.setGridColor(this.editor.graph.view.gridColor, Editor.isDarkMode());
  12539. mxSettings.save();
  12540. }));
  12541. /**
  12542. * Persists autosave switch in Chrome app.
  12543. */
  12544. if (mxClient.IS_CHROMEAPP || EditorUi.isElectronApp)
  12545. {
  12546. this.editor.addListener('autosaveChanged', mxUtils.bind(this, function(sender, evt)
  12547. {
  12548. mxSettings.setAutosave(this.editor.autosave);
  12549. mxSettings.save();
  12550. }));
  12551. this.editor.autosave = mxSettings.getAutosave();
  12552. }
  12553. if (!this.editor.chromeless || this.editor.editable)
  12554. {
  12555. /**
  12556. * Persists animations switch.
  12557. */
  12558. if (mxSettings.settings.enableAnimations != null)
  12559. {
  12560. Editor.enableAnimations = mxSettings.settings.enableAnimations;
  12561. }
  12562. this.addListener('enableAnimationsChanged', mxUtils.bind(this, function(sender, evt)
  12563. {
  12564. mxSettings.settings.enableAnimations = Editor.enableAnimations;
  12565. mxSettings.save();
  12566. }));
  12567. }
  12568. if (this.sidebar != null)
  12569. {
  12570. if (urlParams['search-shapes'] != null && this.sidebar.searchShapes != null)
  12571. {
  12572. this.sidebar.searchShapes(decodeURIComponent(urlParams['search-shapes']));
  12573. this.sidebar.showEntries('search');
  12574. }
  12575. else
  12576. {
  12577. this.sidebar.showPalette('search', mxSettings.settings.search);
  12578. /**
  12579. * Shows scratchpad if never shown.
  12580. */
  12581. if ((!this.editor.chromeless || this.editor.editable) && (mxSettings.settings.isNew ||
  12582. parseInt(mxSettings.settings.version || 0) <= 8))
  12583. {
  12584. this.toggleScratchpad();
  12585. mxSettings.save();
  12586. }
  12587. }
  12588. }
  12589. // Saves app defaults for UI
  12590. this.addListener('formatWidthChanged', function()
  12591. {
  12592. mxSettings.setFormatWidth(this.formatWidth);
  12593. mxSettings.save();
  12594. });
  12595. }
  12596. };
  12597. /**
  12598. * Copies the given cells and XML to the clipboard as an embedded image.
  12599. */
  12600. EditorUi.prototype.copyImage = function(cells, xml, scale)
  12601. {
  12602. try
  12603. {
  12604. if (navigator.clipboard != null && typeof window.ClipboardItem === 'function' &&
  12605. this.spinner.spin(document.body, mxResources.get('exporting')))
  12606. {
  12607. this.editor.exportToCanvas(mxUtils.bind(this, function(canvas, svgRoot)
  12608. {
  12609. try
  12610. {
  12611. this.spinner.stop();
  12612. // KNOWN: SVG and delayed content currently not supported
  12613. var dataUrl = this.createImageDataUri(canvas, xml, 'png');
  12614. var w = parseInt(svgRoot.getAttribute('width'));
  12615. var h = parseInt(svgRoot.getAttribute('height'));
  12616. this.writeImageToClipboard(dataUrl, w, h, mxUtils.bind(this, function(e)
  12617. {
  12618. this.handleError(e);
  12619. }));
  12620. }
  12621. catch (e)
  12622. {
  12623. this.handleError(e);
  12624. }
  12625. }), null, null, null, mxUtils.bind(this, function(e)
  12626. {
  12627. this.spinner.stop();
  12628. this.handleError(e);
  12629. }), null, null, (scale != null) ? scale : 4,
  12630. this.editor.graph.background == null ||
  12631. this.editor.graph.background == mxConstants.NONE,
  12632. null, null, null, 10, null, null, false, null,
  12633. (cells.length > 0) ? cells : null);
  12634. }
  12635. }
  12636. catch (e)
  12637. {
  12638. this.handleError(e);
  12639. }
  12640. };
  12641. /**
  12642. * Creates the format panel and adds overrides.
  12643. */
  12644. EditorUi.prototype.copyCells = function(elt, removeCells)
  12645. {
  12646. var graph = this.editor.graph;
  12647. if (!graph.isSelectionEmpty())
  12648. {
  12649. // Fixes cross-platform clipboard UTF8 issues by encoding as URI
  12650. var cells = mxUtils.sortCells(graph.model.getTopmostCells(graph.getSelectionCells()));
  12651. var xml = mxUtils.getXml(graph.encodeCells(cells));
  12652. mxUtils.setTextContent(elt, encodeURIComponent(xml));
  12653. if (removeCells)
  12654. {
  12655. graph.removeCells(cells, false);
  12656. graph.lastPasteXml = null;
  12657. }
  12658. else
  12659. {
  12660. graph.lastPasteXml = xml;
  12661. graph.pasteCounter = 0;
  12662. }
  12663. elt.focus();
  12664. document.execCommand('selectAll', false, null);
  12665. }
  12666. else
  12667. {
  12668. // Disables copy on focused element
  12669. elt.innerText = '';
  12670. }
  12671. };
  12672. /**
  12673. * Creates the format panel and adds overrides.
  12674. */
  12675. EditorUi.prototype.copyXml = function()
  12676. {
  12677. var cells = null;
  12678. if (Editor.enableNativeCipboard)
  12679. {
  12680. var graph = this.editor.graph;
  12681. if (!graph.isSelectionEmpty())
  12682. {
  12683. cells = mxUtils.sortCells(graph.getExportableCells(
  12684. graph.model.getTopmostCells(graph.getSelectionCells())));
  12685. var xml = mxUtils.getXml(graph.encodeCells(cells));
  12686. navigator.clipboard.writeText(xml);
  12687. }
  12688. }
  12689. return cells;
  12690. };
  12691. /**
  12692. * Creates the format panel and adds overrides.
  12693. */
  12694. EditorUi.prototype.pasteXml = function(xml, pasteAsLabel, compat, evt, html)
  12695. {
  12696. html = (html != null) ? html : true;
  12697. var graph = this.editor.graph;
  12698. var cells = null;
  12699. if (graph.lastPasteXml == xml)
  12700. {
  12701. graph.pasteCounter++;
  12702. }
  12703. else
  12704. {
  12705. graph.lastPasteXml = xml;
  12706. graph.pasteCounter = 0;
  12707. }
  12708. var dx = graph.pasteCounter * graph.gridSize;
  12709. if (compat || this.isCompatibleString(xml))
  12710. {
  12711. cells = this.importXml(xml, dx, dx);
  12712. graph.setSelectionCells(cells);
  12713. }
  12714. else if (pasteAsLabel && graph.getSelectionCount() == 1)
  12715. {
  12716. var cell = graph.getStartEditingCell(graph.getSelectionCell(), evt);
  12717. if ((/\.(gif|jpg|jpeg|tiff|png|svg)$/i).test(xml) &&
  12718. graph.getCurrentCellStyle(cell)[mxConstants.STYLE_SHAPE] == 'image')
  12719. {
  12720. graph.setCellStyles(mxConstants.STYLE_IMAGE, xml, [cell]);
  12721. }
  12722. else
  12723. {
  12724. graph.model.beginUpdate();
  12725. try
  12726. {
  12727. graph.labelChanged(cell, xml);
  12728. if (Graph.isLink(xml))
  12729. {
  12730. graph.setLinkForCell(cell, xml);
  12731. }
  12732. }
  12733. finally
  12734. {
  12735. graph.model.endUpdate();
  12736. }
  12737. }
  12738. graph.setSelectionCell(cell);
  12739. }
  12740. else
  12741. {
  12742. var pt = graph.getInsertPoint();
  12743. if (graph.isMouseInsertPoint())
  12744. {
  12745. dx = 0;
  12746. // No offset for insert at mouse position
  12747. if (graph.lastPasteXml == xml && graph.pasteCounter > 0)
  12748. {
  12749. graph.pasteCounter--;
  12750. }
  12751. }
  12752. cells = this.insertTextAt(xml, pt.x + dx, pt.y + dx, html);
  12753. graph.setSelectionCells(cells);
  12754. }
  12755. if (!graph.isSelectionEmpty())
  12756. {
  12757. graph.scrollCellToVisible(graph.getSelectionCell());
  12758. if (this.hoverIcons != null)
  12759. {
  12760. this.hoverIcons.update(graph.view.getState(graph.getSelectionCell()));
  12761. }
  12762. }
  12763. return cells;
  12764. };
  12765. /**
  12766. * Creates the format panel and adds overrides.
  12767. */
  12768. EditorUi.prototype.pasteCells = function(evt, realElt, useEvent, pasteAsLabel)
  12769. {
  12770. if (!mxEvent.isConsumed(evt))
  12771. {
  12772. var elt = realElt;
  12773. var asHtml = false;
  12774. if (useEvent && evt.clipboardData != null && evt.clipboardData.getData)
  12775. {
  12776. // Workaround for paste from IE11 where the page is copied
  12777. // as HTML while the data is only available via text/plain
  12778. var plain = evt.clipboardData.getData('text/plain');
  12779. var override = false;
  12780. if (plain != null && plain.length > 0 && plain.substring(0, 18) == '%3CmxGraphModel%3E')
  12781. {
  12782. try
  12783. {
  12784. var tmp = decodeURIComponent(plain);
  12785. if (this.isCompatibleString(tmp))
  12786. {
  12787. override = true;
  12788. plain = tmp;
  12789. }
  12790. }
  12791. catch (e)
  12792. {
  12793. // ignore
  12794. }
  12795. }
  12796. var data = (!override) ? evt.clipboardData.getData('text/html') : null;
  12797. if (data != null && data.length > 0)
  12798. {
  12799. elt = this.parseHtmlData(data);
  12800. asHtml = elt.getAttribute('data-type') != 'text/plain';
  12801. }
  12802. else if (plain != null && plain.length > 0)
  12803. {
  12804. elt = document.createElement('div');
  12805. mxUtils.setTextContent(elt, data);
  12806. }
  12807. }
  12808. var spans = elt.getElementsByTagName('span');
  12809. if (spans != null && spans.length > 0 && spans[0].getAttribute('data-lucid-type') ===
  12810. 'application/vnd.lucid.chart.objects')
  12811. {
  12812. var content = spans[0].getAttribute('data-lucid-content');
  12813. if (content != null && content.length > 0)
  12814. {
  12815. this.convertLucidChart(content, mxUtils.bind(this, function(xml)
  12816. {
  12817. var graph = this.editor.graph;
  12818. if (graph.lastPasteXml == xml)
  12819. {
  12820. graph.pasteCounter++;
  12821. }
  12822. else
  12823. {
  12824. graph.lastPasteXml = xml;
  12825. graph.pasteCounter = 0;
  12826. }
  12827. var dx = graph.pasteCounter * graph.gridSize;
  12828. graph.setSelectionCells(this.importXml(xml, dx, dx));
  12829. graph.scrollCellToVisible(graph.getSelectionCell());
  12830. }), mxUtils.bind(this, function(e)
  12831. {
  12832. this.handleError(e);
  12833. }));
  12834. mxEvent.consume(evt);
  12835. }
  12836. }
  12837. //Miro is using unkown encoding instead of BASE64 as before
  12838. /*else if (spans != null && spans.length > 0 && spans[0].hasAttribute('data-meta')
  12839. && spans[0].getAttribute('data-meta').substring(0, 14) == '<--(miro-data)')
  12840. {
  12841. var miroData = spans[0].getAttribute('data-meta');
  12842. miroData = miroData.substring(14, miroData.length - 15);
  12843. console.log(miroData);
  12844. }*/
  12845. else
  12846. {
  12847. // KNOWN: Paste from IE11 to other browsers on Windows
  12848. // seems to paste the contents of index.html
  12849. var compat = false;
  12850. var xml = '';
  12851. if (asHtml)
  12852. {
  12853. // Extracts compatible XML data
  12854. if (elt.textContent != null &&
  12855. (elt.textContent.substring(0, 7) == '<mxfile' &&
  12856. elt.textContent.substring(elt.textContent.length - 9) == '</mxfile>') ||
  12857. (elt.textContent.substring(0, 13) == '<mxGraphModel' &&
  12858. elt.textContent.substring(elt.textContent.length - 15) == '</mxGraphModel>'))
  12859. {
  12860. // Replaces &nbsp; in text content with normal spaces
  12861. xml = elt.textContent.replace(/\u00a0/g, ' ');
  12862. }
  12863. else
  12864. {
  12865. xml = elt.innerHTML;
  12866. }
  12867. }
  12868. else
  12869. {
  12870. xml = mxUtils.trim((elt.innerText == null) ?
  12871. mxUtils.getTextContent(elt) : elt.innerText);
  12872. }
  12873. // Workaround for junk after XML in VM
  12874. try
  12875. {
  12876. var idx = xml.lastIndexOf('%3E');
  12877. if (idx >= 0 && idx < xml.length - 3)
  12878. {
  12879. xml = xml.substring(0, idx + 3);
  12880. }
  12881. }
  12882. catch (e)
  12883. {
  12884. // ignore
  12885. }
  12886. // Checks for embedded XML content
  12887. try
  12888. {
  12889. var spans = elt.getElementsByTagName('span');
  12890. var tmp = (spans != null && spans.length > 0) ?
  12891. mxUtils.trim(decodeURIComponent(spans[0].textContent)) :
  12892. decodeURIComponent(xml);
  12893. if (tmp && (this.isCompatibleString(tmp) ||
  12894. tmp.substring(0, 20).replace(/\s/g, '').indexOf('{"isProtected":') == 0))
  12895. {
  12896. compat = true;
  12897. xml = tmp;
  12898. }
  12899. }
  12900. catch (e)
  12901. {
  12902. // ignore
  12903. }
  12904. try
  12905. {
  12906. if (xml != null && xml.length > 0)
  12907. {
  12908. if (xml.substring(0, 20).replace(/\s/g, '').indexOf('{"isProtected":') == 0)
  12909. {
  12910. var delayed = mxUtils.bind(this, function ()
  12911. {
  12912. try
  12913. {
  12914. var miro = new MiroImporter();
  12915. xml = miro.importMiroJson(JSON.parse(xml));
  12916. this.pasteXml(xml, pasteAsLabel, compat, evt);
  12917. }
  12918. catch(e)
  12919. {
  12920. console.log('Miro import error:', e);
  12921. }
  12922. });
  12923. if (typeof MiroImporter === 'undefined')
  12924. {
  12925. mxscript('js/diagramly/miro/MiroImporter.js', delayed);
  12926. }
  12927. else
  12928. {
  12929. delayed();
  12930. }
  12931. }
  12932. else
  12933. {
  12934. this.pasteXml(xml, pasteAsLabel, compat, evt, asHtml);
  12935. }
  12936. try
  12937. {
  12938. mxEvent.consume(evt);
  12939. }
  12940. catch (e)
  12941. {
  12942. // ignore event no longer exists in async handler in IE8-
  12943. }
  12944. }
  12945. else if (!useEvent)
  12946. {
  12947. var graph = this.editor.graph;
  12948. graph.lastPasteXml = null;
  12949. graph.pasteCounter = 0;
  12950. }
  12951. }
  12952. catch (e)
  12953. {
  12954. this.handleError(e);
  12955. }
  12956. }
  12957. }
  12958. realElt.innerHTML = '&nbsp;';
  12959. };
  12960. /**
  12961. *
  12962. */
  12963. EditorUi.prototype.showPrintDialog = function(title, fn)
  12964. {
  12965. var h = 320;
  12966. if (this.editor.graph.isEnabled())
  12967. {
  12968. h += 40;
  12969. }
  12970. if (this.pages != null && this.pages.length > 1)
  12971. {
  12972. h += 40;
  12973. // Additional height in lightbox for pages
  12974. if (!this.editor.graph.isEnabled())
  12975. {
  12976. h += 10;
  12977. }
  12978. }
  12979. // Additional height for include diagram
  12980. if (fn != null && !mxClient.IS_CHROMEAPP &&
  12981. this.getServiceName() == 'draw.io')
  12982. {
  12983. h += 20;
  12984. }
  12985. this.showDialog(new PrintDialog(this, title, fn).container, 320, h, true, true);
  12986. };
  12987. /**
  12988. *
  12989. */
  12990. EditorUi.prototype.print = function(preview, args)
  12991. {
  12992. var editor = this.editor;
  12993. var graph = editor.graph;
  12994. var printScale = 1;
  12995. var idx = this.getPageIndex(this.currentPage);
  12996. var currentPage = (idx != null) ? idx + 1 : 1;
  12997. // Disables dark mode while printing
  12998. var darkStylesheet = null;
  12999. var darkFg = graph.shapeForegroundColor;
  13000. var darkBg = graph.shapeBackgroundColor;
  13001. if (graph.themes != null && graph.defaultThemeName == 'darkTheme')
  13002. {
  13003. darkStylesheet = graph.stylesheet;
  13004. graph.stylesheet = graph.getDefaultStylesheet()
  13005. graph.shapeForegroundColor = '#000000';
  13006. graph.shapeBackgroundColor = '#ffffff';
  13007. graph.refresh();
  13008. }
  13009. var printGraph = mxUtils.bind(this, function(thisGraph, pv, forcePageBreaks, pageId)
  13010. {
  13011. // Workaround for CSS transforms affecting the print output
  13012. // is to disable during print output and restore after
  13013. var prev = thisGraph.useCssTransforms;
  13014. var prevTranslate = thisGraph.currentTranslate;
  13015. var prevScale = thisGraph.currentScale;
  13016. var prevViewTranslate = thisGraph.view.translate;
  13017. var prevViewScale = thisGraph.view.scale;
  13018. if (thisGraph.useCssTransforms)
  13019. {
  13020. thisGraph.useCssTransforms = false;
  13021. thisGraph.currentTranslate = new mxPoint(0,0);
  13022. thisGraph.currentScale = 1;
  13023. thisGraph.view.translate = new mxPoint(0,0);
  13024. thisGraph.view.scale = 1;
  13025. }
  13026. // Negative coordinates are cropped or shifted if page visible
  13027. var gb = thisGraph.getGraphBounds();
  13028. var border = 0;
  13029. var x0 = 0;
  13030. var y0 = 0;
  13031. var pf = mxRectangle.fromRectangle(thisGraph.pageFormat);
  13032. var autoOrigin = args.fit || args.crop || !thisGraph.pageVisible;
  13033. var temp = args.scale;
  13034. pf.width = Math.ceil(pf.width * thisGraph.pageScale);
  13035. pf.height = Math.ceil(pf.height * thisGraph.pageScale);
  13036. var scale = 1;
  13037. if (args.fit)
  13038. {
  13039. var h = args.sheetsAcross;
  13040. var v = args.sheetsDown;
  13041. if (!isNaN(temp))
  13042. {
  13043. pf.width = Math.ceil(pf.width * temp);
  13044. pf.height = Math.ceil(pf.height * temp);
  13045. }
  13046. scale = Math.min((pf.height * v) / (gb.height / thisGraph.view.scale),
  13047. (pf.width * h) / (gb.width / thisGraph.view.scale));
  13048. }
  13049. else
  13050. {
  13051. scale = !isNaN(temp) ? temp : 1;
  13052. }
  13053. // Applies print scale
  13054. scale *= printScale;
  13055. // Starts at first visible page
  13056. if (!autoOrigin && thisGraph.pageVisible)
  13057. {
  13058. var layout = thisGraph.getPageLayout();
  13059. x0 -= layout.x * pf.width;
  13060. y0 -= layout.y * pf.height;
  13061. }
  13062. else
  13063. {
  13064. autoOrigin = true;
  13065. }
  13066. if (args.crop)
  13067. {
  13068. if (args.selection)
  13069. {
  13070. gb = graph.getBoundingBox(graph.getSelectionCells());
  13071. }
  13072. pf.width = (gb.width + 1) * scale / thisGraph.view.scale;
  13073. pf.height = (gb.height + 1) * scale / thisGraph.view.scale;
  13074. }
  13075. pf.width = Math.ceil(pf.width * printScale);
  13076. pf.height = Math.ceil(pf.height * printScale);
  13077. var anchorId = (pageId != null) ? 'page/id,' + pageId : null;
  13078. if (pv == null)
  13079. {
  13080. pv = PrintDialog.createPrintPreview(thisGraph, scale, null, border, x0, y0, autoOrigin);
  13081. pv.title = this.getBaseFilename(true);
  13082. pv.pageSelector = false;
  13083. pv.mathEnabled = false;
  13084. var pageMargin = args.border;
  13085. if (!isNaN(pageMargin))
  13086. {
  13087. pv.pageMargin = pageMargin;
  13088. }
  13089. if (args.selection)
  13090. {
  13091. pv.isCellVisible = function(cell)
  13092. {
  13093. return thisGraph.isCellSelected(cell);
  13094. };
  13095. }
  13096. var writeHead = pv.writeHead;
  13097. // Overridden to add custom fonts
  13098. pv.writeHead = function(doc)
  13099. {
  13100. writeHead.apply(this, arguments);
  13101. // Fixes bold math when exported to PDF
  13102. if (mxClient.IS_GC)
  13103. {
  13104. doc.writeln('<style type="text/css">');
  13105. doc.writeln('@media print {');
  13106. doc.writeln('.MathJax svg { shape-rendering: crispEdges; }');
  13107. doc.writeln('}');
  13108. doc.writeln('</style>');
  13109. }
  13110. if (editor.fontCss != null)
  13111. {
  13112. doc.writeln('<style type="text/css">');
  13113. doc.writeln(mxUtils.htmlEntities(editor.fontCss,
  13114. false, false, false));
  13115. doc.writeln('</style>');
  13116. }
  13117. var fonts = thisGraph.getCustomFonts();
  13118. for (var i = 0; i < fonts.length; i++)
  13119. {
  13120. var fontName = fonts[i].name;
  13121. var fontUrl = fonts[i].url;
  13122. if (Graph.isCssFontUrl(fontUrl))
  13123. {
  13124. doc.writeln('<link rel="stylesheet" href="' +
  13125. mxUtils.htmlEntities(Graph.rewriteGoogleFontUrl(fontUrl)) +
  13126. '" charset="UTF-8" type="text/css">');
  13127. }
  13128. else
  13129. {
  13130. doc.writeln('<style type="text/css">');
  13131. doc.writeln('@font-face {\n' +
  13132. 'font-family: "' + mxUtils.htmlEntities(fontName) + '";\n' +
  13133. 'src: url("' + mxUtils.htmlEntities(fontUrl) + '");\n}');
  13134. doc.writeln('</style>');
  13135. }
  13136. }
  13137. };
  13138. // Adapts background images
  13139. if (Editor.enableCssDarkMode)
  13140. {
  13141. var printGetBackgroundImage = pv.getBackgroundImage;
  13142. pv.getBackgroundImage = function()
  13143. {
  13144. return graph.adaptBackgroundPage(
  13145. printGetBackgroundImage.apply(
  13146. this, arguments));
  13147. };
  13148. }
  13149. // Replaces background images with SVG subtrees
  13150. if (Editor.replaceSvgDataUris)
  13151. {
  13152. var printDrawBackgroundImage = pv.drawBackgroundImage;
  13153. pv.drawBackgroundImage = function(img)
  13154. {
  13155. printDrawBackgroundImage.apply(this, arguments);
  13156. if (img.node != null)
  13157. {
  13158. EditorUi.embedSvgImages(img.node);
  13159. graph.disableSvgLinks(img.node, function(link)
  13160. {
  13161. link.setAttribute('href', 'javascript:void(0)');
  13162. });
  13163. }
  13164. };
  13165. }
  13166. // Renders grid and handles math
  13167. var printPreviewAddGraphFragment = pv.addGraphFragment;
  13168. pv.addGraphFragment = function(dx, dy, scale, pageNumber, div, clip)
  13169. {
  13170. printPreviewAddGraphFragment.apply(this, arguments);
  13171. if (this.graph.mathEnabled)
  13172. {
  13173. this.mathEnabled = this.mathEnabled || true;
  13174. }
  13175. else
  13176. {
  13177. div.classList.add('geDisableMathJax');
  13178. }
  13179. };
  13180. // Switches stylesheet for print output in dark mode
  13181. var temp = null;
  13182. var tempFg = graph.shapeForegroundColor;
  13183. var tempBg = graph.shapeBackgroundColor;
  13184. if (graph.themes != null && graph.defaultThemeName == 'darkTheme')
  13185. {
  13186. temp = graph.stylesheet;
  13187. graph.stylesheet = graph.getDefaultStylesheet()
  13188. graph.shapeForegroundColor = '#000000';
  13189. graph.shapeBackgroundColor = '#ffffff';
  13190. graph.refresh();
  13191. }
  13192. // Generates the print output
  13193. if (args.grid)
  13194. {
  13195. pv.gridSize = thisGraph.gridSize;
  13196. pv.gridSteps = thisGraph.view.gridSteps;
  13197. pv.gridColor = Editor.isDarkMode() ?
  13198. mxGraphView.prototype.defaultGridColor :
  13199. thisGraph.view.gridColor;
  13200. }
  13201. pv.open(null, null, forcePageBreaks, true, anchorId, pf,
  13202. (args.selection) ? thisGraph.getSelectionCells() : null);
  13203. // Restores the stylesheet
  13204. if (temp != null)
  13205. {
  13206. graph.shapeForegroundColor = tempFg;
  13207. graph.shapeBackgroundColor = tempBg;
  13208. graph.stylesheet = temp;
  13209. graph.refresh();
  13210. }
  13211. }
  13212. else
  13213. {
  13214. var bg = thisGraph.background;
  13215. if (bg == null || bg == '' || bg == mxConstants.NONE)
  13216. {
  13217. bg = '#ffffff';
  13218. }
  13219. pv.backgroundColor = bg;
  13220. pv.autoOrigin = autoOrigin;
  13221. if (args.grid)
  13222. {
  13223. pv.gridSize = thisGraph.gridSize;
  13224. pv.gridSteps = thisGraph.view.gridSteps;
  13225. pv.gridColor = Editor.isDarkMode() ?
  13226. mxGraphView.prototype.defaultGridColor :
  13227. thisGraph.view.gridColor;
  13228. }
  13229. pv.appendGraph(thisGraph, scale, x0, y0, forcePageBreaks, true, anchorId, pf,
  13230. (args.selection) ? thisGraph.getSelectionCells() : null);
  13231. var extFonts = thisGraph.getCustomFonts();
  13232. if (pv.wnd != null)
  13233. {
  13234. for (var i = 0; i < extFonts.length; i++)
  13235. {
  13236. var fontName = extFonts[i].name;
  13237. var fontUrl = extFonts[i].url;
  13238. if (Graph.isCssFontUrl(fontUrl))
  13239. {
  13240. pv.wnd.document.writeln('<link rel="stylesheet" href="' +
  13241. mxUtils.htmlEntities(fontUrl) +
  13242. '" charset="UTF-8" type="text/css">');
  13243. }
  13244. else
  13245. {
  13246. pv.wnd.document.writeln('<style type="text/css">');
  13247. pv.wnd.document.writeln('@font-face {\n' +
  13248. 'font-family: "' + mxUtils.htmlEntities(fontName) + '";\n' +
  13249. 'src: url("' + mxUtils.htmlEntities(fontUrl) + '");\n}');
  13250. pv.wnd.document.writeln('</style>');
  13251. }
  13252. }
  13253. }
  13254. }
  13255. // Restores state if css transforms are used
  13256. if (prev)
  13257. {
  13258. thisGraph.useCssTransforms = prev;
  13259. thisGraph.currentTranslate = prevTranslate;
  13260. thisGraph.currentScale = prevScale;
  13261. thisGraph.view.translate = prevViewTranslate;
  13262. thisGraph.view.scale = prevViewScale;
  13263. }
  13264. return pv;
  13265. });
  13266. var pagesFrom = args.pagesFrom;
  13267. var pagesTo = args.pagesTo;
  13268. var ignorePages = !args.allPages;
  13269. var pv = null;
  13270. if (EditorUi.isElectronApp)
  13271. {
  13272. PrintDialog.electronPrint(this, args);
  13273. return;
  13274. }
  13275. if (ignorePages)
  13276. {
  13277. ignorePages = args.selection ||
  13278. (pagesFrom == currentPage &&
  13279. pagesTo == currentPage);
  13280. }
  13281. if (!ignorePages && this.pages != null && this.pages.length)
  13282. {
  13283. var i0 = 0;
  13284. var imax = this.pages.length - 1;
  13285. if (!args.allPages)
  13286. {
  13287. i0 = parseInt(pagesFrom) - 1;
  13288. imax = parseInt(pagesTo) - 1;
  13289. }
  13290. for (var i = i0; i <= imax; i++)
  13291. {
  13292. var page = this.pages[i];
  13293. var tempGraph = (page == this.currentPage) ? graph : null;
  13294. if (tempGraph == null)
  13295. {
  13296. tempGraph = this.createTemporaryGraph(graph.stylesheet);
  13297. tempGraph.shapeForegroundColor = graph.shapeForegroundColor;
  13298. tempGraph.shapeBackgroundColor = graph.shapeBackgroundColor;
  13299. // Restores graph settings that are relevant for printing
  13300. var pageVisible = true;
  13301. var mathEnabled = false;
  13302. var bg = null;
  13303. var bgImage = null;
  13304. if (page.viewState == null)
  13305. {
  13306. // Workaround to extract view state from XML node
  13307. // This changes the state of the page and parses
  13308. // the XML for the graph model even if not needed.
  13309. if (page.root == null)
  13310. {
  13311. this.updatePageRoot(page);
  13312. }
  13313. }
  13314. if (page.viewState != null)
  13315. {
  13316. pageVisible = page.viewState.pageVisible;
  13317. mathEnabled = page.viewState.mathEnabled;
  13318. bg = page.viewState.background;
  13319. bgImage = page.viewState.backgroundImage;
  13320. tempGraph.pageFormat = page.viewState.pageFormat;
  13321. tempGraph.gridSize = page.viewState.gridSize;
  13322. }
  13323. // Forces update of background page image in offscreen page
  13324. if (bgImage != null && bgImage.originalSrc != null)
  13325. {
  13326. bgImage = this.createImageForPageLink(
  13327. bgImage.originalSrc, page);
  13328. }
  13329. tempGraph.background = bg;
  13330. tempGraph.backgroundImage = (bgImage != null) ? new mxImage(
  13331. bgImage.src, bgImage.width, bgImage.height,
  13332. bgImage.x, bgImage.y) : null;
  13333. tempGraph.pageVisible = pageVisible;
  13334. tempGraph.mathEnabled = mathEnabled;
  13335. // Overrides graph bounds to include background images
  13336. var graphGetGraphBounds = tempGraph.getGraphBounds;
  13337. tempGraph.getGraphBounds = function()
  13338. {
  13339. var bounds = graphGetGraphBounds.apply(this, arguments);
  13340. var img = this.backgroundImage;
  13341. if (img != null && img.width != null && img.height != null)
  13342. {
  13343. var t = this.view.translate;
  13344. var s = this.view.scale;
  13345. bounds = mxRectangle.fromRectangle(bounds);
  13346. bounds.add(new mxRectangle(
  13347. (t.x + img.x) * s, (t.y + img.y) * s,
  13348. img.width * s, img.height * s));
  13349. }
  13350. return bounds;
  13351. };
  13352. // Redirects placeholders to current page
  13353. var graphGetGlobalVariable = tempGraph.getGlobalVariable;
  13354. tempGraph.getGlobalVariable = function(name)
  13355. {
  13356. if (name == 'page')
  13357. {
  13358. return page.getName();
  13359. }
  13360. else if (name == 'pagenumber')
  13361. {
  13362. return i + 1;
  13363. }
  13364. else if (name == 'pagecount')
  13365. {
  13366. return (this.pages != null) ? this.pages.length : 1;
  13367. }
  13368. return graphGetGlobalVariable.apply(this, arguments);
  13369. };
  13370. document.body.appendChild(tempGraph.container);
  13371. this.updatePageRoot(page);
  13372. tempGraph.model.setRoot(page.root);
  13373. }
  13374. pv = printGraph(tempGraph, pv, i != imax, page.getId());
  13375. if (tempGraph != graph)
  13376. {
  13377. tempGraph.container.parentNode.removeChild(tempGraph.container);
  13378. }
  13379. }
  13380. }
  13381. else
  13382. {
  13383. pv = printGraph(graph);
  13384. }
  13385. if (pv == null || pv.wnd == null)
  13386. {
  13387. this.handleError({message: mxResources.get('errorUpdatingPreview')});
  13388. }
  13389. else
  13390. {
  13391. if (pv.mathEnabled)
  13392. {
  13393. var doc = pv.wnd.document;
  13394. // Adds asynchronous printing when MathJax finishes rendering
  13395. // via global variable that is checked in math-print.js to
  13396. // avoid generating unsafe-inline script or adding SHA to CSP
  13397. if (!preview)
  13398. {
  13399. pv.wnd.IMMEDIATE_PRINT = true;
  13400. }
  13401. doc.writeln('<script type="text/javascript" src="js/math-print.js"></script>');
  13402. }
  13403. pv.closeDocument();
  13404. // Rewrites page links to point to internal anchors
  13405. Graph.rewritePageLinks(pv.wnd.document, true);
  13406. if (!pv.mathEnabled && !preview)
  13407. {
  13408. PrintDialog.printPreview(pv);
  13409. }
  13410. }
  13411. // Restores dark mode
  13412. if (darkStylesheet != null)
  13413. {
  13414. graph.shapeForegroundColor = darkFg;
  13415. graph.shapeBackgroundColor = darkBg;
  13416. graph.stylesheet = darkStylesheet;
  13417. graph.refresh();
  13418. }
  13419. return pv;
  13420. };
  13421. /**
  13422. * Adds a file drop handler for opening local files.
  13423. */
  13424. EditorUi.prototype.addFileDropHandler = function(elts)
  13425. {
  13426. // Installs drag and drop handler for files
  13427. if (Graph.fileSupport)
  13428. {
  13429. var dropElt = null;
  13430. for (var i = 0; i < elts.length; i++)
  13431. {
  13432. // Setup the dnd listeners
  13433. mxEvent.addListener(elts[i], 'dragleave', function(evt)
  13434. {
  13435. if (dropElt != null)
  13436. {
  13437. dropElt.parentNode.removeChild(dropElt);
  13438. dropElt = null;
  13439. }
  13440. evt.stopPropagation();
  13441. evt.preventDefault();
  13442. });
  13443. mxEvent.addListener(elts[i], 'dragover', mxUtils.bind(this, function(evt)
  13444. {
  13445. if (this.editor.graph.isEnabled() || urlParams['embed'] != '1')
  13446. {
  13447. // IE 10 does not implement pointer-events so it can't have a drop highlight
  13448. if (dropElt == null && (!mxClient.IS_IE || (document.documentMode > 10 && document.documentMode < 12)))
  13449. {
  13450. dropElt = this.highlightElement();
  13451. }
  13452. }
  13453. evt.stopPropagation();
  13454. evt.preventDefault();
  13455. }));
  13456. mxEvent.addListener(elts[i], 'drop', mxUtils.bind(this, function(evt)
  13457. {
  13458. if (dropElt != null)
  13459. {
  13460. dropElt.parentNode.removeChild(dropElt);
  13461. dropElt = null;
  13462. }
  13463. if (this.editor.graph.isEnabled() || urlParams['embed'] != '1')
  13464. {
  13465. if (evt.dataTransfer.files.length > 0)
  13466. {
  13467. this.hideDialog();
  13468. // Never open files in embed mode
  13469. if (urlParams['embed'] == '1')
  13470. {
  13471. this.importFiles(evt.dataTransfer.files, 0, 0, this.maxImageSize, null, null,
  13472. null, null, !mxEvent.isControlDown(evt) && !mxEvent.isShiftDown(evt));
  13473. }
  13474. else
  13475. {
  13476. this.openFiles(evt.dataTransfer.files, true);
  13477. }
  13478. }
  13479. else
  13480. {
  13481. // Handles open special files via text drag and drop
  13482. var data = this.extractGraphModelFromEvent(evt);
  13483. // Tries additional and async parsing of text content such as HTML, Gliffy data
  13484. if (data == null)
  13485. {
  13486. var provider = (evt.dataTransfer != null) ? evt.dataTransfer : evt.clipboardData;
  13487. if (provider != null)
  13488. {
  13489. if (document.documentMode == 10 || document.documentMode == 11)
  13490. {
  13491. data = provider.getData('Text');
  13492. }
  13493. else
  13494. {
  13495. var data = null;
  13496. if (mxUtils.indexOf(provider.types, 'text/uri-list') >= 0)
  13497. {
  13498. data = evt.dataTransfer.getData('text/uri-list');
  13499. }
  13500. else
  13501. {
  13502. data = (mxUtils.indexOf(provider.types, 'text/html') >= 0) ? provider.getData('text/html') : null;
  13503. }
  13504. if (data != null && data.length > 0)
  13505. {
  13506. var div = document.createElement('div');
  13507. div.innerHTML = Graph.sanitizeHtml(data);
  13508. // Extracts single image
  13509. var imgs = div.getElementsByTagName('img');
  13510. if (imgs.length > 0)
  13511. {
  13512. data = imgs[0].getAttribute('src');
  13513. }
  13514. }
  13515. else if (mxUtils.indexOf(provider.types, 'text/plain') >= 0)
  13516. {
  13517. data = provider.getData('text/plain');
  13518. }
  13519. }
  13520. if (data != null)
  13521. {
  13522. // Checks for embedded XML in PNG
  13523. if (Editor.isPngDataUrl(data))
  13524. {
  13525. var xml = Editor.extractGraphModelFromPng(data);
  13526. if (xml != null && xml.length > 0)
  13527. {
  13528. this.openLocalFile(xml, null, true);
  13529. }
  13530. }
  13531. else if (this.isRemoteFileFormat(data))
  13532. {
  13533. if (this.isOffline())
  13534. {
  13535. this.showError(mxResources.get('error'), mxResources.get('notInOffline'));
  13536. }
  13537. else
  13538. {
  13539. new mxXmlRequest(OPEN_URL, 'format=xml&data=' + encodeURIComponent(data)).send(mxUtils.bind(this, function(req)
  13540. {
  13541. if (req.getStatus() >= 200 && req.getStatus() <= 299)
  13542. {
  13543. this.openLocalFile(req.getText(), null, true);
  13544. }
  13545. else
  13546. {
  13547. this.showError(mxResources.get('error'), req.getStatus() == 413? mxResources.get('diagramTooLarge') :
  13548. mxResources.get('unknownError'));
  13549. }
  13550. }));
  13551. }
  13552. }
  13553. else if (/^https?:\/\//.test(data))
  13554. {
  13555. if (this.getCurrentFile() == null)
  13556. {
  13557. window.location.hash = '#U' + encodeURIComponent(data);
  13558. }
  13559. else
  13560. {
  13561. window.geOpenWindow(((mxClient.IS_CHROMEAPP) ?
  13562. (EditorUi.drawHost + '/') : 'https://' + location.host + '/') +
  13563. window.location.search + '#U' + encodeURIComponent(data));
  13564. }
  13565. }
  13566. }
  13567. }
  13568. }
  13569. else
  13570. {
  13571. this.openLocalFile(data, null, true);
  13572. }
  13573. }
  13574. }
  13575. evt.stopPropagation();
  13576. evt.preventDefault();
  13577. }));
  13578. }
  13579. }
  13580. };
  13581. /**
  13582. * Highlights the given element
  13583. */
  13584. EditorUi.prototype.highlightElement = function(elt)
  13585. {
  13586. var x = 0;
  13587. var y = 0;
  13588. var w = 0;
  13589. var h = 0;
  13590. if (elt == null)
  13591. {
  13592. var b = document.body;
  13593. var d = document.documentElement;
  13594. w = (b.clientWidth || d.clientWidth) - 3;
  13595. h = Math.max(b.clientHeight || 0, d.clientHeight) - 3;
  13596. }
  13597. else
  13598. {
  13599. x = elt.offsetTop;
  13600. y = elt.offsetLeft;
  13601. w = elt.clientWidth;
  13602. h = elt.clientHeight;
  13603. }
  13604. var hl = document.createElement('div');
  13605. hl.style.zIndex = mxPopupMenu.prototype.zIndex + 2;
  13606. hl.style.border = '3px dotted rgb(254, 137, 12)';
  13607. hl.style.pointerEvents = 'none';
  13608. hl.style.position = 'absolute';
  13609. hl.style.top = x + 'px';
  13610. hl.style.left = y + 'px';
  13611. hl.style.width = Math.max(0, w - 3) + 'px';
  13612. hl.style.height = Math.max(0, h - 3) + 'px';
  13613. if (elt != null && elt.parentNode == this.editor.graph.container)
  13614. {
  13615. this.editor.graph.container.appendChild(hl);
  13616. }
  13617. else
  13618. {
  13619. document.body.appendChild(hl);
  13620. }
  13621. return hl;
  13622. };
  13623. /**
  13624. * Highlights the given element
  13625. */
  13626. EditorUi.prototype.stringToCells = function(xml)
  13627. {
  13628. var doc = mxUtils.parseXml(xml);
  13629. var node = this.editor.extractGraphModel(doc.documentElement);
  13630. var cells = [];
  13631. if (node != null)
  13632. {
  13633. var codec = new mxCodec(node.ownerDocument);
  13634. var model = new mxGraphModel();
  13635. codec.decode(node, model);
  13636. var parent = model.getChildAt(model.getRoot(), 0);
  13637. for (var j = 0; j < model.getChildCount(parent); j++)
  13638. {
  13639. cells.push(model.getChildAt(parent, j));
  13640. }
  13641. }
  13642. return cells;
  13643. };
  13644. /**
  13645. * Opens the given files in the editor.
  13646. */
  13647. EditorUi.prototype.openFileHandle = function(data, name, file, temp, fileHandle, editable)
  13648. {
  13649. if (name != null && name.length > 0)
  13650. {
  13651. if ((!this.useCanvasForExport && /(\.png)$/i.test(name)) ||
  13652. /(\.pdf)$/i.test(name))
  13653. {
  13654. name = name.substring(0, name.length - 4);
  13655. if (!/(\.drawio)$/i.test(name))
  13656. {
  13657. name = name + '.drawio';
  13658. }
  13659. }
  13660. var handleResult = mxUtils.bind(this, function(xml)
  13661. {
  13662. var dot = name.lastIndexOf('.');
  13663. if (dot >= 0)
  13664. {
  13665. name = name.substring(0, name.lastIndexOf('.')) + '.drawio';
  13666. }
  13667. else
  13668. {
  13669. name = name + '.drawio';
  13670. }
  13671. if (xml.substring(0, 10) == '<mxlibrary')
  13672. {
  13673. // Creates new temporary file if library is dropped in splash screen
  13674. if (this.getCurrentFile() == null && urlParams['embed'] != '1')
  13675. {
  13676. this.openLocalFile(this.emptyDiagramXml, this.defaultFilename, temp);
  13677. }
  13678. try
  13679. {
  13680. this.loadLibrary(new LocalLibrary(this, xml, name));
  13681. this.showSidebar();
  13682. }
  13683. catch (e)
  13684. {
  13685. this.handleError(e, mxResources.get('errorLoadingFile'));
  13686. }
  13687. }
  13688. else
  13689. {
  13690. this.openLocalFile(xml, name, temp);
  13691. }
  13692. });
  13693. if (EditorUi.isVisioFilename(name))
  13694. {
  13695. this.importVisio(file, mxUtils.bind(this, function(xml)
  13696. {
  13697. this.spinner.stop();
  13698. handleResult(xml);
  13699. }));
  13700. }
  13701. else if (/(\.*<graphml )/.test(data))
  13702. {
  13703. this.importGraphML(data, mxUtils.bind(this, function(xml)
  13704. {
  13705. this.spinner.stop();
  13706. handleResult(xml);
  13707. }));
  13708. }
  13709. else if (Graph.fileSupport && new XMLHttpRequest().upload &&
  13710. this.isRemoteFileFormat(data, name))
  13711. {
  13712. if (this.isOffline())
  13713. {
  13714. this.spinner.stop();
  13715. this.showError(mxResources.get('error'), mxResources.get('notInOffline'));
  13716. }
  13717. else
  13718. {
  13719. this.parseFile(file, mxUtils.bind(this, function(xhr)
  13720. {
  13721. if (xhr.readyState == 4)
  13722. {
  13723. this.spinner.stop();
  13724. if (xhr.status >= 200 && xhr.status <= 299)
  13725. {
  13726. handleResult(xhr.responseText);
  13727. }
  13728. else
  13729. {
  13730. this.handleError({message: mxResources.get((xhr.status == 413) ?
  13731. 'drawingTooLarge' : 'invalidOrMissingFile')},
  13732. mxResources.get('errorLoadingFile'));
  13733. }
  13734. }
  13735. }));
  13736. }
  13737. }
  13738. else if (this.isLucidChartData(data))
  13739. {
  13740. if (/(\.json)$/i.test(name))
  13741. {
  13742. name = name.substring(0, name.length - 5) + '.drawio';
  13743. }
  13744. // LATER: Add import step that produces cells and use callback
  13745. this.convertLucidChart(data, mxUtils.bind(this, function(xml)
  13746. {
  13747. this.spinner.stop();
  13748. this.openLocalFile(xml, name, temp);
  13749. }), mxUtils.bind(this, function(e)
  13750. {
  13751. this.spinner.stop();
  13752. this.handleError(e);
  13753. }));
  13754. }
  13755. else if (data.substring(0, 10) == '<mxlibrary')
  13756. {
  13757. this.spinner.stop();
  13758. // Creates new temporary file if library is dropped in splash screen
  13759. if (this.getCurrentFile() == null && urlParams['embed'] != '1')
  13760. {
  13761. this.openLocalFile(this.emptyDiagramXml, this.defaultFilename, temp);
  13762. }
  13763. try
  13764. {
  13765. this.loadLibrary(new LocalLibrary(this, data, file.name));
  13766. this.showSidebar();
  13767. }
  13768. catch (e)
  13769. {
  13770. this.handleError(e, mxResources.get('errorLoadingFile'));
  13771. }
  13772. }
  13773. else if (data.indexOf('PK') == 0)
  13774. {
  13775. this.importZipFile(file, mxUtils.bind(this, function(xml)
  13776. {
  13777. this.spinner.stop();
  13778. handleResult(xml);
  13779. }), mxUtils.bind(this, function()
  13780. {
  13781. this.spinner.stop();
  13782. this.openLocalFile(data, name, temp);
  13783. }));
  13784. }
  13785. else
  13786. {
  13787. if (file.type.substring(0, 9) == 'image/png')
  13788. {
  13789. data = this.extractGraphModelFromPng(data);
  13790. }
  13791. else if (file.type == 'application/pdf')
  13792. {
  13793. var xml = Editor.extractGraphModelFromPdf(data);
  13794. if (xml != null)
  13795. {
  13796. fileHandle = null;
  13797. temp = true;
  13798. data = xml;
  13799. }
  13800. }
  13801. this.spinner.stop();
  13802. this.openLocalFile(data, name, temp, fileHandle,
  13803. (fileHandle != null) ? file : null, editable);
  13804. }
  13805. }
  13806. };
  13807. /**
  13808. * Opens the given files in the editor.
  13809. */
  13810. EditorUi.prototype.openFiles = function(files, temp)
  13811. {
  13812. if (this.spinner.spin(document.body, mxResources.get('loading')))
  13813. {
  13814. for (var i = 0; i < files.length; i++)
  13815. {
  13816. (mxUtils.bind(this, function(file)
  13817. {
  13818. var reader = new FileReader();
  13819. reader.onload = mxUtils.bind(this, function(e)
  13820. {
  13821. try
  13822. {
  13823. this.openFileHandle(e.target.result, file.name, file, temp);
  13824. }
  13825. catch (e)
  13826. {
  13827. this.handleError(e);
  13828. }
  13829. });
  13830. reader.onerror = mxUtils.bind(this, function(e)
  13831. {
  13832. this.spinner.stop();
  13833. this.handleError(e);
  13834. window.openFile = null;
  13835. });
  13836. if ((file.type.substring(0, 5) === 'image' ||
  13837. file.type === 'application/pdf') &&
  13838. file.type.substring(0, 9) !== 'image/svg')
  13839. {
  13840. reader.readAsDataURL(file);
  13841. }
  13842. else
  13843. {
  13844. reader.readAsText(file);
  13845. }
  13846. }))(files[i]);
  13847. }
  13848. }
  13849. };
  13850. /**
  13851. * Shows the layers dialog if the graph has more than one layer.
  13852. */
  13853. EditorUi.prototype.openLocalFile = function(data, name, temp, fileHandle, desc, editable)
  13854. {
  13855. var currentFile = this.getCurrentFile();
  13856. var fn = mxUtils.bind(this, function()
  13857. {
  13858. window.openFile = null;
  13859. if (name == null && this.getCurrentFile() != null && this.isDiagramEmpty())
  13860. {
  13861. var doc = mxUtils.parseXml(data);
  13862. if (doc != null)
  13863. {
  13864. this.editor.setGraphXml(doc.documentElement);
  13865. this.editor.graph.selectAll();
  13866. }
  13867. }
  13868. else
  13869. {
  13870. this.fileLoaded(new LocalFile(this, data,
  13871. name || this.defaultFilename, temp,
  13872. fileHandle, desc, editable));
  13873. }
  13874. });
  13875. if (data != null && data.length > 0)
  13876. {
  13877. if (currentFile == null || (!currentFile.isModified() &&
  13878. (mxClient.IS_CHROMEAPP || EditorUi.isElectronApp || fileHandle != null)))
  13879. {
  13880. fn();
  13881. }
  13882. else if ((mxClient.IS_CHROMEAPP || EditorUi.isElectronApp || fileHandle != null) &&
  13883. currentFile != null && currentFile.isModified())
  13884. {
  13885. this.confirm(mxResources.get('allChangesLost'), null, fn,
  13886. mxResources.get('cancel'), mxResources.get('discardChanges'));
  13887. }
  13888. else
  13889. {
  13890. window.openFile = new OpenFile(function()
  13891. {
  13892. window.openFile = null;
  13893. });
  13894. window.openFile.setData(data, name);
  13895. window.geOpenWindow(this.getUrl(), null, mxUtils.bind(this, function()
  13896. {
  13897. if (currentFile != null && currentFile.isModified())
  13898. {
  13899. this.confirm(mxResources.get('allChangesLost'), null, fn,
  13900. mxResources.get('cancel'), mxResources.get('discardChanges'));
  13901. }
  13902. else
  13903. {
  13904. fn();
  13905. }
  13906. }));
  13907. }
  13908. }
  13909. else
  13910. {
  13911. throw new Error(mxResources.get('notADiagramFile'));
  13912. }
  13913. };
  13914. /**
  13915. * Returns a list of all shapes used in the current file.
  13916. */
  13917. EditorUi.prototype.getBasenames = function()
  13918. {
  13919. var basenames = {};
  13920. if (this.pages != null)
  13921. {
  13922. for (var i = 0; i < this.pages.length; i++)
  13923. {
  13924. this.updatePageRoot(this.pages[i]);
  13925. this.addBasenamesForCell(this.pages[i].root, basenames);
  13926. }
  13927. }
  13928. else
  13929. {
  13930. this.addBasenamesForCell(this.editor.graph.model.getRoot(), basenames);
  13931. }
  13932. var result = [];
  13933. for (var key in basenames)
  13934. {
  13935. result.push(key);
  13936. }
  13937. return result;
  13938. };
  13939. /**
  13940. * Returns a list of all shapes used in the current file.
  13941. */
  13942. EditorUi.prototype.addBasenamesForCell = function(cell, basenames)
  13943. {
  13944. function addName(name)
  13945. {
  13946. if (name != null)
  13947. {
  13948. // LATER: Check if this case exists
  13949. var dot = name.lastIndexOf('.');
  13950. if (dot > 0)
  13951. {
  13952. name = name.substring(dot + 1, name.length);
  13953. }
  13954. if (basenames[name] == null)
  13955. {
  13956. basenames[name] = true;
  13957. }
  13958. }
  13959. };
  13960. var graph = this.editor.graph;
  13961. var style = graph.getCellStyle(cell);
  13962. var shape = style[mxConstants.STYLE_SHAPE];
  13963. addName(mxStencilRegistry.getBasenameForStencil(shape));
  13964. // Adds package names for markers in edges
  13965. if (graph.model.isEdge(cell))
  13966. {
  13967. addName(mxMarker.getPackageForType(style[mxConstants.STYLE_STARTARROW]));
  13968. addName(mxMarker.getPackageForType(style[mxConstants.STYLE_ENDARROW]));
  13969. }
  13970. var childCount = graph.model.getChildCount(cell);
  13971. for (var i = 0; i < childCount; i++)
  13972. {
  13973. this.addBasenamesForCell(graph.model.getChildAt(cell, i), basenames);
  13974. }
  13975. };
  13976. /**
  13977. * Shows the layers dialog if the graph has more than one layer.
  13978. */
  13979. EditorUi.prototype.setGraphEnabled = function(enabled)
  13980. {
  13981. this.diagramContainer.style.visibility = (enabled) ? '' : 'hidden';
  13982. this.formatContainer.style.visibility = (enabled) ? '' : 'hidden';
  13983. this.sidebarContainer.style.display = (enabled) ? '' : 'none';
  13984. this.hsplit.style.display = (enabled && Editor.currentTheme != 'sketch' &&
  13985. Editor.currentTheme != 'min') ? '' : 'none';
  13986. this.editor.graph.setEnabled(enabled);
  13987. if (this.tabContainer != null)
  13988. {
  13989. this.tabContainer.style.visibility = (enabled) ? '' : 'hidden';
  13990. }
  13991. if (this.ruler != null)
  13992. {
  13993. this.ruler.hRuler.container.style.visibility = (enabled) ? '' : 'hidden';
  13994. this.ruler.vRuler.container.style.visibility = (enabled) ? '' : 'hidden';
  13995. }
  13996. if (!enabled)
  13997. {
  13998. this.hideWindows();
  13999. }
  14000. };
  14001. /**
  14002. * Shows the layers dialog if the graph has more than one layer.
  14003. */
  14004. EditorUi.prototype.initializeEmbedMode = function()
  14005. {
  14006. this.setGraphEnabled(false);
  14007. var parent = window.opener || window.parent;
  14008. if (parent != window)
  14009. {
  14010. if (urlParams['spin'] != '1' || this.spinner.spin(document.body, mxResources.get('loading')))
  14011. {
  14012. var initialized = false;
  14013. this.installMessageHandler(mxUtils.bind(this, function(xml, evt, modified, convertToSketch)
  14014. {
  14015. if (!initialized)
  14016. {
  14017. initialized = true;
  14018. this.spinner.stop();
  14019. this.addEmbedButtons();
  14020. this.setGraphEnabled(true);
  14021. }
  14022. if (xml == null || xml.length == 0)
  14023. {
  14024. xml = this.emptyDiagramXml;
  14025. }
  14026. // Creates temporary file for diff sync in embed mode
  14027. this.setCurrentFile(new EmbedFile(this, xml, {}));
  14028. this.mode = App.MODE_EMBED;
  14029. this.setFileData(xml);
  14030. // TODO: Check if cellsInserted should be fired instead here
  14031. if (convertToSketch)
  14032. {
  14033. try
  14034. {
  14035. //Disable grid and page view
  14036. var graph = this.editor.graph;
  14037. graph.setGridEnabled(false);
  14038. graph.pageVisible = false;
  14039. var cells = graph.model.cells;
  14040. //Add sketch style and font to all cells
  14041. for (var id in cells)
  14042. {
  14043. var cell = cells[id];
  14044. if (cell != null && cell.style != null)
  14045. {
  14046. cell.style += ';sketch=1;' + (cell.style.indexOf('fontFamily=') == -1 || cell.style.indexOf('fontFamily=Helvetica;') > -1?
  14047. 'fontFamily=Architects Daughter;fontSource=https%3A%2F%2Ffonts.googleapis.com%2Fcss%3Ffamily%3DArchitects%2BDaughter;' : '');
  14048. }
  14049. }
  14050. }
  14051. catch(e)
  14052. {
  14053. console.log(e); //Ignore
  14054. }
  14055. }
  14056. if (!this.editor.isChromelessView())
  14057. {
  14058. this.showLayersDialog();
  14059. }
  14060. else if (this.editor.graph.isLightboxView())
  14061. {
  14062. this.lightboxFit();
  14063. }
  14064. if (this.chromelessResize)
  14065. {
  14066. this.chromelessResize();
  14067. }
  14068. this.editor.undoManager.clear();
  14069. this.editor.modified = (modified != null) ? modified : false;
  14070. this.updateUi();
  14071. // Workaround for no initial focus in FF
  14072. // (does not work in Conf Cloud with FF)
  14073. if (window.self !== window.top)
  14074. {
  14075. window.focus();
  14076. }
  14077. if (this.format != null)
  14078. {
  14079. this.format.refresh();
  14080. }
  14081. }));
  14082. }
  14083. }
  14084. };
  14085. /**
  14086. * Shows the layers dialog if the graph has more than one layer.
  14087. */
  14088. EditorUi.prototype.showLayersDialog = function()
  14089. {
  14090. if (this.editor.graph.getModel().getChildCount(this.editor.graph.getModel().getRoot()) > 1)
  14091. {
  14092. if (this.actions.layersWindow == null)
  14093. {
  14094. this.actions.get('layers').funct();
  14095. }
  14096. else
  14097. {
  14098. this.actions.layersWindow.window.setVisible(true);
  14099. }
  14100. }
  14101. };
  14102. /**
  14103. * Tries to find a public URL for the given file.
  14104. */
  14105. EditorUi.prototype.getPublicUrl = function(file, fn)
  14106. {
  14107. if (file != null)
  14108. {
  14109. if (this.spinner.spin(document.body, mxResources.get('loading')))
  14110. {
  14111. file.getPublicUrl(mxUtils.bind(this, function(url)
  14112. {
  14113. this.spinner.stop();
  14114. fn(url);
  14115. }));
  14116. }
  14117. }
  14118. else
  14119. {
  14120. fn(null);
  14121. }
  14122. };
  14123. /**
  14124. * Adds the buttons for embedded mode.
  14125. */
  14126. EditorUi.prototype.createLoadMessage = function(eventName)
  14127. {
  14128. var graph = this.editor.graph;
  14129. return {event: eventName, pageVisible: graph.pageVisible, translate: graph.view.translate,
  14130. bounds: graph.getGraphBounds(), currentPage: this.getSelectedPageIndex(),
  14131. scale: graph.view.scale, page: graph.view.getBackgroundPageBounds()};
  14132. };
  14133. /**
  14134. * Adds the buttons for embedded mode.
  14135. */
  14136. EditorUi.prototype.sendEmbeddedSvgExport = function(noExit)
  14137. {
  14138. try
  14139. {
  14140. var graph = this.editor.graph;
  14141. if (graph.isEditing())
  14142. {
  14143. graph.stopEditing(!graph.isInvokesStopCellEditing());
  14144. }
  14145. var parent = window.opener || window.parent;
  14146. if (!this.editor.modified)
  14147. {
  14148. if (!noExit)
  14149. {
  14150. parent.postMessage(JSON.stringify({event: 'exit',
  14151. point: this.embedExitPoint}), '*');
  14152. }
  14153. }
  14154. else
  14155. {
  14156. var bg = graph.background;
  14157. if (bg == null || bg == mxConstants.NONE)
  14158. {
  14159. bg = this.embedExportBackground;
  14160. }
  14161. this.getEmbeddedSvg(this.getFileData(true, null, null, null, null,
  14162. null, null, null, null, false), graph, null, true,
  14163. mxUtils.bind(this, function(svg)
  14164. {
  14165. parent.postMessage(JSON.stringify({
  14166. event: 'export', point: this.embedExitPoint,
  14167. exit: (noExit != null) ? !noExit : true,
  14168. data: Editor.createSvgDataUri(svg)
  14169. }), '*');
  14170. }), null, null, true, bg, 1, this.embedExportBorder);
  14171. }
  14172. if (!noExit)
  14173. {
  14174. this.diagramContainer.removeAttribute('data-bounds');
  14175. Editor.inlineFullscreen = false;
  14176. graph.model.clear();
  14177. this.editor.undoManager.clear();
  14178. this.setBackgroundImage(null);
  14179. this.editor.modified = false;
  14180. if (urlParams['embed'] != '1')
  14181. {
  14182. this.fireEvent(new mxEventObject('editInlineStop'));
  14183. }
  14184. }
  14185. }
  14186. catch (e)
  14187. {
  14188. if (!noExit)
  14189. {
  14190. this.handleError(e);
  14191. }
  14192. }
  14193. };
  14194. /**
  14195. * Adds the buttons for embedded mode.
  14196. */
  14197. EditorUi.prototype.installMessageHandler = function(fn)
  14198. {
  14199. var changeListener = null;
  14200. var ignoreChange = false;
  14201. var autosave = false;
  14202. var lastData = null;
  14203. var updateStatus = mxUtils.bind(this, function(sender, eventObject)
  14204. {
  14205. if (!this.editor.modified || urlParams['modified'] == '0')
  14206. {
  14207. this.editor.setStatus('');
  14208. }
  14209. else if (urlParams['modified'] != null)
  14210. {
  14211. this.editor.setStatus(mxUtils.htmlEntities(mxResources.get(urlParams['modified'])));
  14212. }
  14213. });
  14214. this.editor.graph.model.addListener(mxEvent.CHANGE, updateStatus);
  14215. // Receives XML message from opener and puts it into the graph
  14216. mxEvent.addListener(window, 'message', mxUtils.bind(this, function(evt)
  14217. {
  14218. var validSource = window.opener || window.parent;
  14219. if (evt.source != validSource)
  14220. {
  14221. return;
  14222. }
  14223. var data = evt.data;
  14224. var afterLoad = null;
  14225. var extractDiagramXml = mxUtils.bind(this, function(data)
  14226. {
  14227. if (data != null && typeof data.charAt === 'function' && data.charAt(0) != '<')
  14228. {
  14229. try
  14230. {
  14231. if (Editor.isPngDataUrl(data))
  14232. {
  14233. data = Editor.extractGraphModelFromPng(data);
  14234. }
  14235. else if (data.substring(0, 26) == 'data:image/svg+xml;base64,')
  14236. {
  14237. data = atob(data.substring(26));
  14238. }
  14239. else if (data.substring(0, 24) == 'data:image/svg+xml;utf8,')
  14240. {
  14241. data = data.substring(24);
  14242. }
  14243. if (data != null)
  14244. {
  14245. if (data.trim() == '')
  14246. {
  14247. data = null; // nextcloud case
  14248. }
  14249. else if (data.charAt(0) == '%')
  14250. {
  14251. data = decodeURIComponent(data);
  14252. }
  14253. else if (data.charAt(0) != '<')
  14254. {
  14255. data = Graph.decompress(data);
  14256. }
  14257. }
  14258. }
  14259. catch (e)
  14260. {
  14261. // ignore compression errors and use empty data
  14262. }
  14263. }
  14264. return data;
  14265. });
  14266. if (urlParams['proto'] == 'json')
  14267. {
  14268. var convertToSketch = false;
  14269. try
  14270. {
  14271. data = JSON.parse(data);
  14272. EditorUi.debug('EditorUi.installMessageHandler',
  14273. [this], 'evt', [evt], 'data', [data]);
  14274. }
  14275. catch (e)
  14276. {
  14277. data = null;
  14278. }
  14279. try
  14280. {
  14281. if (data == null)
  14282. {
  14283. // Ignore
  14284. return;
  14285. }
  14286. else if (data.action == 'dialog')
  14287. {
  14288. this.showError((data.titleKey != null) ? mxResources.get(data.titleKey) :
  14289. data.title, (data.messageKey != null) ? mxResources.get(data.messageKey) :
  14290. mxUtils.htmlEntities(data.message), (data.buttonKey != null) ?
  14291. mxResources.get(data.buttonKey) : data.button);
  14292. if (data.modified != null)
  14293. {
  14294. this.editor.modified = data.modified;
  14295. }
  14296. return;
  14297. }
  14298. else if (data.action == 'layout')
  14299. {
  14300. this.executeLayouts(this.editor.graph.createLayouts(data.layouts));
  14301. return;
  14302. }
  14303. else if (data.action == 'prompt')
  14304. {
  14305. this.spinner.stop();
  14306. var dlg = new FilenameDialog(this, data.defaultValue || '',
  14307. (data.okKey != null) ? mxResources.get(data.okKey) : data.ok, function(value)
  14308. {
  14309. if (value != null)
  14310. {
  14311. parent.postMessage(JSON.stringify({event: 'prompt', value: value, message: data}), '*');
  14312. }
  14313. else
  14314. {
  14315. parent.postMessage(JSON.stringify({event: 'prompt-cancel', message: data}), '*');
  14316. }
  14317. }, (data.titleKey != null) ? mxResources.get(data.titleKey) : data.title,
  14318. null, null, null, null, function()
  14319. {
  14320. parent.postMessage(JSON.stringify({event: 'prompt-cancel', message: data}), '*');
  14321. });
  14322. this.showDialog(dlg.container, 300, 80, true, false);
  14323. dlg.init();
  14324. return;
  14325. }
  14326. else if (data.action == 'draft')
  14327. {
  14328. var tmp = extractDiagramXml(data.xml);
  14329. this.spinner.stop();
  14330. var dlg = new DraftDialog(this, mxResources.get('draftFound',
  14331. [data.name || this.defaultFilename]),
  14332. tmp, mxUtils.bind(this, function()
  14333. {
  14334. this.hideDialog();
  14335. parent.postMessage(JSON.stringify({event: 'draft',
  14336. result: 'edit', message: data}), '*');
  14337. }), mxUtils.bind(this, function()
  14338. {
  14339. this.hideDialog();
  14340. parent.postMessage(JSON.stringify({event: 'draft',
  14341. result: 'discard', message: data}), '*');
  14342. }), (data.editKey) ? mxResources.get(data.editKey) : null,
  14343. (data.discardKey) ? mxResources.get(data.discardKey) : null,
  14344. (data.ignore) ? mxUtils.bind(this, function()
  14345. {
  14346. this.hideDialog();
  14347. parent.postMessage(JSON.stringify({event: 'draft',
  14348. result: 'ignore', message: data}), '*');
  14349. }) : null);
  14350. this.showDialog(dlg.container, 640, 480, true, false, mxUtils.bind(this, function(cancel)
  14351. {
  14352. if (cancel)
  14353. {
  14354. this.actions.get('exit').funct();
  14355. }
  14356. }));
  14357. try
  14358. {
  14359. dlg.init();
  14360. }
  14361. catch (e)
  14362. {
  14363. parent.postMessage(JSON.stringify({event: 'draft',
  14364. error: e.toString(), message: data}), '*');
  14365. }
  14366. return;
  14367. }
  14368. else if (data.action == 'template')
  14369. {
  14370. this.spinner.stop();
  14371. var enableRecentDocs = data.enableRecent == 1;
  14372. var enableSearchDocs = data.enableSearch == 1;
  14373. var enableCustomTemp = data.enableCustomTemp == 1;
  14374. var dlg = new NewDialog(this, false, data.templatesOnly? false : data.callback != null,
  14375. mxUtils.bind(this, function(xml, name, url, libs)
  14376. {
  14377. xml = xml || this.emptyDiagramXml;
  14378. // LATER: Add autosave option in template message
  14379. if (data.callback != null)
  14380. {
  14381. parent.postMessage(JSON.stringify({event: 'template', xml: xml,
  14382. blank: xml == this.emptyDiagramXml, name: name,
  14383. tempUrl: url, libs: libs, builtIn: true,
  14384. message: data}), '*');
  14385. }
  14386. else
  14387. {
  14388. fn(xml, evt, xml != this.emptyDiagramXml, data.toSketch);
  14389. // Workaround for status updated before modified applied
  14390. if (!this.editor.modified)
  14391. {
  14392. this.editor.setStatus('');
  14393. }
  14394. }
  14395. }), null, null, null, null, null, null, null,
  14396. enableRecentDocs? mxUtils.bind(this, function(recentReadyCallback)
  14397. {
  14398. this.remoteInvoke('getRecentDiagrams', [null], null, recentReadyCallback, function()
  14399. {
  14400. recentReadyCallback(null, 'Network Error!');
  14401. });
  14402. }) : null,
  14403. enableSearchDocs? mxUtils.bind(this, function(searchStr, searchReadyCallback)
  14404. {
  14405. this.remoteInvoke('searchDiagrams', [searchStr, null], null, searchReadyCallback, function()
  14406. {
  14407. searchReadyCallback(null, 'Network Error!');
  14408. });
  14409. }) : null,
  14410. mxUtils.bind(this, function(url, info, name)
  14411. {
  14412. //If binary files are possible, we can get the file content using remote invokation, imported it, and send final mxFile back
  14413. parent.postMessage(JSON.stringify({event: 'template', docUrl: url, info: info,
  14414. name: name}), '*');
  14415. }), null, null,
  14416. enableCustomTemp? mxUtils.bind(this, function(customTempCallback)
  14417. {
  14418. this.remoteInvoke('getCustomTemplates', null, null, customTempCallback, function()
  14419. {
  14420. customTempCallback({}, 0); //ignore error by sending empty templates
  14421. });
  14422. }) : null, data.withoutType == 1);
  14423. this.showDialog(dlg.container, 620, 460, true, false, mxUtils.bind(this, function(cancel)
  14424. {
  14425. if (this.sidebar != null)
  14426. {
  14427. this.sidebar.hideTooltip();
  14428. }
  14429. if (cancel)
  14430. {
  14431. this.actions.get('exit').funct();
  14432. }
  14433. }));
  14434. dlg.init();
  14435. return;
  14436. }
  14437. else if (data.action == 'textContent')
  14438. {
  14439. //TODO Remove this message and use remote invokation instead
  14440. var allPagesTxt = this.getDiagramTextContent();
  14441. parent.postMessage(JSON.stringify({event: 'textContent',
  14442. data: allPagesTxt, message: data}), '*');
  14443. return;
  14444. }
  14445. else if (data.action == 'status')
  14446. {
  14447. if (data.messageKey != null)
  14448. {
  14449. this.editor.setStatus(mxUtils.htmlEntities(mxResources.get(data.messageKey)));
  14450. }
  14451. else if (data.message != null)
  14452. {
  14453. this.editor.setStatus(mxUtils.htmlEntities(data.message));
  14454. }
  14455. if (data.modified != null)
  14456. {
  14457. this.editor.modified = data.modified;
  14458. }
  14459. return;
  14460. }
  14461. else if (data.action == 'spinner')
  14462. {
  14463. var msg = (data.messageKey != null) ? mxResources.get(data.messageKey) : data.message;
  14464. if (data.show != null && !data.show)
  14465. {
  14466. this.spinner.stop();
  14467. }
  14468. else
  14469. {
  14470. this.spinner.spin(document.body, msg)
  14471. }
  14472. return;
  14473. }
  14474. else if (data.action == 'exit')
  14475. {
  14476. this.actions.get('exit').funct();
  14477. return;
  14478. }
  14479. else if (data.action == 'viewport')
  14480. {
  14481. if (data.viewport != null)
  14482. {
  14483. this.embedViewport = data.viewport;
  14484. this.editor.graph.refresh();
  14485. this.fireEvent(new mxEventObject('embedViewportChanged'));
  14486. }
  14487. return;
  14488. }
  14489. else if (data.action == 'fullscreenChanged')
  14490. {
  14491. var scrollState = null;
  14492. try
  14493. {
  14494. var temp = this.diagramContainer.getAttribute('data-scrollState');
  14495. if (temp != null)
  14496. {
  14497. this.diagramContainer.removeAttribute('data-scrollState');
  14498. scrollState = JSON.parse(temp);
  14499. }
  14500. }
  14501. catch (e)
  14502. {
  14503. // ignore
  14504. }
  14505. Editor.inlineFullscreen = data.value;
  14506. this.fireEvent(new mxEventObject('inlineFullscreenChanged'));
  14507. if (scrollState != null)
  14508. {
  14509. this.restoreScrollState(scrollState);
  14510. }
  14511. return;
  14512. }
  14513. else if (data.action == 'snapshot')
  14514. {
  14515. this.sendEmbeddedSvgExport(true);
  14516. return;
  14517. }
  14518. else if (data.action == 'export')
  14519. {
  14520. if (data.format == 'png' || data.format == 'xmlpng')
  14521. {
  14522. if ((data.spin == null && data.spinKey == null) || this.spinner.spin(document.body,
  14523. (data.spinKey != null) ? mxResources.get(data.spinKey) : data.spin))
  14524. {
  14525. var xml = (data.xml != null) ? data.xml : this.getFileData(true);
  14526. this.editor.graph.setEnabled(false);
  14527. var graph = this.editor.graph;
  14528. var postDataBack = mxUtils.bind(this, function(uri, svg)
  14529. {
  14530. if (data.withSvg)
  14531. {
  14532. data.withSvg = false;
  14533. this.getEmbeddedSvg(xml, this.editor.graph, null, true, function(svg)
  14534. {
  14535. postDataBack(uri, svg);
  14536. }, null, null, data.embedImages, this.editor.graph.background,
  14537. data.scale, data.border, data.shadow);
  14538. return;
  14539. }
  14540. this.editor.graph.setEnabled(true);
  14541. this.spinner.stop();
  14542. var msg = this.createLoadMessage('export');
  14543. msg.format = data.format;
  14544. msg.message = data;
  14545. msg.data = uri;
  14546. msg.svg = svg;
  14547. msg.xml = xml;
  14548. parent.postMessage(JSON.stringify(msg), '*');
  14549. });
  14550. var processUri = mxUtils.bind(this, function(uri)
  14551. {
  14552. if (uri == null)
  14553. {
  14554. uri = Editor.blankImage;
  14555. }
  14556. if (data.format == 'xmlpng')
  14557. {
  14558. uri = Editor.writeGraphModelToPng(uri, 'tEXt', 'mxfile',
  14559. encodeURIComponent(xml));
  14560. }
  14561. // Removes temporary graph from DOM
  14562. if (graph != this.editor.graph)
  14563. {
  14564. graph.container.parentNode.removeChild(graph.container);
  14565. }
  14566. postDataBack(uri);
  14567. });
  14568. var pageId = data.pageId || (this.pages != null? ((data.currentPage) ?
  14569. this.currentPage.getId() : this.pages[0].getId()) : null);
  14570. if (this.isExportToCanvas())
  14571. {
  14572. var graphReady = mxUtils.bind(this, function()
  14573. {
  14574. // Exports PNG for first/specific page while other page is visible by creating a graph
  14575. // LATER: Add caching for the graph or SVG while not on first page
  14576. if (this.pages != null && this.currentPage.getId() != pageId)
  14577. {
  14578. var graphGetGlobalVariable = graph.getGlobalVariable;
  14579. graph = this.createTemporaryGraph(graph.getStylesheet());
  14580. var page;
  14581. for (var i = 0; i < this.pages.length; i++)
  14582. {
  14583. if (this.pages[i].getId() == pageId)
  14584. {
  14585. page = this.updatePageRoot(this.pages[i]);
  14586. break;
  14587. }
  14588. }
  14589. //If pageId info is incorrect
  14590. if (page == null)
  14591. {
  14592. page = this.currentPage;
  14593. }
  14594. graph.getGlobalVariable = function(name)
  14595. {
  14596. if (name == 'page')
  14597. {
  14598. return page.getName();
  14599. }
  14600. else if (name == 'pagenumber')
  14601. {
  14602. return 1;
  14603. }
  14604. return graphGetGlobalVariable.apply(this, arguments);
  14605. };
  14606. document.body.appendChild(graph.container);
  14607. graph.model.setRoot(page.root);
  14608. }
  14609. // Set visible layers based on message setting
  14610. if (data.layerIds != null)
  14611. {
  14612. var graphModel = graph.model;
  14613. var layers = graphModel.getChildCells(graphModel.getRoot());
  14614. var layerIdsMap = {};
  14615. for (var i = 0; i < data.layerIds.length; i++)
  14616. {
  14617. layerIdsMap[data.layerIds[i]] = true;
  14618. }
  14619. for (var i = 0; i < layers.length; i++)
  14620. {
  14621. graphModel.setVisible(layers[i], layerIdsMap[layers[i].id] || false);
  14622. }
  14623. }
  14624. var theme = null;
  14625. if (data.keepTheme)
  14626. {
  14627. theme = (Editor.cssDarkMode || Editor.isDarkMode()) ? 'dark' : 'light'
  14628. }
  14629. this.editor.exportToCanvas(mxUtils.bind(this, function(canvas)
  14630. {
  14631. processUri(canvas.toDataURL('image/png'));
  14632. }), data.width, null, data.background, mxUtils.bind(this, function()
  14633. {
  14634. processUri(null);
  14635. }), null, null, data.scale, data.transparent, data.shadow,
  14636. null, graph, data.border, null, data.grid, theme);
  14637. });
  14638. // Uses optional XML from incoming message
  14639. if (data.xml != null && data.xml.length > 0)
  14640. {
  14641. ignoreChange = true;
  14642. this.setFileData(xml);
  14643. ignoreChange = false;
  14644. }
  14645. graphReady();
  14646. }
  14647. else
  14648. {
  14649. // Data from server is base64 encoded to avoid binary XHR
  14650. // Double encoding for XML arg is needed for UTF8 encoding
  14651. var req = new mxXmlRequest(EXPORT_URL, 'format=png&embedXml=' +
  14652. ((data.format == 'xmlpng') ? '1' : '0') +
  14653. (pageId != null? '&pageId=' + pageId : '') +
  14654. (data.layerIds != null && data.layerIds.length > 0?
  14655. '&extras=' + encodeURIComponent(JSON.stringify({layerIds: data.layerIds})) : '') +
  14656. (data.scale != null? '&scale=' + data.scale : '') +'&base64=1&xml=' +
  14657. encodeURIComponent(xml));
  14658. req.send(mxUtils.bind(this, function(req)
  14659. {
  14660. // Temp graph was never created at this point so we can
  14661. // skip processUri since it already contains the XML
  14662. if (req.getStatus() >= 200 && req.getStatus() <= 299)
  14663. {
  14664. postDataBack('data:image/png;base64,' + req.getText());
  14665. }
  14666. else
  14667. {
  14668. processUri(null);
  14669. }
  14670. }), mxUtils.bind(this, function()
  14671. {
  14672. processUri(null);
  14673. }));
  14674. }
  14675. }
  14676. }
  14677. else
  14678. {
  14679. var graphReady = mxUtils.bind(this, function()
  14680. {
  14681. var msg = this.createLoadMessage('export');
  14682. // Attaches incoming message
  14683. msg.message = data;
  14684. // Forces new HTML format if pages exists
  14685. if (data.format == 'html2' || (data.format == 'html' && (urlParams['pages'] != '0' ||
  14686. (this.pages != null && this.pages.length > 1))))
  14687. {
  14688. var node = this.getXmlFileData();
  14689. msg.xml = mxUtils.getXml(node);
  14690. msg.data = this.getFileData(null, null, true, null, null, null, node);
  14691. msg.format = data.format;
  14692. }
  14693. else if (data.format == 'html')
  14694. {
  14695. var xml = this.editor.getGraphXml();
  14696. msg.data = this.getHtml(xml, this.editor.graph);
  14697. msg.xml = mxUtils.getXml(xml);
  14698. msg.format = data.format;
  14699. }
  14700. else
  14701. {
  14702. // Creates a preview with no alt text for unsupported browsers
  14703. mxSvgCanvas2D.prototype.foAltText = null;
  14704. var bg = (data.background != null) ? data.background : this.editor.graph.background;
  14705. if (bg == mxConstants.NONE)
  14706. {
  14707. bg = null;
  14708. }
  14709. msg.xml = this.getFileData(true, null, null, null, null,
  14710. null, null, null, null, false);
  14711. msg.format = 'svg';
  14712. var postResult = mxUtils.bind(this, function(svg)
  14713. {
  14714. this.editor.graph.setEnabled(true);
  14715. this.spinner.stop();
  14716. msg.data = Editor.createSvgDataUri(svg);
  14717. parent.postMessage(JSON.stringify(msg), '*');
  14718. });
  14719. var theme = null;
  14720. if (data.keepTheme)
  14721. {
  14722. theme = (Editor.cssDarkMode || Editor.isDarkMode()) ? 'dark' : 'light'
  14723. }
  14724. if (data.format == 'xmlsvg')
  14725. {
  14726. if ((data.spin == null && data.spinKey == null) || this.spinner.spin(document.body,
  14727. (data.spinKey != null) ? mxResources.get(data.spinKey) : data.spin))
  14728. {
  14729. this.getEmbeddedSvg(msg.xml, this.editor.graph, null, true, postResult, null, null,
  14730. data.embedImages, bg, data.scale, data.border, data.shadow, theme);
  14731. }
  14732. }
  14733. else
  14734. {
  14735. if ((data.spin == null && data.spinKey == null) || this.spinner.spin(document.body,
  14736. (data.spinKey != null) ? mxResources.get(data.spinKey) : data.spin))
  14737. {
  14738. this.editor.graph.setEnabled(false);
  14739. var svgRoot = this.editor.graph.getSvg(bg, data.scale, data.border, null, null,
  14740. null, null, null, null, this.editor.graph.shadowVisible || data.shadow,
  14741. null, theme);
  14742. if (this.editor.graph.shadowVisible || data.shadow)
  14743. {
  14744. this.editor.graph.addSvgShadow(svgRoot);
  14745. }
  14746. this.embedFonts(svgRoot, mxUtils.bind(this, function(svgRoot)
  14747. {
  14748. if (data.embedImages || data.embedImages == null)
  14749. {
  14750. this.editor.convertImages(svgRoot, mxUtils.bind(this, function(svgRoot)
  14751. {
  14752. postResult(mxUtils.getXml(svgRoot));
  14753. }));
  14754. }
  14755. else
  14756. {
  14757. postResult(mxUtils.getXml(svgRoot));
  14758. }
  14759. }));
  14760. }
  14761. }
  14762. return;
  14763. }
  14764. parent.postMessage(JSON.stringify(msg), '*');
  14765. });
  14766. // SVG is generated from graph so parse optional XML
  14767. if (data.xml != null && data.xml.length > 0)
  14768. {
  14769. if (this.editor.graph.mathEnabled)
  14770. {
  14771. // Waits for MathJax autoloading and rendering
  14772. var editorOnMathJaxDone = Editor.onMathJaxDone;
  14773. Editor.onMathJaxDone = function()
  14774. {
  14775. editorOnMathJaxDone.apply(this, arguments);
  14776. graphReady();
  14777. };
  14778. }
  14779. ignoreChange = true;
  14780. this.setFileData(data.xml);
  14781. ignoreChange = false;
  14782. if (!this.editor.graph.mathEnabled)
  14783. {
  14784. graphReady();
  14785. }
  14786. }
  14787. else
  14788. {
  14789. graphReady();
  14790. }
  14791. }
  14792. return;
  14793. }
  14794. else if (data.action == 'load')
  14795. {
  14796. convertToSketch = data.toSketch;
  14797. autosave = data.autosave == 1;
  14798. this.hideDialog();
  14799. if (data.modified != null && urlParams['modified'] == null)
  14800. {
  14801. urlParams['modified'] = data.modified;
  14802. }
  14803. if (data.saveAndExit != null && urlParams['saveAndExit'] == null)
  14804. {
  14805. urlParams['saveAndExit'] = data.saveAndExit;
  14806. }
  14807. if (data.noSaveBtn != null && urlParams['noSaveBtn'] == null)
  14808. {
  14809. urlParams['noSaveBtn'] = data.noSaveBtn;
  14810. }
  14811. if (data.rough != null)
  14812. {
  14813. var initial = Editor.sketchMode;
  14814. this.doSetSketchMode(data.rough);
  14815. if (initial != Editor.sketchMode)
  14816. {
  14817. this.fireEvent(new mxEventObject('sketchModeChanged'));
  14818. }
  14819. }
  14820. if (data.dark != null)
  14821. {
  14822. this.setDarkMode(data.dark);
  14823. }
  14824. if (data.border != null)
  14825. {
  14826. this.embedExportBorder = data.border;
  14827. }
  14828. if (data.background != null)
  14829. {
  14830. this.embedExportBackground = data.background;
  14831. }
  14832. if (data.viewport != null)
  14833. {
  14834. this.embedViewport = data.viewport;
  14835. }
  14836. this.embedExitPoint = null;
  14837. if (data.rect != null)
  14838. {
  14839. var border = this.embedExportBorder;
  14840. this.diagramContainer.style.left = Math.max(60, data.rect.left) + 'px';
  14841. this.diagramContainer.style.top = Math.max(40, data.rect.top) + 'px';
  14842. // Inline min width and height
  14843. this.minInlineWidth = data.minWidth;
  14844. this.minInlineHeight = data.minHeight;
  14845. this.diagramContainer.style.border = '2px solid #295fcc';
  14846. this.diagramContainer.style.bottom = '';
  14847. this.diagramContainer.style.right = '';
  14848. // Data is extracted diagram in async code
  14849. var maxFitScale = data.maxFitScale;
  14850. var w = data.rect.width + 2 * border;
  14851. var h0 = data.rect.height + 2 * border;
  14852. afterLoad = mxUtils.bind(this, function()
  14853. {
  14854. var ds = mxUtils.getDocumentSize();
  14855. w = Math.min((this.minInlineWidth != null) ? Math.max(
  14856. this.minInlineWidth, w) : w, ds.width - 80);
  14857. var h = Math.min((this.minInlineHeight != null) ? Math.max(
  14858. this.minInlineHeight, h0) : h0, ds.height - 80);
  14859. this.diagramContainer.style.width = w + 'px';
  14860. this.diagramContainer.style.height = h + 'px';
  14861. var graph = this.editor.graph;
  14862. var prev = graph.maxFitScale;
  14863. graph.maxFitScale = maxFitScale;
  14864. graph.fit(2 * border, null, null, null, null, null, h0);
  14865. this.setPageVisible(false);
  14866. if (this.minInlineWidth != null &&
  14867. graph.getGraphBounds().width < this.minInlineWidth)
  14868. {
  14869. var dy = graph.container.scrollTop;
  14870. this.resetScrollbars();
  14871. graph.container.scrollTop = dy;
  14872. }
  14873. graph.maxFitScale = prev;
  14874. graph.container.scrollTop -= border;
  14875. graph.container.scrollLeft -= border;
  14876. window.setTimeout(mxUtils.bind(this, function()
  14877. {
  14878. this.fireEvent(new mxEventObject('editInlineStart', 'data', [data]));
  14879. graph.container.focus();
  14880. // Moves format window to top of graph
  14881. if (this.formatWindow != null &&
  14882. this.formatWindow.window != null &&
  14883. this.formatWindow.window.isVisible())
  14884. {
  14885. this.formatWindow.window.div.style.top =
  14886. graph.container.style.top;
  14887. }
  14888. // Centers horizontally
  14889. var bounds = graph.getGraphBounds();
  14890. if (graph.container.clientWidth > bounds.width + 2 * border)
  14891. {
  14892. graph.container.scrollLeft = bounds.x + ((bounds.width +
  14893. border) - graph.container.clientWidth) / 2;
  14894. }
  14895. }), 10);
  14896. });
  14897. }
  14898. if (data.noExitBtn != null && urlParams['noExitBtn'] == null)
  14899. {
  14900. urlParams['noExitBtn'] = data.noExitBtn;
  14901. }
  14902. if (data.title != null && this.buttonContainer != null)
  14903. {
  14904. var tmp = this.createStatusDiv('');
  14905. mxUtils.write(tmp, data.title);
  14906. if (this.embedFilenameSpan != null)
  14907. {
  14908. this.embedFilenameSpan.parentNode.removeChild(this.embedFilenameSpan);
  14909. }
  14910. this.buttonContainer.appendChild(tmp);
  14911. this.embedFilenameSpan = tmp;
  14912. }
  14913. try
  14914. {
  14915. if (data.libs)
  14916. {
  14917. this.sidebar.showEntries(data.libs);
  14918. }
  14919. }
  14920. catch(e){}
  14921. if (data.xmlpng != null)
  14922. {
  14923. data = this.extractGraphModelFromPng(data.xmlpng);
  14924. }
  14925. else if (data.descriptor != null)
  14926. {
  14927. data = data.descriptor;
  14928. }
  14929. else
  14930. {
  14931. data = data.xml;
  14932. }
  14933. }
  14934. else if (data.action == 'merge')
  14935. {
  14936. var file = this.getCurrentFile();
  14937. if (file != null)
  14938. {
  14939. var tmp = extractDiagramXml(data.xml);
  14940. if (tmp != null && tmp != '')
  14941. {
  14942. file.mergeFile(new LocalFile(this, tmp), function()
  14943. {
  14944. parent.postMessage(JSON.stringify({event: 'merge', message: data}), '*');
  14945. }, function(err)
  14946. {
  14947. parent.postMessage(JSON.stringify({event: 'merge', message: data, error: err}), '*');
  14948. });
  14949. }
  14950. }
  14951. return;
  14952. }
  14953. else if (data.action == 'remoteInvokeReady')
  14954. {
  14955. this.handleRemoteInvokeReady(parent);
  14956. return;
  14957. }
  14958. else if (data.action == 'remoteInvoke')
  14959. {
  14960. this.handleRemoteInvoke(data, evt.origin);
  14961. return;
  14962. }
  14963. else if (data.action == 'remoteInvokeResponse')
  14964. {
  14965. this.handleRemoteInvokeResponse(data);
  14966. return;
  14967. }
  14968. else
  14969. {
  14970. // Unknown message must stop execution
  14971. parent.postMessage(JSON.stringify({error: 'unknownMessage', data: JSON.stringify(data)}), '*');
  14972. return;
  14973. }
  14974. }
  14975. catch (e)
  14976. {
  14977. // TODO: Block handling of more messages when in error state
  14978. this.handleError(e);
  14979. }
  14980. }
  14981. var getData = mxUtils.bind(this, function()
  14982. {
  14983. return (urlParams['pages'] != '0' || (this.pages != null && this.pages.length > 1)) ?
  14984. this.getFileData(true): mxUtils.getXml(this.editor.getGraphXml());
  14985. });
  14986. var doLoad = mxUtils.bind(this, function(data, evt)
  14987. {
  14988. ignoreChange = true;
  14989. try
  14990. {
  14991. fn(data, evt, null, convertToSketch);
  14992. }
  14993. catch (e)
  14994. {
  14995. this.handleError(e);
  14996. }
  14997. ignoreChange = false;
  14998. if (urlParams['modified'] != null)
  14999. {
  15000. this.editor.setStatus('');
  15001. }
  15002. lastData = getData();
  15003. if (autosave && changeListener == null)
  15004. {
  15005. changeListener = mxUtils.bind(this, function(sender, eventObject)
  15006. {
  15007. var data = getData();
  15008. if (data != lastData && !ignoreChange)
  15009. {
  15010. var msg = this.createLoadMessage('autosave');
  15011. msg.xml = data;
  15012. var parent = window.opener || window.parent;
  15013. parent.postMessage(JSON.stringify(msg), '*');
  15014. }
  15015. lastData = data;
  15016. });
  15017. this.editor.graph.model.addListener(mxEvent.CHANGE, changeListener);
  15018. // Some options trigger autosave
  15019. this.editor.graph.addListener('gridSizeChanged', changeListener);
  15020. this.editor.graph.addListener('shadowVisibleChanged', changeListener);
  15021. this.addListener('pageFormatChanged', changeListener);
  15022. this.addListener('pageScaleChanged', changeListener);
  15023. this.addListener('backgroundColorChanged', changeListener);
  15024. this.addListener('backgroundImageChanged', changeListener);
  15025. this.addListener('foldingEnabledChanged', changeListener);
  15026. this.addListener('mathEnabledChanged', changeListener);
  15027. this.addListener('gridEnabledChanged', changeListener);
  15028. this.addListener('guidesEnabledChanged', changeListener);
  15029. this.addListener('pageViewChanged', changeListener);
  15030. }
  15031. // Sends the bounds of the graph to the host after parsing
  15032. if (urlParams['returnbounds'] == '1' || urlParams['proto'] == 'json')
  15033. {
  15034. var resp = this.createLoadMessage('load');
  15035. // Attaches XML to response
  15036. resp.xml = data;
  15037. parent.postMessage(JSON.stringify(resp), '*');
  15038. }
  15039. if (afterLoad != null)
  15040. {
  15041. afterLoad();
  15042. }
  15043. });
  15044. if (data != null && typeof data.substring === 'function' && data.substring(0, 34) == 'data:application/vnd.visio;base64,')
  15045. {
  15046. // Checks VND binary magic number in base64
  15047. var filename = (data.substring(34, 45) == '0M8R4KGxGuE') ? 'raw.vsd' : 'raw.vsdx';
  15048. this.importVisio(this.base64ToBlob(data.substring(data.indexOf(',') + 1)), function(xml)
  15049. {
  15050. doLoad(xml, evt);
  15051. }, mxUtils.bind(this, function(e)
  15052. {
  15053. this.handleError(e);
  15054. }), filename);
  15055. }
  15056. else if (data != null && typeof data.substring === 'function' && new XMLHttpRequest().upload && this.isRemoteFileFormat(data, ''))
  15057. {
  15058. if (this.isOffline())
  15059. {
  15060. this.showError(mxResources.get('error'), mxResources.get('notInOffline'));
  15061. }
  15062. else
  15063. {
  15064. // Asynchronous parsing via server
  15065. this.parseFileData(data, mxUtils.bind(this, function(xhr)
  15066. {
  15067. if (xhr.readyState == 4)
  15068. {
  15069. if (xhr.status >= 200 && xhr.status <= 299 &&
  15070. xhr.responseText.substring(0, 13) == '<mxGraphModel')
  15071. {
  15072. doLoad(xhr.responseText, evt);
  15073. }
  15074. else
  15075. {
  15076. this.handleError({message: xhr.status == 413? mxResources.get('diagramTooLarge') :
  15077. mxResources.get('unknownError')});
  15078. }
  15079. }
  15080. }), '');
  15081. }
  15082. }
  15083. else if (data != null && typeof data.substring === 'function' && this.isLucidChartData(data))
  15084. {
  15085. this.convertLucidChart(data, mxUtils.bind(this, function(xml)
  15086. {
  15087. doLoad(xml);
  15088. }), mxUtils.bind(this, function(e)
  15089. {
  15090. this.handleError(e);
  15091. }));
  15092. }
  15093. else if (data != null && typeof data === 'object' && data.format != null && (data.data != null || data.url != null))
  15094. {
  15095. this.loadDescriptor(data, mxUtils.bind(this, function(e)
  15096. {
  15097. doLoad(getData(), evt);
  15098. }), mxUtils.bind(this, function(e)
  15099. {
  15100. this.handleError(e, mxResources.get('errorLoadingFile'));
  15101. }));
  15102. }
  15103. else
  15104. {
  15105. data = extractDiagramXml(data);
  15106. doLoad(data, evt);
  15107. }
  15108. }));
  15109. // Requests data from the sender. This is a workaround for not allowing
  15110. // the opener to listen for the onload event if not in the same origin.
  15111. var parent = window.opener || window.parent;
  15112. var msg = (urlParams['proto'] == 'json') ? JSON.stringify({event: 'init'}) : (urlParams['ready'] || 'ready');
  15113. parent.postMessage(msg, '*');
  15114. // Adds JSON event for opening links
  15115. if (urlParams['proto'] == 'json')
  15116. {
  15117. var graphOpenLink = this.editor.graph.openLink;
  15118. this.editor.graph.openLink = function(href, target, allowOpener)
  15119. {
  15120. graphOpenLink.apply(this, arguments);
  15121. parent.postMessage(JSON.stringify({event: 'openLink', href: href, target: target, allowOpener: allowOpener}), '*');
  15122. };
  15123. }
  15124. };
  15125. /**
  15126. * Adds the buttons for embedded mode.
  15127. */
  15128. EditorUi.prototype.createEmbedButton = function(title, fn, shortcut, primary)
  15129. {
  15130. var modern = (Editor.currentTheme == 'simple' ||
  15131. Editor.currentTheme == 'sketch' ||
  15132. Editor.currentTheme == 'min');
  15133. var btn = document.createElement('button');
  15134. btn.setAttribute('title', title + ((shortcut != null) ?
  15135. ' (' + shortcut + ')' : ''));
  15136. btn.style.marginLeft = '6px';
  15137. mxUtils.write(btn, title);
  15138. if (modern)
  15139. {
  15140. btn.className = ((primary) ? 'gePrimaryBtn' : '');
  15141. btn.style.marginLeft = '8px';
  15142. btn.style.padding = '6px';
  15143. }
  15144. else
  15145. {
  15146. btn.className = 'geBigButton' + ((!primary) ?
  15147. ' geBigStandardButton' : '');
  15148. }
  15149. mxEvent.addListener(btn, 'click', fn);
  15150. return btn;
  15151. };
  15152. /**
  15153. * Adds the buttons for embedded mode.
  15154. */
  15155. EditorUi.prototype.addEmbedButtons = function()
  15156. {
  15157. var div = document.createElement('div');
  15158. div.style.display = 'inline-flex';
  15159. div.style.alignItems = 'center';
  15160. div.style.marginLeft = 'auto';
  15161. if (Editor.currentTheme != 'simple' &&
  15162. Editor.currentTheme != 'sketch' &&
  15163. Editor.currentTheme != 'min')
  15164. {
  15165. div.style.marginRight = '66px';
  15166. div.style['float'] = 'right';
  15167. if (Editor.currentTheme == 'atlas')
  15168. {
  15169. div.style.marginTop = '2px';
  15170. }
  15171. }
  15172. var button = document.createElement('button');
  15173. button.className = 'geBigButton';
  15174. if (urlParams['noSaveBtn'] == '1')
  15175. {
  15176. if (urlParams['saveAndExit'] != '0')
  15177. {
  15178. div.appendChild(this.createEmbedButton(urlParams['publishClose'] == '1' ?
  15179. mxResources.get('publish') : mxResources.get('saveAndExit'),
  15180. this.actions.get('saveAndExit').funct, null, true));
  15181. }
  15182. }
  15183. else
  15184. {
  15185. div.appendChild(this.createEmbedButton(mxResources.get('save'), mxUtils.bind(this, function()
  15186. {
  15187. this.actions.get('save').funct(false);
  15188. }), Editor.ctrlKey + '+S', true));
  15189. if (urlParams['saveAndExit'] == '1')
  15190. {
  15191. div.appendChild(this.createEmbedButton(mxResources.get('saveAndExit'),
  15192. this.actions.get('saveAndExit').funct));
  15193. }
  15194. }
  15195. if (urlParams['noExitBtn'] != '1')
  15196. {
  15197. div.appendChild(this.createEmbedButton(urlParams['publishClose'] == '1' ?
  15198. mxResources.get('close') : mxResources.get('exit'),
  15199. this.actions.get('exit').funct));
  15200. }
  15201. if ((Editor.currentTheme == 'simple' ||
  15202. Editor.currentTheme == 'sketch' ||
  15203. Editor.currentTheme == 'min') &&
  15204. this.buttonContainer != null)
  15205. {
  15206. this.buttonContainer.appendChild(div);
  15207. this.editor.fireEvent(new mxEventObject('statusChanged'));
  15208. }
  15209. else if (this.menubar != null)
  15210. {
  15211. this.toolbar.container.appendChild(div);
  15212. this.toolbar.staticElements.push(div);
  15213. }
  15214. };
  15215. /**
  15216. *
  15217. */
  15218. EditorUi.prototype.showImportCsvDialog = function()
  15219. {
  15220. if (this.importCsvDialog == null)
  15221. {
  15222. this.importCsvDialog = new TextareaDialog(this, mxResources.get('csv') + ':',
  15223. Editor.defaultCsvValue, mxUtils.bind(this, function(newValue)
  15224. {
  15225. this.importCsv(newValue);
  15226. }), null, null, 620, 430, null, true, true, mxResources.get('import'),
  15227. !this.isOffline() ? 'https://drawio-app.com/import-from-csv-to-drawio/' : null);
  15228. }
  15229. this.showDialog(this.importCsvDialog.container, 640, 520, true, true, null, null, null, null, true);
  15230. this.importCsvDialog.init();
  15231. };
  15232. /**
  15233. * Loads orgchart layouts and executes the given function.
  15234. */
  15235. EditorUi.prototype.showCustomLayoutDialog = function(value)
  15236. {
  15237. this.loadOrgChartLayouts(mxUtils.bind(this, function()
  15238. {
  15239. var dlg = new TextareaDialog(this, mxResources.get('layout'),
  15240. value, mxUtils.bind(this, function(newValue)
  15241. {
  15242. if (newValue.length > 0)
  15243. {
  15244. try
  15245. {
  15246. var list = JSON.parse(newValue);
  15247. this.executeLayouts(this.editor.graph.createLayouts(list));
  15248. this.customLayoutConfig = list;
  15249. this.hideDialog();
  15250. }
  15251. catch (e)
  15252. {
  15253. this.handleError(e);
  15254. }
  15255. }
  15256. }), null, null, null, null, mxUtils.bind(this, function(buttons, input)
  15257. {
  15258. var copyBtn = mxUtils.button(mxResources.get('copy'), mxUtils.bind(this, function()
  15259. {
  15260. try
  15261. {
  15262. var orig = input.value;
  15263. input.value = JSON.stringify(JSON.parse(orig));
  15264. input.focus();
  15265. if (mxClient.IS_GC || mxClient.IS_FF || document.documentMode >= 5)
  15266. {
  15267. input.select();
  15268. }
  15269. else
  15270. {
  15271. document.execCommand('selectAll', false, null);
  15272. }
  15273. document.execCommand('copy');
  15274. this.alert(mxResources.get('copiedToClipboard'));
  15275. input.value = orig;
  15276. }
  15277. catch (e)
  15278. {
  15279. this.handleError(e);
  15280. }
  15281. }));
  15282. copyBtn.setAttribute('title', 'copy');
  15283. copyBtn.className = 'geBtn';
  15284. buttons.appendChild(copyBtn);
  15285. }), true, null, null, 'https://www.drawio.com/doc/faq/apply-layouts');
  15286. this.showDialog(dlg.container, 620, 460, true, true);
  15287. dlg.init();
  15288. }));
  15289. };
  15290. /**
  15291. * Loads orgchart layouts and executes the given function.
  15292. */
  15293. EditorUi.prototype.loadOrgChartLayouts = function(fn)
  15294. {
  15295. this.createTimeout(null, mxUtils.bind(this, function(timeout)
  15296. {
  15297. var onload = mxUtils.bind(this, function()
  15298. {
  15299. this.loadingOrgChart = false;
  15300. if (timeout.clear())
  15301. {
  15302. Graph.layoutNames.push('mxOrgChartLayout');
  15303. this.spinner.stop();
  15304. fn();
  15305. }
  15306. });
  15307. var onerror = mxUtils.bind(this, function(e)
  15308. {
  15309. this.loadingOrgChart = false;
  15310. if (timeout.clear())
  15311. {
  15312. this.handleError(e);
  15313. }
  15314. });
  15315. if (typeof mxOrgChartLayout === 'undefined' && !this.loadingOrgChart && !this.isOffline(true))
  15316. {
  15317. if (this.spinner.spin(document.body, mxResources.get('loading')))
  15318. {
  15319. this.loadingOrgChart = true;
  15320. if (urlParams['dev'] == '1')
  15321. {
  15322. mxscript('js/orgchart/bridge.min.js', function()
  15323. {
  15324. mxscript('js/orgchart/bridge.collections.min.js', function()
  15325. {
  15326. mxscript('js/orgchart/OrgChart.Layout.min.js', function()
  15327. {
  15328. mxscript('js/orgchart/mxOrgChartLayout.js',
  15329. onload, null, null, null, onerror);
  15330. }, null, null, null, onerror);
  15331. }, null, null, null, onerror);
  15332. }, null, null, null, onerror);
  15333. }
  15334. else
  15335. {
  15336. mxscript(window.DRAWIO_SERVER_URL + 'js/orgchart.min.js', onload, null, null, null, onerror);
  15337. }
  15338. }
  15339. }
  15340. else
  15341. {
  15342. onload();
  15343. }
  15344. }), onerror);
  15345. };
  15346. /**
  15347. *
  15348. */
  15349. EditorUi.prototype.importCsv = function(text, done)
  15350. {
  15351. this.loadOrgChartLayouts(mxUtils.bind(this, function()
  15352. {
  15353. this.doImportCsv(text, done);
  15354. }));
  15355. };
  15356. /**
  15357. *
  15358. */
  15359. EditorUi.prototype.doImportCsv = function(text, done)
  15360. {
  15361. try
  15362. {
  15363. var lines = text.split('\n');
  15364. var allCells = [];
  15365. var parents = [];
  15366. var cells = [];
  15367. var dups = {};
  15368. if (lines.length > 0)
  15369. {
  15370. // Internal lookup table
  15371. var lookups = {};
  15372. // Default values
  15373. var graph = this.editor.graph;
  15374. var vars = null;
  15375. var style = null;
  15376. var styles = null;
  15377. var stylename = null;
  15378. var labelname = null;
  15379. var unknownStyle = null;
  15380. var labels = null;
  15381. var parentstyle = 'whiteSpace=wrap;html=1;';
  15382. var identity = null;
  15383. var parent = null;
  15384. var namespace = '';
  15385. var width = 'auto';
  15386. var height = 'auto';
  15387. var collapsed = false;
  15388. var left = null;
  15389. var top = null;
  15390. var edgespacing = 40;
  15391. var nodespacing = 40;
  15392. var levelspacing = 100;
  15393. var padding = 0;
  15394. // Delayed after optional layout
  15395. var afterInsert = mxUtils.bind(this, function()
  15396. {
  15397. if (done != null)
  15398. {
  15399. done(select);
  15400. }
  15401. else
  15402. {
  15403. graph.setSelectionCells(select);
  15404. graph.scrollCellToVisible(graph.getSelectionCell());
  15405. }
  15406. if (this.chromelessResize != null)
  15407. {
  15408. window.setTimeout(mxUtils.bind(this, function()
  15409. {
  15410. this.chromelessResize(true);
  15411. }), 0);
  15412. }
  15413. });
  15414. // Computes unscaled, untranslated graph bounds
  15415. var pt = graph.getFreeInsertPoint();
  15416. var x0 = pt.x;
  15417. var y0 = pt.y;
  15418. var y = y0;
  15419. // Default label value depends on column names
  15420. var label = null;
  15421. // Default layout to run.
  15422. var layout = 'auto';
  15423. // Name of the attribute that contains the parent reference
  15424. var parent = null;
  15425. // Name of the attribute that contains the references for creating edges
  15426. var edges = [];
  15427. // Name of the column for hyperlinks
  15428. var link = null;
  15429. // String array of names to remove from metadata
  15430. var ignore = null;
  15431. // Read processing instructions first
  15432. var index = 0;
  15433. while (index < lines.length && lines[index].charAt(0) == '#')
  15434. {
  15435. var text = lines[index].replace(/\r$/,''); // Remove trailing \r if the file uses \r\n line breaks
  15436. index++;
  15437. while (index < lines.length && text.charAt(text.length - 1) == '\\' &&
  15438. lines[index].charAt(0) == '#')
  15439. {
  15440. text = text.substring(0, text.length - 1) + mxUtils.trim(lines[index].substring(1));
  15441. index++;
  15442. }
  15443. if (text.charAt(1) != '#')
  15444. {
  15445. // Processing instruction
  15446. var idx = text.indexOf(':');
  15447. if (idx > 0)
  15448. {
  15449. var key = mxUtils.trim(text.substring(1, idx));
  15450. var value = mxUtils.trim(text.substring(idx + 1));
  15451. if (key == 'label')
  15452. {
  15453. label = Graph.sanitizeHtml(value);
  15454. }
  15455. else if (key == 'labelname' && value.length > 0 && value != '-')
  15456. {
  15457. labelname = value;
  15458. }
  15459. else if (key == 'labels' && value.length > 0 && value != '-')
  15460. {
  15461. labels = JSON.parse(value);
  15462. }
  15463. else if (key == 'style')
  15464. {
  15465. style = value;
  15466. }
  15467. else if (key == 'parentstyle')
  15468. {
  15469. parentstyle = value;
  15470. }
  15471. else if (key == 'unknownStyle' && value != '-')
  15472. {
  15473. unknownStyle = value;
  15474. }
  15475. else if (key == 'stylename' && value.length > 0 && value != '-')
  15476. {
  15477. stylename = value;
  15478. }
  15479. else if (key == 'styles' && value.length > 0 && value != '-')
  15480. {
  15481. styles = JSON.parse(value);
  15482. }
  15483. else if (key == 'vars' && value.length > 0 && value != '-')
  15484. {
  15485. vars = JSON.parse(value);
  15486. }
  15487. else if (key == 'identity' && value.length > 0 && value != '-')
  15488. {
  15489. identity = value;
  15490. }
  15491. else if (key == 'parent' && value.length > 0 && value != '-')
  15492. {
  15493. parent = value;
  15494. }
  15495. else if (key == 'namespace' && value.length > 0 && value != '-')
  15496. {
  15497. namespace = value;
  15498. }
  15499. else if (key == 'width')
  15500. {
  15501. width = value;
  15502. }
  15503. else if (key == 'height')
  15504. {
  15505. height = value;
  15506. }
  15507. else if (key == 'collapsed' && value != '-')
  15508. {
  15509. collapsed = value == 'true';
  15510. }
  15511. else if (key == 'left' && value.length > 0)
  15512. {
  15513. left = value;
  15514. }
  15515. else if (key == 'top' && value.length > 0)
  15516. {
  15517. top = value;
  15518. }
  15519. else if (key == 'ignore')
  15520. {
  15521. ignore = value.split(',');
  15522. }
  15523. else if (key == 'connect')
  15524. {
  15525. edges.push(JSON.parse(value));
  15526. }
  15527. else if (key == 'link')
  15528. {
  15529. link = value;
  15530. }
  15531. else if (key == 'padding')
  15532. {
  15533. padding = parseFloat(value);
  15534. }
  15535. else if (key == 'edgespacing')
  15536. {
  15537. edgespacing = parseFloat(value);
  15538. }
  15539. else if (key == 'nodespacing')
  15540. {
  15541. nodespacing = parseFloat(value);
  15542. }
  15543. else if (key == 'levelspacing')
  15544. {
  15545. levelspacing = parseFloat(value);
  15546. }
  15547. else if (key == 'layout')
  15548. {
  15549. layout = value;
  15550. }
  15551. }
  15552. }
  15553. }
  15554. if (lines[index] == null)
  15555. {
  15556. throw new Error(mxResources.get('invalidOrMissingFile'));
  15557. }
  15558. // Converts identity and parent to index and validates XML attribute names
  15559. var keys = this.editor.csvToArray(lines[index].replace(/\r$/,''));
  15560. var identityIndex = null;
  15561. var parentIndex = null;
  15562. var attribs = [];
  15563. for (var i = 0; i < keys.length; i++)
  15564. {
  15565. if (identity == keys[i])
  15566. {
  15567. identityIndex = i;
  15568. }
  15569. if (parent == keys[i])
  15570. {
  15571. parentIndex = i;
  15572. }
  15573. attribs.push(mxUtils.trim(keys[i]).replace(/[^a-z0-9]+/ig, '_').
  15574. replace(/^\d+/, '').replace(/_+$/, ''));
  15575. }
  15576. if (label == null)
  15577. {
  15578. label = '%' + attribs[0] + '%';
  15579. }
  15580. if (edges != null)
  15581. {
  15582. for (var e = 0; e < edges.length; e++)
  15583. {
  15584. if (lookups[edges[e].to] == null)
  15585. {
  15586. lookups[edges[e].to] = {};
  15587. }
  15588. }
  15589. }
  15590. // Parse and validate input
  15591. var arrays = [];
  15592. for (var i = index + 1; i < lines.length; i++)
  15593. {
  15594. var values = this.editor.csvToArray(lines[i].replace(/\r$/,''));
  15595. if (values == null)
  15596. {
  15597. var short = (lines[i].length > 40) ? lines[i].substring(0, 40) + '...' : lines[i];
  15598. throw new Error(short + ' (' + i + '):\n' + mxResources.get('containsValidationErrors'));
  15599. }
  15600. else if (values.length > 0)
  15601. {
  15602. arrays.push(values);
  15603. }
  15604. }
  15605. graph.model.beginUpdate();
  15606. try
  15607. {
  15608. for (var i = 0; i < arrays.length; i++)
  15609. {
  15610. var values = arrays[i];
  15611. var cell = null;
  15612. var id = (identityIndex != null) ? namespace + values[identityIndex] : null;
  15613. var ignoreCell = false;
  15614. if (id != null)
  15615. {
  15616. cell = graph.model.getCell(id);
  15617. // Bypasses update of cells inserted during this run
  15618. ignoreCell = cell == null || mxUtils.indexOf(
  15619. allCells, cell) >= 0;
  15620. }
  15621. var newCell = new mxCell(label, new mxGeometry(x0, y,
  15622. 0, 0), style || 'whiteSpace=wrap;html=1;');
  15623. newCell.collapsed = collapsed;
  15624. newCell.vertex = true;
  15625. newCell.id = id;
  15626. if (cell != null && !ignoreCell)
  15627. {
  15628. graph.model.setCollapsed(cell, collapsed);
  15629. }
  15630. for (var j = 0; j < values.length; j++)
  15631. {
  15632. graph.setAttributeForCell(newCell, attribs[j], values[j]);
  15633. if (cell != null && !ignoreCell)
  15634. {
  15635. graph.setAttributeForCell(cell, attribs[j], values[j]);
  15636. }
  15637. }
  15638. if (labelname != null && labels != null)
  15639. {
  15640. var tempLabel = labels[newCell.getAttribute(labelname)];
  15641. if (tempLabel != null)
  15642. {
  15643. graph.labelChanged(newCell, tempLabel);
  15644. if (cell != null && !ignoreCell)
  15645. {
  15646. graph.cellLabelChanged(cell, tempLabel);
  15647. }
  15648. }
  15649. }
  15650. if (stylename != null && styles != null)
  15651. {
  15652. var tempStyle = styles[newCell.getAttribute(stylename)];
  15653. if (tempStyle != null)
  15654. {
  15655. newCell.style = tempStyle;
  15656. }
  15657. }
  15658. graph.setAttributeForCell(newCell, 'placeholders', '1');
  15659. newCell.style = graph.replacePlaceholders(newCell, newCell.style, vars);
  15660. if (cell != null && !ignoreCell)
  15661. {
  15662. graph.model.setStyle(cell, newCell.style);
  15663. if (mxUtils.indexOf(cells, cell) < 0)
  15664. {
  15665. cells.push(cell);
  15666. }
  15667. graph.fireEvent(new mxEventObject('cellsInserted', 'cells', [cell]));
  15668. }
  15669. else
  15670. {
  15671. graph.fireEvent(new mxEventObject('cellsInserted', 'cells', [newCell]));
  15672. }
  15673. var exists = cell != null;
  15674. cell = newCell;
  15675. if (!exists)
  15676. {
  15677. for (var e = 0; e < edges.length; e++)
  15678. {
  15679. lookups[edges[e].to][cell.getAttribute(edges[e].to)] = cell;
  15680. }
  15681. }
  15682. if (link != null && link != 'link')
  15683. {
  15684. graph.setLinkForCell(cell, cell.getAttribute(link));
  15685. // Removes attribute
  15686. graph.setAttributeForCell(cell, link, null);
  15687. }
  15688. // Sets the geometry
  15689. var size = this.editor.graph.getPreferredSizeForCell(cell);
  15690. var parent = (parentIndex != null) ? graph.model.getCell(
  15691. namespace + values[parentIndex]) : null;
  15692. if (cell.vertex)
  15693. {
  15694. var originX = (parent != null) ? 0 : x0;
  15695. var originY = (parent != null) ? 0 : y0;
  15696. if (left != null && cell.getAttribute(left) != null)
  15697. {
  15698. cell.geometry.x = originX + parseFloat(cell.getAttribute(left));
  15699. }
  15700. if (top != null && cell.getAttribute(top) != null)
  15701. {
  15702. cell.geometry.y = originY + parseFloat(cell.getAttribute(top));
  15703. }
  15704. var widthValue = (width.charAt(0) == '@') ? cell.getAttribute(width.substring(1)) : null;
  15705. if (widthValue != null && widthValue != 'auto')
  15706. {
  15707. cell.geometry.width = parseFloat(cell.getAttribute(width.substring(1)));
  15708. }
  15709. else
  15710. {
  15711. cell.geometry.width = (width == 'auto' || widthValue == 'auto') ?
  15712. size.width + padding : parseFloat(width);
  15713. }
  15714. var heightValue = (height.charAt(0) == '@') ? cell.getAttribute(height.substring(1)) : null;
  15715. if (heightValue != null && heightValue != 'auto' && height != 'width')
  15716. {
  15717. cell.geometry.height = parseFloat(heightValue);
  15718. }
  15719. else
  15720. {
  15721. cell.geometry.height = (height == 'auto' || heightValue == 'auto') ?
  15722. size.height + padding : (height == 'width' ?
  15723. cell.geometry.width : parseFloat(height));
  15724. }
  15725. y += cell.geometry.height + nodespacing;
  15726. }
  15727. if (!exists)
  15728. {
  15729. allCells.push(cell);
  15730. if (parent != null)
  15731. {
  15732. parent.style = graph.replacePlaceholders(parent, parentstyle, vars);
  15733. graph.addCell(cell, parent);
  15734. parents.push(parent);
  15735. }
  15736. else
  15737. {
  15738. cells.push(graph.addCell(cell));
  15739. }
  15740. }
  15741. else
  15742. {
  15743. if (dups[id] == null)
  15744. {
  15745. dups[id] = [];
  15746. }
  15747. dups[id].push(cell);
  15748. }
  15749. }
  15750. // Process parents for autosize
  15751. for (var i = 0; i < parents.length; i++)
  15752. {
  15753. var widthValue = (width.charAt(0) == '@') ? parents[i].getAttribute(width.substring(1)) : null;
  15754. var heightValue = (height.charAt(0) == '@') ? parents[i].getAttribute(height.substring(1)) : null;
  15755. if ((width == 'auto' || widthValue == 'auto') &&
  15756. (height == 'auto' || heightValue == 'auto'))
  15757. {
  15758. graph.updateGroupBounds([parents[i]], padding, true);
  15759. }
  15760. }
  15761. var roots = cells.slice();
  15762. var select = cells.slice();
  15763. for (var e = 0; e < edges.length; e++)
  15764. {
  15765. var edge = edges[e];
  15766. for (var i = 0; i < allCells.length; i++)
  15767. {
  15768. var cell = allCells[i];
  15769. var insertEdge = mxUtils.bind(this, function(realCell, dataCell, edge)
  15770. {
  15771. var tmp = dataCell.getAttribute(edge.from);
  15772. if (tmp != null && tmp != '')
  15773. {
  15774. var refs = tmp.split(',');
  15775. for (var j = 0; j < refs.length; j++)
  15776. {
  15777. var ref = lookups[edge.to][refs[j]];
  15778. if (ref == null && unknownStyle != null)
  15779. {
  15780. ref = new mxCell(refs[j], new mxGeometry(x0, y0, 0, 0), unknownStyle);
  15781. ref.style = graph.replacePlaceholders(dataCell, ref.style, vars);
  15782. var refSize = this.editor.graph.getPreferredSizeForCell(ref);
  15783. ref.geometry.width = refSize.width + padding;
  15784. ref.geometry.height = refSize.height + padding;
  15785. lookups[edge.to][refs[j]] = ref;
  15786. ref.vertex = true;
  15787. ref.id = refs[j];
  15788. cells.push(graph.addCell(ref));
  15789. }
  15790. if (ref != null)
  15791. {
  15792. var label = edge.label;
  15793. if (edge.fromlabel != null)
  15794. {
  15795. label = (dataCell.getAttribute(edge.fromlabel) || '') + (label || '');
  15796. }
  15797. if (edge.sourcelabel != null)
  15798. {
  15799. label = graph.replacePlaceholders(dataCell,
  15800. edge.sourcelabel, vars) + (label || '');
  15801. }
  15802. if (edge.tolabel != null)
  15803. {
  15804. label = (label || '') + (ref.getAttribute(edge.tolabel) || '');
  15805. }
  15806. if (edge.targetlabel != null)
  15807. {
  15808. label = (label || '') + graph.replacePlaceholders(
  15809. ref, edge.targetlabel, vars);
  15810. }
  15811. var placeholders = ((edge.placeholders == 'target') ==
  15812. !edge.invert) ? ref : realCell;
  15813. var edgeStyle = (edge.style != null) ?
  15814. graph.replacePlaceholders(placeholders, edge.style, vars) :
  15815. graph.createCurrentEdgeStyle();
  15816. var edgeCell = graph.insertEdge(null, null, label || '', (edge.invert) ?
  15817. ref : realCell, (edge.invert) ? realCell : ref, edgeStyle);
  15818. // Adds additional edge labels
  15819. if (edge.labels != null)
  15820. {
  15821. for (var k = 0; k < edge.labels.length; k++)
  15822. {
  15823. var def = edge.labels[k];
  15824. var elx = (def.x != null) ? def.x : 0;
  15825. var ely = (def.y != null) ? def.y : 0;
  15826. var st = 'resizable=0;html=1;';
  15827. var el = new mxCell(def.label || k,
  15828. new mxGeometry(elx, ely, 0, 0), st);
  15829. el.vertex = true;
  15830. el.connectable = false;
  15831. el.geometry.relative = true;
  15832. if (def.placeholders != null)
  15833. {
  15834. el.value = graph.replacePlaceholders(
  15835. ((def.placeholders == 'target') ==
  15836. !edge.invert) ? ref : realCell,
  15837. el.value, vars)
  15838. }
  15839. if (def.dx != null || def.dy != null)
  15840. {
  15841. el.geometry.offset = new mxPoint(
  15842. (def.dx != null) ? def.dx : 0,
  15843. (def.dy != null) ? def.dy : 0);
  15844. }
  15845. edgeCell.insert(el);
  15846. }
  15847. }
  15848. // Adds edge metadata
  15849. if (edge.data != null)
  15850. {
  15851. for (var key in edge.data)
  15852. {
  15853. graph.setAttributeForCell(edgeCell, key, edge.data[key]);
  15854. }
  15855. }
  15856. select.push(edgeCell);
  15857. mxUtils.remove((edge.invert) ? realCell : ref, roots);
  15858. }
  15859. }
  15860. }
  15861. });
  15862. insertEdge(cell, cell, edge);
  15863. // Checks more entries
  15864. if (dups[cell.id] != null)
  15865. {
  15866. for (var j = 0; j < dups[cell.id].length; j++)
  15867. {
  15868. insertEdge(cell, dups[cell.id][j], edge);
  15869. }
  15870. }
  15871. }
  15872. }
  15873. // Removes ignored attributes after processing above
  15874. if (ignore != null)
  15875. {
  15876. for (var i = 0; i < allCells.length; i++)
  15877. {
  15878. var cell = allCells[i];
  15879. for (var j = 0; j < ignore.length; j++)
  15880. {
  15881. graph.setAttributeForCell(cell, mxUtils.trim(ignore[j]), null);
  15882. }
  15883. }
  15884. }
  15885. if (cells.length > 0)
  15886. {
  15887. var edgeLayout = new mxParallelEdgeLayout(graph);
  15888. edgeLayout.spacing = edgespacing;
  15889. edgeLayout.checkOverlap = true;
  15890. var postProcess = function()
  15891. {
  15892. if (edgeLayout.spacing > 0)
  15893. {
  15894. edgeLayout.execute(graph.getDefaultParent());
  15895. }
  15896. // Aligns cells to grid and/or rounds positions
  15897. for (var i = 0; i < cells.length; i++)
  15898. {
  15899. var geo = graph.getCellGeometry(cells[i]);
  15900. geo.x = Math.round(graph.snap(geo.x));
  15901. geo.y = Math.round(graph.snap(geo.y));
  15902. if (width == 'auto')
  15903. {
  15904. geo.width = Math.round(graph.snap(geo.width));
  15905. }
  15906. if (height == 'auto')
  15907. {
  15908. geo.height = Math.round(graph.snap(geo.height));
  15909. }
  15910. }
  15911. };
  15912. if (layout.charAt(0) == '[')
  15913. {
  15914. // Required for layouts to work with new cells
  15915. var temp = afterInsert;
  15916. graph.view.validate();
  15917. this.executeLayouts(graph.createLayouts(JSON.parse(layout)), function()
  15918. {
  15919. postProcess();
  15920. temp();
  15921. });
  15922. afterInsert = null;
  15923. }
  15924. else if (layout == 'circle')
  15925. {
  15926. var circleLayout = new mxCircleLayout(graph);
  15927. circleLayout.disableEdgeStyle = false;
  15928. circleLayout.resetEdges = false;
  15929. var circleLayoutIsVertexIgnored = circleLayout.isVertexIgnored;
  15930. // Ignore other cells
  15931. circleLayout.isVertexIgnored = function(vertex)
  15932. {
  15933. return circleLayoutIsVertexIgnored.apply(this, arguments) ||
  15934. mxUtils.indexOf(cells, vertex) < 0;
  15935. };
  15936. this.executeLayout(function()
  15937. {
  15938. circleLayout.execute(graph.getDefaultParent());
  15939. postProcess();
  15940. }, true, afterInsert);
  15941. afterInsert = null;
  15942. }
  15943. else if (layout == 'horizontaltree' || layout == 'verticaltree' ||
  15944. (layout == 'auto' && select.length == 2 * cells.length - 1 && roots.length == 1))
  15945. {
  15946. // Required for layouts to work with new cells
  15947. graph.view.validate();
  15948. var treeLayout = new mxCompactTreeLayout(graph, layout == 'horizontaltree');
  15949. treeLayout.levelDistance = nodespacing;
  15950. treeLayout.edgeRouting = false;
  15951. treeLayout.resetEdges = false;
  15952. treeLayout.sortEdges = true;
  15953. this.executeLayout(function()
  15954. {
  15955. treeLayout.execute(graph.getDefaultParent(), (roots.length > 0) ? roots[0] : null);
  15956. }, true, afterInsert);
  15957. afterInsert = null;
  15958. }
  15959. else if (layout == 'horizontalflow' || layout == 'verticalflow' ||
  15960. (layout == 'auto' && roots.length == 1))
  15961. {
  15962. // Required for layouts to work with new cells
  15963. graph.view.validate();
  15964. var flowLayout = new mxHierarchicalLayout(graph,
  15965. (layout == 'horizontalflow') ?
  15966. mxConstants.DIRECTION_WEST :
  15967. mxConstants.DIRECTION_NORTH);
  15968. flowLayout.intraCellSpacing = nodespacing;
  15969. flowLayout.parallelEdgeSpacing = edgespacing;
  15970. flowLayout.interRankCellSpacing = levelspacing;
  15971. flowLayout.disableEdgeStyle = false;
  15972. this.executeLayout(function()
  15973. {
  15974. flowLayout.execute(graph.getDefaultParent(), select);
  15975. // Workaround for flow layout moving cells to origin
  15976. graph.moveCells(select, x0, y0);
  15977. }, true, afterInsert);
  15978. afterInsert = null;
  15979. }
  15980. else if (layout == 'orgchart')
  15981. {
  15982. // Required for layouts to work with new cells
  15983. graph.view.validate();
  15984. var orgChartLayout = new mxOrgChartLayout(graph,
  15985. 2, levelspacing, nodespacing);
  15986. var orgChartLayoutIsVertexIgnored = orgChartLayout.isVertexIgnored;
  15987. // Ignore other cells
  15988. orgChartLayout.isVertexIgnored = function(vertex)
  15989. {
  15990. return orgChartLayoutIsVertexIgnored.apply(this, arguments) ||
  15991. mxUtils.indexOf(cells, vertex) < 0;
  15992. };
  15993. this.executeLayout(function()
  15994. {
  15995. orgChartLayout.execute(graph.getDefaultParent());
  15996. postProcess();
  15997. }, true, afterInsert);
  15998. afterInsert = null;
  15999. }
  16000. else if (layout == 'organic' || (layout == 'auto' &&
  16001. select.length > cells.length))
  16002. {
  16003. // Required for layouts to work with new cells
  16004. graph.view.validate();
  16005. var organicLayout = new mxFastOrganicLayout(graph);
  16006. organicLayout.forceConstant = nodespacing * 3;
  16007. organicLayout.disableEdgeStyle = false;
  16008. organicLayout.resetEdges = false;
  16009. var organicLayoutIsVertexIgnored = organicLayout.isVertexIgnored;
  16010. // Ignore other cells
  16011. organicLayout.isVertexIgnored = function(vertex)
  16012. {
  16013. return organicLayoutIsVertexIgnored.apply(this, arguments) ||
  16014. mxUtils.indexOf(cells, vertex) < 0;
  16015. };
  16016. this.executeLayout(function()
  16017. {
  16018. organicLayout.execute(graph.getDefaultParent());
  16019. postProcess();
  16020. }, true, afterInsert);
  16021. afterInsert = null;
  16022. }
  16023. }
  16024. this.hideDialog();
  16025. }
  16026. finally
  16027. {
  16028. graph.model.endUpdate();
  16029. }
  16030. if (afterInsert != null)
  16031. {
  16032. afterInsert();
  16033. }
  16034. }
  16035. }
  16036. catch (e)
  16037. {
  16038. this.handleError(e);
  16039. }
  16040. };
  16041. /**
  16042. * Translates this point by the given vector.
  16043. *
  16044. * @param {number} dx X-coordinate of the translation.
  16045. * @param {number} dy Y-coordinate of the translation.
  16046. */
  16047. EditorUi.prototype.getSearch = function(exclude)
  16048. {
  16049. var result = '';
  16050. if (urlParams['offline'] != '1' && urlParams['demo'] != '1' && exclude != null && window.location.search.length > 0)
  16051. {
  16052. var amp = '?';
  16053. for (var key in urlParams)
  16054. {
  16055. if (mxUtils.indexOf(exclude, key) < 0 && urlParams[key] != null)
  16056. {
  16057. result += amp + key + '=' + urlParams[key];
  16058. amp = '&';
  16059. }
  16060. }
  16061. }
  16062. else
  16063. {
  16064. result = window.location.search;
  16065. }
  16066. return result;
  16067. };
  16068. /**
  16069. * Returns the URL for a copy of this editor with no state.
  16070. */
  16071. EditorUi.prototype.getUrl = function(pathname)
  16072. {
  16073. var href = (pathname != null) ? pathname : window.location.pathname;
  16074. var parms = (href.indexOf('?') > 0) ? 1 : 0;
  16075. if (urlParams['offline'] == '1')
  16076. {
  16077. href += window.location.search;
  16078. }
  16079. else
  16080. {
  16081. var ignored = ['tmp', 'libs', 'clibs', 'state', 'fileId', 'code', 'share', 'notitle',
  16082. 'data', 'url', 'embed', 'client', 'create', 'title', 'splash'];
  16083. // Removes template URL parameter for new blank diagram
  16084. for (var key in urlParams)
  16085. {
  16086. if (mxUtils.indexOf(ignored, key) < 0)
  16087. {
  16088. if (parms == 0)
  16089. {
  16090. href += '?';
  16091. }
  16092. else
  16093. {
  16094. href += '&';
  16095. }
  16096. if (urlParams[key] != null)
  16097. {
  16098. href += key + '=' + urlParams[key];
  16099. parms++;
  16100. }
  16101. }
  16102. }
  16103. }
  16104. return href;
  16105. };
  16106. /**
  16107. * Overrides link dialog.
  16108. */
  16109. EditorUi.prototype.showLinkDialog = function(value, btnLabel, fn, showNewWindowOption, linkTarget)
  16110. {
  16111. var dlg = new LinkDialog(this, value, btnLabel, fn, true, showNewWindowOption, linkTarget);
  16112. this.showDialog(dlg.container, 440, 120, true, true);
  16113. dlg.init();
  16114. };
  16115. /**
  16116. * Returns the number of storage options enabled
  16117. */
  16118. EditorUi.prototype.getServiceCount = function(allowBrowser)
  16119. {
  16120. var serviceCount = 1;
  16121. if (this.drive != null || typeof window.DriveClient === 'function')
  16122. {
  16123. serviceCount++
  16124. }
  16125. if (this.dropbox != null || typeof window.DropboxClient === 'function')
  16126. {
  16127. serviceCount++
  16128. }
  16129. if (this.oneDrive != null || typeof window.OneDriveClient === 'function')
  16130. {
  16131. serviceCount++
  16132. }
  16133. if (this.gitHub != null)
  16134. {
  16135. serviceCount++
  16136. }
  16137. if (this.gitLab != null)
  16138. {
  16139. serviceCount++
  16140. }
  16141. if (allowBrowser && isLocalStorage && urlParams['browser'] == '1')
  16142. {
  16143. serviceCount++
  16144. }
  16145. return serviceCount;
  16146. }
  16147. /**
  16148. * Updates action and menu states depending on the file.
  16149. */
  16150. EditorUi.prototype.updateUi = function()
  16151. {
  16152. this.updateButtonContainer();
  16153. this.updateActionStates();
  16154. // Action states that only need update for new files
  16155. var file = this.getCurrentFile();
  16156. var active = file != null || (urlParams['embed'] == '1' &&
  16157. this.editor.graph.isEnabled());
  16158. this.menus.get('viewPanels').setEnabled(active);
  16159. this.menus.get('viewZoom').setEnabled(active);
  16160. var restricted = (urlParams['embed'] != '1' || urlParams['embedRT'] == '1' ||
  16161. !this.editor.graph.isEnabled()) &&
  16162. (file == null || file.isRestricted());
  16163. this.actions.get('makeCopy').setEnabled(!restricted);
  16164. this.actions.get('print').setEnabled(!restricted);
  16165. this.menus.get('exportAs').setEnabled(!restricted);
  16166. this.menus.get('embed').setEnabled(!restricted);
  16167. // Disables libraries and extras menu in embed mode
  16168. // while waiting for file data
  16169. var libsEnabled = urlParams['embed'] != '1' ||
  16170. this.editor.graph.isEnabled();
  16171. this.menus.get('extras').setEnabled(libsEnabled);
  16172. if (Editor.enableCustomLibraries)
  16173. {
  16174. this.menus.get('openLibraryFrom').setEnabled(libsEnabled);
  16175. this.menus.get('newLibrary').setEnabled(libsEnabled);
  16176. }
  16177. // Disables actions in the toolbar
  16178. var editable = (urlParams['embed'] == '1' &&
  16179. this.editor.graph.isEnabled()) ||
  16180. (file != null && file.isEditable());
  16181. this.actions.get('image').setEnabled(active);
  16182. this.actions.get('zoomIn').setEnabled(active);
  16183. this.actions.get('zoomOut').setEnabled(active);
  16184. this.actions.get('smartFit').setEnabled(active);
  16185. this.actions.get('resetView').setEnabled(active);
  16186. this.actions.get('darkMode').setEnabled(Editor.currentTheme != 'atlas');
  16187. this.actions.get('lightMode').setEnabled(Editor.currentTheme != 'atlas');
  16188. var autoModeAction = this.actions.get('autoMode');
  16189. autoModeAction.setEnabled(autoModeAction.isEnabled() && Editor.currentTheme != 'atlas');
  16190. // Disables menus
  16191. this.menus.get('edit').setEnabled(active);
  16192. this.menus.get('view').setEnabled(active);
  16193. this.menus.get('importFrom').setEnabled(editable);
  16194. this.menus.get('arrange').setEnabled(editable);
  16195. // Disables connection drop downs in toolbar
  16196. if (this.toolbar != null)
  16197. {
  16198. if (this.toolbar.edgeShapeMenu != null)
  16199. {
  16200. this.toolbar.edgeShapeMenu.setEnabled(editable);
  16201. }
  16202. if (this.toolbar.edgeStyleMenu != null)
  16203. {
  16204. this.toolbar.edgeStyleMenu.setEnabled(editable);
  16205. }
  16206. }
  16207. this.updateUserElement();
  16208. };
  16209. /**
  16210. * Hook for subclassers
  16211. */
  16212. EditorUi.prototype.updateButtonContainer = function()
  16213. {
  16214. // do nothing
  16215. };
  16216. /**
  16217. * Hook for subclassers
  16218. */
  16219. EditorUi.prototype.updateUserElement = function()
  16220. {
  16221. // do nothing
  16222. };
  16223. /**
  16224. * Hook for subclassers
  16225. */
  16226. EditorUi.prototype.scheduleSanityCheck = function()
  16227. {
  16228. // do nothing
  16229. };
  16230. /**
  16231. * Hook for subclassers
  16232. */
  16233. EditorUi.prototype.stopSanityCheck = function()
  16234. {
  16235. // do nothing
  16236. };
  16237. /**
  16238. * Returns true if a diagram is cative and editable.
  16239. */
  16240. EditorUi.prototype.isDiagramActive = function()
  16241. {
  16242. var file = this.getCurrentFile();
  16243. return (file != null && file.isEditable()) ||
  16244. (urlParams['embed'] == '1' && this.editor.graph.isEnabled());
  16245. };
  16246. /**
  16247. * Extends sidebar construction to add listeners for theme changes.
  16248. */
  16249. var editorUiCreateSidebar = EditorUi.prototype.createSidebar;
  16250. EditorUi.prototype.createSidebar = function(container)
  16251. {
  16252. var sidebar = editorUiCreateSidebar.apply(this, arguments);
  16253. var refreshSidebar = mxUtils.bind(this, function()
  16254. {
  16255. sidebar.refresh();
  16256. this.restoreOpenLibraries();
  16257. });
  16258. this.addListener('darkModeChanged', mxUtils.bind(this, function()
  16259. {
  16260. if (!Editor.enableCssDarkMode)
  16261. {
  16262. refreshSidebar();
  16263. }
  16264. }));
  16265. this.addListener('sketchModeChanged', refreshSidebar);
  16266. this.addListener('currentThemeChanged', refreshSidebar);
  16267. return sidebar;
  16268. };
  16269. /**
  16270. * Extends sidebar construction to add listeners for theme changes.
  16271. */
  16272. EditorUi.prototype.restoreOpenLibraries = function()
  16273. {
  16274. var temp = this.openLibraries;
  16275. this.openLibraries = null;
  16276. if (temp != null)
  16277. {
  16278. for (var i = 0; i < temp.length; i++)
  16279. {
  16280. this.libraryLoaded(temp[i].file, temp[i].images,
  16281. temp[i].title, (temp[i].div != null) ?
  16282. temp[i].div.style.display != 'none' :
  16283. temp[i].expand);
  16284. }
  16285. }
  16286. };
  16287. /**
  16288. * Updates action states depending on the selection.
  16289. */
  16290. var editorUiUpdateActionStates = EditorUi.prototype.updateActionStates;
  16291. EditorUi.prototype.updateActionStates = function()
  16292. {
  16293. editorUiUpdateActionStates.apply(this, arguments);
  16294. var graph = this.editor.graph;
  16295. var file = this.getCurrentFile();
  16296. var ss = this.getSelectionState();
  16297. var active = this.isDiagramActive();
  16298. var editable = (urlParams['embed'] == '1' &&
  16299. this.editor.graph.isEnabled()) ||
  16300. (file != null && file.isEditable());
  16301. this.actions.get('undo').setEnabled(this.canUndo() && editable);
  16302. this.actions.get('redo').setEnabled(this.canRedo() && editable);
  16303. this.actions.get('autosave').setEnabled(file != null && file.isEditable() && file.isAutosaveOptional());
  16304. this.actions.get('guides').setEnabled(active);
  16305. this.actions.get('editData').setEnabled(graph.isEnabled());
  16306. this.actions.get('editConnectionPoints').setEnabled(active && ss.edges.length == 0 && ss.vertices.length == 1);
  16307. this.actions.get('editImage').setEnabled(active && ss.image && ss.cells.length > 0);
  16308. this.actions.get('crop').setEnabled(active && ss.image && ss.cells.length > 0);
  16309. this.actions.get('pageSetup').setEnabled(active);
  16310. this.actions.get('shadowVisible').setEnabled(active);
  16311. this.actions.get('connectionArrows').setEnabled(active);
  16312. this.actions.get('connectionPoints').setEnabled(active);
  16313. this.actions.get('copyStyle').setEnabled(active && !graph.isSelectionEmpty());
  16314. this.actions.get('pasteStyle').setEnabled(this.copiedStyle != null && active && ss.cells.length > 0);
  16315. this.actions.get('editGeometry').setEnabled(ss.vertices.length > 0);
  16316. this.actions.get('createShape').setEnabled(active);
  16317. this.actions.get('createRevision').setEnabled(active);
  16318. this.actions.get('moveToFolder').setEnabled(file != null);
  16319. this.actions.get('makeCopy').setEnabled(file != null && !file.isRestricted());
  16320. this.actions.get('editDiagram').setEnabled(active && (file == null || !file.isRestricted()));
  16321. this.actions.get('publishLink').setEnabled(file != null && !file.isRestricted());
  16322. this.actions.get('removeFormat').setEnabled(graph.isEnabled() &&
  16323. !graph.isSelectionEmpty() && !graph.isEditing());
  16324. this.actions.get('tags').setEnabled(this.diagramContainer.style.visibility != 'hidden');
  16325. this.actions.get('layers').setEnabled(this.diagramContainer.style.visibility != 'hidden');
  16326. this.actions.get('outline').setEnabled(this.diagramContainer.style.visibility != 'hidden');
  16327. this.actions.get('rename').setEnabled((file != null && file.isRenamable()) || urlParams['embed'] == '1');
  16328. this.actions.get('close').setEnabled(file != null);
  16329. this.actions.get('properties').setEnabled(file != null);
  16330. this.menus.get('publish').setEnabled(file != null && !file.isRestricted());
  16331. var findReplace = this.actions.get('findReplace');
  16332. findReplace.setEnabled(this.diagramContainer.style.visibility != 'hidden');
  16333. findReplace.label = mxResources.get('find') + ((graph.isEnabled()) ?
  16334. '/' + mxResources.get('replace') : '');
  16335. var state = graph.view.getState(graph.getSelectionCell());
  16336. this.actions.get('editShape').setEnabled(active && state != null &&
  16337. state.shape != null && state.shape.stencil != null);
  16338. };
  16339. /**
  16340. * Overridden to remove export dialog in chromeless lightbox.
  16341. */
  16342. var editoUiDestroy = EditorUi.prototype.destroy;
  16343. EditorUi.prototype.destroy = function()
  16344. {
  16345. if (this.exportDialog != null)
  16346. {
  16347. this.exportDialog.parentNode.removeChild(this.exportDialog);
  16348. this.exportDialog = null;
  16349. }
  16350. editoUiDestroy.apply(this, arguments);
  16351. };
  16352. /**
  16353. * Overrides export dialog for using ui functions for save and setting global switches.
  16354. */
  16355. if (window.ExportDialog != null)
  16356. {
  16357. ExportDialog.showXmlOption = false;
  16358. ExportDialog.showGifOption = false;
  16359. ExportDialog.exportFile = function(editorUi, name, format, bg, s, b, dpi, grid)
  16360. {
  16361. var graph = editorUi.editor.graph;
  16362. if (format == 'xml')
  16363. {
  16364. editorUi.hideDialog();
  16365. editorUi.saveData(name, 'xml', mxUtils.getXml(editorUi.editor.getGraphXml()), 'text/xml');
  16366. }
  16367. else if (format == 'svg')
  16368. {
  16369. editorUi.hideDialog();
  16370. editorUi.saveData(name, 'svg', mxUtils.getXml(graph.getSvg(bg, s, b)), 'image/svg+xml');
  16371. }
  16372. else
  16373. {
  16374. var data = editorUi.getFileData(true, null, null, null, null, true);
  16375. var bounds = graph.getGraphBounds();
  16376. var w = Math.floor(bounds.width * s / graph.view.scale);
  16377. var h = Math.floor(bounds.height * s / graph.view.scale);
  16378. if (data.length <= MAX_REQUEST_SIZE && w * h < MAX_AREA)
  16379. {
  16380. editorUi.hideDialog();
  16381. if ((format == 'png' || format == 'jpg' || format == 'jpeg') && editorUi.isExportToCanvas())
  16382. {
  16383. if (format == 'png')
  16384. {
  16385. editorUi.exportImage(s, bg == null || bg == 'none', true,
  16386. false, false, b, true, false, null, grid, dpi);
  16387. }
  16388. else
  16389. {
  16390. editorUi.exportImage(s, false, true,
  16391. false, false, b, true, false, 'jpeg', grid);
  16392. }
  16393. }
  16394. else
  16395. {
  16396. var extras = {globalVars: graph.getExportVariables()};
  16397. if (grid)
  16398. {
  16399. extras.grid = {
  16400. size: graph.gridSize,
  16401. steps: graph.view.gridSteps,
  16402. color: graph.view.gridColor
  16403. };
  16404. }
  16405. editorUi.saveRequest(name, format,
  16406. function(newTitle, base64)
  16407. {
  16408. return new mxXmlRequest(EXPORT_URL, 'format=' + format + '&base64=' + (base64 || '0') +
  16409. ((newTitle != null) ? '&filename=' + encodeURIComponent(newTitle) : '') +
  16410. '&extras=' + encodeURIComponent(JSON.stringify(extras)) +
  16411. (dpi > 0? '&dpi=' + dpi : '') +
  16412. '&bg=' + ((bg != null) ? bg : 'none') + '&w=' + w + '&h=' + h +
  16413. '&border=' + b + '&xml=' + encodeURIComponent(data));
  16414. });
  16415. }
  16416. }
  16417. else
  16418. {
  16419. mxUtils.alert(mxResources.get('drawingTooLarge'));
  16420. }
  16421. }
  16422. };
  16423. }
  16424. EditorUi.prototype.getDiagramTextContent = function()
  16425. {
  16426. this.editor.graph.setEnabled(false);
  16427. var graph = this.editor.graph;
  16428. var allPagesTxt = '';
  16429. if (this.pages != null)
  16430. {
  16431. for (var i = 0; i < this.pages.length; i++)
  16432. {
  16433. var pageGraph = graph;
  16434. if (this.currentPage != this.pages[i])
  16435. {
  16436. pageGraph = this.createTemporaryGraph(graph.getStylesheet());
  16437. this.updatePageRoot(this.pages[i]);
  16438. pageGraph.model.setRoot(this.pages[i].root);
  16439. }
  16440. allPagesTxt += this.pages[i].getName() + ' ' + pageGraph.getIndexableText() + ' ';
  16441. }
  16442. }
  16443. else
  16444. {
  16445. allPagesTxt = graph.getIndexableText();
  16446. }
  16447. this.editor.graph.setEnabled(true);
  16448. return allPagesTxt;
  16449. };
  16450. EditorUi.prototype.showRemotelyStoredLibrary = function(title)
  16451. {
  16452. var selectedLibs = {};
  16453. var div = document.createElement('div');
  16454. div.style.whiteSpace = 'nowrap';
  16455. var graph = this.editor.graph;
  16456. var hd = document.createElement('h3');
  16457. mxUtils.write(hd, mxUtils.htmlEntities(title));
  16458. hd.style.cssText = 'width:100%;text-align:center;margin-top:0px;margin-bottom:12px';
  16459. div.appendChild(hd);
  16460. var libsSection = document.createElement('div');
  16461. libsSection.style.cssText = 'border:1px solid lightGray;overflow: auto;height:300px';
  16462. libsSection.innerHTML = '<div style="text-align:center;padding:8px;"><img src="' + IMAGE_PATH + '/spin.gif"></div>';
  16463. var loadedLibs = {};
  16464. try
  16465. {
  16466. var custLibs = mxSettings.getCustomLibraries();
  16467. for (var j = 0; j < custLibs.length; j++)
  16468. {
  16469. var l = custLibs[j];
  16470. if (l.substring(0, 1) == 'R')
  16471. {
  16472. var libDesc = JSON.parse(decodeURIComponent(l.substring(1)));
  16473. loadedLibs[libDesc[0]] = {
  16474. id: libDesc[0],
  16475. title: libDesc[1],
  16476. downloadUrl: libDesc[2]
  16477. };
  16478. }
  16479. }
  16480. }
  16481. catch(e){}
  16482. this.remoteInvoke('getCustomLibraries', null, null, function(libsList)
  16483. {
  16484. libsSection.innerText = '';
  16485. if (libsList.length == 0)
  16486. {
  16487. libsSection.innerHTML = '<div style="text-align:center;padding-top:20px;color:gray;">' +
  16488. mxUtils.htmlEntities(mxResources.get('noLibraries')) + '</div>';
  16489. }
  16490. else
  16491. {
  16492. for (var i = 0; i < libsList.length; i++)
  16493. {
  16494. var lib = libsList[i];
  16495. if (loadedLibs[lib.id])
  16496. {
  16497. selectedLibs[lib.id] = lib;
  16498. }
  16499. var libCheck = this.addCheckbox(libsSection, lib.title, loadedLibs[lib.id]);
  16500. (function(lib2, check)
  16501. {
  16502. mxEvent.addListener(check, 'change', function()
  16503. {
  16504. if (this.checked)
  16505. {
  16506. selectedLibs[lib2.id] = lib2;
  16507. }
  16508. else
  16509. {
  16510. delete selectedLibs[lib2.id];
  16511. }
  16512. });
  16513. })(lib, libCheck)
  16514. }
  16515. }
  16516. }, mxUtils.bind(this, function(e)
  16517. {
  16518. libsSection.innerText = '';
  16519. var status = document.createElement('div');
  16520. status.style.padding = '8px';
  16521. status.style.textAlign = 'center';
  16522. mxUtils.write(status, mxResources.get('error') + ': ');
  16523. mxUtils.write(status, (e != null && e.message != null) ?
  16524. e.message : mxResources.get('unknownError'));
  16525. libsSection.appendChild(status);
  16526. }));
  16527. div.appendChild(libsSection);
  16528. var dlg = new CustomDialog(this, div, mxUtils.bind(this, function()
  16529. {
  16530. this.spinner.spin(document.body, mxResources.get('loading'));
  16531. var pendingLibs = 0;
  16532. for (var id in selectedLibs)
  16533. {
  16534. if (loadedLibs[id] != null) continue; //already loaded!
  16535. pendingLibs++;
  16536. (mxUtils.bind(this, function(lib)
  16537. {
  16538. this.remoteInvoke('getFileContent', [lib.downloadUrl], null, mxUtils.bind(this, function(libContent)
  16539. {
  16540. pendingLibs--;
  16541. if (pendingLibs == 0) this.spinner.stop();
  16542. try
  16543. {
  16544. this.loadLibrary(new RemoteLibrary(this, libContent, lib));
  16545. this.showSidebar();
  16546. }
  16547. catch (e)
  16548. {
  16549. this.handleError(e, mxResources.get('errorLoadingFile'));
  16550. }
  16551. }), mxUtils.bind(this, function()
  16552. {
  16553. pendingLibs--;
  16554. if (pendingLibs == 0) this.spinner.stop();
  16555. this.handleError(null, mxResources.get('errorLoadingFile'));
  16556. }));
  16557. }))(selectedLibs[id]);
  16558. }
  16559. for (var id in loadedLibs)
  16560. {
  16561. if (!selectedLibs[id]) //Removed
  16562. {
  16563. this.closeLibrary(new RemoteLibrary(this, null, loadedLibs[id])); //create a dummy library such that we can call closeLibrary
  16564. }
  16565. }
  16566. if (pendingLibs == 0) this.spinner.stop();
  16567. }), null, null, 'https://www.drawio.com/doc/faq/custom-libraries-confluence-cloud');
  16568. this.showDialog(dlg.container, 340, 390, true, true, null, null, null, null, true);
  16569. };
  16570. //Remote invokation, currently limited to functions in EditorUi (and its sub objects) for security reasons
  16571. //White-listed functions and some info about it
  16572. EditorUi.prototype.remoteInvokableFns = {
  16573. getDiagramTextContent: {isAsync: false},
  16574. getLocalStorageFile: {isAsync: false, allowedDomains: ['app.diagrams.net']},
  16575. getLocalStorageFileNames: {isAsync: false, allowedDomains: ['app.diagrams.net']},
  16576. setMigratedFlag: {isAsync: false, allowedDomains: ['app.diagrams.net']}
  16577. };
  16578. EditorUi.prototype.remoteInvokeCallbacks = [];
  16579. EditorUi.prototype.remoteInvokeQueue = [];
  16580. EditorUi.prototype.handleRemoteInvokeReady = function(remoteWin)
  16581. {
  16582. this.remoteWin = remoteWin;
  16583. for (var i = 0; i < this.remoteInvokeQueue.length; i++)
  16584. {
  16585. remoteWin.postMessage(this.remoteInvokeQueue[i], '*');
  16586. }
  16587. this.remoteInvokeQueue = [];
  16588. };
  16589. EditorUi.prototype.handleRemoteInvokeResponse = function(msg)
  16590. {
  16591. var msgMarkers = msg.msgMarkers;
  16592. var callback = this.remoteInvokeCallbacks[msgMarkers.callbackId];
  16593. if (callback == null)
  16594. {
  16595. throw new Error('No callback for ' + ((msgMarkers != null) ? msgMarkers.callbackId : 'null'));
  16596. }
  16597. else if (msg.error)
  16598. {
  16599. if (callback.error) callback.error(msg.error.errResp);
  16600. }
  16601. else if (callback.callback)
  16602. {
  16603. callback.callback.apply(this, msg.resp);
  16604. }
  16605. this.remoteInvokeCallbacks[msgMarkers.callbackId] = null; //set it to null only to keep the index
  16606. };
  16607. EditorUi.prototype.remoteInvoke = function(remoteFn, remoteFnArgs, msgMarkers, callback, error)
  16608. {
  16609. var acceptResponse = true;
  16610. var timeoutThread = window.setTimeout(mxUtils.bind(this, function()
  16611. {
  16612. acceptResponse = false;
  16613. error({code: App.ERROR_TIMEOUT, message: mxResources.get('timeout')});
  16614. }), this.timeout);
  16615. var wrapper = mxUtils.bind(this, function()
  16616. {
  16617. window.clearTimeout(timeoutThread);
  16618. if (acceptResponse)
  16619. {
  16620. callback.apply(this, arguments);
  16621. }
  16622. });
  16623. var errWrapper = mxUtils.bind(this, function()
  16624. {
  16625. window.clearTimeout(timeoutThread);
  16626. if (acceptResponse)
  16627. {
  16628. error.apply(this, arguments);
  16629. }
  16630. });
  16631. msgMarkers = msgMarkers || {};
  16632. msgMarkers.callbackId = this.remoteInvokeCallbacks.length;
  16633. this.remoteInvokeCallbacks.push({callback: wrapper, error: errWrapper});
  16634. var msg = JSON.stringify({event: 'remoteInvoke', funtionName: remoteFn, functionArgs: remoteFnArgs, msgMarkers: msgMarkers});
  16635. if (this.remoteWin != null) //remote invoke is ready
  16636. {
  16637. this.remoteWin.postMessage(msg, '*');
  16638. }
  16639. else
  16640. {
  16641. this.remoteInvokeQueue.push(msg);
  16642. }
  16643. };
  16644. EditorUi.prototype.handleRemoteInvoke = function(msg, origin)
  16645. {
  16646. var sendResponse = mxUtils.bind(this, function(resp, error)
  16647. {
  16648. var respMsg = {event: 'remoteInvokeResponse', msgMarkers: msg.msgMarkers};
  16649. if (error != null)
  16650. {
  16651. respMsg.error = {errResp: error};
  16652. }
  16653. else if (resp != null)
  16654. {
  16655. respMsg.resp = resp;
  16656. }
  16657. this.remoteWin.postMessage(JSON.stringify(respMsg), '*');
  16658. });
  16659. try
  16660. {
  16661. //Remote invoke are allowed to call functions in AC
  16662. var funtionName = msg.funtionName;
  16663. var functionInfo = this.remoteInvokableFns[funtionName];
  16664. if (functionInfo != null && typeof this[funtionName] === 'function')
  16665. {
  16666. if (functionInfo.allowedDomains)
  16667. {
  16668. var allowed = false;
  16669. for (var i = 0; i < functionInfo.allowedDomains.length; i++)
  16670. {
  16671. if (origin == 'https://' + functionInfo.allowedDomains[i])
  16672. {
  16673. allowed = true;
  16674. break;
  16675. }
  16676. }
  16677. if (!allowed)
  16678. {
  16679. sendResponse(null, 'Invalid Call: ' + funtionName + ' is not allowed.');
  16680. return;
  16681. }
  16682. }
  16683. var functionArgs = msg.functionArgs;
  16684. //Confirm functionArgs are not null and is array, otherwise, discard it
  16685. if (!Array.isArray(functionArgs))
  16686. {
  16687. functionArgs = [];
  16688. }
  16689. //for functions with callbacks (async) we assume last two arguments are success, error
  16690. if (functionInfo.isAsync)
  16691. {
  16692. //success
  16693. functionArgs.push(function()
  16694. {
  16695. sendResponse(Array.prototype.slice.apply(arguments));
  16696. });
  16697. //error
  16698. functionArgs.push(function(err)
  16699. {
  16700. sendResponse(null, err || 'Unkown Error');
  16701. });
  16702. this[funtionName].apply(this, functionArgs);
  16703. }
  16704. else
  16705. {
  16706. var resp = this[funtionName].apply(this, functionArgs);
  16707. sendResponse([resp]);
  16708. }
  16709. }
  16710. else
  16711. {
  16712. sendResponse(null, 'Invalid Call: ' + funtionName + ' is not found.');
  16713. }
  16714. }
  16715. catch(e)
  16716. {
  16717. sendResponse(null, 'Invalid Call: An error occurred, ' + e.message);
  16718. }
  16719. };
  16720. /**
  16721. * Opens the application keystore.
  16722. */
  16723. EditorUi.prototype.openDatabase = function(success, error)
  16724. {
  16725. if (this.database == null)
  16726. {
  16727. var indexedDB = window.indexedDB || window.mozIndexedDB || window.webkitIndexedDB;
  16728. if (indexedDB != null)
  16729. {
  16730. try
  16731. {
  16732. var req = indexedDB.open('database', 2);
  16733. req.onupgradeneeded = function(e)
  16734. {
  16735. try
  16736. {
  16737. var db = req.result;
  16738. if (e.oldVersion < 1)
  16739. {
  16740. // Version 1 is the first version of the database.
  16741. db.createObjectStore('objects', {keyPath: 'key'});
  16742. }
  16743. if (e.oldVersion < 2)
  16744. {
  16745. // Version 2 introduces browser file storage.
  16746. db.createObjectStore('files', {keyPath: 'title'});
  16747. db.createObjectStore('filesInfo', {keyPath: 'title'});
  16748. EditorUi.migrateStorageFiles = isLocalStorage;
  16749. }
  16750. }
  16751. catch (e)
  16752. {
  16753. if (error != null)
  16754. {
  16755. error(e);
  16756. }
  16757. }
  16758. }
  16759. req.onsuccess = mxUtils.bind(this, function(e)
  16760. {
  16761. try
  16762. {
  16763. var db = req.result;
  16764. this.database = db;
  16765. if (EditorUi.migrateStorageFiles)
  16766. {
  16767. StorageFile.migrate(db);
  16768. EditorUi.migrateStorageFiles = false;
  16769. }
  16770. if (location.host == 'app.diagrams.net' && !this.drawioMigrationStarted)
  16771. {
  16772. this.drawioMigrationStarted = true;
  16773. this.getDatabaseItem('.drawioMigrated3', mxUtils.bind(this, function(value)
  16774. {
  16775. if (value && urlParams['forceMigration'] != '1') //Already migrated
  16776. {
  16777. return;
  16778. }
  16779. var drawioFrame = document.createElement('iframe');
  16780. drawioFrame.style.display = 'none';
  16781. drawioFrame.setAttribute('src', 'https://www.draw.io?embed=1&proto=json&forceMigration=' + urlParams['forceMigration']);
  16782. document.body.appendChild(drawioFrame);
  16783. var collectNames = true, allDone = false;
  16784. var fileNames, index = 0;
  16785. var markAsMigrated = mxUtils.bind(this, function()
  16786. {
  16787. allDone = true;
  16788. this.setDatabaseItem('.drawioMigrated3', true);
  16789. drawioFrame.contentWindow.postMessage(JSON.stringify({action: 'remoteInvoke', funtionName: 'setMigratedFlag'}), '*');
  16790. });
  16791. var next = mxUtils.bind(this, function()
  16792. {
  16793. index++;
  16794. fetchOneFile();
  16795. });
  16796. var fetchOneFile = mxUtils.bind(this, function()
  16797. {
  16798. try
  16799. {
  16800. if (index >= fileNames.length)
  16801. {
  16802. markAsMigrated();
  16803. return;
  16804. }
  16805. var fileTitle = fileNames[index];
  16806. StorageFile.getFileContent(this, fileTitle, mxUtils.bind(this, function(data)
  16807. {
  16808. if (data == null || (fileTitle == '.scratchpad' && data == this.emptyLibraryXml)) //Don't overwrite
  16809. {
  16810. drawioFrame.contentWindow.postMessage(JSON.stringify({action: 'remoteInvoke', funtionName: 'getLocalStorageFile', functionArgs: [fileTitle]}), '*');
  16811. }
  16812. else
  16813. {
  16814. next();
  16815. }
  16816. }), next); //Ignore errors
  16817. }
  16818. catch(e)
  16819. {
  16820. //Log error
  16821. console.log(e);
  16822. }
  16823. });
  16824. var importOneFile = mxUtils.bind(this, function(file)
  16825. {
  16826. try
  16827. {
  16828. this.setDatabaseItem(null, [{
  16829. title: file.title,
  16830. size: file.data.length,
  16831. lastModified: Date.now(),
  16832. type: file.isLib? 'L' : 'F'
  16833. }, {
  16834. title: file.title,
  16835. data: file.data
  16836. }], next, next /* Ignore errors */, ['filesInfo', 'files']);
  16837. }
  16838. catch(e)
  16839. {
  16840. //Log error
  16841. console.log(e);
  16842. }
  16843. });
  16844. var messageListener = mxUtils.bind(this, function(evt)
  16845. {
  16846. try
  16847. {
  16848. //Only accept messages from migration iframe
  16849. if (evt.source != drawioFrame.contentWindow)
  16850. {
  16851. return;
  16852. }
  16853. var drawMsg = {};
  16854. try
  16855. {
  16856. drawMsg = JSON.parse(evt.data);
  16857. }
  16858. catch(e){} //Ignore
  16859. if (drawMsg.event == 'init')
  16860. {
  16861. drawioFrame.contentWindow.postMessage(JSON.stringify({action: 'remoteInvokeReady'}), '*');
  16862. drawioFrame.contentWindow.postMessage(JSON.stringify({action: 'remoteInvoke', funtionName: 'getLocalStorageFileNames'}), '*');
  16863. }
  16864. else if (drawMsg.event == 'remoteInvokeResponse' && !allDone)
  16865. {
  16866. if (collectNames)
  16867. {
  16868. if (drawMsg.resp != null && drawMsg.resp.length > 0 && drawMsg.resp[0] != null)
  16869. {
  16870. fileNames = drawMsg.resp[0];
  16871. collectNames = false;
  16872. fetchOneFile();
  16873. }
  16874. else
  16875. {
  16876. //Nothing in draw.io localStorage
  16877. markAsMigrated();
  16878. }
  16879. }
  16880. else
  16881. {
  16882. //Add the file, then move to the next
  16883. if (drawMsg.resp != null && drawMsg.resp.length > 0 && drawMsg.resp[0] != null)
  16884. {
  16885. importOneFile(drawMsg.resp[0]);
  16886. }
  16887. else
  16888. {
  16889. next();
  16890. }
  16891. }
  16892. }
  16893. }
  16894. catch(e)
  16895. {
  16896. console.log(e);
  16897. }
  16898. });
  16899. window.addEventListener('message', messageListener);
  16900. })); //Ignore errors
  16901. }
  16902. success(db);
  16903. db.onversionchange = function()
  16904. {
  16905. //TODO Handle DB revision update while code is running
  16906. // Save open file and request a page reload before closing the DB
  16907. db.close();
  16908. };
  16909. }
  16910. catch (e)
  16911. {
  16912. // Warn if error handler is not set
  16913. if (error != null)
  16914. {
  16915. error(e);
  16916. }
  16917. else if (window.console != null)
  16918. {
  16919. console.warn(e);
  16920. }
  16921. };
  16922. });
  16923. req.onerror = error;
  16924. req.onblocked = function()
  16925. {
  16926. //TODO Use this when a new version is introduced
  16927. // there's another open connection to same database
  16928. // and it wasn't closed after db.onversionchange triggered for them
  16929. };
  16930. }
  16931. catch (e)
  16932. {
  16933. // Warn if error handler is not set
  16934. if (error != null)
  16935. {
  16936. error(e);
  16937. }
  16938. else if (window.console != null)
  16939. {
  16940. console.error(e);
  16941. }
  16942. }
  16943. }
  16944. else if (error != null)
  16945. {
  16946. error(new Error('IndexedDB not supported'));
  16947. }
  16948. }
  16949. else
  16950. {
  16951. success(this.database);
  16952. }
  16953. };
  16954. /**
  16955. * Add/Update item(s) in the database. It supports multiple stores transactions by sending an array of data, storeName
  16956. * (key is optional, can be an array also if multiple stores are needed)
  16957. */
  16958. EditorUi.prototype.setDatabaseItem = function(key, data, success, error, storeName)
  16959. {
  16960. this.openDatabase(mxUtils.bind(this, function(db)
  16961. {
  16962. try
  16963. {
  16964. storeName = storeName || 'objects';
  16965. if (!Array.isArray(storeName))
  16966. {
  16967. storeName = [storeName];
  16968. key = [key];
  16969. data = [data];
  16970. }
  16971. var trx = db.transaction(storeName, 'readwrite');
  16972. trx.oncomplete = success;
  16973. trx.onerror = error;
  16974. for (var i = 0; i < storeName.length; i++)
  16975. {
  16976. trx.objectStore(storeName[i]).put(key != null && key[i] != null? {key: key[i], data: data[i]} : data[i]);
  16977. }
  16978. }
  16979. catch (e)
  16980. {
  16981. if (error != null)
  16982. {
  16983. error(e);
  16984. }
  16985. }
  16986. }), error);
  16987. };
  16988. /**
  16989. * Removes the item for the given key from the database.
  16990. */
  16991. EditorUi.prototype.removeDatabaseItem = function(key, success, error, storeName)
  16992. {
  16993. this.openDatabase(mxUtils.bind(this, function(db)
  16994. {
  16995. try
  16996. {
  16997. storeName = storeName || 'objects';
  16998. if (!Array.isArray(storeName))
  16999. {
  17000. storeName = [storeName];
  17001. key = [key];
  17002. }
  17003. var trx = db.transaction(storeName, 'readwrite');
  17004. trx.oncomplete = success;
  17005. trx.onerror = error;
  17006. for (var i = 0; i < storeName.length; i++)
  17007. {
  17008. trx.objectStore(storeName[i]).delete(key[i]);
  17009. }
  17010. }
  17011. catch (e)
  17012. {
  17013. if (error != null)
  17014. {
  17015. error(e);
  17016. }
  17017. }
  17018. }), error);
  17019. };
  17020. /**
  17021. * Returns one item from the database.
  17022. */
  17023. EditorUi.prototype.getDatabaseItem = function(key, success, error, storeName)
  17024. {
  17025. this.openDatabase(mxUtils.bind(this, function(db)
  17026. {
  17027. try
  17028. {
  17029. storeName = storeName || 'objects';
  17030. var trx = db.transaction([storeName], 'readonly');
  17031. var req = trx.objectStore(storeName).get(key);
  17032. req.onsuccess = function()
  17033. {
  17034. success(req.result);
  17035. };
  17036. req.onerror = error;
  17037. }
  17038. catch (e)
  17039. {
  17040. if (error != null)
  17041. {
  17042. error(e);
  17043. }
  17044. }
  17045. }), error);
  17046. };
  17047. /**
  17048. * Returns all items from the database.
  17049. */
  17050. EditorUi.prototype.getDatabaseItems = function(success, error, storeName)
  17051. {
  17052. this.openDatabase(mxUtils.bind(this, function(db)
  17053. {
  17054. try
  17055. {
  17056. storeName = storeName || 'objects';
  17057. var trx = db.transaction([storeName], 'readonly');
  17058. var req = trx.objectStore(storeName).openCursor(
  17059. IDBKeyRange.lowerBound(0));
  17060. var items = [];
  17061. req.onsuccess = function(e)
  17062. {
  17063. if (e.target.result == null)
  17064. {
  17065. success(items);
  17066. }
  17067. else
  17068. {
  17069. items.push(e.target.result.value);
  17070. e.target.result.continue();
  17071. }
  17072. };
  17073. req.onerror = error;
  17074. }
  17075. catch (e)
  17076. {
  17077. if (error != null)
  17078. {
  17079. error(e);
  17080. }
  17081. }
  17082. }), error);
  17083. };
  17084. /**
  17085. * Returns all item keys from the database.
  17086. */
  17087. EditorUi.prototype.getDatabaseItemKeys = function(success, error, storeName)
  17088. {
  17089. this.openDatabase(mxUtils.bind(this, function(db)
  17090. {
  17091. try
  17092. {
  17093. storeName = storeName || 'objects';
  17094. var trx = db.transaction([storeName], 'readonly');
  17095. var req = trx.objectStore(storeName).getAllKeys();
  17096. req.onsuccess = function()
  17097. {
  17098. success(req.result);
  17099. };
  17100. req.onerror = error;
  17101. }
  17102. catch (e)
  17103. {
  17104. if (error != null)
  17105. {
  17106. error(e);
  17107. }
  17108. }
  17109. }), error);
  17110. };
  17111. /**
  17112. * Comments: We need these functions as wrapper of File functions in order to facilitate
  17113. * overriding them if comments are needed without having a file (e.g. Confluence Plugin)
  17114. */
  17115. /**
  17116. * Are comments supported
  17117. */
  17118. EditorUi.prototype.commentsSupported = function()
  17119. {
  17120. var file = this.getCurrentFile();
  17121. return file != null? file.commentsSupported() : false;
  17122. };
  17123. /**
  17124. * Show refresh button?
  17125. */
  17126. EditorUi.prototype.commentsRefreshNeeded = function()
  17127. {
  17128. var file = this.getCurrentFile();
  17129. return file != null? file.commentsRefreshNeeded() : true;
  17130. };
  17131. /**
  17132. * Show save button?
  17133. */
  17134. EditorUi.prototype.commentsSaveNeeded = function()
  17135. {
  17136. var file = this.getCurrentFile();
  17137. return file != null? file.commentsSaveNeeded() : false;
  17138. };
  17139. /**
  17140. * Get comments
  17141. */
  17142. EditorUi.prototype.getComments = function(success, error)
  17143. {
  17144. var file = this.getCurrentFile();
  17145. if (file != null)
  17146. {
  17147. file.getComments(success, error);
  17148. }
  17149. else
  17150. {
  17151. success([]); //placeholder
  17152. }
  17153. };
  17154. /**
  17155. * Add a comment
  17156. */
  17157. EditorUi.prototype.addComment = function(comment, success, error)
  17158. {
  17159. var file = this.getCurrentFile();
  17160. if (file != null)
  17161. {
  17162. file.addComment(comment, success, error);
  17163. }
  17164. else
  17165. {
  17166. success(Date.now()); //placeholder
  17167. }
  17168. };
  17169. /**
  17170. * Can add a reply to a reply
  17171. */
  17172. EditorUi.prototype.canReplyToReplies = function()
  17173. {
  17174. var file = this.getCurrentFile();
  17175. return file != null? file.canReplyToReplies() : true;
  17176. };
  17177. /**
  17178. * Can add comments (The permission to comment)
  17179. */
  17180. EditorUi.prototype.canComment = function()
  17181. {
  17182. var file = this.getCurrentFile();
  17183. return file != null? file.canComment() : true;
  17184. };
  17185. /**
  17186. * Get a new comment object
  17187. */
  17188. EditorUi.prototype.newComment = function(content, user)
  17189. {
  17190. var file = this.getCurrentFile();
  17191. if (file != null)
  17192. {
  17193. return file.newComment(content, user)
  17194. }
  17195. else
  17196. {
  17197. return new DrawioComment(this, null, content, Date.now(), Date.now(), false, user);
  17198. }
  17199. };
  17200. //==================================================== End of comments =================================================================
  17201. /**
  17202. * Does revisions history available
  17203. */
  17204. EditorUi.prototype.isRevisionHistorySupported = function()
  17205. {
  17206. var file = this.getCurrentFile();
  17207. return file != null && file.isRevisionHistorySupported();
  17208. };
  17209. /**
  17210. * Get revisions of current file
  17211. */
  17212. EditorUi.prototype.getRevisions = function(success, error)
  17213. {
  17214. var file = this.getCurrentFile();
  17215. if (file != null && file.getRevisions)
  17216. {
  17217. file.getRevisions(success, error);
  17218. }
  17219. else
  17220. {
  17221. error({message: mxResources.get('unknownError')});
  17222. }
  17223. };
  17224. /**
  17225. * Is revisions history enabled
  17226. */
  17227. EditorUi.prototype.isRevisionHistoryEnabled = function()
  17228. {
  17229. var file = this.getCurrentFile();
  17230. return file != null &&
  17231. ((file.constructor == DriveFile && file.isEditable()) ||
  17232. file.constructor == DropboxFile);
  17233. };
  17234. //===========Adding methods to find the service running draw.io and allowing calling draw.io remote services
  17235. EditorUi.prototype.getServiceName = function()
  17236. {
  17237. return 'draw.io';
  17238. };
  17239. EditorUi.prototype.addRemoteServiceSecurityCheck = function(xhr)
  17240. {
  17241. //Using a standard header with specific sequence
  17242. xhr.setRequestHeader('Content-Language', 'da, mi, en, de-DE');
  17243. };
  17244. //===========To Be Removed Soon==========
  17245. EditorUi.prototype.loadUrl = function(url, success, error, forceBinary, retry, dataUriPrefix, noBinary, headers)
  17246. {
  17247. EditorUi.logEvent('SHOULD NOT BE CALLED: loadUrl');
  17248. return this.editor.loadUrl(url, success, error, forceBinary, retry, dataUriPrefix, noBinary, headers);
  17249. };
  17250. EditorUi.prototype.loadFonts = function(then)
  17251. {
  17252. EditorUi.logEvent('SHOULD NOT BE CALLED: loadFonts');
  17253. return this.editor.loadFonts(then);
  17254. };
  17255. EditorUi.prototype.createSvgDataUri = function(svg)
  17256. {
  17257. EditorUi.logEvent('SHOULD NOT BE CALLED: createSvgDataUri');
  17258. return Editor.createSvgDataUri(svg);
  17259. };
  17260. EditorUi.prototype.embedCssFonts = function(fontCss, then)
  17261. {
  17262. EditorUi.logEvent('SHOULD NOT BE CALLED: embedCssFonts');
  17263. return this.editor.embedCssFonts(fontCss, then);
  17264. };
  17265. EditorUi.prototype.embedExtFonts = function(callback)
  17266. {
  17267. EditorUi.logEvent('SHOULD NOT BE CALLED: embedExtFonts');
  17268. return this.editor.embedExtFonts(callback);
  17269. };
  17270. EditorUi.prototype.exportToCanvas = function(callback, width, imageCache, background, error, limitHeight,
  17271. ignoreSelection, scale, transparentBackground, addShadow, converter, graph, border, noCrop, grid, theme)
  17272. {
  17273. EditorUi.logEvent('SHOULD NOT BE CALLED: exportToCanvas');
  17274. return this.editor.exportToCanvas(callback, width, imageCache, background, error, limitHeight,
  17275. ignoreSelection, scale, transparentBackground, addShadow, converter, graph, border,
  17276. noCrop, grid, theme);
  17277. };
  17278. EditorUi.prototype.createImageUrlConverter = function()
  17279. {
  17280. EditorUi.logEvent('SHOULD NOT BE CALLED: createImageUrlConverter');
  17281. return this.editor.createImageUrlConverter();
  17282. };
  17283. EditorUi.prototype.convertImages = function(svgRoot, callback, imageCache, converter)
  17284. {
  17285. EditorUi.logEvent('SHOULD NOT BE CALLED: convertImages');
  17286. return this.editor.convertImages(svgRoot, callback, imageCache, converter);
  17287. };
  17288. EditorUi.prototype.convertImageToDataUri = function(url, callback)
  17289. {
  17290. EditorUi.logEvent('SHOULD NOT BE CALLED: convertImageToDataUri');
  17291. return this.editor.convertImageToDataUri(url, callback);
  17292. };
  17293. EditorUi.prototype.base64Encode = function(str)
  17294. {
  17295. EditorUi.logEvent('SHOULD NOT BE CALLED: base64Encode');
  17296. return Editor.base64Encode(str);
  17297. };
  17298. EditorUi.prototype.updateCRC = function(crc, data, off, len)
  17299. {
  17300. EditorUi.logEvent('SHOULD NOT BE CALLED: updateCRC');
  17301. return Editor.updateCRC(crc, data, off, len);
  17302. };
  17303. EditorUi.prototype.crc32 = function(str)
  17304. {
  17305. EditorUi.logEvent('SHOULD NOT BE CALLED: crc32');
  17306. return Editor.crc32(str);
  17307. };
  17308. EditorUi.prototype.writeGraphModelToPng = function(data, type, key, value, error)
  17309. {
  17310. EditorUi.logEvent('SHOULD NOT BE CALLED: writeGraphModelToPng');
  17311. return Editor.writeGraphModelToPng(data, type, key, value, error);
  17312. };
  17313. //=======End of To Be Removed Soon==========
  17314. EditorUi.prototype.getLocalStorageFileNames = function()
  17315. {
  17316. if (localStorage.getItem('.localStorageMigrated') == '1' && urlParams['forceMigration'] != '1')
  17317. {
  17318. return null;
  17319. }
  17320. var files = [];
  17321. for (var i = 0; i < localStorage.length; i++)
  17322. {
  17323. var key = localStorage.key(i);
  17324. var value = localStorage.getItem(key);
  17325. if (key.length > 0 && (key == '.scratchpad' || key.charAt(0) != '.') && value.length > 0)
  17326. {
  17327. var isFile = (value.substring(0, 8) === '<mxfile ' ||
  17328. value.substring(0, 5) === '<?xml' || value.substring(0, 12) === '<!--[if IE]>');
  17329. var isLib = (value.substring(0, 11) === '<mxlibrary>');
  17330. if (isFile || isLib)
  17331. {
  17332. files.push(key);
  17333. }
  17334. }
  17335. }
  17336. return files;
  17337. };
  17338. EditorUi.prototype.getLocalStorageFile = function(key)
  17339. {
  17340. if (localStorage.getItem('.localStorageMigrated') == '1' && urlParams['forceMigration'] != '1')
  17341. {
  17342. return null;
  17343. }
  17344. var value = localStorage.getItem(key);
  17345. return {title: key, data: value, isLib: value.substring(0, 11) === '<mxlibrary>'};
  17346. };
  17347. EditorUi.prototype.setMigratedFlag = function()
  17348. {
  17349. localStorage.setItem('.localStorageMigrated', '1');
  17350. };
  17351. })();
  17352. /**
  17353. * Comments Window, It is used by both editor and viewer. So, it is here in a common place
  17354. */
  17355. var CommentsWindow = function(editorUi, x, y, w, h, saveCallback)
  17356. {
  17357. var readOnly = !editorUi.canComment();
  17358. var canReplyToReplies = editorUi.canReplyToReplies();
  17359. var curEdited = null;
  17360. var div = document.createElement('div');
  17361. div.className = 'geCommentsWin';
  17362. div.style.background = (Editor.isDarkMode()) ?
  17363. Editor.darkColor : 'whiteSmoke';
  17364. var tbarHeight = (!EditorUi.compactUi) ? '30px' : '26px';
  17365. var listDiv = document.createElement('div');
  17366. listDiv.className = 'geCommentsList';
  17367. listDiv.style.backgroundColor = (Editor.isDarkMode()) ?
  17368. Editor.darkColor : 'whiteSmoke';
  17369. listDiv.style.bottom = (parseInt(tbarHeight) + 7) + 'px';
  17370. div.appendChild(listDiv);
  17371. var noComments = document.createElement('span');
  17372. noComments.style.cssText = 'display:none;padding-top:10px;text-align:center;';
  17373. mxUtils.write(noComments, mxResources.get('noCommentsFound'));
  17374. var selectionComment = null;
  17375. var ldiv = document.createElement('div');
  17376. ldiv.className = 'geToolbarContainer geCommentsToolbar';
  17377. ldiv.style.height = tbarHeight;
  17378. ldiv.style.padding = (!EditorUi.compactUi) ? '1px' : '4px 0px 3px 0px';
  17379. var link = document.createElement('a');
  17380. link.className = 'geButton';
  17381. function updateNoComments()
  17382. {
  17383. var divs = listDiv.getElementsByTagName('div');
  17384. var visibleCount = 0;
  17385. for (var i = 0; i < divs.length; i++)
  17386. {
  17387. if (divs[i].style.display != 'none' && divs[i].parentNode == listDiv)
  17388. {
  17389. visibleCount++;
  17390. }
  17391. }
  17392. noComments.style.display = (visibleCount == 0) ? 'block' : 'none';
  17393. };
  17394. function editComment(comment, cdiv, saveCallback, deleteOnCancel)
  17395. {
  17396. curEdited = {div: cdiv, comment: comment, saveCallback: saveCallback, deleteOnCancel: deleteOnCancel};
  17397. var commentTxt = cdiv.querySelector('.geCommentTxt');
  17398. var actionsDiv = cdiv.querySelector('.geCommentActionsList');
  17399. var textArea = document.createElement('textarea');
  17400. textArea.className = 'geCommentEditTxtArea';
  17401. textArea.style.minHeight = commentTxt.offsetHeight + 'px';
  17402. textArea.value = comment.content;
  17403. cdiv.insertBefore(textArea, commentTxt);
  17404. var btnDiv = document.createElement('div');
  17405. btnDiv.className = 'geCommentEditBtns';
  17406. function reset()
  17407. {
  17408. cdiv.removeChild(textArea);
  17409. cdiv.removeChild(btnDiv);
  17410. actionsDiv.style.display = 'block';
  17411. commentTxt.style.display = 'block';
  17412. };
  17413. var cancelBtn = mxUtils.button(mxResources.get('cancel'), function()
  17414. {
  17415. if (deleteOnCancel)
  17416. {
  17417. cdiv.parentNode.removeChild(cdiv);
  17418. updateNoComments();
  17419. }
  17420. else
  17421. {
  17422. reset();
  17423. }
  17424. curEdited = null;
  17425. });
  17426. cancelBtn.className = 'geCommentEditBtn';
  17427. btnDiv.appendChild(cancelBtn);
  17428. var saveBtn = mxUtils.button(mxResources.get('save'), function()
  17429. {
  17430. commentTxt.innerText = '';
  17431. comment.content = textArea.value;
  17432. mxUtils.write(commentTxt, comment.content);
  17433. reset();
  17434. saveCallback(comment);
  17435. curEdited = null;
  17436. });
  17437. // Updates modified state and handles placeholder text
  17438. mxEvent.addListener(textArea, 'keydown', mxUtils.bind(this, function(evt)
  17439. {
  17440. if (!mxEvent.isConsumed(evt))
  17441. {
  17442. if ((mxEvent.isControlDown(evt) || (mxClient.IS_MAC &&
  17443. mxEvent.isMetaDown(evt))) && evt.keyCode == 13 /* Ctrl+Enter */)
  17444. {
  17445. saveBtn.click();
  17446. mxEvent.consume(evt);
  17447. }
  17448. else if (evt.keyCode == 27 /* Escape */)
  17449. {
  17450. cancelBtn.click();
  17451. mxEvent.consume(evt);
  17452. }
  17453. }
  17454. }));
  17455. // Focused to include in viewport before focusin textbox
  17456. saveBtn.focus();
  17457. saveBtn.className = 'geCommentEditBtn gePrimaryBtn';
  17458. btnDiv.appendChild(saveBtn);
  17459. cdiv.insertBefore(btnDiv, commentTxt);
  17460. actionsDiv.style.display = 'none';
  17461. commentTxt.style.display = 'none';
  17462. textArea.focus();
  17463. };
  17464. function writeCommentDate(comment, dateDiv)
  17465. {
  17466. dateDiv.innerText = '';
  17467. var ts = new Date(comment.modifiedDate);
  17468. var str = editorUi.timeSince(ts);
  17469. if (str == null)
  17470. {
  17471. str = mxResources.get('lessThanAMinute');
  17472. }
  17473. mxUtils.write(dateDiv, mxResources.get('timeAgo', [str], '{1} ago'));
  17474. dateDiv.setAttribute('title', ts.toLocaleDateString() + ' ' +
  17475. ts.toLocaleTimeString());
  17476. };
  17477. function showBusy(commentDiv)
  17478. {
  17479. var busyImg = document.createElement('img');
  17480. busyImg.className = 'geCommentBusyImg';
  17481. busyImg.src= IMAGE_PATH + '/spin.gif';
  17482. commentDiv.appendChild(busyImg);
  17483. commentDiv.busyImg = busyImg;
  17484. };
  17485. function showError(commentDiv)
  17486. {
  17487. commentDiv.style.border = '1px solid red';
  17488. if (commentDiv.busyImg.parentNode == commentDiv)
  17489. {
  17490. commentDiv.removeChild(commentDiv.busyImg);
  17491. }
  17492. };
  17493. function showDone(commentDiv)
  17494. {
  17495. commentDiv.style.border = '';
  17496. if (commentDiv.busyImg.parentNode == commentDiv)
  17497. {
  17498. commentDiv.removeChild(commentDiv.busyImg);
  17499. }
  17500. };
  17501. function addComment(comment, parentArr, parent, level, showResolved)
  17502. {
  17503. //Skip resolved comments if showResolved is not set
  17504. if (!showResolved && comment.isResolved)
  17505. {
  17506. return;
  17507. }
  17508. noComments.style.display = 'none';
  17509. var cdiv = document.createElement('div');
  17510. cdiv.className = 'geCommentContainer';
  17511. cdiv.setAttribute('data-commentId', comment.id);
  17512. cdiv.style.marginLeft = (level * 20 + 5) + 'px';
  17513. if (comment.isResolved && !Editor.isDarkMode())
  17514. {
  17515. cdiv.style.backgroundColor = 'ghostWhite';
  17516. }
  17517. var headerDiv = document.createElement('div');
  17518. headerDiv.className = 'geCommentHeader';
  17519. var userImg = document.createElement('img');
  17520. userImg.className = 'geCommentUserImg';
  17521. userImg.src = (comment.user != null &&
  17522. comment.user.pictureUrl != null) ?
  17523. comment.user.pictureUrl : Editor.userImage;
  17524. headerDiv.appendChild(userImg);
  17525. var headerTxt = document.createElement('div');
  17526. headerTxt.className = 'geCommentHeaderTxt';
  17527. headerDiv.appendChild(headerTxt);
  17528. var usernameDiv = document.createElement('div');
  17529. usernameDiv.className = 'geCommentUsername';
  17530. mxUtils.write(usernameDiv, (comment.user != null) ?
  17531. comment.user.displayName : mxResources.get('unknownUser'));
  17532. headerTxt.appendChild(usernameDiv);
  17533. var dateDiv = document.createElement('div');
  17534. dateDiv.className = 'geCommentDate';
  17535. dateDiv.setAttribute('data-commentId', comment.id);
  17536. writeCommentDate(comment, dateDiv);
  17537. headerTxt.appendChild(dateDiv);
  17538. cdiv.appendChild(headerDiv);
  17539. var commentTxtDiv = document.createElement('div');
  17540. commentTxtDiv.className = 'geCommentTxt';
  17541. mxUtils.write(commentTxtDiv, comment.content || '');
  17542. cdiv.appendChild(commentTxtDiv);
  17543. if (comment.isLocked)
  17544. {
  17545. cdiv.style.opacity = '0.5';
  17546. }
  17547. var actionsDiv = document.createElement('div');
  17548. actionsDiv.className = 'geCommentActions';
  17549. var actionsList = document.createElement('ul');
  17550. actionsList.className = 'geCommentActionsList';
  17551. actionsDiv.appendChild(actionsList);
  17552. function addAction(name, evtHandler, hide)
  17553. {
  17554. var action = document.createElement('li');
  17555. action.className = 'geCommentAction';
  17556. var actionLnk = document.createElement('a');
  17557. actionLnk.className = 'geCommentActionLnk';
  17558. mxUtils.write(actionLnk, name);
  17559. action.appendChild(actionLnk);
  17560. mxEvent.addListener(actionLnk, 'click', function(evt)
  17561. {
  17562. evtHandler(evt, comment);
  17563. evt.preventDefault();
  17564. mxEvent.consume(evt);
  17565. });
  17566. actionsList.appendChild(action);
  17567. if (hide) action.style.display = 'none';
  17568. };
  17569. function collectReplies()
  17570. {
  17571. var replies = [];
  17572. var pdiv = cdiv;
  17573. function collectReplies(comment)
  17574. {
  17575. replies.push(pdiv);
  17576. if (comment.replies != null)
  17577. {
  17578. for (var i = 0; i < comment.replies.length; i++)
  17579. {
  17580. pdiv = pdiv.nextSibling;
  17581. collectReplies(comment.replies[i]);
  17582. }
  17583. }
  17584. }
  17585. collectReplies(comment);
  17586. return {pdiv: pdiv, replies: replies};
  17587. };
  17588. function addReply(initContent, editIt, saveCallback, doResolve, doReopen)
  17589. {
  17590. var pdiv = collectReplies().pdiv;
  17591. var newReply = editorUi.newComment(initContent, editorUi.getCurrentUser());
  17592. newReply.pCommentId = comment.id;
  17593. if (comment.replies == null) comment.replies = [];
  17594. var replyComment = addComment(newReply, comment.replies, pdiv, level + 1);
  17595. function doAddReply()
  17596. {
  17597. showBusy(replyComment);
  17598. comment.addReply(newReply, function(id)
  17599. {
  17600. newReply.id = id;
  17601. comment.replies.push(newReply);
  17602. showDone(replyComment);
  17603. if (saveCallback) saveCallback();
  17604. }, function(err)
  17605. {
  17606. doEdit();
  17607. showError(replyComment);
  17608. editorUi.handleError(err, null, null, null,
  17609. mxUtils.htmlEntities(mxResources.get('objectNotFound')));
  17610. }, doResolve, doReopen);
  17611. };
  17612. function doEdit()
  17613. {
  17614. editComment(newReply, replyComment, function(newReply)
  17615. {
  17616. doAddReply();
  17617. }, true);
  17618. };
  17619. if (editIt)
  17620. {
  17621. doEdit();
  17622. }
  17623. else
  17624. {
  17625. doAddReply();
  17626. }
  17627. };
  17628. if (!readOnly && !comment.isLocked && (level == 0 || canReplyToReplies))
  17629. {
  17630. addAction(mxResources.get('reply'), function()
  17631. {
  17632. addReply('', true);
  17633. }, comment.isResolved);
  17634. }
  17635. var user = editorUi.getCurrentUser();
  17636. if (user != null && comment.user != null &&
  17637. user.id == comment.user.id &&
  17638. !readOnly && !comment.isLocked)
  17639. {
  17640. addAction(mxResources.get('edit'), function()
  17641. {
  17642. function doEditComment()
  17643. {
  17644. editComment(comment, cdiv, function()
  17645. {
  17646. showBusy(cdiv);
  17647. comment.editComment(comment.content, function()
  17648. {
  17649. showDone(cdiv);
  17650. }, function(err)
  17651. {
  17652. showError(cdiv);
  17653. doEditComment();
  17654. editorUi.handleError(err, null, null, null,
  17655. mxUtils.htmlEntities(mxResources.get('objectNotFound')));
  17656. });
  17657. });
  17658. };
  17659. doEditComment();
  17660. }, comment.isResolved);
  17661. addAction(mxResources.get('delete'), function()
  17662. {
  17663. editorUi.confirm(mxResources.get('areYouSure'), function()
  17664. {
  17665. showBusy(cdiv);
  17666. comment.deleteComment(function(markedOnly)
  17667. {
  17668. if (markedOnly === true)
  17669. {
  17670. var commentTxt = cdiv.querySelector('.geCommentTxt');
  17671. commentTxt.innerText = '';
  17672. mxUtils.write(commentTxt, mxResources.get('msgDeleted'));
  17673. var actions = cdiv.querySelectorAll('.geCommentAction');
  17674. for (var i = 0; i < actions.length; i++)
  17675. {
  17676. actions[i].parentNode.removeChild(actions[i]);
  17677. }
  17678. showDone(cdiv);
  17679. cdiv.style.opacity = '0.5';
  17680. }
  17681. else
  17682. {
  17683. var replies = collectReplies(comment).replies;
  17684. for (var i = 0; i < replies.length; i++)
  17685. {
  17686. listDiv.removeChild(replies[i]);
  17687. }
  17688. for (var i = 0; i < parentArr.length; i++)
  17689. {
  17690. if (parentArr[i] == comment)
  17691. {
  17692. parentArr.splice(i, 1);
  17693. break;
  17694. }
  17695. }
  17696. noComments.style.display = (listDiv.getElementsByTagName('div').length == 0) ? 'block' : 'none';
  17697. }
  17698. }, function(err)
  17699. {
  17700. showError(cdiv);
  17701. editorUi.handleError(err, null, null, null,
  17702. mxUtils.htmlEntities(mxResources.get('objectNotFound')));
  17703. });
  17704. });
  17705. }, comment.isResolved);
  17706. }
  17707. if (!readOnly && !comment.isLocked && level == 0) //Resolve is a top-level action only
  17708. {
  17709. function toggleResolve(evt)
  17710. {
  17711. function doToggle()
  17712. {
  17713. var resolveActionLnk = evt.target;
  17714. resolveActionLnk.innerText = '';
  17715. comment.isResolved = !comment.isResolved;
  17716. mxUtils.write(resolveActionLnk, comment.isResolved? mxResources.get('reopen') : mxResources.get('resolve'));
  17717. var actionsDisplay = comment.isResolved? 'none' : '';
  17718. var replies = collectReplies(comment).replies;
  17719. var color = (Editor.isDarkMode()) ? 'transparent' : (comment.isResolved? 'ghostWhite' : 'white');
  17720. for (var i = 0; i < replies.length; i++)
  17721. {
  17722. replies[i].style.backgroundColor = color;
  17723. var forOpenActions = replies[i].querySelectorAll('.geCommentAction');
  17724. for (var j = 0; j < forOpenActions.length; j ++)
  17725. {
  17726. if (forOpenActions[j] == resolveActionLnk.parentNode) continue;
  17727. forOpenActions[j].style.display = actionsDisplay;
  17728. }
  17729. if (!resolvedChecked)
  17730. {
  17731. replies[i].style.display = 'none';
  17732. }
  17733. }
  17734. updateNoComments();
  17735. };
  17736. if (comment.isResolved)
  17737. {
  17738. addReply(mxResources.get('reOpened') + ': ', true, doToggle, false, true);
  17739. }
  17740. else
  17741. {
  17742. addReply(mxResources.get('markedAsResolved'), false, doToggle, true);
  17743. }
  17744. };
  17745. addAction(comment.isResolved? mxResources.get('reopen') : mxResources.get('resolve'), toggleResolve);
  17746. }
  17747. cdiv.appendChild(actionsDiv);
  17748. if (parent != null)
  17749. {
  17750. listDiv.insertBefore(cdiv, parent.nextSibling);
  17751. }
  17752. else
  17753. {
  17754. listDiv.appendChild(cdiv);
  17755. }
  17756. for (var i = 0; comment.replies != null && i < comment.replies.length; i++)
  17757. {
  17758. var reply = comment.replies[i];
  17759. reply.isResolved = comment.isResolved; //copy isResolved to child comments (replies)
  17760. addComment(reply, comment.replies, null, level + 1, showResolved);
  17761. }
  17762. if (curEdited != null)
  17763. {
  17764. if (curEdited.comment.id == comment.id)
  17765. {
  17766. var origContent = comment.content;
  17767. comment.content = curEdited.comment.content;
  17768. editComment(comment, cdiv, curEdited.saveCallback, curEdited.deleteOnCancel);
  17769. comment.content = origContent;
  17770. }
  17771. else if (curEdited.comment.id == null && curEdited.comment.pCommentId == comment.id)
  17772. {
  17773. listDiv.appendChild(curEdited.div);
  17774. editComment(curEdited.comment, curEdited.div, curEdited.saveCallback, curEdited.deleteOnCancel);
  17775. }
  17776. }
  17777. return cdiv;
  17778. };
  17779. if (!readOnly)
  17780. {
  17781. var addLink = link.cloneNode();
  17782. addLink.innerHTML = '<div class="geSprite geSprite-plus" style="display:inline-block;"></div>';
  17783. addLink.setAttribute('title', mxResources.get('create') + '...');
  17784. mxEvent.addListener(addLink, 'click', function(evt)
  17785. {
  17786. var newComment = editorUi.newComment('', editorUi.getCurrentUser());
  17787. var newCommentDiv = addComment(newComment, comments, null, 0);
  17788. function doAddComment()
  17789. {
  17790. editComment(newComment, newCommentDiv, function(newComment)
  17791. {
  17792. showBusy(newCommentDiv);
  17793. editorUi.addComment(newComment, function(id)
  17794. {
  17795. newComment.id = id;
  17796. comments.push(newComment);
  17797. showDone(newCommentDiv);
  17798. }, function(err)
  17799. {
  17800. showError(newCommentDiv);
  17801. doAddComment();
  17802. editorUi.handleError(err, null, null, null,
  17803. mxUtils.htmlEntities(mxResources.get('objectNotFound')));
  17804. });
  17805. }, true);
  17806. }
  17807. doAddComment();
  17808. evt.preventDefault();
  17809. mxEvent.consume(evt);
  17810. });
  17811. ldiv.appendChild(addLink);
  17812. }
  17813. var resolvedLink = link.cloneNode();
  17814. resolvedLink.innerHTML = '<img class="geAdaptiveAsset" src="' + IMAGE_PATH + '/check.png" style="width: 16px; padding: 2px;">';
  17815. resolvedLink.setAttribute('title', mxResources.get('showResolved'));
  17816. resolvedLink.className = 'geButton';
  17817. var resolvedChecked = false;
  17818. mxEvent.addListener(resolvedLink, 'click', function(evt)
  17819. {
  17820. resolvedChecked = !resolvedChecked;
  17821. this.className = resolvedChecked? 'geButton geCheckedBtn' : 'geButton';
  17822. refresh();
  17823. evt.preventDefault();
  17824. mxEvent.consume(evt);
  17825. });
  17826. ldiv.appendChild(resolvedLink);
  17827. if (editorUi.commentsRefreshNeeded())
  17828. {
  17829. var refreshLink = link.cloneNode();
  17830. refreshLink.innerHTML = '<img class="geAdaptiveAsset" src="' + IMAGE_PATH + '/update16.png" style="width: 16px; padding: 2px;">';
  17831. refreshLink.setAttribute('title', mxResources.get('refresh'));
  17832. refreshLink.className = 'geButton';
  17833. mxEvent.addListener(refreshLink, 'click', function(evt)
  17834. {
  17835. refresh();
  17836. evt.preventDefault();
  17837. mxEvent.consume(evt);
  17838. });
  17839. ldiv.appendChild(refreshLink);
  17840. }
  17841. if (editorUi.commentsSaveNeeded())
  17842. {
  17843. var saveLink = link.cloneNode();
  17844. saveLink.innerHTML = '<img src="' + IMAGE_PATH + '/save.png" style="width: 20px; padding: 2px;">';
  17845. saveLink.setAttribute('title', mxResources.get('save'));
  17846. saveLink.className = 'geButton geAdaptiveAsset';
  17847. mxEvent.addListener(saveLink, 'click', function(evt)
  17848. {
  17849. saveCallback();
  17850. evt.preventDefault();
  17851. mxEvent.consume(evt);
  17852. });
  17853. ldiv.appendChild(saveLink);
  17854. }
  17855. div.appendChild(ldiv);
  17856. var comments = [];
  17857. var refresh = mxUtils.bind(this, function()
  17858. {
  17859. this.hasError = false;
  17860. if (curEdited != null)
  17861. {
  17862. try
  17863. {
  17864. curEdited.div = curEdited.div.cloneNode(true);
  17865. var commentEditTxt = curEdited.div.querySelector('.geCommentEditTxtArea');
  17866. var commentEditBtns = curEdited.div.querySelector('.geCommentEditBtns');
  17867. curEdited.comment.content = commentEditTxt.value;
  17868. commentEditTxt.parentNode.removeChild(commentEditTxt);
  17869. commentEditBtns.parentNode.removeChild(commentEditBtns);
  17870. }
  17871. catch (e)
  17872. {
  17873. editorUi.handleError(e);
  17874. }
  17875. }
  17876. listDiv.innerHTML = '<div style="padding-top:10px;text-align:center;"><img src="' + IMAGE_PATH + '/spin.gif" valign="middle"> ' +
  17877. mxUtils.htmlEntities(mxResources.get('loading')) + '...</div>';
  17878. canReplyToReplies = editorUi.canReplyToReplies();
  17879. if (editorUi.commentsSupported())
  17880. {
  17881. editorUi.getComments(function(list)
  17882. {
  17883. function sortReplies(replies)
  17884. {
  17885. if (replies != null)
  17886. {
  17887. //Sort replies old to new
  17888. replies.sort(function(r1, r2)
  17889. {
  17890. return new Date(r1.modifiedDate) - new Date(r2.modifiedDate);
  17891. });
  17892. for (var i = 0; i < replies.length; i++)
  17893. {
  17894. sortReplies(replies[i].replies);
  17895. }
  17896. }
  17897. };
  17898. //Sort comments old to new
  17899. list.sort(function(c1, c2)
  17900. {
  17901. return new Date(c1.modifiedDate) - new Date(c2.modifiedDate);
  17902. });
  17903. listDiv.innerText = '';
  17904. listDiv.appendChild(noComments);
  17905. noComments.style.display = 'block';
  17906. comments = list;
  17907. for (var i = 0; i < comments.length; i++)
  17908. {
  17909. sortReplies(comments[i].replies);
  17910. addComment(comments[i], comments, null, 0, resolvedChecked);
  17911. }
  17912. //New comment case
  17913. if (curEdited != null && curEdited.comment.id == null && curEdited.comment.pCommentId == null)
  17914. {
  17915. listDiv.appendChild(curEdited.div);
  17916. editComment(curEdited.comment, curEdited.div, curEdited.saveCallback, curEdited.deleteOnCancel);
  17917. }
  17918. }, mxUtils.bind(this, function(err)
  17919. {
  17920. listDiv.innerHTML = mxUtils.htmlEntities(mxResources.get('error') + (err && err.message? ': ' + err.message : ''));
  17921. this.hasError = true;
  17922. }));
  17923. }
  17924. else
  17925. {
  17926. //TODO if comments are not supported, close the dialog
  17927. listDiv.innerHTML = mxUtils.htmlEntities(mxResources.get('error'));
  17928. }
  17929. });
  17930. refresh();
  17931. this.refreshComments = refresh;
  17932. //Refresh the modified date of each comment if the window is visible
  17933. var refreshCommentsTime = mxUtils.bind(this, function()
  17934. {
  17935. if (!this.window.isVisible()) return; //only update if it is visible
  17936. var modDateDivs = listDiv.querySelectorAll('.geCommentDate');
  17937. var modDateDivsMap = {};
  17938. for (var i = 0; i < modDateDivs.length; i++)
  17939. {
  17940. var div = modDateDivs[i];
  17941. modDateDivsMap[div.getAttribute('data-commentId')] = div;
  17942. }
  17943. function processComment(comment)
  17944. {
  17945. var div = modDateDivsMap[comment.id];
  17946. if (div == null) return; //resolved comments
  17947. writeCommentDate(comment, div);
  17948. for (var i = 0; comment.replies != null && i < comment.replies.length; i++)
  17949. {
  17950. processComment(comment.replies[i]);
  17951. }
  17952. };
  17953. for (var i = 0; i < comments.length; i++)
  17954. {
  17955. processComment(comments[i]);
  17956. }
  17957. });
  17958. //Periodically refresh time every one minute
  17959. setInterval(refreshCommentsTime, 60000);
  17960. this.refreshCommentsTime = refreshCommentsTime;
  17961. this.window = new mxWindow(mxResources.get('comments'), div, x, y, w, h, true, true);
  17962. this.window.minimumSize = new mxRectangle(0, 0, 260, 200);
  17963. this.window.destroyOnClose = false;
  17964. this.window.setMaximizable(false);
  17965. this.window.setResizable(true);
  17966. this.window.setClosable(true);
  17967. this.window.setVisible(true);
  17968. this.window.addListener(mxEvent.SHOW, mxUtils.bind(this, function()
  17969. {
  17970. this.window.fit();
  17971. }));
  17972. editorUi.installResizeHandler(this, true);
  17973. };
  17974. /**
  17975. *
  17976. */
  17977. var ConfirmDialog = function(editorUi, message, okFn, cancelFn, okLabel, cancelLabel,
  17978. okImg, cancelImg, showRememberOption, imgSrc, maxHeight)
  17979. {
  17980. var div = document.createElement('div');
  17981. div.style.textAlign = 'center';
  17982. maxHeight = (maxHeight != null) ? maxHeight : 44;
  17983. var p2 = document.createElement('div');
  17984. p2.style.padding = '6px';
  17985. p2.style.overflow = 'auto';
  17986. p2.style.maxHeight = maxHeight + 'px';
  17987. p2.style.lineHeight = '1.2em';
  17988. mxUtils.write(p2, message);
  17989. div.appendChild(p2);
  17990. if (imgSrc != null)
  17991. {
  17992. var p3 = document.createElement('div');
  17993. p3.style.padding = '6px 0 6px 0';
  17994. var img = document.createElement('img');
  17995. img.setAttribute('src', imgSrc);
  17996. p3.appendChild(img);
  17997. div.appendChild(p3);
  17998. }
  17999. var btns = document.createElement('div');
  18000. btns.style.textAlign = 'center';
  18001. btns.style.whiteSpace = 'nowrap';
  18002. var cb = document.createElement('input');
  18003. cb.setAttribute('type', 'checkbox');
  18004. var cancelBtn = mxUtils.button(cancelLabel || mxResources.get('cancel'), function()
  18005. {
  18006. editorUi.hideDialog();
  18007. if (cancelFn != null)
  18008. {
  18009. cancelFn(cb.checked);
  18010. }
  18011. });
  18012. cancelBtn.className = 'geBtn';
  18013. if (cancelImg != null)
  18014. {
  18015. cancelBtn.innerHTML = cancelImg + '<br>' + cancelBtn.innerHTML;
  18016. cancelBtn.style.paddingBottom = '8px';
  18017. cancelBtn.style.paddingTop = '8px';
  18018. cancelBtn.style.height = 'auto';
  18019. cancelBtn.style.width = '40%';
  18020. }
  18021. if (editorUi.editor.cancelFirst)
  18022. {
  18023. btns.appendChild(cancelBtn);
  18024. }
  18025. var okBtn = mxUtils.button(okLabel || mxResources.get('ok'), function()
  18026. {
  18027. editorUi.hideDialog();
  18028. if (okFn != null)
  18029. {
  18030. okFn(cb.checked);
  18031. }
  18032. });
  18033. btns.appendChild(okBtn);
  18034. if (okImg != null)
  18035. {
  18036. okBtn.innerHTML = okImg + '<br>' + okBtn.innerHTML + '<br>';
  18037. okBtn.style.paddingBottom = '8px';
  18038. okBtn.style.paddingTop = '8px';
  18039. okBtn.style.height = 'auto';
  18040. okBtn.className = 'geBtn';
  18041. okBtn.style.width = '40%';
  18042. }
  18043. else
  18044. {
  18045. okBtn.className = 'geBtn gePrimaryBtn';
  18046. }
  18047. if (!editorUi.editor.cancelFirst)
  18048. {
  18049. btns.appendChild(cancelBtn);
  18050. }
  18051. div.appendChild(btns);
  18052. if (showRememberOption)
  18053. {
  18054. btns.style.marginTop = '10px';
  18055. var p2 = document.createElement('p');
  18056. p2.style.marginTop = '20px';
  18057. p2.style.marginBottom = '0px';
  18058. p2.appendChild(cb);
  18059. var span = document.createElement('span');
  18060. mxUtils.write(span, ' ' + mxResources.get('rememberThisSetting'));
  18061. p2.appendChild(span);
  18062. div.appendChild(p2);
  18063. mxEvent.addListener(span, 'click', function(evt)
  18064. {
  18065. cb.checked = !cb.checked;
  18066. mxEvent.consume(evt);
  18067. });
  18068. }
  18069. else
  18070. {
  18071. btns.style.marginTop = '12px';
  18072. }
  18073. this.init = function()
  18074. {
  18075. okBtn.focus();
  18076. };
  18077. this.container = div;
  18078. };
  18079. /**
  18080. * Headless Editor UI class for offscreen editor instances.
  18081. */
  18082. var HeadlessEditorUi = function()
  18083. {
  18084. EditorUi.call(this, new Editor(true), document.createElement('div'), true);
  18085. };
  18086. /**
  18087. * Extends EditorUi.
  18088. */
  18089. mxUtils.extend(HeadlessEditorUi, EditorUi);
  18090. /**
  18091. * Avoid creating UI and event listeners.
  18092. */
  18093. HeadlessEditorUi.prototype.createUi = function() {};
  18094. HeadlessEditorUi.prototype.addTrees = function() {};
  18095. HeadlessEditorUi.prototype.onBeforeUnload = function() {};
  18096. HeadlessEditorUi.prototype.updateActionStates = function() {};