EditorUi.js 399 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036303730383039304030413042304330443045304630473048304930503051305230533054305530563057305830593060306130623063306430653066306730683069307030713072307330743075307630773078307930803081308230833084308530863087308830893090309130923093309430953096309730983099310031013102310331043105310631073108310931103111311231133114311531163117311831193120312131223123312431253126312731283129313031313132313331343135313631373138313931403141314231433144314531463147314831493150315131523153315431553156315731583159316031613162316331643165316631673168316931703171317231733174317531763177317831793180318131823183318431853186318731883189319031913192319331943195319631973198319932003201320232033204320532063207320832093210321132123213321432153216321732183219322032213222322332243225322632273228322932303231323232333234323532363237323832393240324132423243324432453246324732483249325032513252325332543255325632573258325932603261326232633264326532663267326832693270327132723273327432753276327732783279328032813282328332843285328632873288328932903291329232933294329532963297329832993300330133023303330433053306330733083309331033113312331333143315331633173318331933203321332233233324332533263327332833293330333133323333333433353336333733383339334033413342334333443345334633473348334933503351335233533354335533563357335833593360336133623363336433653366336733683369337033713372337333743375337633773378337933803381338233833384338533863387338833893390339133923393339433953396339733983399340034013402340334043405340634073408340934103411341234133414341534163417341834193420342134223423342434253426342734283429343034313432343334343435343634373438343934403441344234433444344534463447344834493450345134523453345434553456345734583459346034613462346334643465346634673468346934703471347234733474347534763477347834793480348134823483348434853486348734883489349034913492349334943495349634973498349935003501350235033504350535063507350835093510351135123513351435153516351735183519352035213522352335243525352635273528352935303531353235333534353535363537353835393540354135423543354435453546354735483549355035513552355335543555355635573558355935603561356235633564356535663567356835693570357135723573357435753576357735783579358035813582358335843585358635873588358935903591359235933594359535963597359835993600360136023603360436053606360736083609361036113612361336143615361636173618361936203621362236233624362536263627362836293630363136323633363436353636363736383639364036413642364336443645364636473648364936503651365236533654365536563657365836593660366136623663366436653666366736683669367036713672367336743675367636773678367936803681368236833684368536863687368836893690369136923693369436953696369736983699370037013702370337043705370637073708370937103711371237133714371537163717371837193720372137223723372437253726372737283729373037313732373337343735373637373738373937403741374237433744374537463747374837493750375137523753375437553756375737583759376037613762376337643765376637673768376937703771377237733774377537763777377837793780378137823783378437853786378737883789379037913792379337943795379637973798379938003801380238033804380538063807380838093810381138123813381438153816381738183819382038213822382338243825382638273828382938303831383238333834383538363837383838393840384138423843384438453846384738483849385038513852385338543855385638573858385938603861386238633864386538663867386838693870387138723873387438753876387738783879388038813882388338843885388638873888388938903891389238933894389538963897389838993900390139023903390439053906390739083909391039113912391339143915391639173918391939203921392239233924392539263927392839293930393139323933393439353936393739383939394039413942394339443945394639473948394939503951395239533954395539563957395839593960396139623963396439653966396739683969397039713972397339743975397639773978397939803981398239833984398539863987398839893990399139923993399439953996399739983999400040014002400340044005400640074008400940104011401240134014401540164017401840194020402140224023402440254026402740284029403040314032403340344035403640374038403940404041404240434044404540464047404840494050405140524053405440554056405740584059406040614062406340644065406640674068406940704071407240734074407540764077407840794080408140824083408440854086408740884089409040914092409340944095409640974098409941004101410241034104410541064107410841094110411141124113411441154116411741184119412041214122412341244125412641274128412941304131413241334134413541364137413841394140414141424143414441454146414741484149415041514152415341544155415641574158415941604161416241634164416541664167416841694170417141724173417441754176417741784179418041814182418341844185418641874188418941904191419241934194419541964197419841994200420142024203420442054206420742084209421042114212421342144215421642174218421942204221422242234224422542264227422842294230423142324233423442354236423742384239424042414242424342444245424642474248424942504251425242534254425542564257425842594260426142624263426442654266426742684269427042714272427342744275427642774278427942804281428242834284428542864287428842894290429142924293429442954296429742984299430043014302430343044305430643074308430943104311431243134314431543164317431843194320432143224323432443254326432743284329433043314332433343344335433643374338433943404341434243434344434543464347434843494350435143524353435443554356435743584359436043614362436343644365436643674368436943704371437243734374437543764377437843794380438143824383438443854386438743884389439043914392439343944395439643974398439944004401440244034404440544064407440844094410441144124413441444154416441744184419442044214422442344244425442644274428442944304431443244334434443544364437443844394440444144424443444444454446444744484449445044514452445344544455445644574458445944604461446244634464446544664467446844694470447144724473447444754476447744784479448044814482448344844485448644874488448944904491449244934494449544964497449844994500450145024503450445054506450745084509451045114512451345144515451645174518451945204521452245234524452545264527452845294530453145324533453445354536453745384539454045414542454345444545454645474548454945504551455245534554455545564557455845594560456145624563456445654566456745684569457045714572457345744575457645774578457945804581458245834584458545864587458845894590459145924593459445954596459745984599460046014602460346044605460646074608460946104611461246134614461546164617461846194620462146224623462446254626462746284629463046314632463346344635463646374638463946404641464246434644464546464647464846494650465146524653465446554656465746584659466046614662466346644665466646674668466946704671467246734674467546764677467846794680468146824683468446854686468746884689469046914692469346944695469646974698469947004701470247034704470547064707470847094710471147124713471447154716471747184719472047214722472347244725472647274728472947304731473247334734473547364737473847394740474147424743474447454746474747484749475047514752475347544755475647574758475947604761476247634764476547664767476847694770477147724773477447754776477747784779478047814782478347844785478647874788478947904791479247934794479547964797479847994800480148024803480448054806480748084809481048114812481348144815481648174818481948204821482248234824482548264827482848294830483148324833483448354836483748384839484048414842484348444845484648474848484948504851485248534854485548564857485848594860486148624863486448654866486748684869487048714872487348744875487648774878487948804881488248834884488548864887488848894890489148924893489448954896489748984899490049014902490349044905490649074908490949104911491249134914491549164917491849194920492149224923492449254926492749284929493049314932493349344935493649374938493949404941494249434944494549464947494849494950495149524953495449554956495749584959496049614962496349644965496649674968496949704971497249734974497549764977497849794980498149824983498449854986498749884989499049914992499349944995499649974998499950005001500250035004500550065007500850095010501150125013501450155016501750185019502050215022502350245025502650275028502950305031503250335034503550365037503850395040504150425043504450455046504750485049505050515052505350545055505650575058505950605061506250635064506550665067506850695070507150725073507450755076507750785079508050815082508350845085508650875088508950905091509250935094509550965097509850995100510151025103510451055106510751085109511051115112511351145115511651175118511951205121512251235124512551265127512851295130513151325133513451355136513751385139514051415142514351445145514651475148514951505151515251535154515551565157515851595160516151625163516451655166516751685169517051715172517351745175517651775178517951805181518251835184518551865187518851895190519151925193519451955196519751985199520052015202520352045205520652075208520952105211521252135214521552165217521852195220522152225223522452255226522752285229523052315232523352345235523652375238523952405241524252435244524552465247524852495250525152525253525452555256525752585259526052615262526352645265526652675268526952705271527252735274527552765277527852795280528152825283528452855286528752885289529052915292529352945295529652975298529953005301530253035304530553065307530853095310531153125313531453155316531753185319532053215322532353245325532653275328532953305331533253335334533553365337533853395340534153425343534453455346534753485349535053515352535353545355535653575358535953605361536253635364536553665367536853695370537153725373537453755376537753785379538053815382538353845385538653875388538953905391539253935394539553965397539853995400540154025403540454055406540754085409541054115412541354145415541654175418541954205421542254235424542554265427542854295430543154325433543454355436543754385439544054415442544354445445544654475448544954505451545254535454545554565457545854595460546154625463546454655466546754685469547054715472547354745475547654775478547954805481548254835484548554865487548854895490549154925493549454955496549754985499550055015502550355045505550655075508550955105511551255135514551555165517551855195520552155225523552455255526552755285529553055315532553355345535553655375538553955405541554255435544554555465547554855495550555155525553555455555556555755585559556055615562556355645565556655675568556955705571557255735574557555765577557855795580558155825583558455855586558755885589559055915592559355945595559655975598559956005601560256035604560556065607560856095610561156125613561456155616561756185619562056215622562356245625562656275628562956305631563256335634563556365637563856395640564156425643564456455646564756485649565056515652565356545655565656575658565956605661566256635664566556665667566856695670567156725673567456755676567756785679568056815682568356845685568656875688568956905691569256935694569556965697569856995700570157025703570457055706570757085709571057115712571357145715571657175718571957205721572257235724572557265727572857295730573157325733573457355736573757385739574057415742574357445745574657475748574957505751575257535754575557565757575857595760576157625763576457655766576757685769577057715772577357745775577657775778577957805781578257835784578557865787578857895790579157925793579457955796579757985799580058015802580358045805580658075808580958105811581258135814581558165817581858195820582158225823582458255826582758285829583058315832583358345835583658375838583958405841584258435844584558465847584858495850585158525853585458555856585758585859586058615862586358645865586658675868586958705871587258735874587558765877587858795880588158825883588458855886588758885889589058915892589358945895589658975898589959005901590259035904590559065907590859095910591159125913591459155916591759185919592059215922592359245925592659275928592959305931593259335934593559365937593859395940594159425943594459455946594759485949595059515952595359545955595659575958595959605961596259635964596559665967596859695970597159725973597459755976597759785979598059815982598359845985598659875988598959905991599259935994599559965997599859996000600160026003600460056006600760086009601060116012601360146015601660176018601960206021602260236024602560266027602860296030603160326033603460356036603760386039604060416042604360446045604660476048604960506051605260536054605560566057605860596060606160626063606460656066606760686069607060716072607360746075607660776078607960806081608260836084608560866087608860896090609160926093609460956096609760986099610061016102610361046105610661076108610961106111611261136114611561166117611861196120612161226123612461256126612761286129613061316132613361346135613661376138613961406141614261436144614561466147614861496150615161526153615461556156615761586159616061616162616361646165616661676168616961706171617261736174617561766177617861796180618161826183618461856186618761886189619061916192619361946195619661976198619962006201620262036204620562066207620862096210621162126213621462156216621762186219622062216222622362246225622662276228622962306231623262336234623562366237623862396240624162426243624462456246624762486249625062516252625362546255625662576258625962606261626262636264626562666267626862696270627162726273627462756276627762786279628062816282628362846285628662876288628962906291629262936294629562966297629862996300630163026303630463056306630763086309631063116312631363146315631663176318631963206321632263236324632563266327632863296330633163326333633463356336633763386339634063416342634363446345634663476348634963506351635263536354635563566357635863596360636163626363636463656366636763686369637063716372637363746375637663776378637963806381638263836384638563866387638863896390639163926393639463956396639763986399640064016402640364046405640664076408640964106411641264136414641564166417641864196420642164226423642464256426642764286429643064316432643364346435643664376438643964406441644264436444644564466447644864496450645164526453645464556456645764586459646064616462646364646465646664676468646964706471647264736474647564766477647864796480648164826483648464856486648764886489649064916492649364946495649664976498649965006501650265036504650565066507650865096510651165126513651465156516651765186519652065216522652365246525652665276528652965306531653265336534653565366537653865396540654165426543654465456546654765486549655065516552655365546555655665576558655965606561656265636564656565666567656865696570657165726573657465756576657765786579658065816582658365846585658665876588658965906591659265936594659565966597659865996600660166026603660466056606660766086609661066116612661366146615661666176618661966206621662266236624662566266627662866296630663166326633663466356636663766386639664066416642664366446645664666476648664966506651665266536654665566566657665866596660666166626663666466656666666766686669667066716672667366746675667666776678667966806681668266836684668566866687668866896690669166926693669466956696669766986699670067016702670367046705670667076708670967106711671267136714671567166717671867196720672167226723672467256726672767286729673067316732673367346735673667376738673967406741674267436744674567466747674867496750675167526753675467556756675767586759676067616762676367646765676667676768676967706771677267736774677567766777677867796780678167826783678467856786678767886789679067916792679367946795679667976798679968006801680268036804680568066807680868096810681168126813681468156816681768186819682068216822682368246825682668276828682968306831683268336834683568366837683868396840684168426843684468456846684768486849685068516852685368546855685668576858685968606861686268636864686568666867686868696870687168726873687468756876687768786879688068816882688368846885688668876888688968906891689268936894689568966897689868996900690169026903690469056906690769086909691069116912691369146915691669176918691969206921692269236924692569266927692869296930693169326933693469356936693769386939694069416942694369446945694669476948694969506951695269536954695569566957695869596960696169626963696469656966696769686969697069716972697369746975697669776978697969806981698269836984698569866987698869896990699169926993699469956996699769986999700070017002700370047005700670077008700970107011701270137014701570167017701870197020702170227023702470257026702770287029703070317032703370347035703670377038703970407041704270437044704570467047704870497050705170527053705470557056705770587059706070617062706370647065706670677068706970707071707270737074707570767077707870797080708170827083708470857086708770887089709070917092709370947095709670977098709971007101710271037104710571067107710871097110711171127113711471157116711771187119712071217122712371247125712671277128712971307131713271337134713571367137713871397140714171427143714471457146714771487149715071517152715371547155715671577158715971607161716271637164716571667167716871697170717171727173717471757176717771787179718071817182718371847185718671877188718971907191719271937194719571967197719871997200720172027203720472057206720772087209721072117212721372147215721672177218721972207221722272237224722572267227722872297230723172327233723472357236723772387239724072417242724372447245724672477248724972507251725272537254725572567257725872597260726172627263726472657266726772687269727072717272727372747275727672777278727972807281728272837284728572867287728872897290729172927293729472957296729772987299730073017302730373047305730673077308730973107311731273137314731573167317731873197320732173227323732473257326732773287329733073317332733373347335733673377338733973407341734273437344734573467347734873497350735173527353735473557356735773587359736073617362736373647365736673677368736973707371737273737374737573767377737873797380738173827383738473857386738773887389739073917392739373947395739673977398739974007401740274037404740574067407740874097410741174127413741474157416741774187419742074217422742374247425742674277428742974307431743274337434743574367437743874397440744174427443744474457446744774487449745074517452745374547455745674577458745974607461746274637464746574667467746874697470747174727473747474757476747774787479748074817482748374847485748674877488748974907491749274937494749574967497749874997500750175027503750475057506750775087509751075117512751375147515751675177518751975207521752275237524752575267527752875297530753175327533753475357536753775387539754075417542754375447545754675477548754975507551755275537554755575567557755875597560756175627563756475657566756775687569757075717572757375747575757675777578757975807581758275837584758575867587758875897590759175927593759475957596759775987599760076017602760376047605760676077608760976107611761276137614761576167617761876197620762176227623762476257626762776287629763076317632763376347635763676377638763976407641764276437644764576467647764876497650765176527653765476557656765776587659766076617662766376647665766676677668766976707671767276737674767576767677767876797680768176827683768476857686768776887689769076917692769376947695769676977698769977007701770277037704770577067707770877097710771177127713771477157716771777187719772077217722772377247725772677277728772977307731773277337734773577367737773877397740774177427743774477457746774777487749775077517752775377547755775677577758775977607761776277637764776577667767776877697770777177727773777477757776777777787779778077817782778377847785778677877788778977907791779277937794779577967797779877997800780178027803780478057806780778087809781078117812781378147815781678177818781978207821782278237824782578267827782878297830783178327833783478357836783778387839784078417842784378447845784678477848784978507851785278537854785578567857785878597860786178627863786478657866786778687869787078717872787378747875787678777878787978807881788278837884788578867887788878897890789178927893789478957896789778987899790079017902790379047905790679077908790979107911791279137914791579167917791879197920792179227923792479257926792779287929793079317932793379347935793679377938793979407941794279437944794579467947794879497950795179527953795479557956795779587959796079617962796379647965796679677968796979707971797279737974797579767977797879797980798179827983798479857986798779887989799079917992799379947995799679977998799980008001800280038004800580068007800880098010801180128013801480158016801780188019802080218022802380248025802680278028802980308031803280338034803580368037803880398040804180428043804480458046804780488049805080518052805380548055805680578058805980608061806280638064806580668067806880698070807180728073807480758076807780788079808080818082808380848085808680878088808980908091809280938094809580968097809880998100810181028103810481058106810781088109811081118112811381148115811681178118811981208121812281238124812581268127812881298130813181328133813481358136813781388139814081418142814381448145814681478148814981508151815281538154815581568157815881598160816181628163816481658166816781688169817081718172817381748175817681778178817981808181818281838184818581868187818881898190819181928193819481958196819781988199820082018202820382048205820682078208820982108211821282138214821582168217821882198220822182228223822482258226822782288229823082318232823382348235823682378238823982408241824282438244824582468247824882498250825182528253825482558256825782588259826082618262826382648265826682678268826982708271827282738274827582768277827882798280828182828283828482858286828782888289829082918292829382948295829682978298829983008301830283038304830583068307830883098310831183128313831483158316831783188319832083218322832383248325832683278328832983308331833283338334833583368337833883398340834183428343834483458346834783488349835083518352835383548355835683578358835983608361836283638364836583668367836883698370837183728373837483758376837783788379838083818382838383848385838683878388838983908391839283938394839583968397839883998400840184028403840484058406840784088409841084118412841384148415841684178418841984208421842284238424842584268427842884298430843184328433843484358436843784388439844084418442844384448445844684478448844984508451845284538454845584568457845884598460846184628463846484658466846784688469847084718472847384748475847684778478847984808481848284838484848584868487848884898490849184928493849484958496849784988499850085018502850385048505850685078508850985108511851285138514851585168517851885198520852185228523852485258526852785288529853085318532853385348535853685378538853985408541854285438544854585468547854885498550855185528553855485558556855785588559856085618562856385648565856685678568856985708571857285738574857585768577857885798580858185828583858485858586858785888589859085918592859385948595859685978598859986008601860286038604860586068607860886098610861186128613861486158616861786188619862086218622862386248625862686278628862986308631863286338634863586368637863886398640864186428643864486458646864786488649865086518652865386548655865686578658865986608661866286638664866586668667866886698670867186728673867486758676867786788679868086818682868386848685868686878688868986908691869286938694869586968697869886998700870187028703870487058706870787088709871087118712871387148715871687178718871987208721872287238724872587268727872887298730873187328733873487358736873787388739874087418742874387448745874687478748874987508751875287538754875587568757875887598760876187628763876487658766876787688769877087718772877387748775877687778778877987808781878287838784878587868787878887898790879187928793879487958796879787988799880088018802880388048805880688078808880988108811881288138814881588168817881888198820882188228823882488258826882788288829883088318832883388348835883688378838883988408841884288438844884588468847884888498850885188528853885488558856885788588859886088618862886388648865886688678868886988708871887288738874887588768877887888798880888188828883888488858886888788888889889088918892889388948895889688978898889989008901890289038904890589068907890889098910891189128913891489158916891789188919892089218922892389248925892689278928892989308931893289338934893589368937893889398940894189428943894489458946894789488949895089518952895389548955895689578958895989608961896289638964896589668967896889698970897189728973897489758976897789788979898089818982898389848985898689878988898989908991899289938994899589968997899889999000900190029003900490059006900790089009901090119012901390149015901690179018901990209021902290239024902590269027902890299030903190329033903490359036903790389039904090419042904390449045904690479048904990509051905290539054905590569057905890599060906190629063906490659066906790689069907090719072907390749075907690779078907990809081908290839084908590869087908890899090909190929093909490959096909790989099910091019102910391049105910691079108910991109111911291139114911591169117911891199120912191229123912491259126912791289129913091319132913391349135913691379138913991409141914291439144914591469147914891499150915191529153915491559156915791589159916091619162916391649165916691679168916991709171917291739174917591769177917891799180918191829183918491859186918791889189919091919192919391949195919691979198919992009201920292039204920592069207920892099210921192129213921492159216921792189219922092219222922392249225922692279228922992309231923292339234923592369237923892399240924192429243924492459246924792489249925092519252925392549255925692579258925992609261926292639264926592669267926892699270927192729273927492759276927792789279928092819282928392849285928692879288928992909291929292939294929592969297929892999300930193029303930493059306930793089309931093119312931393149315931693179318931993209321932293239324932593269327932893299330933193329333933493359336933793389339934093419342934393449345934693479348934993509351935293539354935593569357935893599360936193629363936493659366936793689369937093719372937393749375937693779378937993809381938293839384938593869387938893899390939193929393939493959396939793989399940094019402940394049405940694079408940994109411941294139414941594169417941894199420942194229423942494259426942794289429943094319432943394349435943694379438943994409441944294439444944594469447944894499450945194529453945494559456945794589459946094619462946394649465946694679468946994709471947294739474947594769477947894799480948194829483948494859486948794889489949094919492949394949495949694979498949995009501950295039504950595069507950895099510951195129513951495159516951795189519952095219522952395249525952695279528952995309531953295339534953595369537953895399540954195429543954495459546954795489549955095519552955395549555955695579558955995609561956295639564956595669567956895699570957195729573957495759576957795789579958095819582958395849585958695879588958995909591959295939594959595969597959895999600960196029603960496059606960796089609961096119612961396149615961696179618961996209621962296239624962596269627962896299630963196329633963496359636963796389639964096419642964396449645964696479648964996509651965296539654965596569657965896599660966196629663966496659666966796689669967096719672967396749675967696779678967996809681968296839684968596869687968896899690969196929693969496959696969796989699970097019702970397049705970697079708970997109711971297139714971597169717971897199720972197229723972497259726972797289729973097319732973397349735973697379738973997409741974297439744974597469747974897499750975197529753975497559756975797589759976097619762976397649765976697679768976997709771977297739774977597769777977897799780978197829783978497859786978797889789979097919792979397949795979697979798979998009801980298039804980598069807980898099810981198129813981498159816981798189819982098219822982398249825982698279828982998309831983298339834983598369837983898399840984198429843984498459846984798489849985098519852985398549855985698579858985998609861986298639864986598669867986898699870987198729873987498759876987798789879988098819882988398849885988698879888988998909891989298939894989598969897989898999900990199029903990499059906990799089909991099119912991399149915991699179918991999209921992299239924992599269927992899299930993199329933993499359936993799389939994099419942994399449945994699479948994999509951995299539954995599569957995899599960996199629963996499659966996799689969997099719972997399749975997699779978997999809981998299839984998599869987998899899990999199929993999499959996999799989999100001000110002100031000410005100061000710008100091001010011100121001310014100151001610017100181001910020100211002210023100241002510026100271002810029100301003110032100331003410035100361003710038100391004010041100421004310044100451004610047100481004910050100511005210053100541005510056100571005810059100601006110062100631006410065100661006710068100691007010071100721007310074100751007610077100781007910080100811008210083100841008510086100871008810089100901009110092100931009410095100961009710098100991010010101101021010310104101051010610107101081010910110101111011210113101141011510116101171011810119101201012110122101231012410125101261012710128101291013010131101321013310134101351013610137101381013910140101411014210143101441014510146101471014810149101501015110152101531015410155101561015710158101591016010161101621016310164101651016610167101681016910170101711017210173101741017510176101771017810179101801018110182101831018410185101861018710188101891019010191101921019310194101951019610197101981019910200102011020210203102041020510206102071020810209102101021110212102131021410215102161021710218102191022010221102221022310224102251022610227102281022910230102311023210233102341023510236102371023810239102401024110242102431024410245102461024710248102491025010251102521025310254102551025610257102581025910260102611026210263102641026510266102671026810269102701027110272102731027410275102761027710278102791028010281102821028310284102851028610287102881028910290102911029210293102941029510296102971029810299103001030110302103031030410305103061030710308103091031010311103121031310314103151031610317103181031910320103211032210323103241032510326103271032810329103301033110332103331033410335103361033710338103391034010341103421034310344103451034610347103481034910350103511035210353103541035510356103571035810359103601036110362103631036410365103661036710368103691037010371103721037310374103751037610377103781037910380103811038210383103841038510386103871038810389103901039110392103931039410395103961039710398103991040010401104021040310404104051040610407104081040910410104111041210413104141041510416104171041810419104201042110422104231042410425104261042710428104291043010431104321043310434104351043610437104381043910440104411044210443104441044510446104471044810449104501045110452104531045410455104561045710458104591046010461104621046310464104651046610467104681046910470104711047210473104741047510476104771047810479104801048110482104831048410485104861048710488104891049010491104921049310494104951049610497104981049910500105011050210503105041050510506105071050810509105101051110512105131051410515105161051710518105191052010521105221052310524105251052610527105281052910530105311053210533105341053510536105371053810539105401054110542105431054410545105461054710548105491055010551105521055310554105551055610557105581055910560105611056210563105641056510566105671056810569105701057110572105731057410575105761057710578105791058010581105821058310584105851058610587105881058910590105911059210593105941059510596105971059810599106001060110602106031060410605106061060710608106091061010611106121061310614106151061610617106181061910620106211062210623106241062510626106271062810629106301063110632106331063410635106361063710638106391064010641106421064310644106451064610647106481064910650106511065210653106541065510656106571065810659106601066110662106631066410665106661066710668106691067010671106721067310674106751067610677106781067910680106811068210683106841068510686106871068810689106901069110692106931069410695106961069710698106991070010701107021070310704107051070610707107081070910710107111071210713107141071510716107171071810719107201072110722107231072410725107261072710728107291073010731107321073310734107351073610737107381073910740107411074210743107441074510746107471074810749107501075110752107531075410755107561075710758107591076010761107621076310764107651076610767107681076910770107711077210773107741077510776107771077810779107801078110782107831078410785107861078710788107891079010791107921079310794107951079610797107981079910800108011080210803108041080510806108071080810809108101081110812108131081410815108161081710818108191082010821108221082310824108251082610827108281082910830108311083210833108341083510836108371083810839108401084110842108431084410845108461084710848108491085010851108521085310854108551085610857108581085910860108611086210863108641086510866108671086810869108701087110872108731087410875108761087710878108791088010881108821088310884108851088610887108881088910890108911089210893108941089510896108971089810899109001090110902109031090410905109061090710908109091091010911109121091310914109151091610917109181091910920109211092210923109241092510926109271092810929109301093110932109331093410935109361093710938109391094010941109421094310944109451094610947109481094910950109511095210953109541095510956109571095810959109601096110962109631096410965109661096710968109691097010971109721097310974109751097610977109781097910980109811098210983109841098510986109871098810989109901099110992109931099410995109961099710998109991100011001110021100311004110051100611007110081100911010110111101211013110141101511016110171101811019110201102111022110231102411025110261102711028110291103011031110321103311034110351103611037110381103911040110411104211043110441104511046110471104811049110501105111052110531105411055110561105711058110591106011061110621106311064110651106611067110681106911070110711107211073110741107511076110771107811079110801108111082110831108411085110861108711088110891109011091110921109311094110951109611097110981109911100111011110211103111041110511106111071110811109111101111111112111131111411115111161111711118111191112011121111221112311124111251112611127111281112911130111311113211133111341113511136111371113811139111401114111142111431114411145111461114711148111491115011151111521115311154111551115611157111581115911160111611116211163111641116511166111671116811169111701117111172111731117411175111761117711178111791118011181111821118311184111851118611187111881118911190111911119211193111941119511196111971119811199112001120111202112031120411205112061120711208112091121011211112121121311214112151121611217112181121911220112211122211223112241122511226112271122811229112301123111232112331123411235112361123711238112391124011241112421124311244112451124611247112481124911250112511125211253112541125511256112571125811259112601126111262112631126411265112661126711268112691127011271112721127311274112751127611277112781127911280112811128211283112841128511286112871128811289112901129111292112931129411295112961129711298112991130011301113021130311304113051130611307113081130911310113111131211313113141131511316113171131811319113201132111322113231132411325113261132711328113291133011331113321133311334113351133611337113381133911340113411134211343113441134511346113471134811349113501135111352113531135411355113561135711358113591136011361113621136311364113651136611367113681136911370113711137211373113741137511376113771137811379113801138111382113831138411385113861138711388113891139011391113921139311394113951139611397113981139911400114011140211403114041140511406114071140811409114101141111412114131141411415114161141711418114191142011421114221142311424114251142611427114281142911430114311143211433114341143511436114371143811439114401144111442114431144411445114461144711448114491145011451114521145311454114551145611457114581145911460114611146211463114641146511466114671146811469114701147111472114731147411475114761147711478114791148011481114821148311484114851148611487114881148911490114911149211493114941149511496114971149811499115001150111502115031150411505115061150711508115091151011511115121151311514115151151611517115181151911520115211152211523115241152511526115271152811529115301153111532115331153411535115361153711538115391154011541115421154311544115451154611547115481154911550115511155211553115541155511556115571155811559115601156111562115631156411565115661156711568115691157011571115721157311574115751157611577115781157911580115811158211583115841158511586115871158811589115901159111592115931159411595115961159711598115991160011601116021160311604116051160611607116081160911610116111161211613116141161511616116171161811619116201162111622116231162411625116261162711628116291163011631116321163311634116351163611637116381163911640116411164211643116441164511646116471164811649116501165111652116531165411655116561165711658116591166011661116621166311664116651166611667116681166911670116711167211673116741167511676116771167811679116801168111682116831168411685116861168711688116891169011691116921169311694116951169611697116981169911700117011170211703117041170511706117071170811709117101171111712117131171411715117161171711718117191172011721117221172311724117251172611727117281172911730117311173211733117341173511736117371173811739117401174111742117431174411745117461174711748117491175011751117521175311754117551175611757117581175911760117611176211763117641176511766117671176811769117701177111772117731177411775117761177711778117791178011781117821178311784117851178611787117881178911790117911179211793117941179511796117971179811799118001180111802118031180411805118061180711808118091181011811118121181311814118151181611817118181181911820118211182211823118241182511826118271182811829118301183111832118331183411835118361183711838118391184011841118421184311844118451184611847118481184911850118511185211853118541185511856118571185811859118601186111862118631186411865118661186711868118691187011871118721187311874118751187611877118781187911880118811188211883118841188511886118871188811889118901189111892118931189411895118961189711898118991190011901119021190311904119051190611907119081190911910119111191211913119141191511916119171191811919119201192111922119231192411925119261192711928119291193011931119321193311934119351193611937119381193911940119411194211943119441194511946119471194811949119501195111952119531195411955119561195711958119591196011961119621196311964119651196611967119681196911970119711197211973119741197511976119771197811979119801198111982119831198411985119861198711988119891199011991119921199311994119951199611997119981199912000120011200212003120041200512006120071200812009120101201112012120131201412015120161201712018120191202012021120221202312024120251202612027120281202912030120311203212033120341203512036120371203812039120401204112042120431204412045120461204712048120491205012051120521205312054120551205612057120581205912060120611206212063120641206512066120671206812069120701207112072120731207412075120761207712078120791208012081120821208312084120851208612087120881208912090120911209212093120941209512096120971209812099121001210112102121031210412105121061210712108121091211012111121121211312114121151211612117121181211912120121211212212123121241212512126121271212812129121301213112132121331213412135121361213712138121391214012141121421214312144121451214612147121481214912150121511215212153121541215512156121571215812159121601216112162121631216412165121661216712168121691217012171121721217312174121751217612177121781217912180121811218212183121841218512186121871218812189121901219112192121931219412195121961219712198121991220012201122021220312204122051220612207122081220912210122111221212213122141221512216122171221812219122201222112222122231222412225122261222712228122291223012231122321223312234122351223612237122381223912240122411224212243122441224512246122471224812249122501225112252122531225412255122561225712258122591226012261122621226312264122651226612267122681226912270122711227212273122741227512276122771227812279122801228112282122831228412285122861228712288122891229012291122921229312294122951229612297122981229912300123011230212303123041230512306123071230812309123101231112312123131231412315123161231712318123191232012321123221232312324123251232612327123281232912330123311233212333123341233512336123371233812339123401234112342123431234412345123461234712348123491235012351123521235312354123551235612357123581235912360123611236212363123641236512366123671236812369123701237112372123731237412375123761237712378123791238012381123821238312384123851238612387123881238912390123911239212393123941239512396123971239812399124001240112402124031240412405124061240712408124091241012411124121241312414124151241612417124181241912420124211242212423124241242512426124271242812429124301243112432124331243412435124361243712438124391244012441124421244312444124451244612447124481244912450124511245212453124541245512456124571245812459124601246112462124631246412465124661246712468124691247012471124721247312474124751247612477124781247912480124811248212483124841248512486124871248812489124901249112492124931249412495124961249712498124991250012501125021250312504125051250612507125081250912510125111251212513125141251512516125171251812519125201252112522125231252412525125261252712528125291253012531125321253312534125351253612537125381253912540125411254212543125441254512546125471254812549125501255112552125531255412555125561255712558125591256012561125621256312564125651256612567125681256912570125711257212573125741257512576125771257812579125801258112582125831258412585125861258712588125891259012591125921259312594125951259612597125981259912600126011260212603126041260512606126071260812609126101261112612126131261412615126161261712618126191262012621126221262312624126251262612627126281262912630126311263212633126341263512636126371263812639126401264112642126431264412645126461264712648126491265012651126521265312654126551265612657126581265912660126611266212663126641266512666126671266812669126701267112672126731267412675126761267712678126791268012681126821268312684126851268612687126881268912690126911269212693126941269512696126971269812699127001270112702127031270412705127061270712708127091271012711127121271312714127151271612717127181271912720127211272212723127241272512726127271272812729127301273112732127331273412735127361273712738127391274012741127421274312744127451274612747127481274912750127511275212753127541275512756127571275812759127601276112762127631276412765127661276712768127691277012771127721277312774127751277612777127781277912780127811278212783127841278512786127871278812789127901279112792127931279412795127961279712798127991280012801128021280312804128051280612807128081280912810128111281212813128141281512816128171281812819128201282112822128231282412825128261282712828128291283012831128321283312834128351283612837128381283912840128411284212843128441284512846128471284812849128501285112852128531285412855128561285712858128591286012861128621286312864128651286612867128681286912870128711287212873128741287512876128771287812879128801288112882128831288412885128861288712888128891289012891128921289312894128951289612897128981289912900129011290212903129041290512906129071290812909129101291112912129131291412915129161291712918129191292012921129221292312924129251292612927129281292912930129311293212933129341293512936129371293812939129401294112942129431294412945129461294712948129491295012951129521295312954129551295612957129581295912960129611296212963129641296512966129671296812969129701297112972129731297412975129761297712978129791298012981129821298312984129851298612987129881298912990129911299212993129941299512996129971299812999130001300113002130031300413005130061300713008130091301013011130121301313014130151301613017130181301913020130211302213023130241302513026130271302813029130301303113032130331303413035130361303713038130391304013041130421304313044130451304613047130481304913050130511305213053130541305513056130571305813059130601306113062130631306413065130661306713068130691307013071130721307313074130751307613077130781307913080130811308213083130841308513086130871308813089130901309113092130931309413095130961309713098130991310013101131021310313104131051310613107131081310913110131111311213113131141311513116131171311813119131201312113122131231312413125131261312713128131291313013131131321313313134131351313613137131381313913140131411314213143131441314513146131471314813149131501315113152131531315413155131561315713158131591316013161131621316313164131651316613167131681316913170131711317213173131741317513176131771317813179131801318113182131831318413185131861318713188131891319013191131921319313194131951319613197131981319913200132011320213203132041320513206132071320813209132101321113212132131321413215132161321713218132191322013221132221322313224132251322613227132281322913230132311323213233132341323513236132371323813239132401324113242132431324413245132461324713248132491325013251132521325313254132551325613257132581325913260132611326213263132641326513266132671326813269132701327113272132731327413275132761327713278132791328013281132821328313284132851328613287132881328913290132911329213293132941329513296132971329813299133001330113302133031330413305133061330713308133091331013311133121331313314133151331613317133181331913320133211332213323133241332513326133271332813329133301333113332133331333413335133361333713338133391334013341133421334313344133451334613347133481334913350133511335213353133541335513356133571335813359133601336113362133631336413365133661336713368133691337013371133721337313374133751337613377133781337913380133811338213383133841338513386133871338813389133901339113392133931339413395133961339713398133991340013401134021340313404134051340613407134081340913410134111341213413134141341513416134171341813419134201342113422134231342413425134261342713428134291343013431134321343313434134351343613437134381343913440134411344213443134441344513446134471344813449134501345113452134531345413455134561345713458134591346013461134621346313464134651346613467134681346913470134711347213473134741347513476134771347813479134801348113482134831348413485134861348713488134891349013491134921349313494134951349613497134981349913500135011350213503135041350513506135071350813509135101351113512135131351413515135161351713518135191352013521135221352313524135251352613527135281352913530135311353213533135341353513536135371353813539135401354113542135431354413545135461354713548135491355013551135521355313554135551355613557135581355913560135611356213563135641356513566135671356813569135701357113572135731357413575135761357713578135791358013581135821358313584135851358613587135881358913590135911359213593135941359513596135971359813599136001360113602136031360413605136061360713608136091361013611136121361313614136151361613617136181361913620136211362213623136241362513626136271362813629136301363113632136331363413635136361363713638136391364013641136421364313644136451364613647136481364913650136511365213653136541365513656136571365813659136601366113662136631366413665136661366713668136691367013671136721367313674136751367613677136781367913680136811368213683136841368513686136871368813689136901369113692136931369413695136961369713698136991370013701137021370313704137051370613707137081370913710137111371213713137141371513716137171371813719137201372113722137231372413725137261372713728137291373013731137321373313734137351373613737137381373913740137411374213743137441374513746137471374813749137501375113752137531375413755137561375713758137591376013761137621376313764137651376613767137681376913770137711377213773137741377513776137771377813779137801378113782137831378413785137861378713788137891379013791137921379313794137951379613797137981379913800138011380213803138041380513806138071380813809138101381113812138131381413815138161381713818138191382013821138221382313824138251382613827138281382913830138311383213833138341383513836138371383813839138401384113842138431384413845138461384713848138491385013851138521385313854138551385613857138581385913860138611386213863138641386513866138671386813869138701387113872138731387413875138761387713878138791388013881138821388313884138851388613887138881388913890138911389213893138941389513896138971389813899139001390113902139031390413905139061390713908139091391013911139121391313914139151391613917139181391913920139211392213923139241392513926139271392813929139301393113932139331393413935139361393713938139391394013941139421394313944139451394613947139481394913950139511395213953139541395513956139571395813959139601396113962139631396413965139661396713968139691397013971139721397313974139751397613977139781397913980139811398213983139841398513986139871398813989139901399113992139931399413995139961399713998139991400014001140021400314004140051400614007140081400914010140111401214013140141401514016140171401814019140201402114022140231402414025140261402714028140291403014031140321403314034140351403614037140381403914040140411404214043140441404514046140471404814049140501405114052140531405414055140561405714058140591406014061140621406314064140651406614067140681406914070140711407214073140741407514076140771407814079140801408114082140831408414085140861408714088140891409014091140921409314094140951409614097140981409914100141011410214103141041410514106141071410814109141101411114112141131411414115141161411714118141191412014121141221412314124141251412614127141281412914130141311413214133141341413514136141371413814139141401414114142141431414414145141461414714148141491415014151141521415314154141551415614157141581415914160141611416214163141641416514166141671416814169141701417114172141731417414175141761417714178141791418014181141821418314184141851418614187141881418914190141911419214193141941419514196141971419814199142001420114202142031420414205142061420714208142091421014211142121421314214142151421614217142181421914220142211422214223142241422514226142271422814229142301423114232142331423414235142361423714238142391424014241142421424314244142451424614247142481424914250142511425214253142541425514256142571425814259142601426114262142631426414265142661426714268142691427014271142721427314274142751427614277142781427914280142811428214283142841428514286142871428814289142901429114292142931429414295142961429714298142991430014301143021430314304143051430614307143081430914310143111431214313143141431514316143171431814319143201432114322143231432414325143261432714328143291433014331143321433314334143351433614337143381433914340143411434214343143441434514346143471434814349143501435114352143531435414355143561435714358143591436014361143621436314364143651436614367143681436914370143711437214373143741437514376143771437814379143801438114382143831438414385143861438714388143891439014391143921439314394143951439614397143981439914400144011440214403144041440514406144071440814409144101441114412144131441414415144161441714418144191442014421144221442314424144251442614427144281442914430144311443214433144341443514436144371443814439144401444114442144431444414445144461444714448144491445014451144521445314454144551445614457144581445914460144611446214463144641446514466144671446814469144701447114472144731447414475144761447714478144791448014481144821448314484144851448614487144881448914490144911449214493144941449514496144971449814499145001450114502145031450414505145061450714508145091451014511145121451314514145151451614517145181451914520145211452214523145241452514526145271452814529145301453114532145331453414535145361453714538145391454014541145421454314544145451454614547145481454914550145511455214553145541455514556145571455814559145601456114562145631456414565145661456714568145691457014571145721457314574145751457614577145781457914580145811458214583145841458514586145871458814589145901459114592145931459414595145961459714598145991460014601146021460314604146051460614607146081460914610146111461214613146141461514616146171461814619146201462114622146231462414625146261462714628146291463014631146321463314634146351463614637146381463914640146411464214643146441464514646146471464814649146501465114652146531465414655146561465714658146591466014661146621466314664146651466614667146681466914670146711467214673146741467514676146771467814679146801468114682146831468414685146861468714688146891469014691146921469314694146951469614697146981469914700147011470214703147041470514706147071470814709147101471114712147131471414715147161471714718147191472014721147221472314724147251472614727147281472914730147311473214733147341473514736147371473814739147401474114742147431474414745147461474714748147491475014751147521475314754147551475614757147581475914760147611476214763147641476514766147671476814769147701477114772147731477414775147761477714778147791478014781147821478314784147851478614787147881478914790147911479214793147941479514796147971479814799148001480114802148031480414805148061480714808148091481014811148121481314814148151481614817148181481914820148211482214823148241482514826148271482814829148301483114832148331483414835148361483714838148391484014841148421484314844148451484614847148481484914850148511485214853148541485514856148571485814859148601486114862148631486414865148661486714868148691487014871148721487314874148751487614877148781487914880148811488214883148841488514886148871488814889148901489114892148931489414895148961489714898148991490014901149021490314904149051490614907149081490914910149111491214913149141491514916149171491814919149201492114922149231492414925149261492714928149291493014931149321493314934149351493614937149381493914940149411494214943149441494514946149471494814949149501495114952149531495414955149561495714958149591496014961149621496314964149651496614967149681496914970149711497214973149741497514976149771497814979149801498114982149831498414985149861498714988149891499014991149921499314994149951499614997149981499915000150011500215003150041500515006150071500815009150101501115012150131501415015150161501715018150191502015021150221502315024150251502615027150281502915030150311503215033150341503515036150371503815039150401504115042150431504415045150461504715048150491505015051150521505315054150551505615057150581505915060150611506215063150641506515066150671506815069150701507115072150731507415075150761507715078150791508015081150821508315084150851508615087150881508915090150911509215093150941509515096150971509815099151001510115102151031510415105151061510715108151091511015111151121511315114151151511615117151181511915120151211512215123151241512515126151271512815129151301513115132151331513415135151361513715138151391514015141151421514315144151451514615147151481514915150151511515215153151541515515156151571515815159151601516115162151631516415165151661516715168151691517015171151721517315174151751517615177151781517915180
  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 = uiTheme != 'atlas';
  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 != 'support.draw.io';
  29. /**
  30. * Protocol and hostname to use for embedded files. Default is https://www.draw.io
  31. */
  32. EditorUi.drawHost = window.DRAWIO_BASE_URL;
  33. /**
  34. * Protocol and hostname to use for embedded files. Default is https://www.draw.io
  35. */
  36. EditorUi.lightboxHost = window.DRAWIO_LIGHTBOX_URL;
  37. /**
  38. * Switch to disable logging for mode and search terms.
  39. */
  40. EditorUi.lastErrorMessage = null;
  41. /**
  42. * Switch to disable logging for mode and search terms.
  43. */
  44. EditorUi.ignoredAnonymizedChars = '\n\t`~!@#$%^&*()_+{}|:"<>?-=[]\;\'.\/,\n\t';
  45. /**
  46. * Specifies the URL for the templates index file.
  47. */
  48. EditorUi.templateFile = TEMPLATE_PATH + '/index.xml';
  49. /**
  50. * Specifies the URL for the diffsync cache.
  51. */
  52. EditorUi.cacheUrl = (urlParams['dev'] == '1') ? '/cache' : window.REALTIME_URL;
  53. if (EditorUi.cacheUrl == null && typeof DrawioFile !== 'undefined')
  54. {
  55. DrawioFile.SYNC = 'none'; //Disable real-time sync
  56. }
  57. /**
  58. * Cache timeout is 10 seconds.
  59. */
  60. Editor.cacheTimeout = 10000;
  61. /**
  62. * Switch to enable PlantUML in the insert from text dialog.
  63. * NOTE: This must also be enabled on the server-side.
  64. */
  65. EditorUi.enablePlantUml = EditorUi.enableLogging;
  66. /**
  67. * https://github.com/electron/electron/issues/2288
  68. */
  69. EditorUi.isElectronApp = window != null && window.process != null &&
  70. window.process.versions != null && window.process.versions['electron'] != null;
  71. /**
  72. * Specifies if drafts should be saved in IndexedDB.
  73. */
  74. EditorUi.enableDrafts = !mxClient.IS_CHROMEAPP && !EditorUi.isElectronApp &&
  75. isLocalStorage && urlParams['drafts'] != '0';
  76. /**
  77. * Link for scratchpad help.
  78. */
  79. EditorUi.scratchpadHelpLink = 'https://www.diagrams.net/doc/faq/scratchpad';
  80. /**
  81. * Default Mermaid config without using foreign objects in flowcharts.
  82. */
  83. EditorUi.defaultMermaidConfig = {
  84. theme:'neutral',
  85. arrowMarkerAbsolute:false,
  86. flowchart:
  87. {
  88. htmlLabels:false
  89. },
  90. sequence:
  91. {
  92. diagramMarginX:50,
  93. diagramMarginY:10,
  94. actorMargin:50,
  95. width:150,
  96. height:65,
  97. boxMargin:10,
  98. boxTextMargin:5,
  99. noteMargin:10,
  100. messageMargin:35,
  101. mirrorActors:true,
  102. bottomMarginAdj:1,
  103. useMaxWidth:true,
  104. rightAngles:false,
  105. showSequenceNumbers:false
  106. },
  107. gantt:{
  108. titleTopMargin:25,
  109. barHeight:20,
  110. barGap:4,
  111. topPadding:50,
  112. leftPadding:75,
  113. gridLineStartPadding:35,
  114. fontSize:11,
  115. fontFamily:'"Open-Sans", "sans-serif"',
  116. numberSectionStyles:4,
  117. axisFormat:'%Y-%m-%d'
  118. }
  119. };
  120. /**
  121. * Updates action states depending on the selection.
  122. */
  123. EditorUi.logError = function(message, url, linenumber, colno, err, severity, quiet)
  124. {
  125. severity = ((severity != null) ? severity : (message.indexOf('NetworkError') >= 0 ||
  126. message.indexOf('SecurityError') >= 0 || message.indexOf('NS_ERROR_FAILURE') >= 0 ||
  127. message.indexOf('out of memory') >= 0) ? 'CONFIG' : 'SEVERE');
  128. if (EditorUi.enableLogging && urlParams['dev'] != '1')
  129. {
  130. try
  131. {
  132. if (message == EditorUi.lastErrorMessage || (message != null && url != null &&
  133. ((message.indexOf('Script error') != -1) || (message.indexOf('extension') != -1))))
  134. {
  135. // TODO log external domain script failure "Script error." is
  136. // reported when the error occurs in a script that is hosted
  137. // on a domain other than the domain of the current page
  138. }
  139. // DocumentClosedError seems to be an FF bug an can be ignored for now
  140. else if (message != null && message.indexOf('DocumentClosedError') < 0)
  141. {
  142. EditorUi.lastErrorMessage = message;
  143. var logDomain = window.DRAWIO_LOG_URL != null ? window.DRAWIO_LOG_URL : '';
  144. err = (err != null) ? err : new Error(message);
  145. var img = new Image();
  146. img.src = logDomain + '/log?severity=' + severity + '&v=' + encodeURIComponent(EditorUi.VERSION) +
  147. '&msg=clientError:' + encodeURIComponent(message) + ':url:' + encodeURIComponent(window.location.href) +
  148. ':lnum:' + encodeURIComponent(linenumber) + ((colno != null) ? ':colno:' + encodeURIComponent(colno) : '') +
  149. ((err != null && err.stack != null) ? '&stack=' + encodeURIComponent(err.stack) : '');
  150. }
  151. }
  152. catch (e)
  153. {
  154. // do nothing
  155. }
  156. }
  157. try
  158. {
  159. if (!quiet && window.console != null)
  160. {
  161. console.error(severity, message, url, linenumber, colno, err);
  162. }
  163. }
  164. catch (e)
  165. {
  166. // ignore
  167. }
  168. };
  169. /**
  170. * Updates action states depending on the selection.
  171. */
  172. EditorUi.logEvent = function(data)
  173. {
  174. if (urlParams['dev'] == '1')
  175. {
  176. EditorUi.debug('logEvent', data);
  177. }
  178. else if (EditorUi.enableLogging)
  179. {
  180. try
  181. {
  182. var logDomain = window.DRAWIO_LOG_URL != null ? window.DRAWIO_LOG_URL : '';
  183. var img = new Image();
  184. img.src = logDomain + '/images/1x1.png?' +
  185. 'v=' + encodeURIComponent(EditorUi.VERSION) +
  186. ((data != null) ? '&data=' + encodeURIComponent(JSON.stringify(data)) : '');
  187. }
  188. catch (e)
  189. {
  190. // ignore
  191. }
  192. }
  193. };
  194. /**
  195. * Sending error reports.
  196. */
  197. EditorUi.sendReport = function(data, maxLength)
  198. {
  199. if (urlParams['dev'] == '1')
  200. {
  201. EditorUi.debug('sendReport', data);
  202. }
  203. else if (EditorUi.enableLogging)
  204. {
  205. try
  206. {
  207. maxLength = (maxLength != null) ? maxLength : 50000;
  208. if (data.length > maxLength)
  209. {
  210. data = data.substring(0, maxLength) + '\n...[SHORTENED]'
  211. }
  212. mxUtils.post('/email', 'version=' + encodeURIComponent(EditorUi.VERSION) +
  213. '&url=' + encodeURIComponent(window.location.href) +
  214. '&data=' + encodeURIComponent(data));
  215. }
  216. catch (e)
  217. {
  218. // ignore
  219. }
  220. }
  221. };
  222. /**
  223. * Adds the listener for automatically saving the diagram for local changes.
  224. */
  225. EditorUi.debug = function()
  226. {
  227. try
  228. {
  229. if (window.console != null && urlParams['test'] == '1')
  230. {
  231. var args = [new Date().toISOString()];
  232. for (var i = 0; i < arguments.length; i++)
  233. {
  234. if (arguments[i] != null)
  235. {
  236. args.push(arguments[i]);
  237. }
  238. }
  239. console.log.apply(console, args);
  240. }
  241. }
  242. catch (e)
  243. {
  244. // ignore
  245. }
  246. };
  247. /**
  248. * Static method for pasing PNG files.
  249. */
  250. EditorUi.parsePng = function(f, fn, error)
  251. {
  252. var pos = 0;
  253. function fread(d, count)
  254. {
  255. var start = pos;
  256. pos += count;
  257. return d.substring(start, pos);
  258. };
  259. // Reads unsigned long 32 bit big endian
  260. function _freadint(d)
  261. {
  262. var bytes = fread(d, 4);
  263. return bytes.charCodeAt(3) + (bytes.charCodeAt(2) << 8) +
  264. (bytes.charCodeAt(1) << 16) + (bytes.charCodeAt(0) << 24);
  265. };
  266. // Checks signature
  267. if (fread(f,8) != String.fromCharCode(137) + 'PNG' + String.fromCharCode(13, 10, 26, 10))
  268. {
  269. if (error != null)
  270. {
  271. error();
  272. }
  273. return;
  274. }
  275. // Reads header chunk
  276. fread(f,4);
  277. if (fread(f,4) != 'IHDR')
  278. {
  279. if (error != null)
  280. {
  281. error();
  282. }
  283. return;
  284. }
  285. fread(f, 17);
  286. do
  287. {
  288. var n = _freadint(f);
  289. var type = fread(f,4);
  290. if (fn != null)
  291. {
  292. if (fn(pos - 8, type, n))
  293. {
  294. break;
  295. }
  296. }
  297. value = fread(f,n);
  298. fread(f,4);
  299. if (type == 'IEND')
  300. {
  301. break;
  302. }
  303. }
  304. while (n);
  305. };
  306. /**
  307. * Removes any values, styles and geometries from the given XML node.
  308. */
  309. EditorUi.removeChildNodes = function(node)
  310. {
  311. while (node.firstChild != null)
  312. {
  313. node.removeChild(node.firstChild);
  314. }
  315. };
  316. /**
  317. * Contains the default XML for an empty diagram.
  318. */
  319. EditorUi.prototype.emptyDiagramXml = '<mxGraphModel><root><mxCell id="0"/><mxCell id="1" parent="0"/></root></mxGraphModel>';
  320. /**
  321. *
  322. */
  323. EditorUi.prototype.emptyLibraryXml = '<mxlibrary>[]</mxlibrary>';
  324. /**
  325. * Sets the delay for autosave in milliseconds. Default is 2000.
  326. */
  327. EditorUi.prototype.mode = null;
  328. /**
  329. * General timeout is 25 seconds.
  330. * LATER: Move to Editor
  331. */
  332. EditorUi.prototype.timeout = Editor.prototype.timeout;
  333. /**
  334. * Allows for two buttons in the sidebar footer.
  335. */
  336. EditorUi.prototype.sidebarFooterHeight = 38;
  337. /**
  338. * Specifies the default custom shape style.
  339. */
  340. 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;';
  341. /**
  342. * Defines the maximum size for images.
  343. */
  344. EditorUi.prototype.maxBackgroundSize = 1600;
  345. /**
  346. * Defines the maximum size for images.
  347. */
  348. EditorUi.prototype.maxImageSize = 520;
  349. /**
  350. * Defines the maximum width for pasted text.
  351. * Use 0 to disable check.
  352. */
  353. EditorUi.prototype.maxTextWidth = 520;
  354. /**
  355. * Images above 100K should be resampled.
  356. */
  357. EditorUi.prototype.resampleThreshold = 100000;
  358. /**
  359. * Maximum allowed size for images is 1 MB.
  360. */
  361. EditorUi.prototype.maxImageBytes = 1000000;
  362. /**
  363. * Maximum size for background images is 2.5 MB.
  364. */
  365. EditorUi.prototype.maxBackgroundBytes = 2500000;
  366. /**
  367. * Maximum size for text files in labels is 0.5 MB.
  368. */
  369. EditorUi.prototype.maxTextBytes = 500000;
  370. /**
  371. * Holds the current file.
  372. */
  373. EditorUi.prototype.currentFile = null;
  374. /**
  375. * Specifies if PDF export should be done via print dialog. Default is
  376. * false which uses the PhantomJS backend to create the PDF.
  377. */
  378. EditorUi.prototype.printPdfExport = false;
  379. /**
  380. * Specifies if PDF export with pages is enabled.
  381. */
  382. EditorUi.prototype.pdfPageExport = true;
  383. /**
  384. * Restores app defaults for UI
  385. */
  386. EditorUi.prototype.formatEnabled = urlParams['format'] != '0';
  387. /**
  388. * Whether template action should be shown in insert menu.
  389. */
  390. EditorUi.prototype.insertTemplateEnabled = true;
  391. /**
  392. * Restores app defaults for UI
  393. */
  394. EditorUi.prototype.closableScratchpad = true;
  395. /**
  396. * Capability check for canvas export
  397. */
  398. (function()
  399. {
  400. EditorUi.prototype.useCanvasForExport = false;
  401. EditorUi.prototype.jpgSupported = false;
  402. // Checks if canvas is supported
  403. try
  404. {
  405. var cnv = document.createElement('canvas');
  406. EditorUi.prototype.canvasSupported = !!(cnv.getContext && cnv.getContext('2d'));
  407. }
  408. catch (e)
  409. {
  410. // ignore
  411. }
  412. try
  413. {
  414. var canvas = document.createElement('canvas');
  415. var img = new Image();
  416. // LATER: Capability check should not be async
  417. img.onload = function()
  418. {
  419. try
  420. {
  421. var ctx = canvas.getContext('2d');
  422. ctx.drawImage(img, 0, 0);
  423. // Works in Chrome, Firefox, Edge, Safari and Opera
  424. var result = canvas.toDataURL('image/png');
  425. EditorUi.prototype.useCanvasForExport = result != null && result.length > 6;
  426. }
  427. catch (e)
  428. {
  429. // ignore
  430. }
  431. };
  432. // Checks if SVG with foreignObject can be exported
  433. 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>';
  434. img.src = 'data:image/svg+xml;base64,' + btoa(unescape(encodeURIComponent(svg)));
  435. }
  436. catch (e)
  437. {
  438. // ignore
  439. }
  440. // Checks for client-side JPG support
  441. try
  442. {
  443. var canvas = document.createElement('canvas');
  444. canvas.width = canvas.height = 1;
  445. var uri = canvas.toDataURL('image/jpeg');
  446. EditorUi.prototype.jpgSupported = (uri.match('image/jpeg') !== null);
  447. }
  448. catch (e)
  449. {
  450. // ignore
  451. }
  452. })();
  453. /**
  454. * Hook for subclassers.
  455. */
  456. EditorUi.prototype.openLink = function(url, target, allowOpener)
  457. {
  458. // LATER: Replace this with direct calls to graph
  459. return this.editor.graph.openLink(url, target, allowOpener);
  460. };
  461. /**
  462. * Hook for subclassers.
  463. */
  464. EditorUi.prototype.showSplash = function(force) { };
  465. /**
  466. * Abstraction for local storage access.
  467. */
  468. EditorUi.prototype.getLocalData = function(key, fn)
  469. {
  470. fn(localStorage.getItem(key));
  471. };
  472. /**
  473. * Abstraction for local storage access.
  474. */
  475. EditorUi.prototype.setLocalData = function(key, data, fn)
  476. {
  477. localStorage.setItem(key, data);
  478. if (fn != null)
  479. {
  480. fn();
  481. }
  482. };
  483. /**
  484. * Abstraction for local storage access.
  485. */
  486. EditorUi.prototype.removeLocalData = function(key, fn)
  487. {
  488. localStorage.removeItem(key)
  489. fn();
  490. };
  491. EditorUi.prototype.setMathEnabled = function(value)
  492. {
  493. this.editor.graph.mathEnabled = value;
  494. this.editor.updateGraphComponents();
  495. this.editor.graph.refresh();
  496. this.fireEvent(new mxEventObject('mathEnabledChanged'));
  497. };
  498. EditorUi.prototype.isMathEnabled = function(value)
  499. {
  500. return this.editor.graph.mathEnabled;
  501. };
  502. /**
  503. * Returns true if offline app, which isn't a defined thing
  504. */
  505. EditorUi.prototype.isOfflineApp = function()
  506. {
  507. return urlParams['offline'] == '1';
  508. };
  509. /**
  510. * Returns true if no external comms allowed or possible
  511. */
  512. EditorUi.prototype.isOffline = function(ignoreStealth)
  513. {
  514. return this.isOfflineApp() || !navigator.onLine || (!ignoreStealth && (urlParams['stealth'] == '1' || urlParams['lockdown'] == '1'));
  515. };
  516. /**
  517. * Translates this point by the given vector.
  518. *
  519. * @param {number} dx X-coordinate of the translation.
  520. * @param {number} dy Y-coordinate of the translation.
  521. */
  522. EditorUi.prototype.createSpinner = function(x, y, size)
  523. {
  524. var autoPosition = (x == null || y == null);
  525. size = (size != null) ? size : 24;
  526. var spinner = new Spinner({
  527. lines: 12, // The number of lines to draw
  528. length: size, // The length of each line
  529. width: Math.round(size / 3), // The line thickness
  530. radius: Math.round(size / 2), // The radius of the inner circle
  531. rotate: 0, // The rotation offset
  532. color: (Editor.isDarkMode()) ? '#c0c0c0' : '#000', // #rgb or #rrggbb
  533. speed: 1.5, // Rounds per second
  534. trail: 60, // Afterglow percentage
  535. shadow: false, // Whether to render a shadow
  536. hwaccel: false, // Whether to use hardware acceleration
  537. zIndex: 2e9 // The z-index (defaults to 2000000000)
  538. });
  539. // Extends spin method to include an optional label
  540. var oldSpin = spinner.spin;
  541. spinner.spin = function(container, label)
  542. {
  543. var result = false;
  544. if (!this.active)
  545. {
  546. oldSpin.call(this, container);
  547. this.active = true;
  548. if (label != null)
  549. {
  550. if (autoPosition)
  551. {
  552. y = Math.max(document.body.clientHeight || 0, document.documentElement.clientHeight || 0) / 2;
  553. x = document.body.clientWidth / 2 - 2;
  554. }
  555. var status = document.createElement('div');
  556. status.style.position = 'absolute';
  557. status.style.whiteSpace = 'nowrap';
  558. status.style.background = '#4B4243';
  559. status.style.color = 'white';
  560. status.style.fontFamily = 'Helvetica, Arial';
  561. status.style.fontSize = '9pt';
  562. status.style.padding = '6px';
  563. status.style.paddingLeft = '10px';
  564. status.style.paddingRight = '10px';
  565. status.style.zIndex = 2e9;
  566. status.style.left = Math.max(0, x) + 'px';
  567. status.style.top = Math.max(0, y + 70) + 'px';
  568. mxUtils.setPrefixedStyle(status.style, 'borderRadius', '6px');
  569. mxUtils.setPrefixedStyle(status.style, 'transform', 'translate(-50%,-50%)');
  570. if (!Editor.isDarkMode())
  571. {
  572. mxUtils.setPrefixedStyle(status.style, 'boxShadow', '2px 2px 3px 0px #ddd');
  573. }
  574. if (label.substring(label.length - 3, label.length) != '...' &&
  575. label.charAt(label.length - 1) != '!')
  576. {
  577. label = label + '...';
  578. }
  579. status.innerHTML = label;
  580. container.appendChild(status);
  581. spinner.status = status;
  582. }
  583. // Pause returns a function to resume the spinner
  584. this.pause = mxUtils.bind(this, function()
  585. {
  586. var fn = function() { };
  587. if (this.active)
  588. {
  589. fn = mxUtils.bind(this, function()
  590. {
  591. this.spin(container, label);
  592. });
  593. }
  594. this.stop();
  595. return fn;
  596. });
  597. result = true;
  598. }
  599. return result;
  600. };
  601. // Extends stop method to remove the optional label
  602. var oldStop = spinner.stop;
  603. spinner.stop = function()
  604. {
  605. oldStop.call(this);
  606. this.active = false;
  607. if (spinner.status != null && spinner.status.parentNode != null)
  608. {
  609. spinner.status.parentNode.removeChild(spinner.status);
  610. }
  611. spinner.status = null;
  612. };
  613. spinner.pause = function()
  614. {
  615. return function() {};
  616. };
  617. return spinner;
  618. };
  619. /**
  620. * Returns true if the given string contains a compatible graph model.
  621. */
  622. EditorUi.prototype.isCompatibleString = function(data)
  623. {
  624. try
  625. {
  626. var doc = mxUtils.parseXml(data);
  627. var node = this.editor.extractGraphModel(doc.documentElement, true);
  628. return node != null && node.getElementsByTagName('parsererror').length == 0;
  629. }
  630. catch (e)
  631. {
  632. // ignore
  633. }
  634. return false;
  635. };
  636. /**
  637. * Returns true if the given binary data is a Visio file.
  638. */
  639. EditorUi.prototype.isVisioData = function(data)
  640. {
  641. return data.length > 8 && ((data.charCodeAt(0) == 0xD0 && data.charCodeAt(1) == 0xCF &&
  642. data.charCodeAt(2) == 0x11 && data.charCodeAt(3) == 0xE0 && data.charCodeAt(4) == 0xA1 && data.charCodeAt(5) == 0xB1 &&
  643. data.charCodeAt(6) == 0x1A && data.charCodeAt(7) == 0xE1) || (data.charCodeAt(0) == 0x50 && data.charCodeAt(1) == 0x4B &&
  644. data.charCodeAt(2) == 0x03 && data.charCodeAt(3) == 0x04) || (data.charCodeAt(0) == 0x50 && data.charCodeAt(1) == 0x4B &&
  645. data.charCodeAt(2) == 0x03 && data.charCodeAt(3) == 0x06));
  646. };
  647. /**
  648. * Returns true if the given binary data is a Visio file that requires remote conversion.
  649. * This code returns true for vss, vsd and vdx files.
  650. */
  651. EditorUi.prototype.isRemoteVisioData = function(data)
  652. {
  653. return data.length > 8 && ((data.charCodeAt(0) == 0xD0 && data.charCodeAt(1) == 0xCF &&
  654. data.charCodeAt(2) == 0x11 && data.charCodeAt(3) == 0xE0 && data.charCodeAt(4) == 0xA1 && data.charCodeAt(5) == 0xB1 &&
  655. data.charCodeAt(6) == 0x1A && data.charCodeAt(7) == 0xE1) || (data.charCodeAt(0) == 0x3C && data.charCodeAt(1) == 0x3F &&
  656. data.charCodeAt(2) == 0x78 && data.charCodeAt(3) == 0x6D && data.charCodeAt(3) == 0x6C));
  657. };
  658. /**
  659. * Returns true if the given binary data is a PNG file.
  660. */
  661. EditorUi.prototype.isPngData = function(data)
  662. {
  663. return data.length > 8 && data.charCodeAt(0) == 137 && data.charCodeAt(1) == 80 &&
  664. data.charCodeAt(2) == 78 && data.charCodeAt(3) == 71 && data.charCodeAt(4) == 13 &&
  665. data.charCodeAt(5) == 10 && data.charCodeAt(6) == 26 && data.charCodeAt(7) == 10;
  666. };
  667. /**
  668. * Adds keyboard shortcuts for page handling.
  669. */
  670. var editorUiCreateKeyHandler = EditorUi.prototype.createKeyHandler;
  671. EditorUi.prototype.createKeyHandler = function(editor)
  672. {
  673. var keyHandler = editorUiCreateKeyHandler.apply(this, arguments);
  674. if (!this.editor.chromeless || this.editor.editable)
  675. {
  676. var keyHandlerGetFunction = keyHandler.getFunction;
  677. var graph = this.editor.graph;
  678. var ui = this;
  679. keyHandler.getFunction = function(evt)
  680. {
  681. if (graph.isSelectionEmpty() && ui.pages != null && ui.pages.length > 0)
  682. {
  683. var idx = ui.getSelectedPageIndex();
  684. if (mxEvent.isShiftDown(evt))
  685. {
  686. if (evt.keyCode == 37)
  687. {
  688. return function()
  689. {
  690. if (idx > 0)
  691. {
  692. ui.movePage(idx, idx - 1);
  693. }
  694. };
  695. }
  696. else if (evt.keyCode == 38)
  697. {
  698. return function()
  699. {
  700. if (idx > 0)
  701. {
  702. ui.movePage(idx, 0);
  703. }
  704. };
  705. }
  706. else if (evt.keyCode == 39)
  707. {
  708. return function()
  709. {
  710. if (idx < ui.pages.length - 1)
  711. {
  712. ui.movePage(idx, idx + 1);
  713. }
  714. };
  715. }
  716. else if (evt.keyCode == 40)
  717. {
  718. return function()
  719. {
  720. if (idx < ui.pages.length - 1)
  721. {
  722. ui.movePage(idx, ui.pages.length - 1);
  723. }
  724. };
  725. }
  726. }
  727. else if (mxEvent.isControlDown(evt) || (mxClient.IS_MAC && mxEvent.isMetaDown(evt)))
  728. {
  729. if (evt.keyCode == 37)
  730. {
  731. return function()
  732. {
  733. if (idx > 0)
  734. {
  735. ui.selectNextPage(false);
  736. }
  737. };
  738. }
  739. else if (evt.keyCode == 38)
  740. {
  741. return function()
  742. {
  743. if (idx > 0)
  744. {
  745. ui.selectPage(ui.pages[0]);
  746. }
  747. };
  748. }
  749. else if (evt.keyCode == 39)
  750. {
  751. return function()
  752. {
  753. if (idx < ui.pages.length - 1)
  754. {
  755. ui.selectNextPage(true);
  756. }
  757. };
  758. }
  759. else if (evt.keyCode == 40)
  760. {
  761. return function()
  762. {
  763. if (idx < ui.pages.length - 1)
  764. {
  765. ui.selectPage(ui.pages[ui.pages.length - 1]);
  766. }
  767. };
  768. }
  769. }
  770. }
  771. return keyHandlerGetFunction.apply(this, arguments);
  772. };
  773. }
  774. return keyHandler;
  775. };
  776. /**
  777. * Extracts the mxfile from the given HTML data from a data transfer event.
  778. */
  779. var editorUiExtractGraphModelFromHtml = EditorUi.prototype.extractGraphModelFromHtml;
  780. EditorUi.prototype.extractGraphModelFromHtml = function(data)
  781. {
  782. var result = editorUiExtractGraphModelFromHtml.apply(this, arguments);
  783. if (result == null)
  784. {
  785. try
  786. {
  787. var idx = data.indexOf('&lt;mxfile ');
  788. if (idx >= 0)
  789. {
  790. var idx2 = data.lastIndexOf('&lt;/mxfile&gt;');
  791. if (idx2 > idx)
  792. {
  793. result = data.substring(idx, idx2 + 15).replace(/&gt;/g, '>').
  794. replace(/&lt;/g, '<').replace(/\\&quot;/g, '"').replace(/\n/g, '');
  795. }
  796. }
  797. else
  798. {
  799. // Gets compressed data from mxgraph element in HTML document
  800. var doc = mxUtils.parseXml(data);
  801. var node = this.editor.extractGraphModel(doc.documentElement, this.pages != null ||
  802. this.diagramContainer.style.visibility == 'hidden');
  803. result = (node != null) ? mxUtils.getXml(node) : '';
  804. }
  805. }
  806. catch (e)
  807. {
  808. // ignore
  809. }
  810. }
  811. return result;
  812. };
  813. /**
  814. * Workaround for malformed xhtml meta element bug 07.08.16. The trailing slash was missing causing
  815. * reopen to fail trying to parse. Used in replaceFileData, setFileData and importFile.
  816. */
  817. EditorUi.prototype.validateFileData = function(data)
  818. {
  819. if (data != null && data.length > 0)
  820. {
  821. var index = data.indexOf('<meta charset="utf-8">');
  822. if (index >= 0)
  823. {
  824. var replaceString = '<meta charset="utf-8"/>';
  825. var replaceStrLen = replaceString.length;
  826. data = data.slice(0, index) + replaceString + data.slice(index + replaceStrLen - 1, data.length);
  827. }
  828. data = Graph.zapGremlins(data);
  829. }
  830. return data;
  831. };
  832. /**
  833. *
  834. */
  835. EditorUi.prototype.replaceFileData = function(data)
  836. {
  837. data = this.validateFileData(data);
  838. var node = (data != null && data.length > 0) ? mxUtils.parseXml(data).documentElement : null;
  839. // Some nodes must be extracted here to find the mxfile node
  840. // LATER: Remove duplicate call to extractGraphModel in overridden setGraphXml
  841. var tmp = (node != null) ? this.editor.extractGraphModel(node, true) : null;
  842. if (tmp != null)
  843. {
  844. node = tmp;
  845. }
  846. if (node != null)
  847. {
  848. var graph = this.editor.graph;
  849. graph.model.beginUpdate();
  850. try
  851. {
  852. var oldPages = (this.pages != null) ? this.pages.slice() : null;
  853. var nodes = node.getElementsByTagName('diagram');
  854. if (urlParams['pages'] != '0' || nodes.length > 1 ||
  855. (nodes.length == 1 && nodes[0].hasAttribute('name')))
  856. {
  857. this.fileNode = node;
  858. this.pages = (this.pages != null) ? this.pages : [];
  859. // Wraps page nodes
  860. for (var i = nodes.length - 1; i >= 0; i--)
  861. {
  862. var page = this.updatePageRoot(new DiagramPage(nodes[i]));
  863. // Checks for invalid page names
  864. if (page.getName() == null)
  865. {
  866. page.setName(mxResources.get('pageWithNumber', [i + 1]));
  867. }
  868. graph.model.execute(new ChangePage(this, page, (i == 0) ? page : null, 0));
  869. }
  870. }
  871. else
  872. {
  873. // Creates tabbed file structure if enforced by URL
  874. if (urlParams['pages'] != '0' && this.fileNode == null)
  875. {
  876. this.fileNode = node.ownerDocument.createElement('mxfile');
  877. this.currentPage = new DiagramPage(node.ownerDocument.createElement('diagram'));
  878. this.currentPage.setName(mxResources.get('pageWithNumber', [1]));
  879. graph.model.execute(new ChangePage(this, this.currentPage, this.currentPage, 0));
  880. }
  881. // Avoids scroll offset when switching page
  882. this.editor.setGraphXml(node);
  883. // Avoids duplicate parsing of the XML stored in the node
  884. if (this.currentPage != null)
  885. {
  886. this.currentPage.root = this.editor.graph.model.root;
  887. }
  888. }
  889. if (oldPages != null)
  890. {
  891. for (var i = 0; i < oldPages.length; i++)
  892. {
  893. graph.model.execute(new ChangePage(this, oldPages[i], null));
  894. }
  895. }
  896. }
  897. finally
  898. {
  899. graph.model.endUpdate();
  900. }
  901. }
  902. };
  903. /**
  904. * Translates this point by the given vector.
  905. *
  906. * @param {number} dx X-coordinate of the translation.
  907. * @param {number} dy Y-coordinate of the translation.
  908. */
  909. EditorUi.prototype.createFileData = function(node, graph, file, url, forceXml, forceSvg, forceHtml,
  910. embeddedCallback, ignoreSelection, compact, uncompressed)
  911. {
  912. graph = (graph != null) ? graph : this.editor.graph;
  913. forceXml = (forceXml != null) ? forceXml : false;
  914. ignoreSelection = (ignoreSelection != null) ? ignoreSelection : true;
  915. var editLink = null;
  916. var redirect = null;
  917. if (file == null || file.getMode() == App.MODE_DEVICE || file.getMode() == App.MODE_BROWSER)
  918. {
  919. editLink = '_blank';
  920. }
  921. else
  922. {
  923. editLink = url;
  924. redirect = editLink;
  925. }
  926. if (node == null)
  927. {
  928. return '';
  929. }
  930. else
  931. {
  932. var fileNode = node;
  933. // Ignores case for possible HTML or XML nodes
  934. if (fileNode.nodeName.toLowerCase() != 'mxfile')
  935. {
  936. if (uncompressed)
  937. {
  938. var diagramNode = node.ownerDocument.createElement('diagram');
  939. diagramNode.setAttribute('id', Editor.guid());
  940. diagramNode.appendChild(node);
  941. fileNode = node.ownerDocument.createElement('mxfile');
  942. fileNode.appendChild(diagramNode);
  943. }
  944. else
  945. {
  946. // Removes control chars in input for correct roundtrip check
  947. var text = Graph.zapGremlins(mxUtils.getXml(node));
  948. var data = Graph.compress(text);
  949. // Fallback to plain XML for invalid compression
  950. // TODO: Remove this fallback with active pages
  951. if (Graph.decompress(data) != text)
  952. {
  953. return text;
  954. }
  955. else
  956. {
  957. var diagramNode = node.ownerDocument.createElement('diagram');
  958. diagramNode.setAttribute('id', Editor.guid());
  959. mxUtils.setTextContent(diagramNode, data);
  960. fileNode = node.ownerDocument.createElement('mxfile');
  961. fileNode.appendChild(diagramNode);
  962. }
  963. }
  964. }
  965. if (!compact)
  966. {
  967. // Removes old metadata
  968. fileNode.removeAttribute('userAgent');
  969. fileNode.removeAttribute('version');
  970. fileNode.removeAttribute('editor');
  971. fileNode.removeAttribute('pages');
  972. fileNode.removeAttribute('type');
  973. if (mxClient.IS_CHROMEAPP)
  974. {
  975. fileNode.setAttribute('host', 'Chrome');
  976. }
  977. else if (EditorUi.isElectronApp)
  978. {
  979. fileNode.setAttribute('host', 'Electron');
  980. }
  981. else
  982. {
  983. fileNode.setAttribute('host', window.location.hostname);
  984. }
  985. // Adds new metadata
  986. fileNode.setAttribute('modified', new Date().toISOString());
  987. fileNode.setAttribute('agent', navigator.appVersion);
  988. fileNode.setAttribute('version', EditorUi.VERSION);
  989. fileNode.setAttribute('etag', Editor.guid());
  990. var md = (file != null) ? file.getMode() : this.mode;
  991. if (md != null)
  992. {
  993. fileNode.setAttribute('type', md);
  994. }
  995. if (fileNode.getElementsByTagName('diagram').length > 1 && this.pages != null)
  996. {
  997. fileNode.setAttribute('pages', this.pages.length);
  998. }
  999. }
  1000. else
  1001. {
  1002. fileNode = fileNode.cloneNode(true);
  1003. fileNode.removeAttribute('modified');
  1004. fileNode.removeAttribute('host');
  1005. fileNode.removeAttribute('agent');
  1006. fileNode.removeAttribute('etag');
  1007. fileNode.removeAttribute('userAgent');
  1008. fileNode.removeAttribute('version');
  1009. fileNode.removeAttribute('editor');
  1010. fileNode.removeAttribute('type');
  1011. }
  1012. var xml = (uncompressed) ? mxUtils.getPrettyXml(fileNode) : mxUtils.getXml(fileNode);
  1013. // Writes the file as an embedded HTML file
  1014. if (!forceSvg && !forceXml && (forceHtml || (file != null && /(\.html)$/i.test(file.getTitle()))))
  1015. {
  1016. xml = this.getHtml2(mxUtils.getXml(fileNode), graph, (file != null) ? file.getTitle() : null, editLink, redirect);
  1017. }
  1018. // Maps the XML data to the content attribute in the SVG node
  1019. else if (forceSvg || (!forceXml && file != null && /(\.svg)$/i.test(file.getTitle())))
  1020. {
  1021. if (file != null && (file.getMode() == App.MODE_DEVICE || file.getMode() == App.MODE_BROWSER))
  1022. {
  1023. url = null;
  1024. }
  1025. xml = this.getEmbeddedSvg(xml, graph, url, null, embeddedCallback, ignoreSelection, redirect);
  1026. }
  1027. return xml;
  1028. }
  1029. };
  1030. /**
  1031. * Translates this point by the given vector.
  1032. *
  1033. * @param {number} dx X-coordinate of the translation.
  1034. * @param {number} dy Y-coordinate of the translation.
  1035. */
  1036. EditorUi.prototype.getXmlFileData = function(ignoreSelection, currentPage, uncompressed)
  1037. {
  1038. ignoreSelection = (ignoreSelection != null) ? ignoreSelection : true;
  1039. currentPage = (currentPage != null) ? currentPage : false;
  1040. uncompressed = (uncompressed != null) ? uncompressed : !Editor.compressXml;
  1041. // Generats graph model XML node for single page export
  1042. var node = this.editor.getGraphXml(ignoreSelection);
  1043. if (ignoreSelection && this.fileNode != null && this.currentPage != null)
  1044. {
  1045. // Updates current page XML if selection is ignored
  1046. EditorUi.removeChildNodes(this.currentPage.node);
  1047. mxUtils.setTextContent(this.currentPage.node, Graph.compressNode(node));
  1048. // Creates a clone of the file node for processing
  1049. node = this.fileNode.cloneNode(false);
  1050. // Appends the node of the page and applies compression
  1051. function appendPage(pageNode)
  1052. {
  1053. var models = pageNode.getElementsByTagName('mxGraphModel');
  1054. var modelNode = (models.length > 0) ? models[0] : null;
  1055. var clone = pageNode;
  1056. if (modelNode == null && uncompressed)
  1057. {
  1058. var text = mxUtils.trim(mxUtils.getTextContent(pageNode));
  1059. clone = pageNode.cloneNode(false);
  1060. if (text.length > 0)
  1061. {
  1062. var tmp = Graph.decompress(text);
  1063. if (tmp != null && tmp.length > 0)
  1064. {
  1065. clone.appendChild(mxUtils.parseXml(tmp).documentElement);
  1066. }
  1067. }
  1068. }
  1069. else if (modelNode != null && !uncompressed)
  1070. {
  1071. clone = pageNode.cloneNode(false);
  1072. mxUtils.setTextContent(clone, Graph.compressNode(modelNode));
  1073. }
  1074. else
  1075. {
  1076. clone = pageNode.cloneNode(true);
  1077. }
  1078. node.appendChild(clone);
  1079. };
  1080. if (currentPage)
  1081. {
  1082. appendPage(this.currentPage.node);
  1083. }
  1084. else
  1085. {
  1086. // Restores order of pages
  1087. for (var i = 0; i < this.pages.length; i++)
  1088. {
  1089. if (this.currentPage != this.pages[i])
  1090. {
  1091. if (this.pages[i].needsUpdate)
  1092. {
  1093. var enc = new mxCodec(mxUtils.createXmlDocument());
  1094. var temp = enc.encode(new mxGraphModel(this.pages[i].root));
  1095. this.editor.graph.saveViewState(this.pages[i].viewState, temp);
  1096. EditorUi.removeChildNodes(this.pages[i].node);
  1097. mxUtils.setTextContent(this.pages[i].node, Graph.compressNode(temp));
  1098. // Marks the page as up-to-date
  1099. delete this.pages[i].needsUpdate;
  1100. }
  1101. }
  1102. appendPage(this.pages[i].node);
  1103. }
  1104. }
  1105. }
  1106. return node;
  1107. };
  1108. /**
  1109. * Removes any values, styles and geometries from the given XML node.
  1110. */
  1111. EditorUi.prototype.anonymizeString = function(text, zeros)
  1112. {
  1113. var result = [];
  1114. for (var i = 0; i < text.length; i++)
  1115. {
  1116. var c = text.charAt(i);
  1117. if (EditorUi.ignoredAnonymizedChars.indexOf(c) >= 0)
  1118. {
  1119. result.push(c);
  1120. }
  1121. else if (!isNaN(parseInt(c)))
  1122. {
  1123. result.push((zeros) ? '0' : Math.round(Math.random() * 9));
  1124. }
  1125. else if (c.toLowerCase() != c)
  1126. {
  1127. result.push(String.fromCharCode(65 + Math.round(Math.random() * 25)));
  1128. }
  1129. else if (c.toUpperCase() != c)
  1130. {
  1131. result.push(String.fromCharCode(97 + Math.round(Math.random() * 25)));
  1132. }
  1133. else if (/\s/.test(c))
  1134. {
  1135. /* any whitespace */
  1136. result.push(' ');
  1137. }
  1138. else
  1139. {
  1140. result.push('?');
  1141. }
  1142. }
  1143. return result.join('');
  1144. };
  1145. /**
  1146. * Removes any values, styles and geometries from the given XML node.
  1147. */
  1148. EditorUi.prototype.anonymizePatch = function(patch)
  1149. {
  1150. if (patch[EditorUi.DIFF_INSERT] != null)
  1151. {
  1152. for (var i = 0; i < patch[EditorUi.DIFF_INSERT].length; i++)
  1153. {
  1154. try
  1155. {
  1156. var data = patch[EditorUi.DIFF_INSERT][i].data;
  1157. var doc = mxUtils.parseXml(data);
  1158. var clone = doc.documentElement.cloneNode(false);
  1159. if (clone.getAttribute('name') != null)
  1160. {
  1161. clone.setAttribute('name', this.anonymizeString(clone.getAttribute('name')));
  1162. }
  1163. patch[EditorUi.DIFF_INSERT][i].data = mxUtils.getXml(clone);
  1164. }
  1165. catch (e)
  1166. {
  1167. patch[EditorUi.DIFF_INSERT][i].data = e.message;
  1168. }
  1169. }
  1170. }
  1171. if (patch[EditorUi.DIFF_UPDATE] != null)
  1172. {
  1173. for (var pageId in patch[EditorUi.DIFF_UPDATE])
  1174. {
  1175. var diff = patch[EditorUi.DIFF_UPDATE][pageId];
  1176. if (diff.name != null)
  1177. {
  1178. diff.name = this.anonymizeString(diff.name);
  1179. }
  1180. if (diff.cells != null)
  1181. {
  1182. var anonymizeCellDiffs = mxUtils.bind(this, function(key)
  1183. {
  1184. var cellDiffs = diff.cells[key];
  1185. if (cellDiffs != null)
  1186. {
  1187. for (var cellId in cellDiffs)
  1188. {
  1189. if (cellDiffs[cellId].value != null)
  1190. {
  1191. cellDiffs[cellId].value = '[' +
  1192. cellDiffs[cellId].value.length + ']';
  1193. }
  1194. if (cellDiffs[cellId].xmlValue != null)
  1195. {
  1196. cellDiffs[cellId].xmlValue = '[' +
  1197. cellDiffs[cellId].xmlValue.length + ']';
  1198. }
  1199. if (cellDiffs[cellId].style != null)
  1200. {
  1201. cellDiffs[cellId].style = '[' +
  1202. cellDiffs[cellId].style.length + ']';
  1203. }
  1204. if (Object.keys(cellDiffs[cellId]).length == 0)
  1205. {
  1206. delete cellDiffs[cellId];
  1207. }
  1208. }
  1209. if (Object.keys(cellDiffs).length == 0)
  1210. {
  1211. delete diff.cells[key];
  1212. }
  1213. }
  1214. });
  1215. anonymizeCellDiffs(EditorUi.DIFF_INSERT);
  1216. anonymizeCellDiffs(EditorUi.DIFF_UPDATE);
  1217. if (Object.keys(diff.cells).length == 0)
  1218. {
  1219. delete diff.cells;
  1220. }
  1221. }
  1222. if (Object.keys(diff).length == 0)
  1223. {
  1224. delete patch[EditorUi.DIFF_UPDATE][pageId];
  1225. }
  1226. }
  1227. if (Object.keys(patch[EditorUi.DIFF_UPDATE]).length == 0)
  1228. {
  1229. delete patch[EditorUi.DIFF_UPDATE];
  1230. }
  1231. }
  1232. return patch;
  1233. };
  1234. /**
  1235. * Removes any values, styles and geometries from the given XML node.
  1236. */
  1237. EditorUi.prototype.anonymizeAttributes = function(node, zeros)
  1238. {
  1239. if (node.attributes != null)
  1240. {
  1241. for (var i = 0; i < node.attributes.length; i++)
  1242. {
  1243. if (node.attributes[i].name != 'as')
  1244. {
  1245. node.setAttribute(node.attributes[i].name,
  1246. this.anonymizeString(node.attributes[i].value, zeros));
  1247. }
  1248. }
  1249. }
  1250. if (node.childNodes != null)
  1251. {
  1252. for (var i = 0; i < node.childNodes.length; i++)
  1253. {
  1254. this.anonymizeAttributes(node.childNodes[i], zeros);
  1255. }
  1256. }
  1257. };
  1258. /**
  1259. * Removes any values, styles and geometries from the given XML node.
  1260. */
  1261. EditorUi.prototype.anonymizeNode = function(node, zeros)
  1262. {
  1263. var nodes = node.getElementsByTagName('mxCell');
  1264. for (var i = 0; i < nodes.length; i++)
  1265. {
  1266. if (nodes[i].getAttribute('value') != null)
  1267. {
  1268. nodes[i].setAttribute('value', '[' + nodes[i].getAttribute('value').length + ']');
  1269. }
  1270. if (nodes[i].getAttribute('xmlValue') != null)
  1271. {
  1272. nodes[i].setAttribute('xmlValue', '[' + nodes[i].getAttribute('xmlValue').length + ']');
  1273. }
  1274. if (nodes[i].getAttribute('style') != null)
  1275. {
  1276. nodes[i].setAttribute('style', '[' + nodes[i].getAttribute('style').length + ']');
  1277. }
  1278. if (nodes[i].parentNode != null && nodes[i].parentNode.nodeName != 'root' &&
  1279. nodes[i].parentNode.parentNode != null)
  1280. {
  1281. nodes[i].setAttribute('id', nodes[i].parentNode.getAttribute('id'));
  1282. nodes[i].parentNode.parentNode.replaceChild(nodes[i], nodes[i].parentNode);
  1283. }
  1284. }
  1285. return node;
  1286. };
  1287. /**
  1288. * Translates this point by the given vector.
  1289. *
  1290. * @param {number} dx X-coordinate of the translation.
  1291. * @param {number} dy Y-coordinate of the translation.
  1292. */
  1293. EditorUi.prototype.synchronizeCurrentFile = function(forceReload)
  1294. {
  1295. var currentFile = this.getCurrentFile();
  1296. if (currentFile != null)
  1297. {
  1298. if (currentFile.savingFile)
  1299. {
  1300. this.handleError({message: mxResources.get('busy')});
  1301. }
  1302. else if (!forceReload && currentFile.invalidChecksum)
  1303. {
  1304. currentFile.handleFileError(null, true);
  1305. }
  1306. else if (this.spinner.spin(document.body, mxResources.get('updatingDocument')))
  1307. {
  1308. currentFile.clearAutosave();
  1309. this.editor.setStatus('');
  1310. if (forceReload)
  1311. {
  1312. currentFile.reloadFile(mxUtils.bind(this, function()
  1313. {
  1314. currentFile.handleFileSuccess(DrawioFile.SYNC == 'manual');
  1315. }), mxUtils.bind(this, function(err)
  1316. {
  1317. currentFile.handleFileError(err, true);
  1318. }));
  1319. }
  1320. else
  1321. {
  1322. currentFile.synchronizeFile(mxUtils.bind(this, function()
  1323. {
  1324. currentFile.handleFileSuccess(DrawioFile.SYNC == 'manual');
  1325. }), mxUtils.bind(this, function(err)
  1326. {
  1327. currentFile.handleFileError(err, true);
  1328. }));
  1329. }
  1330. }
  1331. }
  1332. };
  1333. /**
  1334. * Translates this point by the given vector.
  1335. *
  1336. * @param {number} dx X-coordinate of the translation.
  1337. * @param {number} dy Y-coordinate of the translation.
  1338. */
  1339. EditorUi.prototype.getFileData = function(forceXml, forceSvg, forceHtml, embeddedCallback, ignoreSelection,
  1340. currentPage, node, compact, file, uncompressed)
  1341. {
  1342. ignoreSelection = (ignoreSelection != null) ? ignoreSelection : true;
  1343. currentPage = (currentPage != null) ? currentPage : false;
  1344. var graph = this.editor.graph;
  1345. // Forces compression of embedded XML
  1346. if (forceSvg || (!forceXml && file != null && /(\.svg)$/i.test(file.getTitle())))
  1347. {
  1348. uncompressed = false;
  1349. var darkTheme = graph.themes != null && graph.defaultThemeName == 'darkTheme';
  1350. // Exports SVG for first page while other page is visible by creating a graph
  1351. // LATER: Add caching for the graph or SVG while not on first page
  1352. // Dark mode requires a refresh that would destroy all handlers
  1353. // LATER: Use dark theme here to bypass refresh
  1354. if (darkTheme || (this.pages != null && this.currentPage != this.pages[0]))
  1355. {
  1356. var graphGetGlobalVariable = graph.getGlobalVariable;
  1357. graph = this.createTemporaryGraph(graph.getStylesheet());
  1358. var page = this.pages[0];
  1359. graph.getGlobalVariable = function(name)
  1360. {
  1361. if (name == 'page')
  1362. {
  1363. return page.getName();
  1364. }
  1365. else if (name == 'pagenumber')
  1366. {
  1367. return 1;
  1368. }
  1369. return graphGetGlobalVariable.apply(this, arguments);
  1370. };
  1371. document.body.appendChild(graph.container);
  1372. graph.model.setRoot(page.root);
  1373. }
  1374. }
  1375. node = (node != null) ? node : this.getXmlFileData(ignoreSelection, currentPage, uncompressed);
  1376. file = (file != null) ? file : this.getCurrentFile();
  1377. var result = this.createFileData(node, graph, file, window.location.href,
  1378. forceXml, forceSvg, forceHtml, embeddedCallback, ignoreSelection, compact,
  1379. uncompressed);
  1380. // Removes temporary graph from DOM
  1381. if (graph != this.editor.graph)
  1382. {
  1383. graph.container.parentNode.removeChild(graph.container);
  1384. }
  1385. return result;
  1386. };
  1387. /**
  1388. *
  1389. */
  1390. EditorUi.prototype.getHtml = function(node, graph, title, editLink, redirect, ignoreSelection)
  1391. {
  1392. ignoreSelection = (ignoreSelection != null) ? ignoreSelection : true;
  1393. var bg = null;
  1394. var js = EditorUi.drawHost + '/js/embed-static.min.js';
  1395. // LATER: Merge common code with EmbedDialog
  1396. if (graph != null)
  1397. {
  1398. var bounds = (ignoreSelection) ? graph.getGraphBounds() : graph.getBoundingBox(graph.getSelectionCells());
  1399. var scale = graph.view.scale;
  1400. var x0 = Math.floor(bounds.x / scale - graph.view.translate.x);
  1401. var y0 = Math.floor(bounds.y / scale - graph.view.translate.y);
  1402. bg = graph.background;
  1403. // Embed script only used if no redirect
  1404. if (redirect == null)
  1405. {
  1406. var s = this.getBasenames().join(';');
  1407. if (s.length > 0)
  1408. {
  1409. js = EditorUi.drawHost + '/embed.js?s=' + s;
  1410. }
  1411. }
  1412. // Adds embed attributes
  1413. node.setAttribute('x0', x0);
  1414. node.setAttribute('y0', y0);
  1415. }
  1416. if (node != null)
  1417. {
  1418. node.setAttribute('pan', '1');
  1419. node.setAttribute('zoom', '1');
  1420. node.setAttribute('resize', '0');
  1421. node.setAttribute('fit', '0');
  1422. node.setAttribute('border', '20');
  1423. // Hidden attributes
  1424. node.setAttribute('links', '1');
  1425. if (editLink != null)
  1426. {
  1427. node.setAttribute('edit', editLink);
  1428. }
  1429. }
  1430. // Makes XHTML compatible
  1431. if (redirect != null)
  1432. {
  1433. redirect = redirect.replace(/&/g, '&amp;');
  1434. }
  1435. // Removes control chars in input for correct roundtrip check
  1436. var text = (node != null) ? Graph.zapGremlins(mxUtils.getXml(node)) : '';
  1437. // Double compression for mxfile not fixed since it may cause imcompatibilites with
  1438. // embed clients that rely on this format. HTML files and export use getHtml2.
  1439. var data = Graph.compress(text);
  1440. // Fallback to URI encoded XML for invalid compression
  1441. if (Graph.decompress(data) != text)
  1442. {
  1443. data = encodeURIComponent(text);
  1444. }
  1445. var style = 'position:relative;overflow:auto;width:100%;';
  1446. return ((redirect == null) ? '<!--[if IE]><meta http-equiv="X-UA-Compatible" content="IE=5,IE=9" ><![endif]-->\n' : '') +
  1447. '<!DOCTYPE html>\n<html' + ((redirect != null) ? ' xmlns="http://www.w3.org/1999/xhtml">' : '>') +
  1448. '\n<head>\n' + ((redirect == null) ? ((title != null) ? '<title>' + mxUtils.htmlEntities(title) +
  1449. '</title>\n' : '') : '<title>diagrams.net</title>\n') +
  1450. ((redirect != null) ? '<meta http-equiv="refresh" content="0;URL=\'' + redirect + '\'"/>\n' : '') +
  1451. '</head>\n<body' +
  1452. (((redirect == null && bg != null && bg != mxConstants.NONE) ? ' style="background-color:' + bg + ';">' : '>')) +
  1453. '\n<div class="mxgraph" style="' + style + '">\n' +
  1454. '<div style="width:1px;height:1px;overflow:hidden;">' + data + '</div>\n</div>\n' +
  1455. ((redirect == null) ? '<script type="text/javascript" src="' + js + '"></script>' :
  1456. '<a style="position:absolute;top:50%;left:50%;margin-top:-128px;margin-left:-64px;" ' +
  1457. 'href="' + redirect + '" target="_blank"><img border="0" ' +
  1458. 'src="' + EditorUi.drawHost + '/images/drawlogo128.png"/></a>') +
  1459. '\n</body>\n</html>\n';
  1460. };
  1461. /**
  1462. * Same as above but using the new embed code.
  1463. */
  1464. EditorUi.prototype.getHtml2 = function(xml, graph, title, editLink, redirect)
  1465. {
  1466. var js = window.DRAWIO_VIEWER_URL || EditorUi.drawHost + '/js/viewer-static.min.js';
  1467. // Makes XHTML compatible
  1468. if (redirect != null)
  1469. {
  1470. redirect = redirect.replace(/&/g, '&amp;');
  1471. }
  1472. var data = {highlight: '#0000ff', nav: this.editor.graph.foldingEnabled, resize: true,
  1473. xml: Graph.zapGremlins(xml), toolbar: 'pages zoom layers lightbox'};
  1474. if (this.pages != null && this.currentPage != null)
  1475. {
  1476. data.page = mxUtils.indexOf(this.pages, this.currentPage);
  1477. }
  1478. var style = 'max-width:100%;border:1px solid transparent;';
  1479. return ((redirect == null) ? '<!--[if IE]><meta http-equiv="X-UA-Compatible" content="IE=5,IE=9" ><![endif]-->\n' : '') +
  1480. '<!DOCTYPE html>\n<html' + ((redirect != null) ? ' xmlns="http://www.w3.org/1999/xhtml">' : '>') +
  1481. '\n<head>\n' + ((redirect == null) ? ((title != null) ? '<title>' + mxUtils.htmlEntities(title) +
  1482. '</title>\n' : '') : '<title>diagrams.net</title>\n') +
  1483. ((redirect != null) ? '<meta http-equiv="refresh" content="0;URL=\'' + redirect + '\'"/>\n' : '') +
  1484. '<meta charset="utf-8"/>\n</head>\n<body>' +
  1485. '\n<div class="mxgraph" style="' + style + '" data-mxgraph="' + mxUtils.htmlEntities(JSON.stringify(data)) + '"></div>\n' +
  1486. ((redirect == null) ? '<script type="text/javascript" src="' + js + '"></script>' :
  1487. '<a style="position:absolute;top:50%;left:50%;margin-top:-128px;margin-left:-64px;" ' +
  1488. 'href="' + redirect + '" target="_blank"><img border="0" ' +
  1489. 'src="' + EditorUi.drawHost + '/images/drawlogo128.png"/></a>') +
  1490. '\n</body>\n</html>\n';
  1491. };
  1492. /**
  1493. *
  1494. */
  1495. EditorUi.prototype.setFileData = function(data)
  1496. {
  1497. data = this.validateFileData(data);
  1498. this.currentPage = null;
  1499. this.fileNode = null;
  1500. this.pages = null;
  1501. var node = (data != null && data.length > 0) ? mxUtils.parseXml(data).documentElement : null;
  1502. // Checks for parser errors
  1503. var cause = Editor.extractParserError(node, mxResources.get('invalidOrMissingFile'));
  1504. if (cause)
  1505. {
  1506. throw new Error(mxResources.get('notADiagramFile') + ' (' + cause + ')');
  1507. }
  1508. else
  1509. {
  1510. // Some nodes must be extracted here to find the mxfile node
  1511. // LATER: Remove duplicate call to extractGraphModel in overridden setGraphXml
  1512. var tmp = (node != null) ? this.editor.extractGraphModel(node, true) : null;
  1513. if (tmp != null)
  1514. {
  1515. node = tmp;
  1516. }
  1517. if (node != null && node.nodeName == 'mxfile')
  1518. {
  1519. var nodes = node.getElementsByTagName('diagram');
  1520. if (urlParams['pages'] != '0' || nodes.length > 1 ||
  1521. (nodes.length == 1 && nodes[0].hasAttribute('name')))
  1522. {
  1523. var selectedPage = null;
  1524. this.fileNode = node;
  1525. this.pages = [];
  1526. // Wraps page nodes
  1527. for (var i = 0; i < nodes.length; i++)
  1528. {
  1529. // Adds page ID based on page order to match
  1530. // remote IDs given if IDs are missing here
  1531. if (nodes[i].getAttribute('id') == null)
  1532. {
  1533. nodes[i].setAttribute('id', i);
  1534. }
  1535. var page = new DiagramPage(nodes[i]);
  1536. // Checks for invalid page names
  1537. if (page.getName() == null)
  1538. {
  1539. page.setName(mxResources.get('pageWithNumber', [i + 1]));
  1540. }
  1541. this.pages.push(page);
  1542. if (urlParams['page-id'] != null && page.getId() == urlParams['page-id'])
  1543. {
  1544. selectedPage = page;
  1545. }
  1546. }
  1547. this.currentPage = (selectedPage != null) ? selectedPage :
  1548. this.pages[Math.max(0, Math.min(this.pages.length - 1, urlParams['page'] || 0))];
  1549. node = this.currentPage.node;
  1550. }
  1551. }
  1552. // Creates tabbed file structure if enforced by URL
  1553. if (urlParams['pages'] != '0' && this.fileNode == null && node != null)
  1554. {
  1555. this.fileNode = node.ownerDocument.createElement('mxfile');
  1556. this.currentPage = new DiagramPage(node.ownerDocument.createElement('diagram'));
  1557. this.currentPage.setName(mxResources.get('pageWithNumber', [1]));
  1558. this.pages = [this.currentPage];
  1559. }
  1560. // Avoids scroll offset when switching page
  1561. this.editor.setGraphXml(node);
  1562. // Avoids duplicate parsing of the XML stored in the node
  1563. if (this.currentPage != null)
  1564. {
  1565. this.currentPage.root = this.editor.graph.model.root;
  1566. }
  1567. if (urlParams['layer-ids'] != null)
  1568. {
  1569. try
  1570. {
  1571. var layerIds = urlParams['layer-ids'].split(' ');
  1572. var layerIdsMap = {};
  1573. for (var i = 0; i < layerIds.length; i++)
  1574. {
  1575. layerIdsMap[layerIds[i]] = true;
  1576. }
  1577. var model = this.editor.graph.getModel();
  1578. var children = model.getChildren(model.root);
  1579. // handle layers visibility
  1580. for (var i = 0; i < children.length; i++)
  1581. {
  1582. var child = children[i];
  1583. model.setVisible(child, layerIdsMap[child.id] || false);
  1584. }
  1585. }
  1586. catch(e){} //ignore
  1587. }
  1588. }
  1589. };
  1590. /**
  1591. * Translates this point by the given vector.
  1592. *
  1593. * @param {number} dx X-coordinate of the translation.
  1594. * @param {number} dy Y-coordinate of the translation.
  1595. */
  1596. EditorUi.prototype.getBaseFilename = function(ignorePageName)
  1597. {
  1598. var file = this.getCurrentFile();
  1599. var basename = (file != null && file.getTitle() != null) ? file.getTitle() : this.defaultFilename;
  1600. if (/(\.xml)$/i.test(basename) || /(\.html)$/i.test(basename) ||
  1601. /(\.svg)$/i.test(basename) || /(\.png)$/i.test(basename) ||
  1602. /(\.drawio)$/i.test(basename))
  1603. {
  1604. basename = basename.substring(0, basename.lastIndexOf('.'));
  1605. }
  1606. if (!ignorePageName && this.pages != null && this.pages.length > 1 &&
  1607. this.currentPage != null && this.currentPage.node.getAttribute('name') != null &&
  1608. this.currentPage.getName().length > 0)
  1609. {
  1610. basename = basename + '-' + this.currentPage.getName();
  1611. }
  1612. return basename;
  1613. };
  1614. /**
  1615. * Translates this point by the given vector.
  1616. *
  1617. * @param {number} dx X-coordinate of the translation.
  1618. * @param {number} dy Y-coordinate of the translation.
  1619. */
  1620. EditorUi.prototype.downloadFile = function(format, uncompressed, addShadow, ignoreSelection, currentPage,
  1621. pageVisible, transparent, scale, border, grid, includeXml)
  1622. {
  1623. try
  1624. {
  1625. ignoreSelection = (ignoreSelection != null) ? ignoreSelection : this.editor.graph.isSelectionEmpty();
  1626. var basename = this.getBaseFilename(!currentPage);
  1627. var filename = basename + '.' + format;
  1628. if (format == 'xml')
  1629. {
  1630. var data = '<?xml version="1.0" encoding="UTF-8"?>\n' +
  1631. this.getFileData(true, null, null, null, ignoreSelection, currentPage,
  1632. null, null, null, uncompressed);
  1633. this.saveData(filename, format, data, 'text/xml');
  1634. }
  1635. else if (format == 'html')
  1636. {
  1637. var data = this.getHtml2(this.getFileData(true), this.editor.graph, basename);
  1638. this.saveData(filename, format, data, 'text/html');
  1639. }
  1640. else if ((format == 'svg' || format == 'xmlsvg') && this.spinner.spin(document.body, mxResources.get('export')))
  1641. {
  1642. var svg = null;
  1643. var saveSvg = mxUtils.bind(this, function(data)
  1644. {
  1645. if (data.length <= MAX_REQUEST_SIZE)
  1646. {
  1647. this.saveData(filename, 'svg', data, 'image/svg+xml');
  1648. }
  1649. else
  1650. {
  1651. this.handleError({message: mxResources.get('drawingTooLarge')}, mxResources.get('error'), mxUtils.bind(this, function()
  1652. {
  1653. mxUtils.popup(svg);
  1654. }));
  1655. }
  1656. });
  1657. if (format == 'svg')
  1658. {
  1659. var bg = this.editor.graph.background;
  1660. if (transparent || bg == mxConstants.NONE)
  1661. {
  1662. bg = null;
  1663. }
  1664. // Sets or disables alternate text for foreignObjects. Disabling is needed
  1665. // because PhantomJS seems to ignore switch statements and paint all text.
  1666. var svgRoot = this.editor.graph.getSvg(bg, null, null, null, null, ignoreSelection);
  1667. if (addShadow)
  1668. {
  1669. this.editor.graph.addSvgShadow(svgRoot);
  1670. }
  1671. // Embeds the images in the SVG output (async)
  1672. this.editor.convertImages(svgRoot, mxUtils.bind(this, mxUtils.bind(this, function(svgRoot2)
  1673. {
  1674. this.spinner.stop();
  1675. saveSvg('<?xml version="1.0" encoding="UTF-8"?>\n' +
  1676. '<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">\n' +
  1677. mxUtils.getXml(svgRoot2));
  1678. })));
  1679. }
  1680. else
  1681. {
  1682. filename = basename + '.svg';
  1683. svg = this.getFileData(false, true, null, mxUtils.bind(this, function(svg)
  1684. {
  1685. this.spinner.stop();
  1686. saveSvg(svg);
  1687. }), ignoreSelection);
  1688. }
  1689. }
  1690. else
  1691. {
  1692. if (format == 'xmlpng')
  1693. {
  1694. filename = basename + '.png';
  1695. }
  1696. else if (format == 'jpeg')
  1697. {
  1698. filename = basename + '.jpg';
  1699. }
  1700. this.saveRequest(filename, format, mxUtils.bind(this, function(newTitle, base64)
  1701. {
  1702. try
  1703. {
  1704. var prev = this.editor.graph.pageVisible;
  1705. if (pageVisible != null)
  1706. {
  1707. this.editor.graph.pageVisible = pageVisible;
  1708. }
  1709. var req = this.createDownloadRequest(newTitle, format, ignoreSelection, base64,
  1710. transparent, currentPage, scale, border, grid, includeXml);
  1711. this.editor.graph.pageVisible = prev;
  1712. return req;
  1713. }
  1714. catch (e)
  1715. {
  1716. this.handleError(e);
  1717. }
  1718. }));
  1719. }
  1720. }
  1721. catch (e)
  1722. {
  1723. this.handleError(e);
  1724. }
  1725. };
  1726. /**
  1727. * Translates this point by the given vector.
  1728. *
  1729. * @param {number} dx X-coordinate of the translation.
  1730. * @param {number} dy Y-coordinate of the translation.
  1731. */
  1732. EditorUi.prototype.createDownloadRequest = function(filename, format, ignoreSelection, base64, transparent,
  1733. currentPage, scale, border, grid, includeXml)
  1734. {
  1735. var graph = this.editor.graph;
  1736. var bounds = graph.getGraphBounds();
  1737. // Exports only current page for images that does not contain file data, but for
  1738. // the other formats with XML included or pdf with all pages, we need to send the complete data and use
  1739. // the from/to URL parameters to specify the page to be exported.
  1740. var data = this.getFileData(true, null, null, null, ignoreSelection, currentPage == false? false : format != 'xmlpng');
  1741. var range = '';
  1742. var allPages = '';
  1743. if (bounds.width * bounds.height > MAX_AREA || data.length > MAX_REQUEST_SIZE)
  1744. {
  1745. throw {message: mxResources.get('drawingTooLarge')};
  1746. }
  1747. var embed = (includeXml) ? '1' : '0';
  1748. if (format == 'pdf' && currentPage == false)
  1749. {
  1750. allPages = '&allPages=1';
  1751. }
  1752. if (format == 'xmlpng')
  1753. {
  1754. embed = '1';
  1755. format = 'png';
  1756. // Finds the current page number
  1757. if (this.pages != null && this.currentPage != null)
  1758. {
  1759. for (var i = 0; i < this.pages.length; i++)
  1760. {
  1761. if (this.pages[i] == this.currentPage)
  1762. {
  1763. range = '&from=' + i;
  1764. break;
  1765. }
  1766. }
  1767. }
  1768. }
  1769. var bg = graph.background;
  1770. if ((format == 'png' || format == 'pdf') && transparent)
  1771. {
  1772. bg = mxConstants.NONE;
  1773. }
  1774. else if (!transparent && (bg == null || bg == mxConstants.NONE))
  1775. {
  1776. bg = '#ffffff';
  1777. }
  1778. var extras = {globalVars: graph.getExportVariables()};
  1779. if (grid)
  1780. {
  1781. extras.grid = {
  1782. size: graph.gridSize,
  1783. steps: graph.view.gridSteps,
  1784. color: graph.view.gridColor
  1785. };
  1786. }
  1787. if (Graph.translateDiagram)
  1788. {
  1789. extras.diagramLanguage = Graph.diagramLanguage;
  1790. }
  1791. return new mxXmlRequest(EXPORT_URL, 'format=' + format + range + allPages +
  1792. '&bg=' + ((bg != null) ? bg : mxConstants.NONE) +
  1793. '&base64=' + base64 + '&embedXml=' + embed + '&xml=' +
  1794. encodeURIComponent(data) + ((filename != null) ?
  1795. '&filename=' + encodeURIComponent(filename) : '') +
  1796. '&extras=' + encodeURIComponent(JSON.stringify(extras)) +
  1797. (scale != null? '&scale=' + scale : '') +
  1798. (border != null? '&border=' + border : ''));
  1799. };
  1800. /**
  1801. * Translates this point by the given vector.
  1802. *
  1803. * @param {number} dx X-coordinate of the translation.
  1804. * @param {number} dy Y-coordinate of the translation.
  1805. */
  1806. EditorUi.prototype.setMode = function(mode, remember)
  1807. {
  1808. this.mode = mode;
  1809. };
  1810. /**
  1811. * Loads the given file descriptor. The descriptor may define the following properties:
  1812. *
  1813. * - url: The url to load the data from (proxy is used if CORS is not enabled)
  1814. * - data: The data to be inserted. If both, data and url are defined, then the data
  1815. * is preprendended to the data returned from the given URL.
  1816. * - format: Currently, only 'csv' is supported as an optional value. Default is XML.
  1817. * - update: Optional URL to fetch updates from (POST request with the page XML).
  1818. * - interval: Optional interval for fetching updates. Default is 60000 (60 seconds).
  1819. */
  1820. EditorUi.prototype.loadDescriptor = function(desc, success, error)
  1821. {
  1822. var hash = window.location.hash;
  1823. var loadData = mxUtils.bind(this, function(data)
  1824. {
  1825. var realData = (desc.data != null) ? desc.data : '';
  1826. if (data != null && data.length > 0)
  1827. {
  1828. if (realData.length > 0)
  1829. {
  1830. realData += '\n';
  1831. }
  1832. realData += data;
  1833. }
  1834. var xml = (desc.format != 'csv' && realData.length > 0) ? realData : this.emptyDiagramXml;
  1835. var tempFile = new LocalFile(this, xml, (urlParams['title'] != null) ?
  1836. decodeURIComponent(urlParams['title']) : this.defaultFilename, true);
  1837. tempFile.getHash = function()
  1838. {
  1839. return hash;
  1840. };
  1841. this.fileLoaded(tempFile);
  1842. if (desc.format == 'csv')
  1843. {
  1844. this.importCsv(realData, mxUtils.bind(this, function(cells)
  1845. {
  1846. this.editor.undoManager.clear();
  1847. this.editor.setModified(false);
  1848. this.editor.setStatus('');
  1849. }));
  1850. }
  1851. // Installs updates
  1852. if (desc.update != null)
  1853. {
  1854. var interval = (desc.interval != null) ? parseInt(desc.interval) : 60000;
  1855. var currentThread = null;
  1856. var doUpdate = mxUtils.bind(this, function()
  1857. {
  1858. var page = this.currentPage;
  1859. mxUtils.post(desc.update, 'xml=' + encodeURIComponent(
  1860. mxUtils.getXml(this.editor.getGraphXml())),
  1861. mxUtils.bind(this, function(req)
  1862. {
  1863. if (page === this.currentPage)
  1864. {
  1865. if (req.getStatus() >= 200 && req.getStatus() <= 300)
  1866. {
  1867. var doc = this.updateDiagram(req.getText());
  1868. schedule();
  1869. }
  1870. else
  1871. {
  1872. this.handleError({message: mxResources.get('error') + ' ' + req.getStatus()});
  1873. }
  1874. }
  1875. }), mxUtils.bind(this, function(err)
  1876. {
  1877. this.handleError(err);
  1878. }));
  1879. });
  1880. var schedule = mxUtils.bind(this, function()
  1881. {
  1882. window.clearTimeout(currentThread);
  1883. currentThread = window.setTimeout(doUpdate, interval);
  1884. });
  1885. this.editor.addListener('pageSelected', mxUtils.bind(this, function()
  1886. {
  1887. schedule();
  1888. doUpdate();
  1889. }));
  1890. schedule();
  1891. doUpdate();
  1892. }
  1893. if (success != null)
  1894. {
  1895. success();
  1896. }
  1897. });
  1898. if (desc.url != null && desc.url.length > 0)
  1899. {
  1900. // Cannot use proxy here as it will block unknown text content
  1901. // LATER: Remove cache-control header
  1902. this.editor.loadUrl(desc.url, mxUtils.bind(this, function(data)
  1903. {
  1904. loadData(data);
  1905. }), mxUtils.bind(this, function(err)
  1906. {
  1907. if (error != null)
  1908. {
  1909. error(err)
  1910. }
  1911. }));
  1912. }
  1913. else
  1914. {
  1915. loadData('');
  1916. }
  1917. };
  1918. /**
  1919. * Translates this point by the given vector.
  1920. *
  1921. * @param {number} dx X-coordinate of the translation.
  1922. * @param {number} dy Y-coordinate of the translation.
  1923. */
  1924. EditorUi.prototype.updateDiagram = function(xml)
  1925. {
  1926. var doc = null;
  1927. var ui = this;
  1928. function createOverlay(desc)
  1929. {
  1930. var overlay = new mxCellOverlay(desc.image || graph.warningImage,
  1931. desc.tooltip, desc.align, desc.valign, desc.offset);
  1932. // Installs a handler for clicks on the overlay
  1933. overlay.addListener(mxEvent.CLICK, function(sender, evt)
  1934. {
  1935. ui.alert(desc.tooltip);
  1936. });
  1937. return overlay;
  1938. };
  1939. if (xml != null && xml.length > 0)
  1940. {
  1941. doc = mxUtils.parseXml(xml);
  1942. var node = (doc != null) ? doc.documentElement : null;
  1943. if (node != null && node.nodeName == 'updates')
  1944. {
  1945. var graph = this.editor.graph;
  1946. var model = graph.getModel();
  1947. model.beginUpdate();
  1948. var fit = null;
  1949. try
  1950. {
  1951. node = node.firstChild;
  1952. while (node != null)
  1953. {
  1954. if (node.nodeName == 'update')
  1955. {
  1956. // Resolves the cell ID
  1957. var cell = model.getCell(node.getAttribute('id'));
  1958. if (cell != null)
  1959. {
  1960. // Changes the value
  1961. try
  1962. {
  1963. var value = node.getAttribute('value');
  1964. if (value != null)
  1965. {
  1966. var valueNode = mxUtils.parseXml(value).documentElement;
  1967. if (valueNode != null)
  1968. {
  1969. if (valueNode.getAttribute('replace-value') == '1')
  1970. {
  1971. model.setValue(cell, valueNode);
  1972. }
  1973. else
  1974. {
  1975. var attrs = valueNode.attributes;
  1976. for (var j = 0; j < attrs.length; j++)
  1977. {
  1978. graph.setAttributeForCell(cell, attrs[j].nodeName,
  1979. (attrs[j].nodeValue.length > 0) ? attrs[j].nodeValue : null);
  1980. }
  1981. }
  1982. }
  1983. }
  1984. }
  1985. catch (e)
  1986. {
  1987. if (window.console != null)
  1988. {
  1989. console.log('Error in value for ' + cell.id + ': ' + e);
  1990. }
  1991. }
  1992. // Changes the style
  1993. try
  1994. {
  1995. var style = node.getAttribute('style');
  1996. if (style != null)
  1997. {
  1998. graph.model.setStyle(cell, style);
  1999. }
  2000. }
  2001. catch (e)
  2002. {
  2003. if (window.console != null)
  2004. {
  2005. console.log('Error in style for ' + cell.id + ': ' + e);
  2006. }
  2007. }
  2008. // Adds or removes an overlay icon
  2009. try
  2010. {
  2011. var icon = node.getAttribute('icon');
  2012. if (icon != null)
  2013. {
  2014. var desc = (icon.length > 0) ? JSON.parse(icon) : null;
  2015. if (desc == null || !desc.append)
  2016. {
  2017. graph.removeCellOverlays(cell);
  2018. }
  2019. if (desc != null)
  2020. {
  2021. graph.addCellOverlay(cell, createOverlay(desc));
  2022. }
  2023. }
  2024. }
  2025. catch (e)
  2026. {
  2027. if (window.console != null)
  2028. {
  2029. console.log('Error in icon for ' + cell.id + ': ' + e);
  2030. }
  2031. }
  2032. // Replaces the geometry
  2033. try
  2034. {
  2035. var geo = node.getAttribute('geometry');
  2036. if (geo != null)
  2037. {
  2038. geo = JSON.parse(geo);
  2039. var curr = graph.getCellGeometry(cell);
  2040. if (curr != null)
  2041. {
  2042. curr = curr.clone();
  2043. // Partially overwrites geometry
  2044. for (key in geo)
  2045. {
  2046. var val = parseFloat(geo[key]);
  2047. if (key == 'dx')
  2048. {
  2049. curr.x += val;
  2050. }
  2051. else if (key == 'dy')
  2052. {
  2053. curr.y += val;
  2054. }
  2055. else if (key == 'dw')
  2056. {
  2057. curr.width += val;
  2058. }
  2059. else if (key == 'dh')
  2060. {
  2061. curr.height += val;
  2062. }
  2063. else
  2064. {
  2065. curr[key] = parseFloat(geo[key]);
  2066. }
  2067. }
  2068. graph.model.setGeometry(cell, curr);
  2069. }
  2070. }
  2071. }
  2072. catch (e)
  2073. {
  2074. if (window.console != null)
  2075. {
  2076. console.log('Error in icon for ' + cell.id + ': ' + e);
  2077. }
  2078. }
  2079. } // if cell != null
  2080. } // if node.nodeName == 'update
  2081. else if (node.nodeName == 'model')
  2082. {
  2083. // Finds first child element
  2084. var dataNode = node.firstChild;
  2085. while (dataNode != null && dataNode.nodeType != mxConstants.NODETYPE_ELEMENT)
  2086. {
  2087. dataNode = dataNode.nextSibling;
  2088. }
  2089. if (dataNode != null)
  2090. {
  2091. var dec = new mxCodec(node.firstChild);
  2092. dec.decode(dataNode, model);
  2093. }
  2094. }
  2095. else if (node.nodeName == 'view')
  2096. {
  2097. if (node.hasAttribute('scale'))
  2098. {
  2099. graph.view.scale = parseFloat(node.getAttribute('scale'));
  2100. }
  2101. if (node.hasAttribute('dx') || node.hasAttribute('dy'))
  2102. {
  2103. graph.view.translate = new mxPoint(parseFloat(node.getAttribute('dx') || 0),
  2104. parseFloat(node.getAttribute('dy') || 0));
  2105. }
  2106. }
  2107. else if (node.nodeName == 'fit')
  2108. {
  2109. if (node.hasAttribute('max-scale'))
  2110. {
  2111. fit = parseFloat(node.getAttribute('max-scale'));
  2112. }
  2113. else
  2114. {
  2115. fit = 1;
  2116. }
  2117. }
  2118. node = node.nextSibling;
  2119. } // end of while
  2120. }
  2121. finally
  2122. {
  2123. model.endUpdate();
  2124. }
  2125. if (fit != null && this.chromelessResize)
  2126. {
  2127. this.chromelessResize(true, fit);
  2128. }
  2129. }
  2130. }
  2131. return doc;
  2132. };
  2133. /**
  2134. * Constructs a filename for a copy of the given file.
  2135. */
  2136. EditorUi.prototype.getCopyFilename = function(file, timestamp)
  2137. {
  2138. var title = (file != null && file.getTitle() != null) ?
  2139. file.getTitle() : this.defaultFilename;
  2140. // Handles extension
  2141. var extension = '';
  2142. var dot = title.lastIndexOf('.');
  2143. if (dot >= 0)
  2144. {
  2145. extension = title.substring(dot);
  2146. title = title.substring(0, dot);
  2147. }
  2148. if (timestamp)
  2149. {
  2150. function getFormattedTime()
  2151. {
  2152. var today = new Date();
  2153. var y = today.getFullYear();
  2154. // JavaScript months are 0-based.
  2155. var m = today.getMonth() + 1;
  2156. var d = today.getDate();
  2157. var h = today.getHours();
  2158. var mi = today.getMinutes();
  2159. var s = today.getSeconds();
  2160. return y + "-" + m + "-" + d + "-" + h + "-" + mi + "-" + s;
  2161. }
  2162. var ts = new Date();
  2163. title += ' ' + getFormattedTime();
  2164. }
  2165. title = mxResources.get('copyOf', [title]) + extension;
  2166. return title;
  2167. };
  2168. /**
  2169. * Translates this point by the given vector.
  2170. *
  2171. * @param {number} dx X-coordinate of the translation.
  2172. * @param {number} dy Y-coordinate of the translation.
  2173. */
  2174. EditorUi.prototype.fileLoaded = function(file, noDialogs)
  2175. {
  2176. var oldFile = this.getCurrentFile();
  2177. this.fileLoadedError = null;
  2178. this.fileEditable = null;
  2179. this.setCurrentFile(null);
  2180. var result = false;
  2181. this.hideDialog();
  2182. if (oldFile != null)
  2183. {
  2184. EditorUi.debug('File.closed', [oldFile]);
  2185. oldFile.removeListener(this.descriptorChangedListener);
  2186. oldFile.close();
  2187. }
  2188. this.editor.graph.model.clear();
  2189. this.editor.undoManager.clear();
  2190. var noFile = mxUtils.bind(this, function()
  2191. {
  2192. this.setGraphEnabled(false);
  2193. this.setCurrentFile(null);
  2194. // Keeps initial title if no file existed before
  2195. if (oldFile != null)
  2196. {
  2197. this.updateDocumentTitle();
  2198. }
  2199. // File might have been loaded halfway
  2200. this.editor.graph.model.clear();
  2201. this.editor.undoManager.clear();
  2202. this.setBackgroundImage(null);
  2203. // Avoids empty hash with no value
  2204. if (!noDialogs && window.location.hash != null && window.location.hash.length > 0)
  2205. {
  2206. window.location.hash = '';
  2207. }
  2208. if (this.fname != null)
  2209. {
  2210. this.fnameWrapper.style.display = 'none';
  2211. this.fname.innerHTML = '';
  2212. this.fname.setAttribute('title', mxResources.get('rename'));
  2213. }
  2214. this.editor.setStatus('');
  2215. this.updateUi();
  2216. if (!noDialogs)
  2217. {
  2218. this.showSplash();
  2219. }
  2220. });
  2221. if (file != null)
  2222. {
  2223. try
  2224. {
  2225. // Workaround for delayed scroll repaint with min UI in Safari
  2226. if (mxClient.IS_SF && uiTheme == 'min')
  2227. {
  2228. this.diagramContainer.style.visibility = '';
  2229. }
  2230. // Order is significant, current file needed for correct
  2231. // file format for initial save after starting realtime
  2232. this.openingFile = true;
  2233. this.setCurrentFile(file);
  2234. file.addListener('descriptorChanged', this.descriptorChangedListener);
  2235. file.addListener('contentChanged', this.descriptorChangedListener);
  2236. file.open();
  2237. delete this.openingFile;
  2238. // DescriptorChanged updates the enabled state of the graph
  2239. this.setGraphEnabled(true);
  2240. this.setMode(file.getMode());
  2241. this.editor.graph.model.prefix = Editor.guid() + '-';
  2242. this.editor.undoManager.clear();
  2243. this.descriptorChanged();
  2244. this.updateUi();
  2245. // Realtime files have a valid status message
  2246. if (!file.isEditable())
  2247. {
  2248. this.editor.setStatus('<span class="geStatusAlert" style="margin-left:8px;">' +
  2249. mxUtils.htmlEntities(mxResources.get('readOnly')) + '</span>');
  2250. }
  2251. // Handles modified state after error of loading new file
  2252. else if (file.isModified())
  2253. {
  2254. file.addUnsavedStatus();
  2255. // Restores unsaved data
  2256. if (file.backupPatch != null)
  2257. {
  2258. file.patch([file.backupPatch]);
  2259. }
  2260. }
  2261. else
  2262. {
  2263. this.editor.setStatus('');
  2264. }
  2265. if (!this.editor.isChromelessView() || this.editor.editable)
  2266. {
  2267. this.editor.graph.selectUnlockedLayer();
  2268. this.showLayersDialog();
  2269. this.restoreLibraries();
  2270. // Workaround for no initial focus in FF
  2271. if (window.self !== window.top)
  2272. {
  2273. window.focus();
  2274. }
  2275. }
  2276. else if (this.editor.graph.isLightboxView())
  2277. {
  2278. this.lightboxFit();
  2279. }
  2280. if (this.chromelessResize)
  2281. {
  2282. this.chromelessResize();
  2283. }
  2284. this.editor.fireEvent(new mxEventObject('fileLoaded'));
  2285. result = true;
  2286. if (!this.isOffline() && file.getMode() != null)
  2287. {
  2288. EditorUi.logEvent({category: file.getMode().toUpperCase() + '-OPEN-FILE-' + file.getHash(),
  2289. action: 'size_' + file.getSize(),
  2290. label: 'autosave_' + ((this.editor.autosave) ? 'on' : 'off')});
  2291. }
  2292. EditorUi.debug('File.opened', [file]);
  2293. if (this.editor.editable && this.mode == file.getMode() &&
  2294. file.getMode() != App.MODE_DEVICE && file.getMode() != null)
  2295. {
  2296. try
  2297. {
  2298. this.addRecent({id: file.getHash(), title: file.getTitle(), mode: file.getMode()});
  2299. }
  2300. catch (e)
  2301. {
  2302. // ignore
  2303. }
  2304. }
  2305. try
  2306. {
  2307. mxSettings.setOpenCounter(mxSettings.getOpenCounter() + 1);
  2308. mxSettings.save();
  2309. }
  2310. catch (e)
  2311. {
  2312. // ignore
  2313. }
  2314. }
  2315. catch (e)
  2316. {
  2317. this.fileLoadedError = e;
  2318. if (EditorUi.enableLogging && !this.isOffline())
  2319. {
  2320. try
  2321. {
  2322. EditorUi.logEvent({category: 'ERROR-LOAD-FILE-' +
  2323. ((file != null) ? file.getHash() : 'none'),
  2324. action: 'message_' + e.message,
  2325. label: 'stack_' + e.stack});
  2326. }
  2327. catch (e)
  2328. {
  2329. // ignore
  2330. }
  2331. }
  2332. // Asynchronous handling of errors
  2333. var fn = mxUtils.bind(this, function()
  2334. {
  2335. // Removes URL parameter and reloads the page
  2336. if (urlParams['url'] != null && this.spinner.spin(document.body, mxResources.get('reconnecting')))
  2337. {
  2338. window.location.search = this.getSearch(['url']);
  2339. }
  2340. else if (oldFile != null)
  2341. {
  2342. this.fileLoaded(oldFile);
  2343. }
  2344. else
  2345. {
  2346. noFile();
  2347. }
  2348. });
  2349. if (!noDialogs)
  2350. {
  2351. this.handleError(e, mxResources.get('errorLoadingFile'), fn, true, null, null, true);
  2352. }
  2353. else
  2354. {
  2355. fn();
  2356. }
  2357. }
  2358. }
  2359. else
  2360. {
  2361. noFile();
  2362. }
  2363. return result;
  2364. };
  2365. /**
  2366. * Creates a hash value for the current file.
  2367. */
  2368. EditorUi.prototype.getHashValueForPages = function(pages, details)
  2369. {
  2370. // TODO: Avoid encoding to XML to make it faster
  2371. var hash = 0;
  2372. var model = new mxGraphModel();
  2373. var codec = new mxCodec();
  2374. if (details != null)
  2375. {
  2376. details.byteCount = 0;
  2377. details.attrCount = 0;
  2378. details.eltCount = 0;
  2379. details.nodeCount = 0;
  2380. }
  2381. for (var i = 0; i < pages.length; i++)
  2382. {
  2383. this.updatePageRoot(pages[i]);
  2384. var diagram = pages[i].node.cloneNode(false);
  2385. // FIXME: Check why names can be null in newer files
  2386. // ignore in hash and do not diff null names for now
  2387. diagram.removeAttribute('name');
  2388. // Model is only a holder for the root
  2389. model.root = pages[i].root;
  2390. var xmlNode = codec.encode(model);
  2391. this.editor.graph.saveViewState(pages[i].viewState, xmlNode, true);
  2392. // Local defaults may be different in files so ignore
  2393. xmlNode.removeAttribute('pageWidth');
  2394. xmlNode.removeAttribute('pageHeight');
  2395. diagram.appendChild(xmlNode);
  2396. if (details != null)
  2397. {
  2398. details.eltCount += diagram.getElementsByTagName('*').length;
  2399. details.nodeCount += diagram.getElementsByTagName('mxCell').length;
  2400. }
  2401. hash = ((hash << 5) - hash + this.hashValue(diagram, function(obj, key, value, isXml)
  2402. {
  2403. // Ignores JS machine rounding errors in known numeric attributes
  2404. // eg. 412.33333333333326 (Webkit/FF) == 412.33333333333325 (Edge/IE11)
  2405. if (isXml && (obj.nodeName == 'mxGeometry' || obj.nodeName == 'mxPoint') &&
  2406. (key == 'x' || key == 'y' || key == 'width' || key == 'height'))
  2407. {
  2408. return Math.round(value);
  2409. }
  2410. // Workaround for previous in patch written to mxCell in 10.0.23
  2411. else if (isXml && obj.nodeName == 'mxCell' && key == 'previous')
  2412. {
  2413. return null;
  2414. }
  2415. else
  2416. {
  2417. return value;
  2418. }
  2419. }, details)) << 0;
  2420. }
  2421. return hash;
  2422. };
  2423. /**
  2424. * Creates a hash value for the given object. Replacer returns the value of the
  2425. * property or attribute for the given object or XML node.
  2426. */
  2427. EditorUi.prototype.hashValue = function(obj, replacer, details)
  2428. {
  2429. var hash = 0;
  2430. // Checks for XML nodes
  2431. if (obj != null && typeof obj === 'object' && typeof obj.nodeType === 'number' &&
  2432. typeof obj.nodeName === 'string' && typeof obj.getAttribute === 'function')
  2433. {
  2434. if (obj.nodeName != null)
  2435. {
  2436. hash = hash ^ this.hashValue(obj.nodeName, replacer, details);
  2437. }
  2438. if (obj.attributes != null)
  2439. {
  2440. if (details != null)
  2441. {
  2442. details.attrCount += obj.attributes.length;
  2443. }
  2444. for (var i = 0; i < obj.attributes.length; i++)
  2445. {
  2446. var key = obj.attributes[i].name;
  2447. var value = (replacer != null) ? replacer(obj, key, obj.attributes[i].value, true) : obj.attributes[i].value;
  2448. if (value != null)
  2449. {
  2450. hash = hash ^ (this.hashValue(key, replacer, details) +
  2451. this.hashValue(value, replacer, details));
  2452. }
  2453. }
  2454. }
  2455. if (obj.childNodes != null)
  2456. {
  2457. for (var i = 0; i < obj.childNodes.length; i++)
  2458. {
  2459. hash = ((hash << 5) - hash + this.hashValue(
  2460. obj.childNodes[i], replacer, details)) << 0;
  2461. }
  2462. }
  2463. }
  2464. else if (obj != null && typeof obj !== 'function')
  2465. {
  2466. var str = String(obj);
  2467. var temp = 0;
  2468. if (details != null)
  2469. {
  2470. details.byteCount += str.length;
  2471. }
  2472. for (var i = 0; i < str.length; i++)
  2473. {
  2474. temp = ((temp << 5) - temp + str.charCodeAt(i)) << 0;
  2475. }
  2476. hash = hash ^ temp;
  2477. }
  2478. return hash;
  2479. };
  2480. /**
  2481. * Adds empty implementation
  2482. */
  2483. EditorUi.prototype.descriptorChanged = function()
  2484. {
  2485. // empty
  2486. };
  2487. /**
  2488. * Hook for subclassers.
  2489. */
  2490. EditorUi.prototype.restoreLibraries = function() { };
  2491. /**
  2492. * Hook for subclassers.
  2493. */
  2494. EditorUi.prototype.saveLibrary = function(name, images, file, mode, noSpin, noReload, fn) { };
  2495. /**
  2496. *
  2497. */
  2498. EditorUi.prototype.isScratchpadEnabled = function()
  2499. {
  2500. return isLocalStorage || mxClient.IS_CHROMEAPP;
  2501. };
  2502. /**
  2503. * Shows or hides the scratchpad library.
  2504. */
  2505. EditorUi.prototype.toggleScratchpad = function()
  2506. {
  2507. if (this.isScratchpadEnabled())
  2508. {
  2509. if (this.scratchpad == null)
  2510. {
  2511. StorageFile.getFileContent(this, '.scratchpad', mxUtils.bind(this, function(xml)
  2512. {
  2513. if (xml == null)
  2514. {
  2515. xml = this.emptyLibraryXml;
  2516. }
  2517. this.loadLibrary(new StorageLibrary(this, xml, '.scratchpad'));
  2518. }));
  2519. }
  2520. else
  2521. {
  2522. this.closeLibrary(this.scratchpad);
  2523. }
  2524. }
  2525. };
  2526. /**
  2527. * Translates this point by the given vector.
  2528. *
  2529. * @param {number} dx X-coordinate of the translation.
  2530. * @param {number} dy Y-coordinate of the translation.
  2531. */
  2532. EditorUi.prototype.createLibraryDataFromImages = function(images)
  2533. {
  2534. var doc = mxUtils.createXmlDocument();
  2535. var library = doc.createElement('mxlibrary');
  2536. mxUtils.setTextContent(library, JSON.stringify(images));
  2537. doc.appendChild(library);
  2538. return mxUtils.getXml(doc);
  2539. };
  2540. /**
  2541. * Translates this point by the given vector.
  2542. *
  2543. * @param {number} dx X-coordinate of the translation.
  2544. * @param {number} dy Y-coordinate of the translation.
  2545. */
  2546. EditorUi.prototype.closeLibrary = function(file)
  2547. {
  2548. if (file != null)
  2549. {
  2550. this.removeLibrarySidebar(file.getHash());
  2551. if (file.constructor != LocalLibrary)
  2552. {
  2553. mxSettings.removeCustomLibrary(file.getHash());
  2554. }
  2555. if (file.title == '.scratchpad')
  2556. {
  2557. this.scratchpad = null;
  2558. }
  2559. }
  2560. };
  2561. /**
  2562. * Translates this point by the given vector.
  2563. *
  2564. * @param {number} dx X-coordinate of the translation.
  2565. * @param {number} dy Y-coordinate of the translation.
  2566. */
  2567. EditorUi.prototype.removeLibrarySidebar = function(id)
  2568. {
  2569. var elts = this.sidebar.palettes[id];
  2570. if (elts != null)
  2571. {
  2572. for (var i = 0; i < elts.length; i++)
  2573. {
  2574. elts[i].parentNode.removeChild(elts[i]);
  2575. }
  2576. delete this.sidebar.palettes[id];
  2577. }
  2578. };
  2579. /**
  2580. * Changes the position of the library in the sidebar
  2581. */
  2582. EditorUi.prototype.repositionLibrary = function(nextChild)
  2583. {
  2584. var c = this.sidebar.container;
  2585. if (nextChild == null)
  2586. {
  2587. var elts = this.sidebar.palettes['L.scratchpad'];
  2588. if (elts == null)
  2589. {
  2590. elts = this.sidebar.palettes['search'];
  2591. }
  2592. if (elts != null)
  2593. {
  2594. nextChild = elts[elts.length - 1].nextSibling;
  2595. }
  2596. }
  2597. nextChild = (nextChild != null) ? nextChild : c.firstChild.nextSibling.nextSibling;
  2598. var content = c.lastChild;
  2599. var title = content.previousSibling;
  2600. c.insertBefore(content, nextChild);
  2601. c.insertBefore(title, content);
  2602. }
  2603. /**
  2604. * Translates this point by the given vector.
  2605. *
  2606. * @param {number} dx X-coordinate of the translation.
  2607. * @param {number} dy Y-coordinate of the translation.
  2608. */
  2609. EditorUi.prototype.loadLibrary = function(file, expand)
  2610. {
  2611. var doc = mxUtils.parseXml(file.getData());
  2612. if (doc.documentElement.nodeName == 'mxlibrary')
  2613. {
  2614. var images = JSON.parse(mxUtils.getTextContent(doc.documentElement));
  2615. this.libraryLoaded(file, images, doc.documentElement.getAttribute('title'), expand);
  2616. }
  2617. else
  2618. {
  2619. throw {message: mxResources.get('notALibraryFile')};
  2620. }
  2621. };
  2622. /**
  2623. * Translates this point by the given vector.
  2624. *
  2625. * @param {number} dx X-coordinate of the translation.
  2626. * @param {number} dy Y-coordinate of the translation.
  2627. */
  2628. EditorUi.prototype.getLibraryStorageHint = function(file)
  2629. {
  2630. return '';
  2631. };
  2632. /**
  2633. * Translates this point by the given vector.
  2634. *
  2635. * @param {number} dx X-coordinate of the translation.
  2636. * @param {number} dy Y-coordinate of the translation.
  2637. */
  2638. EditorUi.prototype.libraryLoaded = function(file, images, optionalTitle, expand)
  2639. {
  2640. if (this.sidebar == null)
  2641. {
  2642. return;
  2643. }
  2644. if (file.constructor != LocalLibrary)
  2645. {
  2646. mxSettings.addCustomLibrary(file.getHash());
  2647. }
  2648. if (file.title == '.scratchpad')
  2649. {
  2650. this.scratchpad = file;
  2651. }
  2652. var elts = this.sidebar.palettes[file.getHash()];
  2653. var nextSibling = (elts != null) ? elts[elts.length - 1].nextSibling : null;
  2654. // Removes existing sidebar entry for this library
  2655. this.removeLibrarySidebar(file.getHash());
  2656. var dropTarget = null;
  2657. var addImages = mxUtils.bind(this, function(imgs, content)
  2658. {
  2659. if (imgs.length == 0 && file.isEditable())
  2660. {
  2661. if (dropTarget == null)
  2662. {
  2663. dropTarget = document.createElement('div');
  2664. dropTarget.className = 'geDropTarget';
  2665. mxUtils.write(dropTarget, mxResources.get('dragElementsHere'));
  2666. }
  2667. content.appendChild(dropTarget);
  2668. }
  2669. else
  2670. {
  2671. this.addLibraryEntries(imgs, content);
  2672. }
  2673. });
  2674. // Adds entries to search index
  2675. // KNOWN: Existing entries are not replaced after edit of custom library
  2676. if (this.sidebar != null && images != null)
  2677. {
  2678. this.sidebar.addEntries(images);
  2679. }
  2680. // Adds new sidebar entry for this library
  2681. var tmp = (optionalTitle != null && optionalTitle.length > 0) ? optionalTitle : file.getTitle();
  2682. var contentDiv = this.sidebar.addPalette(file.getHash(), tmp,
  2683. (expand != null) ? expand : true, mxUtils.bind(this, function(content)
  2684. {
  2685. addImages(images, content);
  2686. }));
  2687. this.repositionLibrary(nextSibling);
  2688. // Adds tooltip for backend
  2689. var title = contentDiv.parentNode.previousSibling;
  2690. var tip = title.getAttribute('title');
  2691. if (tip != null && tip.length > 0 && file.title != '.scratchpad')
  2692. {
  2693. title.setAttribute('title', this.getLibraryStorageHint(file) + '\n' + tip);
  2694. }
  2695. var buttons = document.createElement('div');
  2696. buttons.style.position = 'absolute';
  2697. buttons.style.right = '0px';
  2698. buttons.style.top = '0px';
  2699. buttons.style.padding = '8px'
  2700. buttons.style.backgroundColor = 'inherit';
  2701. title.style.position = 'relative';
  2702. var btnWidth = 18;
  2703. var btn = document.createElement('img');
  2704. btn.setAttribute('src', Dialog.prototype.closeImage);
  2705. btn.setAttribute('title', mxResources.get('close'));
  2706. btn.setAttribute('valign', 'absmiddle');
  2707. btn.setAttribute('border', '0');
  2708. btn.style.cursor = 'pointer';
  2709. btn.style.margin = '0 3px';
  2710. var saveBtn = null;
  2711. if (file.title != '.scratchpad' || this.closableScratchpad)
  2712. {
  2713. buttons.appendChild(btn);
  2714. mxEvent.addListener(btn, 'click', mxUtils.bind(this, function(evt)
  2715. {
  2716. // Workaround for close after any button click in IE8
  2717. if (!mxEvent.isConsumed(evt))
  2718. {
  2719. var fn = mxUtils.bind(this, function()
  2720. {
  2721. this.closeLibrary(file);
  2722. });
  2723. if (saveBtn != null)
  2724. {
  2725. this.confirm(mxResources.get('allChangesLost'), null, fn,
  2726. mxResources.get('cancel'), mxResources.get('discardChanges'));
  2727. }
  2728. else
  2729. {
  2730. fn();
  2731. }
  2732. mxEvent.consume(evt);
  2733. }
  2734. }));
  2735. }
  2736. if (file.isEditable())
  2737. {
  2738. var graph = this.editor.graph;
  2739. var spinBtn = null;
  2740. var editLibrary = mxUtils.bind(this, function(evt)
  2741. {
  2742. this.showLibraryDialog(file.getTitle(), contentDiv, images, file, file.getMode());
  2743. mxEvent.consume(evt);
  2744. });
  2745. var saveLibrary = mxUtils.bind(this, function(evt)
  2746. {
  2747. file.setModified(true);
  2748. if (file.isAutosave())
  2749. {
  2750. if (spinBtn != null && spinBtn.parentNode != null)
  2751. {
  2752. spinBtn.parentNode.removeChild(spinBtn);
  2753. }
  2754. spinBtn = btn.cloneNode(false);
  2755. spinBtn.setAttribute('src', Editor.spinImage);
  2756. spinBtn.setAttribute('title', mxResources.get('saving'));
  2757. spinBtn.style.cursor = 'default';
  2758. spinBtn.style.marginRight = '2px';
  2759. spinBtn.style.marginTop = '-2px';
  2760. buttons.insertBefore(spinBtn, buttons.firstChild);
  2761. title.style.paddingRight = (buttons.childNodes.length * btnWidth) + 'px';
  2762. this.saveLibrary(file.getTitle(), images, file, file.getMode(), true, true, function()
  2763. {
  2764. if (spinBtn != null && spinBtn.parentNode != null)
  2765. {
  2766. spinBtn.parentNode.removeChild(spinBtn);
  2767. title.style.paddingRight = (buttons.childNodes.length * btnWidth) + 'px';
  2768. }
  2769. });
  2770. }
  2771. else if (saveBtn == null)
  2772. {
  2773. saveBtn = btn.cloneNode(false);
  2774. saveBtn.setAttribute('src', IMAGE_PATH + '/download.png');
  2775. saveBtn.setAttribute('title', mxResources.get('save'));
  2776. buttons.insertBefore(saveBtn, buttons.firstChild);
  2777. mxEvent.addListener(saveBtn, 'click', mxUtils.bind(this, function(evt)
  2778. {
  2779. this.saveLibrary(file.getTitle(), images, file, file.getMode(),
  2780. file.constructor == LocalLibrary, true, function()
  2781. {
  2782. if (saveBtn != null && !file.isModified())
  2783. {
  2784. title.style.paddingRight = (buttons.childNodes.length * btnWidth) + 'px';
  2785. saveBtn.parentNode.removeChild(saveBtn);
  2786. saveBtn = null;
  2787. }
  2788. });
  2789. mxEvent.consume(evt);
  2790. }));
  2791. title.style.paddingRight = (buttons.childNodes.length * btnWidth) + 'px';
  2792. }
  2793. });
  2794. var addCells = mxUtils.bind(this, function(cells, bounds, evt, title)
  2795. {
  2796. cells = graph.cloneCells(mxUtils.sortCells(graph.model.getTopmostCells(cells)));
  2797. // Translates cells to origin
  2798. for (var i = 0; i < cells.length; i++)
  2799. {
  2800. var geo = graph.getCellGeometry(cells[i]);
  2801. if (geo != null)
  2802. {
  2803. geo.translate(-bounds.x, -bounds.y);
  2804. }
  2805. }
  2806. contentDiv.appendChild(this.sidebar.createVertexTemplateFromCells(
  2807. cells, bounds.width, bounds.height, title || '', true, false, false));
  2808. var xml = Graph.compress(mxUtils.getXml(this.editor.graph.encodeCells(cells)));
  2809. var entry = {xml: xml, w: bounds.width, h: bounds.height};
  2810. if (title != null)
  2811. {
  2812. entry.title = title;
  2813. }
  2814. images.push(entry);
  2815. saveLibrary(evt);
  2816. if (dropTarget != null && dropTarget.parentNode != null && images.length > 0)
  2817. {
  2818. dropTarget.parentNode.removeChild(dropTarget);
  2819. dropTarget = null;
  2820. }
  2821. });
  2822. var addSelection = mxUtils.bind(this, function(evt)
  2823. {
  2824. if (!graph.isSelectionEmpty())
  2825. {
  2826. var cells = graph.getSelectionCells();
  2827. var bounds = graph.view.getBounds(cells);
  2828. var s = graph.view.scale;
  2829. bounds.x /= s;
  2830. bounds.y /= s;
  2831. bounds.width /= s;
  2832. bounds.height /= s;
  2833. bounds.x -= graph.view.translate.x;
  2834. bounds.y -= graph.view.translate.y;
  2835. addCells(cells, bounds);
  2836. }
  2837. else if (graph.getRubberband().isActive())
  2838. {
  2839. graph.getRubberband().execute(evt);
  2840. graph.getRubberband().reset();
  2841. }
  2842. else
  2843. {
  2844. this.showError(mxResources.get('error'), mxResources.get('nothingIsSelected'), mxResources.get('ok'));
  2845. }
  2846. mxEvent.consume(evt);
  2847. });
  2848. // Adds drop handler from graph
  2849. mxEvent.addGestureListeners(contentDiv, function(){}, mxUtils.bind(this, function(evt)
  2850. {
  2851. if (graph.isMouseDown && graph.panningManager != null && graph.graphHandler.first != null)
  2852. {
  2853. graph.graphHandler.suspend();
  2854. if (graph.graphHandler.hint != null)
  2855. {
  2856. graph.graphHandler.hint.style.visibility = 'hidden';
  2857. }
  2858. contentDiv.style.backgroundColor = '#f1f3f4';
  2859. contentDiv.style.cursor = 'copy';
  2860. graph.panningManager.stop();
  2861. graph.autoScroll = false;
  2862. mxEvent.consume(evt);
  2863. }
  2864. }), mxUtils.bind(this, function(evt)
  2865. {
  2866. if (graph.isMouseDown && graph.panningManager != null && graph.graphHandler != null)
  2867. {
  2868. contentDiv.style.backgroundColor = '';
  2869. contentDiv.style.cursor = 'default';
  2870. this.sidebar.showTooltips = true;
  2871. graph.panningManager.stop();
  2872. graph.graphHandler.reset();
  2873. graph.isMouseDown = false;
  2874. graph.autoScroll = true;
  2875. addSelection(evt);
  2876. mxEvent.consume(evt);
  2877. }
  2878. }));
  2879. // Handles mouse leaving the library and restoring move
  2880. mxEvent.addListener(contentDiv, 'mouseleave', mxUtils.bind(this, function(evt)
  2881. {
  2882. if (graph.isMouseDown && graph.graphHandler.first != null)
  2883. {
  2884. graph.graphHandler.resume();
  2885. if (graph.graphHandler.hint != null)
  2886. {
  2887. graph.graphHandler.hint.style.visibility = 'visible';
  2888. }
  2889. contentDiv.style.backgroundColor = '';
  2890. contentDiv.style.cursor = '';
  2891. graph.autoScroll = true;
  2892. }
  2893. }));
  2894. // Adds drop handler from filesystem
  2895. if (Graph.fileSupport)
  2896. {
  2897. mxEvent.addListener(contentDiv, 'dragover', mxUtils.bind(this, function(evt)
  2898. {
  2899. contentDiv.style.backgroundColor = '#f1f3f4';
  2900. evt.dataTransfer.dropEffect = 'copy';
  2901. contentDiv.style.cursor = 'copy';
  2902. this.sidebar.hideTooltip();
  2903. evt.stopPropagation();
  2904. evt.preventDefault();
  2905. }));
  2906. mxEvent.addListener(contentDiv, 'drop', mxUtils.bind(this, function(evt)
  2907. {
  2908. contentDiv.style.cursor = '';
  2909. contentDiv.style.backgroundColor = '';
  2910. if (evt.dataTransfer.files.length > 0)
  2911. {
  2912. this.importFiles(evt.dataTransfer.files, 0, 0, this.maxImageSize, mxUtils.bind(this, function(data, mimeType, x, y, w, h, img, doneFn, file)
  2913. {
  2914. if (data != null && mimeType.substring(0, 6) == 'image/')
  2915. {
  2916. var style = 'shape=image;verticalLabelPosition=bottom;verticalAlign=top;imageAspect=0;aspect=fixed;image=' +
  2917. this.convertDataUri(data);
  2918. var cells = [new mxCell('', new mxGeometry(0, 0, w, h), style)];
  2919. cells[0].vertex = true;
  2920. addCells(cells, new mxRectangle(0, 0, w, h), evt, (mxEvent.isAltDown(evt)) ? null : img.substring(0, img.lastIndexOf('.')).replace(/_/g, ' '));
  2921. if (dropTarget != null && dropTarget.parentNode != null && images.length > 0)
  2922. {
  2923. dropTarget.parentNode.removeChild(dropTarget);
  2924. dropTarget = null;
  2925. }
  2926. }
  2927. else
  2928. {
  2929. var done = false;
  2930. var doImport = mxUtils.bind(this, function(theData, theMimeType)
  2931. {
  2932. if (theData != null && theMimeType == 'application/pdf')
  2933. {
  2934. var xml = Editor.extractGraphModelFromPdf(theData);
  2935. if (xml != null && xml.length > 0)
  2936. {
  2937. theMimeType = 'text/xml';
  2938. theData = xml;
  2939. }
  2940. }
  2941. if (theData != null) //Try to parse the file as xml (can be a library or mxfile). Otherwise, an error will be shown
  2942. {
  2943. var doc = mxUtils.parseXml(theData);
  2944. if (doc.documentElement.nodeName == 'mxlibrary')
  2945. {
  2946. try
  2947. {
  2948. var temp = JSON.parse(mxUtils.getTextContent(doc.documentElement));
  2949. addImages(temp, contentDiv);
  2950. images = images.concat(temp);
  2951. saveLibrary(evt);
  2952. this.spinner.stop();
  2953. done = true;
  2954. }
  2955. catch (e)
  2956. {
  2957. // ignore
  2958. }
  2959. }
  2960. else if (doc.documentElement.nodeName == 'mxfile')
  2961. {
  2962. try
  2963. {
  2964. var pages = doc.documentElement.getElementsByTagName('diagram');
  2965. for (var i = 0; i < pages.length; i++)
  2966. {
  2967. var cells = this.stringToCells(Editor.getDiagramNodeXml(pages[i]));
  2968. var size = this.editor.graph.getBoundingBoxFromGeometry(cells);
  2969. addCells(cells, new mxRectangle(0, 0, size.width, size.height), evt);
  2970. }
  2971. done = true;
  2972. }
  2973. catch (e)
  2974. {
  2975. if (window.console != null)
  2976. {
  2977. console.log('error in drop handler:', e);
  2978. }
  2979. }
  2980. }
  2981. }
  2982. if (!done)
  2983. {
  2984. this.spinner.stop();
  2985. this.handleError({message: mxResources.get('errorLoadingFile')})
  2986. }
  2987. if (dropTarget != null && dropTarget.parentNode != null && images.length > 0)
  2988. {
  2989. dropTarget.parentNode.removeChild(dropTarget);
  2990. dropTarget = null;
  2991. }
  2992. });
  2993. if (file != null && img != null && ((/(\.v(dx|sdx?))($|\?)/i.test(img)) || /(\.vs(x|sx?))($|\?)/i.test(img)))
  2994. {
  2995. this.importVisio(file, function(xml)
  2996. {
  2997. doImport(xml, 'text/xml');
  2998. }, null, img);
  2999. }
  3000. else if (!this.isOffline() && new XMLHttpRequest().upload && this.isRemoteFileFormat(data, img) && file != null)
  3001. {
  3002. this.parseFile(file, mxUtils.bind(this, function(xhr)
  3003. {
  3004. if (xhr.readyState == 4)
  3005. {
  3006. this.spinner.stop();
  3007. if (xhr.status >= 200 && xhr.status <= 299)
  3008. {
  3009. doImport(xhr.responseText, 'text/xml');
  3010. }
  3011. else
  3012. {
  3013. this.handleError({message: mxResources.get((xhr.status == 413) ?
  3014. 'drawingTooLarge' : 'invalidOrMissingFile')},
  3015. mxResources.get('errorLoadingFile'));
  3016. }
  3017. }
  3018. }));
  3019. }
  3020. else
  3021. {
  3022. doImport(data, mimeType);
  3023. }
  3024. }
  3025. }));
  3026. }
  3027. evt.stopPropagation();
  3028. evt.preventDefault();
  3029. }));
  3030. mxEvent.addListener(contentDiv, 'dragleave', function(evt)
  3031. {
  3032. contentDiv.style.cursor = '';
  3033. contentDiv.style.backgroundColor = '';
  3034. evt.stopPropagation();
  3035. evt.preventDefault();
  3036. });
  3037. }
  3038. btn = btn.cloneNode(false);
  3039. btn.setAttribute('src', Editor.editImage);
  3040. btn.setAttribute('title', mxResources.get('edit'));
  3041. buttons.insertBefore(btn, buttons.firstChild);
  3042. mxEvent.addListener(btn, 'click', editLibrary);
  3043. mxEvent.addListener(contentDiv, 'dblclick', function(evt)
  3044. {
  3045. if (mxEvent.getSource(evt) == contentDiv)
  3046. {
  3047. editLibrary(evt);
  3048. }
  3049. });
  3050. var btn2 = btn.cloneNode(false);
  3051. btn2.setAttribute('src', Editor.plusImage);
  3052. btn2.setAttribute('title', mxResources.get('add'));
  3053. buttons.insertBefore(btn2, buttons.firstChild);
  3054. mxEvent.addListener(btn2, 'click', addSelection);
  3055. if (!this.isOffline() && file.title == '.scratchpad' && EditorUi.scratchpadHelpLink != null)
  3056. {
  3057. var link = document.createElement('span');
  3058. link.setAttribute('title', mxResources.get('help'));
  3059. link.style.cssText = 'color:#a3a3a3;text-decoration:none;margin-right:2px;';
  3060. mxUtils.write(link, '?');
  3061. mxEvent.addGestureListeners(link, mxUtils.bind(this, function(evt)
  3062. {
  3063. this.openLink(EditorUi.scratchpadHelpLink);
  3064. mxEvent.consume(evt);
  3065. }));
  3066. buttons.insertBefore(link, buttons.firstChild);
  3067. }
  3068. }
  3069. title.appendChild(buttons);
  3070. title.style.paddingRight = (buttons.childNodes.length * btnWidth) + 'px';
  3071. };
  3072. /**
  3073. * Adds the library entries to the given DOM node.
  3074. */
  3075. EditorUi.prototype.addLibraryEntries = function(imgs, content)
  3076. {
  3077. for (var i = 0; i < imgs.length; i++)
  3078. {
  3079. var img = imgs[i];
  3080. var data = img.data;
  3081. if (data != null)
  3082. {
  3083. data = this.convertDataUri(data);
  3084. var s = 'shape=image;verticalLabelPosition=bottom;verticalAlign=top;imageAspect=0;';
  3085. if (img.aspect == 'fixed')
  3086. {
  3087. s += 'aspect=fixed;'
  3088. }
  3089. content.appendChild(this.sidebar.createVertexTemplate(s + 'image=' +
  3090. data, img.w, img.h, '', img.title || '', false, false, true));
  3091. }
  3092. else if (img.xml != null)
  3093. {
  3094. var cells = this.stringToCells(Graph.decompress(img.xml));
  3095. if (cells.length > 0)
  3096. {
  3097. content.appendChild(this.sidebar.createVertexTemplateFromCells(
  3098. cells, img.w, img.h, img.title || '', true, false, true));
  3099. }
  3100. }
  3101. }
  3102. };
  3103. /**
  3104. * Extracts the resource for the current language from the given multi language
  3105. * resource object of the form {es: "...", de: "...", main: "..."} where the keys
  3106. * are country codes and main defines the fallback if no resource for the current
  3107. * country code exists.
  3108. */
  3109. EditorUi.prototype.getResource = function(obj)
  3110. {
  3111. return (obj != null) ? (obj[mxLanguage] || obj.main) : null;
  3112. };
  3113. /**
  3114. * EditorUi Overrides
  3115. */
  3116. EditorUi.prototype.footerHeight = 0;
  3117. if (urlParams['savesidebar'] == '1')
  3118. {
  3119. Sidebar.prototype.thumbWidth = 64;
  3120. Sidebar.prototype.thumbHeight = 64;
  3121. }
  3122. /**
  3123. * Programmatic settings for theme.
  3124. */
  3125. EditorUi.initTheme = function()
  3126. {
  3127. if (uiTheme == 'atlas')
  3128. {
  3129. mxClient.link('stylesheet', STYLE_PATH + '/atlas.css');
  3130. if (typeof Toolbar !== 'undefined')
  3131. {
  3132. Toolbar.prototype.unselectedBackground = 'linear-gradient(rgb(255, 255, 255) 0px, rgb(242, 242, 242) 100%)';
  3133. Toolbar.prototype.selectedBackground = 'rgb(242, 242, 242)';
  3134. }
  3135. Editor.prototype.initialTopSpacing = 3;
  3136. EditorUi.prototype.menubarHeight = 41;
  3137. EditorUi.prototype.toolbarHeight = 38;
  3138. }
  3139. else if (Editor.isDarkMode())
  3140. {
  3141. mxClient.link('stylesheet', STYLE_PATH + '/dark.css');
  3142. Dialog.backdropColor = '#2a2a2a';
  3143. Format.inactiveTabBackgroundColor = 'black';
  3144. Graph.prototype.defaultThemeName = 'darkTheme';
  3145. Graph.prototype.defaultPageBackgroundColor = '#2a2a2a';
  3146. Graph.prototype.defaultPageBorderColor = '#505759';
  3147. BaseFormatPanel.prototype.buttonBackgroundColor = '#2a2a2a';
  3148. mxGraphHandler.prototype.previewColor = '#cccccc';
  3149. StyleFormatPanel.prototype.defaultStrokeColor = '#cccccc';
  3150. mxConstants.DROP_TARGET_COLOR = '#00ff00';
  3151. }
  3152. Editor.sketchFontFamily = 'Architects Daughter';
  3153. Editor.sketchFontSource = 'https%3A%2F%2Ffonts.googleapis.com%2Fcss%3Ffamily%3DArchitects%2BDaughter';
  3154. // Implements the sketch-min UI
  3155. if (urlParams['sketch'] == '1')
  3156. {
  3157. Menus.prototype.defaultFonts = [{'fontFamily': Editor.sketchFontFamily,
  3158. 'fontUrl': decodeURIComponent(Editor.sketchFontSource)},
  3159. {'fontFamily': 'Rock Salt', 'fontUrl': 'https://fonts.googleapis.com/css?family=Rock+Salt'},
  3160. {'fontFamily': 'Permanent Marker', 'fontUrl': 'https://fonts.googleapis.com/css?family=Permanent+Marker'}].
  3161. concat(Menus.prototype.defaultFonts);
  3162. Graph.prototype.defaultVertexStyle = {'fontFamily': Editor.sketchFontFamily , 'fontSize': '20',
  3163. 'fontSource': Editor.sketchFontSource, 'pointerEvents': '0', 'sketch':
  3164. urlParams['rough'] == '0' ? '0' : '1', 'hachureGap': '4'};
  3165. Graph.prototype.defaultEdgeStyle = {'edgeStyle': 'none', 'rounded': '0', 'curved': '1',
  3166. 'jettySize': 'auto', 'orthogonalLoop': '1', 'endArrow': 'open', 'startSize': '14', 'endSize': '14',
  3167. 'fontFamily': Editor.sketchFontFamily, 'fontSize': '20','fontSource': Editor.sketchFontSource,
  3168. 'sourcePerimeterSpacing': '8', 'targetPerimeterSpacing': '8', 'sketch':
  3169. urlParams['rough'] == '0' ? '0' : '1'};
  3170. Editor.configurationKey = '.sketch-configuration';
  3171. Editor.settingsKey = '.sketch-config';
  3172. Graph.prototype.defaultGridEnabled = false;
  3173. Graph.prototype.defaultPageVisible = false;
  3174. Graph.prototype.defaultEdgeLength = 120;
  3175. Editor.fitWindowBorders = new mxRectangle(60, 30, 30, 30);
  3176. }
  3177. };
  3178. EditorUi.initTheme();
  3179. /**
  3180. * Overrides image dialog to add image search and Google+.
  3181. */
  3182. EditorUi.prototype.showImageDialog = function(title, value, fn, ignoreExisting, convertDataUri)
  3183. {
  3184. // KNOWN: IE+FF don't return keyboard focus after image dialog (calling focus doesn't help)
  3185. var dlg = new ImageDialog(this, title, value, fn, ignoreExisting, convertDataUri);
  3186. this.showDialog(dlg.container, (Graph.fileSupport) ? 480 : 360, (Graph.fileSupport) ? 200 : 90, true, true);
  3187. dlg.init();
  3188. };
  3189. /**
  3190. * Hides the current menu.
  3191. */
  3192. EditorUi.prototype.showBackgroundImageDialog = function(apply, img)
  3193. {
  3194. apply = (apply != null) ? apply : mxUtils.bind(this, function(image, failed)
  3195. {
  3196. if (!failed)
  3197. {
  3198. var change = new ChangePageSetup(this, null, image);
  3199. change.ignoreColor = true;
  3200. this.editor.graph.model.execute(change);
  3201. }
  3202. });
  3203. var dlg = new BackgroundImageDialog(this, apply, img);
  3204. this.showDialog(dlg.container, 360, 200, true, true);
  3205. dlg.init();
  3206. };
  3207. /**
  3208. * Hides the current menu.
  3209. */
  3210. EditorUi.prototype.showLibraryDialog = function(name, sidebar, images, file, mode)
  3211. {
  3212. var dlg = new LibraryDialog(this, name, sidebar, images, file, mode);
  3213. this.showDialog(dlg.container, 640, 440, true, false, mxUtils.bind(this, function(cancel)
  3214. {
  3215. if (cancel && this.getCurrentFile() == null && urlParams['embed'] != '1')
  3216. {
  3217. this.showSplash();
  3218. }
  3219. }));
  3220. dlg.init();
  3221. };
  3222. /**
  3223. * Overridden to update after view state changes.
  3224. */
  3225. var editorUiCreateFormat = EditorUi.prototype.createFormat;
  3226. EditorUi.prototype.createFormat = function(container)
  3227. {
  3228. var format = editorUiCreateFormat.apply(this, arguments);
  3229. this.editor.graph.addListener('viewStateChanged', mxUtils.bind(this, function(evt)
  3230. {
  3231. if (this.editor.graph.isSelectionEmpty())
  3232. {
  3233. format.refresh();
  3234. }
  3235. }));
  3236. return format;
  3237. };
  3238. /**
  3239. * Hook for sidebar footer container.
  3240. */
  3241. EditorUi.prototype.createSidebarFooterContainer = function()
  3242. {
  3243. var div = this.createDiv('geSidebarContainer geSidebarFooter');
  3244. div.style.position = 'absolute';
  3245. div.style.overflow = 'hidden';
  3246. var elt2 = document.createElement('a');
  3247. elt2.className = 'geTitle';
  3248. elt2.style.color = '#DF6C0C';
  3249. elt2.style.fontWeight = 'bold';
  3250. elt2.style.height = '100%';
  3251. elt2.style.paddingTop = '9px';
  3252. elt2.innerHTML = '<span style="font-size:18px;margin-right:5px;">+</span>';
  3253. mxUtils.write(elt2, mxResources.get('moreShapes') + '...');
  3254. // Prevents focus
  3255. mxEvent.addListener(elt2, (mxClient.IS_POINTER) ? 'pointerdown' : 'mousedown',
  3256. mxUtils.bind(this, function(evt)
  3257. {
  3258. evt.preventDefault();
  3259. }));
  3260. mxEvent.addListener(elt2, 'click', mxUtils.bind(this, function(evt)
  3261. {
  3262. this.actions.get('shapes').funct();
  3263. mxEvent.consume(evt);
  3264. }));
  3265. div.appendChild(elt2);
  3266. return div;
  3267. };
  3268. /**
  3269. * Translates this point by the given vector.
  3270. *
  3271. * @param {number} dx X-coordinate of the translation.
  3272. * @param {number} dy Y-coordinate of the translation.
  3273. */
  3274. EditorUi.prototype.handleError = function(resp, title, fn, invokeFnOnClose, notFoundMessage, fileHash, disableLogging)
  3275. {
  3276. var resume = (this.spinner != null && this.spinner.pause != null) ? this.spinner.pause() : function() {};
  3277. var e = (resp != null && resp.error != null) ? resp.error : resp;
  3278. // Logs errors and writes stack to console
  3279. if (resp != null && resp.stack != null && resp.message != null)
  3280. {
  3281. try
  3282. {
  3283. if (!disableLogging)
  3284. {
  3285. EditorUi.logError('Caught: ' +
  3286. (resp.message == '' && resp.name != null) ? resp.name : resp.message,
  3287. resp.filename, resp.lineNumber, resp.columnNumber, resp, 'INFO');
  3288. }
  3289. else
  3290. {
  3291. if (window.console != null)
  3292. {
  3293. console.error('EditorUi.handleError:', resp);
  3294. }
  3295. }
  3296. }
  3297. catch (e)
  3298. {
  3299. // ignore
  3300. }
  3301. }
  3302. if (e != null || title != null)
  3303. {
  3304. var msg = mxUtils.htmlEntities(mxResources.get('unknownError'));
  3305. var btn = mxResources.get('ok');
  3306. var retry = null;
  3307. title = (title != null) ? title : mxResources.get('error');
  3308. if (e != null)
  3309. {
  3310. if (e.retry != null)
  3311. {
  3312. btn = mxResources.get('cancel');
  3313. retry = function()
  3314. {
  3315. resume();
  3316. e.retry();
  3317. };
  3318. }
  3319. if (e.code == 404 || e.status == 404 || e.code == 403)
  3320. {
  3321. if (e.code == 403)
  3322. {
  3323. if (e.message != null)
  3324. {
  3325. msg = mxUtils.htmlEntities(e.message);
  3326. }
  3327. else
  3328. {
  3329. msg = mxUtils.htmlEntities(mxResources.get('accessDenied'));
  3330. }
  3331. }
  3332. else
  3333. {
  3334. msg = (notFoundMessage != null) ? notFoundMessage :
  3335. mxUtils.htmlEntities(mxResources.get('fileNotFoundOrDenied') +
  3336. ((this.drive != null && this.drive.user != null) ? ' (' + this.drive.user.displayName +
  3337. ', ' + this.drive.user.email+ ')' : ''));
  3338. }
  3339. var id = (notFoundMessage != null) ? null : ((fileHash != null) ? fileHash : window.location.hash);
  3340. // #U handles case where we tried to fallback to Google File and
  3341. // hash property still shows the public URL we tried to load
  3342. if (id != null && (id.substring(0, 2) == '#G' ||
  3343. id.substring(0, 45) == '#Uhttps%3A%2F%2Fdrive.google.com%2Fuc%3Fid%3D') &&
  3344. ((resp != null && resp.error != null && ((resp.error.errors != null &&
  3345. resp.error.errors.length > 0 && resp.error.errors[0].reason == 'fileAccess') ||
  3346. (resp.error.data != null && resp.error.data.length > 0 &&
  3347. resp.error.data[0].reason == 'fileAccess'))) ||
  3348. e.code == 404 || e.status == 404))
  3349. {
  3350. id = (id.substring(0, 2) == '#U') ? id.substring(45, id.lastIndexOf('%26ex')) : id.substring(2);
  3351. // Special case where the button must have a different label and function
  3352. this.showError(title, msg, mxResources.get('openInNewWindow'), mxUtils.bind(this, function()
  3353. {
  3354. this.editor.graph.openLink('https://drive.google.com/open?id=' + id);
  3355. this.handleError(resp, title, fn, invokeFnOnClose, notFoundMessage)
  3356. }), retry, mxResources.get('changeUser'), mxUtils.bind(this, function()
  3357. {
  3358. var driveUsers = this.drive.getUsersList();
  3359. var div = document.createElement('div');
  3360. var title = document.createElement('span');
  3361. title.style.marginTop = '6px';
  3362. mxUtils.write(title, mxResources.get('changeUser') + ': ');
  3363. div.appendChild(title);
  3364. var usersSelect = document.createElement('select');
  3365. usersSelect.style.width = '200px';
  3366. //TODO This code is similar to Dialogs.js change user part in SplashDialog
  3367. function fillUsersSelect()
  3368. {
  3369. usersSelect.innerHTML = '';
  3370. for (var i = 0; i < driveUsers.length; i++)
  3371. {
  3372. var option = document.createElement('option');
  3373. mxUtils.write(option, driveUsers[i].displayName);
  3374. option.value = i;
  3375. usersSelect.appendChild(option);
  3376. //More info (email) about the user in a disabled option
  3377. option = document.createElement('option');
  3378. option.innerHTML = '&nbsp;&nbsp;&nbsp;';
  3379. mxUtils.write(option, '<' + driveUsers[i].email + '>');
  3380. option.setAttribute('disabled', 'disabled');
  3381. usersSelect.appendChild(option);
  3382. }
  3383. //Add account option
  3384. var option = document.createElement('option');
  3385. mxUtils.write(option, mxResources.get('addAccount'));
  3386. option.value = driveUsers.length;
  3387. usersSelect.appendChild(option);
  3388. }
  3389. fillUsersSelect();
  3390. mxEvent.addListener(usersSelect, 'change', mxUtils.bind(this, function()
  3391. {
  3392. var userIndex = usersSelect.value;
  3393. var existingAccount = driveUsers.length != userIndex;
  3394. if (existingAccount)
  3395. {
  3396. this.drive.setUser(driveUsers[userIndex]);
  3397. }
  3398. this.drive.authorize(existingAccount, mxUtils.bind(this, function()
  3399. {
  3400. if (!existingAccount)
  3401. {
  3402. driveUsers = this.drive.getUsersList();
  3403. fillUsersSelect();
  3404. }
  3405. }), mxUtils.bind(this, function(resp)
  3406. {
  3407. this.handleError(resp);
  3408. }), true);
  3409. }));
  3410. div.appendChild(usersSelect);
  3411. var dlg = new CustomDialog(this, div, mxUtils.bind(this, function()
  3412. {
  3413. this.loadFile(window.location.hash.substr(1), true);
  3414. }));
  3415. this.showDialog(dlg.container, 300, 75, true, true);
  3416. }), mxResources.get('cancel'), mxUtils.bind(this, function()
  3417. {
  3418. this.hideDialog();
  3419. if (fn != null)
  3420. {
  3421. fn();
  3422. }
  3423. }), 480, 150);
  3424. return;
  3425. }
  3426. }
  3427. if (e.message != null)
  3428. {
  3429. if (e.message == '' && e.name != null)
  3430. {
  3431. msg = mxUtils.htmlEntities(e.name);
  3432. }
  3433. else
  3434. {
  3435. msg = mxUtils.htmlEntities(e.message);
  3436. }
  3437. }
  3438. else if (e.response != null && e.response.error != null)
  3439. {
  3440. msg = mxUtils.htmlEntities(e.response.error);
  3441. }
  3442. else if (typeof window.App !== 'undefined')
  3443. {
  3444. if (e.code == App.ERROR_TIMEOUT)
  3445. {
  3446. msg = mxUtils.htmlEntities(mxResources.get('timeout'));
  3447. }
  3448. else if (e.code == App.ERROR_BUSY)
  3449. {
  3450. msg = mxUtils.htmlEntities(mxResources.get('busy'));
  3451. }
  3452. else if (typeof e === 'string' && e.length > 0)
  3453. {
  3454. msg = mxUtils.htmlEntities(e);
  3455. }
  3456. }
  3457. }
  3458. var btn3 = null;
  3459. var fn3 = null;
  3460. if (e != null && e.helpLink != null)
  3461. {
  3462. btn3 = mxResources.get('help');
  3463. fn3 = mxUtils.bind(this, function()
  3464. {
  3465. return this.editor.graph.openLink(e.helpLink);
  3466. });
  3467. }
  3468. this.showError(title, msg, btn, fn, retry, null, null, btn3, fn3,
  3469. null, null, null, (invokeFnOnClose) ? fn : null);
  3470. }
  3471. else if (fn != null)
  3472. {
  3473. fn();
  3474. }
  3475. };
  3476. /**
  3477. * Translates this point by the given vector.
  3478. *
  3479. * @param {number} dx X-coordinate of the translation.
  3480. * @param {number} dy Y-coordinate of the translation.
  3481. */
  3482. EditorUi.prototype.alert = function(msg, fn, optionalWidth)
  3483. {
  3484. var dlg = new ErrorDialog(this, null, msg, mxResources.get('ok'), fn);
  3485. this.showDialog(dlg.container, optionalWidth || 340, 100, true, false);
  3486. dlg.init();
  3487. };
  3488. /**
  3489. * Translates this point by the given vector.
  3490. *
  3491. * @param {number} dx X-coordinate of the translation.
  3492. * @param {number} dy Y-coordinate of the translation.
  3493. */
  3494. EditorUi.prototype.confirm = function(msg, okFn, cancelFn, okLabel, cancelLabel, closable)
  3495. {
  3496. var resume = (this.spinner != null && this.spinner.pause != null) ? this.spinner.pause() : function() {};
  3497. var height = Math.min(200, Math.ceil(msg.length / 50) * 28);
  3498. var dlg = new ConfirmDialog(this, msg, function()
  3499. {
  3500. resume();
  3501. if (okFn != null)
  3502. {
  3503. okFn();
  3504. }
  3505. }, function()
  3506. {
  3507. resume();
  3508. if (cancelFn != null)
  3509. {
  3510. cancelFn();
  3511. }
  3512. }, okLabel, cancelLabel, null, null, null, null, height);
  3513. this.showDialog(dlg.container, 340, 46 + height, true, closable);
  3514. dlg.init();
  3515. };
  3516. /**
  3517. * Creates a popup banner.
  3518. */
  3519. EditorUi.prototype.showBanner = function(id, text, onclick, doNotShowAgainOnClose)
  3520. {
  3521. var result = false;
  3522. if (!this.bannerShowing && !this['hideBanner' + id] &&
  3523. (!isLocalStorage || mxSettings.settings == null ||
  3524. mxSettings.settings['close' + id] == null))
  3525. {
  3526. var banner = document.createElement('div');
  3527. banner.style.cssText = 'position:absolute;bottom:10px;left:50%;max-width:90%;padding:18px 34px 12px 20px;' +
  3528. 'font-size:16px;font-weight:bold;white-space:nowrap;cursor:pointer;z-index:' + mxPopupMenu.prototype.zIndex + ';';
  3529. mxUtils.setPrefixedStyle(banner.style, 'box-shadow', '1px 1px 2px 0px #ddd');
  3530. mxUtils.setPrefixedStyle(banner.style, 'transform', 'translate(-50%,120%)');
  3531. mxUtils.setPrefixedStyle(banner.style, 'transition', 'all 1s ease');
  3532. banner.className = 'geBtn gePrimaryBtn';
  3533. var logo = document.createElement('img');
  3534. logo.setAttribute('src', IMAGE_PATH + '/logo.png');
  3535. logo.setAttribute('border', '0');
  3536. logo.setAttribute('align', 'absmiddle');
  3537. logo.style.cssText = 'margin-top:-4px;margin-left:8px;margin-right:12px;width:26px;height:26px;';
  3538. banner.appendChild(logo);
  3539. var img = document.createElement('img');
  3540. img.setAttribute('src', Dialog.prototype.closeImage);
  3541. img.setAttribute('title', mxResources.get((doNotShowAgainOnClose) ? 'doNotShowAgain' : 'close'));
  3542. img.setAttribute('border', '0');
  3543. img.style.cssText = 'position:absolute;right:10px;top:12px;filter:invert(1);padding:6px;margin:-6px;cursor:default;';
  3544. banner.appendChild(img);
  3545. mxUtils.write(banner, text);
  3546. document.body.appendChild(banner);
  3547. this.bannerShowing = true;
  3548. var div = document.createElement('div');
  3549. div.style.cssText = 'font-size:11px;text-align:center;font-weight:normal;';
  3550. var chk = document.createElement('input');
  3551. chk.setAttribute('type', 'checkbox');
  3552. chk.setAttribute('id', 'geDoNotShowAgainCheckbox');
  3553. chk.style.marginRight = '6px';
  3554. if (!doNotShowAgainOnClose)
  3555. {
  3556. div.appendChild(chk);
  3557. var label = document.createElement('label');
  3558. label.setAttribute('for', 'geDoNotShowAgainCheckbox');
  3559. mxUtils.write(label, mxResources.get('doNotShowAgain'));
  3560. div.appendChild(label);
  3561. banner.style.paddingBottom = '30px';
  3562. banner.appendChild(div);
  3563. }
  3564. var onclose = mxUtils.bind(this, function()
  3565. {
  3566. if (banner.parentNode != null)
  3567. {
  3568. banner.parentNode.removeChild(banner);
  3569. this.bannerShowing = false;
  3570. if (chk.checked || doNotShowAgainOnClose)
  3571. {
  3572. this['hideBanner' + id] = true;
  3573. if (isLocalStorage && mxSettings.settings != null)
  3574. {
  3575. mxSettings.settings['close' + id] = Date.now();
  3576. mxSettings.save();
  3577. }
  3578. }
  3579. }
  3580. });
  3581. mxEvent.addListener(img, 'click', mxUtils.bind(this, function(e)
  3582. {
  3583. mxEvent.consume(e);
  3584. onclose();
  3585. }));
  3586. var hide = mxUtils.bind(this, function()
  3587. {
  3588. mxUtils.setPrefixedStyle(banner.style, 'transform', 'translate(-50%,120%)');
  3589. window.setTimeout(mxUtils.bind(this, function()
  3590. {
  3591. onclose();
  3592. }), 1000);
  3593. });
  3594. mxEvent.addListener(banner, 'click', mxUtils.bind(this, function(e)
  3595. {
  3596. var source = mxEvent.getSource(e);
  3597. if (source != chk && source != label)
  3598. {
  3599. if (onclick != null)
  3600. {
  3601. onclick();
  3602. }
  3603. onclose();
  3604. mxEvent.consume(e);
  3605. }
  3606. else
  3607. {
  3608. hide();
  3609. }
  3610. }));
  3611. window.setTimeout(mxUtils.bind(this, function()
  3612. {
  3613. mxUtils.setPrefixedStyle(banner.style, 'transform', 'translate(-50%,0%)');
  3614. }), 500);
  3615. window.setTimeout(hide, 30000);
  3616. result = true;
  3617. }
  3618. return result;
  3619. };
  3620. /**
  3621. * Translates this point by the given vector.
  3622. *
  3623. * @param {number} dx X-coordinate of the translation.
  3624. * @param {number} dy Y-coordinate of the translation.
  3625. */
  3626. EditorUi.prototype.setCurrentFile = function(file)
  3627. {
  3628. if (file != null)
  3629. {
  3630. file.opened = new Date();
  3631. }
  3632. this.currentFile = file;
  3633. };
  3634. /**
  3635. * Translates this point by the given vector.
  3636. *
  3637. * @param {number} dx X-coordinate of the translation.
  3638. * @param {number} dy Y-coordinate of the translation.
  3639. */
  3640. EditorUi.prototype.getCurrentFile = function()
  3641. {
  3642. return this.currentFile;
  3643. };
  3644. /**
  3645. * Handling for canvas export.
  3646. */
  3647. EditorUi.prototype.isExportToCanvas = function()
  3648. {
  3649. return this.editor.isExportToCanvas();
  3650. };
  3651. /**
  3652. *
  3653. */
  3654. EditorUi.prototype.createImageDataUri = function(canvas, xml, format, dpi)
  3655. {
  3656. var data = canvas.toDataURL('image/' + format);
  3657. // Checks for valid output
  3658. if (data != null && data.length > 6)
  3659. {
  3660. if (xml != null)
  3661. {
  3662. data = Editor.writeGraphModelToPng(data, 'tEXt', 'mxfile', encodeURIComponent(xml));
  3663. }
  3664. if (dpi > 0)
  3665. {
  3666. data = Editor.writeGraphModelToPng(data, 'pHYs', 'dpi', dpi);
  3667. }
  3668. }
  3669. else
  3670. {
  3671. throw {message: mxResources.get('unknownError')};
  3672. }
  3673. return data;
  3674. };
  3675. /**
  3676. *
  3677. */
  3678. EditorUi.prototype.saveCanvas = function(canvas, xml, format, ignorePageName, dpi)
  3679. {
  3680. var ext = ((format == 'jpeg') ? 'jpg' : format);
  3681. var filename = this.getBaseFilename(ignorePageName) + '.' + ext;
  3682. var data = this.createImageDataUri(canvas, xml, format, dpi);
  3683. this.saveData(filename, ext, data.substring(data.lastIndexOf(',') + 1), 'image/' + format, true);
  3684. };
  3685. /**
  3686. * Returns true if files should be saved using <saveLocalFile>.
  3687. */
  3688. EditorUi.prototype.isLocalFileSave = function()
  3689. {
  3690. return ((urlParams['save'] != 'remote' && (mxClient.IS_IE ||
  3691. (typeof window.Blob !== 'undefined' && typeof window.URL !== 'undefined')) &&
  3692. document.documentMode != 9 && document.documentMode != 8 &&
  3693. document.documentMode != 7) ||
  3694. this.isOfflineApp() || mxClient.IS_IOS);
  3695. };
  3696. /**
  3697. * Translates this point by the given vector.
  3698. *
  3699. * @param {number} dx X-coordinate of the translation.
  3700. * @param {number} dy Y-coordinate of the translation.
  3701. */
  3702. EditorUi.prototype.showTextDialog = function(title, text)
  3703. {
  3704. var dlg = new TextareaDialog(this, title, text, null, null, mxResources.get('close'));
  3705. dlg.textarea.style.width = '600px';
  3706. dlg.textarea.style.height = '380px';
  3707. this.showDialog(dlg.container, 620, 460, true, true, null, null, null, null, true);
  3708. dlg.init();
  3709. document.execCommand('selectall', false, null);
  3710. };
  3711. /**
  3712. * Translates this point by the given vector.
  3713. *
  3714. * @param {number} dx X-coordinate of the translation.
  3715. * @param {number} dy Y-coordinate of the translation.
  3716. */
  3717. EditorUi.prototype.doSaveLocalFile = function(data, filename, mimeType, base64Encoded, format, defaultExtension)
  3718. {
  3719. // Appends .drawio extension for XML files with no extension
  3720. // to avoid the browser to automatically append .xml instead
  3721. if (mimeType == 'text/xml' &&
  3722. !/(\.drawio)$/i.test(filename) &&
  3723. !/(\.xml)$/i.test(filename) &&
  3724. !/(\.svg)$/i.test(filename) &&
  3725. !/(\.html)$/i.test(filename))
  3726. {
  3727. defaultExtension = (defaultExtension != null) ? defaultExtension : 'drawio';
  3728. filename = filename + '.' + defaultExtension;
  3729. }
  3730. // Newer versions of IE
  3731. if (window.Blob && navigator.msSaveOrOpenBlob)
  3732. {
  3733. var blob = (base64Encoded) ?
  3734. this.base64ToBlob(data, mimeType) :
  3735. new Blob([data], {type: mimeType})
  3736. navigator.msSaveOrOpenBlob(blob, filename);
  3737. }
  3738. // Older versions of IE (binary not supported)
  3739. else if (mxClient.IS_IE)
  3740. {
  3741. var win = window.open('about:blank', '_blank');
  3742. if (win == null)
  3743. {
  3744. mxUtils.popup(data, true);
  3745. }
  3746. else
  3747. {
  3748. win.document.write(data);
  3749. win.document.close();
  3750. win.document.execCommand('SaveAs', true, filename);
  3751. win.close();
  3752. }
  3753. }
  3754. else if (mxClient.IS_IOS && this.isOffline())
  3755. {
  3756. // Workaround for "WebKitBlobResource error 1" in mobile Safari
  3757. if (!navigator.standalone && mimeType != null && mimeType.substring(0, 6) == 'image/')
  3758. {
  3759. this.openInNewWindow(data, mimeType, base64Encoded);
  3760. }
  3761. else
  3762. {
  3763. this.showTextDialog(filename + ':', data);
  3764. }
  3765. }
  3766. else
  3767. {
  3768. var a = document.createElement('a');
  3769. // Workaround for mxXmlRequest.simulate no longer working in PaleMoon
  3770. // if this is used (ie PNG export broken after XML export in PaleMoon)
  3771. // and for "WebKitBlobResource error 1" for all browsers on iOS.
  3772. var useDownload = (navigator.userAgent == null ||
  3773. navigator.userAgent.indexOf("PaleMoon/") < 0) &&
  3774. typeof a.download !== 'undefined';
  3775. // Workaround for Chromium 65 cross-domain anchor download issue
  3776. if (mxClient.IS_GC && navigator.userAgent != null)
  3777. {
  3778. var raw = navigator.userAgent.match(/Chrom(e|ium)\/([0-9]+)\./)
  3779. var vers = raw ? parseInt(raw[2], 10) : false;
  3780. useDownload = vers == 65 ? false : useDownload;
  3781. }
  3782. if (useDownload || this.isOffline())
  3783. {
  3784. a.href = URL.createObjectURL((base64Encoded) ?
  3785. this.base64ToBlob(data, mimeType) :
  3786. new Blob([data], {type: mimeType}));
  3787. if (useDownload)
  3788. {
  3789. a.download = filename;
  3790. }
  3791. else
  3792. {
  3793. // Workaround for same window in Safari
  3794. a.setAttribute('target', '_blank');
  3795. }
  3796. document.body.appendChild(a);
  3797. try
  3798. {
  3799. window.setTimeout(function()
  3800. {
  3801. URL.revokeObjectURL(a.href);
  3802. }, 20000);
  3803. a.click();
  3804. a.parentNode.removeChild(a);
  3805. }
  3806. catch (e)
  3807. {
  3808. // ignore
  3809. }
  3810. }
  3811. else
  3812. {
  3813. var req = this.createEchoRequest(data, filename, mimeType, base64Encoded, format);
  3814. req.simulate(document, '_blank');
  3815. }
  3816. }
  3817. };
  3818. /**
  3819. * Translates this point by the given vector.
  3820. *
  3821. * @param {number} dx X-coordinate of the translation.
  3822. * @param {number} dy Y-coordinate of the translation.
  3823. */
  3824. EditorUi.prototype.createEchoRequest = function(data, filename, mimeType, base64Encoded, format, base64Response)
  3825. {
  3826. var param = (typeof(pako) === 'undefined' || true) ? 'xml=' + encodeURIComponent(data) :
  3827. 'data=' + encodeURIComponent(Graph.compress(data));
  3828. return new mxXmlRequest(SAVE_URL, param +
  3829. ((mimeType != null) ? '&mime=' + mimeType : '') +
  3830. ((format != null) ? '&format=' + format : '') +
  3831. ((base64Response != null) ? '&base64=' + base64Response : '') +
  3832. ((filename != null) ? '&filename=' + encodeURIComponent(filename) : '') +
  3833. ((base64Encoded) ? '&binary=1' : ''));
  3834. };
  3835. /**
  3836. * Translates this point by the given vector.
  3837. *
  3838. * @param {number} dx X-coordinate of the translation.
  3839. * @param {number} dy Y-coordinate of the translation.
  3840. */
  3841. EditorUi.prototype.base64ToBlob = function(base64Data, contentType)
  3842. {
  3843. contentType = contentType || '';
  3844. var sliceSize = 1024;
  3845. var byteCharacters = atob(base64Data);
  3846. var bytesLength = byteCharacters.length;
  3847. var slicesCount = Math.ceil(bytesLength / sliceSize);
  3848. var byteArrays = new Array(slicesCount);
  3849. for (var sliceIndex = 0; sliceIndex < slicesCount; ++sliceIndex)
  3850. {
  3851. var begin = sliceIndex * sliceSize;
  3852. var end = Math.min(begin + sliceSize, bytesLength);
  3853. var bytes = new Array(end - begin);
  3854. for (var offset = begin, i = 0 ; offset < end; ++i, ++offset)
  3855. {
  3856. bytes[i] = byteCharacters[offset].charCodeAt(0);
  3857. }
  3858. byteArrays[sliceIndex] = new Uint8Array(bytes);
  3859. }
  3860. return new Blob(byteArrays, {type: contentType});
  3861. };
  3862. /**
  3863. * Translates this point by the given vector.
  3864. *
  3865. * @param {number} dx X-coordinate of the translation.
  3866. * @param {number} dy Y-coordinate of the translation.
  3867. */
  3868. EditorUi.prototype.saveLocalFile = function(data, filename, mimeType, base64Encoded, format, allowBrowser, allowTab, defaultExtension)
  3869. {
  3870. allowBrowser = (allowBrowser != null) ? allowBrowser : false;
  3871. allowTab = (allowTab != null) ? allowTab : (format != 'vsdx') && (!mxClient.IS_IOS || !navigator.standalone);
  3872. var count = this.getServiceCount(allowBrowser);
  3873. if (isLocalStorage)
  3874. {
  3875. count++;
  3876. }
  3877. var rowLimit = (count <= 4) ? 2 : (count > 6 ? 4 : 3);
  3878. var dlg = new CreateDialog(this, filename, mxUtils.bind(this, function(newTitle, mode)
  3879. {
  3880. try
  3881. {
  3882. // Opens a new window
  3883. if (mode == '_blank')
  3884. {
  3885. if (mimeType != null && mimeType.substring(0, 6) == 'image/')
  3886. {
  3887. this.openInNewWindow(data, mimeType, base64Encoded);
  3888. }
  3889. else if (mimeType != null && mimeType.substring(0, 9) == 'text/html')
  3890. {
  3891. var dlg = new EmbedDialog(this, data);
  3892. this.showDialog(dlg.container, 440, 240, true, true);
  3893. dlg.init();
  3894. }
  3895. else
  3896. {
  3897. var win = window.open('about:blank');
  3898. if (win == null)
  3899. {
  3900. mxUtils.popup(data, true);
  3901. }
  3902. else
  3903. {
  3904. win.document.write('<pre>' + mxUtils.htmlEntities(data, false) + '</pre>');
  3905. win.document.close();
  3906. }
  3907. }
  3908. }
  3909. else if (mode == App.MODE_DEVICE || mode == 'download')
  3910. {
  3911. this.doSaveLocalFile(data, newTitle, mimeType, base64Encoded, null, defaultExtension);
  3912. }
  3913. else if (newTitle != null && newTitle.length > 0)
  3914. {
  3915. this.pickFolder(mode, mxUtils.bind(this, function(folderId)
  3916. {
  3917. try
  3918. {
  3919. this.exportFile(data, newTitle, mimeType, base64Encoded, mode, folderId);
  3920. }
  3921. catch (e)
  3922. {
  3923. this.handleError(e);
  3924. }
  3925. }));
  3926. }
  3927. }
  3928. catch (e)
  3929. {
  3930. this.handleError(e);
  3931. }
  3932. }), mxUtils.bind(this, function()
  3933. {
  3934. this.hideDialog();
  3935. }), mxResources.get('saveAs'), mxResources.get('download'), false, allowBrowser, allowTab,
  3936. null, count > 1, rowLimit, data, mimeType, base64Encoded);
  3937. var height = (this.isServices(count)) ? ((count > rowLimit) ? 390 : 270) : 160;
  3938. this.showDialog(dlg.container, 400, height, true, true);
  3939. dlg.init();
  3940. };
  3941. /**
  3942. *
  3943. */
  3944. EditorUi.prototype.openInNewWindow = function(data, mimeType, base64Encoded)
  3945. {
  3946. var win = window.open('about:blank');
  3947. if (win == null || win.document == null)
  3948. {
  3949. mxUtils.popup(data, true);
  3950. }
  3951. else
  3952. {
  3953. if (mimeType == 'image/svg+xml' && !mxClient.IS_SVG)
  3954. {
  3955. // KNOWN: Output is scaled in Chrome on macOS
  3956. win.document.write('<html><pre>' + mxUtils.htmlEntities(data, false) + '</pre></html>');
  3957. win.document.close();
  3958. }
  3959. else
  3960. {
  3961. if (mimeType == 'image/svg+xml')
  3962. {
  3963. win.document.write('<html>'+ data + '</html>');
  3964. }
  3965. else
  3966. {
  3967. var temp = (base64Encoded) ? data : btoa(unescape(encodeURIComponent(data)));
  3968. win.document.write('<html><img style="max-width:100%;" src="data:' +
  3969. mimeType + ';base64,' + temp + '"/></html>');
  3970. }
  3971. win.document.close();
  3972. }
  3973. }
  3974. };
  3975. var editoUiAddChromelessToolbarItems = EditorUi.prototype.addChromelessToolbarItems;
  3976. /**
  3977. * Creates a temporary graph instance for rendering off-screen content.
  3978. */
  3979. EditorUi.prototype.addChromelessToolbarItems = function(addButton)
  3980. {
  3981. if (this.isExportToCanvas())
  3982. {
  3983. this.exportDialog = null;
  3984. var exportButton = addButton(mxUtils.bind(this, function(evt)
  3985. {
  3986. var clickHandler = mxUtils.bind(this, function()
  3987. {
  3988. mxEvent.removeListener(this.editor.graph.container, 'click', clickHandler);
  3989. if (this.exportDialog != null)
  3990. {
  3991. this.exportDialog.parentNode.removeChild(this.exportDialog);
  3992. this.exportDialog = null;
  3993. }
  3994. });
  3995. if (this.exportDialog != null)
  3996. {
  3997. clickHandler.apply(this);
  3998. }
  3999. else
  4000. {
  4001. this.exportDialog = document.createElement('div');
  4002. var r = exportButton.getBoundingClientRect();
  4003. mxUtils.setPrefixedStyle(this.exportDialog.style, 'borderRadius', '5px');
  4004. this.exportDialog.style.position = 'fixed';
  4005. this.exportDialog.style.textAlign = 'center';
  4006. this.exportDialog.style.fontFamily = 'Helvetica,Arial';
  4007. this.exportDialog.style.backgroundColor = '#000000';
  4008. this.exportDialog.style.width = '50px';
  4009. this.exportDialog.style.height = '50px';
  4010. this.exportDialog.style.padding = '4px 2px 4px 2px';
  4011. this.exportDialog.style.color = '#ffffff';
  4012. mxUtils.setOpacity(this.exportDialog, 70);
  4013. this.exportDialog.style.left = r.left + 'px';
  4014. this.exportDialog.style.bottom = parseInt(this.chromelessToolbar.style.bottom) +
  4015. this.chromelessToolbar.offsetHeight + 4 + 'px';
  4016. // Puts the dialog on top of the container z-index
  4017. var style = mxUtils.getCurrentStyle(this.editor.graph.container);
  4018. this.exportDialog.style.zIndex = style.zIndex;
  4019. var spinner = new Spinner({
  4020. lines: 8, // The number of lines to draw
  4021. length: 6, // The length of each line
  4022. width: 5, // The line thickness
  4023. radius: 6, // The radius of the inner circle
  4024. rotate: 0, // The rotation offset
  4025. color: '#fff', // #rgb or #rrggbb
  4026. speed: 1.5, // Rounds per second
  4027. trail: 60, // Afterglow percentage
  4028. shadow: false, // Whether to render a shadow
  4029. hwaccel: false, // Whether to use hardware acceleration
  4030. top: '28px',
  4031. zIndex: 2e9 // The z-index (defaults to 2000000000)
  4032. });
  4033. spinner.spin(this.exportDialog);
  4034. this.editor.exportToCanvas(mxUtils.bind(this, function(canvas)
  4035. {
  4036. spinner.stop();
  4037. this.exportDialog.style.width = 'auto';
  4038. this.exportDialog.style.height = 'auto';
  4039. this.exportDialog.style.padding = '10px';
  4040. var data = this.createImageDataUri(canvas, null, 'png');
  4041. var img = document.createElement('img');
  4042. img.style.maxWidth = '140px';
  4043. img.style.maxHeight = '140px';
  4044. img.style.cursor = 'pointer';
  4045. img.style.backgroundColor = 'white';
  4046. img.setAttribute('title', mxResources.get('openInNewWindow'));
  4047. img.setAttribute('border', '0');
  4048. img.setAttribute('src', data);
  4049. this.exportDialog.appendChild(img);
  4050. mxEvent.addListener(img, 'click', mxUtils.bind(this, function()
  4051. {
  4052. this.openInNewWindow(data.substring(data.indexOf(',') + 1), 'image/png', true);
  4053. clickHandler.apply(this, arguments);
  4054. }));
  4055. }), null, this.thumbImageCache, null, mxUtils.bind(this, function(e)
  4056. {
  4057. this.spinner.stop();
  4058. this.handleError(e);
  4059. }), null, null, null, null, null, null, null, Editor.defaultBorder);
  4060. mxEvent.addListener(this.editor.graph.container, 'click', clickHandler);
  4061. document.body.appendChild(this.exportDialog);
  4062. }
  4063. mxEvent.consume(evt);
  4064. }), Editor.cameraLargeImage, mxResources.get('export'));
  4065. }
  4066. editoUiAddChromelessToolbarItems.apply(this, arguments);
  4067. };
  4068. /**
  4069. * Translates this point by the given vector.
  4070. *
  4071. * @param {number} dx X-coordinate of the translation.
  4072. * @param {number} dy Y-coordinate of the translation.
  4073. */
  4074. EditorUi.prototype.saveData = function(filename, format, data, mime, base64Encoded)
  4075. {
  4076. if (this.isLocalFileSave())
  4077. {
  4078. this.saveLocalFile(data, filename, mime, base64Encoded, format);
  4079. }
  4080. else
  4081. {
  4082. this.saveRequest(filename, format, mxUtils.bind(this, function(newTitle, base64)
  4083. {
  4084. return this.createEchoRequest(data, newTitle, mime, base64Encoded, format, base64);
  4085. }), data, base64Encoded, mime);
  4086. }
  4087. };
  4088. /**
  4089. * Translates this point by the given vector.
  4090. *
  4091. * Last 3 argument are optional and must only be used if the data can be stored as is on the client
  4092. * side without requiring a server roundtrip.
  4093. *
  4094. * @param {number} dx X-coordinate of the translation.
  4095. * @param {number} dy Y-coordinate of the translation.
  4096. */
  4097. EditorUi.prototype.saveRequest = function(filename, format, fn, data, base64Encoded, mimeType, allowTab)
  4098. {
  4099. allowTab = (allowTab != null) ? allowTab : !mxClient.IS_IOS || !navigator.standalone;
  4100. var count = this.getServiceCount(false);
  4101. if (isLocalStorage)
  4102. {
  4103. count++;
  4104. }
  4105. var rowLimit = (count <= 4) ? 2 : (count > 6 ? 4 : 3);
  4106. var dlg = new CreateDialog(this, filename, mxUtils.bind(this, function(newTitle, mode)
  4107. {
  4108. if (mode == '_blank' || newTitle != null && newTitle.length > 0)
  4109. {
  4110. var base64 = (mode == App.MODE_DEVICE || mode == 'download' || mode == null || mode == '_blank') ? '0' : '1';
  4111. var xhr = fn((mode == '_blank') ? null : newTitle, base64);
  4112. if (xhr != null)
  4113. {
  4114. if (mode == App.MODE_DEVICE || mode == 'download' || mode == '_blank')
  4115. {
  4116. xhr.simulate(document, '_blank');
  4117. }
  4118. else
  4119. {
  4120. this.pickFolder(mode, mxUtils.bind(this, function(folderId)
  4121. {
  4122. mimeType = (mimeType != null) ? mimeType : ((format == 'pdf') ?
  4123. 'application/pdf' : 'image/' + format);
  4124. // Workaround for no roundtrip required if data is available on client-side
  4125. // TODO: Refactor the saveData/saveRequest call chain for local data
  4126. if (data != null)
  4127. {
  4128. try
  4129. {
  4130. this.exportFile(data, newTitle, mimeType, true, mode, folderId);
  4131. }
  4132. catch (e)
  4133. {
  4134. this.handleError(e);
  4135. }
  4136. }
  4137. else if (this.spinner.spin(document.body, mxResources.get('saving')))
  4138. {
  4139. // LATER: Catch possible mixed content error
  4140. // see http://stackoverflow.com/questions/30646417/catching-mixed-content-error
  4141. xhr.send(mxUtils.bind(this, function()
  4142. {
  4143. this.spinner.stop();
  4144. if (xhr.getStatus() >= 200 && xhr.getStatus() <= 299)
  4145. {
  4146. try
  4147. {
  4148. this.exportFile(xhr.getText(), newTitle, mimeType, true, mode, folderId);
  4149. }
  4150. catch (e)
  4151. {
  4152. this.handleError(e);
  4153. }
  4154. }
  4155. else
  4156. {
  4157. this.handleError({message: mxResources.get('errorSavingFile')});
  4158. }
  4159. }), function(resp)
  4160. {
  4161. this.spinner.stop();
  4162. this.handleError(resp);
  4163. });
  4164. }
  4165. }));
  4166. }
  4167. }
  4168. }
  4169. }), mxUtils.bind(this, function()
  4170. {
  4171. this.hideDialog();
  4172. }), mxResources.get('saveAs'), mxResources.get('download'), false, false, allowTab,
  4173. null, count > 1, rowLimit, data, mimeType, base64Encoded);
  4174. var height = (this.isServices(count)) ? ((count > 4) ? 390 : 270) : 160;
  4175. this.showDialog(dlg.container, 380, height, true, true);
  4176. dlg.init();
  4177. };
  4178. /**
  4179. * Returns whether or not any services should be shown in dialogs
  4180. */
  4181. EditorUi.prototype.isServices = function(count)
  4182. {
  4183. var noServices = 1; //(mxClient.IS_IOS) ? 0 : 1;
  4184. return count != noServices;
  4185. };
  4186. /**
  4187. *
  4188. */
  4189. EditorUi.prototype.getEditBlankXml = function()
  4190. {
  4191. return this.getFileData(true);
  4192. };
  4193. /**
  4194. * Hook for subclassers.
  4195. */
  4196. EditorUi.prototype.exportFile = function(data, filename, mimeType, base64Encoded, mode, folderId)
  4197. {
  4198. // do nothing
  4199. };
  4200. /**
  4201. * Hook for subclassers.
  4202. */
  4203. EditorUi.prototype.pickFolder = function(mode, fn, enabled)
  4204. {
  4205. fn(null);
  4206. };
  4207. /**
  4208. *
  4209. */
  4210. EditorUi.prototype.exportSvg = function(scale, transparentBackground, ignoreSelection, addShadow,
  4211. editable, embedImages, border, noCrop, currentPage, linkTarget, keepTheme, exportType)
  4212. {
  4213. if (this.spinner.spin(document.body, mxResources.get('export')))
  4214. {
  4215. try
  4216. {
  4217. var selectionEmpty = this.editor.graph.isSelectionEmpty();
  4218. ignoreSelection = (ignoreSelection != null) ? ignoreSelection : selectionEmpty;
  4219. var bg = (transparentBackground) ? null : this.editor.graph.background;
  4220. if (bg == mxConstants.NONE)
  4221. {
  4222. bg = null;
  4223. }
  4224. // Handles special case where background is null but transparent is false
  4225. if (bg == null && transparentBackground == false)
  4226. {
  4227. bg = (keepTheme) ? this.editor.graph.defaultPageBackgroundColor : '#ffffff';
  4228. }
  4229. // Sets or disables alternate text for foreignObjects. Disabling is needed
  4230. // because PhantomJS seems to ignore switch statements and paint all text.
  4231. var svgRoot = this.editor.graph.getSvg(bg, scale, border, noCrop,
  4232. null, ignoreSelection, null, null, (linkTarget == 'blank') ? '_blank' :
  4233. ((linkTarget == 'self') ? '_top' : null), null, true, keepTheme,
  4234. exportType);
  4235. if (addShadow)
  4236. {
  4237. this.editor.graph.addSvgShadow(svgRoot);
  4238. }
  4239. var filename = this.getBaseFilename() + '.svg';
  4240. var doSave = mxUtils.bind(this, function(svgRoot)
  4241. {
  4242. this.spinner.stop();
  4243. if (editable)
  4244. {
  4245. svgRoot.setAttribute('content', this.getFileData(true, null, null, null, ignoreSelection,
  4246. currentPage, null, null, null, false));
  4247. }
  4248. var svg = '<?xml version="1.0" encoding="UTF-8"?>\n' +
  4249. '<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">\n' +
  4250. mxUtils.getXml(svgRoot);
  4251. if (this.isLocalFileSave() || svg.length <= MAX_REQUEST_SIZE)
  4252. {
  4253. this.saveData(filename, 'svg', svg, 'image/svg+xml');
  4254. }
  4255. else
  4256. {
  4257. this.handleError({message: mxResources.get('drawingTooLarge')}, mxResources.get('error'), mxUtils.bind(this, function()
  4258. {
  4259. mxUtils.popup(svg);
  4260. }));
  4261. }
  4262. });
  4263. // Adds CSS
  4264. this.editor.addFontCss(svgRoot);
  4265. if (this.editor.graph.mathEnabled)
  4266. {
  4267. this.editor.addMathCss(svgRoot);
  4268. }
  4269. if (embedImages)
  4270. {
  4271. // Caches images
  4272. if (this.thumbImageCache == null)
  4273. {
  4274. this.thumbImageCache = new Object();
  4275. }
  4276. this.editor.convertImages(svgRoot, doSave, this.thumbImageCache);
  4277. }
  4278. else
  4279. {
  4280. doSave(svgRoot);
  4281. }
  4282. }
  4283. catch (e)
  4284. {
  4285. this.handleError(e);
  4286. }
  4287. }
  4288. };
  4289. EditorUi.prototype.addRadiobox = function(div, radioGroupName, label, checked, disabled, disableNewline, visible)
  4290. {
  4291. return this.addCheckbox(div, label, checked, disabled, disableNewline, visible, true, radioGroupName);
  4292. }
  4293. /**
  4294. *
  4295. */
  4296. EditorUi.prototype.addCheckbox = function(div, label, checked, disabled, disableNewline, visible, asRadio, radioGroupName)
  4297. {
  4298. visible = (visible != null) ? visible : true;
  4299. var cb = document.createElement('input');
  4300. cb.style.marginRight = '8px';
  4301. cb.style.marginTop = '16px';
  4302. cb.setAttribute('type', asRadio? 'radio' : 'checkbox');
  4303. var id = 'geCheckbox-' + Editor.guid();
  4304. cb.id = id;
  4305. if (radioGroupName != null)
  4306. {
  4307. cb.setAttribute('name', radioGroupName);
  4308. }
  4309. if (checked)
  4310. {
  4311. cb.setAttribute('checked', 'checked');
  4312. cb.defaultChecked = true;
  4313. }
  4314. if (disabled)
  4315. {
  4316. cb.setAttribute('disabled', 'disabled');
  4317. }
  4318. if (visible)
  4319. {
  4320. div.appendChild(cb);
  4321. var lbl = document.createElement('label');
  4322. mxUtils.write(lbl, label);
  4323. lbl.setAttribute('for', id);
  4324. div.appendChild(lbl);
  4325. if (!disableNewline)
  4326. {
  4327. mxUtils.br(div);
  4328. }
  4329. }
  4330. return cb;
  4331. };
  4332. /**
  4333. *
  4334. */
  4335. EditorUi.prototype.addEditButton = function(div, lightbox)
  4336. {
  4337. var edit = this.addCheckbox(div, mxResources.get('edit') + ':', true, null, true);
  4338. edit.style.marginLeft = '24px';
  4339. var file = this.getCurrentFile();
  4340. var editUrl = '';
  4341. if (file != null && file.getMode() != App.MODE_DEVICE && file.getMode() != App.MODE_BROWSER)
  4342. {
  4343. editUrl = window.location.href;
  4344. }
  4345. var editSelect = document.createElement('select');
  4346. editSelect.style.width = '120px';
  4347. editSelect.style.marginLeft = '8px';
  4348. editSelect.style.marginRight = '10px';
  4349. editSelect.className = 'geBtn';
  4350. var blankOption = document.createElement('option');
  4351. blankOption.setAttribute('value', 'blank');
  4352. mxUtils.write(blankOption, mxResources.get('makeCopy'));
  4353. editSelect.appendChild(blankOption);
  4354. var customOption = document.createElement('option');
  4355. customOption.setAttribute('value', 'custom');
  4356. mxUtils.write(customOption, mxResources.get('custom') + '...');
  4357. editSelect.appendChild(customOption);
  4358. div.appendChild(editSelect);
  4359. mxEvent.addListener(editSelect, 'change', mxUtils.bind(this, function()
  4360. {
  4361. if (editSelect.value == 'custom')
  4362. {
  4363. var dlg2 = new FilenameDialog(this, editUrl, mxResources.get('ok'), function(value)
  4364. {
  4365. if (value != null)
  4366. {
  4367. editUrl = value;
  4368. }
  4369. else
  4370. {
  4371. editSelect.value = 'blank';
  4372. }
  4373. }, mxResources.get('url'), null, null, null, null, function()
  4374. {
  4375. editSelect.value = 'blank';
  4376. });
  4377. this.showDialog(dlg2.container, 300, 80, true, false);
  4378. dlg2.init();
  4379. }
  4380. }));
  4381. mxEvent.addListener(edit, 'change', mxUtils.bind(this, function()
  4382. {
  4383. if (edit.checked && (lightbox == null || lightbox.checked))
  4384. {
  4385. editSelect.removeAttribute('disabled');
  4386. }
  4387. else
  4388. {
  4389. editSelect.setAttribute('disabled', 'disabled');
  4390. }
  4391. }));
  4392. mxUtils.br(div);
  4393. return {
  4394. getLink: function()
  4395. {
  4396. return (edit.checked) ? ((editSelect.value === 'blank') ? '_blank' : editUrl) : null;
  4397. },
  4398. getEditInput: function()
  4399. {
  4400. return edit;
  4401. },
  4402. getEditSelect: function()
  4403. {
  4404. return editSelect;
  4405. }
  4406. };
  4407. }
  4408. /**
  4409. *
  4410. */
  4411. EditorUi.prototype.addLinkSection = function(div, showFrameOption)
  4412. {
  4413. mxUtils.write(div, mxResources.get('links') + ':');
  4414. var linkSelect = document.createElement('select');
  4415. linkSelect.style.width = '100px';
  4416. linkSelect.style.marginLeft = '8px';
  4417. linkSelect.style.marginRight = '10px';
  4418. linkSelect.className = 'geBtn';
  4419. var autoOption = document.createElement('option');
  4420. autoOption.setAttribute('value', 'auto');
  4421. mxUtils.write(autoOption, mxResources.get('automatic'));
  4422. linkSelect.appendChild(autoOption);
  4423. var blankOption = document.createElement('option');
  4424. blankOption.setAttribute('value', 'blank');
  4425. mxUtils.write(blankOption, mxResources.get('openInNewWindow'));
  4426. linkSelect.appendChild(blankOption);
  4427. var selfOption = document.createElement('option');
  4428. selfOption.setAttribute('value', 'self');
  4429. mxUtils.write(selfOption, mxResources.get('openInThisWindow'));
  4430. linkSelect.appendChild(selfOption);
  4431. if (showFrameOption)
  4432. {
  4433. var frameOption = document.createElement('option');
  4434. frameOption.setAttribute('value', 'frame');
  4435. mxUtils.write(frameOption, mxResources.get('openInThisWindow') +
  4436. ' (' + mxResources.get('iframe') + ')');
  4437. linkSelect.appendChild(frameOption);
  4438. }
  4439. div.appendChild(linkSelect);
  4440. mxUtils.write(div, mxResources.get('borderColor') + ':');
  4441. var linkColor = '#0000ff';
  4442. var linkButton = null;
  4443. function updateLinkColor()
  4444. {
  4445. linkButton.innerHTML = '<div style="width:100%;height:100%;box-sizing:border-box;' +
  4446. ((linkColor != null && linkColor != mxConstants.NONE) ?
  4447. 'border:1px solid black;background-color:' + linkColor :
  4448. 'background-position:center center;background-repeat:no-repeat;' +
  4449. 'background-image:url(\'' + Dialog.prototype.closeImage + '\')') + ';"></div>';
  4450. };
  4451. linkButton = mxUtils.button('', mxUtils.bind(this, function(evt)
  4452. {
  4453. this.pickColor(linkColor || 'none', function(color)
  4454. {
  4455. linkColor = color;
  4456. updateLinkColor();
  4457. });
  4458. mxEvent.consume(evt);
  4459. }));
  4460. updateLinkColor();
  4461. linkButton.style.padding = (mxClient.IS_FF) ? '4px 2px 4px 2px' : '4px';
  4462. linkButton.style.marginLeft = '4px';
  4463. linkButton.style.height = '22px';
  4464. linkButton.style.width = '22px';
  4465. linkButton.style.position = 'relative';
  4466. linkButton.style.top = (mxClient.IS_IE || mxClient.IS_IE11 || mxClient.IS_EDGE) ? '6px' : '1px';
  4467. linkButton.className = 'geColorBtn';
  4468. div.appendChild(linkButton);
  4469. mxUtils.br(div);
  4470. return {
  4471. getColor: function()
  4472. {
  4473. return linkColor;
  4474. },
  4475. getTarget: function()
  4476. {
  4477. return linkSelect.value;
  4478. },
  4479. focus: function()
  4480. {
  4481. linkSelect.focus();
  4482. }
  4483. };
  4484. }
  4485. /**
  4486. *
  4487. */
  4488. EditorUi.prototype.createUrlParameters = function(linkTarget, linkColor, allPages, lightbox, editLink, layers, params)
  4489. {
  4490. params = (params != null) ? params : [];
  4491. if (lightbox)
  4492. {
  4493. if (EditorUi.lightboxHost != 'https://viewer.diagrams.net' || urlParams['dev'] == '1')
  4494. {
  4495. params.push('lightbox=1');
  4496. }
  4497. if (linkTarget != 'auto')
  4498. {
  4499. params.push('target=' + linkTarget);
  4500. }
  4501. if (linkColor != null && linkColor != mxConstants.NONE)
  4502. {
  4503. params.push('highlight=' + ((linkColor.charAt(0) == '#') ?
  4504. linkColor.substring(1) : linkColor));
  4505. }
  4506. if (editLink != null && editLink.length > 0)
  4507. {
  4508. params.push('edit=' + encodeURIComponent(editLink));
  4509. }
  4510. if (layers)
  4511. {
  4512. params.push('layers=1');
  4513. }
  4514. if (this.editor.graph.foldingEnabled)
  4515. {
  4516. params.push('nav=1');
  4517. }
  4518. }
  4519. if (allPages && this.currentPage != null && this.pages != null &&
  4520. this.currentPage != this.pages[0])
  4521. {
  4522. params.push('page-id=' + this.currentPage.getId());
  4523. }
  4524. return params;
  4525. };
  4526. /**
  4527. *
  4528. */
  4529. EditorUi.prototype.createLink = function(linkTarget, linkColor, allPages, lightbox, editLink, layers, url, ignoreFile, params, useOpenParameter)
  4530. {
  4531. params = this.createUrlParameters(linkTarget, linkColor, allPages, lightbox, editLink, layers, params);
  4532. var file = this.getCurrentFile();
  4533. var addTitle = true;
  4534. var data = '';
  4535. if (url != null)
  4536. {
  4537. data = '#U' + encodeURIComponent(url);
  4538. }
  4539. else
  4540. {
  4541. var file = this.getCurrentFile();
  4542. // Fallback to non-public URL for Drive files
  4543. if (!ignoreFile && file != null && file.constructor == window.DriveFile)
  4544. {
  4545. data = '#' + file.getHash();
  4546. addTitle = false;
  4547. }
  4548. else
  4549. {
  4550. data = '#R' + encodeURIComponent((allPages) ?
  4551. this.getFileData(true, null, null, null, null, null, null, true, null, false) :
  4552. Graph.compress(mxUtils.getXml(this.editor.getGraphXml())))
  4553. }
  4554. }
  4555. if (addTitle && file != null && file.getTitle() != null && file.getTitle() != this.defaultFilename)
  4556. {
  4557. params.push('title=' + encodeURIComponent(file.getTitle()));
  4558. }
  4559. if (useOpenParameter && data.length > 1)
  4560. {
  4561. params.push('open=' + data.substring(1));
  4562. data = '';
  4563. }
  4564. return ((lightbox && urlParams['dev'] != '1') ? EditorUi.lightboxHost :
  4565. (((mxClient.IS_CHROMEAPP || EditorUi.isElectronApp ||
  4566. !(/.*\.draw\.io$/.test(window.location.hostname))) ?
  4567. EditorUi.drawHost : 'https://' + window.location.host))) + '/' +
  4568. ((params.length > 0) ? '?' + params.join('&') : '') + data;
  4569. };
  4570. /**
  4571. *
  4572. */
  4573. EditorUi.prototype.createHtml = function(publicUrl, zoomEnabled, initialZoom, linkTarget,
  4574. linkColor, fit, allPages, layers, lightbox, editLink, fn)
  4575. {
  4576. var s = this.getBasenames();
  4577. var data = {};
  4578. if (linkColor != '' && linkColor != mxConstants.NONE)
  4579. {
  4580. data.highlight = linkColor;
  4581. }
  4582. if (linkTarget !== 'auto')
  4583. {
  4584. data.target = linkTarget;
  4585. }
  4586. if (!lightbox)
  4587. {
  4588. data.lightbox = false;
  4589. }
  4590. data.nav = this.editor.graph.foldingEnabled;
  4591. var zoom = parseInt(initialZoom);
  4592. if (!isNaN(zoom) && zoom != 100)
  4593. {
  4594. data.zoom = zoom / 100;
  4595. }
  4596. var tb = [];
  4597. if (allPages)
  4598. {
  4599. tb.push('pages');
  4600. data.resize = true;
  4601. if (this.pages != null && this.currentPage != null)
  4602. {
  4603. data.page = mxUtils.indexOf(this.pages, this.currentPage);
  4604. }
  4605. }
  4606. if (zoomEnabled)
  4607. {
  4608. tb.push('zoom');
  4609. data.resize = true;
  4610. }
  4611. if (layers)
  4612. {
  4613. tb.push('layers');
  4614. }
  4615. if (tb.length > 0)
  4616. {
  4617. if (lightbox)
  4618. {
  4619. tb.push('lightbox');
  4620. }
  4621. data.toolbar = tb.join(' ');
  4622. }
  4623. if (editLink != null && editLink.length > 0)
  4624. {
  4625. data.edit = editLink;
  4626. }
  4627. if (publicUrl != null)
  4628. {
  4629. data.url = publicUrl;
  4630. }
  4631. else
  4632. {
  4633. data.xml = this.getFileData(true, null, null, null, null, !allPages);
  4634. }
  4635. var value = '<div class="mxgraph" style="' +
  4636. ((fit) ? 'max-width:100%;' : '') +
  4637. ((tb != '') ? 'border:1px solid transparent;' : '') +
  4638. '" data-mxgraph="' + mxUtils.htmlEntities(JSON.stringify(data)) + '"></div>';
  4639. var fetchParam = (publicUrl != null) ? '&fetch=' + encodeURIComponent(publicUrl) : '';
  4640. var s2 = (fetchParam.length > 0) ? (((urlParams['dev'] == '1') ?
  4641. 'https://test.draw.io/embed2.js?dev=1' : EditorUi.lightboxHost + '/embed2.js?')) + fetchParam :
  4642. (((urlParams['dev'] == '1') ? 'https://test.draw.io/js/viewer-static.min.js' :
  4643. window.VIEWER_URL ? window.VIEWER_URL : EditorUi.lightboxHost + '/js/viewer-static.min.js'));
  4644. var src = '<script type="text/javascript" src="' + s2 + '"></script>';
  4645. fn(value, src);
  4646. };
  4647. /**
  4648. *
  4649. */
  4650. EditorUi.prototype.showHtmlDialog = function(btnLabel, helpLink, publicUrl, fn)
  4651. {
  4652. var div = document.createElement('div');
  4653. div.style.whiteSpace = 'nowrap';
  4654. var graph = this.editor.graph;
  4655. var hd = document.createElement('h3');
  4656. mxUtils.write(hd, mxResources.get('html'));
  4657. hd.style.cssText = 'width:100%;text-align:center;margin-top:0px;margin-bottom:12px';
  4658. div.appendChild(hd);
  4659. var radioSection = document.createElement('div');
  4660. radioSection.style.cssText = 'border-bottom:1px solid lightGray;padding-bottom:8px;margin-bottom:12px;';
  4661. var publicUrlRadio = document.createElement('input');
  4662. publicUrlRadio.style.cssText = 'margin-right:8px;margin-top:8px;margin-bottom:8px;';
  4663. publicUrlRadio.setAttribute('value', 'url');
  4664. publicUrlRadio.setAttribute('type', 'radio');
  4665. publicUrlRadio.setAttribute('name', 'type-embedhtmldialog');
  4666. var copyRadio = publicUrlRadio.cloneNode(true);
  4667. copyRadio.setAttribute('value', 'copy');
  4668. radioSection.appendChild(copyRadio);
  4669. var span = document.createElement('span');
  4670. mxUtils.write(span, mxResources.get('includeCopyOfMyDiagram'));
  4671. radioSection.appendChild(span);
  4672. mxUtils.br(radioSection);
  4673. radioSection.appendChild(publicUrlRadio);
  4674. var span = document.createElement('span');
  4675. mxUtils.write(span, mxResources.get('publicDiagramUrl'));
  4676. radioSection.appendChild(span);
  4677. var file = this.getCurrentFile();
  4678. if (publicUrl == null && file != null && file.constructor == window.DriveFile)
  4679. {
  4680. var testLink = document.createElement('a');
  4681. testLink.style.paddingLeft = '12px';
  4682. testLink.style.color = 'gray';
  4683. testLink.style.cursor = 'pointer';
  4684. mxUtils.write(testLink, mxResources.get('share'));
  4685. radioSection.appendChild(testLink);
  4686. mxEvent.addListener(testLink, 'click', mxUtils.bind(this, function()
  4687. {
  4688. this.hideDialog();
  4689. this.drive.showPermissions(file.getId());
  4690. }));
  4691. }
  4692. copyRadio.setAttribute('checked', 'checked');
  4693. if (publicUrl == null)
  4694. {
  4695. publicUrlRadio.setAttribute('disabled', 'disabled');
  4696. }
  4697. div.appendChild(radioSection);
  4698. var linkSection = this.addLinkSection(div);
  4699. var zoom = this.addCheckbox(div, mxResources.get('zoom'), true, null, true);
  4700. mxUtils.write(div, ':');
  4701. var zoomInput = document.createElement('input');
  4702. zoomInput.setAttribute('type', 'text');
  4703. zoomInput.style.marginRight = '16px';
  4704. zoomInput.style.width = '60px';
  4705. zoomInput.style.marginLeft = '4px';
  4706. zoomInput.style.marginRight = '12px';
  4707. zoomInput.value = '100%';
  4708. div.appendChild(zoomInput);
  4709. var fit = this.addCheckbox(div, mxResources.get('fit'), true);
  4710. var hasPages = this.pages != null && this.pages.length > 1;
  4711. var allPages = allPages = this.addCheckbox(div, mxResources.get('allPages'), hasPages, !hasPages);
  4712. var layers = this.addCheckbox(div, mxResources.get('layers'), true);
  4713. var lightbox = this.addCheckbox(div, mxResources.get('lightbox'), true);
  4714. var editSection = this.addEditButton(div, lightbox);
  4715. var edit = editSection.getEditInput();
  4716. edit.style.marginBottom = '16px';
  4717. mxEvent.addListener(lightbox, 'change', function()
  4718. {
  4719. if (lightbox.checked)
  4720. {
  4721. edit.removeAttribute('disabled');
  4722. }
  4723. else
  4724. {
  4725. edit.setAttribute('disabled', 'disabled');
  4726. }
  4727. if (edit.checked && lightbox.checked)
  4728. {
  4729. editSection.getEditSelect().removeAttribute('disabled');
  4730. }
  4731. else
  4732. {
  4733. editSection.getEditSelect().setAttribute('disabled', 'disabled');
  4734. }
  4735. });
  4736. var dlg = new CustomDialog(this, div, mxUtils.bind(this, function()
  4737. {
  4738. fn((publicUrlRadio.checked) ? publicUrl : null, zoom.checked, zoomInput.value, linkSection.getTarget(),
  4739. linkSection.getColor(), fit.checked, allPages.checked, layers.checked, lightbox.checked,
  4740. editSection.getLink());
  4741. }), null, btnLabel, helpLink);
  4742. this.showDialog(dlg.container, 340, 384, true, true);
  4743. copyRadio.focus();
  4744. };
  4745. /**
  4746. *
  4747. */
  4748. EditorUi.prototype.showPublishLinkDialog = function(title, hideShare, width, height, fn, showFrameOption)
  4749. {
  4750. var div = document.createElement('div');
  4751. div.style.whiteSpace = 'nowrap';
  4752. var graph = this.editor.graph;
  4753. var hd = document.createElement('h3');
  4754. mxUtils.write(hd, title || mxResources.get('link'));
  4755. hd.style.cssText = 'width:100%;text-align:center;margin-top:0px;margin-bottom:12px';
  4756. div.appendChild(hd);
  4757. var file = this.getCurrentFile();
  4758. var helpLink = 'https://www.diagrams.net/doc/faq/publish-diagram-as-link';
  4759. var dy = 0;
  4760. if (file != null && file.constructor == window.DriveFile && !hideShare)
  4761. {
  4762. dy = 80;
  4763. helpLink = 'https://www.diagrams.net/doc/faq/google-drive-publicly-publish-diagram';
  4764. var hintSection = document.createElement('div');
  4765. hintSection.style.cssText = 'border-bottom:1px solid lightGray;padding-bottom:14px;padding-top:6px;margin-bottom:14px;text-align:center;';
  4766. var text = document.createElement('div');
  4767. text.style.whiteSpace = 'normal';
  4768. mxUtils.write(text, mxResources.get('linkAccountRequired'));
  4769. hintSection.appendChild(text);
  4770. var shareBtn = mxUtils.button(mxResources.get('share'), mxUtils.bind(this, function()
  4771. {
  4772. this.drive.showPermissions(file.getId());
  4773. }));
  4774. shareBtn.style.marginTop = '12px';
  4775. shareBtn.className = 'geBtn';
  4776. hintSection.appendChild(shareBtn);
  4777. div.appendChild(hintSection);
  4778. var testLink = document.createElement('a');
  4779. testLink.style.paddingLeft = '12px';
  4780. testLink.style.color = 'gray';
  4781. testLink.style.fontSize = '11px';
  4782. testLink.style.cursor = 'pointer';
  4783. mxUtils.write(testLink, mxResources.get('check'));
  4784. hintSection.appendChild(testLink);
  4785. mxEvent.addListener(testLink, 'click', mxUtils.bind(this, function()
  4786. {
  4787. if (this.spinner.spin(document.body, mxResources.get('loading')))
  4788. {
  4789. this.getPublicUrl(this.getCurrentFile(), mxUtils.bind(this, function(url)
  4790. {
  4791. this.spinner.stop();
  4792. var dlg = new ErrorDialog(this, null, mxResources.get((url != null) ?
  4793. 'diagramIsPublic' : 'diagramIsNotPublic'), mxResources.get('ok'));
  4794. this.showDialog(dlg.container, 300, 80, true, false);
  4795. dlg.init();
  4796. }));
  4797. }
  4798. }));
  4799. }
  4800. var widthInput = null;
  4801. var heightInput = null;
  4802. if (width != null || height != null)
  4803. {
  4804. dy += 30;
  4805. mxUtils.write(div, mxResources.get('width') + ':');
  4806. widthInput = document.createElement('input');
  4807. widthInput.setAttribute('type', 'text');
  4808. widthInput.style.marginRight = '16px';
  4809. widthInput.style.width = '50px';
  4810. widthInput.style.marginLeft = '6px';
  4811. widthInput.style.marginRight = '16px';
  4812. widthInput.style.marginBottom = '10px';
  4813. widthInput.value = '100%';
  4814. div.appendChild(widthInput);
  4815. mxUtils.write(div, mxResources.get('height') + ':');
  4816. heightInput = document.createElement('input');
  4817. heightInput.setAttribute('type', 'text');
  4818. heightInput.style.width = '50px';
  4819. heightInput.style.marginLeft = '6px';
  4820. heightInput.style.marginBottom = '10px';
  4821. heightInput.value = height + 'px';
  4822. div.appendChild(heightInput);
  4823. mxUtils.br(div);
  4824. }
  4825. var linkSection = this.addLinkSection(div, showFrameOption);
  4826. var hasPages = this.pages != null && this.pages.length > 1;
  4827. var allPages = null;
  4828. if (file == null || file.constructor != window.DriveFile || hideShare)
  4829. {
  4830. allPages = this.addCheckbox(div, mxResources.get('allPages'), hasPages, !hasPages);
  4831. }
  4832. var lightbox = this.addCheckbox(div, mxResources.get('lightbox'), true, null, null, !showFrameOption);
  4833. var editSection = this.addEditButton(div, lightbox);
  4834. var edit = editSection.getEditInput();
  4835. // Cannot disable lightbox in iframes
  4836. if (showFrameOption)
  4837. {
  4838. edit.style.marginLeft = lightbox.style.marginLeft;
  4839. lightbox.style.display = 'none';
  4840. dy -= 30;
  4841. }
  4842. var layers = this.addCheckbox(div, mxResources.get('layers'), true);
  4843. layers.style.marginLeft = edit.style.marginLeft;
  4844. layers.style.marginBottom = '16px';
  4845. layers.style.marginTop = '8px';
  4846. mxEvent.addListener(lightbox, 'change', function()
  4847. {
  4848. if (lightbox.checked)
  4849. {
  4850. layers.removeAttribute('disabled');
  4851. edit.removeAttribute('disabled');
  4852. }
  4853. else
  4854. {
  4855. layers.setAttribute('disabled', 'disabled');
  4856. edit.setAttribute('disabled', 'disabled');
  4857. }
  4858. if (edit.checked && lightbox.checked)
  4859. {
  4860. editSection.getEditSelect().removeAttribute('disabled');
  4861. }
  4862. else
  4863. {
  4864. editSection.getEditSelect().setAttribute('disabled', 'disabled');
  4865. }
  4866. });
  4867. var dlg = new CustomDialog(this, div, mxUtils.bind(this, function()
  4868. {
  4869. fn(linkSection.getTarget(), linkSection.getColor(),
  4870. (allPages == null) ? true : allPages.checked,
  4871. lightbox.checked, editSection.getLink(),
  4872. layers.checked, (widthInput != null) ? widthInput.value : null,
  4873. (heightInput != null) ? heightInput.value : null);
  4874. }), null, mxResources.get('create'), helpLink);
  4875. this.showDialog(dlg.container, 340, 254 + dy, true, true);
  4876. if (widthInput != null)
  4877. {
  4878. widthInput.focus();
  4879. if (mxClient.IS_GC || mxClient.IS_FF || document.documentMode >= 5)
  4880. {
  4881. widthInput.select();
  4882. }
  4883. else
  4884. {
  4885. document.execCommand('selectAll', false, null);
  4886. }
  4887. }
  4888. else
  4889. {
  4890. linkSection.focus();
  4891. }
  4892. };
  4893. /**
  4894. *
  4895. */
  4896. EditorUi.prototype.showRemoteExportDialog = function(btnLabel, helpLink, callback, hideInclude, showZoomBorder)
  4897. {
  4898. var div = document.createElement('div');
  4899. div.style.whiteSpace = 'nowrap';
  4900. var hd = document.createElement('h3');
  4901. mxUtils.write(hd, mxResources.get('image'));
  4902. hd.style.cssText = 'width:100%;text-align:center;margin-top:0px;margin-bottom:' + (showZoomBorder? '10' : '4') +'px';
  4903. div.appendChild(hd);
  4904. if (showZoomBorder)
  4905. {
  4906. mxUtils.write(div, mxResources.get('zoom') + ':');
  4907. var zoomInput = document.createElement('input');
  4908. zoomInput.setAttribute('type', 'text');
  4909. zoomInput.style.marginRight = '16px';
  4910. zoomInput.style.width = '60px';
  4911. zoomInput.style.marginLeft = '4px';
  4912. zoomInput.style.marginRight = '12px';
  4913. zoomInput.value = this.lastExportZoom || '100%';
  4914. div.appendChild(zoomInput);
  4915. mxUtils.write(div, mxResources.get('borderWidth') + ':');
  4916. var borderInput = document.createElement('input');
  4917. borderInput.setAttribute('type', 'text');
  4918. borderInput.style.marginRight = '16px';
  4919. borderInput.style.width = '60px';
  4920. borderInput.style.marginLeft = '4px';
  4921. borderInput.value = this.lastExportBorder || '0';
  4922. div.appendChild(borderInput);
  4923. mxUtils.br(div);
  4924. }
  4925. var selection = this.addCheckbox(div, mxResources.get('selectionOnly'), false,
  4926. this.editor.graph.isSelectionEmpty());
  4927. var include = (hideInclude) ? null : this.addCheckbox(div, mxResources.get('includeCopyOfMyDiagram'), true);
  4928. var graph = this.editor.graph;
  4929. var transparent = (hideInclude) ? null : this.addCheckbox(div, mxResources.get('transparentBackground'),
  4930. graph.background == mxConstants.NONE || graph.background == null);
  4931. if (transparent != null)
  4932. {
  4933. transparent.style.marginBottom = '16px';
  4934. }
  4935. var dlg = new CustomDialog(this, div, mxUtils.bind(this, function()
  4936. {
  4937. var scale = parseInt(zoomInput.value) / 100 || 1;
  4938. var border = parseInt(borderInput.value) || 0;
  4939. callback(!selection.checked, (include != null) ? include.checked : false,
  4940. (transparent != null) ? transparent.checked : false, scale, border);
  4941. }), null, btnLabel, helpLink);
  4942. this.showDialog(dlg.container, 300, (showZoomBorder? 25 : 0) + (hideInclude ? 125 : 210), true, true);
  4943. };
  4944. /**
  4945. *
  4946. */
  4947. EditorUi.prototype.showExportDialog = function(title, embedOption, btnLabel, helpLink, callback,
  4948. cropOption, defaultInclude, format, exportOption)
  4949. {
  4950. defaultInclude = (defaultInclude != null) ? defaultInclude : true;
  4951. var div = document.createElement('div');
  4952. div.style.whiteSpace = 'nowrap';
  4953. var graph = this.editor.graph;
  4954. var height = (format == 'jpeg') ? 196 : 300;
  4955. var hd = document.createElement('h3');
  4956. mxUtils.write(hd, title);
  4957. hd.style.cssText = 'width:100%;text-align:center;margin-top:0px;margin-bottom:10px';
  4958. div.appendChild(hd);
  4959. mxUtils.write(div, mxResources.get('zoom') + ':');
  4960. var zoomInput = document.createElement('input');
  4961. zoomInput.setAttribute('type', 'text');
  4962. zoomInput.style.marginRight = '16px';
  4963. zoomInput.style.width = '60px';
  4964. zoomInput.style.marginLeft = '4px';
  4965. zoomInput.style.marginRight = '12px';
  4966. zoomInput.value = this.lastExportZoom || '100%';
  4967. div.appendChild(zoomInput);
  4968. mxUtils.write(div, mxResources.get('borderWidth') + ':');
  4969. var borderInput = document.createElement('input');
  4970. borderInput.setAttribute('type', 'text');
  4971. borderInput.style.marginRight = '16px';
  4972. borderInput.style.width = '60px';
  4973. borderInput.style.marginLeft = '4px';
  4974. borderInput.value = this.lastExportBorder || '0';
  4975. div.appendChild(borderInput);
  4976. mxUtils.br(div);
  4977. var selection = this.addCheckbox(div, mxResources.get('selectionOnly'),
  4978. false, graph.isSelectionEmpty());
  4979. var cb6 = document.createElement('input');
  4980. cb6.style.marginTop = '16px';
  4981. cb6.style.marginRight = '8px';
  4982. cb6.style.marginLeft = '24px';
  4983. cb6.setAttribute('disabled', 'disabled');
  4984. cb6.setAttribute('type', 'checkbox');
  4985. var exportSelect = document.createElement('select');
  4986. exportSelect.style.marginTop = '16px';
  4987. exportSelect.style.marginLeft = '8px';
  4988. var sizes = ['selectionOnly', 'diagram', 'page'];
  4989. for (var i = 0; i < sizes.length; i++)
  4990. {
  4991. if (!graph.isSelectionEmpty() || sizes[i] != 'selectionOnly')
  4992. {
  4993. var opt = document.createElement('option');
  4994. mxUtils.write(opt, mxResources.get(sizes[i]));
  4995. opt.setAttribute('value', sizes[i]);
  4996. exportSelect.appendChild(opt);
  4997. }
  4998. }
  4999. if (exportOption)
  5000. {
  5001. mxUtils.write(div, mxResources.get('size') + ':');
  5002. div.appendChild(exportSelect);
  5003. mxUtils.br(div);
  5004. height += 26;
  5005. mxEvent.addListener(exportSelect, 'change', function()
  5006. {
  5007. if (exportSelect.value == 'selectionOnly')
  5008. {
  5009. selection.checked = true;
  5010. }
  5011. });
  5012. }
  5013. else if (cropOption)
  5014. {
  5015. div.appendChild(cb6);
  5016. mxUtils.write(div, mxResources.get('crop'));
  5017. mxUtils.br(div);
  5018. height += 26;
  5019. mxEvent.addListener(selection, 'change', function()
  5020. {
  5021. if (selection.checked)
  5022. {
  5023. cb6.removeAttribute('disabled');
  5024. }
  5025. else
  5026. {
  5027. cb6.setAttribute('disabled', 'disabled');
  5028. }
  5029. });
  5030. }
  5031. if (graph.isSelectionEmpty())
  5032. {
  5033. if (exportOption)
  5034. {
  5035. selection.style.display = 'none';
  5036. selection.nextSibling.style.display = 'none';
  5037. selection.nextSibling.nextSibling.style.display = 'none';
  5038. height -= 26;
  5039. }
  5040. }
  5041. else
  5042. {
  5043. exportSelect.value = 'diagram';
  5044. cb6.setAttribute('checked', 'checked');
  5045. cb6.defaultChecked = true;
  5046. mxEvent.addListener(selection, 'change', function()
  5047. {
  5048. if (selection.checked)
  5049. {
  5050. exportSelect.value = 'selectionOnly';
  5051. }
  5052. else
  5053. {
  5054. exportSelect.value = 'diagram';
  5055. }
  5056. });
  5057. }
  5058. var defaultTransparent = false; /*graph.background == mxConstants.NONE || graph.background == null*/;
  5059. var transparent = this.addCheckbox(div, mxResources.get('transparentBackground'),
  5060. defaultTransparent, null, null, format != 'jpeg');
  5061. var keepTheme = null;
  5062. if (Editor.isDarkMode())
  5063. {
  5064. keepTheme = this.addCheckbox(div, mxResources.get('dark'), true);
  5065. height += 26;
  5066. }
  5067. var shadow = this.addCheckbox(div, mxResources.get('shadow'), graph.shadowVisible);
  5068. var cb5 = document.createElement('input');
  5069. cb5.style.marginTop = '16px';
  5070. cb5.style.marginRight = '8px';
  5071. cb5.setAttribute('type', 'checkbox');
  5072. if (this.isOffline() || !this.canvasSupported)
  5073. {
  5074. cb5.setAttribute('disabled', 'disabled');
  5075. }
  5076. if (embedOption)
  5077. {
  5078. div.appendChild(cb5);
  5079. mxUtils.write(div, mxResources.get('embedImages'));
  5080. mxUtils.br(div);
  5081. height += 26;
  5082. }
  5083. var grid = null;
  5084. if (format == 'png' || format == 'jpeg')
  5085. {
  5086. grid = this.addCheckbox(div, mxResources.get('grid'), false, this.isOffline() || !this.canvasSupported, false, true);
  5087. height += 26;
  5088. }
  5089. var include = this.addCheckbox(div, mxResources.get('includeCopyOfMyDiagram'), defaultInclude, null, null, format != 'jpeg');
  5090. include.style.marginBottom = '16px';
  5091. var linkSelect = document.createElement('select');
  5092. linkSelect.style.maxWidth = '260px';
  5093. linkSelect.style.marginLeft = '8px';
  5094. linkSelect.style.marginRight = '10px';
  5095. linkSelect.className = 'geBtn';
  5096. var autoOption = document.createElement('option');
  5097. autoOption.setAttribute('value', 'auto');
  5098. mxUtils.write(autoOption, mxResources.get('automatic'));
  5099. linkSelect.appendChild(autoOption);
  5100. var blankOption = document.createElement('option');
  5101. blankOption.setAttribute('value', 'blank');
  5102. mxUtils.write(blankOption, mxResources.get('openInNewWindow'));
  5103. linkSelect.appendChild(blankOption);
  5104. var selfOption = document.createElement('option');
  5105. selfOption.setAttribute('value', 'self');
  5106. mxUtils.write(selfOption, mxResources.get('openInThisWindow'));
  5107. linkSelect.appendChild(selfOption);
  5108. if (format == 'svg')
  5109. {
  5110. mxUtils.write(div, mxResources.get('links') + ':');
  5111. div.appendChild(linkSelect);
  5112. mxUtils.br(div);
  5113. mxUtils.br(div);
  5114. height += 26;
  5115. }
  5116. var dlg = new CustomDialog(this, div, mxUtils.bind(this, function()
  5117. {
  5118. this.lastExportBorder = borderInput.value;
  5119. this.lastExportZoom = zoomInput.value;
  5120. callback(zoomInput.value, transparent.checked, !selection.checked, shadow.checked,
  5121. include.checked, cb5.checked, borderInput.value, cb6.checked, false,
  5122. linkSelect.value, (grid != null) ? grid.checked : null, (keepTheme != null) ?
  5123. keepTheme.checked : null, exportSelect.value);
  5124. }), null, btnLabel, helpLink);
  5125. this.showDialog(dlg.container, 340, height, true, true, null, null, null, null, true);
  5126. zoomInput.focus();
  5127. if (mxClient.IS_GC || mxClient.IS_FF || document.documentMode >= 5)
  5128. {
  5129. zoomInput.select();
  5130. }
  5131. else
  5132. {
  5133. document.execCommand('selectAll', false, null);
  5134. }
  5135. };
  5136. /**
  5137. *
  5138. */
  5139. EditorUi.prototype.showEmbedImageDialog = function(fn, title, imageLabel, shadowEnabled, helpLink)
  5140. {
  5141. var div = document.createElement('div');
  5142. div.style.whiteSpace = 'nowrap';
  5143. var graph = this.editor.graph;
  5144. if (title != null)
  5145. {
  5146. var hd = document.createElement('h3');
  5147. mxUtils.write(hd, title);
  5148. hd.style.cssText = 'width:100%;text-align:center;margin-top:0px;margin-bottom:4px';
  5149. div.appendChild(hd);
  5150. }
  5151. var fit = this.addCheckbox(div, mxResources.get('fit'), true);
  5152. var shadow = this.addCheckbox(div, mxResources.get('shadow'),
  5153. graph.shadowVisible && shadowEnabled, !shadowEnabled);
  5154. var image = this.addCheckbox(div, imageLabel);
  5155. var lightbox = this.addCheckbox(div, mxResources.get('lightbox'), true);
  5156. var editSection = this.addEditButton(div, lightbox);
  5157. var edit = editSection.getEditInput();
  5158. var hasLayers = graph.model.getChildCount(graph.model.getRoot()) > 1;
  5159. var layers = this.addCheckbox(div, mxResources.get('layers'), hasLayers, !hasLayers);
  5160. layers.style.marginLeft = edit.style.marginLeft;
  5161. layers.style.marginBottom = '12px';
  5162. layers.style.marginTop = '8px';
  5163. mxEvent.addListener(lightbox, 'change', function()
  5164. {
  5165. if (lightbox.checked)
  5166. {
  5167. if (hasLayers)
  5168. {
  5169. layers.removeAttribute('disabled');
  5170. }
  5171. edit.removeAttribute('disabled');
  5172. }
  5173. else
  5174. {
  5175. layers.setAttribute('disabled', 'disabled');
  5176. edit.setAttribute('disabled', 'disabled');
  5177. }
  5178. if (edit.checked && lightbox.checked)
  5179. {
  5180. editSection.getEditSelect().removeAttribute('disabled');
  5181. }
  5182. else
  5183. {
  5184. editSection.getEditSelect().setAttribute('disabled', 'disabled');
  5185. }
  5186. });
  5187. var dlg = new CustomDialog(this, div, mxUtils.bind(this, function()
  5188. {
  5189. fn(fit.checked, shadow.checked, image.checked, lightbox.checked,
  5190. editSection.getLink(), layers.checked);
  5191. }), null, mxResources.get('embed'), helpLink);
  5192. this.showDialog(dlg.container, 280, 280, true, true);
  5193. };
  5194. /**
  5195. *
  5196. */
  5197. EditorUi.prototype.createEmbedImage = function(fit, shadow, retina, lightbox, edit, layers, fn, err)
  5198. {
  5199. var bounds = this.editor.graph.getGraphBounds();
  5200. var page = this.getSelectedPageIndex();
  5201. function doUpdate(dataUri)
  5202. {
  5203. var onclick = ' ';
  5204. var css = '';
  5205. // Adds double click handling
  5206. if (lightbox)
  5207. {
  5208. // KNOWN: Message passing does not seem to work in IE11
  5209. 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(" +
  5210. "img.getAttribute('src')),'*');window.removeEventListener('message',r);}};window.addEventListener('message',r);img.wnd=window.open('" + EditorUi.lightboxHost + "/?client=1" +
  5211. ((page != null) ? ("&page=" + page) : "") +
  5212. ((edit) ? "&edit=_blank" : "") +
  5213. ((layers) ? '&layers=1' : '') + "');}})(this);\"";
  5214. css += 'cursor:pointer;';
  5215. }
  5216. if (fit)
  5217. {
  5218. css += 'max-width:100%;';
  5219. }
  5220. var atts = '';
  5221. if (retina)
  5222. {
  5223. atts = ' width="' + Math.round(bounds.width) + '" height="' + Math.round(bounds.height) + '"';
  5224. }
  5225. fn('<img src="' + dataUri + '"' + atts + ((css != '') ? ' style="' + css + '"' : '') + onclick + '/>');
  5226. };
  5227. if (this.isExportToCanvas())
  5228. {
  5229. this.editor.exportToCanvas(mxUtils.bind(this, function(canvas)
  5230. {
  5231. var xml = (lightbox) ? this.getFileData(true) : null;
  5232. var data = this.createImageDataUri(canvas, xml, 'png');
  5233. doUpdate(data);
  5234. }), null, null, null, mxUtils.bind(this, function(e)
  5235. {
  5236. err({message: mxResources.get('unknownError')});
  5237. }), null, true, (retina) ? 2 : 1, null, shadow, null, null, Editor.defaultBorder);
  5238. }
  5239. else
  5240. {
  5241. var data = this.getFileData(true);
  5242. if (bounds.width * bounds.height <= MAX_AREA && data.length <= MAX_REQUEST_SIZE)
  5243. {
  5244. var size = '';
  5245. if (retina)
  5246. {
  5247. size = '&w=' + Math.round(2 * bounds.width) +
  5248. '&h=' + Math.round(2 * bounds.height);
  5249. }
  5250. var embed = (lightbox) ? '1' : '0';
  5251. var req = new mxXmlRequest(EXPORT_URL, 'format=png' +
  5252. '&base64=1&embedXml=' + embed + size + '&xml=' +
  5253. encodeURIComponent(data));
  5254. // LATER: Updates on each change, add a delay
  5255. req.send(mxUtils.bind(this, function()
  5256. {
  5257. if (req.getStatus() >= 200 && req.getStatus() <= 299)
  5258. {
  5259. // Fixes possible "incorrect function" for select() on
  5260. // DOM node which is no longer in document with IE11
  5261. doUpdate('data:image/png;base64,' + req.getText());
  5262. }
  5263. else
  5264. {
  5265. err({message: mxResources.get('unknownError')});
  5266. }
  5267. }));
  5268. }
  5269. else
  5270. {
  5271. err({message: mxResources.get('drawingTooLarge')});
  5272. }
  5273. }
  5274. };
  5275. /**
  5276. *
  5277. */
  5278. EditorUi.prototype.createEmbedSvg = function(fit, shadow, image, lightbox, edit, layers, fn)
  5279. {
  5280. var svgRoot = this.editor.graph.getSvg(null, null, null, null, null,
  5281. null, null, null, null, null, !image);
  5282. // Keeps hashtag links on same page
  5283. var links = svgRoot.getElementsByTagName('a');
  5284. if (links != null)
  5285. {
  5286. for (var i = 0; i < links.length; i++)
  5287. {
  5288. var href = links[i].getAttribute('href');
  5289. if (href != null && href.charAt(0) == '#' &&
  5290. links[i].getAttribute('target') == '_blank')
  5291. {
  5292. links[i].removeAttribute('target');
  5293. }
  5294. }
  5295. }
  5296. if (lightbox)
  5297. {
  5298. svgRoot.setAttribute('content', this.getFileData(true));
  5299. }
  5300. // Adds shadow filter
  5301. if (shadow)
  5302. {
  5303. this.editor.graph.addSvgShadow(svgRoot);
  5304. }
  5305. // SVG inside image tag
  5306. if (image)
  5307. {
  5308. var onclick = ' ';
  5309. var css = '';
  5310. // Adds double click handling
  5311. if (lightbox)
  5312. {
  5313. // KNOWN: Message passing does not seem to work in IE11
  5314. 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(" +
  5315. "img.getAttribute('src')),'*');window.removeEventListener('message',r);}};window.addEventListener('message',r);img.wnd=window.open('" + EditorUi.lightboxHost + "/?client=1" +
  5316. ((edit) ? "&edit=_blank" : "") + ((layers) ? '&layers=1' : '') + "');}})(this);\"";
  5317. css += 'cursor:pointer;';
  5318. }
  5319. if (fit)
  5320. {
  5321. css += 'max-width:100%;';
  5322. }
  5323. // Images inside IMG don't seem to work so embed them all
  5324. this.editor.convertImages(svgRoot, mxUtils.bind(this, function(svgRoot)
  5325. {
  5326. fn('<img src="' + Editor.createSvgDataUri(mxUtils.getXml(svgRoot)) + '"' +
  5327. ((css != '') ? ' style="' + css + '"' : '') + onclick + '/>');
  5328. }));
  5329. }
  5330. else
  5331. {
  5332. var css = '';
  5333. // Adds double click handling
  5334. if (lightbox)
  5335. {
  5336. var page = this.getSelectedPageIndex();
  5337. // KNOWN: Message passing does not seem to work in IE11
  5338. var js = "(function(svg){var src=window.event.target||window.event.srcElement;" +
  5339. // Ignores link events
  5340. "while (src!=null&&src.nodeName.toLowerCase()!='a'){src=src.parentNode;}if(src==null)" +
  5341. // Focus existing lightbox
  5342. "{if(svg.wnd!=null&&!svg.wnd.closed){svg.wnd.focus();}else{var r=function(evt){" +
  5343. // Message handling
  5344. "if(evt.data=='ready'&&evt.source==svg.wnd){svg.wnd.postMessage(decodeURIComponent(" +
  5345. "svg.getAttribute('content')),'*');window.removeEventListener('message',r);}};" +
  5346. "window.addEventListener('message',r);" +
  5347. // Opens lightbox window
  5348. "svg.wnd=window.open('" + EditorUi.lightboxHost + "/?client=1" +
  5349. ((page != null) ? ("&page=" + page) : "") +
  5350. ((edit) ? "&edit=_blank" : "") + ((layers) ? '&layers=1' : '') + "');}}})(this);";
  5351. svgRoot.setAttribute('onclick', js);
  5352. css += 'cursor:pointer;';
  5353. }
  5354. // Adds responsive size
  5355. if (fit)
  5356. {
  5357. var w = parseInt(svgRoot.getAttribute('width'));
  5358. var h = parseInt(svgRoot.getAttribute('height'));
  5359. svgRoot.setAttribute('viewBox', '-0.5 -0.5 ' + w + ' ' + h);
  5360. css += 'max-width:100%;max-height:' + h + 'px;';
  5361. svgRoot.removeAttribute('height');
  5362. }
  5363. if (css != '')
  5364. {
  5365. svgRoot.setAttribute('style', css);
  5366. }
  5367. // Adds CSS
  5368. this.editor.addFontCss(svgRoot);
  5369. if (this.editor.graph.mathEnabled)
  5370. {
  5371. this.editor.addMathCss(svgRoot);
  5372. }
  5373. fn(mxUtils.getXml(svgRoot));
  5374. }
  5375. };
  5376. /**
  5377. * Translates this point by the given vector.
  5378. *
  5379. * @param {number} dx X-coordinate of the translation.
  5380. * @param {number} dy Y-coordinate of the translation.
  5381. */
  5382. EditorUi.prototype.timeSince = function(date)
  5383. {
  5384. var seconds = Math.floor((new Date() - date) / 1000);
  5385. var interval = Math.floor(seconds / 31536000);
  5386. if (interval > 1)
  5387. {
  5388. return interval + ' ' + mxResources.get('years');
  5389. }
  5390. interval = Math.floor(seconds / 2592000);
  5391. if (interval > 1)
  5392. {
  5393. return interval + ' ' + mxResources.get('months');
  5394. }
  5395. interval = Math.floor(seconds / 86400);
  5396. if (interval > 1)
  5397. {
  5398. return interval + ' ' + mxResources.get('days');
  5399. }
  5400. interval = Math.floor(seconds / 3600);
  5401. if (interval > 1)
  5402. {
  5403. return interval + ' ' + mxResources.get('hours');
  5404. }
  5405. interval = Math.floor(seconds / 60);
  5406. if (interval > 1)
  5407. {
  5408. return interval + ' ' + mxResources.get('minutes');
  5409. }
  5410. if (interval == 1)
  5411. {
  5412. return interval + ' ' + mxResources.get('minute');
  5413. }
  5414. return null;
  5415. };
  5416. /**
  5417. *
  5418. */
  5419. EditorUi.prototype.decodeNodeIntoGraph = function(node, graph)
  5420. {
  5421. if (node != null)
  5422. {
  5423. var diagramNode = null;
  5424. if (node.nodeName == 'diagram')
  5425. {
  5426. diagramNode = node;
  5427. }
  5428. else if (node.nodeName == 'mxfile')
  5429. {
  5430. var diagrams = node.getElementsByTagName('diagram');
  5431. if (diagrams.length > 0)
  5432. {
  5433. diagramNode = diagrams[0];
  5434. var graphGetGlobalVariable = graph.getGlobalVariable;
  5435. graph.getGlobalVariable = function(name)
  5436. {
  5437. if (name == 'page')
  5438. {
  5439. return diagramNode.getAttribute('name') || mxResources.get('pageWithNumber', [1])
  5440. }
  5441. else if (name == 'pagenumber')
  5442. {
  5443. return 1;
  5444. }
  5445. return graphGetGlobalVariable.apply(this, arguments);
  5446. };
  5447. }
  5448. }
  5449. if (diagramNode != null)
  5450. {
  5451. node = Editor.parseDiagramNode(diagramNode);
  5452. }
  5453. }
  5454. // Hack to decode XML into temp graph via editor
  5455. var prev = this.editor.graph;
  5456. try
  5457. {
  5458. this.editor.graph = graph;
  5459. this.editor.setGraphXml(node);
  5460. }
  5461. catch (e)
  5462. {
  5463. // ignore
  5464. }
  5465. finally
  5466. {
  5467. this.editor.graph = prev;
  5468. }
  5469. return node;
  5470. };
  5471. /**
  5472. *
  5473. */
  5474. EditorUi.prototype.getPngFileProperties = function(node)
  5475. {
  5476. var scale = 1;
  5477. var border = 0;
  5478. if (node != null)
  5479. {
  5480. if (node.hasAttribute('scale'))
  5481. {
  5482. var temp = parseFloat(node.getAttribute('scale'));
  5483. if (!isNaN(temp) && temp > 0)
  5484. {
  5485. scale = temp;
  5486. }
  5487. }
  5488. if (node.hasAttribute('border'))
  5489. {
  5490. var temp = parseInt(node.getAttribute('border'));
  5491. if (!isNaN(temp) && temp > 0)
  5492. {
  5493. border = temp;
  5494. }
  5495. }
  5496. }
  5497. return {scale: scale, border: border};
  5498. };
  5499. /**
  5500. *
  5501. */
  5502. EditorUi.prototype.getEmbeddedPng = function(success, error, optionalData, scale, border)
  5503. {
  5504. try
  5505. {
  5506. var graph = this.editor.graph;
  5507. var darkTheme = graph.themes != null && graph.defaultThemeName == 'darkTheme';
  5508. var diagramData = null;
  5509. // Exports PNG for given optional data
  5510. if (optionalData != null && optionalData.length > 0)
  5511. {
  5512. graph = this.createTemporaryGraph(this.editor.graph.getStylesheet());
  5513. document.body.appendChild(graph.container);
  5514. this.decodeNodeIntoGraph(this.editor.extractGraphModel(
  5515. mxUtils.parseXml(optionalData).documentElement, true), graph);
  5516. diagramData = optionalData;
  5517. }
  5518. // Exports PNG for first page while other page is showing
  5519. else if (darkTheme || (this.pages != null && this.currentPage != this.pages[0]))
  5520. {
  5521. graph = this.createTemporaryGraph(graph.getStylesheet());
  5522. var graphGetGlobalVariable = graph.getGlobalVariable;
  5523. var page = this.pages[0];
  5524. graph.getGlobalVariable = function(name)
  5525. {
  5526. if (name == 'page')
  5527. {
  5528. return page.getName();
  5529. }
  5530. else if (name == 'pagenumber')
  5531. {
  5532. return 1;
  5533. }
  5534. return graphGetGlobalVariable.apply(this, arguments);
  5535. };
  5536. document.body.appendChild(graph.container);
  5537. graph.model.setRoot(page.root);
  5538. }
  5539. this.editor.exportToCanvas(mxUtils.bind(this, function(canvas)
  5540. {
  5541. try
  5542. {
  5543. if (diagramData == null)
  5544. {
  5545. diagramData = this.getFileData(true, null, null, null, null,
  5546. null, null, null, null, false);
  5547. }
  5548. var data = canvas.toDataURL('image/png');
  5549. data = Editor.writeGraphModelToPng(data,
  5550. 'tEXt', 'mxfile', encodeURIComponent(diagramData));
  5551. success(data.substring(data.lastIndexOf(',') + 1));
  5552. // Removes temporary graph from DOM
  5553. if (graph != this.editor.graph)
  5554. {
  5555. graph.container.parentNode.removeChild(graph.container);
  5556. }
  5557. }
  5558. catch (e)
  5559. {
  5560. if (error != null)
  5561. {
  5562. error(e);
  5563. }
  5564. }
  5565. }), null, null, null, mxUtils.bind(this, function(e)
  5566. {
  5567. if (error != null)
  5568. {
  5569. error(e);
  5570. }
  5571. }), null, null, scale, null, graph.shadowVisible, null, graph, border);
  5572. }
  5573. catch (e)
  5574. {
  5575. if (error != null)
  5576. {
  5577. error(e);
  5578. }
  5579. }
  5580. }
  5581. /**
  5582. * Returns the SVG of the diagram with embedded XML. If a callback function is
  5583. * used, the images are converted to data URIs.
  5584. */
  5585. EditorUi.prototype.getEmbeddedSvg = function(xml, graph, url, noHeader, callback, ignoreSelection,
  5586. redirect, embedImages, background, scale, border, shadow, keepTheme)
  5587. {
  5588. embedImages = (embedImages != null) ? embedImages : true;
  5589. var bg = (background != null) ? background : graph.background;
  5590. if (bg == mxConstants.NONE)
  5591. {
  5592. bg = null;
  5593. }
  5594. // Sets or disables alternate text for foreignObjects. Disabling is needed
  5595. // because PhantomJS seems to ignore switch statements and paint all text.
  5596. var svgRoot = graph.getSvg(bg, scale, border, null, null, ignoreSelection,
  5597. null, null, null, graph.shadowVisible || shadow, null, keepTheme);
  5598. if (graph.shadowVisible || shadow)
  5599. {
  5600. graph.addSvgShadow(svgRoot);
  5601. }
  5602. if (xml != null)
  5603. {
  5604. svgRoot.setAttribute('content', xml);
  5605. }
  5606. if (url != null)
  5607. {
  5608. svgRoot.setAttribute('resource', url);
  5609. }
  5610. // LATER: Click on SVG content to start editing
  5611. // if (redirect != null)
  5612. // {
  5613. // // TODO: Ignore anchor tag source for click event
  5614. // svgRoot.setAttribute('style', 'cursor:pointer;');
  5615. // svgRoot.setAttribute('onclick', 'window.location.href=\'' + redirect + '\';');
  5616. // }
  5617. if (callback != null)
  5618. {
  5619. this.embedFonts(svgRoot, mxUtils.bind(this, function(svgRoot)
  5620. {
  5621. if (embedImages)
  5622. {
  5623. this.editor.convertImages(svgRoot, mxUtils.bind(this, function(svgRoot)
  5624. {
  5625. callback(((!noHeader) ? '<?xml version="1.0" encoding="UTF-8"?>\n' +
  5626. '<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">\n' : '') +
  5627. mxUtils.getXml(svgRoot));
  5628. }));
  5629. }
  5630. else
  5631. {
  5632. callback(((!noHeader) ? '<?xml version="1.0" encoding="UTF-8"?>\n' +
  5633. '<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">\n' : '') +
  5634. mxUtils.getXml(svgRoot));
  5635. }
  5636. }));
  5637. }
  5638. else
  5639. {
  5640. return ((!noHeader) ? '<?xml version="1.0" encoding="UTF-8"?>\n' +
  5641. '<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">\n' : '') +
  5642. mxUtils.getXml(svgRoot);
  5643. }
  5644. };
  5645. /**
  5646. * Embeds font CSS as data URIs into the given svgRoot.
  5647. */
  5648. EditorUi.prototype.embedFonts = function(svgRoot, callback)
  5649. {
  5650. this.editor.loadFonts(mxUtils.bind(this, function()
  5651. {
  5652. try
  5653. {
  5654. if (this.editor.resolvedFontCss != null)
  5655. {
  5656. this.editor.addFontCss(svgRoot, this.editor.resolvedFontCss);
  5657. }
  5658. this.editor.embedExtFonts(mxUtils.bind(this, function(extFontsEmbeddedCss)
  5659. {
  5660. try
  5661. {
  5662. if (extFontsEmbeddedCss != null)
  5663. {
  5664. this.editor.addFontCss(svgRoot, extFontsEmbeddedCss);
  5665. }
  5666. callback(svgRoot);
  5667. }
  5668. catch (e)
  5669. {
  5670. callback(svgRoot);
  5671. }
  5672. }));
  5673. }
  5674. catch (e)
  5675. {
  5676. callback(svgRoot);
  5677. }
  5678. }));
  5679. };
  5680. /**
  5681. *
  5682. */
  5683. EditorUi.prototype.exportImage = function(scale, transparentBackground, ignoreSelection, addShadow,
  5684. editable, border, noCrop, currentPage, format, grid, dpi, keepTheme, exportType)
  5685. {
  5686. format = (format != null) ? format : 'png';
  5687. if (this.spinner.spin(document.body, mxResources.get('exporting')))
  5688. {
  5689. var selectionEmpty = this.editor.graph.isSelectionEmpty();
  5690. ignoreSelection = (ignoreSelection != null) ? ignoreSelection : selectionEmpty;
  5691. // Caches images
  5692. if (this.thumbImageCache == null)
  5693. {
  5694. this.thumbImageCache = new Object();
  5695. }
  5696. try
  5697. {
  5698. this.editor.exportToCanvas(mxUtils.bind(this, function(canvas)
  5699. {
  5700. this.spinner.stop();
  5701. try
  5702. {
  5703. this.saveCanvas(canvas, (editable) ? this.getFileData(true, null,
  5704. null, null, ignoreSelection, currentPage) : null,
  5705. format, (this.pages == null || this.pages.length == 0), dpi);
  5706. }
  5707. catch (e)
  5708. {
  5709. this.handleError(e);
  5710. }
  5711. }), null, this.thumbImageCache, null, mxUtils.bind(this, function(e)
  5712. {
  5713. this.spinner.stop();
  5714. this.handleError(e);
  5715. }), null, ignoreSelection, scale || 1, transparentBackground, addShadow,
  5716. null, null, border, noCrop, grid, keepTheme, exportType);
  5717. }
  5718. catch (e)
  5719. {
  5720. this.spinner.stop();
  5721. this.handleError(e);
  5722. }
  5723. }
  5724. };
  5725. /**
  5726. /**
  5727. * Returns true if the given URL is known to have CORS headers.
  5728. */
  5729. EditorUi.prototype.isCorsEnabledForUrl = function(url)
  5730. {
  5731. return this.editor.isCorsEnabledForUrl(url);
  5732. };
  5733. /**
  5734. * Handling drag and drop and import.
  5735. */
  5736. /**
  5737. * Imports the given XML into the existing diagram.
  5738. */
  5739. EditorUi.prototype.importXml = function(xml, dx, dy, crop, noErrorHandling, addNewPage)
  5740. {
  5741. dx = (dx != null) ? dx : 0;
  5742. dy = (dy != null) ? dy : 0;
  5743. var cells = []
  5744. try
  5745. {
  5746. var graph = this.editor.graph;
  5747. if (xml != null && xml.length > 0)
  5748. {
  5749. // Adds pages
  5750. graph.model.beginUpdate();
  5751. try
  5752. {
  5753. var doc = mxUtils.parseXml(xml);
  5754. var mapping = {};
  5755. // Checks for mxfile with multiple pages
  5756. var node = this.editor.extractGraphModel(doc.documentElement, this.pages != null);
  5757. if (node != null && node.nodeName == 'mxfile' && this.pages != null)
  5758. {
  5759. var diagrams = node.getElementsByTagName('diagram');
  5760. if (diagrams.length == 1 && !addNewPage)
  5761. {
  5762. node = Editor.parseDiagramNode(diagrams[0]);
  5763. if (this.currentPage != null)
  5764. {
  5765. mapping[diagrams[0].getAttribute('id')] = this.currentPage.getId();
  5766. // Renames page if diagram has one blank page with default name
  5767. if (this.pages != null && this.pages.length == 1 &&
  5768. this.isDiagramEmpty() && this.currentPage.getName() ==
  5769. mxResources.get('pageWithNumber', [1]))
  5770. {
  5771. var name = diagrams[0].getAttribute('name');
  5772. if (name != null && name != '')
  5773. {
  5774. this.editor.graph.model.execute(new RenamePage(
  5775. this, this.currentPage, name));
  5776. }
  5777. }
  5778. }
  5779. }
  5780. else if (diagrams.length > 0)
  5781. {
  5782. var pages = [];
  5783. var i0 = 0;
  5784. // Adds first page to current page if current page is only page and empty
  5785. if (this.pages != null && this.pages.length == 1 && this.isDiagramEmpty())
  5786. {
  5787. mapping[diagrams[0].getAttribute('id')] = this.pages[0].getId();
  5788. node = Editor.parseDiagramNode(diagrams[0]);
  5789. crop = false;
  5790. i0 = 1;
  5791. }
  5792. for (var i = i0; i < diagrams.length; i++)
  5793. {
  5794. // Imported pages must obtain a new ID and
  5795. // all links to pages must be updated below
  5796. var oldId = diagrams[i].getAttribute('id')
  5797. diagrams[i].removeAttribute('id');
  5798. var page = this.updatePageRoot(new DiagramPage(diagrams[i]));
  5799. mapping[oldId] = diagrams[i].getAttribute('id');
  5800. var index = this.pages.length;
  5801. // Checks for invalid page names
  5802. if (page.getName() == null)
  5803. {
  5804. page.setName(mxResources.get('pageWithNumber', [index + 1]));
  5805. }
  5806. graph.model.execute(new ChangePage(this, page, page, index, true));
  5807. pages.push(page);
  5808. }
  5809. this.updatePageLinks(mapping, pages);
  5810. }
  5811. }
  5812. if (node != null && node.nodeName === 'mxGraphModel')
  5813. {
  5814. cells = graph.importGraphModel(node, dx, dy, crop);
  5815. if (cells != null)
  5816. {
  5817. for (var i = 0; i < cells.length; i++)
  5818. {
  5819. this.updatePageLinksForCell(mapping, cells[i]);
  5820. }
  5821. }
  5822. }
  5823. }
  5824. finally
  5825. {
  5826. graph.model.endUpdate();
  5827. }
  5828. }
  5829. }
  5830. catch (e)
  5831. {
  5832. if (!noErrorHandling)
  5833. {
  5834. this.handleError(e);
  5835. }
  5836. else
  5837. {
  5838. throw e;
  5839. }
  5840. }
  5841. return cells;
  5842. };
  5843. /**
  5844. * Updates links to pages in shapes and labels.
  5845. */
  5846. EditorUi.prototype.updatePageLinks = function(mapping, pages)
  5847. {
  5848. for (var i = 0; i < pages.length; i++)
  5849. {
  5850. this.updatePageLinksForCell(mapping, pages[i].root);
  5851. }
  5852. };
  5853. /**
  5854. * Updates links to pages in shapes and labels.
  5855. */
  5856. EditorUi.prototype.updatePageLinksForCell = function(mapping, cell)
  5857. {
  5858. var temp = document.createElement('div');
  5859. var graph = this.editor.graph;
  5860. var href = graph.getLinkForCell(cell);
  5861. if (href != null)
  5862. {
  5863. graph.setLinkForCell(cell, this.updatePageLink(mapping, href));
  5864. }
  5865. if (graph.isHtmlLabel(cell))
  5866. {
  5867. temp.innerHTML = graph.sanitizeHtml(graph.getLabel(cell));
  5868. var links = temp.getElementsByTagName('a');
  5869. var changed = false;
  5870. for (var i = 0; i < links.length; i++)
  5871. {
  5872. href = links[i].getAttribute('href');
  5873. if (href != null)
  5874. {
  5875. links[i].setAttribute('href', this.updatePageLink(mapping, href));
  5876. changed = true;
  5877. }
  5878. }
  5879. if (changed)
  5880. {
  5881. graph.labelChanged(cell, temp.innerHTML);
  5882. }
  5883. }
  5884. for (var i = 0; i < graph.model.getChildCount(cell); i++)
  5885. {
  5886. this.updatePageLinksForCell(mapping, graph.model.getChildAt(cell, i));
  5887. }
  5888. };
  5889. /**
  5890. * Updates links to pages in shapes and labels.
  5891. */
  5892. EditorUi.prototype.updatePageLink = function(mapping, href)
  5893. {
  5894. if (href.substring(0, 13) == 'data:page/id,')
  5895. {
  5896. var newId = mapping[href.substring(href.indexOf(',') + 1)];
  5897. href = (newId != null) ? 'data:page/id,' + newId : null;
  5898. }
  5899. else if (href.substring(0, 17) == 'data:action/json,')
  5900. {
  5901. try
  5902. {
  5903. var link = JSON.parse(href.substring(17));
  5904. if (link.actions != null)
  5905. {
  5906. for (var i = 0; i < link.actions.length; i++)
  5907. {
  5908. var action = link.actions[i];
  5909. if (action.open != null && action.open.substring(0, 13) == 'data:page/id,')
  5910. {
  5911. var oldId = action.open.substring(action.open.indexOf(',') + 1);
  5912. var newId = mapping[oldId];
  5913. if (newId != null)
  5914. {
  5915. action.open = 'data:page/id,' + newId;
  5916. }
  5917. else if (this.getPageById(oldId) == null)
  5918. {
  5919. delete action.open;
  5920. }
  5921. }
  5922. }
  5923. href = 'data:action/json,' + JSON.stringify(link);
  5924. }
  5925. }
  5926. catch (e)
  5927. {
  5928. // Ignore
  5929. }
  5930. }
  5931. return href;
  5932. };
  5933. /**
  5934. * Returns true for VSD, VDX and VSS, VSX files.
  5935. */
  5936. EditorUi.prototype.isRemoteVisioFormat = function(filename)
  5937. {
  5938. return /(\.v(sd|dx))($|\?)/i.test(filename) || /(\.vs(s|x))($|\?)/i.test(filename);
  5939. };
  5940. /**
  5941. * Imports the given Visio file
  5942. */
  5943. EditorUi.prototype.importVisio = function(file, done, onerror, filename)
  5944. {
  5945. //A reduced version of this code is used in conf/jira plugins, review that code whenever this function is changed
  5946. filename = (filename != null) ? filename : file.name;
  5947. onerror = (onerror != null) ? onerror : mxUtils.bind(this, function(e)
  5948. {
  5949. this.handleError(e);
  5950. });
  5951. var delayed = mxUtils.bind(this, function()
  5952. {
  5953. this.loadingExtensions = false;
  5954. if (this.doImportVisio)
  5955. {
  5956. var remote = this.isRemoteVisioFormat(filename);
  5957. try
  5958. {
  5959. var ext = 'UNKNOWN-VISIO';
  5960. var dot = filename.lastIndexOf('.');
  5961. if (dot >= 0 && dot < filename.length)
  5962. {
  5963. ext = filename.substring(dot + 1).toUpperCase();
  5964. }
  5965. else
  5966. {
  5967. var slash = filename.lastIndexOf('/');
  5968. if (slash >= 0 && slash < filename.length)
  5969. {
  5970. filename = filename.substring(slash + 1);
  5971. }
  5972. }
  5973. EditorUi.logEvent({category: ext + '-MS-IMPORT-FILE',
  5974. action: 'filename_' + filename,
  5975. label: (remote) ? 'remote' : 'local'});
  5976. }
  5977. catch (e)
  5978. {
  5979. // ignore
  5980. }
  5981. if (remote)
  5982. {
  5983. if (VSD_CONVERT_URL != null && !this.isOffline())
  5984. {
  5985. var formData = new FormData();
  5986. formData.append('file1', file, filename);
  5987. var xhr = new XMLHttpRequest();
  5988. xhr.open('POST', VSD_CONVERT_URL + (/(\.vss|\.vsx)$/.test(filename)? '?stencil=1' : ''));
  5989. xhr.responseType = 'blob';
  5990. this.addRemoteServiceSecurityCheck(xhr);
  5991. xhr.onreadystatechange = mxUtils.bind(this, function()
  5992. {
  5993. if (xhr.readyState == 4)
  5994. {
  5995. if (xhr.status >= 200 && xhr.status <= 299)
  5996. {
  5997. try
  5998. {
  5999. var resp = xhr.response;
  6000. if (resp.type == 'text/xml')
  6001. {
  6002. var reader = new FileReader();
  6003. reader.onload = mxUtils.bind(this, function(e)
  6004. {
  6005. try
  6006. {
  6007. done(e.target.result);
  6008. }
  6009. catch (e)
  6010. {
  6011. onerror({message: mxResources.get('errorLoadingFile')});
  6012. }
  6013. });
  6014. reader.readAsText(resp);
  6015. }
  6016. else
  6017. {
  6018. this.doImportVisio(resp, done, onerror, filename);
  6019. }
  6020. }
  6021. catch (e)
  6022. {
  6023. onerror(e);
  6024. }
  6025. }
  6026. else
  6027. {
  6028. try
  6029. {
  6030. if (xhr.responseType == '' || xhr.responseType == 'text')
  6031. {
  6032. onerror({message: xhr.responseText});
  6033. }
  6034. else
  6035. {
  6036. var reader = new FileReader();
  6037. reader.onload = function()
  6038. {
  6039. onerror({message: JSON.parse(reader.result).Message});
  6040. }
  6041. reader.readAsText(xhr.response);
  6042. }
  6043. }
  6044. catch(e)
  6045. {
  6046. onerror({});
  6047. }
  6048. }
  6049. }
  6050. });
  6051. xhr.send(formData);
  6052. }
  6053. else
  6054. {
  6055. onerror({message: this.getServiceName() == 'conf'? mxResources.get('vsdNoConfig') : mxResources.get('serviceUnavailableOrBlocked')});
  6056. }
  6057. }
  6058. else
  6059. {
  6060. try
  6061. {
  6062. this.doImportVisio(file, done, onerror, filename);
  6063. }
  6064. catch (e)
  6065. {
  6066. onerror(e);
  6067. }
  6068. }
  6069. }
  6070. else
  6071. {
  6072. this.spinner.stop();
  6073. this.handleError({message: mxResources.get('serviceUnavailableOrBlocked')});
  6074. }
  6075. });
  6076. if (!this.doImportVisio && !this.loadingExtensions && !this.isOffline(true))
  6077. {
  6078. this.loadingExtensions = true;
  6079. mxscript('js/extensions.min.js', delayed);
  6080. }
  6081. else
  6082. {
  6083. delayed();
  6084. }
  6085. };
  6086. /**
  6087. * Imports the given GraphML (yEd) file
  6088. */
  6089. EditorUi.prototype.importGraphML = function(xmlData, done, onerror)
  6090. {
  6091. onerror = (onerror != null) ? onerror : mxUtils.bind(this, function(e)
  6092. {
  6093. this.handleError(e);
  6094. });
  6095. var delayed = mxUtils.bind(this, function()
  6096. {
  6097. this.loadingExtensions = false;
  6098. if (this.doImportGraphML)
  6099. {
  6100. try
  6101. {
  6102. this.doImportGraphML(xmlData, done, onerror);
  6103. }
  6104. catch (e)
  6105. {
  6106. onerror(e);
  6107. }
  6108. }
  6109. else
  6110. {
  6111. this.spinner.stop();
  6112. this.handleError({message: mxResources.get('serviceUnavailableOrBlocked')});
  6113. }
  6114. });
  6115. if (!this.doImportGraphML && !this.loadingExtensions && !this.isOffline(true))
  6116. {
  6117. this.loadingExtensions = true;
  6118. mxscript('js/extensions.min.js', delayed);
  6119. }
  6120. else
  6121. {
  6122. delayed();
  6123. }
  6124. };
  6125. /**
  6126. * Export the diagram to VSDX
  6127. */
  6128. EditorUi.prototype.exportVisio = function(currentPage)
  6129. {
  6130. var delayed = mxUtils.bind(this, function()
  6131. {
  6132. this.loadingExtensions = false;
  6133. if (typeof VsdxExport !== 'undefined')
  6134. {
  6135. try
  6136. {
  6137. var expSuccess = new VsdxExport(this).exportCurrentDiagrams(currentPage);
  6138. if (!expSuccess)
  6139. {
  6140. this.handleError({message: mxResources.get('unknownError')});
  6141. }
  6142. }
  6143. catch (e)
  6144. {
  6145. this.handleError(e);
  6146. }
  6147. }
  6148. else
  6149. {
  6150. this.spinner.stop();
  6151. this.handleError({message: mxResources.get('serviceUnavailableOrBlocked')});
  6152. }
  6153. });
  6154. if (typeof VsdxExport === 'undefined' && !this.loadingExtensions && !this.isOffline(true))
  6155. {
  6156. this.loadingExtensions = true;
  6157. mxscript('js/extensions.min.js', delayed);
  6158. }
  6159. else
  6160. {
  6161. delayed();
  6162. }
  6163. };
  6164. /**
  6165. * Imports the given Lucidchart data.
  6166. */
  6167. EditorUi.prototype.convertLucidChart = function(data, success, error)
  6168. {
  6169. var delayed = mxUtils.bind(this, function()
  6170. {
  6171. this.loadingExtensions = false;
  6172. // Checks for signature method
  6173. if (typeof window.LucidImporter !== 'undefined')
  6174. {
  6175. try
  6176. {
  6177. EditorUi.logEvent({category: 'LUCIDCHART-IMPORT-FILE',
  6178. action: 'size_' + data.length});
  6179. EditorUi.debug('convertLucidChart', data);
  6180. }
  6181. catch (e)
  6182. {
  6183. // ignore
  6184. }
  6185. try
  6186. {
  6187. success(LucidImporter.importState(JSON.parse(data)));
  6188. }
  6189. catch (e)
  6190. {
  6191. if (window.console != null)
  6192. {
  6193. console.error(e);
  6194. }
  6195. error(e);
  6196. }
  6197. }
  6198. else
  6199. {
  6200. error({message: mxResources.get('serviceUnavailableOrBlocked')});
  6201. }
  6202. });
  6203. if (typeof window.LucidImporter === 'undefined' &&
  6204. !this.loadingExtensions && !this.isOffline(true))
  6205. {
  6206. this.loadingExtensions = true;
  6207. if (urlParams['dev'] == '1')
  6208. {
  6209. //Lucid org chart requires orgChart layout, in production, it is part of the extemsions.min.js
  6210. mxscript('js/diagramly/Extensions.js', function()
  6211. {
  6212. mxscript('js/orgchart/bridge.min.js', function()
  6213. {
  6214. mxscript('js/orgchart/bridge.collections.min.js', function()
  6215. {
  6216. mxscript('js/orgchart/OrgChart.Layout.min.js', function()
  6217. {
  6218. mxscript('js/orgchart/mxOrgChartLayout.js', delayed);
  6219. });
  6220. });
  6221. });
  6222. });
  6223. }
  6224. else
  6225. {
  6226. mxscript('js/extensions.min.js', delayed);
  6227. }
  6228. }
  6229. else
  6230. {
  6231. // Async needed for selection
  6232. window.setTimeout(delayed, 0);
  6233. }
  6234. };
  6235. /**
  6236. * Generates a Mermaid image.
  6237. */
  6238. EditorUi.prototype.generateMermaidImage = function(data, config, success, error)
  6239. {
  6240. var ui = this;
  6241. var delayed = function()
  6242. {
  6243. try
  6244. {
  6245. this.loadingMermaid = false;
  6246. config = (config != null) ? config : EditorUi.defaultMermaidConfig;
  6247. config.securityLevel = 'strict';
  6248. config.startOnLoad = false;
  6249. mermaid.mermaidAPI.initialize(config);
  6250. mermaid.mermaidAPI.render('geMermaidOutput-' + new Date().getTime(), data, function(svg)
  6251. {
  6252. try
  6253. {
  6254. // Workaround for namespace errors in SVG output for IE
  6255. if (mxClient.IS_IE || mxClient.IS_IE11)
  6256. {
  6257. svg = svg.replace(/ xmlns:\S*="http:\/\/www.w3.org\/XML\/1998\/namespace"/g, '').
  6258. replace(/ (NS xml|\S*):space="preserve"/g, ' xml:space="preserve"');
  6259. }
  6260. var doc = mxUtils.parseXml(svg);
  6261. var svgs = doc.getElementsByTagName('svg');
  6262. if (svgs.length > 0)
  6263. {
  6264. var w = parseFloat(svgs[0].getAttribute('width'));
  6265. var h = parseFloat(svgs[0].getAttribute('height'));
  6266. if (isNaN(w) || isNaN(h))
  6267. {
  6268. try
  6269. {
  6270. var viewBox = svgs[0].getAttribute('viewBox').split(/\s+/);
  6271. w = parseFloat(viewBox[2]);
  6272. h = parseFloat(viewBox[3]);
  6273. }
  6274. catch(e)
  6275. {
  6276. //Any size such that it shows up
  6277. w = w || 100;
  6278. h = h || 100;
  6279. }
  6280. }
  6281. success(ui.convertDataUri(Editor.createSvgDataUri(svg)), w, h);
  6282. }
  6283. else
  6284. {
  6285. error({message: mxResources.get('invalidInput')});
  6286. }
  6287. }
  6288. catch (e)
  6289. {
  6290. error(e);
  6291. }
  6292. });
  6293. }
  6294. catch (e)
  6295. {
  6296. error(e);
  6297. }
  6298. };
  6299. if (typeof mermaid === 'undefined' && !this.loadingMermaid && !this.isOffline(true))
  6300. {
  6301. this.loadingMermaid = true;
  6302. if (urlParams['dev'] == '1')
  6303. {
  6304. mxscript('js/mermaid/mermaid.min.js', delayed);
  6305. }
  6306. else
  6307. {
  6308. mxscript('js/extensions.min.js', delayed);
  6309. }
  6310. }
  6311. else
  6312. {
  6313. delayed();
  6314. }
  6315. };
  6316. /**
  6317. * Generates a plant UML image. Possible types are svg, png and txt.
  6318. */
  6319. EditorUi.prototype.generatePlantUmlImage = function(data, type, success, error)
  6320. {
  6321. function encode64(data)
  6322. {
  6323. r = "";
  6324. for (i = 0; i < data.length; i += 3)
  6325. {
  6326. if (i + 2 == data.length)
  6327. {
  6328. r += append3bytes(data.charCodeAt(i), data.charCodeAt(i + 1), 0);
  6329. }
  6330. else if (i + 1 == data.length)
  6331. {
  6332. r += append3bytes(data.charCodeAt(i), 0, 0);
  6333. }
  6334. else
  6335. {
  6336. r += append3bytes(data.charCodeAt(i), data.charCodeAt(i + 1),
  6337. data.charCodeAt(i + 2));
  6338. }
  6339. }
  6340. return r;
  6341. }
  6342. function append3bytes(b1, b2, b3)
  6343. {
  6344. c1 = b1 >> 2;
  6345. c2 = ((b1 & 0x3) << 4) | (b2 >> 4);
  6346. c3 = ((b2 & 0xF) << 2) | (b3 >> 6);
  6347. c4 = b3 & 0x3F;
  6348. r = "";
  6349. r += encode6bit(c1 & 0x3F);
  6350. r += encode6bit(c2 & 0x3F);
  6351. r += encode6bit(c3 & 0x3F);
  6352. r += encode6bit(c4 & 0x3F);
  6353. return r;
  6354. }
  6355. function encode6bit(b)
  6356. {
  6357. if (b < 10)
  6358. {
  6359. return String.fromCharCode(48 + b);
  6360. }
  6361. b -= 10;
  6362. if (b < 26)
  6363. {
  6364. return String.fromCharCode(65 + b);
  6365. }
  6366. b -= 26;
  6367. if (b < 26)
  6368. {
  6369. return String.fromCharCode(97 + b);
  6370. }
  6371. b -= 26;
  6372. if (b == 0)
  6373. {
  6374. return '-';
  6375. }
  6376. if (b == 1)
  6377. {
  6378. return '_';
  6379. }
  6380. return '?';
  6381. }
  6382. // TODO: Remove unescape, use btoa for compatibility with graph.compress
  6383. function compress(s)
  6384. {
  6385. return encode64(Graph.arrayBufferToString(pako.deflateRaw(s)));
  6386. };
  6387. var plantUmlServerUrl = (type == 'txt') ? PLANT_URL + '/txt/' :
  6388. ((type == 'png') ? PLANT_URL + '/png/' : PLANT_URL + '/svg/');
  6389. var xhr = new XMLHttpRequest();
  6390. xhr.open('GET', plantUmlServerUrl + compress(data), true);
  6391. if (type != 'txt')
  6392. {
  6393. xhr.responseType = 'blob';
  6394. }
  6395. xhr.onload = function(e)
  6396. {
  6397. if (this.status >= 200 && this.status < 300)
  6398. {
  6399. if (type == 'txt')
  6400. {
  6401. success(this.response);
  6402. }
  6403. else
  6404. {
  6405. var reader = new FileReader();
  6406. reader.readAsDataURL(this.response);
  6407. reader.onloadend = function(e)
  6408. {
  6409. var img = new Image();
  6410. img.onload = function()
  6411. {
  6412. try
  6413. {
  6414. var w = img.width;
  6415. var h = img.height;
  6416. // Workaround for 0 image size in IE11
  6417. if (w == 0 && h == 0)
  6418. {
  6419. var data = reader.result;
  6420. var comma = data.indexOf(',');
  6421. var svgText = decodeURIComponent(escape(atob(data.substring(comma + 1))));
  6422. var root = mxUtils.parseXml(svgText);
  6423. var svgs = root.getElementsByTagName('svg');
  6424. if (svgs.length > 0)
  6425. {
  6426. w = parseFloat(svgs[0].getAttribute('width'));
  6427. h = parseFloat(svgs[0].getAttribute('height'));
  6428. }
  6429. }
  6430. success(reader.result, w, h);
  6431. }
  6432. catch (e)
  6433. {
  6434. error(e);
  6435. }
  6436. };
  6437. img.src = reader.result;
  6438. };
  6439. reader.onerror = function(e)
  6440. {
  6441. error(e);
  6442. };
  6443. }
  6444. }
  6445. else
  6446. {
  6447. error(e);
  6448. }
  6449. };
  6450. xhr.onerror = function(e)
  6451. {
  6452. error(e);
  6453. };
  6454. xhr.send();
  6455. };
  6456. /**
  6457. * Inserts the given text as a preformatted HTML text.
  6458. */
  6459. EditorUi.prototype.insertAsPreText = function(text, x, y)
  6460. {
  6461. var graph = this.editor.graph;
  6462. var cell = null;
  6463. graph.getModel().beginUpdate();
  6464. try
  6465. {
  6466. cell = graph.insertVertex(null, null, '<pre>' + text + '</pre>',
  6467. x, y, 1, 1, 'text;html=1;align=left;verticalAlign=top;');
  6468. graph.updateCellSize(cell, true);
  6469. }
  6470. finally
  6471. {
  6472. graph.getModel().endUpdate();
  6473. }
  6474. return cell;
  6475. };
  6476. /**
  6477. * Imports the given XML into the existing diagram.
  6478. * TODO: Make this function asynchronous
  6479. */
  6480. EditorUi.prototype.insertTextAt = function(text, dx, dy, html, asImage, crop, resizeImages, addNewPage)
  6481. {
  6482. crop = (crop != null) ? crop : true;
  6483. resizeImages = (resizeImages != null) ? resizeImages : true;
  6484. // Handles special case for Gliffy data which requires async server-side for parsing
  6485. if (text != null)
  6486. {
  6487. if (Graph.fileSupport && !this.isOffline() && new XMLHttpRequest().upload && this.isRemoteFileFormat(text))
  6488. {
  6489. // Fixes possible parsing problems with ASCII 160 (non-breaking space)
  6490. this.parseFile(new Blob([text.replace(/\s+/g,' ')], {type: 'application/octet-stream'}), mxUtils.bind(this, function(xhr)
  6491. {
  6492. if (xhr.readyState == 4 && xhr.status >= 200 && xhr.status <= 299)
  6493. {
  6494. this.editor.graph.setSelectionCells(this.insertTextAt(
  6495. xhr.responseText, dx, dy, true));
  6496. }
  6497. }));
  6498. // Returns empty cells array as it is aysynchronous
  6499. return [];
  6500. }
  6501. // Handles special case of data URI which requires async loading for finding size
  6502. else if (text.substring(0, 5) == 'data:' || (!this.isOffline() &&
  6503. (asImage || (/\.(gif|jpg|jpeg|tiff|png|svg)$/i).test(text))))
  6504. {
  6505. var graph = this.editor.graph;
  6506. // Checks for embedded XML in PDF
  6507. if (text.substring(0, 28) == 'data:application/pdf;base64,')
  6508. {
  6509. var xml = Editor.extractGraphModelFromPdf(text);
  6510. if (xml != null && xml.length > 0)
  6511. {
  6512. return this.importXml(xml, dx, dy, crop, true, addNewPage);
  6513. }
  6514. }
  6515. // Checks for embedded XML in PNG
  6516. if (text.substring(0, 22) == 'data:image/png;base64,')
  6517. {
  6518. var xml = this.extractGraphModelFromPng(text);
  6519. if (xml != null && xml.length > 0)
  6520. {
  6521. return this.importXml(xml, dx, dy, crop, true, addNewPage);
  6522. }
  6523. }
  6524. // Tries to extract embedded XML from SVG data URI
  6525. if (text.substring(0, 19) == 'data:image/svg+xml;')
  6526. {
  6527. try
  6528. {
  6529. var xml = null;
  6530. if (text.substring(0, 26) == 'data:image/svg+xml;base64,')
  6531. {
  6532. xml = text.substring(text.indexOf(',') + 1);
  6533. xml = (window.atob && !mxClient.IS_SF) ? atob(xml) : Base64.decode(xml, true);
  6534. }
  6535. else
  6536. {
  6537. xml = decodeURIComponent(text.substring(text.indexOf(',') + 1));
  6538. }
  6539. var result = this.importXml(xml, dx, dy, crop, true, addNewPage);
  6540. if (result.length > 0)
  6541. {
  6542. return result;
  6543. }
  6544. }
  6545. catch (e)
  6546. {
  6547. // Ignore
  6548. }
  6549. }
  6550. this.loadImage(text, mxUtils.bind(this, function(img)
  6551. {
  6552. if (text.substring(0, 5) == 'data:')
  6553. {
  6554. this.resizeImage(img, text, mxUtils.bind(this, function(data2, w2, h2)
  6555. {
  6556. graph.setSelectionCell(graph.insertVertex(null, null, '', graph.snap(dx), graph.snap(dy),
  6557. w2, h2, 'shape=image;verticalLabelPosition=bottom;labelBackgroundColor=#ffffff;' +
  6558. 'verticalAlign=top;aspect=fixed;imageAspect=0;image=' + this.convertDataUri(data2) + ';'));
  6559. }), resizeImages, this.maxImageSize);
  6560. }
  6561. else
  6562. {
  6563. var s = Math.min(1, Math.min(this.maxImageSize / img.width, this.maxImageSize / img.height));
  6564. var w = Math.round(img.width * s);
  6565. var h = Math.round(img.height * s);
  6566. graph.setSelectionCell(graph.insertVertex(null, null, '', graph.snap(dx), graph.snap(dy),
  6567. w, h, 'shape=image;verticalLabelPosition=bottom;labelBackgroundColor=#ffffff;' +
  6568. 'verticalAlign=top;aspect=fixed;imageAspect=0;image=' + text + ';'));
  6569. }
  6570. }), mxUtils.bind(this, function()
  6571. {
  6572. var cell = null;
  6573. // Inserts invalid data URIs as text
  6574. graph.getModel().beginUpdate();
  6575. try
  6576. {
  6577. cell = graph.insertVertex(graph.getDefaultParent(), null, text,
  6578. graph.snap(dx), graph.snap(dy), 1, 1, 'text;' + ((html) ? 'html=1;' : ''));
  6579. graph.updateCellSize(cell);
  6580. graph.fireEvent(new mxEventObject('textInserted', 'cells', [cell]));
  6581. }
  6582. finally
  6583. {
  6584. graph.getModel().endUpdate();
  6585. }
  6586. graph.setSelectionCell(cell);
  6587. }));
  6588. return [];
  6589. }
  6590. else
  6591. {
  6592. text = Graph.zapGremlins(mxUtils.trim(text));
  6593. if (this.isCompatibleString(text))
  6594. {
  6595. return this.importXml(text, dx, dy, crop, null, addNewPage);
  6596. }
  6597. else if (text.length > 0)
  6598. {
  6599. if (this.isLucidChartData(text))
  6600. {
  6601. this.convertLucidChart(text, mxUtils.bind(this, function(xml)
  6602. {
  6603. this.editor.graph.setSelectionCells(
  6604. this.importXml(xml, dx, dy, crop,
  6605. null, addNewPage));
  6606. }), mxUtils.bind(this, function(e)
  6607. {
  6608. this.handleError(e);
  6609. }));
  6610. }
  6611. else
  6612. {
  6613. var graph = this.editor.graph;
  6614. var cell = null;
  6615. graph.getModel().beginUpdate();
  6616. try
  6617. {
  6618. // Fires cellsInserted to apply the current style to the inserted text.
  6619. // This requires the value to be empty when the event is fired.
  6620. cell = graph.insertVertex(graph.getDefaultParent(), null, '',
  6621. graph.snap(dx), graph.snap(dy), 1, 1, 'text;whiteSpace=wrap;' + ((html) ? 'html=1;' : ''));
  6622. graph.fireEvent(new mxEventObject('textInserted', 'cells', [cell]));
  6623. // Single tag is converted
  6624. if (text.charAt(0) == '<' && text.indexOf('>') == text.length - 1)
  6625. {
  6626. text = mxUtils.htmlEntities(text);
  6627. }
  6628. //TODO Refuse unsupported file types early as at this stage a lot of processing has beed done and time is wasted.
  6629. // For example, 5 MB PDF files is processed and then only 0.5 MB of meaningless text is added!
  6630. //Limit labels to maxTextBytes
  6631. if (text.length > this.maxTextBytes)
  6632. {
  6633. text = text.substring(0, this.maxTextBytes) + '...';
  6634. }
  6635. // Apply value and updates the cell size to fit the text block
  6636. cell.value = text;
  6637. graph.updateCellSize(cell);
  6638. // Adds wrapping for large text blocks
  6639. if (this.maxTextWidth > 0 && cell.geometry.width > this.maxTextWidth)
  6640. {
  6641. var size = graph.getPreferredSizeForCell(cell, this.maxTextWidth);
  6642. cell.geometry.width = size.width;
  6643. cell.geometry.height = size.height;
  6644. }
  6645. // See https://stackoverflow.com/questions/5717093/check-if-a-javascript-string-is-a-url
  6646. if (Graph.isLink(cell.value))
  6647. {
  6648. graph.setLinkForCell(cell, cell.value);
  6649. }
  6650. // Adds spacing
  6651. cell.geometry.width += graph.gridSize;
  6652. cell.geometry.height += graph.gridSize;
  6653. }
  6654. finally
  6655. {
  6656. graph.getModel().endUpdate();
  6657. }
  6658. return [cell];
  6659. }
  6660. }
  6661. }
  6662. }
  6663. return [];
  6664. };
  6665. /**
  6666. * Formats the given file size.
  6667. */
  6668. EditorUi.prototype.formatFileSize = function(size)
  6669. {
  6670. var units = [' kB', ' MB', ' GB', ' TB', 'PB', 'EB', 'ZB', 'YB'];
  6671. var i = -1;
  6672. do
  6673. {
  6674. size = size / 1024;
  6675. i++;
  6676. } while (size > 1024);
  6677. return Math.max(size, 0.1).toFixed(1) + units[i];
  6678. };
  6679. /**
  6680. * Imports the given XML into the existing diagram.
  6681. */
  6682. EditorUi.prototype.convertDataUri = function(uri)
  6683. {
  6684. // Handles special case of data URI which needs to be rewritten
  6685. // to be used in a cell style to remove the semicolon
  6686. if (uri.substring(0, 5) == 'data:')
  6687. {
  6688. var semi = uri.indexOf(';');
  6689. if (semi > 0)
  6690. {
  6691. uri = uri.substring(0, semi) + uri.substring(uri.indexOf(',', semi + 1));
  6692. }
  6693. }
  6694. return uri;
  6695. };
  6696. /**
  6697. * Returns true for Gliffy data.
  6698. */
  6699. EditorUi.prototype.isRemoteFileFormat = function(data, filename)
  6700. {
  6701. return /(\"contentType\":\s*\"application\/gliffy\+json\")/.test(data);
  6702. };
  6703. /**
  6704. * Returns true for Gliffy
  6705. */
  6706. EditorUi.prototype.isLucidChartData = function(data)
  6707. {
  6708. return data != null && (data.substring(0, 26) ==
  6709. '{"state":"{\\"Properties\\":' ||
  6710. data.substring(0, 14) == '{"Properties":');
  6711. };
  6712. /**
  6713. * Imports a local file from the device or local storage.
  6714. */
  6715. EditorUi.prototype.importLocalFile = function(device, noSplash)
  6716. {
  6717. if (device && Graph.fileSupport)
  6718. {
  6719. if (this.importFileInputElt == null)
  6720. {
  6721. var input = document.createElement('input');
  6722. input.setAttribute('type', 'file');
  6723. mxEvent.addListener(input, 'change', mxUtils.bind(this, function()
  6724. {
  6725. if (input.files != null)
  6726. {
  6727. // Using null for position will disable crop of input file
  6728. this.importFiles(input.files, null, null, this.maxImageSize);
  6729. // Resets input to force change event for same file (type reset required for IE)
  6730. input.type = '';
  6731. input.type = 'file';
  6732. input.value = '';
  6733. }
  6734. }));
  6735. input.style.display = 'none';
  6736. document.body.appendChild(input);
  6737. this.importFileInputElt = input;
  6738. }
  6739. this.importFileInputElt.click();
  6740. }
  6741. else
  6742. {
  6743. window.openNew = false;
  6744. window.openKey = 'import';
  6745. window.listBrowserFiles = mxUtils.bind(this, function(success, error)
  6746. {
  6747. StorageFile.listFiles(this, 'F', success, error);
  6748. });
  6749. window.openBrowserFile = mxUtils.bind(this, function(title, success, error)
  6750. {
  6751. StorageFile.getFileContent(this, title, success, error);
  6752. });
  6753. window.deleteBrowserFile = mxUtils.bind(this, function(title, success, error)
  6754. {
  6755. StorageFile.deleteFile(this, title, success, error);
  6756. });
  6757. if (!noSplash)
  6758. {
  6759. var prevValue = Editor.useLocalStorage;
  6760. Editor.useLocalStorage = !device;
  6761. }
  6762. // Closes dialog after open
  6763. window.openFile = new OpenFile(mxUtils.bind(this, function(cancel)
  6764. {
  6765. this.hideDialog(cancel);
  6766. }));
  6767. window.openFile.setConsumer(mxUtils.bind(this, function(xml, filename)
  6768. {
  6769. if (filename != null && Graph.fileSupport && /(\.v(dx|sdx?))($|\?)/i.test(filename))
  6770. {
  6771. // "Not a UTF 8 file" when opening VSDX in IE so this is never called
  6772. var file = new Blob([xml], {type: 'application/octet-stream'})
  6773. this.importVisio(file, mxUtils.bind(this, function(xml)
  6774. {
  6775. this.importXml(xml, 0, 0, true);
  6776. }), null, filename);
  6777. }
  6778. else
  6779. {
  6780. this.editor.graph.setSelectionCells(this.importXml(xml, 0, 0, true));
  6781. }
  6782. }));
  6783. // Removes openFile if dialog is closed
  6784. this.showDialog(new OpenDialog(this).container, (Editor.useLocalStorage) ? 640 : 360,
  6785. (Editor.useLocalStorage) ? 480 : 220, true, true, function()
  6786. {
  6787. window.openFile = null;
  6788. });
  6789. // Extends dialog close to show splash screen
  6790. if (!noSplash)
  6791. {
  6792. var dlg = this.dialog;
  6793. var dlgClose = dlg.close;
  6794. this.dialog.close = mxUtils.bind(this, function(cancel)
  6795. {
  6796. Editor.useLocalStorage = prevValue;
  6797. dlgClose.apply(dlg, arguments);
  6798. if (cancel && this.getCurrentFile() == null && urlParams['embed'] != '1')
  6799. {
  6800. this.showSplash();
  6801. }
  6802. });
  6803. }
  6804. }
  6805. };
  6806. /**
  6807. * Imports the given zip file.
  6808. */
  6809. EditorUi.prototype.importZipFile = function(file, success, onerror)
  6810. {
  6811. var ui = this;
  6812. var delayed = mxUtils.bind(this, function()
  6813. {
  6814. this.loadingExtensions = false;
  6815. if (typeof JSZip !== 'undefined')
  6816. {
  6817. JSZip.loadAsync(file)
  6818. .then(function(zip)
  6819. {
  6820. if (Object.keys(zip.files).length == 0)
  6821. {
  6822. onerror();
  6823. }
  6824. else
  6825. {
  6826. var gliffyLatestVer = {version: 0};
  6827. var drawioFound = false;
  6828. zip.forEach(function (relativePath, zipEntry)
  6829. {
  6830. var name = zipEntry.name.toLowerCase();
  6831. if (name == 'diagram/diagram.xml') //draw.io zip format has the latest diagram version at diagram/diagram.xml
  6832. {
  6833. drawioFound = true;
  6834. zipEntry.async("string").then(function(str){
  6835. if (str.indexOf('<mxfile ') == 0)
  6836. {
  6837. success(str);
  6838. }
  6839. else
  6840. {
  6841. onerror();
  6842. }
  6843. });
  6844. }
  6845. else if (name.indexOf('versions/') == 0) //Gliffy zip format has the versions inside versions folder
  6846. {
  6847. var version = parseInt(name.substr(9)); //9 is the length of versions/
  6848. if (version > gliffyLatestVer.version)
  6849. {
  6850. gliffyLatestVer = {version: version, zipEntry: zipEntry}
  6851. }
  6852. }
  6853. });
  6854. if (gliffyLatestVer.version > 0)
  6855. {
  6856. gliffyLatestVer.zipEntry.async("string").then(function(data)
  6857. {
  6858. if (!ui.isOffline() && new XMLHttpRequest().upload && ui.isRemoteFileFormat(data, file.name))
  6859. {
  6860. ui.parseFile(new Blob([data], {type: 'application/octet-stream'}), mxUtils.bind(this, function(xhr)
  6861. {
  6862. if (xhr.readyState == 4)
  6863. {
  6864. if (xhr.status >= 200 && xhr.status <= 299)
  6865. {
  6866. success(xhr.responseText);
  6867. }
  6868. else
  6869. {
  6870. onerror();
  6871. }
  6872. }
  6873. }), file.name);
  6874. }
  6875. else
  6876. {
  6877. onerror();
  6878. }
  6879. });
  6880. }
  6881. else if (!drawioFound)
  6882. {
  6883. onerror();
  6884. }
  6885. }
  6886. }, function (e) {
  6887. onerror(e);
  6888. });
  6889. }
  6890. else
  6891. {
  6892. onerror();
  6893. }
  6894. });
  6895. if (typeof JSZip === 'undefined' && !this.loadingExtensions && !this.isOffline(true))
  6896. {
  6897. this.loadingExtensions = true;
  6898. mxscript('js/extensions.min.js', delayed);
  6899. }
  6900. else
  6901. {
  6902. delayed();
  6903. }
  6904. };
  6905. /**
  6906. * Imports the given XML into the existing diagram.
  6907. */
  6908. EditorUi.prototype.importFile = function(data, mimeType, dx, dy, w, h, filename,
  6909. done, file, crop, ignoreEmbeddedXml, evt)
  6910. {
  6911. crop = (crop != null) ? crop : true;
  6912. var async = false;
  6913. var cells = null;
  6914. var handleResult = mxUtils.bind(this, function(xml)
  6915. {
  6916. var importedCells = null;
  6917. if (xml != null && xml.substring(0, 10) == '<mxlibrary')
  6918. {
  6919. this.loadLibrary(new LocalLibrary(this, xml, filename));
  6920. }
  6921. else
  6922. {
  6923. importedCells = this.importXml(xml, dx, dy, crop, null,
  6924. (evt != null) ? mxEvent.isControlDown(evt) : null);
  6925. }
  6926. if (done != null)
  6927. {
  6928. done(importedCells);
  6929. }
  6930. });
  6931. if (mimeType.substring(0, 5) == 'image')
  6932. {
  6933. var containsModel = false;
  6934. if (mimeType.substring(0, 9) == 'image/png')
  6935. {
  6936. var xml = (ignoreEmbeddedXml) ? null : this.extractGraphModelFromPng(data);
  6937. if (xml != null && xml.length > 0)
  6938. {
  6939. cells = this.importXml(xml, dx, dy, crop, null, (evt != null) ?
  6940. mxEvent.isControlDown(evt) : null);
  6941. containsModel = true;
  6942. }
  6943. }
  6944. if (!containsModel)
  6945. {
  6946. var graph = this.editor.graph;
  6947. // Strips encoding bit (eg. ;base64,) for cell style
  6948. var semi = data.indexOf(';');
  6949. if (semi > 0)
  6950. {
  6951. data = data.substring(0, semi) + data.substring(data.indexOf(',', semi + 1));
  6952. }
  6953. if (crop && graph.isGridEnabled())
  6954. {
  6955. dx = graph.snap(dx);
  6956. dy = graph.snap(dy);
  6957. }
  6958. cells = [graph.insertVertex(null, null, '', dx, dy, w, h,
  6959. 'shape=image;verticalLabelPosition=bottom;labelBackgroundColor=#ffffff;' +
  6960. 'verticalAlign=top;aspect=fixed;imageAspect=0;image=' + data + ';')];
  6961. }
  6962. }
  6963. else if (/(\.*<graphml )/.test(data))
  6964. {
  6965. async = true;
  6966. this.importGraphML(data, handleResult);
  6967. }
  6968. else if (file != null && filename != null && ((/(\.v(dx|sdx?))($|\?)/i.test(filename)) || /(\.vs(x|sx?))($|\?)/i.test(filename)))
  6969. {
  6970. // LATER: done and async are a hack before making this asynchronous
  6971. async = true;
  6972. this.importVisio(file, handleResult);
  6973. }
  6974. else if (!this.isOffline() && new XMLHttpRequest().upload && this.isRemoteFileFormat(data, filename))
  6975. {
  6976. // LATER: done and async are a hack before making this asynchronous
  6977. async = true;
  6978. // Returns empty cells array as it is aysynchronous
  6979. this.parseFile((file != null) ? file : new Blob([data], {type: 'application/octet-stream'}), mxUtils.bind(this, function(xhr)
  6980. {
  6981. if (xhr.readyState == 4)
  6982. {
  6983. if (xhr.status >= 200 && xhr.status <= 299)
  6984. {
  6985. handleResult(xhr.responseText);
  6986. }
  6987. else if (done != null)
  6988. {
  6989. done(null);
  6990. }
  6991. }
  6992. }), filename);
  6993. }
  6994. else if (data.indexOf('PK') == 0 && file != null)
  6995. {
  6996. async = true;
  6997. this.importZipFile(file, handleResult, mxUtils.bind(this, function()
  6998. {
  6999. //If importing as a zip file failed, just insert as text
  7000. cells = this.insertTextAt(this.validateFileData(data), dx, dy, true, null, crop);
  7001. done(cells);
  7002. }));
  7003. }
  7004. else if (!/(\.v(sd|dx))($|\?)/i.test(filename) && !/(\.vs(s|x))($|\?)/i.test(filename))
  7005. {
  7006. cells = this.insertTextAt(this.validateFileData(data), dx, dy, true,
  7007. null, crop, null, (evt != null) ? mxEvent.isControlDown(evt) : null);
  7008. }
  7009. if (!async && done != null)
  7010. {
  7011. done(cells);
  7012. }
  7013. return cells;
  7014. };
  7015. /**
  7016. *
  7017. */
  7018. EditorUi.prototype.importFiles = function(files, x, y, maxSize, fn, resultFn, filterFn, barrierFn,
  7019. resizeDialog, maxBytes, resampleThreshold, ignoreEmbeddedXml, evt)
  7020. {
  7021. maxSize = (maxSize != null) ? maxSize : this.maxImageSize;
  7022. maxBytes = (maxBytes != null) ? maxBytes : this.maxImageBytes;
  7023. var crop = x != null && y != null;
  7024. var resizeImages = true;
  7025. x = (x != null) ? x : 0;
  7026. y = (y != null) ? y : 0;
  7027. // Checks if large images are imported
  7028. var largeImages = false;
  7029. if (!mxClient.IS_CHROMEAPP && files != null)
  7030. {
  7031. var thresh = resampleThreshold || this.resampleThreshold;
  7032. for (var i = 0; i < files.length; i++)
  7033. {
  7034. if (files[i].type.substring(0, 6) == 'image/' && files[i].size > thresh)
  7035. {
  7036. largeImages = true;
  7037. break;
  7038. }
  7039. }
  7040. }
  7041. var doImportFiles = mxUtils.bind(this, function()
  7042. {
  7043. var graph = this.editor.graph;
  7044. var gs = graph.gridSize;
  7045. fn = (fn != null) ? fn : mxUtils.bind(this, function(data, mimeType, x, y, w, h, filename, done, file)
  7046. {
  7047. try
  7048. {
  7049. if (data != null && data.substring(0, 10) == '<mxlibrary')
  7050. {
  7051. this.spinner.stop();
  7052. this.loadLibrary(new LocalLibrary(this, data, filename));
  7053. return null;
  7054. }
  7055. else
  7056. {
  7057. return this.importFile(data, mimeType, x, y, w, h, filename,
  7058. done, file, crop, ignoreEmbeddedXml, evt);
  7059. }
  7060. }
  7061. catch (e)
  7062. {
  7063. this.handleError(e);
  7064. return null;
  7065. }
  7066. });
  7067. resultFn = (resultFn != null) ? resultFn : mxUtils.bind(this, function(cells)
  7068. {
  7069. graph.setSelectionCells(cells);
  7070. });
  7071. if (this.spinner.spin(document.body, mxResources.get('loading')))
  7072. {
  7073. var count = files.length;
  7074. var remain = count;
  7075. var queue = [];
  7076. // Barrier waits for all files to be loaded asynchronously
  7077. var barrier = mxUtils.bind(this, function(index, fnc)
  7078. {
  7079. queue[index] = fnc;
  7080. if (--remain == 0)
  7081. {
  7082. this.spinner.stop();
  7083. if (barrierFn != null)
  7084. {
  7085. barrierFn(queue);
  7086. }
  7087. else
  7088. {
  7089. var cells = [];
  7090. graph.getModel().beginUpdate();
  7091. try
  7092. {
  7093. for (var j = 0; j < queue.length; j++)
  7094. {
  7095. var tmp = queue[j]();
  7096. if (tmp != null)
  7097. {
  7098. cells = cells.concat(tmp);
  7099. }
  7100. }
  7101. }
  7102. finally
  7103. {
  7104. graph.getModel().endUpdate();
  7105. }
  7106. }
  7107. resultFn(cells);
  7108. }
  7109. });
  7110. for (var i = 0; i < count; i++)
  7111. {
  7112. (mxUtils.bind(this, function(index)
  7113. {
  7114. var file = files[index];
  7115. if (file != null)
  7116. {
  7117. var reader = new FileReader();
  7118. reader.onload = mxUtils.bind(this, function(e)
  7119. {
  7120. if (filterFn == null || filterFn(file))
  7121. {
  7122. if (file.type.substring(0, 6) == 'image/')
  7123. {
  7124. if (file.type.substring(0, 9) == 'image/svg')
  7125. {
  7126. // Checks if SVG contains content attribute
  7127. var data = Graph.clipSvgDataUri(e.target.result);
  7128. var comma = data.indexOf(',');
  7129. var svgText = decodeURIComponent(escape(atob(data.substring(comma + 1))));
  7130. var root = mxUtils.parseXml(svgText);
  7131. var svgs = root.getElementsByTagName('svg');
  7132. if (svgs.length > 0)
  7133. {
  7134. var svgRoot = svgs[0];
  7135. var cont = (ignoreEmbeddedXml) ? null : svgRoot.getAttribute('content');
  7136. if (cont != null && cont.charAt(0) != '<' && cont.charAt(0) != '%')
  7137. {
  7138. cont = unescape((window.atob) ? atob(cont) : Base64.decode(cont, true));
  7139. }
  7140. if (cont != null && cont.charAt(0) == '%')
  7141. {
  7142. cont = decodeURIComponent(cont);
  7143. }
  7144. if (cont != null && (cont.substring(0, 8) === '<mxfile ' ||
  7145. cont.substring(0, 14) === '<mxGraphModel '))
  7146. {
  7147. barrier(index, mxUtils.bind(this, function()
  7148. {
  7149. return fn(cont, 'text/xml', x + index * gs, y + index * gs, 0, 0, file.name);
  7150. }));
  7151. }
  7152. else
  7153. {
  7154. // SVG needs special handling to add viewbox if missing and
  7155. // find initial size from SVG attributes (only for IE11)
  7156. barrier(index, mxUtils.bind(this, function()
  7157. {
  7158. try
  7159. {
  7160. var prefix = data.substring(0, comma + 1);
  7161. // Parses SVG and find width and height
  7162. if (root != null)
  7163. {
  7164. var svgs = root.getElementsByTagName('svg');
  7165. if (svgs.length > 0)
  7166. {
  7167. var svgRoot = svgs[0];
  7168. var w = svgRoot.getAttribute('width');
  7169. var h = svgRoot.getAttribute('height');
  7170. if (w != null && w.charAt(w.length - 1) != '%')
  7171. {
  7172. w = parseFloat(w);
  7173. }
  7174. else
  7175. {
  7176. w = NaN;
  7177. }
  7178. if (h != null && h.charAt(h.length - 1) != '%')
  7179. {
  7180. h = parseFloat(h);
  7181. }
  7182. else
  7183. {
  7184. h = NaN;
  7185. }
  7186. // Check if viewBox attribute already exists
  7187. var vb = svgRoot.getAttribute('viewBox');
  7188. if (vb == null || vb.length == 0)
  7189. {
  7190. svgRoot.setAttribute('viewBox', '0 0 ' + w + ' ' + h);
  7191. }
  7192. // Uses width and height from viewbox for
  7193. // missing width and height attributes
  7194. else if (isNaN(w) || isNaN(h))
  7195. {
  7196. var tokens = vb.split(' ');
  7197. if (tokens.length > 3)
  7198. {
  7199. w = parseFloat(tokens[2]);
  7200. h = parseFloat(tokens[3]);
  7201. }
  7202. }
  7203. data = Editor.createSvgDataUri(mxUtils.getXml(svgRoot));
  7204. var s = Math.min(1, Math.min(maxSize / Math.max(1, w)), maxSize / Math.max(1, h));
  7205. var cells = fn(data, file.type, x + index * gs, y + index * gs, Math.max(
  7206. 1, Math.round(w * s)), Math.max(1, Math.round(h * s)), file.name);
  7207. // Hack to fix width and height asynchronously
  7208. if (isNaN(w) || isNaN(h))
  7209. {
  7210. var img = new Image();
  7211. img.onload = mxUtils.bind(this, function()
  7212. {
  7213. w = Math.max(1, img.width);
  7214. h = Math.max(1, img.height);
  7215. cells[0].geometry.width = w;
  7216. cells[0].geometry.height = h;
  7217. svgRoot.setAttribute('viewBox', '0 0 ' + w + ' ' + h);
  7218. data = Editor.createSvgDataUri(mxUtils.getXml(svgRoot));
  7219. var semi = data.indexOf(';');
  7220. if (semi > 0)
  7221. {
  7222. data = data.substring(0, semi) + data.substring(data.indexOf(',', semi + 1));
  7223. }
  7224. graph.setCellStyles('image', data, [cells[0]]);
  7225. });
  7226. img.src = Editor.createSvgDataUri(mxUtils.getXml(svgRoot));
  7227. }
  7228. return cells;
  7229. }
  7230. }
  7231. }
  7232. catch (e)
  7233. {
  7234. // ignores any SVG parsing errors
  7235. }
  7236. return null;
  7237. }));
  7238. }
  7239. }
  7240. else
  7241. {
  7242. barrier(index, mxUtils.bind(this, function()
  7243. {
  7244. return null;
  7245. }));
  7246. }
  7247. }
  7248. else
  7249. {
  7250. // Checks if PNG+XML is available to bypass code below
  7251. var containsModel = false;
  7252. if (file.type == 'image/png')
  7253. {
  7254. var xml = (ignoreEmbeddedXml) ? null : this.extractGraphModelFromPng(e.target.result);
  7255. if (xml != null && xml.length > 0)
  7256. {
  7257. var img = new Image();
  7258. img.src = e.target.result;
  7259. barrier(index, mxUtils.bind(this, function()
  7260. {
  7261. return fn(xml, 'text/xml', x + index * gs, y + index * gs,
  7262. img.width, img.height, file.name);
  7263. }));
  7264. containsModel = true;
  7265. }
  7266. }
  7267. // Additional asynchronous step for finding image size
  7268. if (!containsModel)
  7269. {
  7270. // Cannot load local files in Chrome App
  7271. if (mxClient.IS_CHROMEAPP)
  7272. {
  7273. this.spinner.stop();
  7274. this.showError(mxResources.get('error'), mxResources.get('dragAndDropNotSupported'),
  7275. mxResources.get('cancel'), mxUtils.bind(this, function()
  7276. {
  7277. // Hides the dialog
  7278. }), null, mxResources.get('ok'), mxUtils.bind(this, function()
  7279. {
  7280. // Redirects to import function
  7281. this.actions.get('import').funct();
  7282. })
  7283. );
  7284. }
  7285. else
  7286. {
  7287. this.loadImage(e.target.result, mxUtils.bind(this, function(img)
  7288. {
  7289. this.resizeImage(img, e.target.result, mxUtils.bind(this, function(data2, w2, h2)
  7290. {
  7291. barrier(index, mxUtils.bind(this, function()
  7292. {
  7293. // Refuses to insert images above a certain size as they kill the app
  7294. if (data2 != null && data2.length < maxBytes)
  7295. {
  7296. var s = (!resizeImages || !this.isResampleImageSize(file.size, resampleThreshold)) ? 1 : Math.min(1, Math.min(maxSize / w2, maxSize / h2));
  7297. return fn(data2, file.type, x + index * gs, y + index * gs, Math.round(w2 * s), Math.round(h2 * s), file.name);
  7298. }
  7299. else
  7300. {
  7301. this.handleError({message: mxResources.get('imageTooBig')});
  7302. return null;
  7303. }
  7304. }));
  7305. }), resizeImages, maxSize, resampleThreshold, file.size);
  7306. }), mxUtils.bind(this, function()
  7307. {
  7308. this.handleError({message: mxResources.get('invalidOrMissingFile')});
  7309. }));
  7310. }
  7311. }
  7312. }
  7313. }
  7314. else
  7315. {
  7316. var data = e.target.result;
  7317. fn(data, file.type, x + index * gs, y + index * gs, 240, 160, file.name, function(cells)
  7318. {
  7319. barrier(index, function()
  7320. {
  7321. return cells;
  7322. });
  7323. }, file);
  7324. }
  7325. }
  7326. });
  7327. // Handles special cases
  7328. if (/(\.v(dx|sdx?))($|\?)/i.test(file.name) || /(\.vs(x|sx?))($|\?)/i.test(file.name))
  7329. {
  7330. fn(null, file.type, x + index * gs, y + index * gs, 240, 160, file.name, function(cells)
  7331. {
  7332. barrier(index, function()
  7333. {
  7334. return cells;
  7335. });
  7336. }, file);
  7337. }
  7338. else if (file.type.substring(0, 5) == 'image' || file.type == 'application/pdf')
  7339. {
  7340. reader.readAsDataURL(file);
  7341. }
  7342. else
  7343. {
  7344. reader.readAsText(file);
  7345. }
  7346. }
  7347. }))(i);
  7348. }
  7349. }
  7350. });
  7351. if (largeImages)
  7352. {
  7353. // Workaround for lost files array in async code
  7354. var tmp = [];
  7355. for (var i = 0; i < files.length; i++)
  7356. {
  7357. tmp.push(files[i]);
  7358. }
  7359. files = tmp;
  7360. this.confirmImageResize(function(doResize)
  7361. {
  7362. resizeImages = doResize;
  7363. doImportFiles();
  7364. }, resizeDialog);
  7365. }
  7366. else
  7367. {
  7368. doImportFiles();
  7369. }
  7370. };
  7371. /**
  7372. * Parses the file using XHR2 via the server. File can be a blob or file object.
  7373. * Filename is an optional parameter for blobs (that do not have a filename).
  7374. */
  7375. EditorUi.prototype.confirmImageResize = function(fn, force)
  7376. {
  7377. force = (force != null) ? force : false;
  7378. var resume = (this.spinner != null && this.spinner.pause != null) ? this.spinner.pause() : function() {};
  7379. var resizeImages = (isLocalStorage || mxClient.IS_CHROMEAPP) ? mxSettings.getResizeImages() : null;
  7380. var wrapper = function(remember, resize)
  7381. {
  7382. if (remember || force)
  7383. {
  7384. mxSettings.setResizeImages((remember) ? resize : null);
  7385. mxSettings.save();
  7386. }
  7387. resume();
  7388. fn(resize);
  7389. };
  7390. if (resizeImages != null && !force)
  7391. {
  7392. wrapper(false, resizeImages);
  7393. }
  7394. else
  7395. {
  7396. this.showDialog(new ConfirmDialog(this, mxResources.get('resizeLargeImages'),
  7397. function(remember)
  7398. {
  7399. wrapper(remember, true);
  7400. },
  7401. function(remember)
  7402. {
  7403. wrapper(remember, false);
  7404. }, mxResources.get('resize'), mxResources.get('actualSize'),
  7405. '<img style="margin-top:8px;" src="' + Editor.loResImage + '"/>',
  7406. '<img style="margin-top:8px;" src="' + Editor.hiResImage + '"/>',
  7407. isLocalStorage || mxClient.IS_CHROMEAPP).container, 340,
  7408. (isLocalStorage || mxClient.IS_CHROMEAPP) ? 220 : 200, true, true);
  7409. }
  7410. };
  7411. /**
  7412. * Parses the file using XHR2 via the server. File can be a blob or file object.
  7413. * Filename is an optional parameter for blobs (that do not have a filename).
  7414. */
  7415. EditorUi.prototype.parseFile = function(file, fn, filename)
  7416. {
  7417. filename = (filename != null) ? filename : file.name;
  7418. var formData = new FormData();
  7419. formData.append('format', 'xml');
  7420. formData.append('upfile', file, filename);
  7421. var xhr = new XMLHttpRequest();
  7422. xhr.open('POST', OPEN_URL);
  7423. xhr.onreadystatechange = function()
  7424. {
  7425. fn(xhr);
  7426. };
  7427. xhr.send(formData);
  7428. try
  7429. {
  7430. EditorUi.logEvent({category: 'GLIFFY-IMPORT-FILE',
  7431. action: 'size_' + file.size});
  7432. }
  7433. catch (e)
  7434. {
  7435. // ignore
  7436. }
  7437. };
  7438. /**
  7439. *
  7440. */
  7441. EditorUi.prototype.isResampleImageSize = function(size, thresh)
  7442. {
  7443. thresh = (thresh != null) ? thresh : this.resampleThreshold;
  7444. return size > thresh;
  7445. };
  7446. /**
  7447. * Resizes the given image if <maxImageBytes> is not null.
  7448. */
  7449. EditorUi.prototype.resizeImage = function(img, data, fn, enabled, maxSize, thresh, fileSize)
  7450. {
  7451. maxSize = (maxSize != null) ? maxSize : this.maxImageSize;
  7452. var w = Math.max(1, img.width);
  7453. var h = Math.max(1, img.height);
  7454. if (enabled && this.isResampleImageSize((fileSize != null) ? fileSize : data.length, thresh))
  7455. {
  7456. try
  7457. {
  7458. var factor = Math.max(w / maxSize, h / maxSize);
  7459. if (factor > 1)
  7460. {
  7461. var w2 = Math.round(w / factor);
  7462. var h2 = Math.round(h / factor);
  7463. var canvas = document.createElement('canvas');
  7464. canvas.width = w2;
  7465. canvas.height = h2;
  7466. var ctx = canvas.getContext('2d');
  7467. ctx.drawImage(img, 0, 0, w2, h2);
  7468. var tmp = canvas.toDataURL();
  7469. // Uses new image if smaller
  7470. if (tmp.length < data.length)
  7471. {
  7472. // Checks if the image is empty by comparing
  7473. // with an empty image of the same size
  7474. var canvas2 = document.createElement('canvas');
  7475. canvas2.width = w2;
  7476. canvas2.height = h2;
  7477. var tmp2 = canvas2.toDataURL();
  7478. if (tmp !== tmp2)
  7479. {
  7480. data = tmp;
  7481. w = w2;
  7482. h = h2;
  7483. }
  7484. }
  7485. }
  7486. }
  7487. catch (e)
  7488. {
  7489. // ignores image scaling errors
  7490. }
  7491. }
  7492. fn(data, w, h);
  7493. };
  7494. /**
  7495. * Extracts the XML from the compressed or non-compressed text chunk.
  7496. */
  7497. EditorUi.prototype.extractGraphModelFromPng = function(data)
  7498. {
  7499. return Editor.extractGraphModelFromPng(data);
  7500. };
  7501. /**
  7502. * Loads the image from the given URI.
  7503. *
  7504. * @param {number} dx X-coordinate of the translation.
  7505. * @param {number} dy Y-coordinate of the translation.
  7506. */
  7507. EditorUi.prototype.loadImage = function(uri, onload, onerror)
  7508. {
  7509. try
  7510. {
  7511. var img = new Image();
  7512. img.onload = function()
  7513. {
  7514. img.width = (img.width > 0) ? img.width : 120;
  7515. img.height = (img.height > 0) ? img.height : 120;
  7516. onload(img);
  7517. };
  7518. if (onerror != null)
  7519. {
  7520. img.onerror = onerror;
  7521. };
  7522. img.src = uri;
  7523. }
  7524. catch (e)
  7525. {
  7526. if (onerror != null)
  7527. {
  7528. onerror(e);
  7529. }
  7530. else
  7531. {
  7532. throw e;
  7533. }
  7534. }
  7535. };
  7536. // Initializes the user interface
  7537. var editorUiInit = EditorUi.prototype.init;
  7538. EditorUi.prototype.init = function()
  7539. {
  7540. mxStencilRegistry.allowEval = mxStencilRegistry.allowEval && !this.isOfflineApp();
  7541. // Must be set before UI is created in superclass
  7542. if (typeof window.mxSettings !== 'undefined')
  7543. {
  7544. this.formatWidth = mxSettings.getFormatWidth();
  7545. }
  7546. var ui = this;
  7547. var graph = this.editor.graph;
  7548. if (Editor.isDarkMode())
  7549. {
  7550. graph.view.defaultGridColor = mxGraphView.prototype.defaultDarkGridColor;
  7551. }
  7552. // Starts editing PlantUML data
  7553. graph.cellEditor.editPlantUmlData = function(cell, trigger, data)
  7554. {
  7555. var obj = JSON.parse(data);
  7556. var dlg = new TextareaDialog(ui, mxResources.get('plantUml') + ':',
  7557. obj.data, function(text)
  7558. {
  7559. if (text != null)
  7560. {
  7561. if (ui.spinner.spin(document.body, mxResources.get('inserting')))
  7562. {
  7563. ui.generatePlantUmlImage(text, obj.format, function(data, w, h)
  7564. {
  7565. ui.spinner.stop();
  7566. graph.getModel().beginUpdate();
  7567. try
  7568. {
  7569. if (obj.format == 'txt')
  7570. {
  7571. graph.labelChanged(cell, '<pre>' + data + '</pre>');
  7572. graph.updateCellSize(cell, true);
  7573. }
  7574. else
  7575. {
  7576. graph.setCellStyles('image', ui.convertDataUri(data), [cell]);
  7577. var geo = graph.model.getGeometry(cell);
  7578. if (geo != null)
  7579. {
  7580. geo = geo.clone();
  7581. geo.width = w;
  7582. geo.height = h;
  7583. graph.cellsResized([cell], [geo], false);
  7584. }
  7585. }
  7586. graph.setAttributeForCell(cell, 'plantUmlData',
  7587. JSON.stringify({data: text, format: obj.format}));
  7588. }
  7589. finally
  7590. {
  7591. graph.getModel().endUpdate();
  7592. }
  7593. }, function(e)
  7594. {
  7595. ui.handleError(e);
  7596. });
  7597. }
  7598. }
  7599. }, null, null, 400, 220);
  7600. ui.showDialog(dlg.container, 420, 300, true, true);
  7601. dlg.init();
  7602. };
  7603. // Starts editing Mermaid data
  7604. graph.cellEditor.editMermaidData = function(cell, trigger, data)
  7605. {
  7606. var obj = JSON.parse(data);
  7607. var dlg = new TextareaDialog(ui, mxResources.get('mermaid') + ':',
  7608. obj.data, function(text)
  7609. {
  7610. if (text != null)
  7611. {
  7612. if (ui.spinner.spin(document.body, mxResources.get('inserting')))
  7613. {
  7614. ui.generateMermaidImage(text, obj.config, function(data, w, h)
  7615. {
  7616. ui.spinner.stop();
  7617. graph.getModel().beginUpdate();
  7618. try
  7619. {
  7620. graph.setCellStyles('image', data, [cell]);
  7621. var geo = graph.model.getGeometry(cell);
  7622. if (geo != null)
  7623. {
  7624. geo = geo.clone();
  7625. geo.width = Math.max(geo.width, w);
  7626. geo.height = Math.max(geo.height, h);
  7627. graph.cellsResized([cell], [geo], false);
  7628. }
  7629. graph.setAttributeForCell(cell, 'mermaidData',
  7630. JSON.stringify({data: text, config:
  7631. obj.config}, null, 2));
  7632. }
  7633. finally
  7634. {
  7635. graph.getModel().endUpdate();
  7636. }
  7637. }, function(e)
  7638. {
  7639. ui.handleError(e);
  7640. });
  7641. }
  7642. }
  7643. }, null, null, 400, 220);
  7644. ui.showDialog(dlg.container, 420, 300, true, true);
  7645. dlg.init();
  7646. };
  7647. // Overrides function to add editing for Plant UML.
  7648. var cellEditorStartEditing = graph.cellEditor.startEditing;
  7649. graph.cellEditor.startEditing = function(cell, trigger)
  7650. {
  7651. try
  7652. {
  7653. var data = this.graph.getAttributeForCell(cell, 'plantUmlData');
  7654. if (data != null)
  7655. {
  7656. this.editPlantUmlData(cell, trigger, data);
  7657. }
  7658. else
  7659. {
  7660. data = this.graph.getAttributeForCell(cell, 'mermaidData');
  7661. if (data != null)
  7662. {
  7663. this.editMermaidData(cell, trigger, data);
  7664. }
  7665. else
  7666. {
  7667. var style = graph.getCellStyle(cell);
  7668. if (mxUtils.getValue(style, 'metaEdit', '0') == '1')
  7669. {
  7670. ui.showDataDialog(cell);
  7671. }
  7672. else
  7673. {
  7674. cellEditorStartEditing.apply(this, arguments);
  7675. }
  7676. }
  7677. }
  7678. }
  7679. catch (e)
  7680. {
  7681. ui.handleError(e);
  7682. }
  7683. };
  7684. // Redirects custom link title via UI for page links
  7685. graph.getLinkTitle = function(href)
  7686. {
  7687. return ui.getLinkTitle(href);
  7688. };
  7689. // Redirects custom link via UI for page link handling
  7690. graph.customLinkClicked = function(link)
  7691. {
  7692. var done = false;
  7693. try
  7694. {
  7695. ui.handleCustomLink(link);
  7696. done = true;
  7697. }
  7698. catch (e)
  7699. {
  7700. ui.handleError(e);
  7701. }
  7702. return done;
  7703. };
  7704. // Extends clear default style to clear persisted settings
  7705. var clearDefaultStyle = this.clearDefaultStyle;
  7706. this.clearDefaultStyle = function()
  7707. {
  7708. clearDefaultStyle.apply(this, arguments);
  7709. };
  7710. // Sets help link for placeholders
  7711. if (!this.isOffline() && typeof window.EditDataDialog !== 'undefined')
  7712. {
  7713. EditDataDialog.placeholderHelpLink = 'https://www.diagrams.net/doc/faq/predefined-placeholders';
  7714. }
  7715. if (/viewer\.diagrams\.net$/.test(window.location.hostname) ||
  7716. /embed\.diagrams\.net$/.test(window.location.hostname))
  7717. {
  7718. this.editor.editBlankUrl = 'https://app.diagrams.net/';
  7719. }
  7720. // Passes dev mode to new window
  7721. var editorGetEditBlankUrl = ui.editor.getEditBlankUrl;
  7722. this.editor.getEditBlankUrl = function(params)
  7723. {
  7724. params = (params != null) ? params : '';
  7725. if (urlParams['dev'] == '1')
  7726. {
  7727. params += ((params.length > 0) ? '&' : '?') + 'dev=1';
  7728. }
  7729. return editorGetEditBlankUrl.apply(this, arguments);
  7730. };
  7731. // For chromeless mode and lightbox mode in viewer
  7732. // Must be overridden before supercall to be applied
  7733. // in case of chromeless initialization
  7734. var graphAddClickHandler = graph.addClickHandler;
  7735. graph.addClickHandler = function(highlight, beforeClick, onClick)
  7736. {
  7737. var tmp = beforeClick;
  7738. beforeClick = function(evt, href)
  7739. {
  7740. if (href == null)
  7741. {
  7742. var source = mxEvent.getSource(evt);
  7743. if (source.nodeName.toLowerCase() == 'a')
  7744. {
  7745. href = source.getAttribute('href');
  7746. }
  7747. }
  7748. if (href != null && graph.isCustomLink(href) &&
  7749. (mxEvent.isTouchEvent(evt) ||
  7750. !mxEvent.isPopupTrigger(evt)) &&
  7751. graph.customLinkClicked(href))
  7752. {
  7753. mxEvent.consume(evt);
  7754. }
  7755. if (tmp != null)
  7756. {
  7757. tmp(evt, href);
  7758. }
  7759. };
  7760. // For some reason, local argument override is not enough in this case...
  7761. graphAddClickHandler.call(this, highlight, beforeClick, onClick);
  7762. };
  7763. editorUiInit.apply(this, arguments);
  7764. if (mxClient.IS_SVG)
  7765. {
  7766. // LATER: Add shadow for labels in graph.container (eg. math, NO_FO), scaling
  7767. this.editor.graph.addSvgShadow(graph.view.canvas.ownerSVGElement, null, true);
  7768. }
  7769. if (this.menus != null)
  7770. {
  7771. var menusAddPopupMenuEditItems = Menus.prototype.addPopupMenuEditItems;
  7772. // Inserts copyAsImage into popup menu
  7773. this.menus.addPopupMenuEditItems = function(menu, cell, evt)
  7774. {
  7775. if (ui.editor.graph.isSelectionEmpty())
  7776. {
  7777. menusAddPopupMenuEditItems.apply(this, arguments);
  7778. }
  7779. else
  7780. {
  7781. ui.menus.addMenuItems(menu, ['delete', '-', 'cut', 'copy',
  7782. 'copyAsImage', '-', 'duplicate'], null, evt);
  7783. }
  7784. };
  7785. }
  7786. // Overrides print dialog size
  7787. ui.actions.get('print').funct = function()
  7788. {
  7789. ui.showDialog(new PrintDialog(ui).container, 360,
  7790. (ui.pages != null && ui.pages.length > 1) ?
  7791. 450 : 370, true, true);
  7792. };
  7793. // Specifies the default filename
  7794. this.defaultFilename = mxResources.get('untitledDiagram');
  7795. // Adds export for %page%, %pagenumber% and %pagecount% placeholders
  7796. var graphGetExportVariables = graph.getExportVariables;
  7797. graph.getExportVariables = function()
  7798. {
  7799. var vars = graphGetExportVariables.apply(this, arguments);
  7800. var file = ui.getCurrentFile();
  7801. if (file != null)
  7802. {
  7803. vars['filename'] = file.getTitle();
  7804. }
  7805. vars['pagecount'] = (ui.pages != null) ? ui.pages.length : 1;
  7806. vars['page'] = (ui.currentPage != null) ? ui.currentPage.getName() : '';
  7807. vars['pagenumber'] = (ui.pages != null && ui.currentPage != null) ?
  7808. mxUtils.indexOf(ui.pages, ui.currentPage) + 1 : 1;
  7809. return vars;
  7810. };
  7811. // Adds %page%, %pagenumber% and %pagecount% placeholders
  7812. var graphGetGlobalVariable = graph.getGlobalVariable;
  7813. graph.getGlobalVariable = function(name)
  7814. {
  7815. var file = ui.getCurrentFile();
  7816. if (name == 'filename' && file != null)
  7817. {
  7818. return file.getTitle();
  7819. }
  7820. else if (name == 'page' && ui.currentPage != null)
  7821. {
  7822. return ui.currentPage.getName();
  7823. }
  7824. else if (name == 'pagenumber')
  7825. {
  7826. if (ui.currentPage != null && ui.pages != null)
  7827. {
  7828. return mxUtils.indexOf(ui.pages, ui.currentPage) + 1;
  7829. }
  7830. else
  7831. {
  7832. return 1;
  7833. }
  7834. }
  7835. else if (name == 'pagecount')
  7836. {
  7837. return (ui.pages != null) ? ui.pages.length : 1;
  7838. }
  7839. return graphGetGlobalVariable.apply(this, arguments);
  7840. };
  7841. var graphLabelLinkClicked = graph.labelLinkClicked;
  7842. graph.labelLinkClicked = function(state, elt, evt)
  7843. {
  7844. var href = elt.getAttribute('href');
  7845. if (href != null && graph.isCustomLink(href) &&
  7846. (mxEvent.isTouchEvent(evt) ||
  7847. !mxEvent.isPopupTrigger(evt)))
  7848. {
  7849. // Active links are moved to the hint
  7850. if (!graph.isEnabled() || (state != null && graph.isCellLocked(state.cell)))
  7851. {
  7852. graph.customLinkClicked(href);
  7853. // Resets rubberband after click on locked cell
  7854. graph.getRubberband().reset();
  7855. }
  7856. mxEvent.consume(evt);
  7857. }
  7858. else
  7859. {
  7860. graphLabelLinkClicked.apply(this, arguments);
  7861. }
  7862. };
  7863. // Overrides editor filename
  7864. this.editor.getOrCreateFilename = function()
  7865. {
  7866. var filename = ui.defaultFilename;
  7867. var file = ui.getCurrentFile();
  7868. if (file != null)
  7869. {
  7870. filename = (file.getTitle() != null) ? file.getTitle() : filename;
  7871. }
  7872. return filename;
  7873. };
  7874. // Disables print action for standalone apps on iOS
  7875. // because there is no way to close the new window
  7876. // LATER: Use iframe for print, disable preview
  7877. var printAction = this.actions.get('print');
  7878. printAction.setEnabled(!mxClient.IS_IOS || !navigator.standalone);
  7879. printAction.visible = printAction.isEnabled();
  7880. // Installs additional keyboard shortcuts for editor
  7881. if (!this.editor.chromeless || this.editor.editable)
  7882. {
  7883. // Defines additional hotkeys
  7884. this.keyHandler.bindAction(70, true, 'findReplace'); // Ctrl+F
  7885. this.keyHandler.bindAction(67, true, 'copyStyle', true); // Ctrl+Shift+C
  7886. this.keyHandler.bindAction(86, true, 'pasteStyle', true); // Ctrl+Shift+V
  7887. this.keyHandler.bindAction(77, true, 'editGeometry', true); // Ctrl+Shift+M
  7888. this.keyHandler.bindAction(88, true, 'insertText', true); // Ctrl+Shift+X
  7889. this.keyHandler.bindAction(75, true, 'insertRectangle'); // Ctrl+K
  7890. this.keyHandler.bindAction(75, true, 'insertEllipse', true); // Ctrl+Shift+K
  7891. this.altShiftActions[83] = 'synchronize'; // Alt+Shift+S
  7892. this.installImagePasteHandler();
  7893. this.installNativeClipboardHandler();
  7894. };
  7895. // Creates the spinner
  7896. this.spinner = this.createSpinner(null, null, 24);
  7897. // Installs drag and drop handler for rich text editor
  7898. if (Graph.fileSupport)
  7899. {
  7900. graph.addListener(mxEvent.EDITING_STARTED, mxUtils.bind(this, function(evt)
  7901. {
  7902. // Setup the dnd listeners
  7903. var textElt = graph.cellEditor.text2;
  7904. var dropElt = null;
  7905. if (textElt != null)
  7906. {
  7907. mxEvent.addListener(textElt, 'dragleave', function(evt)
  7908. {
  7909. if (dropElt != null)
  7910. {
  7911. dropElt.parentNode.removeChild(dropElt);
  7912. dropElt = null;
  7913. }
  7914. evt.stopPropagation();
  7915. evt.preventDefault();
  7916. });
  7917. mxEvent.addListener(textElt, 'dragover', mxUtils.bind(this, function(evt)
  7918. {
  7919. // IE 10 does not implement pointer-events so it can't have a drop highlight
  7920. if (dropElt == null && (!mxClient.IS_IE || document.documentMode > 10))
  7921. {
  7922. dropElt = this.highlightElement(textElt);
  7923. }
  7924. evt.stopPropagation();
  7925. evt.preventDefault();
  7926. }));
  7927. mxEvent.addListener(textElt, 'drop', mxUtils.bind(this, function(evt)
  7928. {
  7929. if (dropElt != null)
  7930. {
  7931. dropElt.parentNode.removeChild(dropElt);
  7932. dropElt = null;
  7933. }
  7934. if (evt.dataTransfer.files.length > 0)
  7935. {
  7936. this.importFiles(evt.dataTransfer.files, 0, 0, this.maxImageSize, function(data, mimeType, x, y, w, h)
  7937. {
  7938. // Inserts image into current text box
  7939. graph.insertImage(data, w, h);
  7940. }, function()
  7941. {
  7942. // No post processing
  7943. }, function(file)
  7944. {
  7945. // Handles only images
  7946. return file.type.substring(0, 6) == 'image/';
  7947. }, function(queue)
  7948. {
  7949. // Invokes elements of queue in order
  7950. for (var i = 0; i < queue.length; i++)
  7951. {
  7952. queue[i]();
  7953. }
  7954. }, mxEvent.isControlDown(evt));
  7955. }
  7956. else if (mxUtils.indexOf(evt.dataTransfer.types, 'text/uri-list') >= 0)
  7957. {
  7958. var uri = evt.dataTransfer.getData('text/uri-list');
  7959. if ((/\.(gif|jpg|jpeg|tiff|png|svg)$/i).test(uri))
  7960. {
  7961. this.loadImage(decodeURIComponent(uri), mxUtils.bind(this, function(img)
  7962. {
  7963. var w = Math.max(1, img.width);
  7964. var h = Math.max(1, img.height);
  7965. var maxSize = this.maxImageSize;
  7966. var s = Math.min(1, Math.min(maxSize / Math.max(1, w)), maxSize / Math.max(1, h));
  7967. graph.insertImage(decodeURIComponent(uri), w * s, h * s);
  7968. }));
  7969. }
  7970. else
  7971. {
  7972. document.execCommand('insertHTML', false, evt.dataTransfer.getData('text/plain'));
  7973. }
  7974. }
  7975. else
  7976. {
  7977. if (mxUtils.indexOf(evt.dataTransfer.types, 'text/html') >= 0)
  7978. {
  7979. document.execCommand('insertHTML', false, evt.dataTransfer.getData('text/html'));
  7980. }
  7981. else if (mxUtils.indexOf(evt.dataTransfer.types, 'text/plain') >= 0)
  7982. {
  7983. document.execCommand('insertHTML', false, evt.dataTransfer.getData('text/plain'));
  7984. }
  7985. }
  7986. evt.stopPropagation();
  7987. evt.preventDefault();
  7988. }));
  7989. }
  7990. }));
  7991. }
  7992. // Adding mxRuler to editor
  7993. if (typeof window.mxSettings !== 'undefined')
  7994. {
  7995. var view = this.editor.graph.view;
  7996. view.setUnit(mxSettings.getUnit());
  7997. view.addListener('unitChanged', function(sender, evt)
  7998. {
  7999. mxSettings.setUnit(evt.getProperty('unit'));
  8000. mxSettings.save();
  8001. });
  8002. var showRuler = this.canvasSupported && document.documentMode != 9 &&
  8003. (urlParams['ruler'] == '1' || mxSettings.isRulerOn()) &&
  8004. (!this.editor.isChromelessView() || this.editor.editable);
  8005. this.ruler = (showRuler) ? new mxDualRuler(this, view.unit) : null;
  8006. this.refresh();
  8007. }
  8008. // Adds an element to edit the style in the footer in test mode
  8009. if (urlParams['styledev'] == '1')
  8010. {
  8011. var footer = document.getElementById('geFooter');
  8012. if (footer != null)
  8013. {
  8014. this.styleInput = document.createElement('input');
  8015. this.styleInput.setAttribute('type', 'text');
  8016. this.styleInput.style.position = 'absolute';
  8017. this.styleInput.style.top = '14px';
  8018. this.styleInput.style.left = '2px';
  8019. // Workaround for ignore right CSS property in FF
  8020. this.styleInput.style.width = '98%';
  8021. this.styleInput.style.visibility = 'hidden';
  8022. this.styleInput.style.opacity = '0.9';
  8023. mxEvent.addListener(this.styleInput, 'change', mxUtils.bind(this, function()
  8024. {
  8025. this.editor.graph.getModel().setStyle(this.editor.graph.getSelectionCell(), this.styleInput.value);
  8026. }));
  8027. footer.appendChild(this.styleInput);
  8028. this.editor.graph.getSelectionModel().addListener(mxEvent.CHANGE, mxUtils.bind(this, function(sender, evt)
  8029. {
  8030. if (this.editor.graph.getSelectionCount() > 0)
  8031. {
  8032. var cell = this.editor.graph.getSelectionCell();
  8033. var style = this.editor.graph.getModel().getStyle(cell);
  8034. this.styleInput.value = style || '';
  8035. this.styleInput.style.visibility = 'visible';
  8036. }
  8037. else
  8038. {
  8039. this.styleInput.style.visibility = 'hidden';
  8040. }
  8041. }));
  8042. }
  8043. var isSelectionAllowed = this.isSelectionAllowed;
  8044. this.isSelectionAllowed = function(evt)
  8045. {
  8046. if (mxEvent.getSource(evt) == this.styleInput)
  8047. {
  8048. return true;
  8049. }
  8050. return isSelectionAllowed.apply(this, arguments);
  8051. };
  8052. }
  8053. // Removes info text in page
  8054. var info = document.getElementById('geInfo');
  8055. if (info != null)
  8056. {
  8057. info.parentNode.removeChild(info);
  8058. }
  8059. // Installs drag and drop handler for files
  8060. // Enables dropping files
  8061. if (Graph.fileSupport && (!this.editor.chromeless || this.editor.editable))
  8062. {
  8063. // Setup the dnd listeners
  8064. var dropElt = null;
  8065. mxEvent.addListener(graph.container, 'dragleave', function(evt)
  8066. {
  8067. if (graph.isEnabled())
  8068. {
  8069. if (dropElt != null)
  8070. {
  8071. dropElt.parentNode.removeChild(dropElt);
  8072. dropElt = null;
  8073. }
  8074. evt.stopPropagation();
  8075. evt.preventDefault();
  8076. }
  8077. });
  8078. mxEvent.addListener(graph.container, 'dragover', mxUtils.bind(this, function(evt)
  8079. {
  8080. // IE 10 does not implement pointer-events so it can't have a drop highlight
  8081. if (dropElt == null && (!mxClient.IS_IE || document.documentMode > 10))
  8082. {
  8083. dropElt = this.highlightElement(graph.container);
  8084. }
  8085. if (this.sidebar != null)
  8086. {
  8087. this.sidebar.hideTooltip();
  8088. }
  8089. evt.stopPropagation();
  8090. evt.preventDefault();
  8091. }));
  8092. mxEvent.addListener(graph.container, 'drop', mxUtils.bind(this, function(evt)
  8093. {
  8094. if (dropElt != null)
  8095. {
  8096. dropElt.parentNode.removeChild(dropElt);
  8097. dropElt = null;
  8098. }
  8099. if (graph.isEnabled())
  8100. {
  8101. var pt = mxUtils.convertPoint(graph.container, mxEvent.getClientX(evt), mxEvent.getClientY(evt));
  8102. var tr = graph.view.translate;
  8103. var scale = graph.view.scale;
  8104. var x = pt.x / scale - tr.x;
  8105. var y = pt.y / scale - tr.y;
  8106. if (evt.dataTransfer.files.length > 0)
  8107. {
  8108. if (mxEvent.isShiftDown(evt))
  8109. {
  8110. this.openFiles(evt.dataTransfer.files, true);
  8111. }
  8112. else
  8113. {
  8114. if (mxEvent.isAltDown(evt))
  8115. {
  8116. x = null;
  8117. y = null;
  8118. }
  8119. this.importFiles(evt.dataTransfer.files, x, y, this.maxImageSize, null, null, null,
  8120. null, mxEvent.isControlDown(evt), null, null, mxEvent.isShiftDown(evt), evt);
  8121. }
  8122. }
  8123. else
  8124. {
  8125. if (mxEvent.isAltDown(evt))
  8126. {
  8127. x = 0;
  8128. y = 0;
  8129. }
  8130. var uri = (mxUtils.indexOf(evt.dataTransfer.types, 'text/uri-list') >= 0) ?
  8131. evt.dataTransfer.getData('text/uri-list') : null;
  8132. var data = this.extractGraphModelFromEvent(evt, this.pages != null);
  8133. if (data != null)
  8134. {
  8135. graph.setSelectionCells(this.importXml(data, x, y, true));
  8136. }
  8137. else if (mxUtils.indexOf(evt.dataTransfer.types, 'text/html') >= 0)
  8138. {
  8139. var html = evt.dataTransfer.getData('text/html');
  8140. var div = document.createElement('div');
  8141. div.innerHTML = graph.sanitizeHtml(html);
  8142. // The default is based on the extension
  8143. var asImage = null;
  8144. // Extracts single image
  8145. var imgs = div.getElementsByTagName('img');
  8146. if (imgs != null && imgs.length == 1)
  8147. {
  8148. html = imgs[0].getAttribute('src');
  8149. if (html == null)
  8150. {
  8151. html = imgs[0].getAttribute('srcset');
  8152. }
  8153. // Handles special case where the src attribute has no valid extension
  8154. // in which case the text would be inserted as text with a link
  8155. if (!(/\.(gif|jpg|jpeg|tiff|png|svg)$/i).test(html))
  8156. {
  8157. asImage = true;
  8158. }
  8159. }
  8160. else
  8161. {
  8162. // Extracts single link
  8163. var a = div.getElementsByTagName('a');
  8164. if (a != null && a.length == 1)
  8165. {
  8166. html = a[0].getAttribute('href');
  8167. }
  8168. else
  8169. {
  8170. // Extracts preformatted text
  8171. var pre = div.getElementsByTagName('pre');
  8172. if (pre != null && pre.length == 1)
  8173. {
  8174. html = mxUtils.getTextContent(pre[0]);
  8175. }
  8176. }
  8177. }
  8178. var resizeImages = true;
  8179. var doInsert = mxUtils.bind(this, function()
  8180. {
  8181. graph.setSelectionCells(this.insertTextAt(html, x, y, true,
  8182. asImage, null, resizeImages, mxEvent.isControlDown(evt)));
  8183. });
  8184. if (asImage && html != null && html.length > this.resampleThreshold)
  8185. {
  8186. this.confirmImageResize(function(doResize)
  8187. {
  8188. resizeImages = doResize;
  8189. doInsert();
  8190. }, mxEvent.isControlDown(evt));
  8191. }
  8192. else
  8193. {
  8194. doInsert();
  8195. }
  8196. }
  8197. else if (uri != null && (/\.(gif|jpg|jpeg|tiff|png|svg)$/i).test(uri))
  8198. {
  8199. this.loadImage(decodeURIComponent(uri), mxUtils.bind(this, function(img)
  8200. {
  8201. var w = Math.max(1, img.width);
  8202. var h = Math.max(1, img.height);
  8203. var maxSize = this.maxImageSize;
  8204. var s = Math.min(1, Math.min(maxSize / Math.max(1, w)), maxSize / Math.max(1, h));
  8205. graph.setSelectionCell(graph.insertVertex(null, null, '', x, y, w * s, h * s,
  8206. 'shape=image;verticalLabelPosition=bottom;labelBackgroundColor=#ffffff;' +
  8207. 'verticalAlign=top;aspect=fixed;imageAspect=0;image=' + uri + ';'));
  8208. }), mxUtils.bind(this, function(img)
  8209. {
  8210. graph.setSelectionCells(this.insertTextAt(uri, x, y, true));
  8211. }));
  8212. }
  8213. else if (mxUtils.indexOf(evt.dataTransfer.types, 'text/plain') >= 0)
  8214. {
  8215. graph.setSelectionCells(this.insertTextAt(evt.dataTransfer.getData('text/plain'), x, y, true));
  8216. }
  8217. }
  8218. }
  8219. evt.stopPropagation();
  8220. evt.preventDefault();
  8221. }), false);
  8222. }
  8223. graph.enableFlowAnimation = true;
  8224. this.initPages();
  8225. // Embedded mode
  8226. if (urlParams['embed'] == '1')
  8227. {
  8228. this.initializeEmbedMode();
  8229. }
  8230. this.installSettings();
  8231. };
  8232. /**
  8233. * Installs handler for pasting image from clipboard.
  8234. */
  8235. EditorUi.prototype.installImagePasteHandler = function()
  8236. {
  8237. if (!mxClient.IS_IE)
  8238. {
  8239. var graph = this.editor.graph;
  8240. graph.container.addEventListener('paste', mxUtils.bind(this, function(evt)
  8241. {
  8242. if (!mxEvent.isConsumed(evt))
  8243. {
  8244. try
  8245. {
  8246. var data = (evt.clipboardData || evt.originalEvent.clipboardData);
  8247. var containsText = false;
  8248. // Workaround for asynchronous paste event processing in textInput
  8249. // is to ignore this event if it contains text/html/rtf (see below).
  8250. // NOTE: Image is not pasted into textInput so can't listen there.
  8251. for (var i = 0; i < data.types.length; i++)
  8252. {
  8253. if (data.types[i].substring(0, 5) === 'text/')
  8254. {
  8255. containsText = true;
  8256. break;
  8257. }
  8258. }
  8259. if (!containsText)
  8260. {
  8261. var items = data.items;
  8262. for (index in items)
  8263. {
  8264. var item = items[index];
  8265. if (item.kind === 'file')
  8266. {
  8267. if (graph.isEditing())
  8268. {
  8269. this.importFiles([item.getAsFile()], 0, 0, this.maxImageSize, function(data, mimeType, x, y, w, h)
  8270. {
  8271. // Inserts image into current text box
  8272. graph.insertImage(data, w, h);
  8273. }, function()
  8274. {
  8275. // No post processing
  8276. }, function(file)
  8277. {
  8278. // Handles only images
  8279. return file.type.substring(0, 6) == 'image/';
  8280. }, function(queue)
  8281. {
  8282. // Invokes elements of queue in order
  8283. for (var i = 0; i < queue.length; i++)
  8284. {
  8285. queue[i]();
  8286. }
  8287. });
  8288. }
  8289. else
  8290. {
  8291. var pt = this.editor.graph.getInsertPoint();
  8292. this.importFiles([item.getAsFile()], pt.x, pt.y, this.maxImageSize);
  8293. mxEvent.consume(evt);
  8294. }
  8295. break;
  8296. }
  8297. }
  8298. }
  8299. }
  8300. catch (e)
  8301. {
  8302. // ignore
  8303. }
  8304. }
  8305. }), false);
  8306. }
  8307. };
  8308. /**
  8309. * Installs the native clipboard support.
  8310. */
  8311. EditorUi.prototype.installNativeClipboardHandler = function()
  8312. {
  8313. var graph = this.editor.graph;
  8314. // Focused but invisible textarea during control or meta key events
  8315. // LATER: Disable text rendering to avoid delay while keeping focus
  8316. var textInput = document.createElement('div');
  8317. textInput.setAttribute('autocomplete', 'off');
  8318. textInput.setAttribute('autocorrect', 'off');
  8319. textInput.setAttribute('autocapitalize', 'off');
  8320. textInput.setAttribute('spellcheck', 'false');
  8321. textInput.style.textRendering = 'optimizeSpeed';
  8322. textInput.style.fontFamily = 'monospace';
  8323. textInput.style.wordBreak = 'break-all';
  8324. textInput.style.background = 'transparent';
  8325. textInput.style.color = 'transparent';
  8326. textInput.style.position = 'absolute';
  8327. textInput.style.whiteSpace = 'nowrap';
  8328. textInput.style.overflow = 'hidden';
  8329. textInput.style.display = 'block';
  8330. textInput.style.fontSize = '1';
  8331. textInput.style.zIndex = '-1';
  8332. textInput.style.resize = 'none';
  8333. textInput.style.outline = 'none';
  8334. textInput.style.width = '1px';
  8335. textInput.style.height = '1px';
  8336. mxUtils.setOpacity(textInput, 0);
  8337. textInput.contentEditable = true;
  8338. textInput.innerHTML = '&nbsp;';
  8339. var restoreFocus = false;
  8340. // Disables built-in cut, copy and paste shortcuts
  8341. this.keyHandler.bindControlKey(88, null);
  8342. this.keyHandler.bindControlKey(67, null);
  8343. this.keyHandler.bindControlKey(86, null);
  8344. // Shows a textare when control/cmd is pressed to handle native clipboard actions
  8345. mxEvent.addListener(document, 'keydown', mxUtils.bind(this, function(evt)
  8346. {
  8347. // No dialog visible
  8348. var source = mxEvent.getSource(evt);
  8349. if (graph.container != null && graph.isEnabled() && !graph.isMouseDown && !graph.isEditing() &&
  8350. this.dialog == null && source.nodeName != 'INPUT' && source.nodeName != 'TEXTAREA')
  8351. {
  8352. if (evt.keyCode == 224 /* FF */ || (!mxClient.IS_MAC && evt.keyCode == 17 /* Control */) ||
  8353. (mxClient.IS_MAC && (evt.keyCode == 91 || evt.keyCode == 93) /* Left/Right Meta */))
  8354. {
  8355. // Cannot use parentNode for check in IE
  8356. if (!restoreFocus)
  8357. {
  8358. // Avoid autoscroll but allow handling of all pass-through ctrl shortcuts
  8359. textInput.style.left = (graph.container.scrollLeft + 10) + 'px';
  8360. textInput.style.top = (graph.container.scrollTop + 10) + 'px';
  8361. graph.container.appendChild(textInput);
  8362. restoreFocus = true;
  8363. textInput.focus();
  8364. document.execCommand('selectAll', false, null);
  8365. }
  8366. }
  8367. }
  8368. }));
  8369. // Clears input and restores focus and selection
  8370. function clearInput()
  8371. {
  8372. window.setTimeout(function()
  8373. {
  8374. textInput.innerHTML = '&nbsp;';
  8375. textInput.focus();
  8376. document.execCommand('selectAll', false, null);
  8377. }, 0);
  8378. };
  8379. mxEvent.addListener(document, 'keyup', mxUtils.bind(this, function(evt)
  8380. {
  8381. // Workaround for asynchronous event read invalid in IE quirks mode
  8382. var keyCode = evt.keyCode;
  8383. // Asynchronous workaround for scroll to origin after paste if the
  8384. // Ctrl-key is not pressed for long enough in FF on Windows
  8385. window.setTimeout(mxUtils.bind(this, function()
  8386. {
  8387. if (restoreFocus && (keyCode == 224 /* FF */ || keyCode == 17 /* Control */ ||
  8388. keyCode == 91 /* MetaLeft */ || keyCode == 93 /* MetaRight */))
  8389. {
  8390. restoreFocus = false;
  8391. if (!graph.isEditing() && this.dialog == null && graph.container != null)
  8392. {
  8393. graph.container.focus();
  8394. }
  8395. textInput.parentNode.removeChild(textInput);
  8396. // Workaround for lost cursor in focused element
  8397. if (this.dialog == null)
  8398. {
  8399. mxUtils.clearSelection();
  8400. }
  8401. }
  8402. }), 0);
  8403. }));
  8404. mxEvent.addListener(textInput, 'copy', mxUtils.bind(this, function(evt)
  8405. {
  8406. if (graph.isEnabled())
  8407. {
  8408. try
  8409. {
  8410. mxClipboard.copy(graph);
  8411. this.copyCells(textInput);
  8412. clearInput();
  8413. }
  8414. catch (e)
  8415. {
  8416. this.handleError(e);
  8417. }
  8418. }
  8419. }));
  8420. mxEvent.addListener(textInput, 'cut', mxUtils.bind(this, function(evt)
  8421. {
  8422. if (graph.isEnabled())
  8423. {
  8424. try
  8425. {
  8426. mxClipboard.copy(graph);
  8427. this.copyCells(textInput, true);
  8428. clearInput();
  8429. }
  8430. catch (e)
  8431. {
  8432. this.handleError(e);
  8433. }
  8434. }
  8435. }));
  8436. mxEvent.addListener(textInput, 'paste', mxUtils.bind(this, function(evt)
  8437. {
  8438. if (graph.isEnabled() && !graph.isCellLocked(graph.getDefaultParent()))
  8439. {
  8440. textInput.innerHTML = '&nbsp;';
  8441. textInput.focus();
  8442. if (evt.clipboardData != null)
  8443. {
  8444. this.pasteCells(evt, textInput, true, true);
  8445. }
  8446. if (!mxEvent.isConsumed(evt))
  8447. {
  8448. window.setTimeout(mxUtils.bind(this, function()
  8449. {
  8450. this.pasteCells(evt, textInput, false, true);
  8451. }), 0);
  8452. }
  8453. }
  8454. }), true);
  8455. // Needed for IE11
  8456. var isSelectionAllowed2 = this.isSelectionAllowed;
  8457. this.isSelectionAllowed = function(evt)
  8458. {
  8459. if (mxEvent.getSource(evt) == textInput)
  8460. {
  8461. return true;
  8462. }
  8463. return isSelectionAllowed2.apply(this, arguments);
  8464. };
  8465. };
  8466. /**
  8467. *
  8468. */
  8469. EditorUi.prototype.getLinkTitle = function(href)
  8470. {
  8471. var title = Graph.prototype.getLinkTitle.apply(this, arguments);
  8472. if (href.substring(0, 13) == 'data:page/id,')
  8473. {
  8474. var comma = href.indexOf(',');
  8475. if (comma > 0)
  8476. {
  8477. var page = this.getPageById(href.substring(comma + 1));
  8478. if (page != null)
  8479. {
  8480. title = page.getName();
  8481. }
  8482. else
  8483. {
  8484. title = mxResources.get('pageNotFound');
  8485. }
  8486. }
  8487. }
  8488. else if (href.substring(0, 5) == 'data:')
  8489. {
  8490. title = mxResources.get('action');
  8491. }
  8492. return title;
  8493. };
  8494. /**
  8495. *
  8496. */
  8497. EditorUi.prototype.handleCustomLink = function(href)
  8498. {
  8499. if (href.substring(0, 13) == 'data:page/id,')
  8500. {
  8501. var comma = href.indexOf(',');
  8502. var page = this.getPageById(href.substring(comma + 1));
  8503. if (page)
  8504. {
  8505. this.selectPage(page)
  8506. }
  8507. else
  8508. {
  8509. // Needs fallback for missing resource in case of viewer lightbox
  8510. throw new Error(mxResources.get('pageNotFound') || 'Page not found');
  8511. }
  8512. }
  8513. else
  8514. {
  8515. this.editor.graph.handleCustomLink(href);
  8516. }
  8517. };
  8518. /**
  8519. *
  8520. */
  8521. EditorUi.prototype.isSettingsEnabled = function()
  8522. {
  8523. return typeof window.mxSettings !== 'undefined' && (isLocalStorage || mxClient.IS_CHROMEAPP);
  8524. };
  8525. /**
  8526. * Creates the format panel and adds overrides.
  8527. */
  8528. EditorUi.prototype.installSettings = function()
  8529. {
  8530. if (this.isSettingsEnabled())
  8531. {
  8532. // Gets recent colors from settings
  8533. ColorDialog.recentColors = mxSettings.getRecentColors();
  8534. // Avoids overridden values for changes in
  8535. // multiple windows and updates shared values
  8536. if (isLocalStorage)
  8537. {
  8538. try
  8539. {
  8540. window.addEventListener('storage', mxUtils.bind(this, function(evt)
  8541. {
  8542. if (evt.key == mxSettings.key)
  8543. {
  8544. mxSettings.load();
  8545. // Updates values
  8546. ColorDialog.recentColors = mxSettings.getRecentColors();
  8547. this.menus.customFonts = mxSettings.getCustomFonts();
  8548. }
  8549. }), false);
  8550. }
  8551. catch (e)
  8552. {
  8553. // ignore
  8554. }
  8555. }
  8556. // Updates UI to reflect current edge style
  8557. this.fireEvent(new mxEventObject('styleChanged', 'keys', [], 'values', [], 'cells', []));
  8558. /**
  8559. * Persists custom fonts.
  8560. */
  8561. this.menus.customFonts = mxSettings.getCustomFonts();
  8562. this.addListener('customFontsChanged', mxUtils.bind(this, function(sender, evt)
  8563. {
  8564. if (urlParams['ext-fonts'] != '1')
  8565. {
  8566. mxSettings.setCustomFonts(this.menus.customFonts);
  8567. }
  8568. else
  8569. {
  8570. var customFonts = evt.getProperty('customFonts');
  8571. this.menus.customFonts = customFonts;
  8572. mxSettings.setCustomFonts(customFonts);
  8573. }
  8574. mxSettings.save();
  8575. }));
  8576. /**
  8577. * Persists copy on connect switch.
  8578. */
  8579. this.editor.graph.connectionHandler.setCreateTarget(mxSettings.isCreateTarget());
  8580. this.fireEvent(new mxEventObject('copyConnectChanged'));
  8581. this.addListener('copyConnectChanged', mxUtils.bind(this, function(sender, evt)
  8582. {
  8583. mxSettings.setCreateTarget(this.editor.graph.connectionHandler.isCreateTarget());
  8584. mxSettings.save();
  8585. }));
  8586. /**
  8587. * Persists default page format.
  8588. */
  8589. this.editor.graph.pageFormat = mxSettings.getPageFormat();
  8590. this.addListener('pageFormatChanged', mxUtils.bind(this, function(sender, evt)
  8591. {
  8592. mxSettings.setPageFormat(this.editor.graph.pageFormat);
  8593. mxSettings.save();
  8594. }));
  8595. /**
  8596. * Persists default grid color.
  8597. */
  8598. this.editor.graph.view.gridColor = mxSettings.getGridColor(Editor.isDarkMode());
  8599. this.addListener('gridColorChanged', mxUtils.bind(this, function(sender, evt)
  8600. {
  8601. mxSettings.setGridColor(this.editor.graph.view.gridColor, Editor.isDarkMode());
  8602. mxSettings.save();
  8603. }));
  8604. /**
  8605. * Persists autosave switch in Chrome app.
  8606. */
  8607. if (mxClient.IS_CHROMEAPP || EditorUi.isElectronApp)
  8608. {
  8609. this.editor.addListener('autosaveChanged', mxUtils.bind(this, function(sender, evt)
  8610. {
  8611. mxSettings.setAutosave(this.editor.autosave);
  8612. mxSettings.save();
  8613. }));
  8614. this.editor.autosave = mxSettings.getAutosave();
  8615. }
  8616. if (this.sidebar != null)
  8617. {
  8618. if (urlParams['search-shapes'] != null && this.sidebar.searchShapes != null)
  8619. {
  8620. this.sidebar.searchShapes(decodeURIComponent(urlParams['search-shapes']));
  8621. this.sidebar.showEntries('search');
  8622. }
  8623. else
  8624. {
  8625. this.sidebar.showPalette('search', mxSettings.settings.search);
  8626. /**
  8627. * Shows scratchpad if never shown.
  8628. */
  8629. if ((!this.editor.chromeless || this.editor.editable) && (mxSettings.settings.isNew ||
  8630. parseInt(mxSettings.settings.version || 0) <= 8))
  8631. {
  8632. this.toggleScratchpad();
  8633. mxSettings.save();
  8634. }
  8635. }
  8636. }
  8637. // Saves app defaults for UI
  8638. this.addListener('formatWidthChanged', function()
  8639. {
  8640. mxSettings.setFormatWidth(this.formatWidth);
  8641. mxSettings.save();
  8642. });
  8643. }
  8644. };
  8645. /**
  8646. * Copies the given cells and XML to the clipboard as an embedded image.
  8647. */
  8648. EditorUi.prototype.copyImage = function(cells, xml, scale)
  8649. {
  8650. try
  8651. {
  8652. if (navigator.clipboard != null && this.spinner.spin(document.body, mxResources.get('exporting')))
  8653. {
  8654. this.editor.exportToCanvas(mxUtils.bind(this, function(canvas, svgRoot)
  8655. {
  8656. try
  8657. {
  8658. this.spinner.stop();
  8659. // KNOWN: SVG and delayed content currently not supported
  8660. var dataUrl = this.createImageDataUri(canvas, xml, 'png');
  8661. var w = parseInt(svgRoot.getAttribute('width'));
  8662. var h = parseInt(svgRoot.getAttribute('height'));
  8663. this.writeImageToClipboard(dataUrl, w, h, mxUtils.bind(this, function(e)
  8664. {
  8665. this.handleError(e);
  8666. }));
  8667. }
  8668. catch (e)
  8669. {
  8670. this.handleError(e);
  8671. }
  8672. }), null, null, null, mxUtils.bind(this, function(e)
  8673. {
  8674. this.spinner.stop();
  8675. this.handleError(e);
  8676. }), null, null, (scale != null) ? scale : 4,
  8677. this.editor.graph.background == null ||
  8678. this.editor.graph.background == mxConstants.NONE,
  8679. null, null, null, 10, null, null, true, null,
  8680. (cells.length > 0) ? cells : null);
  8681. }
  8682. }
  8683. catch (e)
  8684. {
  8685. this.handleError(e);
  8686. }
  8687. };
  8688. /**
  8689. * Copies the given cells and XML to the clipboard as an embedded image.
  8690. */
  8691. EditorUi.prototype.writeImageToClipboard = function(dataUrl, w, h, error)
  8692. {
  8693. var blob = this.base64ToBlob(dataUrl.substring(dataUrl.indexOf(',') + 1), 'image/png');
  8694. var html = '<img src="' + dataUrl + '" width="' + w + '" height="' + h + '">';
  8695. var cbi = new ClipboardItem({'image/png': blob,
  8696. 'text/html': new Blob([html], {type: 'text/html'})});
  8697. navigator.clipboard.write([cbi])['catch'](error);
  8698. };
  8699. /**
  8700. * Creates the format panel and adds overrides.
  8701. */
  8702. EditorUi.prototype.copyCells = function(elt, removeCells)
  8703. {
  8704. var graph = this.editor.graph;
  8705. if (!graph.isSelectionEmpty())
  8706. {
  8707. // Fixes cross-platform clipboard UTF8 issues by encoding as URI
  8708. var cells = mxUtils.sortCells(graph.model.getTopmostCells(graph.getSelectionCells()));
  8709. var xml = mxUtils.getXml(graph.encodeCells(cells));
  8710. mxUtils.setTextContent(elt, encodeURIComponent(xml));
  8711. if (removeCells)
  8712. {
  8713. graph.removeCells(cells, false);
  8714. graph.lastPasteXml = null;
  8715. }
  8716. else
  8717. {
  8718. graph.lastPasteXml = xml;
  8719. graph.pasteCounter = 0;
  8720. }
  8721. elt.focus();
  8722. document.execCommand('selectAll', false, null);
  8723. }
  8724. else
  8725. {
  8726. // Disables copy on focused element
  8727. elt.innerHTML = '';
  8728. }
  8729. };
  8730. /**
  8731. * Creates the format panel and adds overrides.
  8732. */
  8733. EditorUi.prototype.copyXml = function()
  8734. {
  8735. var cells = null;
  8736. if (Editor.enableNativeCipboard)
  8737. {
  8738. var graph = this.editor.graph;
  8739. if (!graph.isSelectionEmpty())
  8740. {
  8741. cells = mxUtils.sortCells(graph.getExportableCells(
  8742. graph.model.getTopmostCells(graph.getSelectionCells())));
  8743. var xml = mxUtils.getXml(graph.encodeCells(cells));
  8744. navigator.clipboard.writeText(xml);
  8745. }
  8746. }
  8747. return cells;
  8748. };
  8749. /**
  8750. * Creates the format panel and adds overrides.
  8751. */
  8752. EditorUi.prototype.pasteXml = function(xml, pasteAsLabel, compat, evt)
  8753. {
  8754. var graph = this.editor.graph;
  8755. var cells = null;
  8756. if (graph.lastPasteXml == xml)
  8757. {
  8758. graph.pasteCounter++;
  8759. }
  8760. else
  8761. {
  8762. graph.lastPasteXml = xml;
  8763. graph.pasteCounter = 0;
  8764. }
  8765. var dx = graph.pasteCounter * graph.gridSize;
  8766. if (compat || this.isCompatibleString(xml))
  8767. {
  8768. cells = this.importXml(xml, dx, dx);
  8769. graph.setSelectionCells(cells);
  8770. }
  8771. else if (pasteAsLabel && graph.getSelectionCount() == 1)
  8772. {
  8773. var cell = graph.getStartEditingCell(graph.getSelectionCell(), evt);
  8774. if ((/\.(gif|jpg|jpeg|tiff|png|svg)$/i).test(xml) &&
  8775. graph.getCurrentCellStyle(cell)[mxConstants.STYLE_SHAPE] == 'image')
  8776. {
  8777. graph.setCellStyles(mxConstants.STYLE_IMAGE, xml, [cell]);
  8778. }
  8779. else
  8780. {
  8781. graph.model.beginUpdate();
  8782. try
  8783. {
  8784. graph.labelChanged(cell, xml);
  8785. if (Graph.isLink(xml))
  8786. {
  8787. graph.setLinkForCell(cell, xml);
  8788. }
  8789. }
  8790. finally
  8791. {
  8792. graph.model.endUpdate();
  8793. }
  8794. }
  8795. graph.setSelectionCell(cell);
  8796. }
  8797. else
  8798. {
  8799. var pt = graph.getInsertPoint();
  8800. if (graph.isMouseInsertPoint())
  8801. {
  8802. dx = 0;
  8803. // No offset for insert at mouse position
  8804. if (graph.lastPasteXml == xml && graph.pasteCounter > 0)
  8805. {
  8806. graph.pasteCounter--;
  8807. }
  8808. }
  8809. cells = this.insertTextAt(xml, pt.x + dx, pt.y + dx, true);
  8810. graph.setSelectionCells(cells);
  8811. }
  8812. if (!graph.isSelectionEmpty())
  8813. {
  8814. graph.scrollCellToVisible(graph.getSelectionCell());
  8815. if (this.hoverIcons != null)
  8816. {
  8817. this.hoverIcons.update(graph.view.getState(graph.getSelectionCell()));
  8818. }
  8819. }
  8820. return cells;
  8821. };
  8822. /**
  8823. * Creates the format panel and adds overrides.
  8824. */
  8825. EditorUi.prototype.pasteCells = function(evt, realElt, useEvent, pasteAsLabel)
  8826. {
  8827. if (!mxEvent.isConsumed(evt))
  8828. {
  8829. var elt = realElt;
  8830. var asHtml = false;
  8831. if (useEvent && evt.clipboardData != null && evt.clipboardData.getData)
  8832. {
  8833. // Workaround for paste from IE11 where the page is copied
  8834. // as HTML while the data is only available via text/plain
  8835. var plain = evt.clipboardData.getData('text/plain');
  8836. var override = false;
  8837. if (plain != null && plain.length > 0 && plain.substring(0, 18) == '%3CmxGraphModel%3E')
  8838. {
  8839. var tmp = decodeURIComponent(plain);
  8840. if (this.isCompatibleString(tmp))
  8841. {
  8842. override = true;
  8843. plain = tmp;
  8844. }
  8845. }
  8846. var data = (!override) ? evt.clipboardData.getData('text/html') : null;
  8847. if (data != null && data.length > 0)
  8848. {
  8849. elt = this.parseHtmlData(data);
  8850. asHtml = elt.getAttribute('data-type') != 'text/plain';
  8851. }
  8852. else if (plain != null && plain.length > 0)
  8853. {
  8854. elt = document.createElement('div');
  8855. mxUtils.setTextContent(elt, data);
  8856. }
  8857. }
  8858. var spans = elt.getElementsByTagName('span');
  8859. if (spans != null && spans.length > 0 && spans[0].getAttribute('data-lucid-type') ===
  8860. 'application/vnd.lucid.chart.objects')
  8861. {
  8862. var content = spans[0].getAttribute('data-lucid-content');
  8863. if (content != null && content.length > 0)
  8864. {
  8865. this.convertLucidChart(content, mxUtils.bind(this, function(xml)
  8866. {
  8867. var graph = this.editor.graph;
  8868. if (graph.lastPasteXml == xml)
  8869. {
  8870. graph.pasteCounter++;
  8871. }
  8872. else
  8873. {
  8874. graph.lastPasteXml = xml;
  8875. graph.pasteCounter = 0;
  8876. }
  8877. var dx = graph.pasteCounter * graph.gridSize;
  8878. graph.setSelectionCells(this.importXml(xml, dx, dx));
  8879. graph.scrollCellToVisible(graph.getSelectionCell());
  8880. }), mxUtils.bind(this, function(e)
  8881. {
  8882. this.handleError(e);
  8883. }));
  8884. mxEvent.consume(evt);
  8885. }
  8886. }
  8887. else
  8888. {
  8889. // KNOWN: Paste from IE11 to other browsers on Windows
  8890. // seems to paste the contents of index.html
  8891. var xml = (asHtml) ? elt.innerHTML :
  8892. mxUtils.trim((elt.innerText == null) ?
  8893. mxUtils.getTextContent(elt) : elt.innerText);
  8894. var compat = false;
  8895. // Workaround for junk after XML in VM
  8896. try
  8897. {
  8898. var idx = xml.lastIndexOf('%3E');
  8899. if (idx >= 0 && idx < xml.length - 3)
  8900. {
  8901. xml = xml.substring(0, idx + 3);
  8902. }
  8903. }
  8904. catch (e)
  8905. {
  8906. // ignore
  8907. }
  8908. // Checks for embedded XML content
  8909. try
  8910. {
  8911. var spans = elt.getElementsByTagName('span');
  8912. var tmp = (spans != null && spans.length > 0) ?
  8913. mxUtils.trim(decodeURIComponent(spans[0].textContent)) :
  8914. decodeURIComponent(xml);
  8915. if (this.isCompatibleString(tmp))
  8916. {
  8917. compat = true;
  8918. xml = tmp;
  8919. }
  8920. }
  8921. catch (e)
  8922. {
  8923. // ignore
  8924. }
  8925. try
  8926. {
  8927. if (xml != null && xml.length > 0)
  8928. {
  8929. this.pasteXml(xml, pasteAsLabel, compat, evt);
  8930. try
  8931. {
  8932. mxEvent.consume(evt);
  8933. }
  8934. catch (e)
  8935. {
  8936. // ignore event no longer exists in async handler in IE8-
  8937. }
  8938. }
  8939. else if (!useEvent)
  8940. {
  8941. var graph = this.editor.graph;
  8942. graph.lastPasteXml = null;
  8943. graph.pasteCounter = 0;
  8944. }
  8945. }
  8946. catch (e)
  8947. {
  8948. this.handleError(e);
  8949. }
  8950. }
  8951. }
  8952. realElt.innerHTML = '&nbsp;';
  8953. };
  8954. /**
  8955. * Adds a file drop handler for opening local files.
  8956. */
  8957. EditorUi.prototype.addFileDropHandler = function(elts)
  8958. {
  8959. // Installs drag and drop handler for files
  8960. if (Graph.fileSupport)
  8961. {
  8962. var dropElt = null;
  8963. for (var i = 0; i < elts.length; i++)
  8964. {
  8965. // Setup the dnd listeners
  8966. mxEvent.addListener(elts[i], 'dragleave', function(evt)
  8967. {
  8968. if (dropElt != null)
  8969. {
  8970. dropElt.parentNode.removeChild(dropElt);
  8971. dropElt = null;
  8972. }
  8973. evt.stopPropagation();
  8974. evt.preventDefault();
  8975. });
  8976. mxEvent.addListener(elts[i], 'dragover', mxUtils.bind(this, function(evt)
  8977. {
  8978. if (this.editor.graph.isEnabled() || urlParams['embed'] != '1')
  8979. {
  8980. // IE 10 does not implement pointer-events so it can't have a drop highlight
  8981. if (dropElt == null && (!mxClient.IS_IE || (document.documentMode > 10 && document.documentMode < 12)))
  8982. {
  8983. dropElt = this.highlightElement();
  8984. }
  8985. }
  8986. evt.stopPropagation();
  8987. evt.preventDefault();
  8988. }));
  8989. mxEvent.addListener(elts[i], 'drop', mxUtils.bind(this, function(evt)
  8990. {
  8991. if (dropElt != null)
  8992. {
  8993. dropElt.parentNode.removeChild(dropElt);
  8994. dropElt = null;
  8995. }
  8996. if (this.editor.graph.isEnabled() || urlParams['embed'] != '1')
  8997. {
  8998. if (evt.dataTransfer.files.length > 0)
  8999. {
  9000. this.hideDialog();
  9001. // Never open files in embed mode
  9002. if (urlParams['embed'] == '1')
  9003. {
  9004. this.importFiles(evt.dataTransfer.files, 0, 0, this.maxImageSize, null, null,
  9005. null, null, !mxEvent.isControlDown(evt) && !mxEvent.isShiftDown(evt));
  9006. }
  9007. else
  9008. {
  9009. this.openFiles(evt.dataTransfer.files, true);
  9010. }
  9011. }
  9012. else
  9013. {
  9014. // Handles open special files via text drag and drop
  9015. var data = this.extractGraphModelFromEvent(evt);
  9016. // Tries additional and async parsing of text content such as HTML, Gliffy data
  9017. if (data == null)
  9018. {
  9019. var provider = (evt.dataTransfer != null) ? evt.dataTransfer : evt.clipboardData;
  9020. if (provider != null)
  9021. {
  9022. if (document.documentMode == 10 || document.documentMode == 11)
  9023. {
  9024. data = provider.getData('Text');
  9025. }
  9026. else
  9027. {
  9028. var data = null;
  9029. if (mxUtils.indexOf(provider.types, 'text/uri-list') >= 0)
  9030. {
  9031. data = evt.dataTransfer.getData('text/uri-list');
  9032. }
  9033. else
  9034. {
  9035. data = (mxUtils.indexOf(provider.types, 'text/html') >= 0) ? provider.getData('text/html') : null;
  9036. }
  9037. if (data != null && data.length > 0)
  9038. {
  9039. var div = document.createElement('div');
  9040. div.innerHTML = this.editor.graph.sanitizeHtml(data);
  9041. // Extracts single image
  9042. var imgs = div.getElementsByTagName('img');
  9043. if (imgs.length > 0)
  9044. {
  9045. data = imgs[0].getAttribute('src');
  9046. }
  9047. }
  9048. else if (mxUtils.indexOf(provider.types, 'text/plain') >= 0)
  9049. {
  9050. data = provider.getData('text/plain');
  9051. }
  9052. }
  9053. if (data != null)
  9054. {
  9055. // Checks for embedded XML in PNG
  9056. if (data.substring(0, 22) == 'data:image/png;base64,')
  9057. {
  9058. var xml = this.extractGraphModelFromPng(data);
  9059. if (xml != null && xml.length > 0)
  9060. {
  9061. this.openLocalFile(xml, null, true);
  9062. }
  9063. }
  9064. else if (!this.isOffline() && this.isRemoteFileFormat(data))
  9065. {
  9066. new mxXmlRequest(OPEN_URL, 'format=xml&data=' + encodeURIComponent(data)).send(mxUtils.bind(this, function(req)
  9067. {
  9068. if (req.getStatus() >= 200 && req.getStatus() <= 299)
  9069. {
  9070. this.openLocalFile(req.getText(), null, true);
  9071. }
  9072. }));
  9073. }
  9074. else if (/^https?:\/\//.test(data))
  9075. {
  9076. if (this.getCurrentFile() == null)
  9077. {
  9078. window.location.hash = '#U' + encodeURIComponent(data);
  9079. }
  9080. else
  9081. {
  9082. window.openWindow(((mxClient.IS_CHROMEAPP) ?
  9083. (EditorUi.drawHost + '/') : 'https://' + location.host + '/') +
  9084. window.location.search + '#U' + encodeURIComponent(data));
  9085. }
  9086. }
  9087. }
  9088. }
  9089. }
  9090. else
  9091. {
  9092. this.openLocalFile(data, null, true);
  9093. }
  9094. }
  9095. }
  9096. evt.stopPropagation();
  9097. evt.preventDefault();
  9098. }));
  9099. }
  9100. }
  9101. };
  9102. /**
  9103. * Highlights the given element
  9104. */
  9105. EditorUi.prototype.highlightElement = function(elt)
  9106. {
  9107. var x = 0;
  9108. var y = 0;
  9109. var w = 0;
  9110. var h = 0;
  9111. if (elt == null)
  9112. {
  9113. var b = document.body;
  9114. var d = document.documentElement;
  9115. w = (b.clientWidth || d.clientWidth) - 3;
  9116. h = Math.max(b.clientHeight || 0, d.clientHeight) - 3;
  9117. }
  9118. else
  9119. {
  9120. x = elt.offsetTop;
  9121. y = elt.offsetLeft;
  9122. w = elt.clientWidth;
  9123. h = elt.clientHeight;
  9124. }
  9125. var hl = document.createElement('div');
  9126. hl.style.zIndex = mxPopupMenu.prototype.zIndex + 2;
  9127. hl.style.border = '3px dotted rgb(254, 137, 12)';
  9128. hl.style.pointerEvents = 'none';
  9129. hl.style.position = 'absolute';
  9130. hl.style.top = x + 'px';
  9131. hl.style.left = y + 'px';
  9132. hl.style.width = Math.max(0, w - 3) + 'px';
  9133. hl.style.height = Math.max(0, h - 3) + 'px';
  9134. if (elt != null && elt.parentNode == this.editor.graph.container)
  9135. {
  9136. this.editor.graph.container.appendChild(hl);
  9137. }
  9138. else
  9139. {
  9140. document.body.appendChild(hl);
  9141. }
  9142. return hl;
  9143. };
  9144. /**
  9145. * Highlights the given element
  9146. */
  9147. EditorUi.prototype.stringToCells = function(xml)
  9148. {
  9149. var doc = mxUtils.parseXml(xml);
  9150. var node = this.editor.extractGraphModel(doc.documentElement);
  9151. var cells = [];
  9152. if (node != null)
  9153. {
  9154. var codec = new mxCodec(node.ownerDocument);
  9155. var model = new mxGraphModel();
  9156. codec.decode(node, model);
  9157. var parent = model.getChildAt(model.getRoot(), 0);
  9158. for (var j = 0; j < model.getChildCount(parent); j++)
  9159. {
  9160. cells.push(model.getChildAt(parent, j));
  9161. }
  9162. }
  9163. return cells;
  9164. };
  9165. /**
  9166. * Opens the given files in the editor.
  9167. */
  9168. EditorUi.prototype.openFileHandle = function(data, name, file, temp, fileHandle)
  9169. {
  9170. if (name != null && name.length > 0)
  9171. {
  9172. if (!this.useCanvasForExport && /(\.png)$/i.test(name))
  9173. {
  9174. name = name.substring(0, name.length - 4) + '.drawio';
  9175. }
  9176. else if (/(\.pdf)$/i.test(name))
  9177. {
  9178. name = name.substring(0, name.length - 4) + '.drawio';
  9179. }
  9180. var handleResult = mxUtils.bind(this, function(xml)
  9181. {
  9182. var dot = name.lastIndexOf('.');
  9183. if (dot >= 0)
  9184. {
  9185. name = name.substring(0, name.lastIndexOf('.')) + '.drawio';
  9186. }
  9187. else
  9188. {
  9189. name = name + '.drawio';
  9190. }
  9191. if (xml.substring(0, 10) == '<mxlibrary')
  9192. {
  9193. // Creates new temporary file if library is dropped in splash screen
  9194. if (this.getCurrentFile() == null && urlParams['embed'] != '1')
  9195. {
  9196. this.openLocalFile(this.emptyDiagramXml, this.defaultFilename, temp);
  9197. }
  9198. try
  9199. {
  9200. this.loadLibrary(new LocalLibrary(this, xml, name));
  9201. }
  9202. catch (e)
  9203. {
  9204. this.handleError(e, mxResources.get('errorLoadingFile'));
  9205. }
  9206. }
  9207. else
  9208. {
  9209. this.openLocalFile(xml, name, temp);
  9210. }
  9211. });
  9212. if (/(\.v(dx|sdx?))($|\?)/i.test(name) || /(\.vs(x|sx?))($|\?)/i.test(name))
  9213. {
  9214. this.importVisio(file, mxUtils.bind(this, function(xml)
  9215. {
  9216. this.spinner.stop();
  9217. handleResult(xml);
  9218. }));
  9219. }
  9220. else if (/(\.*<graphml )/.test(data))
  9221. {
  9222. this.importGraphML(data, mxUtils.bind(this, function(xml)
  9223. {
  9224. this.spinner.stop();
  9225. handleResult(xml);
  9226. }));
  9227. }
  9228. else if (Graph.fileSupport && !this.isOffline() && new XMLHttpRequest().upload &&
  9229. this.isRemoteFileFormat(data, name))
  9230. {
  9231. this.parseFile(file, mxUtils.bind(this, function(xhr)
  9232. {
  9233. if (xhr.readyState == 4)
  9234. {
  9235. this.spinner.stop();
  9236. if (xhr.status >= 200 && xhr.status <= 299)
  9237. {
  9238. handleResult(xhr.responseText);
  9239. }
  9240. else
  9241. {
  9242. this.handleError({message: mxResources.get((xhr.status == 413) ?
  9243. 'drawingTooLarge' : 'invalidOrMissingFile')},
  9244. mxResources.get('errorLoadingFile'));
  9245. }
  9246. }
  9247. }));
  9248. }
  9249. else if (this.isLucidChartData(data))
  9250. {
  9251. if (/(\.json)$/i.test(name))
  9252. {
  9253. name = name.substring(0, name.length - 5) + '.drawio';
  9254. }
  9255. // LATER: Add import step that produces cells and use callback
  9256. this.convertLucidChart(data, mxUtils.bind(this, function(xml)
  9257. {
  9258. this.spinner.stop();
  9259. this.openLocalFile(xml, name, temp);
  9260. }), mxUtils.bind(this, function(e)
  9261. {
  9262. this.spinner.stop();
  9263. this.handleError(e);
  9264. }));
  9265. }
  9266. else if (data.substring(0, 10) == '<mxlibrary')
  9267. {
  9268. this.spinner.stop();
  9269. // Creates new temporary file if library is dropped in splash screen
  9270. if (this.getCurrentFile() == null && urlParams['embed'] != '1')
  9271. {
  9272. this.openLocalFile(this.emptyDiagramXml, this.defaultFilename, temp);
  9273. }
  9274. try
  9275. {
  9276. this.loadLibrary(new LocalLibrary(this, data, file.name));
  9277. }
  9278. catch (e)
  9279. {
  9280. this.handleError(e, mxResources.get('errorLoadingFile'));
  9281. }
  9282. }
  9283. else if (data.indexOf('PK') == 0)
  9284. {
  9285. this.importZipFile(file, mxUtils.bind(this, function(xml)
  9286. {
  9287. this.spinner.stop();
  9288. handleResult(xml);
  9289. }), mxUtils.bind(this, function()
  9290. {
  9291. this.spinner.stop();
  9292. this.openLocalFile(data, name, temp);
  9293. }));
  9294. }
  9295. else
  9296. {
  9297. if (file.type.substring(0, 9) == 'image/png')
  9298. {
  9299. data = this.extractGraphModelFromPng(data);
  9300. }
  9301. else if (file.type == 'application/pdf')
  9302. {
  9303. var xml = Editor.extractGraphModelFromPdf(data);
  9304. if (xml != null)
  9305. {
  9306. fileHandle = null;
  9307. temp = true;
  9308. data = xml;
  9309. }
  9310. }
  9311. this.spinner.stop();
  9312. this.openLocalFile(data, name, temp, fileHandle, (fileHandle != null) ? file : null);
  9313. }
  9314. }
  9315. };
  9316. /**
  9317. * Opens the given files in the editor.
  9318. */
  9319. EditorUi.prototype.openFiles = function(files, temp)
  9320. {
  9321. if (this.spinner.spin(document.body, mxResources.get('loading')))
  9322. {
  9323. for (var i = 0; i < files.length; i++)
  9324. {
  9325. (mxUtils.bind(this, function(file)
  9326. {
  9327. var reader = new FileReader();
  9328. reader.onload = mxUtils.bind(this, function(e)
  9329. {
  9330. try
  9331. {
  9332. this.openFileHandle(e.target.result, file.name, file, temp);
  9333. }
  9334. catch (e)
  9335. {
  9336. this.handleError(e);
  9337. }
  9338. });
  9339. reader.onerror = mxUtils.bind(this, function(e)
  9340. {
  9341. this.spinner.stop();
  9342. this.handleError(e);
  9343. window.openFile = null;
  9344. });
  9345. if ((file.type.substring(0, 5) === 'image' ||
  9346. file.type === 'application/pdf') &&
  9347. file.type.substring(0, 9) !== 'image/svg')
  9348. {
  9349. reader.readAsDataURL(file);
  9350. }
  9351. else
  9352. {
  9353. reader.readAsText(file);
  9354. }
  9355. }))(files[i]);
  9356. }
  9357. }
  9358. };
  9359. /**
  9360. * Shows the layers dialog if the graph has more than one layer.
  9361. */
  9362. EditorUi.prototype.openLocalFile = function(data, name, temp, fileHandle, desc)
  9363. {
  9364. var currentFile = this.getCurrentFile();
  9365. var fn = mxUtils.bind(this, function()
  9366. {
  9367. window.openFile = null;
  9368. if (name == null && this.getCurrentFile() != null && this.isDiagramEmpty())
  9369. {
  9370. var doc = mxUtils.parseXml(data);
  9371. if (doc != null)
  9372. {
  9373. this.editor.setGraphXml(doc.documentElement);
  9374. this.editor.graph.selectAll();
  9375. }
  9376. }
  9377. else
  9378. {
  9379. this.fileLoaded(new LocalFile(this, data, name ||
  9380. this.defaultFilename, temp, fileHandle, desc));
  9381. }
  9382. });
  9383. if (data != null && data.length > 0)
  9384. {
  9385. if (currentFile == null || (!currentFile.isModified() &&
  9386. (mxClient.IS_CHROMEAPP || EditorUi.isElectronApp || fileHandle != null)))
  9387. {
  9388. fn();
  9389. }
  9390. else if ((mxClient.IS_CHROMEAPP || EditorUi.isElectronApp || fileHandle != null) &&
  9391. currentFile != null && currentFile.isModified())
  9392. {
  9393. this.confirm(mxResources.get('allChangesLost'), null, fn,
  9394. mxResources.get('cancel'), mxResources.get('discardChanges'));
  9395. }
  9396. else
  9397. {
  9398. window.openFile = new OpenFile(function()
  9399. {
  9400. window.openFile = null;
  9401. });
  9402. window.openFile.setData(data, name);
  9403. window.openWindow(this.getUrl(), null, mxUtils.bind(this, function()
  9404. {
  9405. if (currentFile != null && currentFile.isModified())
  9406. {
  9407. this.confirm(mxResources.get('allChangesLost'), null, fn,
  9408. mxResources.get('cancel'), mxResources.get('discardChanges'));
  9409. }
  9410. else
  9411. {
  9412. fn();
  9413. }
  9414. }));
  9415. }
  9416. }
  9417. else
  9418. {
  9419. throw new Error(mxResources.get('notADiagramFile'));
  9420. }
  9421. };
  9422. /**
  9423. * Returns a list of all shapes used in the current file.
  9424. */
  9425. EditorUi.prototype.getBasenames = function()
  9426. {
  9427. var basenames = {};
  9428. if (this.pages != null)
  9429. {
  9430. for (var i = 0; i < this.pages.length; i++)
  9431. {
  9432. this.updatePageRoot(this.pages[i]);
  9433. this.addBasenamesForCell(this.pages[i].root, basenames);
  9434. }
  9435. }
  9436. else
  9437. {
  9438. this.addBasenamesForCell(this.editor.graph.model.getRoot(), basenames);
  9439. }
  9440. var result = [];
  9441. for (var key in basenames)
  9442. {
  9443. result.push(key);
  9444. }
  9445. return result;
  9446. };
  9447. /**
  9448. * Returns a list of all shapes used in the current file.
  9449. */
  9450. EditorUi.prototype.addBasenamesForCell = function(cell, basenames)
  9451. {
  9452. function addName(name)
  9453. {
  9454. if (name != null)
  9455. {
  9456. // LATER: Check if this case exists
  9457. var dot = name.lastIndexOf('.');
  9458. if (dot > 0)
  9459. {
  9460. name = name.substring(dot + 1, name.length);
  9461. }
  9462. if (basenames[name] == null)
  9463. {
  9464. basenames[name] = true;
  9465. }
  9466. }
  9467. };
  9468. var graph = this.editor.graph;
  9469. var style = graph.getCellStyle(cell);
  9470. var shape = style[mxConstants.STYLE_SHAPE];
  9471. addName(mxStencilRegistry.getBasenameForStencil(shape));
  9472. // Adds package names for markers in edges
  9473. if (graph.model.isEdge(cell))
  9474. {
  9475. addName(mxMarker.getPackageForType(style[mxConstants.STYLE_STARTARROW]));
  9476. addName(mxMarker.getPackageForType(style[mxConstants.STYLE_ENDARROW]));
  9477. }
  9478. var childCount = graph.model.getChildCount(cell);
  9479. for (var i = 0; i < childCount; i++)
  9480. {
  9481. this.addBasenamesForCell(graph.model.getChildAt(cell, i), basenames);
  9482. }
  9483. };
  9484. /**
  9485. * Shows the layers dialog if the graph has more than one layer.
  9486. */
  9487. EditorUi.prototype.setGraphEnabled = function(enabled)
  9488. {
  9489. this.diagramContainer.style.visibility = (enabled) ? '' : 'hidden';
  9490. this.formatContainer.style.visibility = (enabled) ? '' : 'hidden';
  9491. this.sidebarFooterContainer.style.display = (enabled) ? '' : 'none';
  9492. this.sidebarContainer.style.display = (enabled) ? '' : 'none';
  9493. this.hsplit.style.display = (enabled) ? '' : 'none';
  9494. this.editor.graph.setEnabled(enabled);
  9495. if (this.ruler != null)
  9496. {
  9497. this.ruler.hRuler.container.style.visibility = (enabled) ? '' : 'hidden';
  9498. this.ruler.vRuler.container.style.visibility = (enabled) ? '' : 'hidden';
  9499. }
  9500. if (this.tabContainer != null)
  9501. {
  9502. this.tabContainer.style.visibility = (enabled) ? '' : 'hidden';
  9503. }
  9504. if (!enabled)
  9505. {
  9506. if (this.actions.outlineWindow != null)
  9507. {
  9508. this.actions.outlineWindow.window.setVisible(false);
  9509. }
  9510. if (this.actions.layersWindow != null)
  9511. {
  9512. this.actions.layersWindow.window.setVisible(false);
  9513. }
  9514. if (this.menus.tagsWindow != null)
  9515. {
  9516. this.menus.tagsWindow.window.setVisible(false);
  9517. }
  9518. if (this.menus.findWindow != null)
  9519. {
  9520. this.menus.findWindow.window.setVisible(false);
  9521. }
  9522. if (this.menus.findReplaceWindow != null)
  9523. {
  9524. this.menus.findReplaceWindow.window.setVisible(false);
  9525. }
  9526. }
  9527. };
  9528. /**
  9529. * Shows the layers dialog if the graph has more than one layer.
  9530. */
  9531. EditorUi.prototype.initializeEmbedMode = function()
  9532. {
  9533. this.setGraphEnabled(false);
  9534. var parent = window.opener || window.parent;
  9535. if (parent != window)
  9536. {
  9537. if (urlParams['spin'] != '1' || this.spinner.spin(document.body, mxResources.get('loading')))
  9538. {
  9539. this.installMessageHandler(mxUtils.bind(this, function(xml, evt, modified)
  9540. {
  9541. this.spinner.stop();
  9542. this.addEmbedButtons();
  9543. this.setGraphEnabled(true);
  9544. if (xml == null || xml.length == 0)
  9545. {
  9546. xml = this.emptyDiagramXml;
  9547. }
  9548. // Creates temporary file for diff sync in embed mode
  9549. this.setCurrentFile(new EmbedFile(this, xml, {}));
  9550. this.mode = App.MODE_EMBED;
  9551. this.setFileData(xml);
  9552. if (!this.editor.isChromelessView())
  9553. {
  9554. this.showLayersDialog();
  9555. }
  9556. else if (this.editor.graph.isLightboxView())
  9557. {
  9558. this.lightboxFit();
  9559. }
  9560. if (this.chromelessResize)
  9561. {
  9562. this.chromelessResize();
  9563. }
  9564. this.editor.undoManager.clear();
  9565. this.editor.modified = (modified != null) ? modified : false;
  9566. this.updateUi();
  9567. // Workaround for no initial focus in FF
  9568. // (does not work in Conf Cloud with FF)
  9569. if (window.self !== window.top)
  9570. {
  9571. window.focus();
  9572. }
  9573. if (this.format != null)
  9574. {
  9575. this.format.refresh();
  9576. }
  9577. }));
  9578. }
  9579. }
  9580. };
  9581. /**
  9582. * Shows the layers dialog if the graph has more than one layer.
  9583. */
  9584. EditorUi.prototype.showLayersDialog = function()
  9585. {
  9586. if (this.editor.graph.getModel().getChildCount(this.editor.graph.getModel().getRoot()) > 1)
  9587. {
  9588. if (this.actions.layersWindow == null)
  9589. {
  9590. this.actions.get('layers').funct();
  9591. }
  9592. else
  9593. {
  9594. this.actions.layersWindow.window.setVisible(true);
  9595. }
  9596. }
  9597. };
  9598. /**
  9599. * Tries to find a public URL for the given file.
  9600. */
  9601. EditorUi.prototype.getPublicUrl = function(file, fn)
  9602. {
  9603. if (file != null)
  9604. {
  9605. file.getPublicUrl(fn);
  9606. }
  9607. else
  9608. {
  9609. fn(null);
  9610. }
  9611. };
  9612. /**
  9613. * Adds the buttons for embedded mode.
  9614. */
  9615. EditorUi.prototype.createLoadMessage = function(eventName)
  9616. {
  9617. var graph = this.editor.graph;
  9618. return {event: eventName, pageVisible: graph.pageVisible, translate: graph.view.translate,
  9619. bounds: graph.getGraphBounds(), currentPage: this.getSelectedPageIndex(),
  9620. scale: graph.view.scale, page: graph.view.getBackgroundPageBounds()};
  9621. };
  9622. /**
  9623. * Adds the buttons for embedded mode.
  9624. */
  9625. EditorUi.prototype.installMessageHandler = function(fn)
  9626. {
  9627. var changeListener = null;
  9628. var ignoreChange = false;
  9629. var autosave = false;
  9630. var lastData = null;
  9631. var updateStatus = mxUtils.bind(this, function(sender, eventObject)
  9632. {
  9633. if (!this.editor.modified || urlParams['modified'] == '0')
  9634. {
  9635. this.editor.setStatus('');
  9636. }
  9637. else if (urlParams['modified'] != null)
  9638. {
  9639. this.editor.setStatus(mxUtils.htmlEntities(mxResources.get(urlParams['modified'])));
  9640. }
  9641. });
  9642. this.editor.graph.model.addListener(mxEvent.CHANGE, updateStatus);
  9643. // Receives XML message from opener and puts it into the graph
  9644. mxEvent.addListener(window, 'message', mxUtils.bind(this, function(evt)
  9645. {
  9646. var validSource = window.opener || window.parent;
  9647. if (evt.source != validSource)
  9648. {
  9649. return;
  9650. }
  9651. var data = evt.data;
  9652. var extractDiagramXml = mxUtils.bind(this, function(data)
  9653. {
  9654. if (data != null && typeof data.charAt === 'function' && data.charAt(0) != '<')
  9655. {
  9656. try
  9657. {
  9658. if (data.substring(0, 22) == 'data:image/png;base64,')
  9659. {
  9660. data = this.extractGraphModelFromPng(data);
  9661. }
  9662. else if (data.substring(0, 26) == 'data:image/svg+xml;base64,')
  9663. {
  9664. data = atob(data.substring(26));
  9665. }
  9666. else if (data.substring(0, 24) == 'data:image/svg+xml;utf8,')
  9667. {
  9668. data = data.substring(24);
  9669. }
  9670. if (data != null)
  9671. {
  9672. if (data.charAt(0) == '%')
  9673. {
  9674. data = decodeURIComponent(data);
  9675. }
  9676. else if (data.charAt(0) != '<')
  9677. {
  9678. data = Graph.decompress(data);
  9679. }
  9680. }
  9681. }
  9682. catch (e)
  9683. {
  9684. // ignore compression errors and use empty data
  9685. }
  9686. }
  9687. return data;
  9688. });
  9689. if (urlParams['proto'] == 'json')
  9690. {
  9691. try
  9692. {
  9693. data = JSON.parse(data);
  9694. }
  9695. catch (e)
  9696. {
  9697. data = null;
  9698. }
  9699. try
  9700. {
  9701. if (data == null)
  9702. {
  9703. // Ignore
  9704. return;
  9705. }
  9706. else if (data.action == 'dialog')
  9707. {
  9708. this.showError((data.titleKey != null) ? mxResources.get(data.titleKey) : data.title,
  9709. (data.messageKey != null) ? mxResources.get(data.messageKey) : data.message,
  9710. (data.buttonKey != null) ? mxResources.get(data.buttonKey) : data.button);
  9711. if (data.modified != null)
  9712. {
  9713. this.editor.modified = data.modified;
  9714. }
  9715. return;
  9716. }
  9717. else if (data.action == 'layout')
  9718. {
  9719. this.executeLayoutList(data.layouts)
  9720. return;
  9721. }
  9722. else if (data.action == 'prompt')
  9723. {
  9724. this.spinner.stop();
  9725. var dlg = new FilenameDialog(this, data.defaultValue || '',
  9726. (data.okKey != null) ? mxResources.get(data.okKey) : data.ok, function(value)
  9727. {
  9728. if (value != null)
  9729. {
  9730. parent.postMessage(JSON.stringify({event: 'prompt', value: value, message: data}), '*');
  9731. }
  9732. else
  9733. {
  9734. parent.postMessage(JSON.stringify({event: 'prompt-cancel', message: data}), '*');
  9735. }
  9736. }, (data.titleKey != null) ? mxResources.get(data.titleKey) : data.title);
  9737. this.showDialog(dlg.container, 300, 80, true, false);
  9738. dlg.init();
  9739. return;
  9740. }
  9741. else if (data.action == 'draft')
  9742. {
  9743. var tmp = extractDiagramXml(data.xml);
  9744. this.spinner.stop();
  9745. var dlg = new DraftDialog(this, mxResources.get('draftFound',
  9746. [data.name || this.defaultFilename]),
  9747. tmp, mxUtils.bind(this, function()
  9748. {
  9749. this.hideDialog();
  9750. parent.postMessage(JSON.stringify({event: 'draft',
  9751. result: 'edit', message: data}), '*');
  9752. }), mxUtils.bind(this, function()
  9753. {
  9754. this.hideDialog();
  9755. parent.postMessage(JSON.stringify({event: 'draft',
  9756. result: 'discard', message: data}), '*');
  9757. }), (data.editKey) ? mxResources.get(data.editKey) : null,
  9758. (data.discardKey) ? mxResources.get(data.discardKey) : null,
  9759. (data.ignore) ? mxUtils.bind(this, function()
  9760. {
  9761. this.hideDialog();
  9762. parent.postMessage(JSON.stringify({event: 'draft',
  9763. result: 'ignore', message: data}), '*');
  9764. }) : null);
  9765. this.showDialog(dlg.container, 640, 480, true, false, mxUtils.bind(this, function(cancel)
  9766. {
  9767. if (cancel)
  9768. {
  9769. this.actions.get('exit').funct();
  9770. }
  9771. }));
  9772. try
  9773. {
  9774. dlg.init();
  9775. }
  9776. catch (e)
  9777. {
  9778. parent.postMessage(JSON.stringify({event: 'draft',
  9779. error: e.toString(), message: data}), '*');
  9780. }
  9781. return;
  9782. }
  9783. else if (data.action == 'template')
  9784. {
  9785. this.spinner.stop();
  9786. var enableRecentDocs = data.enableRecent == 1;
  9787. var enableSearchDocs = data.enableSearch == 1;
  9788. var enableCustomTemp = data.enableCustomTemp == 1;
  9789. var dlg = new NewDialog(this, false, data.templatesOnly? false : data.callback != null,
  9790. mxUtils.bind(this, function(xml, name, url, libs)
  9791. {
  9792. xml = xml || this.emptyDiagramXml;
  9793. // LATER: Add autosave option in template message
  9794. if (data.callback != null)
  9795. {
  9796. parent.postMessage(JSON.stringify({event: 'template', xml: xml,
  9797. blank: xml == this.emptyDiagramXml, name: name,
  9798. tempUrl: url, libs: libs, builtIn: true,
  9799. message: data}), '*');
  9800. }
  9801. else
  9802. {
  9803. fn(xml, evt, xml != this.emptyDiagramXml);
  9804. // Workaround for status updated before modified applied
  9805. if (!this.editor.modified)
  9806. {
  9807. this.editor.setStatus('');
  9808. }
  9809. }
  9810. }), null, null, null, null, null, null, null,
  9811. enableRecentDocs? mxUtils.bind(this, function(recentReadyCallback)
  9812. {
  9813. this.remoteInvoke('getRecentDiagrams', null, null, recentReadyCallback, function()
  9814. {
  9815. recentReadyCallback(null, 'Network Error!');
  9816. });
  9817. }) : null,
  9818. enableSearchDocs? mxUtils.bind(this, function(searchStr, searchReadyCallback)
  9819. {
  9820. this.remoteInvoke('searchDiagrams', [searchStr], null, searchReadyCallback, function()
  9821. {
  9822. searchReadyCallback(null, 'Network Error!');
  9823. });
  9824. }) : null,
  9825. mxUtils.bind(this, function(url, info, name)
  9826. {
  9827. //If binary files are possible, we can get the file content using remote invokation, imported it, and send final mxFile back
  9828. parent.postMessage(JSON.stringify({event: 'template', docUrl: url, info: info,
  9829. name: name}), '*');
  9830. }), null, null,
  9831. enableCustomTemp? mxUtils.bind(this, function(customTempCallback)
  9832. {
  9833. this.remoteInvoke('getCustomTemplates', null, null, customTempCallback, function()
  9834. {
  9835. customTempCallback({}, 0); //ignore error by sending empty templates
  9836. });
  9837. }) : null);
  9838. this.showDialog(dlg.container, 620, 440, true, false, mxUtils.bind(this, function(cancel)
  9839. {
  9840. if (cancel)
  9841. {
  9842. this.actions.get('exit').funct();
  9843. }
  9844. }));
  9845. dlg.init();
  9846. return;
  9847. }
  9848. else if (data.action == 'textContent')
  9849. {
  9850. //TODO Remove this message and use remote invokation instead
  9851. var allPagesTxt = this.getDiagramTextContent();
  9852. parent.postMessage(JSON.stringify({event: 'textContent',
  9853. data: allPagesTxt, message: data}), '*');
  9854. return;
  9855. }
  9856. else if (data.action == 'status')
  9857. {
  9858. if (data.messageKey != null)
  9859. {
  9860. this.editor.setStatus(mxUtils.htmlEntities(mxResources.get(data.messageKey)));
  9861. }
  9862. else if (data.message != null)
  9863. {
  9864. this.editor.setStatus(mxUtils.htmlEntities(data.message));
  9865. }
  9866. if (data.modified != null)
  9867. {
  9868. this.editor.modified = data.modified;
  9869. }
  9870. return;
  9871. }
  9872. else if (data.action == 'spinner')
  9873. {
  9874. var msg = (data.messageKey != null) ? mxResources.get(data.messageKey) : data.message;
  9875. if (data.show != null && !data.show)
  9876. {
  9877. this.spinner.stop();
  9878. }
  9879. else
  9880. {
  9881. this.spinner.spin(document.body, msg)
  9882. }
  9883. return;
  9884. }
  9885. else if (data.action == 'export')
  9886. {
  9887. if (data.format == 'png' || data.format == 'xmlpng')
  9888. {
  9889. if ((data.spin == null && data.spinKey == null) || this.spinner.spin(document.body,
  9890. (data.spinKey != null) ? mxResources.get(data.spinKey) : data.spin))
  9891. {
  9892. var xml = (data.xml != null) ? data.xml : this.getFileData(true);
  9893. this.editor.graph.setEnabled(false);
  9894. var graph = this.editor.graph;
  9895. var postDataBack = mxUtils.bind(this, function(uri)
  9896. {
  9897. this.editor.graph.setEnabled(true);
  9898. this.spinner.stop();
  9899. var msg = this.createLoadMessage('export');
  9900. msg.format = data.format;
  9901. msg.message = data;
  9902. msg.data = uri;
  9903. msg.xml = xml;
  9904. parent.postMessage(JSON.stringify(msg), '*');
  9905. });
  9906. var processUri = mxUtils.bind(this, function(uri)
  9907. {
  9908. if (uri == null)
  9909. {
  9910. uri = Editor.blankImage;
  9911. }
  9912. if (data.format == 'xmlpng')
  9913. {
  9914. uri = Editor.writeGraphModelToPng(uri, 'tEXt', 'mxfile',
  9915. encodeURIComponent(xml));
  9916. }
  9917. // Removes temporary graph from DOM
  9918. if (graph != this.editor.graph)
  9919. {
  9920. graph.container.parentNode.removeChild(graph.container);
  9921. }
  9922. postDataBack(uri);
  9923. });
  9924. var pageId = data.pageId || (this.pages != null? ((data.currentPage) ?
  9925. this.currentPage.getId() : this.pages[0].getId()) : null);
  9926. if (this.isExportToCanvas())
  9927. {
  9928. // Uses optional XML from incoming message
  9929. if (data.xml != null && data.xml.length > 0)
  9930. {
  9931. ignoreChange = true;
  9932. this.setFileData(xml);
  9933. ignoreChange = false;
  9934. }
  9935. // Exports PNG for first/specific page while other page is visible by creating a graph
  9936. // LATER: Add caching for the graph or SVG while not on first page
  9937. if (this.pages != null && this.currentPage.getId() != pageId)
  9938. {
  9939. var graphGetGlobalVariable = graph.getGlobalVariable;
  9940. graph = this.createTemporaryGraph(graph.getStylesheet());
  9941. var page;
  9942. for (var i = 0; i < this.pages.length; i++)
  9943. {
  9944. if (this.pages[i].getId() == pageId)
  9945. {
  9946. page = this.updatePageRoot(this.pages[i]);
  9947. break;
  9948. }
  9949. }
  9950. //If pageId info is incorrect
  9951. if (page == null)
  9952. {
  9953. page = this.currentPage;
  9954. }
  9955. graph.getGlobalVariable = function(name)
  9956. {
  9957. if (name == 'page')
  9958. {
  9959. return page.getName();
  9960. }
  9961. else if (name == 'pagenumber')
  9962. {
  9963. return 1;
  9964. }
  9965. return graphGetGlobalVariable.apply(this, arguments);
  9966. };
  9967. document.body.appendChild(graph.container);
  9968. graph.model.setRoot(page.root);
  9969. }
  9970. // Set visible layers based on message setting
  9971. if (data.layerIds != null)
  9972. {
  9973. var graphModel = graph.model;
  9974. var layers = graphModel.getChildCells(graphModel.getRoot());
  9975. var layerIdsMap = {};
  9976. for (var i = 0; i < data.layerIds.length; i++)
  9977. {
  9978. layerIdsMap[data.layerIds[i]] = true;
  9979. }
  9980. for (var i = 0; i < layers.length; i++)
  9981. {
  9982. graphModel.setVisible(layers[i], layerIdsMap[layers[i].id] || false);
  9983. }
  9984. }
  9985. this.editor.exportToCanvas(mxUtils.bind(this, function(canvas)
  9986. {
  9987. processUri(canvas.toDataURL('image/png'));
  9988. }), data.width, null, data.background, mxUtils.bind(this, function()
  9989. {
  9990. processUri(null);
  9991. }), null, null, data.scale, data.transparent, data.shadow, null,
  9992. graph, data.border, null, data.grid, data.keepTheme);
  9993. }
  9994. else
  9995. {
  9996. // Data from server is base64 encoded to avoid binary XHR
  9997. // Double encoding for XML arg is needed for UTF8 encoding
  9998. var req = new mxXmlRequest(EXPORT_URL, 'format=png&embedXml=' +
  9999. ((data.format == 'xmlpng') ? '1' : '0') +
  10000. (pageId != null? '&pageId=' + pageId : '') +
  10001. (data.layerIds != null && data.layerIds.length > 0? '&extras=' + encodeURIComponent(JSON.stringify({layerIds: data.layerIds})) : '') +
  10002. (data.scale != null? '&scale=' + data.scale : '') +'&base64=1&xml=' +
  10003. encodeURIComponent(xml));
  10004. req.send(mxUtils.bind(this, function(req)
  10005. {
  10006. // Temp graph was never created at this point so we can
  10007. // skip processUri since it already contains the XML
  10008. if (req.getStatus() >= 200 && req.getStatus() <= 299)
  10009. {
  10010. postDataBack('data:image/png;base64,' + req.getText());
  10011. }
  10012. else
  10013. {
  10014. processUri(null);
  10015. }
  10016. }), mxUtils.bind(this, function()
  10017. {
  10018. processUri(null);
  10019. }));
  10020. }
  10021. }
  10022. }
  10023. else
  10024. {
  10025. // SVG is generated from graph so parse optional XML
  10026. if (data.xml != null && data.xml.length > 0)
  10027. {
  10028. ignoreChange = true;
  10029. this.setFileData(data.xml);
  10030. ignoreChange = false;
  10031. }
  10032. var msg = this.createLoadMessage('export');
  10033. // Attaches incoming message
  10034. msg.message = data;
  10035. // Forces new HTML format if pages exists
  10036. if (data.format == 'html2' || (data.format == 'html' && (urlParams['pages'] != '0' ||
  10037. (this.pages != null && this.pages.length > 1))))
  10038. {
  10039. var node = this.getXmlFileData();
  10040. msg.xml = mxUtils.getXml(node);
  10041. msg.data = this.getFileData(null, null, true, null, null, null, node);
  10042. msg.format = data.format;
  10043. }
  10044. else if (data.format == 'html')
  10045. {
  10046. var xml = this.editor.getGraphXml();
  10047. msg.data = this.getHtml(xml, this.editor.graph);
  10048. msg.xml = mxUtils.getXml(xml);
  10049. msg.format = data.format;
  10050. }
  10051. else
  10052. {
  10053. // Creates a preview with no alt text for unsupported browsers
  10054. mxSvgCanvas2D.prototype.foAltText = null;
  10055. var bg = (data.background != null) ? data.background : this.editor.graph.background;
  10056. if (bg == mxConstants.NONE)
  10057. {
  10058. bg = null;
  10059. }
  10060. msg.xml = this.getFileData(true, null, null, null, null,
  10061. null, null, null, null, false);
  10062. msg.format = 'svg';
  10063. var postResult = mxUtils.bind(this, function(svg)
  10064. {
  10065. this.editor.graph.setEnabled(true);
  10066. this.spinner.stop();
  10067. msg.data = Editor.createSvgDataUri(svg);
  10068. parent.postMessage(JSON.stringify(msg), '*');
  10069. });
  10070. if (data.format == 'xmlsvg')
  10071. {
  10072. if ((data.spin == null && data.spinKey == null) || this.spinner.spin(document.body,
  10073. (data.spinKey != null) ? mxResources.get(data.spinKey) : data.spin))
  10074. {
  10075. this.getEmbeddedSvg(msg.xml, this.editor.graph, null, true, postResult, null, null,
  10076. data.embedImages, bg, data.scale, data.border, data.shadow, data.keepTheme);
  10077. }
  10078. }
  10079. else
  10080. {
  10081. if ((data.spin == null && data.spinKey == null) || this.spinner.spin(document.body,
  10082. (data.spinKey != null) ? mxResources.get(data.spinKey) : data.spin))
  10083. {
  10084. this.editor.graph.setEnabled(false);
  10085. var svgRoot = this.editor.graph.getSvg(bg, data.scale, data.border, null, null,
  10086. null, null, null, null, this.editor.graph.shadowVisible || data.shadow,
  10087. null, data.keepTheme);
  10088. if (this.editor.graph.shadowVisible || data.shadow)
  10089. {
  10090. this.editor.graph.addSvgShadow(svgRoot);
  10091. }
  10092. this.embedFonts(svgRoot, mxUtils.bind(this, function(svgRoot)
  10093. {
  10094. if (data.embedImages || data.embedImages == null)
  10095. {
  10096. this.editor.convertImages(svgRoot, mxUtils.bind(this, function(svgRoot)
  10097. {
  10098. postResult(mxUtils.getXml(svgRoot));
  10099. }));
  10100. }
  10101. else
  10102. {
  10103. postResult(mxUtils.getXml(svgRoot));
  10104. }
  10105. }));
  10106. }
  10107. }
  10108. return;
  10109. }
  10110. parent.postMessage(JSON.stringify(msg), '*');
  10111. }
  10112. return;
  10113. }
  10114. else if (data.action == 'load')
  10115. {
  10116. autosave = data.autosave == 1;
  10117. this.hideDialog();
  10118. if (data.modified != null && urlParams['modified'] == null)
  10119. {
  10120. urlParams['modified'] = data.modified;
  10121. }
  10122. if (data.saveAndExit != null && urlParams['saveAndExit'] == null)
  10123. {
  10124. urlParams['saveAndExit'] = data.saveAndExit;
  10125. }
  10126. if (data.noSaveBtn != null && urlParams['noSaveBtn'] == null)
  10127. {
  10128. urlParams['noSaveBtn'] = data.noSaveBtn;
  10129. }
  10130. if (data.noExitBtn != null && urlParams['noExitBtn'] == null)
  10131. {
  10132. urlParams['noExitBtn'] = data.noExitBtn;
  10133. }
  10134. if (data.title != null && this.buttonContainer != null)
  10135. {
  10136. var tmp = document.createElement('span');
  10137. mxUtils.write(tmp, data.title);
  10138. if (uiTheme == 'atlas')
  10139. {
  10140. this.buttonContainer.style.paddingRight = '12px';
  10141. this.buttonContainer.style.paddingTop = '6px';
  10142. this.buttonContainer.style.right = urlParams['noLangIcon'] == '1'? '0' : '25px';
  10143. }
  10144. else if (uiTheme != 'min')
  10145. {
  10146. this.buttonContainer.style.paddingRight = '38px';
  10147. this.buttonContainer.style.paddingTop = '6px';
  10148. }
  10149. if (this.embedFilenameSpan != null)
  10150. {
  10151. this.embedFilenameSpan.parentNode.removeChild(this.embedFilenameSpan);
  10152. }
  10153. this.buttonContainer.appendChild(tmp);
  10154. this.embedFilenameSpan = tmp;
  10155. }
  10156. try
  10157. {
  10158. if (data.libs)
  10159. {
  10160. this.sidebar.showEntries(data.libs);
  10161. }
  10162. }
  10163. catch(e){}
  10164. if (data.xmlpng != null)
  10165. {
  10166. data = this.extractGraphModelFromPng(data.xmlpng);
  10167. }
  10168. else if (data.descriptor != null)
  10169. {
  10170. data = data.descriptor;
  10171. }
  10172. else
  10173. {
  10174. data = data.xml;
  10175. }
  10176. }
  10177. else if (data.action == 'merge')
  10178. {
  10179. var file = this.getCurrentFile();
  10180. if (file != null)
  10181. {
  10182. var tmp = extractDiagramXml(data.xml);
  10183. if (tmp != null && tmp != '')
  10184. {
  10185. file.mergeFile(new LocalFile(this, tmp), function()
  10186. {
  10187. parent.postMessage(JSON.stringify({event: 'merge', message: data}), '*');
  10188. }, function(err)
  10189. {
  10190. parent.postMessage(JSON.stringify({event: 'merge', message: data, error: err}), '*');
  10191. });
  10192. }
  10193. }
  10194. return;
  10195. }
  10196. else if (data.action == 'remoteInvokeReady')
  10197. {
  10198. this.handleRemoteInvokeReady(parent);
  10199. return;
  10200. }
  10201. else if (data.action == 'remoteInvoke')
  10202. {
  10203. this.handleRemoteInvoke(data, evt.origin);
  10204. return;
  10205. }
  10206. else if (data.action == 'remoteInvokeResponse')
  10207. {
  10208. this.handleRemoteInvokeResponse(data);
  10209. return;
  10210. }
  10211. else
  10212. {
  10213. // Unknown message must stop execution
  10214. parent.postMessage(JSON.stringify({error: 'unknownMessage', data: JSON.stringify(data)}), '*');
  10215. return;
  10216. }
  10217. }
  10218. catch (e)
  10219. {
  10220. // TODO: Block handling of more messages when in error state
  10221. this.handleError(e);
  10222. }
  10223. }
  10224. var getData = mxUtils.bind(this, function()
  10225. {
  10226. return (urlParams['pages'] != '0' || (this.pages != null && this.pages.length > 1)) ?
  10227. this.getFileData(true): mxUtils.getXml(this.editor.getGraphXml());
  10228. });
  10229. var doLoad = mxUtils.bind(this, function(data, evt)
  10230. {
  10231. ignoreChange = true;
  10232. try
  10233. {
  10234. fn(data, evt);
  10235. }
  10236. catch (e)
  10237. {
  10238. this.handleError(e);
  10239. }
  10240. ignoreChange = false;
  10241. if (urlParams['modified'] != null)
  10242. {
  10243. this.editor.setStatus('');
  10244. }
  10245. lastData = getData();
  10246. if (autosave && changeListener == null)
  10247. {
  10248. changeListener = mxUtils.bind(this, function(sender, eventObject)
  10249. {
  10250. var data = getData();
  10251. if (data != lastData && !ignoreChange)
  10252. {
  10253. var msg = this.createLoadMessage('autosave');
  10254. msg.xml = data;
  10255. var parent = window.opener || window.parent;
  10256. parent.postMessage(JSON.stringify(msg), '*');
  10257. }
  10258. lastData = data;
  10259. });
  10260. this.editor.graph.model.addListener(mxEvent.CHANGE, changeListener);
  10261. // Some options trigger autosave
  10262. this.editor.graph.addListener('gridSizeChanged', changeListener);
  10263. this.editor.graph.addListener('shadowVisibleChanged', changeListener);
  10264. this.addListener('pageFormatChanged', changeListener);
  10265. this.addListener('pageScaleChanged', changeListener);
  10266. this.addListener('backgroundColorChanged', changeListener);
  10267. this.addListener('backgroundImageChanged', changeListener);
  10268. this.addListener('foldingEnabledChanged', changeListener);
  10269. this.addListener('mathEnabledChanged', changeListener);
  10270. this.addListener('gridEnabledChanged', changeListener);
  10271. this.addListener('guidesEnabledChanged', changeListener);
  10272. this.addListener('pageViewChanged', changeListener);
  10273. }
  10274. // Sends the bounds of the graph to the host after parsing
  10275. if (urlParams['returnbounds'] == '1' || urlParams['proto'] == 'json')
  10276. {
  10277. var resp = this.createLoadMessage('load');
  10278. // Attaches XML to response
  10279. resp.xml = data;
  10280. parent.postMessage(JSON.stringify(resp), '*');
  10281. }
  10282. });
  10283. if (data != null && typeof data.substring === 'function' && data.substring(0, 34) == 'data:application/vnd.visio;base64,')
  10284. {
  10285. // Checks VND binary magic number in base64
  10286. var filename = (data.substring(34, 45) == '0M8R4KGxGuE') ? 'raw.vsd' : 'raw.vsdx';
  10287. this.importVisio(this.base64ToBlob(data.substring(data.indexOf(',') + 1)), function(xml)
  10288. {
  10289. doLoad(xml, evt);
  10290. }, mxUtils.bind(this, function(e)
  10291. {
  10292. this.handleError(e);
  10293. }), filename);
  10294. }
  10295. else if (data != null && typeof data.substring === 'function' && !this.isOffline() && new XMLHttpRequest().upload && this.isRemoteFileFormat(data, ''))
  10296. {
  10297. // Asynchronous parsing via server
  10298. this.parseFile(new Blob([data], {type: 'application/octet-stream'}), mxUtils.bind(this, function(xhr)
  10299. {
  10300. if (xhr.readyState == 4 && xhr.status >= 200 && xhr.status <= 299 &&
  10301. xhr.responseText.substring(0, 13) == '<mxGraphModel')
  10302. {
  10303. doLoad(xhr.responseText, evt);
  10304. }
  10305. }), '');
  10306. }
  10307. else if (data != null && typeof data.substring === 'function' && this.isLucidChartData(data))
  10308. {
  10309. this.convertLucidChart(data, mxUtils.bind(this, function(xml)
  10310. {
  10311. doLoad(xml);
  10312. }), mxUtils.bind(this, function(e)
  10313. {
  10314. this.handleError(e);
  10315. }));
  10316. }
  10317. else if (data != null && typeof data === 'object' && data.format != null && (data.data != null || data.url != null))
  10318. {
  10319. this.loadDescriptor(data, mxUtils.bind(this, function(e)
  10320. {
  10321. doLoad(getData(), evt);
  10322. }), mxUtils.bind(this, function(e)
  10323. {
  10324. this.handleError(e, mxResources.get('errorLoadingFile'));
  10325. }));
  10326. }
  10327. else
  10328. {
  10329. data = extractDiagramXml(data);
  10330. doLoad(data, evt);
  10331. }
  10332. }));
  10333. // Requests data from the sender. This is a workaround for not allowing
  10334. // the opener to listen for the onload event if not in the same origin.
  10335. var parent = window.opener || window.parent;
  10336. var msg = (urlParams['proto'] == 'json') ? JSON.stringify({event: 'init'}) : (urlParams['ready'] || 'ready');
  10337. parent.postMessage(msg, '*');
  10338. // Adds JSON event for opening links
  10339. if (urlParams['proto'] == 'json')
  10340. {
  10341. var graphOpenLink = this.editor.graph.openLink;
  10342. this.editor.graph.openLink = function(href, target, allowOpener)
  10343. {
  10344. graphOpenLink.apply(this, arguments);
  10345. parent.postMessage(JSON.stringify({event: 'openLink', href: href, target: target, allowOpener: allowOpener}), '*');
  10346. };
  10347. }
  10348. };
  10349. /**
  10350. * Adds the buttons for embedded mode.
  10351. */
  10352. EditorUi.prototype.addEmbedButtons = function()
  10353. {
  10354. if (this.menubar != null)
  10355. {
  10356. var div = document.createElement('div');
  10357. div.style.display = 'inline-block';
  10358. div.style.position = 'absolute';
  10359. div.style.paddingTop = (uiTheme == 'atlas') ? '2px' : '0px';
  10360. div.style.paddingLeft = '8px';
  10361. div.style.paddingBottom = '2px';
  10362. var button = document.createElement('button');
  10363. button.className = 'geBigButton';
  10364. var lastBtn = button;
  10365. if (urlParams['noSaveBtn'] == '1')
  10366. {
  10367. if (urlParams['saveAndExit'] != '0')
  10368. {
  10369. var saveAndExitTitle = urlParams['publishClose'] == '1' ? mxResources.get('publish') : mxResources.get('saveAndExit');
  10370. mxUtils.write(button, saveAndExitTitle);
  10371. button.setAttribute('title', saveAndExitTitle);
  10372. mxEvent.addListener(button, 'click', mxUtils.bind(this, function()
  10373. {
  10374. this.actions.get('saveAndExit').funct();
  10375. }));
  10376. div.appendChild(button);
  10377. }
  10378. }
  10379. else
  10380. {
  10381. mxUtils.write(button, mxResources.get('save'));
  10382. button.setAttribute('title', mxResources.get('save') + ' (' + Editor.ctrlKey + '+S)');
  10383. mxEvent.addListener(button, 'click', mxUtils.bind(this, function()
  10384. {
  10385. this.actions.get('save').funct();
  10386. }));
  10387. div.appendChild(button);
  10388. if (urlParams['saveAndExit'] == '1')
  10389. {
  10390. button = document.createElement('a');
  10391. mxUtils.write(button, mxResources.get('saveAndExit'));
  10392. button.setAttribute('title', mxResources.get('saveAndExit'));
  10393. button.className = 'geBigButton geBigStandardButton';
  10394. button.style.marginLeft = '6px';
  10395. mxEvent.addListener(button, 'click', mxUtils.bind(this, function()
  10396. {
  10397. this.actions.get('saveAndExit').funct();
  10398. }));
  10399. div.appendChild(button);
  10400. lastBtn = button;
  10401. }
  10402. }
  10403. if (urlParams['noExitBtn'] != '1')
  10404. {
  10405. button = document.createElement('a');
  10406. var exitTitle = urlParams['publishClose'] == '1' ? mxResources.get('close') : mxResources.get('exit');
  10407. mxUtils.write(button, exitTitle);
  10408. button.setAttribute('title', exitTitle);
  10409. button.className = 'geBigButton geBigStandardButton';
  10410. button.style.marginLeft = '6px';
  10411. mxEvent.addListener(button, 'click', mxUtils.bind(this, function()
  10412. {
  10413. this.actions.get('exit').funct();
  10414. }));
  10415. div.appendChild(button);
  10416. lastBtn = button;
  10417. }
  10418. lastBtn.style.marginRight = '20px';
  10419. this.toolbar.container.appendChild(div);
  10420. this.toolbar.staticElements.push(div);
  10421. div.style.right = (uiTheme != 'atlas') ? '52px' : '42px';
  10422. }
  10423. };
  10424. /**
  10425. *
  10426. */
  10427. EditorUi.prototype.showImportCsvDialog = function()
  10428. {
  10429. if (this.importCsvDialog == null)
  10430. {
  10431. this.importCsvDialog = new TextareaDialog(this, mxResources.get('csv') + ':',
  10432. Editor.defaultCsvValue, mxUtils.bind(this, function(newValue)
  10433. {
  10434. this.importCsv(newValue);
  10435. }), null, null, 620, 430, null, true, true, mxResources.get('import'),
  10436. !this.isOffline() ? 'https://drawio-app.com/import-from-csv-to-drawio/' : null);
  10437. }
  10438. this.showDialog(this.importCsvDialog.container, 640, 520, true, true, null, null, null, null, true);
  10439. this.importCsvDialog.init();
  10440. };
  10441. /**
  10442. * Runs the layout from the given JavaScript array which is of the form [{layout: name, config: obj}, ...]
  10443. * where name is the layout constructor name and config contains the properties of the layout instance.
  10444. */
  10445. EditorUi.prototype.executeLayoutList = function(layoutList, done)
  10446. {
  10447. var graph = this.editor.graph;
  10448. var cells = graph.getSelectionCells();
  10449. for (var i = 0; i < layoutList.length; i++)
  10450. {
  10451. var layout = new window[layoutList[i].layout](graph);
  10452. if (layoutList[i].config != null)
  10453. {
  10454. for (var key in layoutList[i].config)
  10455. {
  10456. layout[key] = layoutList[i].config[key];
  10457. }
  10458. }
  10459. this.executeLayout(function()
  10460. {
  10461. layout.execute(graph.getDefaultParent(), cells.length == 0 ? null : cells);
  10462. }, i == layoutList.length - 1, done);
  10463. }
  10464. };
  10465. /**
  10466. *
  10467. */
  10468. EditorUi.prototype.importCsv = function(text, done)
  10469. {
  10470. try
  10471. {
  10472. var lines = text.split('\n');
  10473. var allCells = [];
  10474. var cells = [];
  10475. var dups = {};
  10476. if (lines.length > 0)
  10477. {
  10478. // Internal lookup table
  10479. var lookups = {};
  10480. // Default values
  10481. var vars = null;
  10482. var style = null;
  10483. var styles = null;
  10484. var stylename = null;
  10485. var labelname = null;
  10486. var labels = null;
  10487. var parentstyle = null;
  10488. var identity = null;
  10489. var parent = null;
  10490. var namespace = '';
  10491. var width = 'auto';
  10492. var height = 'auto';
  10493. var left = null;
  10494. var top = null;
  10495. var edgespacing = 40;
  10496. var nodespacing = 40;
  10497. var levelspacing = 100;
  10498. var padding = 0;
  10499. var graph = this.editor.graph;
  10500. var view = graph.view;
  10501. var bds = graph.getGraphBounds();
  10502. // Delayed after optional layout
  10503. var afterInsert = function()
  10504. {
  10505. if (done != null)
  10506. {
  10507. done(select);
  10508. }
  10509. else
  10510. {
  10511. graph.setSelectionCells(select);
  10512. graph.scrollCellToVisible(graph.getSelectionCell());
  10513. }
  10514. };
  10515. // Computes unscaled, untranslated graph bounds
  10516. var pt = graph.getFreeInsertPoint();
  10517. var x0 = pt.x;
  10518. var y0 = pt.y;
  10519. var y = y0;
  10520. // Default label value depends on column names
  10521. var label = null;
  10522. // Default layout to run.
  10523. var layout = 'auto';
  10524. // Name of the attribute that contains the parent reference
  10525. var parent = null;
  10526. // Name of the attribute that contains the references for creating edges
  10527. var edges = [];
  10528. // Name of the column for hyperlinks
  10529. var link = null;
  10530. // String array of names to remove from metadata
  10531. var ignore = null;
  10532. // Read processing instructions first
  10533. var index = 0;
  10534. while (index < lines.length && lines[index].charAt(0) == '#')
  10535. {
  10536. var text = lines[index];
  10537. index++;
  10538. while (index < lines.length && text.charAt(text.length - 1) == '\\' &&
  10539. lines[index].charAt(0) == '#')
  10540. {
  10541. text = text.substring(0, text.length - 1) + mxUtils.trim(lines[index].substring(1));
  10542. index++;
  10543. }
  10544. if (text.charAt(1) != '#')
  10545. {
  10546. // Processing instruction
  10547. var idx = text.indexOf(':');
  10548. if (idx > 0)
  10549. {
  10550. var key = mxUtils.trim(text.substring(1, idx));
  10551. var value = mxUtils.trim(text.substring(idx + 1));
  10552. if (key == 'label')
  10553. {
  10554. label = graph.sanitizeHtml(value);
  10555. }
  10556. else if (key == 'labelname' && value.length > 0 && value != '-')
  10557. {
  10558. labelname = value;
  10559. }
  10560. else if (key == 'labels' && value.length > 0 && value != '-')
  10561. {
  10562. labels = JSON.parse(value);
  10563. }
  10564. else if (key == 'style')
  10565. {
  10566. style = value;
  10567. }
  10568. else if (key == 'parentstyle')
  10569. {
  10570. parentstyle = value;
  10571. }
  10572. else if (key == 'stylename' && value.length > 0 && value != '-')
  10573. {
  10574. stylename = value;
  10575. }
  10576. else if (key == 'styles' && value.length > 0 && value != '-')
  10577. {
  10578. styles = JSON.parse(value);
  10579. }
  10580. else if (key == 'vars' && value.length > 0 && value != '-')
  10581. {
  10582. vars = JSON.parse(value);
  10583. }
  10584. else if (key == 'identity' && value.length > 0 && value != '-')
  10585. {
  10586. identity = value;
  10587. }
  10588. else if (key == 'parent' && value.length > 0 && value != '-')
  10589. {
  10590. parent = value;
  10591. }
  10592. else if (key == 'namespace' && value.length > 0 && value != '-')
  10593. {
  10594. namespace = value;
  10595. }
  10596. else if (key == 'width')
  10597. {
  10598. width = value;
  10599. }
  10600. else if (key == 'height')
  10601. {
  10602. height = value;
  10603. }
  10604. else if (key == 'left' && value.length > 0)
  10605. {
  10606. left = value;
  10607. }
  10608. else if (key == 'top' && value.length > 0)
  10609. {
  10610. top = value;
  10611. }
  10612. else if (key == 'ignore')
  10613. {
  10614. ignore = value.split(',');
  10615. }
  10616. else if (key == 'connect')
  10617. {
  10618. edges.push(JSON.parse(value));
  10619. }
  10620. else if (key == 'link')
  10621. {
  10622. link = value;
  10623. }
  10624. else if (key == 'padding')
  10625. {
  10626. padding = parseFloat(value);
  10627. }
  10628. else if (key == 'edgespacing')
  10629. {
  10630. edgespacing = parseFloat(value);
  10631. }
  10632. else if (key == 'nodespacing')
  10633. {
  10634. nodespacing = parseFloat(value);
  10635. }
  10636. else if (key == 'levelspacing')
  10637. {
  10638. levelspacing = parseFloat(value);
  10639. }
  10640. else if (key == 'layout')
  10641. {
  10642. layout = value;
  10643. }
  10644. }
  10645. }
  10646. }
  10647. if (lines[index] == null)
  10648. {
  10649. throw new Error(mxResources.get('invalidOrMissingFile'));
  10650. }
  10651. // Converts identity and parent to index and validates XML attribute names
  10652. var keys = this.editor.csvToArray(lines[index]);
  10653. var identityIndex = null;
  10654. var parentIndex = null;
  10655. var attribs = [];
  10656. for (var i = 0; i < keys.length; i++)
  10657. {
  10658. if (identity == keys[i])
  10659. {
  10660. identityIndex = i;
  10661. }
  10662. if (parent == keys[i])
  10663. {
  10664. parentIndex = i;
  10665. }
  10666. attribs.push(mxUtils.trim(keys[i]).replace(/[^a-z0-9]+/ig, '_').
  10667. replace(/^\d+/, '').replace(/_+$/, ''));
  10668. }
  10669. if (label == null)
  10670. {
  10671. label = '%' + attribs[0] + '%';
  10672. }
  10673. if (edges != null)
  10674. {
  10675. for (var e = 0; e < edges.length; e++)
  10676. {
  10677. if (lookups[edges[e].to] == null)
  10678. {
  10679. lookups[edges[e].to] = {};
  10680. }
  10681. }
  10682. }
  10683. // Parse and validate input
  10684. var arrays = [];
  10685. for (var i = index + 1; i < lines.length; i++)
  10686. {
  10687. var values = this.editor.csvToArray(lines[i]);
  10688. if (values == null)
  10689. {
  10690. var short = (lines[i].length > 40) ? lines[i].substring(0, 40) + '...' : lines[i];
  10691. throw new Error(short + ' (' + i + '):\n' + mxResources.get('containsValidationErrors'));
  10692. }
  10693. else if (values.length > 0)
  10694. {
  10695. arrays.push(values);
  10696. }
  10697. }
  10698. graph.model.beginUpdate();
  10699. try
  10700. {
  10701. for (var i = 0; i < arrays.length; i++)
  10702. {
  10703. var values = arrays[i];
  10704. var cell = null;
  10705. var id = (identityIndex != null) ? namespace + values[identityIndex] : null;
  10706. if (id != null)
  10707. {
  10708. cell = graph.model.getCell(id);
  10709. }
  10710. var exists = cell != null;
  10711. var newCell = new mxCell(label, new mxGeometry(x0, y,
  10712. 0, 0), style || 'whiteSpace=wrap;html=1;');
  10713. newCell.vertex = true;
  10714. newCell.id = id;
  10715. for (var j = 0; j < values.length; j++)
  10716. {
  10717. graph.setAttributeForCell(newCell, attribs[j], values[j]);
  10718. }
  10719. if (labelname != null && labels != null)
  10720. {
  10721. var tempLabel = labels[newCell.getAttribute(labelname)];
  10722. if (tempLabel != null)
  10723. {
  10724. graph.labelChanged(newCell, tempLabel);
  10725. }
  10726. }
  10727. if (stylename != null && styles != null)
  10728. {
  10729. var tempStyle = styles[newCell.getAttribute(stylename)];
  10730. if (tempStyle != null)
  10731. {
  10732. newCell.style = tempStyle;
  10733. }
  10734. }
  10735. graph.setAttributeForCell(newCell, 'placeholders', '1');
  10736. newCell.style = graph.replacePlaceholders(newCell, newCell.style, vars);
  10737. if (exists)
  10738. {
  10739. graph.model.setGeometry(cell, newCell.geometry);
  10740. graph.model.setStyle(cell, newCell.style);
  10741. if (mxUtils.indexOf(cells, cell) < 0)
  10742. {
  10743. cells.push(cell);
  10744. }
  10745. }
  10746. cell = newCell;
  10747. if (!exists)
  10748. {
  10749. for (var e = 0; e < edges.length; e++)
  10750. {
  10751. lookups[edges[e].to][cell.getAttribute(edges[e].to)] = cell;
  10752. }
  10753. }
  10754. if (link != null && link != 'link')
  10755. {
  10756. graph.setLinkForCell(cell, cell.getAttribute(link));
  10757. // Removes attribute
  10758. graph.setAttributeForCell(cell, link, null);
  10759. }
  10760. // Sets the size
  10761. graph.fireEvent(new mxEventObject('cellsInserted', 'cells', [cell]));
  10762. var size = this.editor.graph.getPreferredSizeForCell(cell);
  10763. if (cell.vertex)
  10764. {
  10765. if (left != null && cell.getAttribute(left) != null)
  10766. {
  10767. cell.geometry.x = x0 + parseFloat(cell.getAttribute(left));
  10768. }
  10769. if (top != null && cell.getAttribute(top) != null)
  10770. {
  10771. cell.geometry.y = y0 + parseFloat(cell.getAttribute(top));
  10772. }
  10773. if (width.charAt(0) == '@' && cell.getAttribute(width.substring(1)) != null)
  10774. {
  10775. cell.geometry.width = parseFloat(cell.getAttribute(width.substring(1)));
  10776. }
  10777. else
  10778. {
  10779. cell.geometry.width = (width == 'auto') ? size.width + padding : parseFloat(width);
  10780. }
  10781. if (height.charAt(0) == '@' && cell.getAttribute(height.substring(1)) != null)
  10782. {
  10783. cell.geometry.height = parseFloat(cell.getAttribute(height.substring(1)));
  10784. }
  10785. else
  10786. {
  10787. cell.geometry.height = (height == 'auto') ? size.height + padding : parseFloat(height);
  10788. }
  10789. y += cell.geometry.height + nodespacing;
  10790. }
  10791. if (!exists)
  10792. {
  10793. var parent = (parentIndex != null) ? graph.model.getCell(
  10794. namespace + values[parentIndex]) : null;
  10795. allCells.push(cell);
  10796. if (parent != null)
  10797. {
  10798. parent.style = graph.replacePlaceholders(parent, parentstyle, vars);
  10799. graph.addCell(cell, parent);
  10800. }
  10801. else
  10802. {
  10803. cells.push(graph.addCell(cell));
  10804. }
  10805. }
  10806. else
  10807. {
  10808. if (dups[id] == null)
  10809. {
  10810. dups[id] = [];
  10811. }
  10812. dups[id].push(cell);
  10813. }
  10814. }
  10815. var roots = cells.slice();
  10816. var select = cells.slice();
  10817. for (var e = 0; e < edges.length; e++)
  10818. {
  10819. var edge = edges[e];
  10820. for (var i = 0; i < allCells.length; i++)
  10821. {
  10822. var cell = allCells[i];
  10823. var insertEdge = mxUtils.bind(this, function(realCell, dataCell, edge)
  10824. {
  10825. var tmp = dataCell.getAttribute(edge.from);
  10826. if (tmp != null)
  10827. {
  10828. // Removes attribute
  10829. graph.setAttributeForCell(dataCell, edge.from, null);
  10830. if (tmp != '')
  10831. {
  10832. var refs = tmp.split(',');
  10833. for (var j = 0; j < refs.length; j++)
  10834. {
  10835. var ref = lookups[edge.to][refs[j]];
  10836. if (ref != null)
  10837. {
  10838. var label = edge.label;
  10839. if (edge.fromlabel != null)
  10840. {
  10841. label = (dataCell.getAttribute(edge.fromlabel) || '') + (label || '');
  10842. }
  10843. if (edge.sourcelabel != null)
  10844. {
  10845. label = graph.replacePlaceholders(dataCell,
  10846. edge.sourcelabel, vars) + (label || '');
  10847. }
  10848. if (edge.tolabel != null)
  10849. {
  10850. label = (label || '') + (ref.getAttribute(edge.tolabel) || '');
  10851. }
  10852. if (edge.targetlabel != null)
  10853. {
  10854. label = (label || '') + graph.replacePlaceholders(
  10855. ref, edge.targetlabel, vars);
  10856. }
  10857. var placeholders = ((edge.placeholders == 'target') ==
  10858. !edge.invert) ? ref : realCell;
  10859. var style = (edge.style != null) ?
  10860. graph.replacePlaceholders(placeholders, edge.style, vars) :
  10861. graph.createCurrentEdgeStyle();
  10862. var edgeCell = graph.insertEdge(null, null, label || '', (edge.invert) ?
  10863. ref : realCell, (edge.invert) ? realCell : ref, style);
  10864. // Adds additional edge labels
  10865. if (edge.labels != null)
  10866. {
  10867. for (var k = 0; k < edge.labels.length; k++)
  10868. {
  10869. var def = edge.labels[k];
  10870. var elx = (def.x != null) ? def.x : 0;
  10871. var ely = (def.y != null) ? def.y : 0;
  10872. var st = 'resizable=0;html=1;';
  10873. var el = new mxCell(def.label || k,
  10874. new mxGeometry(elx, ely, 0, 0), st);
  10875. el.vertex = true;
  10876. el.connectable = false;
  10877. el.geometry.relative = true;
  10878. if (def.placeholders != null)
  10879. {
  10880. el.value = graph.replacePlaceholders(
  10881. ((def.placeholders == 'target') ==
  10882. !edge.invert) ? ref : realCell,
  10883. el.value, vars)
  10884. }
  10885. if (def.dx != null || def.dy != null)
  10886. {
  10887. el.geometry.offset = new mxPoint(
  10888. (def.dx != null) ? def.dx : 0,
  10889. (def.dy != null) ? def.dy : 0);
  10890. }
  10891. edgeCell.insert(el);
  10892. }
  10893. }
  10894. select.push(edgeCell);
  10895. mxUtils.remove((edge.invert) ? realCell : ref, roots);
  10896. }
  10897. }
  10898. }
  10899. }
  10900. });
  10901. insertEdge(cell, cell, edge);
  10902. // Checks more entries
  10903. if (dups[cell.id] != null)
  10904. {
  10905. for (var j = 0; j < dups[cell.id].length; j++)
  10906. {
  10907. insertEdge(cell, dups[cell.id][j], edge);
  10908. }
  10909. }
  10910. }
  10911. }
  10912. // Removes ignored attributes after processing above
  10913. if (ignore != null)
  10914. {
  10915. for (var i = 0; i < allCells.length; i++)
  10916. {
  10917. var cell = allCells[i];
  10918. for (var j = 0; j < ignore.length; j++)
  10919. {
  10920. graph.setAttributeForCell(cell, mxUtils.trim(ignore[j]), null);
  10921. }
  10922. }
  10923. }
  10924. if (cells.length > 0)
  10925. {
  10926. var edgeLayout = new mxParallelEdgeLayout(graph);
  10927. edgeLayout.spacing = edgespacing;
  10928. edgeLayout.checkOverlap = true;
  10929. var postProcess = function()
  10930. {
  10931. if (edgeLayout.spacing > 0)
  10932. {
  10933. edgeLayout.execute(graph.getDefaultParent());
  10934. }
  10935. // Aligns cells to grid and/or rounds positions
  10936. for (var i = 0; i < cells.length; i++)
  10937. {
  10938. var geo = graph.getCellGeometry(cells[i]);
  10939. geo.x = Math.round(graph.snap(geo.x));
  10940. geo.y = Math.round(graph.snap(geo.y));
  10941. if (width == 'auto')
  10942. {
  10943. geo.width = Math.round(graph.snap(geo.width));
  10944. }
  10945. if (height == 'auto')
  10946. {
  10947. geo.height = Math.round(graph.snap(geo.height));
  10948. }
  10949. }
  10950. };
  10951. if (layout.charAt(0) == '[')
  10952. {
  10953. // Required for layouts to work with new cells
  10954. var temp = afterInsert;
  10955. graph.view.validate();
  10956. this.executeLayoutList(JSON.parse(layout), function()
  10957. {
  10958. postProcess();
  10959. temp();
  10960. });
  10961. afterInsert = null;
  10962. }
  10963. else if (layout == 'circle')
  10964. {
  10965. var circleLayout = new mxCircleLayout(graph);
  10966. circleLayout.disableEdgeStyle = false;
  10967. circleLayout.resetEdges = false;
  10968. var circleLayoutIsVertexIgnored = circleLayout.isVertexIgnored;
  10969. // Ignore other cells
  10970. circleLayout.isVertexIgnored = function(vertex)
  10971. {
  10972. return circleLayoutIsVertexIgnored.apply(this, arguments) ||
  10973. mxUtils.indexOf(cells, vertex) < 0;
  10974. };
  10975. this.executeLayout(function()
  10976. {
  10977. circleLayout.execute(graph.getDefaultParent());
  10978. postProcess();
  10979. }, true, afterInsert);
  10980. afterInsert = null;
  10981. }
  10982. else if (layout == 'horizontaltree' || layout == 'verticaltree' ||
  10983. (layout == 'auto' && select.length == 2 * cells.length - 1 && roots.length == 1))
  10984. {
  10985. // Required for layouts to work with new cells
  10986. graph.view.validate();
  10987. var treeLayout = new mxCompactTreeLayout(graph, layout == 'horizontaltree');
  10988. treeLayout.levelDistance = nodespacing;
  10989. treeLayout.edgeRouting = false;
  10990. treeLayout.resetEdges = false;
  10991. this.executeLayout(function()
  10992. {
  10993. treeLayout.execute(graph.getDefaultParent(), (roots.length > 0) ? roots[0] : null);
  10994. }, true, afterInsert);
  10995. afterInsert = null;
  10996. }
  10997. else if (layout == 'horizontalflow' || layout == 'verticalflow' ||
  10998. (layout == 'auto' && roots.length == 1))
  10999. {
  11000. // Required for layouts to work with new cells
  11001. graph.view.validate();
  11002. var flowLayout = new mxHierarchicalLayout(graph,
  11003. (layout == 'horizontalflow') ? mxConstants.DIRECTION_WEST : mxConstants.DIRECTION_NORTH);
  11004. flowLayout.intraCellSpacing = nodespacing;
  11005. flowLayout.parallelEdgeSpacing = edgespacing;
  11006. flowLayout.interRankCellSpacing = levelspacing;
  11007. flowLayout.disableEdgeStyle = false;
  11008. this.executeLayout(function()
  11009. {
  11010. flowLayout.execute(graph.getDefaultParent(), select);
  11011. // Workaround for flow layout moving cells to origin
  11012. graph.moveCells(select, x0, y0);
  11013. }, true, afterInsert);
  11014. afterInsert = null;
  11015. }
  11016. else if (layout == 'organic' || (layout == 'auto' &&
  11017. select.length > cells.length))
  11018. {
  11019. // Required for layouts to work with new cells
  11020. graph.view.validate();
  11021. var organicLayout = new mxFastOrganicLayout(graph);
  11022. organicLayout.forceConstant = nodespacing * 3;
  11023. organicLayout.disableEdgeStyle = false;
  11024. organicLayout.resetEdges = false;
  11025. var organicLayoutIsVertexIgnored = organicLayout.isVertexIgnored;
  11026. // Ignore other cells
  11027. organicLayout.isVertexIgnored = function(vertex)
  11028. {
  11029. return organicLayoutIsVertexIgnored.apply(this, arguments) ||
  11030. mxUtils.indexOf(cells, vertex) < 0;
  11031. };
  11032. this.executeLayout(function()
  11033. {
  11034. organicLayout.execute(graph.getDefaultParent());
  11035. postProcess();
  11036. }, true, afterInsert);
  11037. afterInsert = null;
  11038. }
  11039. }
  11040. this.hideDialog();
  11041. }
  11042. finally
  11043. {
  11044. graph.model.endUpdate();
  11045. }
  11046. if (afterInsert != null)
  11047. {
  11048. afterInsert();
  11049. }
  11050. }
  11051. }
  11052. catch (e)
  11053. {
  11054. this.handleError(e);
  11055. }
  11056. };
  11057. /**
  11058. * Translates this point by the given vector.
  11059. *
  11060. * @param {number} dx X-coordinate of the translation.
  11061. * @param {number} dy Y-coordinate of the translation.
  11062. */
  11063. EditorUi.prototype.getSearch = function(exclude)
  11064. {
  11065. var result = '';
  11066. if (urlParams['offline'] != '1' && urlParams['demo'] != '1' && exclude != null && window.location.search.length > 0)
  11067. {
  11068. var amp = '?';
  11069. for (var key in urlParams)
  11070. {
  11071. if (mxUtils.indexOf(exclude, key) < 0 && urlParams[key] != null)
  11072. {
  11073. result += amp + key + '=' + urlParams[key];
  11074. amp = '&';
  11075. }
  11076. }
  11077. }
  11078. else
  11079. {
  11080. result = window.location.search;
  11081. }
  11082. return result;
  11083. };
  11084. /**
  11085. * Returns the URL for a copy of this editor with no state.
  11086. */
  11087. EditorUi.prototype.getUrl = function(pathname)
  11088. {
  11089. var href = (pathname != null) ? pathname : window.location.pathname;
  11090. var parms = (href.indexOf('?') > 0) ? 1 : 0;
  11091. if (urlParams['offline'] == '1')
  11092. {
  11093. href += window.location.search;
  11094. }
  11095. else
  11096. {
  11097. var ignored = ['tmp', 'libs', 'clibs', 'state', 'fileId', 'code', 'share', 'notitle',
  11098. 'data', 'url', 'embed', 'client', 'create', 'title', 'splash'];
  11099. // Removes template URL parameter for new blank diagram
  11100. for (var key in urlParams)
  11101. {
  11102. if (mxUtils.indexOf(ignored, key) < 0)
  11103. {
  11104. if (parms == 0)
  11105. {
  11106. href += '?';
  11107. }
  11108. else
  11109. {
  11110. href += '&';
  11111. }
  11112. if (urlParams[key] != null)
  11113. {
  11114. href += key + '=' + urlParams[key];
  11115. parms++;
  11116. }
  11117. }
  11118. }
  11119. }
  11120. return href;
  11121. };
  11122. /**
  11123. * Overrides link dialog.
  11124. */
  11125. EditorUi.prototype.showLinkDialog = function(value, btnLabel, fn, showNewWindowOption, linkTarget)
  11126. {
  11127. var dlg = new LinkDialog(this, value, btnLabel, fn, true, showNewWindowOption, linkTarget);
  11128. this.showDialog(dlg.container, 560, 130, true, true);
  11129. dlg.init();
  11130. };
  11131. /**
  11132. * Overrides createOutline
  11133. */
  11134. var editorUiCreateOutline = EditorUi.prototype.createOutline;
  11135. EditorUi.prototype.createOutline = function(wnd)
  11136. {
  11137. var outline = editorUiCreateOutline.apply(this, arguments);
  11138. var graph = this.editor.graph;
  11139. var outlineGetSourceGraphBounds = outline.getSourceGraphBounds;
  11140. outline.getSourceGraphBounds = function()
  11141. {
  11142. if (mxUtils.hasScrollbars(graph.container) && graph.pageVisible && this.source.minimumGraphSize != null)
  11143. {
  11144. var pb = this.source.getPagePadding();
  11145. var s = this.source.view.scale;
  11146. var result = new mxRectangle(0, 0, Math.ceil(this.source.minimumGraphSize.width - 2 * pb.x / s),
  11147. Math.ceil(this.source.minimumGraphSize.height - 2 * pb.y / s));
  11148. return result;
  11149. }
  11150. return outlineGetSourceGraphBounds.apply(this, arguments);
  11151. };
  11152. var outlineGetSourceContainerSize = outline.getSourceContainerSize;
  11153. outline.getSourceContainerSize = function()
  11154. {
  11155. if (mxUtils.hasScrollbars(graph.container) && this.source.minimumGraphSize != null)
  11156. {
  11157. var pad = this.source.getPagePadding();
  11158. var s = this.source.view.scale;
  11159. return new mxRectangle(0, 0, Math.ceil(this.source.minimumGraphSize.width * s - 2 * pad.x),
  11160. Math.ceil(this.source.minimumGraphSize.height * s - 2 * pad.y));
  11161. }
  11162. return outlineGetSourceContainerSize.apply(this, arguments);
  11163. };
  11164. outline.getOutlineOffset = function(scale)
  11165. {
  11166. if (mxUtils.hasScrollbars(graph.container) && this.source.minimumGraphSize != null)
  11167. {
  11168. var pb = this.source.getPagePadding();
  11169. var dx = Math.max(0, (outline.outline.container.clientWidth / scale - (this.source.minimumGraphSize.width - 2 * pb.x)) / 2);
  11170. var dy = Math.max(0, (outline.outline.container.clientHeight / scale - (this.source.minimumGraphSize.height - 2 * pb.y)) / 2);
  11171. // Why is vertical offset negative relative to dy
  11172. return new mxPoint(Math.round(dx - pb.x), Math.round(dy - pb.y - 5 / scale));
  11173. }
  11174. return new mxPoint(8 / scale, 8 / scale);
  11175. };
  11176. var outlineInit = outline.init;
  11177. outline.init = function()
  11178. {
  11179. outlineInit.apply(this, arguments);
  11180. // Problem: Need to override a function in the view but the view is created
  11181. // with the graph so a refresh of the page is needed to see this change.
  11182. outline.outline.view.getBackgroundPageBounds = function()
  11183. {
  11184. var layout = graph.getPageLayout();
  11185. var page = graph.getPageSize();
  11186. return new mxRectangle(this.scale * (this.translate.x + layout.x * page.width),
  11187. this.scale * (this.translate.y + layout.y * page.height),
  11188. this.scale * layout.width * page.width,
  11189. this.scale * layout.height * page.height);
  11190. };
  11191. outline.outline.view.validateBackgroundPage();
  11192. };
  11193. this.editor.addListener('pageSelected', function(sender, evt)
  11194. {
  11195. var change = evt.getProperty('change');
  11196. var graph = outline.source;
  11197. var g = outline.outline;
  11198. g.pageScale = graph.pageScale;
  11199. g.pageFormat = graph.pageFormat;
  11200. g.background = graph.background;
  11201. g.pageVisible = graph.pageVisible;
  11202. g.background = graph.background;
  11203. var current = mxUtils.getCurrentStyle(graph.container);
  11204. g.container.style.backgroundColor = current.backgroundColor;
  11205. if (graph.view.backgroundPageShape != null && g.view.backgroundPageShape != null)
  11206. {
  11207. g.view.backgroundPageShape.fill = graph.view.backgroundPageShape.fill;
  11208. }
  11209. outline.outline.view.clear(change.previousPage.root, true);
  11210. outline.outline.view.validate();
  11211. });
  11212. return outline;
  11213. };
  11214. /**
  11215. * Returns the number of storage options enabled
  11216. */
  11217. EditorUi.prototype.getServiceCount = function(allowBrowser)
  11218. {
  11219. var serviceCount = 1;
  11220. if (this.drive != null || typeof window.DriveClient === 'function')
  11221. {
  11222. serviceCount++
  11223. }
  11224. if (this.dropbox != null || typeof window.DropboxClient === 'function')
  11225. {
  11226. serviceCount++
  11227. }
  11228. if (this.oneDrive != null || typeof window.OneDriveClient === 'function')
  11229. {
  11230. serviceCount++
  11231. }
  11232. if (this.gitHub != null)
  11233. {
  11234. serviceCount++
  11235. }
  11236. if (this.gitLab != null)
  11237. {
  11238. serviceCount++
  11239. }
  11240. if (allowBrowser && isLocalStorage && urlParams['browser'] == '1')
  11241. {
  11242. serviceCount++
  11243. }
  11244. return serviceCount;
  11245. }
  11246. /**
  11247. * Updates action and menu states depending on the file.
  11248. */
  11249. EditorUi.prototype.updateUi = function()
  11250. {
  11251. this.updateButtonContainer();
  11252. this.updateActionStates();
  11253. // Action states that only need update for new files
  11254. var file = this.getCurrentFile();
  11255. var active = file != null || (urlParams['embed'] == '1' &&
  11256. this.editor.graph.isEnabled());
  11257. this.menus.get('viewPanels').setEnabled(active);
  11258. this.menus.get('viewZoom').setEnabled(active);
  11259. var restricted = (urlParams['embed'] != '1' ||
  11260. !this.editor.graph.isEnabled()) &&
  11261. (file == null || file.isRestricted());
  11262. this.actions.get('makeCopy').setEnabled(!restricted);
  11263. this.actions.get('print').setEnabled(!restricted);
  11264. this.menus.get('exportAs').setEnabled(!restricted);
  11265. this.menus.get('embed').setEnabled(!restricted);
  11266. // Disables libraries and extras menu in embed mode
  11267. // while waiting for file data
  11268. var libsEnabled = urlParams['embed'] != '1' ||
  11269. this.editor.graph.isEnabled();
  11270. this.menus.get('extras').setEnabled(libsEnabled);
  11271. if (Editor.enableCustomLibraries)
  11272. {
  11273. this.menus.get('openLibraryFrom').setEnabled(libsEnabled);
  11274. this.menus.get('newLibrary').setEnabled(libsEnabled);
  11275. }
  11276. // Disables actions in the toolbar
  11277. var editable = (urlParams['embed'] == '1' &&
  11278. this.editor.graph.isEnabled()) ||
  11279. (file != null && file.isEditable());
  11280. this.actions.get('image').setEnabled(active);
  11281. this.actions.get('zoomIn').setEnabled(active);
  11282. this.actions.get('zoomOut').setEnabled(active);
  11283. this.actions.get('resetView').setEnabled(active);
  11284. // Updates undo history states
  11285. this.actions.get('undo').setEnabled(this.canUndo() && editable);
  11286. this.actions.get('redo').setEnabled(this.canRedo() && editable);
  11287. // Disables menus
  11288. this.menus.get('edit').setEnabled(active);
  11289. this.menus.get('view').setEnabled(active);
  11290. this.menus.get('importFrom').setEnabled(editable);
  11291. this.menus.get('arrange').setEnabled(editable);
  11292. // Disables connection drop downs in toolbar
  11293. if (this.toolbar != null)
  11294. {
  11295. if (this.toolbar.edgeShapeMenu != null)
  11296. {
  11297. this.toolbar.edgeShapeMenu.setEnabled(editable);
  11298. }
  11299. if (this.toolbar.edgeStyleMenu != null)
  11300. {
  11301. this.toolbar.edgeStyleMenu.setEnabled(editable);
  11302. }
  11303. }
  11304. this.updateUserElement();
  11305. };
  11306. /**
  11307. * Hook for subclassers
  11308. */
  11309. EditorUi.prototype.updateButtonContainer = function()
  11310. {
  11311. // do nothing
  11312. };
  11313. /**
  11314. * Hook for subclassers
  11315. */
  11316. EditorUi.prototype.updateUserElement = function()
  11317. {
  11318. // do nothing
  11319. };
  11320. /**
  11321. * Hook for subclassers
  11322. */
  11323. EditorUi.prototype.scheduleSanityCheck = function()
  11324. {
  11325. // do nothing
  11326. };
  11327. /**
  11328. * Hook for subclassers
  11329. */
  11330. EditorUi.prototype.stopSanityCheck = function()
  11331. {
  11332. // do nothing
  11333. };
  11334. /**
  11335. * Returns true if a diagram is cative and editable.
  11336. */
  11337. EditorUi.prototype.isDiagramActive = function()
  11338. {
  11339. var file = this.getCurrentFile();
  11340. return (file != null && file.isEditable()) ||
  11341. (urlParams['embed'] == '1' && this.editor.graph.isEnabled());
  11342. };
  11343. /**
  11344. * Updates action states depending on the selection.
  11345. */
  11346. var editorUiUpdateActionStates = EditorUi.prototype.updateActionStates;
  11347. EditorUi.prototype.updateActionStates = function()
  11348. {
  11349. editorUiUpdateActionStates.apply(this, arguments);
  11350. var graph = this.editor.graph;
  11351. var active = this.isDiagramActive();
  11352. var file = this.getCurrentFile();
  11353. var enabled = file != null || urlParams['embed'] == '1';
  11354. this.actions.get('pageSetup').setEnabled(active);
  11355. this.actions.get('autosave').setEnabled(file != null && file.isEditable() && file.isAutosaveOptional());
  11356. this.actions.get('guides').setEnabled(active);
  11357. this.actions.get('editData').setEnabled(active);
  11358. this.actions.get('shadowVisible').setEnabled(active);
  11359. this.actions.get('connectionArrows').setEnabled(active);
  11360. this.actions.get('connectionPoints').setEnabled(active);
  11361. this.actions.get('copyStyle').setEnabled(active && !graph.isSelectionEmpty());
  11362. this.actions.get('pasteStyle').setEnabled(active && !graph.isSelectionEmpty());
  11363. this.actions.get('editGeometry').setEnabled(graph.getModel().isVertex(graph.getSelectionCell()));
  11364. this.actions.get('createShape').setEnabled(active);
  11365. this.actions.get('createRevision').setEnabled(active);
  11366. this.actions.get('moveToFolder').setEnabled(file != null);
  11367. this.actions.get('makeCopy').setEnabled(file != null && !file.isRestricted());
  11368. this.actions.get('editDiagram').setEnabled(active && (file == null || !file.isRestricted()));
  11369. this.actions.get('publishLink').setEnabled(file != null && !file.isRestricted());
  11370. this.actions.get('tags').setEnabled(this.diagramContainer.style.visibility != 'hidden');
  11371. this.actions.get('layers').setEnabled(this.diagramContainer.style.visibility != 'hidden');
  11372. this.actions.get('outline').setEnabled(this.diagramContainer.style.visibility != 'hidden');
  11373. this.actions.get('rename').setEnabled((file != null && file.isRenamable()) || urlParams['embed'] == '1');
  11374. this.actions.get('close').setEnabled(file != null);
  11375. this.menus.get('publish').setEnabled(file != null && !file.isRestricted());
  11376. var findReplace = this.actions.get('findReplace');
  11377. findReplace.setEnabled(this.diagramContainer.style.visibility != 'hidden');
  11378. findReplace.label = mxResources.get('find') + ((graph.isEnabled()) ?
  11379. '/' + mxResources.get('replace') : '') + '...';
  11380. var state = graph.view.getState(graph.getSelectionCell());
  11381. this.actions.get('editShape').setEnabled(active && state != null && state.shape != null && state.shape.stencil != null);
  11382. };
  11383. /**
  11384. * Overridden to remove export dialog in chromeless lightbox.
  11385. */
  11386. var editoUiDestroy = EditorUi.prototype.destroy;
  11387. EditorUi.prototype.destroy = function()
  11388. {
  11389. if (this.exportDialog != null)
  11390. {
  11391. this.exportDialog.parentNode.removeChild(this.exportDialog);
  11392. this.exportDialog = null;
  11393. }
  11394. editoUiDestroy.apply(this, arguments);
  11395. };
  11396. /**
  11397. * Overrides export dialog for using ui functions for save and setting global switches.
  11398. */
  11399. if (window.ExportDialog != null)
  11400. {
  11401. ExportDialog.showXmlOption = false;
  11402. ExportDialog.showGifOption = false;
  11403. ExportDialog.exportFile = function(editorUi, name, format, bg, s, b, dpi, grid)
  11404. {
  11405. var graph = editorUi.editor.graph;
  11406. if (format == 'xml')
  11407. {
  11408. editorUi.hideDialog();
  11409. editorUi.saveData(name, 'xml', mxUtils.getXml(editorUi.editor.getGraphXml()), 'text/xml');
  11410. }
  11411. else if (format == 'svg')
  11412. {
  11413. editorUi.hideDialog();
  11414. editorUi.saveData(name, 'svg', mxUtils.getXml(graph.getSvg(bg, s, b)), 'image/svg+xml');
  11415. }
  11416. else
  11417. {
  11418. var data = editorUi.getFileData(true, null, null, null, null, true);
  11419. var bounds = graph.getGraphBounds();
  11420. var w = Math.floor(bounds.width * s / graph.view.scale);
  11421. var h = Math.floor(bounds.height * s / graph.view.scale);
  11422. if (data.length <= MAX_REQUEST_SIZE && w * h < MAX_AREA)
  11423. {
  11424. editorUi.hideDialog();
  11425. if ((format == 'png' || format == 'jpg' || format == 'jpeg') && editorUi.isExportToCanvas())
  11426. {
  11427. if (format == 'png')
  11428. {
  11429. editorUi.exportImage(s, bg == null || bg == 'none', true,
  11430. false, false, b, true, false, null, grid, dpi);
  11431. }
  11432. else
  11433. {
  11434. editorUi.exportImage(s, false, true,
  11435. false, false, b, true, false, 'jpeg', grid);
  11436. }
  11437. }
  11438. else
  11439. {
  11440. var extras = {globalVars: graph.getExportVariables()};
  11441. if (grid)
  11442. {
  11443. extras.grid = {
  11444. size: graph.gridSize,
  11445. steps: graph.view.gridSteps,
  11446. color: graph.view.gridColor
  11447. };
  11448. }
  11449. editorUi.saveRequest(name, format,
  11450. function(newTitle, base64)
  11451. {
  11452. return new mxXmlRequest(EXPORT_URL, 'format=' + format + '&base64=' + (base64 || '0') +
  11453. ((newTitle != null) ? '&filename=' + encodeURIComponent(newTitle) : '') +
  11454. '&extras=' + encodeURIComponent(JSON.stringify(extras)) +
  11455. (dpi > 0? '&dpi=' + dpi : '') +
  11456. '&bg=' + ((bg != null) ? bg : 'none') + '&w=' + w + '&h=' + h +
  11457. '&border=' + b + '&xml=' + encodeURIComponent(data));
  11458. });
  11459. }
  11460. }
  11461. else
  11462. {
  11463. mxUtils.alert(mxResources.get('drawingTooLarge'));
  11464. }
  11465. }
  11466. };
  11467. }
  11468. EditorUi.prototype.getDiagramTextContent = function()
  11469. {
  11470. this.editor.graph.setEnabled(false);
  11471. var graph = this.editor.graph;
  11472. var allPagesTxt = '';
  11473. if (this.pages != null)
  11474. {
  11475. for (var i = 0; i < this.pages.length; i++)
  11476. {
  11477. var pageGraph = graph;
  11478. if (this.currentPage != this.pages[i])
  11479. {
  11480. pageGraph = this.createTemporaryGraph(graph.getStylesheet());
  11481. this.updatePageRoot(this.pages[i]);
  11482. pageGraph.model.setRoot(this.pages[i].root);
  11483. }
  11484. allPagesTxt += this.pages[i].getName() + ' ' + pageGraph.getIndexableText() + ' ';
  11485. }
  11486. }
  11487. else
  11488. {
  11489. allPagesTxt = graph.getIndexableText();
  11490. }
  11491. this.editor.graph.setEnabled(true);
  11492. return allPagesTxt;
  11493. };
  11494. EditorUi.prototype.showRemotelyStoredLibrary = function(title)
  11495. {
  11496. var selectedLibs = {};
  11497. var div = document.createElement('div');
  11498. div.style.whiteSpace = 'nowrap';
  11499. var graph = this.editor.graph;
  11500. var hd = document.createElement('h3');
  11501. mxUtils.write(hd, mxUtils.htmlEntities(title));
  11502. hd.style.cssText = 'width:100%;text-align:center;margin-top:0px;margin-bottom:12px';
  11503. div.appendChild(hd);
  11504. var libsSection = document.createElement('div');
  11505. libsSection.style.cssText = 'border:1px solid lightGray;overflow: auto;height:300px';
  11506. libsSection.innerHTML = '<div style="text-align:center;padding:8px;"><img src="' + IMAGE_PATH + '/spin.gif"></div>';
  11507. var loadedLibs = {};
  11508. try
  11509. {
  11510. var custLibs = mxSettings.getCustomLibraries();
  11511. for (var j = 0; j < custLibs.length; j++)
  11512. {
  11513. var l = custLibs[j];
  11514. if (l.substring(0, 1) == 'R')
  11515. {
  11516. var libDesc = JSON.parse(decodeURIComponent(l.substring(1)));
  11517. loadedLibs[libDesc[0]] = {
  11518. id: libDesc[0],
  11519. title: libDesc[1],
  11520. downloadUrl: libDesc[2]
  11521. };
  11522. }
  11523. }
  11524. }
  11525. catch(e){}
  11526. this.remoteInvoke('getCustomLibraries', null, null, function(libsList)
  11527. {
  11528. libsSection.innerHTML = '';
  11529. if (libsList.length == 0)
  11530. {
  11531. libsSection.innerHTML = '<div style="text-align:center;padding-top:20px;color:gray;">' +
  11532. mxUtils.htmlEntities(mxResources.get('noLibraries')) + '</div>';
  11533. }
  11534. else
  11535. {
  11536. for (var i = 0; i < libsList.length; i++)
  11537. {
  11538. var lib = libsList[i];
  11539. if (loadedLibs[lib.id])
  11540. {
  11541. selectedLibs[lib.id] = lib;
  11542. }
  11543. var libCheck = this.addCheckbox(libsSection, lib.title, loadedLibs[lib.id]);
  11544. (function(lib2, check)
  11545. {
  11546. mxEvent.addListener(check, 'change', function()
  11547. {
  11548. if (this.checked)
  11549. {
  11550. selectedLibs[lib2.id] = lib2;
  11551. }
  11552. else
  11553. {
  11554. delete selectedLibs[lib2.id];
  11555. }
  11556. });
  11557. })(lib, libCheck)
  11558. }
  11559. }
  11560. }, mxUtils.bind(this, function(e)
  11561. {
  11562. libsSection.innerHTML = '';
  11563. var status = document.createElement('div');
  11564. status.style.padding = '8px';
  11565. status.style.textAlign = 'center';
  11566. mxUtils.write(status, mxResources.get('error') + ': ');
  11567. mxUtils.write(status, (e != null && e.message != null) ?
  11568. e.message : mxResources.get('unknownError'));
  11569. libsSection.appendChild(status);
  11570. }));
  11571. div.appendChild(libsSection);
  11572. var dlg = new CustomDialog(this, div, mxUtils.bind(this, function()
  11573. {
  11574. this.spinner.spin(document.body, mxResources.get('loading'));
  11575. var pendingLibs = 0;
  11576. for (var id in selectedLibs)
  11577. {
  11578. if (loadedLibs[id] != null) continue; //already loaded!
  11579. pendingLibs++;
  11580. (mxUtils.bind(this, function(lib)
  11581. {
  11582. this.remoteInvoke('getFileContent', [lib.downloadUrl], null, mxUtils.bind(this, function(libContent)
  11583. {
  11584. pendingLibs--;
  11585. if (pendingLibs == 0) this.spinner.stop();
  11586. try
  11587. {
  11588. this.loadLibrary(new RemoteLibrary(this, libContent, lib));
  11589. }
  11590. catch (e)
  11591. {
  11592. this.handleError(e, mxResources.get('errorLoadingFile'));
  11593. }
  11594. }), mxUtils.bind(this, function()
  11595. {
  11596. pendingLibs--;
  11597. if (pendingLibs == 0) this.spinner.stop();
  11598. this.handleError(null, mxResources.get('errorLoadingFile'));
  11599. }));
  11600. }))(selectedLibs[id]);
  11601. }
  11602. for (var id in loadedLibs)
  11603. {
  11604. if (!selectedLibs[id]) //Removed
  11605. {
  11606. this.closeLibrary(new RemoteLibrary(this, null, loadedLibs[id])); //create a dummy library such that we can call closeLibrary
  11607. }
  11608. }
  11609. if (pendingLibs == 0) this.spinner.stop();
  11610. }), null, null, 'https://www.diagrams.net/doc/faq/custom-libraries-confluence-cloud');
  11611. this.showDialog(dlg.container, 340, 375, true, true, null, null, null, null, true);
  11612. };
  11613. //Remote invokation, currently limited to functions in EditorUi (and its sub objects) for security reasons
  11614. //White-listed functions and some info about it
  11615. EditorUi.prototype.remoteInvokableFns = {
  11616. getDiagramTextContent: {isAsync: false},
  11617. getLocalStorageFile: {isAsync: false, allowedDomains: ['app.diagrams.net']},
  11618. getLocalStorageFileNames: {isAsync: false, allowedDomains: ['app.diagrams.net']},
  11619. setMigratedFlag: {isAsync: false, allowedDomains: ['app.diagrams.net']}
  11620. };
  11621. EditorUi.prototype.remoteInvokeCallbacks = [];
  11622. EditorUi.prototype.remoteInvokeQueue = [];
  11623. EditorUi.prototype.handleRemoteInvokeReady = function(remoteWin)
  11624. {
  11625. this.remoteWin = remoteWin;
  11626. for (var i = 0; i < this.remoteInvokeQueue.length; i++)
  11627. {
  11628. remoteWin.postMessage(this.remoteInvokeQueue[i], '*');
  11629. }
  11630. this.remoteInvokeQueue = [];
  11631. };
  11632. EditorUi.prototype.handleRemoteInvokeResponse = function(msg)
  11633. {
  11634. var msgMarkers = msg.msgMarkers;
  11635. var callback = this.remoteInvokeCallbacks[msgMarkers.callbackId];
  11636. if (callback == null)
  11637. {
  11638. throw new Error('No callback for ' + ((msgMarkers != null) ? msgMarkers.callbackId : 'null'));
  11639. }
  11640. else if (msg.error)
  11641. {
  11642. if (callback.error) callback.error(msg.error.errResp);
  11643. }
  11644. else if (callback.callback)
  11645. {
  11646. callback.callback.apply(this, msg.resp);
  11647. }
  11648. this.remoteInvokeCallbacks[msgMarkers.callbackId] = null; //set it to null only to keep the index
  11649. };
  11650. EditorUi.prototype.remoteInvoke = function(remoteFn, remoteFnArgs, msgMarkers, callback, error)
  11651. {
  11652. var acceptResponse = true;
  11653. var timeoutThread = window.setTimeout(mxUtils.bind(this, function()
  11654. {
  11655. acceptResponse = false;
  11656. error({code: App.ERROR_TIMEOUT, message: mxResources.get('timeout')});
  11657. }), this.timeout);
  11658. var wrapper = mxUtils.bind(this, function()
  11659. {
  11660. window.clearTimeout(timeoutThread);
  11661. if (acceptResponse)
  11662. {
  11663. callback.apply(this, arguments);
  11664. }
  11665. });
  11666. var errWrapper = mxUtils.bind(this, function()
  11667. {
  11668. window.clearTimeout(timeoutThread);
  11669. if (acceptResponse)
  11670. {
  11671. error.apply(this, arguments);
  11672. }
  11673. });
  11674. msgMarkers = msgMarkers || {};
  11675. msgMarkers.callbackId = this.remoteInvokeCallbacks.length;
  11676. this.remoteInvokeCallbacks.push({callback: wrapper, error: errWrapper});
  11677. var msg = JSON.stringify({event: 'remoteInvoke', funtionName: remoteFn, functionArgs: remoteFnArgs, msgMarkers: msgMarkers});
  11678. if (this.remoteWin != null) //remote invoke is ready
  11679. {
  11680. this.remoteWin.postMessage(msg, '*');
  11681. }
  11682. else
  11683. {
  11684. this.remoteInvokeQueue.push(msg);
  11685. }
  11686. };
  11687. EditorUi.prototype.handleRemoteInvoke = function(msg, origin)
  11688. {
  11689. var sendResponse = mxUtils.bind(this, function(resp, error)
  11690. {
  11691. var respMsg = {event: 'remoteInvokeResponse', msgMarkers: msg.msgMarkers};
  11692. if (error != null)
  11693. {
  11694. respMsg.error = {errResp: error};
  11695. }
  11696. else if (resp != null)
  11697. {
  11698. respMsg.resp = resp;
  11699. }
  11700. this.remoteWin.postMessage(JSON.stringify(respMsg), '*');
  11701. });
  11702. try
  11703. {
  11704. //Remote invoke are allowed to call functions in AC
  11705. var funtionName = msg.funtionName;
  11706. var functionInfo = this.remoteInvokableFns[funtionName];
  11707. if (functionInfo != null && typeof this[funtionName] === 'function')
  11708. {
  11709. if (functionInfo.allowedDomains)
  11710. {
  11711. var allowed = false;
  11712. for (var i = 0; i < functionInfo.allowedDomains.length; i++)
  11713. {
  11714. if (origin == 'https://' + functionInfo.allowedDomains[i])
  11715. {
  11716. allowed = true;
  11717. break;
  11718. }
  11719. }
  11720. if (!allowed)
  11721. {
  11722. sendResponse(null, 'Invalid Call: ' + funtionName + ' is not allowed.');
  11723. return;
  11724. }
  11725. }
  11726. var functionArgs = msg.functionArgs;
  11727. //Confirm functionArgs are not null and is array, otherwise, discard it
  11728. if (!Array.isArray(functionArgs))
  11729. {
  11730. functionArgs = [];
  11731. }
  11732. //for functions with callbacks (async) we assume last two arguments are success, error
  11733. if (functionInfo.isAsync)
  11734. {
  11735. //success
  11736. functionArgs.push(function()
  11737. {
  11738. sendResponse(Array.prototype.slice.apply(arguments));
  11739. });
  11740. //error
  11741. functionArgs.push(function(err)
  11742. {
  11743. sendResponse(null, err || 'Unkown Error');
  11744. });
  11745. this[funtionName].apply(this, functionArgs);
  11746. }
  11747. else
  11748. {
  11749. var resp = this[funtionName].apply(this, functionArgs);
  11750. sendResponse([resp]);
  11751. }
  11752. }
  11753. else
  11754. {
  11755. sendResponse(null, 'Invalid Call: ' + funtionName + ' is not found.');
  11756. }
  11757. }
  11758. catch(e)
  11759. {
  11760. sendResponse(null, 'Invalid Call: An error occured, ' + e.message);
  11761. }
  11762. };
  11763. /**
  11764. * Opens the application keystore.
  11765. */
  11766. EditorUi.prototype.openDatabase = function(success, error)
  11767. {
  11768. if (this.database == null)
  11769. {
  11770. var indexedDB = window.indexedDB || window.mozIndexedDB || window.webkitIndexedDB;
  11771. if (indexedDB != null)
  11772. {
  11773. try
  11774. {
  11775. var req = indexedDB.open('database', 2);
  11776. req.onupgradeneeded = function(e)
  11777. {
  11778. try
  11779. {
  11780. var db = req.result;
  11781. if (e.oldVersion < 1)
  11782. {
  11783. // Version 1 is the first version of the database.
  11784. db.createObjectStore('objects', {keyPath: 'key'});
  11785. }
  11786. if (e.oldVersion < 2)
  11787. {
  11788. // Version 2 introduces browser file storage.
  11789. db.createObjectStore('files', {keyPath: 'title'});
  11790. db.createObjectStore('filesInfo', {keyPath: 'title'});
  11791. EditorUi.migrateStorageFiles = isLocalStorage;
  11792. }
  11793. }
  11794. catch (e)
  11795. {
  11796. if (error != null)
  11797. {
  11798. error(e);
  11799. }
  11800. }
  11801. }
  11802. req.onsuccess = mxUtils.bind(this, function(e)
  11803. {
  11804. var db = req.result;
  11805. this.database = db;
  11806. if (EditorUi.migrateStorageFiles)
  11807. {
  11808. StorageFile.migrate(db);
  11809. EditorUi.migrateStorageFiles = false;
  11810. }
  11811. if (location.host == 'app.diagrams.net' && !this.drawioMigrationStarted)
  11812. {
  11813. this.drawioMigrationStarted = true;
  11814. this.getDatabaseItem('.drawioMigrated3', mxUtils.bind(this, function(value)
  11815. {
  11816. if (value && urlParams['forceMigration'] != '1') //Already migrated
  11817. {
  11818. return;
  11819. }
  11820. var drawioFrame = document.createElement('iframe');
  11821. drawioFrame.style.display = 'none';
  11822. drawioFrame.setAttribute('src', 'https://www.draw.io?embed=1&proto=json&forceMigration=' + urlParams['forceMigration']);
  11823. document.body.appendChild(drawioFrame);
  11824. var collectNames = true, allDone = false;
  11825. var fileNames, index = 0;
  11826. var markAsMigrated = mxUtils.bind(this, function()
  11827. {
  11828. allDone = true;
  11829. this.setDatabaseItem('.drawioMigrated3', true);
  11830. drawioFrame.contentWindow.postMessage(JSON.stringify({action: 'remoteInvoke', funtionName: 'setMigratedFlag'}), '*');
  11831. });
  11832. var next = mxUtils.bind(this, function()
  11833. {
  11834. index++;
  11835. fetchOneFile();
  11836. });
  11837. var fetchOneFile = mxUtils.bind(this, function()
  11838. {
  11839. try
  11840. {
  11841. if (index >= fileNames.length)
  11842. {
  11843. markAsMigrated();
  11844. return;
  11845. }
  11846. var fileTitle = fileNames[index];
  11847. StorageFile.getFileContent(this, fileTitle, mxUtils.bind(this, function(data)
  11848. {
  11849. if (data == null || (fileTitle == '.scratchpad' && data == this.emptyLibraryXml)) //Don't overwrite
  11850. {
  11851. drawioFrame.contentWindow.postMessage(JSON.stringify({action: 'remoteInvoke', funtionName: 'getLocalStorageFile', functionArgs: [fileTitle]}), '*');
  11852. }
  11853. else
  11854. {
  11855. next();
  11856. }
  11857. }), next); //Ignore errors
  11858. }
  11859. catch(e)
  11860. {
  11861. //Log error
  11862. console.log(e);
  11863. }
  11864. });
  11865. var importOneFile = mxUtils.bind(this, function(file)
  11866. {
  11867. try
  11868. {
  11869. this.setDatabaseItem(null, [{
  11870. title: file.title,
  11871. size: file.data.length,
  11872. lastModified: Date.now(),
  11873. type: file.isLib? 'L' : 'F'
  11874. }, {
  11875. title: file.title,
  11876. data: file.data
  11877. }], next, next /* Ignore errors */, ['filesInfo', 'files']);
  11878. }
  11879. catch(e)
  11880. {
  11881. //Log error
  11882. console.log(e);
  11883. }
  11884. });
  11885. var messageListener = mxUtils.bind(this, function(evt)
  11886. {
  11887. try
  11888. {
  11889. //Only accept messages from migration iframe
  11890. if (evt.source != drawioFrame.contentWindow)
  11891. {
  11892. return;
  11893. }
  11894. var drawMsg = {};
  11895. try
  11896. {
  11897. drawMsg = JSON.parse(evt.data);
  11898. }
  11899. catch(e){} //Ignore
  11900. if (drawMsg.event == 'init')
  11901. {
  11902. drawioFrame.contentWindow.postMessage(JSON.stringify({action: 'remoteInvokeReady'}), '*');
  11903. drawioFrame.contentWindow.postMessage(JSON.stringify({action: 'remoteInvoke', funtionName: 'getLocalStorageFileNames'}), '*');
  11904. }
  11905. else if (drawMsg.event == 'remoteInvokeResponse' && !allDone)
  11906. {
  11907. if (collectNames)
  11908. {
  11909. if (drawMsg.resp != null && drawMsg.resp.length > 0 && drawMsg.resp[0] != null)
  11910. {
  11911. fileNames = drawMsg.resp[0];
  11912. collectNames = false;
  11913. fetchOneFile();
  11914. }
  11915. else
  11916. {
  11917. //Nothing in draw.io localStorage
  11918. markAsMigrated();
  11919. }
  11920. }
  11921. else
  11922. {
  11923. //Add the file, then move to the next
  11924. if (drawMsg.resp != null && drawMsg.resp.length > 0 && drawMsg.resp[0] != null)
  11925. {
  11926. importOneFile(drawMsg.resp[0]);
  11927. }
  11928. else
  11929. {
  11930. next();
  11931. }
  11932. }
  11933. }
  11934. }
  11935. catch(e)
  11936. {
  11937. console.log(e);
  11938. }
  11939. });
  11940. window.addEventListener('message', messageListener);
  11941. })); //Ignore errors
  11942. }
  11943. success(db);
  11944. db.onversionchange = function()
  11945. {
  11946. //TODO Handle DB revision update while code is running
  11947. // Save open file and request a page reload before closing the DB
  11948. db.close();
  11949. };
  11950. });
  11951. req.onerror = error;
  11952. req.onblocked = function()
  11953. {
  11954. //TODO Use this when a new version is introduced
  11955. // there's another open connection to same database
  11956. // and it wasn't closed after db.onversionchange triggered for them
  11957. };
  11958. }
  11959. catch (e)
  11960. {
  11961. if (error != null)
  11962. {
  11963. error(e);
  11964. }
  11965. }
  11966. }
  11967. else if (error != null)
  11968. {
  11969. error();
  11970. }
  11971. }
  11972. else
  11973. {
  11974. success(this.database);
  11975. }
  11976. };
  11977. /**
  11978. * Add/Update item(s) in the database. It supports multiple stores transactions by sending an array of data, storeName
  11979. * (key is optional, can be an array also if multiple stores are needed)
  11980. */
  11981. EditorUi.prototype.setDatabaseItem = function(key, data, success, error, storeName)
  11982. {
  11983. this.openDatabase(mxUtils.bind(this, function(db)
  11984. {
  11985. try
  11986. {
  11987. storeName = storeName || 'objects';
  11988. if (!Array.isArray(storeName))
  11989. {
  11990. storeName = [storeName];
  11991. key = [key];
  11992. data = [data];
  11993. }
  11994. var trx = db.transaction(storeName, 'readwrite');
  11995. trx.oncomplete = success;
  11996. trx.onerror = error;
  11997. for (var i = 0; i < storeName.length; i++)
  11998. {
  11999. trx.objectStore(storeName[i]).put(key != null && key[i] != null? {key: key[i], data: data[i]} : data[i]);
  12000. }
  12001. }
  12002. catch (e)
  12003. {
  12004. if (error != null)
  12005. {
  12006. error(e);
  12007. }
  12008. }
  12009. }), error);
  12010. };
  12011. /**
  12012. * Removes the item for the given key from the database.
  12013. */
  12014. EditorUi.prototype.removeDatabaseItem = function(key, success, error, storeName)
  12015. {
  12016. this.openDatabase(mxUtils.bind(this, function(db)
  12017. {
  12018. storeName = storeName || 'objects';
  12019. if (!Array.isArray(storeName))
  12020. {
  12021. storeName = [storeName];
  12022. key = [key];
  12023. }
  12024. var trx = db.transaction(storeName, 'readwrite');
  12025. trx.oncomplete = success;
  12026. trx.onerror = error;
  12027. for (var i = 0; i < storeName.length; i++)
  12028. {
  12029. trx.objectStore(storeName[i]).delete(key[i]);
  12030. }
  12031. }), error);
  12032. };
  12033. /**
  12034. * Returns one item from the database.
  12035. */
  12036. EditorUi.prototype.getDatabaseItem = function(key, success, error, storeName)
  12037. {
  12038. this.openDatabase(mxUtils.bind(this, function(db)
  12039. {
  12040. try
  12041. {
  12042. storeName = storeName || 'objects';
  12043. var trx = db.transaction([storeName], 'readonly');
  12044. var req = trx.objectStore(storeName).get(key);
  12045. req.onsuccess = function()
  12046. {
  12047. success(req.result);
  12048. };
  12049. req.onerror = error;
  12050. }
  12051. catch (e)
  12052. {
  12053. if (error != null)
  12054. {
  12055. error(e);
  12056. }
  12057. }
  12058. }), error);
  12059. };
  12060. /**
  12061. * Returns all items from the database.
  12062. */
  12063. EditorUi.prototype.getDatabaseItems = function(success, error, storeName)
  12064. {
  12065. this.openDatabase(mxUtils.bind(this, function(db)
  12066. {
  12067. try
  12068. {
  12069. storeName = storeName || 'objects';
  12070. var trx = db.transaction([storeName], 'readonly');
  12071. var req = trx.objectStore(storeName).openCursor(
  12072. IDBKeyRange.lowerBound(0));
  12073. var items = [];
  12074. req.onsuccess = function(e)
  12075. {
  12076. if (e.target.result == null)
  12077. {
  12078. success(items);
  12079. }
  12080. else
  12081. {
  12082. items.push(e.target.result.value);
  12083. e.target.result.continue();
  12084. }
  12085. };
  12086. req.onerror = error;
  12087. }
  12088. catch (e)
  12089. {
  12090. if (error != null)
  12091. {
  12092. error(e);
  12093. }
  12094. }
  12095. }), error);
  12096. };
  12097. /**
  12098. * Returns all item keys from the database.
  12099. */
  12100. EditorUi.prototype.getDatabaseItemKeys = function(success, error, storeName)
  12101. {
  12102. this.openDatabase(mxUtils.bind(this, function(db)
  12103. {
  12104. try
  12105. {
  12106. storeName = storeName || 'objects';
  12107. var trx = db.transaction([storeName], 'readonly');
  12108. var req = trx.objectStore(storeName).getAllKeys();
  12109. req.onsuccess = function()
  12110. {
  12111. success(req.result);
  12112. };
  12113. req.onerror = error;
  12114. }
  12115. catch (e)
  12116. {
  12117. if (error != null)
  12118. {
  12119. error(e);
  12120. }
  12121. }
  12122. }), error);
  12123. };
  12124. /**
  12125. * Comments: We need these functions as wrapper of File functions in order to facilitate
  12126. * overriding them if comments are needed without having a file (e.g. Confluence Plugin)
  12127. */
  12128. /**
  12129. * Are comments supported
  12130. */
  12131. EditorUi.prototype.commentsSupported = function()
  12132. {
  12133. var file = this.getCurrentFile();
  12134. return file != null? file.commentsSupported() : false;
  12135. };
  12136. /**
  12137. * Show refresh button?
  12138. */
  12139. EditorUi.prototype.commentsRefreshNeeded = function()
  12140. {
  12141. var file = this.getCurrentFile();
  12142. return file != null? file.commentsRefreshNeeded() : true;
  12143. };
  12144. /**
  12145. * Show save button?
  12146. */
  12147. EditorUi.prototype.commentsSaveNeeded = function()
  12148. {
  12149. var file = this.getCurrentFile();
  12150. return file != null? file.commentsSaveNeeded() : false;
  12151. };
  12152. /**
  12153. * Get comments
  12154. */
  12155. EditorUi.prototype.getComments = function(success, error)
  12156. {
  12157. var file = this.getCurrentFile();
  12158. if (file != null)
  12159. {
  12160. file.getComments(success, error);
  12161. }
  12162. else
  12163. {
  12164. success([]); //placeholder
  12165. }
  12166. };
  12167. /**
  12168. * Add a comment
  12169. */
  12170. EditorUi.prototype.addComment = function(comment, success, error)
  12171. {
  12172. var file = this.getCurrentFile();
  12173. if (file != null)
  12174. {
  12175. file.addComment(comment, success, error);
  12176. }
  12177. else
  12178. {
  12179. success(Date.now()); //placeholder
  12180. }
  12181. };
  12182. /**
  12183. * Can add a reply to a reply
  12184. */
  12185. EditorUi.prototype.canReplyToReplies = function()
  12186. {
  12187. var file = this.getCurrentFile();
  12188. return file != null? file.canReplyToReplies() : true;
  12189. };
  12190. /**
  12191. * Can add comments (The permission to comment)
  12192. */
  12193. EditorUi.prototype.canComment = function()
  12194. {
  12195. var file = this.getCurrentFile();
  12196. return file != null? file.canComment() : true;
  12197. };
  12198. /**
  12199. * Get a new comment object
  12200. */
  12201. EditorUi.prototype.newComment = function(content, user)
  12202. {
  12203. var file = this.getCurrentFile();
  12204. if (file != null)
  12205. {
  12206. return file.newComment(content, user)
  12207. }
  12208. else
  12209. {
  12210. return new DrawioComment(this, null, content, Date.now(), Date.now(), false, user);
  12211. }
  12212. };
  12213. //==================================================== End of comments =================================================================
  12214. /**
  12215. * Does revisions history available
  12216. */
  12217. EditorUi.prototype.isRevisionHistorySupported = function()
  12218. {
  12219. var file = this.getCurrentFile();
  12220. return file != null && file.isRevisionHistorySupported();
  12221. };
  12222. /**
  12223. * Get revisions of current file
  12224. */
  12225. EditorUi.prototype.getRevisions = function(success, error)
  12226. {
  12227. var file = this.getCurrentFile();
  12228. if (file != null && file.getRevisions)
  12229. {
  12230. file.getRevisions(success, error);
  12231. }
  12232. else
  12233. {
  12234. error({message: mxResources.get('unknownError')});
  12235. }
  12236. };
  12237. /**
  12238. * Is revisions history enabled
  12239. */
  12240. EditorUi.prototype.isRevisionHistoryEnabled = function()
  12241. {
  12242. var file = this.getCurrentFile();
  12243. return file != null &&
  12244. ((file.constructor == DriveFile && file.isEditable()) ||
  12245. file.constructor == DropboxFile);
  12246. };
  12247. //===========Adding methods to find the service running draw.io and allowing calling draw.io remote services
  12248. EditorUi.prototype.getServiceName = function()
  12249. {
  12250. return 'draw.io';
  12251. };
  12252. EditorUi.prototype.addRemoteServiceSecurityCheck = function(xhr)
  12253. {
  12254. //Using a standard header with specific sequence
  12255. xhr.setRequestHeader('Content-Language', 'da, mi, en, de-DE');
  12256. };
  12257. //===========To Be Removed Soon==========
  12258. EditorUi.prototype.loadUrl = function(url, success, error, forceBinary, retry, dataUriPrefix, noBinary, headers)
  12259. {
  12260. EditorUi.logEvent('SHOULD NOT BE CALLED: loadUrl');
  12261. return this.editor.loadUrl(url, success, error, forceBinary, retry, dataUriPrefix, noBinary, headers);
  12262. };
  12263. EditorUi.prototype.loadFonts = function(then)
  12264. {
  12265. EditorUi.logEvent('SHOULD NOT BE CALLED: loadFonts');
  12266. return this.editor.loadFonts(then);
  12267. };
  12268. EditorUi.prototype.createSvgDataUri = function(svg)
  12269. {
  12270. EditorUi.logEvent('SHOULD NOT BE CALLED: createSvgDataUri');
  12271. return Editor.createSvgDataUri(svg);
  12272. };
  12273. EditorUi.prototype.embedCssFonts = function(fontCss, then)
  12274. {
  12275. EditorUi.logEvent('SHOULD NOT BE CALLED: embedCssFonts');
  12276. return this.editor.embedCssFonts(fontCss, then);
  12277. };
  12278. EditorUi.prototype.embedExtFonts = function(callback)
  12279. {
  12280. EditorUi.logEvent('SHOULD NOT BE CALLED: embedExtFonts');
  12281. return this.editor.embedExtFonts(callback);
  12282. };
  12283. EditorUi.prototype.exportToCanvas = function(callback, width, imageCache, background, error, limitHeight,
  12284. ignoreSelection, scale, transparentBackground, addShadow, converter, graph, border, noCrop, grid, keepTheme)
  12285. {
  12286. EditorUi.logEvent('SHOULD NOT BE CALLED: exportToCanvas');
  12287. return this.editor.exportToCanvas(callback, width, imageCache, background, error, limitHeight,
  12288. ignoreSelection, scale, transparentBackground, addShadow, converter, graph, border,
  12289. noCrop, grid, keepTheme);
  12290. };
  12291. EditorUi.prototype.createImageUrlConverter = function()
  12292. {
  12293. EditorUi.logEvent('SHOULD NOT BE CALLED: createImageUrlConverter');
  12294. return this.editor.createImageUrlConverter();
  12295. };
  12296. EditorUi.prototype.convertImages = function(svgRoot, callback, imageCache, converter)
  12297. {
  12298. EditorUi.logEvent('SHOULD NOT BE CALLED: convertImages');
  12299. return this.editor.convertImages(svgRoot, callback, imageCache, converter);
  12300. };
  12301. EditorUi.prototype.convertImageToDataUri = function(url, callback)
  12302. {
  12303. EditorUi.logEvent('SHOULD NOT BE CALLED: convertImageToDataUri');
  12304. return this.editor.convertImageToDataUri(url, callback);
  12305. };
  12306. EditorUi.prototype.base64Encode = function(str)
  12307. {
  12308. EditorUi.logEvent('SHOULD NOT BE CALLED: base64Encode');
  12309. return Editor.base64Encode(str);
  12310. };
  12311. EditorUi.prototype.updateCRC = function(crc, data, off, len)
  12312. {
  12313. EditorUi.logEvent('SHOULD NOT BE CALLED: updateCRC');
  12314. return Editor.updateCRC(crc, data, off, len);
  12315. };
  12316. EditorUi.prototype.crc32 = function(str)
  12317. {
  12318. EditorUi.logEvent('SHOULD NOT BE CALLED: crc32');
  12319. return Editor.crc32(str);
  12320. };
  12321. EditorUi.prototype.writeGraphModelToPng = function(data, type, key, value, error)
  12322. {
  12323. EditorUi.logEvent('SHOULD NOT BE CALLED: writeGraphModelToPng');
  12324. return Editor.writeGraphModelToPng(data, type, key, value, error);
  12325. };
  12326. //=======End of To Be Removed Soon==========
  12327. EditorUi.prototype.getLocalStorageFileNames = function()
  12328. {
  12329. if (localStorage.getItem('.localStorageMigrated') == '1' && urlParams['forceMigration'] != '1')
  12330. {
  12331. return null;
  12332. }
  12333. var files = [];
  12334. for (var i = 0; i < localStorage.length; i++)
  12335. {
  12336. var key = localStorage.key(i);
  12337. var value = localStorage.getItem(key);
  12338. if (key.length > 0 && (key == '.scratchpad' || key.charAt(0) != '.') && value.length > 0)
  12339. {
  12340. var isFile = (value.substring(0, 8) === '<mxfile ' ||
  12341. value.substring(0, 5) === '<?xml' || value.substring(0, 12) === '<!--[if IE]>');
  12342. var isLib = (value.substring(0, 11) === '<mxlibrary>');
  12343. if (isFile || isLib)
  12344. {
  12345. files.push(key);
  12346. }
  12347. }
  12348. }
  12349. return files;
  12350. };
  12351. EditorUi.prototype.getLocalStorageFile = function(key)
  12352. {
  12353. if (localStorage.getItem('.localStorageMigrated') == '1' && urlParams['forceMigration'] != '1')
  12354. {
  12355. return null;
  12356. }
  12357. var value = localStorage.getItem(key);
  12358. return {title: key, data: value, isLib: value.substring(0, 11) === '<mxlibrary>'};
  12359. };
  12360. EditorUi.prototype.setMigratedFlag = function()
  12361. {
  12362. localStorage.setItem('.localStorageMigrated', '1');
  12363. };
  12364. })();
  12365. /**
  12366. * Comments Window, It is used by both editor and viewer. So, it is here in a common place
  12367. */
  12368. var CommentsWindow = function(editorUi, x, y, w, h, saveCallback)
  12369. {
  12370. var readOnly = !editorUi.canComment();
  12371. var canReplyToReplies = editorUi.canReplyToReplies();
  12372. var curEdited = null;
  12373. var div = document.createElement('div');
  12374. div.className = 'geCommentsWin';
  12375. div.style.background = (Dialog.backdropColor == 'white') ? 'whiteSmoke' : Dialog.backdropColor;
  12376. var tbarHeight = (!EditorUi.compactUi) ? '30px' : '26px';
  12377. var listDiv = document.createElement('div');
  12378. listDiv.className = 'geCommentsList';
  12379. listDiv.style.backgroundColor = (Dialog.backdropColor == 'white') ? 'whiteSmoke' : Dialog.backdropColor;
  12380. listDiv.style.bottom = (parseInt(tbarHeight) + 7) + 'px';
  12381. div.appendChild(listDiv);
  12382. var noComments = document.createElement('span');
  12383. noComments.style.cssText = 'display:none;padding-top:10px;text-align:center;';
  12384. mxUtils.write(noComments, mxResources.get('noCommentsFound'));
  12385. var selectionComment = null;
  12386. var ldiv = document.createElement('div');
  12387. ldiv.className = 'geToolbarContainer geCommentsToolbar';
  12388. ldiv.style.height = tbarHeight;
  12389. ldiv.style.padding = (!EditorUi.compactUi) ? '1px' : '4px 0px 3px 0px';
  12390. ldiv.style.backgroundColor = (Dialog.backdropColor == 'white') ? 'whiteSmoke' : Dialog.backdropColor;
  12391. var link = document.createElement('a');
  12392. link.className = 'geButton';
  12393. function updateNoComments()
  12394. {
  12395. var divs = listDiv.getElementsByTagName('div');
  12396. var visibleCount = 0;
  12397. for (var i = 0; i < divs.length; i++)
  12398. {
  12399. if (divs[i].style.display != 'none' && divs[i].parentNode == listDiv)
  12400. {
  12401. visibleCount++;
  12402. }
  12403. }
  12404. noComments.style.display = (visibleCount == 0) ? 'block' : 'none';
  12405. };
  12406. function editComment(comment, cdiv, saveCallback, deleteOnCancel)
  12407. {
  12408. curEdited = {div: cdiv, comment: comment, saveCallback: saveCallback, deleteOnCancel: deleteOnCancel};
  12409. var commentTxt = cdiv.querySelector('.geCommentTxt');
  12410. var actionsDiv = cdiv.querySelector('.geCommentActionsList');
  12411. var textArea = document.createElement('textarea');
  12412. textArea.className = 'geCommentEditTxtArea';
  12413. textArea.style.minHeight = commentTxt.offsetHeight + 'px';
  12414. textArea.value = comment.content;
  12415. cdiv.insertBefore(textArea, commentTxt);
  12416. var btnDiv = document.createElement('div');
  12417. btnDiv.className = 'geCommentEditBtns';
  12418. function reset()
  12419. {
  12420. cdiv.removeChild(textArea);
  12421. cdiv.removeChild(btnDiv);
  12422. actionsDiv.style.display = 'block';
  12423. commentTxt.style.display = 'block';
  12424. };
  12425. var cancelBtn = mxUtils.button(mxResources.get('cancel'), function()
  12426. {
  12427. if (deleteOnCancel)
  12428. {
  12429. cdiv.parentNode.removeChild(cdiv);
  12430. updateNoComments();
  12431. }
  12432. else
  12433. {
  12434. reset();
  12435. }
  12436. curEdited = null;
  12437. });
  12438. cancelBtn.className = 'geCommentEditBtn';
  12439. btnDiv.appendChild(cancelBtn);
  12440. var saveBtn = mxUtils.button(mxResources.get('save'), function()
  12441. {
  12442. commentTxt.innerHTML = '';
  12443. comment.content = textArea.value;
  12444. mxUtils.write(commentTxt, comment.content);
  12445. reset();
  12446. saveCallback(comment);
  12447. curEdited = null;
  12448. });
  12449. // Updates modified state and handles placeholder text
  12450. mxEvent.addListener(textArea, 'keydown', mxUtils.bind(this, function(evt)
  12451. {
  12452. if (!mxEvent.isConsumed(evt))
  12453. {
  12454. if ((mxEvent.isControlDown(evt) || (mxClient.IS_MAC &&
  12455. mxEvent.isMetaDown(evt))) && evt.keyCode == 13 /* Ctrl+Enter */)
  12456. {
  12457. saveBtn.click();
  12458. mxEvent.consume(evt);
  12459. }
  12460. else if (evt.keyCode == 27 /* Escape */)
  12461. {
  12462. cancelBtn.click();
  12463. mxEvent.consume(evt);
  12464. }
  12465. }
  12466. }));
  12467. // Focused to include in viewport before focusin textbox
  12468. saveBtn.focus();
  12469. saveBtn.className = 'geCommentEditBtn gePrimaryBtn';
  12470. btnDiv.appendChild(saveBtn);
  12471. cdiv.insertBefore(btnDiv, commentTxt);
  12472. actionsDiv.style.display = 'none';
  12473. commentTxt.style.display = 'none';
  12474. textArea.focus();
  12475. };
  12476. function writeCommentDate(comment, dateDiv)
  12477. {
  12478. dateDiv.innerHTML = '';
  12479. var ts = new Date(comment.modifiedDate);
  12480. var str = editorUi.timeSince(ts);
  12481. if (str == null)
  12482. {
  12483. str = mxResources.get('lessThanAMinute');
  12484. }
  12485. mxUtils.write(dateDiv, mxResources.get('timeAgo', [str], '{1} ago'));
  12486. dateDiv.setAttribute('title', ts.toLocaleDateString() + ' ' +
  12487. ts.toLocaleTimeString());
  12488. };
  12489. function showBusy(commentDiv)
  12490. {
  12491. var busyImg = document.createElement('img');
  12492. busyImg.className = 'geCommentBusyImg';
  12493. busyImg.src= IMAGE_PATH + '/spin.gif';
  12494. commentDiv.appendChild(busyImg);
  12495. commentDiv.busyImg = busyImg;
  12496. };
  12497. function showError(commentDiv)
  12498. {
  12499. commentDiv.style.border = '1px solid red';
  12500. commentDiv.removeChild(commentDiv.busyImg);
  12501. };
  12502. function showDone(commentDiv)
  12503. {
  12504. commentDiv.style.border = '';
  12505. commentDiv.removeChild(commentDiv.busyImg);
  12506. };
  12507. function addComment(comment, parentArr, parent, level, showResolved)
  12508. {
  12509. //Skip resolved comments if showResolved is not set
  12510. if (!showResolved && comment.isResolved)
  12511. {
  12512. return;
  12513. }
  12514. noComments.style.display = 'none';
  12515. var cdiv = document.createElement('div');
  12516. cdiv.className = 'geCommentContainer';
  12517. cdiv.setAttribute('data-commentId', comment.id);
  12518. cdiv.style.marginLeft = (level * 20 + 5) + 'px';
  12519. if (comment.isResolved && !Editor.isDarkMode())
  12520. {
  12521. cdiv.style.backgroundColor = 'ghostWhite';
  12522. }
  12523. var headerDiv = document.createElement('div');
  12524. headerDiv.className = 'geCommentHeader';
  12525. var userImg = document.createElement('img');
  12526. userImg.className = 'geCommentUserImg';
  12527. userImg.src = comment.user.pictureUrl || Editor.userImage;
  12528. headerDiv.appendChild(userImg);
  12529. var headerTxt = document.createElement('div');
  12530. headerTxt.className = 'geCommentHeaderTxt';
  12531. headerDiv.appendChild(headerTxt);
  12532. var usernameDiv = document.createElement('div');
  12533. usernameDiv.className = 'geCommentUsername';
  12534. mxUtils.write(usernameDiv, comment.user.displayName || '');
  12535. headerTxt.appendChild(usernameDiv);
  12536. var dateDiv = document.createElement('div');
  12537. dateDiv.className = 'geCommentDate';
  12538. dateDiv.setAttribute('data-commentId', comment.id);
  12539. writeCommentDate(comment, dateDiv);
  12540. headerTxt.appendChild(dateDiv);
  12541. cdiv.appendChild(headerDiv);
  12542. var commentTxtDiv = document.createElement('div');
  12543. commentTxtDiv.className = 'geCommentTxt';
  12544. mxUtils.write(commentTxtDiv, comment.content || '');
  12545. cdiv.appendChild(commentTxtDiv);
  12546. if (comment.isLocked)
  12547. {
  12548. cdiv.style.opacity = '0.5';
  12549. }
  12550. var actionsDiv = document.createElement('div');
  12551. actionsDiv.className = 'geCommentActions';
  12552. var actionsList = document.createElement('ul');
  12553. actionsList.className = 'geCommentActionsList';
  12554. actionsDiv.appendChild(actionsList);
  12555. function addAction(name, evtHandler, hide)
  12556. {
  12557. var action = document.createElement('li');
  12558. action.className = 'geCommentAction';
  12559. var actionLnk = document.createElement('a');
  12560. actionLnk.className = 'geCommentActionLnk';
  12561. mxUtils.write(actionLnk, name);
  12562. action.appendChild(actionLnk);
  12563. mxEvent.addListener(actionLnk, 'click', function(evt)
  12564. {
  12565. evtHandler(evt, comment);
  12566. evt.preventDefault();
  12567. mxEvent.consume(evt);
  12568. });
  12569. actionsList.appendChild(action);
  12570. if (hide) action.style.display = 'none';
  12571. };
  12572. function collectReplies()
  12573. {
  12574. var replies = [];
  12575. var pdiv = cdiv;
  12576. function collectReplies(comment)
  12577. {
  12578. replies.push(pdiv);
  12579. if (comment.replies != null)
  12580. {
  12581. for (var i = 0; i < comment.replies.length; i++)
  12582. {
  12583. pdiv = pdiv.nextSibling;
  12584. collectReplies(comment.replies[i]);
  12585. }
  12586. }
  12587. }
  12588. collectReplies(comment);
  12589. return {pdiv: pdiv, replies: replies};
  12590. };
  12591. function addReply(initContent, editIt, saveCallback, doResolve, doReopen)
  12592. {
  12593. var pdiv = collectReplies().pdiv;
  12594. var newReply = editorUi.newComment(initContent, editorUi.getCurrentUser());
  12595. newReply.pCommentId = comment.id;
  12596. if (comment.replies == null) comment.replies = [];
  12597. var replyComment = addComment(newReply, comment.replies, pdiv, level + 1);
  12598. function doAddReply()
  12599. {
  12600. showBusy(replyComment);
  12601. comment.addReply(newReply, function(id)
  12602. {
  12603. newReply.id = id;
  12604. comment.replies.push(newReply);
  12605. showDone(replyComment);
  12606. if (saveCallback) saveCallback();
  12607. }, function(err)
  12608. {
  12609. doEdit();
  12610. showError(replyComment);
  12611. editorUi.handleError(err, null, null, null,
  12612. mxUtils.htmlEntities(mxResources.get('objectNotFound')));
  12613. }, doResolve, doReopen);
  12614. };
  12615. function doEdit()
  12616. {
  12617. editComment(newReply, replyComment, function(newReply)
  12618. {
  12619. doAddReply();
  12620. }, true);
  12621. };
  12622. if (editIt)
  12623. {
  12624. doEdit();
  12625. }
  12626. else
  12627. {
  12628. doAddReply();
  12629. }
  12630. };
  12631. if (!readOnly && !comment.isLocked && (level == 0 || canReplyToReplies))
  12632. {
  12633. addAction(mxResources.get('reply'), function()
  12634. {
  12635. addReply('', true);
  12636. }, comment.isResolved);
  12637. }
  12638. var user = editorUi.getCurrentUser();
  12639. if (user != null && user.id == comment.user.id && !readOnly && !comment.isLocked)
  12640. {
  12641. addAction(mxResources.get('edit'), function()
  12642. {
  12643. function doEditComment()
  12644. {
  12645. editComment(comment, cdiv, function()
  12646. {
  12647. showBusy(cdiv);
  12648. comment.editComment(comment.content, function()
  12649. {
  12650. showDone(cdiv);
  12651. }, function(err)
  12652. {
  12653. showError(cdiv);
  12654. doEditComment();
  12655. editorUi.handleError(err, null, null, null,
  12656. mxUtils.htmlEntities(mxResources.get('objectNotFound')));
  12657. });
  12658. });
  12659. };
  12660. doEditComment();
  12661. }, comment.isResolved);
  12662. addAction(mxResources.get('delete'), function()
  12663. {
  12664. editorUi.confirm(mxResources.get('areYouSure'), function()
  12665. {
  12666. showBusy(cdiv);
  12667. comment.deleteComment(function(markedOnly)
  12668. {
  12669. if (markedOnly === true)
  12670. {
  12671. var commentTxt = cdiv.querySelector('.geCommentTxt');
  12672. commentTxt.innerHTML = '';
  12673. mxUtils.write(commentTxt, mxResources.get('msgDeleted'));
  12674. var actions = cdiv.querySelectorAll('.geCommentAction');
  12675. for (var i = 0; i < actions.length; i++)
  12676. {
  12677. actions[i].parentNode.removeChild(actions[i]);
  12678. }
  12679. showDone(cdiv);
  12680. cdiv.style.opacity = '0.5';
  12681. }
  12682. else
  12683. {
  12684. var replies = collectReplies(comment).replies;
  12685. for (var i = 0; i < replies.length; i++)
  12686. {
  12687. listDiv.removeChild(replies[i]);
  12688. }
  12689. for (var i = 0; i < parentArr.length; i++)
  12690. {
  12691. if (parentArr[i] == comment)
  12692. {
  12693. parentArr.splice(i, 1);
  12694. break;
  12695. }
  12696. }
  12697. noComments.style.display = (listDiv.getElementsByTagName('div').length == 0) ? 'block' : 'none';
  12698. }
  12699. }, function(err)
  12700. {
  12701. showError(cdiv);
  12702. editorUi.handleError(err, null, null, null,
  12703. mxUtils.htmlEntities(mxResources.get('objectNotFound')));
  12704. });
  12705. });
  12706. }, comment.isResolved);
  12707. }
  12708. if (!readOnly && !comment.isLocked && level == 0) //Resolve is a top-level action only
  12709. {
  12710. function toggleResolve(evt)
  12711. {
  12712. function doToggle()
  12713. {
  12714. var resolveActionLnk = evt.target;
  12715. resolveActionLnk.innerHTML = '';
  12716. comment.isResolved = !comment.isResolved;
  12717. mxUtils.write(resolveActionLnk, comment.isResolved? mxResources.get('reopen') : mxResources.get('resolve'));
  12718. var actionsDisplay = comment.isResolved? 'none' : '';
  12719. var replies = collectReplies(comment).replies;
  12720. var color = (Editor.isDarkMode()) ? 'transparent' : (comment.isResolved? 'ghostWhite' : 'white');
  12721. for (var i = 0; i < replies.length; i++)
  12722. {
  12723. replies[i].style.backgroundColor = color;
  12724. var forOpenActions = replies[i].querySelectorAll('.geCommentAction');
  12725. for (var j = 0; j < forOpenActions.length; j ++)
  12726. {
  12727. if (forOpenActions[j] == resolveActionLnk.parentNode) continue;
  12728. forOpenActions[j].style.display = actionsDisplay;
  12729. }
  12730. if (!resolvedChecked)
  12731. {
  12732. replies[i].style.display = 'none';
  12733. }
  12734. }
  12735. updateNoComments();
  12736. };
  12737. if (comment.isResolved)
  12738. {
  12739. addReply(mxResources.get('reOpened') + ': ', true, doToggle, false, true);
  12740. }
  12741. else
  12742. {
  12743. addReply(mxResources.get('markedAsResolved'), false, doToggle, true);
  12744. }
  12745. };
  12746. addAction(comment.isResolved? mxResources.get('reopen') : mxResources.get('resolve'), toggleResolve);
  12747. }
  12748. cdiv.appendChild(actionsDiv);
  12749. if (parent != null)
  12750. {
  12751. listDiv.insertBefore(cdiv, parent.nextSibling);
  12752. }
  12753. else
  12754. {
  12755. listDiv.appendChild(cdiv);
  12756. }
  12757. for (var i = 0; comment.replies != null && i < comment.replies.length; i++)
  12758. {
  12759. var reply = comment.replies[i];
  12760. reply.isResolved = comment.isResolved; //copy isResolved to child comments (replies)
  12761. addComment(reply, comment.replies, null, level + 1, showResolved);
  12762. }
  12763. if (curEdited != null)
  12764. {
  12765. if (curEdited.comment.id == comment.id)
  12766. {
  12767. var origContent = comment.content;
  12768. comment.content = curEdited.comment.content;
  12769. editComment(comment, cdiv, curEdited.saveCallback, curEdited.deleteOnCancel);
  12770. comment.content = origContent;
  12771. }
  12772. else if (curEdited.comment.id == null && curEdited.comment.pCommentId == comment.id)
  12773. {
  12774. listDiv.appendChild(curEdited.div);
  12775. editComment(curEdited.comment, curEdited.div, curEdited.saveCallback, curEdited.deleteOnCancel);
  12776. }
  12777. }
  12778. return cdiv;
  12779. };
  12780. if (!readOnly)
  12781. {
  12782. var addLink = link.cloneNode();
  12783. addLink.innerHTML = '<div class="geSprite geSprite-plus" style="display:inline-block;"></div>';
  12784. addLink.setAttribute('title', mxResources.get('create') + '...');
  12785. mxEvent.addListener(addLink, 'click', function(evt)
  12786. {
  12787. var newComment = editorUi.newComment('', editorUi.getCurrentUser());
  12788. var newCommentDiv = addComment(newComment, comments, null, 0);
  12789. function doAddComment()
  12790. {
  12791. editComment(newComment, newCommentDiv, function(newComment)
  12792. {
  12793. showBusy(newCommentDiv);
  12794. editorUi.addComment(newComment, function(id)
  12795. {
  12796. newComment.id = id;
  12797. comments.push(newComment);
  12798. showDone(newCommentDiv);
  12799. }, function(err)
  12800. {
  12801. showError(newCommentDiv);
  12802. doAddComment();
  12803. editorUi.handleError(err, null, null, null,
  12804. mxUtils.htmlEntities(mxResources.get('objectNotFound')));
  12805. });
  12806. }, true);
  12807. }
  12808. doAddComment();
  12809. evt.preventDefault();
  12810. mxEvent.consume(evt);
  12811. });
  12812. ldiv.appendChild(addLink);
  12813. }
  12814. var resolvedLink = link.cloneNode();
  12815. resolvedLink.innerHTML = '<img src="' + IMAGE_PATH + '/check.png" style="width: 16px; padding: 2px;">';
  12816. resolvedLink.setAttribute('title', mxResources.get('showResolved'));
  12817. var resolvedChecked = false;
  12818. if (Editor.isDarkMode())
  12819. {
  12820. resolvedLink.style.filter = 'invert(100%)';
  12821. }
  12822. mxEvent.addListener(resolvedLink, 'click', function(evt)
  12823. {
  12824. resolvedChecked = !resolvedChecked;
  12825. this.className = resolvedChecked? 'geButton geCheckedBtn' : 'geButton';
  12826. refresh();
  12827. evt.preventDefault();
  12828. mxEvent.consume(evt);
  12829. });
  12830. ldiv.appendChild(resolvedLink);
  12831. if (editorUi.commentsRefreshNeeded())
  12832. {
  12833. var refreshLink = link.cloneNode();
  12834. refreshLink.innerHTML = '<img src="' + IMAGE_PATH + '/update16.png" style="width: 16px; padding: 2px;">';
  12835. refreshLink.setAttribute('title', mxResources.get('refresh'));
  12836. if (Editor.isDarkMode())
  12837. {
  12838. refreshLink.style.filter = 'invert(100%)';
  12839. }
  12840. mxEvent.addListener(refreshLink, 'click', function(evt)
  12841. {
  12842. refresh();
  12843. evt.preventDefault();
  12844. mxEvent.consume(evt);
  12845. });
  12846. ldiv.appendChild(refreshLink);
  12847. }
  12848. if (editorUi.commentsSaveNeeded())
  12849. {
  12850. var saveLink = link.cloneNode();
  12851. saveLink.innerHTML = '<img src="' + IMAGE_PATH + '/save.png" style="width: 20px; padding: 2px;">';
  12852. saveLink.setAttribute('title', mxResources.get('save'));
  12853. if (Editor.isDarkMode())
  12854. {
  12855. saveLink.style.filter = 'invert(100%)';
  12856. }
  12857. mxEvent.addListener(saveLink, 'click', function(evt)
  12858. {
  12859. saveCallback();
  12860. evt.preventDefault();
  12861. mxEvent.consume(evt);
  12862. });
  12863. ldiv.appendChild(saveLink);
  12864. }
  12865. div.appendChild(ldiv);
  12866. var comments = [];
  12867. var refresh = mxUtils.bind(this, function()
  12868. {
  12869. this.hasError = false;
  12870. if (curEdited != null)
  12871. {
  12872. try
  12873. {
  12874. curEdited.div = curEdited.div.cloneNode(true);
  12875. var commentEditTxt = curEdited.div.querySelector('.geCommentEditTxtArea');
  12876. var commentEditBtns = curEdited.div.querySelector('.geCommentEditBtns');
  12877. curEdited.comment.content = commentEditTxt.value;
  12878. commentEditTxt.parentNode.removeChild(commentEditTxt);
  12879. commentEditBtns.parentNode.removeChild(commentEditBtns);
  12880. }
  12881. catch (e)
  12882. {
  12883. editorUi.handleError(e);
  12884. }
  12885. }
  12886. listDiv.innerHTML = '<div style="padding-top:10px;text-align:center;"><img src="' + IMAGE_PATH + '/spin.gif" valign="middle"> ' +
  12887. mxUtils.htmlEntities(mxResources.get('loading')) + '...</div>';
  12888. canReplyToReplies = editorUi.canReplyToReplies();
  12889. if (editorUi.commentsSupported())
  12890. {
  12891. editorUi.getComments(function(list)
  12892. {
  12893. function sortReplies(replies)
  12894. {
  12895. if (replies != null)
  12896. {
  12897. //Sort replies old to new
  12898. replies.sort(function(r1, r2)
  12899. {
  12900. return new Date(r1.modifiedDate) - new Date(r2.modifiedDate);
  12901. });
  12902. for (var i = 0; i < replies.length; i++)
  12903. {
  12904. sortReplies(replies[i].replies);
  12905. }
  12906. }
  12907. };
  12908. //Sort comments old to new
  12909. list.sort(function(c1, c2)
  12910. {
  12911. return new Date(c1.modifiedDate) - new Date(c2.modifiedDate);
  12912. });
  12913. listDiv.innerHTML = '';
  12914. listDiv.appendChild(noComments);
  12915. noComments.style.display = 'block';
  12916. comments = list;
  12917. for (var i = 0; i < comments.length; i++)
  12918. {
  12919. sortReplies(comments[i].replies);
  12920. addComment(comments[i], comments, null, 0, resolvedChecked);
  12921. }
  12922. //New comment case
  12923. if (curEdited != null && curEdited.comment.id == null && curEdited.comment.pCommentId == null)
  12924. {
  12925. listDiv.appendChild(curEdited.div);
  12926. editComment(curEdited.comment, curEdited.div, curEdited.saveCallback, curEdited.deleteOnCancel);
  12927. }
  12928. }, mxUtils.bind(this, function(err)
  12929. {
  12930. listDiv.innerHTML = mxUtils.htmlEntities(mxResources.get('error') + (err && err.message? ': ' + err.message : ''));
  12931. this.hasError = true;
  12932. }));
  12933. }
  12934. else
  12935. {
  12936. //TODO if comments are not supported, close the dialog
  12937. listDiv.innerHTML = mxUtils.htmlEntities(mxResources.get('error'));
  12938. }
  12939. });
  12940. refresh();
  12941. this.refreshComments = refresh;
  12942. //Refresh the modified date of each comment if the window is visible
  12943. var refreshCommentsTime = mxUtils.bind(this, function()
  12944. {
  12945. if (!this.window.isVisible()) return; //only update if it is visible
  12946. var modDateDivs = listDiv.querySelectorAll('.geCommentDate');
  12947. var modDateDivsMap = {};
  12948. for (var i = 0; i < modDateDivs.length; i++)
  12949. {
  12950. var div = modDateDivs[i];
  12951. modDateDivsMap[div.getAttribute('data-commentId')] = div;
  12952. }
  12953. function processComment(comment)
  12954. {
  12955. var div = modDateDivsMap[comment.id];
  12956. if (div == null) return; //resolved comments
  12957. writeCommentDate(comment, div);
  12958. for (var i = 0; comment.replies != null && i < comment.replies.length; i++)
  12959. {
  12960. processComment(comment.replies[i]);
  12961. }
  12962. };
  12963. for (var i = 0; i < comments.length; i++)
  12964. {
  12965. processComment(comments[i]);
  12966. }
  12967. });
  12968. //Periodically refresh time every one minute
  12969. setInterval(refreshCommentsTime, 60000);
  12970. this.refreshCommentsTime = refreshCommentsTime;
  12971. this.window = new mxWindow(mxResources.get('comments'), div, x, y, w, h, true, true);
  12972. this.window.minimumSize = new mxRectangle(0, 0, 300, 200);
  12973. this.window.destroyOnClose = false;
  12974. this.window.setMaximizable(false);
  12975. this.window.setResizable(true);
  12976. this.window.setClosable(true);
  12977. this.window.setVisible(true);
  12978. this.window.addListener(mxEvent.SHOW, mxUtils.bind(this, function()
  12979. {
  12980. this.window.fit();
  12981. }));
  12982. this.window.setLocation = function(x, y)
  12983. {
  12984. var iw = window.innerWidth || document.body.clientWidth || document.documentElement.clientWidth;
  12985. var ih = window.innerHeight || document.body.clientHeight || document.documentElement.clientHeight;
  12986. x = Math.max(0, Math.min(x, iw - this.table.clientWidth));
  12987. y = Math.max(0, Math.min(y, ih - this.table.clientHeight - 48));
  12988. if (this.getX() != x || this.getY() != y)
  12989. {
  12990. mxWindow.prototype.setLocation.apply(this, arguments);
  12991. }
  12992. };
  12993. var resizeListener = mxUtils.bind(this, function()
  12994. {
  12995. var x = this.window.getX();
  12996. var y = this.window.getY();
  12997. this.window.setLocation(x, y);
  12998. });
  12999. mxEvent.addListener(window, 'resize', resizeListener);
  13000. this.destroy = function()
  13001. {
  13002. mxEvent.removeListener(window, 'resize', resizeListener);
  13003. this.window.destroy();
  13004. }
  13005. };
  13006. /**
  13007. *
  13008. */
  13009. var ConfirmDialog = function(editorUi, message, okFn, cancelFn, okLabel, cancelLabel,
  13010. okImg, cancelImg, showRememberOption, imgSrc, maxHeight)
  13011. {
  13012. var div = document.createElement('div');
  13013. div.style.textAlign = 'center';
  13014. maxHeight = (maxHeight != null) ? maxHeight : 44;
  13015. var p2 = document.createElement('div');
  13016. p2.style.padding = '6px';
  13017. p2.style.overflow = 'auto';
  13018. p2.style.maxHeight = maxHeight + 'px';
  13019. p2.style.lineHeight = '1.2em';
  13020. mxUtils.write(p2, message);
  13021. div.appendChild(p2);
  13022. if (imgSrc != null)
  13023. {
  13024. var p3 = document.createElement('div');
  13025. p3.style.padding = '6px 0 6px 0';
  13026. var img = document.createElement('img');
  13027. img.setAttribute('src', imgSrc);
  13028. p3.appendChild(img);
  13029. div.appendChild(p3);
  13030. }
  13031. var btns = document.createElement('div');
  13032. btns.style.textAlign = 'center';
  13033. btns.style.whiteSpace = 'nowrap';
  13034. var cb = document.createElement('input');
  13035. cb.setAttribute('type', 'checkbox');
  13036. var cancelBtn = mxUtils.button(cancelLabel || mxResources.get('cancel'), function()
  13037. {
  13038. editorUi.hideDialog();
  13039. if (cancelFn != null)
  13040. {
  13041. cancelFn(cb.checked);
  13042. }
  13043. });
  13044. cancelBtn.className = 'geBtn';
  13045. if (cancelImg != null)
  13046. {
  13047. cancelBtn.innerHTML = cancelImg + '<br>' + cancelBtn.innerHTML;
  13048. cancelBtn.style.paddingBottom = '8px';
  13049. cancelBtn.style.paddingTop = '8px';
  13050. cancelBtn.style.height = 'auto';
  13051. cancelBtn.style.width = '40%';
  13052. }
  13053. if (editorUi.editor.cancelFirst)
  13054. {
  13055. btns.appendChild(cancelBtn);
  13056. }
  13057. var okBtn = mxUtils.button(okLabel || mxResources.get('ok'), function()
  13058. {
  13059. editorUi.hideDialog();
  13060. if (okFn != null)
  13061. {
  13062. okFn(cb.checked);
  13063. }
  13064. });
  13065. btns.appendChild(okBtn);
  13066. if (okImg != null)
  13067. {
  13068. okBtn.innerHTML = okImg + '<br>' + okBtn.innerHTML + '<br>';
  13069. okBtn.style.paddingBottom = '8px';
  13070. okBtn.style.paddingTop = '8px';
  13071. okBtn.style.height = 'auto';
  13072. okBtn.className = 'geBtn';
  13073. okBtn.style.width = '40%';
  13074. }
  13075. else
  13076. {
  13077. okBtn.className = 'geBtn gePrimaryBtn';
  13078. }
  13079. if (!editorUi.editor.cancelFirst)
  13080. {
  13081. btns.appendChild(cancelBtn);
  13082. }
  13083. div.appendChild(btns);
  13084. if (showRememberOption)
  13085. {
  13086. btns.style.marginTop = '10px';
  13087. var p2 = document.createElement('p');
  13088. p2.style.marginTop = '20px';
  13089. p2.style.marginBottom = '0px';
  13090. p2.appendChild(cb);
  13091. var span = document.createElement('span');
  13092. mxUtils.write(span, ' ' + mxResources.get('rememberThisSetting'));
  13093. p2.appendChild(span);
  13094. div.appendChild(p2);
  13095. mxEvent.addListener(span, 'click', function(evt)
  13096. {
  13097. cb.checked = !cb.checked;
  13098. mxEvent.consume(evt);
  13099. });
  13100. }
  13101. else
  13102. {
  13103. btns.style.marginTop = '12px';
  13104. }
  13105. this.init = function()
  13106. {
  13107. okBtn.focus();
  13108. };
  13109. this.container = div;
  13110. };