2
0

llms-full.txt 211 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891289228932894289528962897289828992900290129022903290429052906290729082909291029112912291329142915291629172918291929202921292229232924292529262927292829292930293129322933293429352936293729382939294029412942294329442945294629472948294929502951295229532954295529562957295829592960296129622963296429652966296729682969297029712972297329742975297629772978297929802981298229832984298529862987298829892990299129922993299429952996299729982999300030013002300330043005300630073008300930103011301230133014301530163017301830193020302130223023302430253026302730283029303030313032303330343035303630373038303930403041304230433044304530463047304830493050305130523053305430553056305730583059306030613062306330643065306630673068306930703071307230733074307530763077307830793080308130823083308430853086308730883089309030913092309330943095309630973098309931003101310231033104310531063107310831093110311131123113311431153116311731183119312031213122312331243125312631273128312931303131313231333134313531363137313831393140314131423143314431453146314731483149315031513152315331543155315631573158315931603161316231633164316531663167316831693170317131723173317431753176317731783179318031813182318331843185318631873188318931903191319231933194319531963197319831993200320132023203320432053206320732083209321032113212321332143215321632173218321932203221322232233224322532263227322832293230323132323233323432353236323732383239324032413242324332443245324632473248324932503251325232533254325532563257325832593260326132623263326432653266326732683269327032713272327332743275327632773278327932803281328232833284328532863287328832893290329132923293329432953296329732983299330033013302330333043305330633073308330933103311331233133314331533163317331833193320332133223323332433253326332733283329333033313332333333343335333633373338333933403341334233433344334533463347334833493350335133523353335433553356335733583359336033613362336333643365336633673368336933703371337233733374337533763377337833793380338133823383338433853386338733883389339033913392339333943395339633973398339934003401340234033404340534063407340834093410341134123413341434153416341734183419342034213422342334243425342634273428342934303431343234333434343534363437343834393440344134423443344434453446344734483449345034513452345334543455345634573458345934603461346234633464346534663467346834693470347134723473347434753476347734783479348034813482348334843485348634873488348934903491349234933494349534963497349834993500350135023503350435053506350735083509351035113512351335143515351635173518351935203521352235233524352535263527352835293530353135323533353435353536353735383539354035413542354335443545354635473548354935503551355235533554355535563557355835593560356135623563356435653566356735683569357035713572357335743575357635773578357935803581358235833584358535863587358835893590359135923593359435953596359735983599360036013602360336043605360636073608360936103611361236133614361536163617361836193620362136223623362436253626362736283629363036313632363336343635363636373638363936403641364236433644364536463647364836493650365136523653365436553656365736583659366036613662366336643665366636673668366936703671367236733674367536763677367836793680368136823683368436853686368736883689369036913692369336943695369636973698369937003701370237033704370537063707370837093710371137123713371437153716371737183719372037213722372337243725372637273728372937303731373237333734373537363737373837393740374137423743374437453746374737483749375037513752375337543755375637573758375937603761376237633764376537663767376837693770377137723773377437753776377737783779378037813782378337843785378637873788378937903791379237933794379537963797379837993800380138023803380438053806380738083809381038113812381338143815381638173818381938203821382238233824382538263827382838293830383138323833383438353836383738383839384038413842384338443845384638473848384938503851385238533854385538563857385838593860386138623863386438653866386738683869387038713872387338743875387638773878387938803881388238833884388538863887388838893890389138923893389438953896389738983899390039013902390339043905390639073908390939103911391239133914391539163917391839193920392139223923392439253926392739283929393039313932393339343935393639373938393939403941394239433944394539463947394839493950395139523953395439553956395739583959396039613962396339643965396639673968396939703971397239733974397539763977397839793980398139823983398439853986398739883989399039913992399339943995399639973998399940004001400240034004400540064007400840094010401140124013401440154016401740184019402040214022402340244025402640274028402940304031403240334034403540364037403840394040404140424043404440454046404740484049405040514052405340544055405640574058405940604061406240634064406540664067406840694070407140724073407440754076407740784079408040814082408340844085408640874088408940904091409240934094409540964097409840994100410141024103410441054106410741084109411041114112411341144115411641174118411941204121412241234124412541264127412841294130413141324133413441354136413741384139414041414142414341444145414641474148414941504151415241534154415541564157415841594160416141624163416441654166416741684169417041714172417341744175417641774178417941804181418241834184418541864187418841894190419141924193419441954196419741984199420042014202420342044205420642074208420942104211421242134214421542164217421842194220422142224223422442254226422742284229423042314232423342344235423642374238423942404241424242434244424542464247424842494250425142524253425442554256425742584259426042614262426342644265426642674268426942704271427242734274427542764277427842794280428142824283428442854286428742884289429042914292429342944295429642974298429943004301430243034304430543064307430843094310431143124313431443154316431743184319432043214322432343244325432643274328432943304331433243334334433543364337433843394340434143424343434443454346434743484349435043514352435343544355435643574358435943604361436243634364436543664367436843694370437143724373437443754376437743784379438043814382438343844385438643874388438943904391439243934394439543964397439843994400440144024403440444054406440744084409441044114412441344144415441644174418441944204421442244234424442544264427442844294430443144324433443444354436443744384439444044414442444344444445444644474448444944504451445244534454445544564457445844594460446144624463446444654466446744684469447044714472447344744475447644774478447944804481448244834484448544864487448844894490449144924493449444954496449744984499450045014502450345044505450645074508450945104511451245134514451545164517451845194520452145224523452445254526452745284529453045314532453345344535453645374538453945404541454245434544454545464547454845494550455145524553455445554556455745584559456045614562456345644565456645674568456945704571457245734574457545764577457845794580458145824583458445854586458745884589459045914592459345944595459645974598459946004601460246034604460546064607460846094610461146124613461446154616461746184619462046214622462346244625462646274628462946304631463246334634463546364637463846394640464146424643464446454646464746484649465046514652465346544655465646574658465946604661466246634664466546664667466846694670467146724673467446754676467746784679468046814682468346844685468646874688468946904691469246934694469546964697469846994700470147024703470447054706470747084709471047114712471347144715471647174718471947204721472247234724472547264727472847294730473147324733473447354736473747384739474047414742474347444745474647474748474947504751475247534754475547564757475847594760476147624763476447654766476747684769477047714772477347744775477647774778477947804781478247834784478547864787478847894790479147924793479447954796479747984799480048014802480348044805480648074808480948104811481248134814481548164817481848194820482148224823482448254826482748284829483048314832483348344835483648374838483948404841484248434844484548464847484848494850485148524853485448554856485748584859486048614862486348644865486648674868486948704871487248734874487548764877487848794880488148824883488448854886488748884889489048914892489348944895489648974898489949004901490249034904490549064907490849094910491149124913491449154916491749184919492049214922492349244925492649274928492949304931493249334934493549364937493849394940494149424943494449454946494749484949495049514952495349544955495649574958495949604961496249634964496549664967496849694970497149724973497449754976497749784979498049814982498349844985498649874988498949904991499249934994499549964997499849995000500150025003500450055006500750085009501050115012501350145015501650175018501950205021502250235024502550265027502850295030503150325033503450355036503750385039504050415042504350445045504650475048504950505051505250535054505550565057505850595060506150625063506450655066506750685069507050715072507350745075507650775078507950805081508250835084508550865087508850895090509150925093509450955096509750985099510051015102510351045105510651075108510951105111511251135114511551165117511851195120512151225123512451255126512751285129513051315132513351345135513651375138513951405141514251435144514551465147514851495150515151525153515451555156515751585159516051615162516351645165516651675168516951705171517251735174517551765177517851795180518151825183518451855186518751885189519051915192519351945195519651975198519952005201520252035204520552065207520852095210521152125213521452155216521752185219522052215222522352245225522652275228522952305231523252335234523552365237523852395240524152425243524452455246524752485249525052515252525352545255525652575258525952605261526252635264526552665267526852695270527152725273527452755276527752785279528052815282528352845285528652875288528952905291529252935294529552965297529852995300530153025303530453055306530753085309531053115312531353145315531653175318531953205321532253235324532553265327532853295330533153325333533453355336533753385339534053415342534353445345534653475348534953505351535253535354535553565357535853595360536153625363536453655366536753685369537053715372537353745375537653775378537953805381538253835384538553865387538853895390539153925393539453955396539753985399540054015402540354045405540654075408540954105411541254135414541554165417541854195420542154225423542454255426542754285429543054315432543354345435543654375438543954405441544254435444544554465447544854495450545154525453545454555456545754585459546054615462546354645465546654675468546954705471547254735474547554765477547854795480548154825483548454855486548754885489549054915492549354945495549654975498549955005501550255035504550555065507550855095510551155125513551455155516551755185519552055215522552355245525552655275528552955305531553255335534553555365537553855395540554155425543554455455546554755485549555055515552555355545555555655575558555955605561556255635564556555665567556855695570557155725573557455755576557755785579558055815582558355845585558655875588558955905591559255935594559555965597559855995600560156025603560456055606560756085609561056115612561356145615561656175618561956205621562256235624562556265627562856295630563156325633563456355636563756385639564056415642564356445645564656475648564956505651565256535654565556565657565856595660566156625663566456655666566756685669567056715672567356745675567656775678567956805681568256835684568556865687568856895690569156925693569456955696569756985699570057015702570357045705570657075708570957105711571257135714571557165717571857195720572157225723572457255726572757285729573057315732573357345735573657375738573957405741574257435744574557465747574857495750575157525753575457555756575757585759576057615762576357645765576657675768576957705771577257735774577557765777577857795780578157825783578457855786578757885789579057915792579357945795579657975798579958005801580258035804580558065807580858095810581158125813581458155816581758185819582058215822582358245825582658275828582958305831583258335834583558365837583858395840584158425843584458455846584758485849585058515852585358545855585658575858585958605861586258635864586558665867586858695870587158725873587458755876587758785879588058815882588358845885588658875888588958905891589258935894589558965897589858995900590159025903590459055906590759085909591059115912591359145915591659175918591959205921592259235924592559265927592859295930593159325933593459355936593759385939594059415942594359445945594659475948594959505951595259535954595559565957595859595960596159625963596459655966596759685969597059715972597359745975597659775978597959805981598259835984598559865987598859895990599159925993599459955996599759985999600060016002600360046005600660076008600960106011601260136014601560166017601860196020602160226023602460256026602760286029603060316032603360346035603660376038603960406041604260436044604560466047604860496050605160526053605460556056605760586059606060616062606360646065606660676068606960706071607260736074607560766077607860796080608160826083608460856086608760886089609060916092609360946095609660976098609961006101610261036104610561066107610861096110611161126113611461156116611761186119612061216122612361246125612661276128612961306131613261336134613561366137613861396140614161426143614461456146614761486149615061516152615361546155615661576158615961606161616261636164616561666167616861696170617161726173617461756176617761786179618061816182618361846185618661876188618961906191619261936194619561966197619861996200620162026203620462056206620762086209621062116212621362146215621662176218621962206221622262236224622562266227622862296230623162326233623462356236623762386239624062416242624362446245624662476248624962506251625262536254625562566257625862596260626162626263626462656266626762686269627062716272627362746275627662776278627962806281628262836284628562866287628862896290629162926293629462956296629762986299630063016302630363046305630663076308630963106311631263136314631563166317631863196320632163226323632463256326632763286329633063316332633363346335633663376338633963406341634263436344634563466347634863496350635163526353635463556356635763586359636063616362636363646365636663676368636963706371637263736374637563766377637863796380638163826383638463856386638763886389639063916392639363946395639663976398639964006401640264036404640564066407640864096410641164126413641464156416641764186419642064216422642364246425642664276428642964306431643264336434643564366437643864396440644164426443644464456446644764486449645064516452645364546455645664576458645964606461646264636464646564666467646864696470647164726473647464756476647764786479648064816482648364846485648664876488648964906491649264936494649564966497649864996500650165026503650465056506650765086509651065116512651365146515651665176518651965206521652265236524652565266527652865296530653165326533653465356536653765386539654065416542654365446545654665476548654965506551655265536554655565566557655865596560656165626563656465656566656765686569657065716572657365746575657665776578657965806581658265836584658565866587658865896590659165926593659465956596659765986599660066016602660366046605660666076608660966106611661266136614661566166617661866196620662166226623662466256626662766286629663066316632663366346635663666376638663966406641664266436644664566466647664866496650665166526653665466556656665766586659666066616662666366646665666666676668666966706671667266736674667566766677667866796680668166826683668466856686668766886689669066916692669366946695669666976698669967006701670267036704670567066707670867096710671167126713671467156716671767186719672067216722672367246725672667276728672967306731673267336734673567366737673867396740674167426743674467456746674767486749675067516752675367546755675667576758675967606761676267636764676567666767676867696770677167726773677467756776677767786779678067816782678367846785678667876788678967906791679267936794679567966797679867996800680168026803680468056806680768086809681068116812681368146815681668176818681968206821682268236824682568266827682868296830683168326833683468356836683768386839684068416842684368446845684668476848684968506851685268536854685568566857685868596860686168626863686468656866686768686869687068716872687368746875687668776878687968806881688268836884688568866887688868896890689168926893689468956896689768986899690069016902690369046905690669076908690969106911691269136914691569166917691869196920692169226923692469256926692769286929693069316932693369346935693669376938693969406941694269436944694569466947694869496950695169526953695469556956695769586959696069616962696369646965696669676968696969706971697269736974697569766977697869796980698169826983698469856986698769886989699069916992699369946995699669976998699970007001700270037004700570067007700870097010701170127013701470157016701770187019702070217022702370247025702670277028702970307031703270337034703570367037703870397040704170427043704470457046704770487049705070517052705370547055705670577058705970607061706270637064706570667067706870697070707170727073707470757076707770787079708070817082708370847085708670877088708970907091709270937094709570967097709870997100710171027103710471057106710771087109711071117112711371147115711671177118711971207121712271237124712571267127712871297130713171327133713471357136713771387139714071417142714371447145714671477148714971507151715271537154715571567157715871597160716171627163716471657166716771687169717071717172717371747175717671777178717971807181718271837184718571867187718871897190719171927193719471957196719771987199720072017202720372047205720672077208720972107211721272137214721572167217721872197220722172227223722472257226722772287229723072317232723372347235723672377238723972407241724272437244724572467247724872497250725172527253725472557256725772587259726072617262726372647265726672677268726972707271727272737274727572767277727872797280728172827283728472857286728772887289729072917292729372947295729672977298729973007301730273037304730573067307730873097310731173127313731473157316731773187319732073217322732373247325732673277328732973307331733273337334733573367337733873397340734173427343734473457346734773487349735073517352735373547355735673577358735973607361736273637364736573667367736873697370737173727373737473757376737773787379738073817382738373847385738673877388738973907391739273937394739573967397739873997400740174027403740474057406740774087409741074117412741374147415741674177418741974207421742274237424742574267427742874297430743174327433743474357436743774387439744074417442744374447445744674477448744974507451745274537454745574567457745874597460746174627463746474657466746774687469747074717472747374747475747674777478747974807481748274837484748574867487748874897490749174927493749474957496749774987499750075017502750375047505750675077508750975107511751275137514751575167517751875197520752175227523752475257526752775287529753075317532753375347535753675377538753975407541754275437544754575467547754875497550755175527553755475557556755775587559756075617562756375647565756675677568756975707571757275737574757575767577757875797580758175827583758475857586758775887589759075917592759375947595759675977598759976007601760276037604760576067607760876097610761176127613761476157616761776187619762076217622762376247625762676277628762976307631763276337634763576367637763876397640764176427643764476457646764776487649765076517652765376547655765676577658765976607661766276637664766576667667766876697670767176727673767476757676767776787679768076817682768376847685768676877688768976907691769276937694769576967697769876997700770177027703770477057706770777087709771077117712771377147715771677177718771977207721772277237724772577267727772877297730773177327733773477357736773777387739774077417742774377447745774677477748774977507751775277537754775577567757775877597760776177627763776477657766776777687769777077717772777377747775777677777778777977807781778277837784778577867787778877897790779177927793779477957796779777987799780078017802780378047805780678077808780978107811781278137814781578167817781878197820782178227823782478257826782778287829783078317832783378347835783678377838783978407841784278437844784578467847784878497850785178527853785478557856785778587859786078617862786378647865786678677868786978707871787278737874787578767877787878797880788178827883788478857886788778887889789078917892789378947895789678977898789979007901790279037904790579067907790879097910791179127913791479157916791779187919792079217922792379247925792679277928792979307931793279337934793579367937793879397940794179427943794479457946794779487949795079517952795379547955795679577958795979607961796279637964796579667967796879697970797179727973797479757976797779787979798079817982798379847985798679877988798979907991799279937994799579967997799879998000800180028003800480058006800780088009801080118012801380148015801680178018801980208021802280238024802580268027802880298030803180328033803480358036803780388039804080418042804380448045804680478048804980508051805280538054805580568057805880598060806180628063806480658066806780688069807080718072807380748075807680778078807980808081808280838084808580868087808880898090809180928093809480958096809780988099810081018102810381048105810681078108810981108111811281138114811581168117811881198120812181228123812481258126812781288129813081318132813381348135813681378138813981408141814281438144814581468147814881498150815181528153815481558156815781588159816081618162816381648165816681678168816981708171817281738174817581768177817881798180818181828183818481858186818781888189819081918192819381948195819681978198819982008201820282038204820582068207820882098210821182128213821482158216821782188219822082218222822382248225822682278228822982308231823282338234823582368237823882398240824182428243824482458246824782488249825082518252825382548255825682578258825982608261826282638264826582668267826882698270827182728273827482758276827782788279828082818282828382848285828682878288828982908291829282938294829582968297829882998300830183028303830483058306830783088309831083118312831383148315831683178318831983208321832283238324832583268327832883298330833183328333833483358336833783388339834083418342834383448345834683478348834983508351835283538354835583568357835883598360836183628363836483658366836783688369837083718372837383748375837683778378837983808381838283838384838583868387838883898390839183928393839483958396839783988399840084018402840384048405840684078408840984108411841284138414841584168417841884198420842184228423842484258426842784288429843084318432843384348435843684378438843984408441844284438444844584468447844884498450845184528453845484558456845784588459846084618462846384648465846684678468846984708471847284738474847584768477847884798480848184828483848484858486848784888489849084918492849384948495849684978498849985008501850285038504850585068507850885098510851185128513851485158516851785188519852085218522852385248525852685278528852985308531853285338534853585368537853885398540854185428543854485458546854785488549855085518552855385548555855685578558855985608561856285638564856585668567856885698570857185728573857485758576857785788579858085818582858385848585858685878588858985908591859285938594859585968597859885998600860186028603860486058606860786088609861086118612861386148615861686178618861986208621862286238624862586268627862886298630863186328633863486358636863786388639864086418642864386448645864686478648864986508651865286538654865586568657865886598660866186628663866486658666866786688669867086718672867386748675867686778678867986808681868286838684868586868687868886898690869186928693869486958696869786988699870087018702870387048705870687078708870987108711871287138714871587168717871887198720872187228723872487258726872787288729873087318732873387348735873687378738873987408741874287438744874587468747874887498750875187528753875487558756875787588759876087618762876387648765876687678768876987708771877287738774877587768777877887798780878187828783878487858786878787888789879087918792879387948795879687978798879988008801880288038804880588068807880888098810881188128813881488158816881788188819882088218822882388248825882688278828882988308831883288338834883588368837883888398840884188428843884488458846884788488849885088518852885388548855885688578858885988608861886288638864886588668867886888698870887188728873887488758876887788788879888088818882888388848885888688878888888988908891889288938894889588968897889888998900890189028903890489058906890789088909
  1. ---
  2. url: /guide/introduction.md
  3. ---
  4. # 介绍
  5. FlowGram 是一套基于节点编辑的流程搭建引擎,帮助开发者快速创建固定布局或自由连线布局模式的流程,并提供一套交互的最佳实践, 很适合有明确输入和输出的可视化工作流。
  6. 在 AI 如火如荼的当下,我们也会更专注于如何让流程赋能 AI,为此特意加上 AI 后缀。
  7. FlowGram = Flow + Program,寓意流程如程序一样,拥有 Condition、Loop 甚至 TryCatch 节点。
  8. ## 官方 Demo
  9. 固定布局
  10. 固定的排版,节点/分支支持指定位置拖拽移动,并提供分支、循环等复合节点
  11. 自由连线布局
  12. 自由的排版,节点支持任意位置移动,通过自由连线连接节点
  13. ## 交互体验
  14. 提供一套交互的最佳实践,让操作流程更加丝滑
  15. Motion 过渡动画
  16. Motion 动画在 Web 端应用可追溯到 Material Design,里边提到元素的变化如宽高或位置需要一个过渡过程,画布引擎会把线条和节点拆分单独绘制,使实现 Motion 过渡动画成本大大降低
  17. 触摸板手势缩放 + 空格自由拖动画布
  18. 手势指在 Mac 触摸板两指展开/合并可以实现画布放大/缩小,或者按住空格拖动画布,交互借鉴 Sketch、Figma
  19. 缩略图
  20. 撤销/重做
  21. 复制/粘贴(支持快捷键)
  22. 框选 + 拖拽
  23. <div>(固定)</div>
  24. 水平/垂直布局切换
  25. <div>(固定)</div>
  26. 分支折叠
  27. <div>(固定)</div>
  28. 分组
  29. <div>(固定)</div>
  30. 自动整理
  31. (自由)
  32. 吸附对齐 + 参考线
  33. (自由)
  34. Coze Loop 子画布
  35. (自由)
  36. ## 线上应用
  37. 扣子工作流
  38. 飞书低代码平台工作流
  39. 飞书多维表格工作流
  40. ---
  41. url: /examples/index.md
  42. ---
  43. ---
  44. url: /examples/fixed-layout/fixed-layout-simple.md
  45. ---
  46. # 基础用法
  47. ## 安装
  48. ```bash
  49. npx @flowgram.ai/create-app@latest fixed-layout-simple
  50. ```
  51. ## 源码
  52. https://github.com/bytedance/flowgram.ai/tree/main/apps/demo-fixed-layout-simple
  53. ---
  54. url: /examples/fixed-layout/fixed-composite-nodes.md
  55. ---
  56. # 复合节点
  57. ## 安装
  58. ```bash
  59. npx @flowgram.ai/create-app@latest fixed-layout-simple
  60. ```
  61. ## 源码
  62. * jsonData: https://github.com/bytedance/flowgram.ai/tree/main/apps/demo-fixed-layout-simple/src/data
  63. * nodeRegistries: https://github.com/bytedance/flowgram.ai/tree/main/packages/canvas-engine/fixed-layout-core/src/activities
  64. ---
  65. url: /examples/fixed-layout/fixed-feature-overview.md
  66. ---
  67. # 最佳实践
  68. ## 安装
  69. ```bash
  70. npx @flowgram.ai/create-app@latest fixed-layout
  71. ```
  72. ## 源码
  73. https://github.com/bytedance/flowgram.ai/tree/main/apps/demo-fixed-layout
  74. ## 功能介绍
  75. 缩略图
  76. 撤销/重做
  77. 复制/粘贴(支持快捷键)
  78. 框选 + 拖拽
  79. 水平/垂直布局切换
  80. 分支折叠
  81. 分组
  82. ---
  83. url: /examples/free-layout/free-layout-simple.md
  84. ---
  85. # 基础用法
  86. ## 功能介绍
  87. Free Layout 是 Flowgram.ai 提供的自由布局编辑器组件,允许用户创建和编辑流程图、工作流和各种节点连接图表。核心功能包括:
  88. * 节点自由拖拽与定位
  89. * 节点连接与边缘管理
  90. * 可配置的节点注册与自定义渲染
  91. * 内置撤销/重做历史记录
  92. * 支持插件扩展(如缩略图、自动对齐等)
  93. ## 从零构建自由布局编辑器
  94. 本节将带你从零开始构建一个自由布局编辑器应用,完整演示如何使用 @flowgram.ai/free-layout-editor 包构建一个可交互的流程编辑器。
  95. ### 1. 环境准备
  96. 首先,我们需要创建一个新的项目:
  97. ```bash
  98. # 使用脚手架快速创建项目
  99. npx @flowgram.ai/create-app@latest free-layout-simple
  100. # 进入项目目录
  101. cd free-layout-simple
  102. # 安装依赖
  103. npm install
  104. ```
  105. ### 2. 项目结构
  106. 创建完成后,项目结构如下:
  107. ```
  108. free-layout-simple/
  109. ├── src/
  110. │ ├── components/ # 组件目录
  111. │ │ ├── node-add-panel.tsx # 节点添加面板
  112. │ │ ├── tools.tsx # 工具栏组件
  113. │ │ └── minimap.tsx # 缩略图组件
  114. │ ├── hooks/
  115. │ │ └── use-editor-props.tsx # 编辑器配置
  116. │ ├── initial-data.ts # 初始数据定义
  117. │ ├── node-registries.ts # 节点类型注册
  118. │ ├── editor.tsx # 编辑器主组件
  119. │ ├── app.tsx # 应用入口
  120. │ ├── index.tsx # 渲染入口
  121. │ └── index.css # 样式文件
  122. ├── package.json
  123. └── ...其他配置文件
  124. ```
  125. ### 3. 开发流程
  126. #### 步骤一:定义初始数据
  127. 首先,我们需要定义画布的初始数据结构,包括节点和连线:
  128. ```tsx
  129. // src/initial-data.ts
  130. import { WorkflowJSON } from '@flowgram.ai/free-layout-editor';
  131. export const initialData: WorkflowJSON = {
  132. nodes: [
  133. {
  134. id: 'start_0',
  135. type: 'start',
  136. meta: {
  137. position: { x: 0, y: 0 },
  138. },
  139. data: {
  140. title: '开始节点',
  141. content: '这是开始节点'
  142. },
  143. },
  144. {
  145. id: 'node_0',
  146. type: 'custom',
  147. meta: {
  148. position: { x: 400, y: 0 },
  149. },
  150. data: {
  151. title: '自定义节点',
  152. content: '这是自定义节点'
  153. },
  154. },
  155. {
  156. id: 'end_0',
  157. type: 'end',
  158. meta: {
  159. position: { x: 800, y: 0 },
  160. },
  161. data: {
  162. title: '结束节点',
  163. content: '这是结束节点'
  164. },
  165. },
  166. ],
  167. edges: [
  168. {
  169. sourceNodeID: 'start_0',
  170. targetNodeID: 'node_0',
  171. },
  172. {
  173. sourceNodeID: 'node_0',
  174. targetNodeID: 'end_0',
  175. },
  176. ],
  177. };
  178. ```
  179. #### 步骤二:注册节点类型
  180. 接下来,我们需要定义不同类型节点的行为和外观:
  181. ```tsx
  182. // src/node-registries.ts
  183. import { WorkflowNodeRegistry } from '@flowgram.ai/free-layout-editor';
  184. /**
  185. * 你可以自定义节点的注册器
  186. */
  187. export const nodeRegistries: WorkflowNodeRegistry[] = [
  188. {
  189. type: 'start',
  190. meta: {
  191. isStart: true, // 开始节点标记
  192. deleteDisable: true, // 开始节点不能被删除
  193. copyDisable: true, // 开始节点不能被 copy
  194. defaultPorts: [{ type: 'output' }], // 定义 input 和 output 端口,开始节点只有 output 端口
  195. },
  196. },
  197. {
  198. type: 'end',
  199. meta: {
  200. deleteDisable: true,
  201. copyDisable: true,
  202. defaultPorts: [{ type: 'input' }], // 结束节点只有 input 端口
  203. },
  204. },
  205. {
  206. type: 'custom',
  207. meta: {},
  208. defaultPorts: [{ type: 'output' }, { type: 'input' }], // 普通节点有两个端口
  209. },
  210. ];
  211. ```
  212. #### 步骤三:创建编辑器配置
  213. 使用 React hook 封装编辑器配置:
  214. ```tsx
  215. // src/hooks/use-editor-props.tsx
  216. import { useMemo } from 'react';
  217. import {
  218. FreeLayoutProps,
  219. WorkflowNodeProps,
  220. WorkflowNodeRenderer,
  221. Field,
  222. useNodeRender,
  223. } from '@flowgram.ai/free-layout-editor';
  224. import { createMinimapPlugin } from '@flowgram.ai/minimap-plugin';
  225. import { createFreeSnapPlugin } from '@flowgram.ai/free-snap-plugin';
  226. import { nodeRegistries } from '../node-registries';
  227. import { initialData } from '../initial-data';
  228. export const useEditorProps = () =>
  229. useMemo<FreeLayoutProps>(
  230. () => ({
  231. // 启用背景网格
  232. background: true,
  233. // 非只读模式
  234. readonly: false,
  235. // 初始数据
  236. initialData,
  237. // 节点类型注册
  238. nodeRegistries,
  239. // 默认节点注册
  240. getNodeDefaultRegistry(type) {
  241. return {
  242. type,
  243. meta: {
  244. defaultExpanded: true,
  245. },
  246. formMeta: {
  247. // 节点表单渲染
  248. render: () => (
  249. <>
  250. <Field<string> name="title">
  251. {({ field }) => <div className="demo-free-node-title">{field.value}</div>}
  252. </Field>
  253. <div className="demo-free-node-content">
  254. <Field<string> name="content">
  255. <input />
  256. </Field>
  257. </div>
  258. </>
  259. ),
  260. },
  261. };
  262. },
  263. // 节点渲染
  264. materials: {
  265. renderDefaultNode: (props: WorkflowNodeProps) => {
  266. const { form } = useNodeRender();
  267. return (
  268. <WorkflowNodeRenderer className="demo-free-node" node={props.node}>
  269. {form?.render()}
  270. </WorkflowNodeRenderer>
  271. );
  272. },
  273. },
  274. // 内容变更回调
  275. onContentChange(ctx, event) {
  276. console.log('数据变更: ', event, ctx.document.toJSON());
  277. },
  278. // 启用节点表单引擎
  279. nodeEngine: {
  280. enable: true,
  281. },
  282. // 启用历史记录
  283. history: {
  284. enable: true,
  285. enableChangeNode: true, // 监听节点引擎数据变化
  286. },
  287. // 初始化回调
  288. onInit: (ctx) => {},
  289. // 渲染完成回调
  290. onAllLayersRendered(ctx) {
  291. ctx.document.fitView(false); // 适应视图
  292. },
  293. // 销毁回调
  294. onDispose() {
  295. console.log('编辑器已销毁');
  296. },
  297. // 插件配置
  298. plugins: () => [
  299. // 缩略图插件
  300. createMinimapPlugin({
  301. disableLayer: true,
  302. canvasStyle: {
  303. canvasWidth: 182,
  304. canvasHeight: 102,
  305. canvasPadding: 50,
  306. canvasBackground: 'rgba(245, 245, 245, 1)',
  307. canvasBorderRadius: 10,
  308. viewportBackground: 'rgba(235, 235, 235, 1)',
  309. viewportBorderRadius: 4,
  310. viewportBorderColor: 'rgba(201, 201, 201, 1)',
  311. viewportBorderWidth: 1,
  312. viewportBorderDashLength: 2,
  313. nodeColor: 'rgba(255, 255, 255, 1)',
  314. nodeBorderRadius: 2,
  315. nodeBorderWidth: 0.145,
  316. nodeBorderColor: 'rgba(6, 7, 9, 0.10)',
  317. overlayColor: 'rgba(255, 255, 255, 0)',
  318. },
  319. inactiveDebounceTime: 1,
  320. }),
  321. // 自动对齐插件
  322. createFreeSnapPlugin({
  323. edgeColor: '#00B2B2',
  324. alignColor: '#00B2B2',
  325. edgeLineWidth: 1,
  326. alignLineWidth: 1,
  327. alignCrossWidth: 8,
  328. }),
  329. ],
  330. }),
  331. []
  332. );
  333. ```
  334. #### 步骤四:创建节点添加面板
  335. ```tsx
  336. // src/components/node-add-panel.tsx
  337. import React from 'react';
  338. import { WorkflowDragService, useService } from '@flowgram.ai/free-layout-editor';
  339. const nodeTypes = ['自定义节点1', '自定义节点2'];
  340. export const NodeAddPanel: React.FC = () => {
  341. const dragService = useService<WorkflowDragService>(WorkflowDragService);
  342. return (
  343. <div className="demo-free-sidebar">
  344. {nodeTypes.map(nodeType => (
  345. <div
  346. key={nodeType}
  347. className="demo-free-card"
  348. onMouseDown={e => dragService.startDragCard(nodeType, e, {
  349. data: {
  350. title: nodeType,
  351. content: '拖拽创建的节点'
  352. }
  353. })}
  354. >
  355. {nodeType}
  356. </div>
  357. ))}
  358. </div>
  359. );
  360. };
  361. ```
  362. #### 步骤五:创建工具栏和缩略图
  363. ```tsx
  364. // src/components/tools.tsx
  365. import React from 'react';
  366. import { useEffect, useState } from 'react';
  367. import { usePlaygroundTools, useClientContext } from '@flowgram.ai/free-layout-editor';
  368. export const Tools: React.FC = () => {
  369. const { history } = useClientContext();
  370. const tools = usePlaygroundTools();
  371. const [canUndo, setCanUndo] = useState(false);
  372. const [canRedo, setCanRedo] = useState(false);
  373. useEffect(() => {
  374. const disposable = history.undoRedoService.onChange(() => {
  375. setCanUndo(history.canUndo());
  376. setCanRedo(history.canRedo());
  377. });
  378. return () => disposable.dispose();
  379. }, [history]);
  380. return (
  381. <div
  382. style={{ position: 'absolute', zIndex: 10, bottom: 16, left: 226, display: 'flex', gap: 8 }}
  383. >
  384. <button onClick={() => tools.zoomin()}>ZoomIn</button>
  385. <button onClick={() => tools.zoomout()}>ZoomOut</button>
  386. <button onClick={() => tools.fitView()}>Fitview</button>
  387. <button onClick={() => tools.autoLayout()}>AutoLayout</button>
  388. <button onClick={() => history.undo()} disabled={!canUndo}>
  389. Undo
  390. </button>
  391. <button onClick={() => history.redo()} disabled={!canRedo}>
  392. Redo
  393. </button>
  394. <span>{Math.floor(tools.zoom * 100)}%</span>
  395. </div>
  396. );
  397. };
  398. // src/components/minimap.tsx
  399. import { FlowMinimapService, MinimapRender } from '@flowgram.ai/minimap-plugin';
  400. import { useService } from '@flowgram.ai/free-layout-editor';
  401. export const Minimap = () => {
  402. const minimapService = useService(FlowMinimapService);
  403. return (
  404. <div
  405. style={{
  406. position: 'absolute',
  407. left: 226,
  408. bottom: 51,
  409. zIndex: 100,
  410. width: 198,
  411. }}
  412. >
  413. <MinimapRender
  414. service={minimapService}
  415. containerStyles={{
  416. pointerEvents: 'auto',
  417. position: 'relative',
  418. top: 'unset',
  419. right: 'unset',
  420. bottom: 'unset',
  421. left: 'unset',
  422. }}
  423. inactiveStyle={{
  424. opacity: 1,
  425. scale: 1,
  426. translateX: 0,
  427. translateY: 0,
  428. }}
  429. />
  430. </div>
  431. );
  432. };
  433. ```
  434. #### 步骤六:组装编辑器主组件
  435. ```tsx
  436. // src/editor.tsx
  437. import { EditorRenderer, FreeLayoutEditorProvider } from '@flowgram.ai/free-layout-editor';
  438. import { useEditorProps } from './hooks/use-editor-props';
  439. import { Tools } from './components/tools';
  440. import { NodeAddPanel } from './components/node-add-panel';
  441. import { Minimap } from './components/minimap';
  442. import '@flowgram.ai/free-layout-editor/index.css';
  443. import './index.css';
  444. export const Editor = () => {
  445. const editorProps = useEditorProps();
  446. return (
  447. <FreeLayoutEditorProvider {...editorProps}>
  448. <div className="demo-free-container">
  449. <div className="demo-free-layout">
  450. <NodeAddPanel />
  451. <EditorRenderer className="demo-free-editor" />
  452. </div>
  453. <Tools />
  454. <Minimap />
  455. </div>
  456. </FreeLayoutEditorProvider>
  457. );
  458. };
  459. ```
  460. #### 步骤七:创建应用入口
  461. ```tsx
  462. // src/app.tsx
  463. import React from 'react';
  464. import ReactDOM from 'react-dom';
  465. import { Editor } from './editor';
  466. ReactDOM.render(<Editor />, document.getElementById('root'))
  467. ```
  468. #### 步骤八:添加样式
  469. ```css
  470. /* src/index.css */
  471. .demo-free-node {
  472. display: flex;
  473. min-width: 300px;
  474. min-height: 100px;
  475. flex-direction: column;
  476. align-items: flex-start;
  477. box-sizing: border-box;
  478. border-radius: 8px;
  479. border: 1px solid var(--light-usage-border-color-border, rgba(28, 31, 35, 0.08));
  480. background: #fff;
  481. box-shadow: 0px 2px 4px 0px rgba(0, 0, 0, 0.1);
  482. }
  483. .demo-free-node-title {
  484. background-color: #93bfe2;
  485. width: 100%;
  486. border-radius: 8px 8px 0 0;
  487. padding: 4px 12px;
  488. }
  489. .demo-free-node-content {
  490. padding: 4px 12px;
  491. flex-grow: 1;
  492. width: 100%;
  493. }
  494. .demo-free-node::before {
  495. content: '';
  496. position: absolute;
  497. top: 0;
  498. right: 0;
  499. bottom: 0;
  500. left: 0;
  501. z-index: -1;
  502. background-color: white;
  503. border-radius: 7px;
  504. }
  505. .demo-free-node:hover:before {
  506. -webkit-filter: drop-shadow(0 0 1px rgba(0, 0, 0, 0.3)) drop-shadow(0 4px 14px rgba(0, 0, 0, 0.1));
  507. filter: drop-shadow(0 0 1px rgba(0, 0, 0, 0.3)) drop-shadow(0 4px 14px rgba(0, 0, 0, 0.1));
  508. }
  509. .demo-free-node.activated:before,
  510. .demo-free-node.selected:before {
  511. outline: 2px solid var(--light-usage-primary-color-primary, #4d53e8);
  512. -webkit-filter: drop-shadow(0 0 1px rgba(0, 0, 0, 0.3)) drop-shadow(0 4px 14px rgba(0, 0, 0, 0.1));
  513. filter: drop-shadow(0 0 1px rgba(0, 0, 0, 0.3)) drop-shadow(0 4px 14px rgba(0, 0, 0, 0.1));
  514. }
  515. .demo-free-sidebar {
  516. height: 100%;
  517. overflow-y: auto;
  518. padding: 12px 16px 0;
  519. box-sizing: border-box;
  520. background: #f7f7fa;
  521. border-right: 1px solid rgba(29, 28, 35, 0.08);
  522. }
  523. .demo-free-right-top-panel {
  524. position: fixed;
  525. right: 10px;
  526. top: 70px;
  527. width: 300px;
  528. z-index: 999;
  529. }
  530. .demo-free-card {
  531. width: 140px;
  532. height: 60px;
  533. display: flex;
  534. align-items: center;
  535. justify-content: center;
  536. font-size: 20px;
  537. background: #fff;
  538. border-radius: 8px;
  539. box-shadow: 0 6px 8px 0 rgba(28, 31, 35, 0.03);
  540. cursor: -webkit-grab;
  541. cursor: grab;
  542. line-height: 16px;
  543. margin-bottom: 12px;
  544. overflow: hidden;
  545. padding: 16px;
  546. position: relative;
  547. color: black;
  548. }
  549. .demo-free-layout {
  550. display: flex;
  551. flex-direction: row;
  552. flex-grow: 1;
  553. }
  554. .demo-free-editor {
  555. flex-grow: 1;
  556. position: relative;
  557. height: 100%;
  558. }
  559. .demo-free-container {
  560. position: absolute;
  561. left: 0;
  562. top: 0;
  563. display: flex;
  564. width: 100%;
  565. height: 100%;
  566. flex-direction: column;
  567. }
  568. ```
  569. ### 4. 运行项目
  570. 完成上述步骤后,你可以运行项目查看效果:
  571. ```bash
  572. npm run dev
  573. ```
  574. 项目将在本地启动,通常访问 http://localhost:3000 即可看到效果。
  575. ## 核心概念
  576. ### 1. 数据结构
  577. Free Layout 使用标准化的数据结构来描述节点和连接:
  578. ```tsx
  579. // 工作流数据结构
  580. const initialData: WorkflowJSON = {
  581. // 节点定义
  582. nodes: [
  583. {
  584. id: 'start_0', // 节点唯一ID
  585. type: 'start', // 节点类型(对应 nodeRegistries 中的注册)
  586. meta: {
  587. position: { x: 0, y: 0 }, // 节点位置
  588. },
  589. data: {
  590. title: 'Start', // 节点数据(可自定义)
  591. content: 'Start content'
  592. },
  593. },
  594. // 更多节点...
  595. ],
  596. // 连线定义
  597. edges: [
  598. {
  599. sourceNodeID: 'start_0', // 源节点ID
  600. targetNodeID: 'node_0', // 目标节点ID
  601. },
  602. // 更多连线...
  603. ],
  604. };
  605. ```
  606. ### 2. 节点注册
  607. 使用 `nodeRegistries` 定义不同类型节点的行为和外观:
  608. ```tsx
  609. // 节点注册
  610. import { WorkflowNodeRegistry } from '@flowgram.ai/free-layout-editor';
  611. export const nodeRegistries: WorkflowNodeRegistry[] = [
  612. // 开始节点定义
  613. {
  614. type: 'start',
  615. meta: {
  616. isStart: true, // Mark as start
  617. deleteDisable: true, // The start node cannot be deleted
  618. copyDisable: true, // The start node cannot be copied
  619. defaultPorts: [{ type: 'output' }], // Used to define the input and output ports, the start node only has the output port
  620. },
  621. },
  622. // 更多节点类型...
  623. ];
  624. ```
  625. ### 3. 编辑器组件
  626. ```tsx
  627. // 核心编辑器容器与渲染器
  628. import {
  629. FreeLayoutEditorProvider,
  630. EditorRenderer
  631. } from '@flowgram.ai/free-layout-editor';
  632. // 编辑器配置示例
  633. const editorProps = {
  634. background: true, // 启用背景网格
  635. readonly: false, // 非只读模式,允许编辑
  636. initialData: {...}, // 初始化数据:节点和边的定义
  637. nodeRegistries: [...], // 节点类型注册
  638. nodeEngine: {
  639. enable: true, // 启用节点表单引擎
  640. },
  641. history: {
  642. enable: true, // 启用历史记录
  643. enableChangeNode: true, // 监听节点数据变化
  644. }
  645. };
  646. // 完整编辑器渲染
  647. <FreeLayoutEditorProvider {...editorProps}>
  648. <div className="container">
  649. <NodeAddPanel /> {/* 节点添加面板 */}
  650. <EditorRenderer /> {/* 核心编辑器渲染区域 */}
  651. <Tools /> {/* 工具栏 */}
  652. <Minimap /> {/* 缩略图 */}
  653. </div>
  654. </FreeLayoutEditorProvider>
  655. ```
  656. ### 4. 核心钩子函数
  657. 在组件中可以使用多种钩子函数获取和操作编辑器:
  658. ```tsx
  659. // 获取拖拽服务
  660. const dragService = useService<WorkflowDragService>(WorkflowDragService);
  661. // 开始拖拽节点
  662. dragService.startDragCard('nodeType', event, { data: {...} });
  663. // 获取编辑器上下文
  664. const { document, playground } = useClientContext();
  665. // 操作画布
  666. document.fitView(); // 适应视图
  667. playground.config.zoomin(); // 缩放画布
  668. document.fromJSON(newData); // 更新数据
  669. ```
  670. ### 5. 插件扩展
  671. Free Layout 支持通过插件机制扩展功能:
  672. ```tsx
  673. plugins: () => [
  674. // 缩略图插件
  675. createMinimapPlugin({
  676. canvasStyle: {
  677. canvasWidth: 180,
  678. canvasHeight: 100,
  679. canvasBackground: 'rgba(245, 245, 245, 1)',
  680. }
  681. }),
  682. // 自动对齐插件
  683. createFreeSnapPlugin({
  684. edgeColor: '#00B2B2', // 对齐线颜色
  685. alignColor: '#00B2B2', // 辅助线颜色
  686. edgeLineWidth: 1, // 线宽
  687. }),
  688. ],
  689. ```
  690. ## 安装
  691. ```bash
  692. npx @flowgram.ai/create-app@latest free-layout-simple
  693. ```
  694. ## 源码
  695. https://github.com/bytedance/flowgram.ai/tree/main/apps/demo-free-layout-simple
  696. ---
  697. url: /examples/free-layout/free-feature-overview.md
  698. ---
  699. # 最佳实践
  700. ## 安装
  701. ```bash
  702. npx @flowgram.ai/create-app@latest free-layout
  703. ```
  704. ## 源码
  705. https://github.com/bytedance/flowgram.ai/tree/main/apps/demo-free-layout
  706. ## 功能介绍
  707. 自动整理
  708. 吸附对齐 + 参考线
  709. ---
  710. url: /examples/node-form/basic.md
  711. ---
  712. # 基础用法
  713. 该例子展示了表单的几个基础用法
  714. * 表单组件渲染
  715. * 必填校验
  716. * 默认值设置
  717. ---
  718. url: /examples/node-form/effect.md
  719. ---
  720. # 副作用
  721. 以下例子展示了表单副作用的配置方式。举了两个个例子,行为描述如下
  722. 1. Basic effect(基础例子):当表单项值变更时,控制台会打印表单当前值。
  723. 2. Control other fields (控制其他表单项的值):当前表单项数据变更时要同时改变另一个表单项的值。
  724. ---
  725. url: /examples/node-form/array.md
  726. ---
  727. # 数组
  728. 以下例子展示了数组的基本用法,包含:
  729. * 基本写法(渲染、增删)。
  730. * 如何对数组每项配置校验逻辑。 此处的校验规则为每项最大长度不超过8个英文字符。
  731. * 如何对数组每项配置副作用。 此处的副作用为每项在初始化时控制台输出 `${name} value init to ${value}`, 值变更时输出 `${name} value changed to ${value}`
  732. * 数组项如何做交换。
  733. ---
  734. url: /examples/node-form/dynamic.md
  735. ---
  736. # 联动
  737. 当前例子展示了如何通过 `deps` 字段来声明表单项之间的联动更新关系。
  738. 例子说明:当 `Country` 有值时才会显示 `City` 字段。
  739. 你也可以将`form.getValueIn('country')` 作为 city `Field` 下组件的入参,来控制组件内的行为, 如筛选当前country下的cities。
  740. ---
  741. url: /api/common-apis.md
  742. ---
  743. # 常用 API
  744. ## FlowDocument (自动化布局文档数据)
  745. ```ts
  746. // 通过 hook 获取,也可以通过ctx
  747. const doc = useService<FlowDocument>(FlowDocument)
  748. doc.fromJSON(data) // 加载数据
  749. doc.getAllNodes() // 获取所有节点
  750. doc.traverseDFS(node => {}) // 深度遍历节点
  751. doc.toJSON() // TODO 这里老版本的数据,还没优化,业务最好自己使用 traverseDFS 实现 json 转换
  752. doc.addFromNode(targetNode, json) // 插入到指定节点的后边
  753. doc.onNodeCreate(({ node, json }) => {}) // 监听节点创建,data 为创建时候的json数据
  754. doc.onNodeDispose(({ node }) => {}) // 监听节点删除
  755. ```
  756. ## WorkflowDocument (自由连线布局文档数据) 继承自 FlowDocument
  757. ```ts
  758. const doc = useService<WorkflowDocument>(WorkflowDocument)
  759. doc.fromJSON(data) // 加载数据
  760. doc.toJSON() // 导出数据
  761. doc.getAllNodes() // 获取所有节点
  762. doc.linesManager.getAllLines() // 获取所有线条
  763. // 创建节点
  764. doc.createWorkflowNode({ id: nanoid(), type: 'xxx', data: {}, meta: { position: { x: 0, y: 0 } } })
  765. // 创建线条,from和to 为对应要连线的节点id, fromPort, toPort 如果为单个端口可以不指定
  766. doc.linesManager.createLine({ from, to, fromPort, toPort })
  767. // 监听变化,这里会监听线条和节点等事件
  768. doc.onContentChange((e) => {
  769. })
  770. ```
  771. ## FlowNodeEntity(节点)
  772. ```ts
  773. node.flowNodeType // 当前节点的type类型
  774. node.transform.bounds // 获取节点的外围矩形框, 包含 x,y,width,height
  775. node.updateExtInfo({ title: 'xxx' }) // 设置扩展数据, 响应式会刷新节点
  776. node.getExtInfo<T>() // 获取扩展数据
  777. node.getNodeRegister() // 拿到当前节点的定义
  778. node.dispose() // 删除节点
  779. // renderData 是 节点 ui相关数据
  780. const renderData = node.renderData
  781. renderData.node // 当前节点的domNode
  782. renderData.expanded // 当前节点是否展开,可以设置
  783. // 拿到所有上游输入和输出节点(自由连线布局)
  784. node.getData<WorkflowNodeLinesData>(WorkflowNodeLinesData).allInputNodes
  785. node.getData<WorkflowNodeLinesData>(WorkflowNodeLinesData).allOutputNodes
  786. ```
  787. ## Playground (画布)
  788. ```ts
  789. // 通过 hook 获取,也可以通过ctx
  790. const playground = useService(Playground)
  791. // 滚动到指定的节点并居中
  792. ctx.playground.config.scrollToView({
  793. entities: [node]
  794. scrollToCenter: true
  795. easing: true // 缓动动画
  796. })
  797. // 滚动画布
  798. ctx.playground.config.scroll({
  799. scrollX: 0
  800. scrollY: 0
  801. })
  802. // 适配屏幕
  803. ctx.playground.config.fitView(
  804. doc.root.getData<FlowNodeTransformData>().bounds, // 需要居中的矩形框,这里拿节点根节点的大小代表最大的框
  805. true, // 是否缓动
  806. 20, // padding,留出空白间距
  807. )
  808. // 缩放
  809. ctx.playground.config.zoomin()
  810. ctx.playground.config.zoomout()
  811. ctx.playground.config.finalScale // 当前缩放比例
  812. ```
  813. ## SelectionService (选择器)
  814. ```ts
  815. const selectionService = useService<SelectionService>()
  816. selection.selection // 返回当前选中的节点数组,也可以修改,如选中节点 seleciton.selection = [node]
  817. selection.onSelectionChanged(() => {}) // 监听变化
  818. ```
  819. ---
  820. url: /api/index.md
  821. ---
  822. ---
  823. url: /api/plugins.md
  824. ---
  825. 这里是官网 api 配置,demo 用。
  826. ---
  827. url: /api/plugins/config-basic.md
  828. ---
  829. # Basic Config
  830. ## root
  831. * Type: `string`
  832. * Default: `docs`
  833. Specifies the document root directory. For example:
  834. ```ts title="rspress.config.ts"
  835. import { defineConfig } from 'rspress/config';
  836. export default defineConfig({
  837. root: 'docs',
  838. });
  839. ```
  840. This config supports both relative and absolute paths, with relative paths being relative to the current working directory(cwd).
  841. Of course, in addition to specifying the document root directory through the config file, you can also specify it through command line parameters, such as:
  842. ```bash
  843. rspress dev docs
  844. rspress build docs
  845. ```
  846. ## base
  847. * Type: `string`
  848. * Default: `/`
  849. Deployment base path. For example, if you plan to deploy your site to `https://foo.github.io/bar/`, then you should set `base` to `"/bar/"`:
  850. ```ts title="rspress.config.ts"
  851. import { defineConfig } from 'rspress/config';
  852. export default defineConfig({
  853. base: '/bar/',
  854. });
  855. ```
  856. ## title
  857. * Type: `string`
  858. * Default: `"Rspress"`
  859. Site title. This parameter will be used as the title of the HTML page. For example:
  860. ```ts title="rspress.config.ts"
  861. import { defineConfig } from 'rspress/config';
  862. export default defineConfig({
  863. title: 'My Site',
  864. });
  865. ```
  866. ## description
  867. * Type: `string`
  868. * Default: `""`
  869. Site description. This will be used as the description of the HTML page. For example:
  870. ```ts title="rspress.config.ts"
  871. import { defineConfig } from 'rspress/config';
  872. export default defineConfig({
  873. description: 'My Site Description',
  874. });
  875. ```
  876. ## icon
  877. * Type: `string`
  878. * Default: `""`
  879. Site icon. This path will be used as the icon path for the HTML page. For example:
  880. ```ts title="rspress.config.ts"
  881. import { defineConfig } from 'rspress/config';
  882. export default defineConfig({
  883. icon: '/favicon.ico',
  884. });
  885. ```
  886. The framework will find your icon in the `public` directory, of course you can also set it to a CDN address.
  887. ## logo \{#logo-1}
  888. * Type: `string | { dark: string; light: string }`
  889. * Default: `""`
  890. Site logo. This path will be used as the logo path in the upper left corner of the navbar. For example:
  891. ```ts title="rspress.config.ts"
  892. import { defineConfig } from 'rspress/config';
  893. export default defineConfig({
  894. logo: '/logo.png',
  895. });
  896. ```
  897. The framework will find your icon in the `public` directory, you can also set it to a CDN address.
  898. Of course you can set different logos for dark/light mode:
  899. ```ts title="rspress.config.ts"
  900. import { defineConfig } from 'rspress/config';
  901. export default defineConfig({
  902. logo: {
  903. dark: '/logo-dark.png',
  904. light: '/logo-light.png',
  905. },
  906. });
  907. ```
  908. ## logoText
  909. * Type: `string`
  910. * Default: `""`
  911. Site logo Text. This text will be used as the logo text in the upper left corner of the navbar. For example:
  912. ```ts title="rspress.config.ts"
  913. import { defineConfig } from 'rspress/config';
  914. export default defineConfig({
  915. logoText: 'rspress',
  916. });
  917. ```
  918. ## outDir
  919. * Type: `string`
  920. * Default: `doc_build`
  921. Custom output directory for built sites. for example:
  922. ```ts title="rspress.config.ts"
  923. import { defineConfig } from 'rspress/config';
  924. export default defineConfig({
  925. outDir: 'doc_build',
  926. });
  927. ```
  928. ## locales
  929. * Type: `Locale[]`
  930. ```ts
  931. export interface Locale {
  932. lang: string;
  933. label: string;
  934. title?: string;
  935. description?: string;
  936. }
  937. ```
  938. I18n config of the site. for example:
  939. ```ts title="rspress.config.ts"
  940. import { defineConfig } from 'rspress/config';
  941. export default defineConfig({
  942. locales: [
  943. {
  944. lang: 'en-US',
  945. label: 'English',
  946. title: 'Doc Tools',
  947. description: 'Doc Tools',
  948. },
  949. {
  950. lang: 'zh-CN',
  951. label: '简体中文',
  952. title: '文档框架',
  953. description: '文档框架',
  954. },
  955. ],
  956. });
  957. ```
  958. ## head
  959. * Type: `string` | `[string, Record<string, string>]` | `(route) => string | [string, Record<string, string>] | undefined`
  960. * Can be appended per page via [frontmatter](/zh/api/plugins/config-frontmatter.md#head)
  961. Additional elements to render in the `<head>` tag in the page HTML.
  962. ```ts title="rspress.config.ts"
  963. import { defineConfig } from 'rspress/config';
  964. export default defineConfig({
  965. // ... other user config
  966. head: [
  967. '<meta name="author" content="John Doe">',
  968. // or
  969. ['meta', { name: 'author', content: 'John Doe' }],
  970. // [htmlTag, { attrName: attrValue, attrName2: attrValue2 }]
  971. // or
  972. (route) => {
  973. if (route.routePath.startsWith('/jane/')) return "<meta name='author' content='Jane Doe'>";
  974. if (route.routePath.startsWith('/john/')) return ['meta', { name: 'author', content: 'John Doe' }];
  975. \\ or even skip returning anything
  976. return undefined;
  977. }
  978. ]
  979. });
  980. ```
  981. ## mediumZoom
  982. * Type: `boolean` | `{ selector?: string }`
  983. * Default: `true`
  984. Whether to enable the image zoom function. It is enabled by default, you can disable it by setting `mediumZoom` to `false`.
  985. > The bottom layer is implemented using the [medium-zoom](https://github.com/francoischalifour/medium-zoom) library.
  986. Example usage:
  987. ```ts title="rspress.config.ts"
  988. import { defineConfig } from 'rspress/config';
  989. export default defineConfig({
  990. // Turn off image zoom
  991. mediumZoom: false,
  992. // Configure the CSS selector to customize the picture to be zoomed, the default is '.rspress-doc img'
  993. mediumZoom: {
  994. selector: '.rspress-doc img',
  995. },
  996. });
  997. ```
  998. ## search
  999. * Type: `{ searchHooks: string; versioned: boolean; }`
  1000. ### searchHooks
  1001. You can add search runtime hooks logic through the `searchHooks` parameter, for example:
  1002. ```ts title="rspress.config.ts"
  1003. import { defineConfig } from 'rspress/config';
  1004. import path from 'path';
  1005. export default defineConfig({
  1006. search: {
  1007. searchHooks: path.join(__dirname, 'searchHooks.ts'),
  1008. },
  1009. });
  1010. ```
  1011. For specific hook logic, you can read [Customize Search Functions](/guide/advanced/custom-search.md).
  1012. ### versioned
  1013. If you are using `multiVersion`, the `versioned` parameter allows you to create a separate search index for each version of your documentation.
  1014. When enabled, the search will only query the index corresponding to the currently selected version.
  1015. ```ts title="rspress.config.ts"
  1016. import { defineConfig } from 'rspress/config';
  1017. export default defineConfig({
  1018. search: {
  1019. versioned: true,
  1020. },
  1021. });
  1022. ```
  1023. ## globalUIComponents
  1024. * Type: `(string | [string, object])[]`
  1025. * Default: `[]`
  1026. You can register global UI components through the `globalUIComponents` parameter, for example:
  1027. ```ts title="rspress.config.ts"
  1028. import { defineConfig } from 'rspress/config';
  1029. import path from 'path';
  1030. export default defineConfig({
  1031. globalUIComponents: [path.join(__dirname, 'components', 'MyComponent.tsx')],
  1032. });
  1033. ```
  1034. The item of `globalUIComponents` can be a string, which is the path of the component file, or an array, the first item is the path of the component file, and the second item is the component props, for example:
  1035. ```ts title="rspress.config.ts"
  1036. import { defineConfig } from 'rspress/config';
  1037. export default defineConfig({
  1038. globalUIComponents: [
  1039. [
  1040. path.join(__dirname, 'components', 'MyComponent.tsx'),
  1041. {
  1042. foo: 'bar',
  1043. },
  1044. ],
  1045. ],
  1046. });
  1047. ```
  1048. ## multiVersion
  1049. * Type: `{ default: string; versions: string[] }`
  1050. You can enable multi-version support through the `multiVersion` parameter, for example:
  1051. ```ts title="rspress.config.ts"
  1052. import { defineConfig } from 'rspress/config';
  1053. export default defineConfig({
  1054. multiVersion: {
  1055. default: 'v1',
  1056. versions: ['v1', 'v2'],
  1057. },
  1058. });
  1059. ```
  1060. The `default` parameter is the default version, and the `versions` parameter is the version list.
  1061. ## route
  1062. * Type: `Object`
  1063. Custom route config.
  1064. ### route.include
  1065. * Type: `string[]`
  1066. * Default: `[]`
  1067. Add some extra files in the route. By default, only the files in the document root directory will be included in the route. If you want to add some extra files to the route, you can use this option. For example:
  1068. ```js
  1069. import { defineConfig } from 'rspress/config';
  1070. export default defineConfig({
  1071. route: {
  1072. include: ['other-dir/**/*.{md,mdx}'],
  1073. },
  1074. });
  1075. ```
  1076. > Note: The strings in the array support glob patterns, the glob expression should be based on the `root` directory of the document, with the corresponding extensions suffix.
  1077. :::note
  1078. We recommend using [addPages hook](/plugin/system/plugin-api.md#addpages) in a custom Rspress plugin to add some additional files to the route, so that the page route and file path/content can be specified more flexibly and reasonably.
  1079. :::
  1080. ### route.exclude
  1081. * Type: `string[]`
  1082. * Default: `[]`
  1083. Exclude some files from the route. For example:
  1084. ```js
  1085. import { defineConfig } from 'rspress/config';
  1086. export default defineConfig({
  1087. route: {
  1088. exclude: ['custom.tsx', 'component/**/*'],
  1089. },
  1090. });
  1091. ```
  1092. > Note: The strings in the array support glob patterns, the glob expression should be based on the `root` directory of the document.
  1093. ### route.extensions
  1094. * Type: `string[]`
  1095. * Default: `[]`
  1096. The extensions of the files that will be included in the route. By default, Rspress will include all `'js'`, `'jsx'`, `'ts'`, `'tsx'`, `'md'`, `'mdx'` files in the route. If you want to customize the extensions, you can use this option. For example:
  1097. ```js
  1098. import { defineConfig } from 'rspress/config';
  1099. export default defineConfig({
  1100. route: {
  1101. extensions: ['.jsx', '.md', '.mdx'],
  1102. },
  1103. });
  1104. ```
  1105. ### route.cleanUrls
  1106. * Type: `Boolean`
  1107. * Default: `false`
  1108. Generate url without suffix when `cleanUrls` is `true` for shorter url link.
  1109. ```js
  1110. import { defineConfig } from 'rspress/config';
  1111. export default defineConfig({
  1112. route: {
  1113. cleanUrls: true,
  1114. },
  1115. });
  1116. ```
  1117. ## ssg
  1118. * Type: `boolean | { strict?: boolean }`
  1119. * Default: `true`
  1120. Determines whether to enable Static Site Generation. It is enabled by default, but you can disable it by setting `ssg` to `false`.
  1121. ```ts title="rspress.config.ts"
  1122. import { defineConfig } from 'rspress/config';
  1123. export default defineConfig({
  1124. ssg: false,
  1125. });
  1126. ```
  1127. If SSG fails, it will fallback to CSR by default. You can set `ssg` to `{ strict: true }` to strictly require SSG to succeed, otherwise an error will be thrown.
  1128. ```ts title="rspress.config.ts"
  1129. export default {
  1130. ssg: {
  1131. strict: true,
  1132. },
  1133. };
  1134. ```
  1135. ## replaceRules
  1136. * Type: `{ search: string | RegExp; replace: string; }[]`
  1137. * Default: `[]`
  1138. You can set text replacement rules for the entire site through `replaceRules`. The rules will apply to everything including `_meta.json` files, frontmatter configurations, and document content and titles.
  1139. ```ts title="rspress.config.ts"
  1140. export default {
  1141. replaceRules: [
  1142. {
  1143. search: /foo/g,
  1144. replace: 'bar',
  1145. },
  1146. ],
  1147. };
  1148. ```
  1149. ---
  1150. url: /api/plugins/config-build.md
  1151. ---
  1152. # Build Config
  1153. ## builderConfig
  1154. * Type: `RsbuildConfig`
  1155. Used to customize the configurations of Rsbuild. For detailed configurations, please refer to [Rsbuild - Config](https://rsbuild.dev/config/).
  1156. * Example: Use [resolve.alias](https://rsbuild.dev/config/resolve/alias) to configure path aliases:
  1157. ```ts title="rspress.config.ts"
  1158. export default defineConfig({
  1159. builderConfig: {
  1160. resolve: {
  1161. alias: {
  1162. '@common': './src/common',
  1163. },
  1164. },
  1165. },
  1166. });
  1167. ```
  1168. * Example: Use [tools.rspack](https://rsbuild.dev/config/tools/rspack) to modify the Rspack configuration, such as registering a webpack or Rspack plugin:
  1169. ```ts title="rspress.config.ts"
  1170. export default defineConfig({
  1171. builderConfig: {
  1172. tools: {
  1173. rspack: async config => {
  1174. const { default: ESLintPlugin } = await import('eslint-webpack-plugin');
  1175. config.plugins?.push(new ESLintPlugin());
  1176. return config;
  1177. },
  1178. },
  1179. },
  1180. });
  1181. ```
  1182. ::: warning
  1183. If you want to modify the output directory, please use [outDir](/api/config/config-basic.md#outdir).
  1184. :::
  1185. ## builderPlugins
  1186. * Type: `RsbuildPlugin[]`
  1187. Used to register [Rsbuild plugins](https://rsbuild.dev/plugins/list/).
  1188. You can use the rich plugins of Rsbuild in the Rspress project to quickly extend the building capabilities.
  1189. * Example: Support Vue SFC through [@rsbuild/plugin-vue](https://rsbuild.dev/plugins/list/plugin-vue)
  1190. ```ts title="rspress.config.ts"
  1191. import { defineConfig } from 'rspress/config';
  1192. import { pluginVue } from '@rsbuild/plugin-vue';
  1193. export default defineConfig({
  1194. builderPlugins: [pluginVue()],
  1195. });
  1196. ```
  1197. * Example: Add Google analytics through [rsbuild-plugin-google-analytics](https://github.com/rspack-contrib/rsbuild-plugin-google-analytics)
  1198. ```ts title="rspress.config.ts"
  1199. import { defineConfig } from 'rspress/config';
  1200. import { pluginGoogleAnalytics } from 'rsbuild-plugin-google-analytics';
  1201. export default defineConfig({
  1202. builderPlugins: [
  1203. pluginGoogleAnalytics({
  1204. // replace this with your Google tag ID
  1205. id: 'G-xxxxxxxxxx',
  1206. }),
  1207. ],
  1208. });
  1209. ```
  1210. * Example: Add Open Graph meta tags through [rsbuild-plugin-open-graph](https://github.com/rspack-contrib/rsbuild-plugin-open-graph)
  1211. ```ts title="rspress.config.ts"
  1212. import { defineConfig } from 'rspress/config';
  1213. import { pluginOpenGraph } from 'rsbuild-plugin-open-graph';
  1214. export default defineConfig({
  1215. builderPlugins: [
  1216. pluginOpenGraph({
  1217. title: 'My Website',
  1218. type: 'website',
  1219. // ...options
  1220. }),
  1221. ],
  1222. });
  1223. ```
  1224. You can also override the built-in plugins [@rsbuild/plugin-react](https://rsbuild.dev/plugins/list/plugin-react), [@rsbuild/plugin-sass](https://rsbuild.dev/plugins/list/plugin-sass) and [@rsbuild/plugin-less](https://rsbuild.dev/plugins/list/plugin-less), and customize relevant plugin options.
  1225. * Example: Modify related options of built-in [@rsbuild/plugin-less](https://rsbuild.dev/plugins/list/plugin-less) plugin
  1226. ```ts title="rspress.config.ts"
  1227. import { defineConfig } from 'rspress/config';
  1228. import { pluginLess } from '@rsbuild/plugin-less';
  1229. export default defineConfig({
  1230. builderPlugins: [
  1231. pluginLess({
  1232. lessLoaderOptions: {
  1233. lessOptions: {
  1234. math: 'always',
  1235. },
  1236. },
  1237. }),
  1238. ],
  1239. });
  1240. ```
  1241. ### Default Config
  1242. If you need to view the default Rspack or Rsbuild configs, you can add the `DEBUG=rsbuild` parameter when running the `rspress dev` or `rspress build` command:
  1243. ```bash
  1244. DEBUG=rsbuild rspress dev
  1245. ```
  1246. After execution, the `rsbuild.config.js` file is created in the `doc_build` directory, which contains the complete `builderConfig`.
  1247. > Please refer to [Rsbuild - Debug Mode](https://rsbuild.dev/guide/debug/debug-mode) for more information on how to debug the Rsbuild.
  1248. ## markdown
  1249. * Type: `Object`
  1250. Configure MDX-related compilation abilities.
  1251. ### markdown.remarkPlugins
  1252. * Type: `Array`
  1253. * Default: `[]`
  1254. Configure the remark plugins. for example:
  1255. ```ts title="rspress.config.ts"
  1256. import { defineConfig } from 'rspress/config';
  1257. export default defineConfig({
  1258. markdown: {
  1259. remarkPlugins: [
  1260. [
  1261. require('remark-autolink-headings'),
  1262. {
  1263. behavior: 'wrap',
  1264. },
  1265. ],
  1266. ],
  1267. },
  1268. });
  1269. ```
  1270. ### markdown.rehypePlugins
  1271. * Type: `Array`
  1272. Configure the rehype plugin. for example:
  1273. ```ts title="rspress.config.ts"
  1274. import { defineConfig } from 'rspress/config';
  1275. export default defineConfig({
  1276. markdown: {
  1277. rehypePlugins: [
  1278. [
  1279. require('rehype-autolink-headings'),
  1280. {
  1281. behavior: 'wrap',
  1282. },
  1283. ],
  1284. ],
  1285. },
  1286. });
  1287. ```
  1288. ### markdown.checkDeadLinks
  1289. * Type: `boolean`
  1290. * Default: `false`
  1291. Whether to check for dead links. for example:
  1292. ```ts title="rspress.config.ts"
  1293. import { defineConfig } from 'rspress/config';
  1294. export default defineConfig({
  1295. markdown: {
  1296. checkDeadLinks: true,
  1297. },
  1298. });
  1299. ```
  1300. After enabling this config, the framework will check the links in the document based on the conventional routing table. If there is an unreachable link, the build will throw an error and exit.
  1301. ### markdown.mdxRs
  1302. * Type: `boolean | { include: (filepath: string) => boolean }`
  1303. * Default: `true`
  1304. ### markdown.showLineNumbers
  1305. * Type: `boolean`
  1306. Whether to display the line number of the code block. Defaults to `false`.
  1307. ### markdown.defaultWrapCode
  1308. * Type: `boolean`
  1309. Whether to enable long code line wrapping display by default. Defaults to `false`.
  1310. ### markdown.globalComponents
  1311. * Type: `string[]`
  1312. Register component to the global scope, which will make it automatically available in every MDX file, without any import statements.For example:
  1313. ```ts title="rspress.config.ts"
  1314. import { defineConfig } from 'rspress/config';
  1315. import path from 'path';
  1316. export default defineConfig({
  1317. markdown: {
  1318. globalComponents: [path.join(__dirname, 'src/src/components/Alert.tsx')],
  1319. },
  1320. });
  1321. ```
  1322. Then you can use the `Alert` component in any MDX file:
  1323. ```mdx title="test.mdx"
  1324. <Alert type="info">This is a info alert</Alert>
  1325. ```
  1326. :::danger Danger
  1327. Please set `markdown.mdxRs` to `false` when configuring `globalComponents`, otherwise the global components will not take effect.
  1328. :::
  1329. ### markdown.highlightLanguages
  1330. * Type: `[string, string][]`
  1331. * Default:
  1332. ```js
  1333. const DEFAULT_HIGHLIGHT_LANGUAGES = [
  1334. ['js', 'javascript'],
  1335. ['ts', 'typescript'],
  1336. ['jsx', 'tsx'],
  1337. ['xml', 'xml-doc'],
  1338. ['md', 'markdown'],
  1339. ['mdx', 'tsx'],
  1340. ];
  1341. ```
  1342. Rspress supports automatic import of highlighted languages and makes some language aliases by default.
  1343. * By default, it is implemented based on [Prism.js](https://prismjs.com/). You can also switch to Shiki through [@rspress/plugin-shiki](/plugin/official-plugins/shiki.md).
  1344. * The default configuration alias languages include `js`, `jsx`, `ts`, `tsx`, `xml`, `md`, `mdx`.
  1345. You can also extend these default aliases, such as:
  1346. ```ts title="rspress.config.ts"
  1347. import { defineConfig } from 'rspress/config';
  1348. import path from 'path';
  1349. export default defineConfig({
  1350. markdown: {
  1351. highlightLanguages: [
  1352. // Alias as md, full name as markdown
  1353. ['md', 'markdown'],
  1354. ],
  1355. },
  1356. });
  1357. ```
  1358. The alias of each language is configured in the format of `[string, string]`. The former is the alias of the language, and the latter is the full name of the language. You can go to [File List](https://github.com/react-syntax-highlighter/react-syntax-highlighter/tree/master/src/languages/prism) to view the full names of all supported languages.
  1359. ---
  1360. url: /api/plugins/config-frontmatter.md
  1361. ---
  1362. # Front Matter Config
  1363. ## title
  1364. * Type: `string`
  1365. The title of the page. By default, the page's h1 heading will be used as the title of the HTML document. But if you want to use a different title, you can use Front Matter to specify the title of the page. For example:
  1366. ```yaml
  1367. ---
  1368. title: My Home Page
  1369. ---
  1370. ```
  1371. ## description
  1372. * Type: `string`
  1373. A custom description for the page. For example:
  1374. ```yaml
  1375. ---
  1376. description: This is my custom description for this page.
  1377. ---
  1378. ```
  1379. ## pageType
  1380. * Type: `'home' | 'doc' | 'custom' | 'blank' | '404'`
  1381. * Default: `'doc'`
  1382. The type of the page. By default, the page type is `doc`. But if you want to use a different page type, you can use the Front Matter field `pageType` to specify the page type. E.g:
  1383. ```yaml
  1384. ---
  1385. pageType: home
  1386. ---
  1387. ```
  1388. The meaning of each `pageType` config is as follows:
  1389. ## titleSuffix
  1390. * Type: `string`
  1391. Set the suffix of the page title. When `titleSuffix` is not set, the site's [title](/api/config/config-basic.md#title) is used as the suffix by default.
  1392. ```yaml
  1393. ---
  1394. titleSuffix: 'Rspack-based Static Site Generator'
  1395. ---
  1396. ```
  1397. The default separator between the title and the suffix is `-`, you can also use `|` for separation:
  1398. ```yaml
  1399. ---
  1400. titleSuffix: '| Rspack-based Static Site Generator'
  1401. ---
  1402. ```
  1403. ## head
  1404. * Type: `[string, Record<string, string>][]`
  1405. Specify extra head tags to be injected for the current page. Will be appended after head tags injected by site-level config.
  1406. For example, you can use these headers to specify custom meta tags for [Open Graph](https://ogp.me/).
  1407. ```yaml
  1408. ---
  1409. head:
  1410. - - meta
  1411. - property: og:title
  1412. content: The Rock
  1413. - - meta
  1414. - property: og:url
  1415. content: https://www.imdb.com/title/tt0117500/
  1416. - - meta
  1417. - property: og:image
  1418. content: https://ia.media-imdb.com/images/rock.jpg
  1419. # - - [htmlTag]
  1420. # - [attributeName]: [attributeValue]
  1421. # [attributeName]: [attributeValue]
  1422. ---
  1423. ```
  1424. ::: tip Note
  1425. Make sure to correctly define the header tag names and their attribute names.
  1426. For tags and attribute names that contain a hyphen (`-`), use the camelCase format.
  1427. For example, `http-equiv="refresh"` should be defined as `httpEquiv: refresh`.
  1428. This is because under the hood, headers are handled by React and react-helmet-async.
  1429. :::
  1430. ## hero
  1431. * Type: `Object`
  1432. The hero config for the home page. It has the following types:
  1433. ```ts
  1434. interface Hero {
  1435. name: string;
  1436. text: string;
  1437. tagline: string;
  1438. image?: {
  1439. src: string | { dark: string; light: string };
  1440. alt: string;
  1441. /**
  1442. * `srcset` and `sizes` are attributes of `<img>` tag. Please refer to https://mdn.io/srcset for the usage.
  1443. * When the value is an array, rspress will join array members with commas.
  1444. **/
  1445. srcset?: string | string[];
  1446. sizes?: string | string[];
  1447. };
  1448. actions: {
  1449. text: string;
  1450. link: string;
  1451. theme: 'brand' | 'alt';
  1452. }[];
  1453. }
  1454. ```
  1455. For example, you can use the following Front Matter to specify a page's hero config:
  1456. ```yaml
  1457. ---
  1458. pageType: home
  1459. hero:
  1460. name: Rspress
  1461. text: A Documentation Solution
  1462. tagline: A modern documentation development technology stack
  1463. actions:
  1464. - theme: brand
  1465. text: Introduction
  1466. link: /en/guide/introduction
  1467. - theme: alt
  1468. text: Quick Start
  1469. link: /en/guide/getting-started
  1470. ---
  1471. ```
  1472. When setting `hero.text`, you can use the `|` symbol in YAML to manually control line breaks:
  1473. ```yaml
  1474. ---
  1475. pageType: home
  1476. hero:
  1477. name: Rspress
  1478. text: |
  1479. A Documentation
  1480. Solution
  1481. ```
  1482. Or you can use `HTML` to specify the hero config for the page:
  1483. ```yaml
  1484. ---
  1485. pageType: home
  1486. hero:
  1487. name: <span class="hero-name">Rspress</span>
  1488. text: <span class="hero-text">A Documentation Solution</span>
  1489. tagline: <span class="hero-tagline">A modern documentation development technology stack</span>
  1490. actions:
  1491. - theme: brand
  1492. text: <span class="hero-actions-text">Introduction</span>
  1493. link: /zh/guide/introduction
  1494. - theme: alt
  1495. text: <span class="hero-actions-text">Quick Start</span>
  1496. link: /zh/guide/getting-started
  1497. ---
  1498. ```
  1499. ## features
  1500. * Type: `Array`
  1501. * Default: `[]`
  1502. features config of the `home` page. It has the following types:
  1503. ```ts
  1504. interface Feature {
  1505. title: string;
  1506. details: string;
  1507. icon: string;
  1508. // The length of the card grid, currently only support[3, 4, 6]
  1509. span?: number;
  1510. // The link of the feature, not required.
  1511. link?: string;
  1512. }
  1513. export type Features = Feature[];
  1514. ```
  1515. For example, you could use the following to specify the features configuration for the `home` page:
  1516. ```yaml
  1517. ---
  1518. pageType: home
  1519. features:
  1520. - title: 'MDX Support'
  1521. details: MDX is a powerful way to write content. You can use React components in Markdown.
  1522. icon: 📦
  1523. - title: 'Feature Rich'
  1524. details: Out of box support for i18n, full-text search etc.
  1525. icon: 🎨
  1526. - title: 'Customizable'
  1527. details: You can customize the theme ui and the build process.
  1528. icon: 🚀
  1529. ---
  1530. ```
  1531. ## sidebar
  1532. Whether to show the sidebar on the left. By default, the `doc` page will display the sidebar on the left. If you want to hide the sidebar on the left, you can use the following Front Matter config:
  1533. ```yaml
  1534. ---
  1535. sidebar: false
  1536. ---
  1537. ```
  1538. ## outline
  1539. Whether to display the outline column on the right. By default, the `doc` page displays the outline column on the right. You can hide the outline column with the following config:
  1540. ```yaml
  1541. ---
  1542. outline: false
  1543. ---
  1544. ```
  1545. ## footer
  1546. Whether to display the components at the bottom of the document (such as previous/next page). By default, the `doc` page will display the footer at the bottom. You can hide the footer with the following config:
  1547. ```yaml
  1548. ---
  1549. footer: false
  1550. ---
  1551. ```
  1552. ## navbar
  1553. Whether to hide the top navigation bar. You can hide the top nav bar with the following config:
  1554. ```yaml
  1555. ---
  1556. navbar: true
  1557. ---
  1558. ```
  1559. ## overviewHeaders
  1560. * Type: `number[]`
  1561. * Default: `[2]`
  1562. The headers shown in the overview page. By default, the displayed header is h2. But if you want to display different headers, you can specify it using the `overviewHeaders` Front Matter field. For example:
  1563. ```yaml
  1564. ---
  1565. overviewHeaders: []
  1566. ---
  1567. ```
  1568. Or
  1569. ```yaml
  1570. ---
  1571. overviewHeaders: [2, 3]
  1572. ---
  1573. ```
  1574. ## context
  1575. * Type: `string`
  1576. After configuration, the `data-context` attribute will be added to the DOM node when the sidebar is generated, and the value is the configured value.
  1577. ```yaml title="foo.mdx"
  1578. ---
  1579. context: 'context-foo'
  1580. ---
  1581. ```
  1582. ```yaml title="bar.mdx"
  1583. ---
  1584. context: 'context-bar'
  1585. ---
  1586. ```
  1587. The DOM structure of the final generated sidebar is abbreviated as follows:
  1588. ```html
  1589. <div class="rspress-sidebar-group">
  1590. <div className="rspress-sidebar-item" data-context="context-foo"></div>
  1591. <div className="rspress-sidebar-item" data-context="context-bar"></div>
  1592. </div>
  1593. ```
  1594. ---
  1595. url: /api/plugins/config-theme.md
  1596. ---
  1597. # Theme Config
  1598. Theme config is located under `themeConfig` in the `doc` param. For example:
  1599. ```ts title="rspress.config.ts"
  1600. import { defineConfig } from 'rspress/config';
  1601. export default defineConfig({
  1602. themeConfig: {
  1603. // ...
  1604. },
  1605. });
  1606. ```
  1607. ## nav
  1608. * Type: `Array`
  1609. * Default: `[]`
  1610. The `nav` configuration is an array of `NavItem` with the following types:
  1611. ```ts
  1612. interface NavItem {
  1613. // Navbar text
  1614. text: string;
  1615. // Navbar link
  1616. link: '/';
  1617. // Activation rules for navbar links
  1618. activeMatch: '^/$|^/';
  1619. // svg tag string or image URL(optional)
  1620. tag?: string;
  1621. }
  1622. ```
  1623. `activeMatch` is used to match the current route, when the route matches the `activeMatch` rule, the nav item will be highlighted. By default, `activeMatch` is the `link` of the nav item.
  1624. For example:
  1625. ```ts title="rspress.config.ts"
  1626. import { defineConfig } from 'rspress/config';
  1627. export default defineConfig({
  1628. themeConfig: {
  1629. nav: [
  1630. {
  1631. text: 'Home',
  1632. link: '/',
  1633. },
  1634. {
  1635. text: 'Guide',
  1636. link: '/guide/',
  1637. },
  1638. ],
  1639. },
  1640. });
  1641. ```
  1642. Of course, multi-level menus can also be configured in the `nav` array with the following types:
  1643. ```ts
  1644. interface NavGroup {
  1645. text: string;
  1646. // submenu
  1647. items: NavItem[];
  1648. // svg tag string or image URL(optional)
  1649. tag?: string;
  1650. }
  1651. ```
  1652. For example the following configuration:
  1653. ```ts title="rspress.config.ts"
  1654. import { defineConfig } from 'rspress/config';
  1655. export default defineConfig({
  1656. themeConfig: {
  1657. nav: [
  1658. {
  1659. text: 'Home',
  1660. link: '/',
  1661. },
  1662. {
  1663. text: 'Guide',
  1664. items: [
  1665. {
  1666. text: 'Getting Started',
  1667. link: '/guide/getting-started',
  1668. },
  1669. {
  1670. text: 'Advanced',
  1671. link: '/guide/advanced',
  1672. },
  1673. // Also support sub group menu
  1674. {
  1675. text: 'Group',
  1676. items: [
  1677. {
  1678. text: 'Personal',
  1679. link: 'http://example.com/',
  1680. },
  1681. {
  1682. text: 'Company',
  1683. link: 'http://example.com/',
  1684. },
  1685. ],
  1686. },
  1687. ],
  1688. },
  1689. ],
  1690. },
  1691. });
  1692. ```
  1693. ## sidebar
  1694. * Type: `Object`
  1695. The sidebar of the website. The config is an object with the following types:
  1696. ```ts
  1697. // The key is the path of SidebarGroup
  1698. // value is an array of SidebarGroup
  1699. type Sidebar = Record<string, SidebarGroup[]>;
  1700. interface SidebarGroup {
  1701. text: string;
  1702. link?: string;
  1703. items: SidebarItem[];
  1704. // whether to be collapsible
  1705. collapsible?: boolean;
  1706. // Whether to be collapsed by default
  1707. collapsed?: boolean;
  1708. // svg tag string or image URL(optional)
  1709. tag?: string;
  1710. }
  1711. // An object can be filled in, or a string can be filled in
  1712. // When filling in a string, it will be converted into an object internally, the string will be used as a link, and the text value will automatically take the title of the corresponding document
  1713. type SidebarItem =
  1714. | {
  1715. // sidebar text
  1716. text: string;
  1717. // sidebar link
  1718. link: string;
  1719. // svg tag string or image URL(optional)
  1720. tag?: string;
  1721. }
  1722. | string;
  1723. ```
  1724. For example:
  1725. ```ts title="rspress.config.ts"
  1726. import { defineConfig } from 'rspress/config';
  1727. export default defineConfig({
  1728. themeConfig: {
  1729. sidebar: {
  1730. '/guide/': [
  1731. {
  1732. text: 'Getting Started',
  1733. items: [
  1734. // Fill in an object
  1735. {
  1736. text: 'Introduction',
  1737. link: '/guide/getting-started/introduction',
  1738. },
  1739. {
  1740. text: 'Installation',
  1741. link: '/guide/getting-started/installation',
  1742. },
  1743. ],
  1744. },
  1745. {
  1746. text: 'Advanced',
  1747. items: [
  1748. // Fill in the link string directly
  1749. '/guide/advanced/customization',
  1750. '/guide/advanced/markdown',
  1751. ],
  1752. },
  1753. ],
  1754. },
  1755. },
  1756. });
  1757. ```
  1758. ## footer
  1759. * Type: `Object`
  1760. * Default: `{}`
  1761. The footer of the home page.
  1762. The `footer` config is an object of `Footer`, which has the following types:
  1763. ```ts
  1764. export interface Footer {
  1765. message?: string;
  1766. copyright?: string;
  1767. }
  1768. ```
  1769. `message` is a string that can contain HTML content. This string will be inserted into the footer using `dangerouslySetInnerHTML`, allowing you to pass in HTML template tags to design your footer.
  1770. For example:
  1771. ```ts title="rspress.config.ts"
  1772. import { defineConfig } from 'rspress/config';
  1773. export default defineConfig({
  1774. themeConfig: {
  1775. footer: {
  1776. message:
  1777. '<p>This is a footer with a <a href="https://example.com">link</a> and <strong>bold text</strong></p>',
  1778. },
  1779. },
  1780. });
  1781. ```
  1782. ## outlineTitle
  1783. * Type: `string`
  1784. * Default: 'ON THIS PAGE'
  1785. Configure the title of the outline in the outline panel.
  1786. For example:
  1787. ```ts title="rspress.config.ts"
  1788. import { defineConfig } from 'rspress/config';
  1789. export default defineConfig({
  1790. themeConfig: {
  1791. outlineTitle: 'Outline',
  1792. },
  1793. });
  1794. ```
  1795. ## lastUpdated
  1796. * Type: `boolean`
  1797. * Default: `false`
  1798. Whether to display the last update time, it is not displayed by default.
  1799. For example:
  1800. ```ts title="rspress.config.ts"
  1801. import { defineConfig } from 'rspress/config';
  1802. export default defineConfig({
  1803. themeConfig: {
  1804. lastUpdated: true,
  1805. },
  1806. });
  1807. ```
  1808. ## lastUpdatedText
  1809. * Type: `string`
  1810. * Default: `Last Updated`
  1811. The text of the last update time.
  1812. For example:
  1813. ```ts title="rspress.config.ts"
  1814. import { defineConfig } from 'rspress/config';
  1815. export default defineConfig({
  1816. themeConfig: {
  1817. lastUpdatedText: 'Last Updated',
  1818. },
  1819. });
  1820. ```
  1821. ## prevPageText
  1822. * Type: `string`
  1823. * Default: `Previous Page`
  1824. The text of the previous page. for example:
  1825. ```ts title="rspress.config.ts"
  1826. import { defineConfig } from 'rspress/config';
  1827. export default defineConfig({
  1828. themeConfig: {
  1829. prevPageText: 'Previous Page',
  1830. },
  1831. });
  1832. ```
  1833. ## searchPlaceholderText
  1834. * Type: `string`
  1835. * Default: `Search Docs`
  1836. The placeholder text of the search box. For example:
  1837. ```ts title="rspress.config.ts"
  1838. import { defineConfig } from 'rspress/config';
  1839. export default defineConfig({
  1840. themeConfig: {
  1841. searchPlaceholderText: 'Search Docs',
  1842. },
  1843. });
  1844. ```
  1845. ## searchNoResultsText
  1846. * Type: `string`
  1847. * Default: `No results for`
  1848. The text of no search result. For example:
  1849. ```ts title="rspress.config.ts"
  1850. import { defineConfig } from 'rspress/config';
  1851. export default defineConfig({
  1852. themeConfig: {
  1853. searchNoResultsText: 'No results for',
  1854. },
  1855. });
  1856. ```
  1857. ## searchSuggestedQueryText
  1858. * Type: `string`
  1859. * Default: `Please try again with a different keyword`
  1860. The text of suggested query text when no search result. For example:
  1861. ```ts title="rspress.config.ts"
  1862. import { defineConfig } from 'rspress/config';
  1863. export default defineConfig({
  1864. themeConfig: {
  1865. searchSuggestedQueryText: 'Please search again',
  1866. },
  1867. });
  1868. ```
  1869. ## overview
  1870. * Type: `Object`
  1871. The config of overview page/component. The config is an object with the following types:
  1872. ```ts
  1873. interface FilterConfig {
  1874. filterNameText?: string;
  1875. filterPlaceholderText?: string;
  1876. filterNoResultText?: string;
  1877. }
  1878. ```
  1879. For example:
  1880. ```ts title="rspress.config.ts"
  1881. import { defineConfig } from 'rspress/config';
  1882. export default defineConfig({
  1883. themeConfig: {
  1884. overview: {
  1885. filterNameText: 'Filter',
  1886. filterPlaceholderText: 'Enter keyword',
  1887. filterNoResultText: 'No matching API found',
  1888. },
  1889. },
  1890. });
  1891. ```
  1892. ## socialLinks
  1893. * Type: `Array`
  1894. * Default: `[]`
  1895. You can add related links through the following config, such as `github` links, `x` links, etc.
  1896. Related links support four modes: `link mode` `text mode` `image mode` `dom mode`, for example:
  1897. ```ts title="rspress.config.ts"
  1898. import { defineConfig } from 'rspress/config';
  1899. export default defineConfig({
  1900. themeConfig: {
  1901. socialLinks: [
  1902. {
  1903. icon: 'github',
  1904. mode: 'link',
  1905. content: 'https://github.com/sanyuan0704/island.js',
  1906. },
  1907. {
  1908. icon: 'wechat',
  1909. mode: 'text',
  1910. content: 'wechat: foo',
  1911. },
  1912. {
  1913. icon: 'qq',
  1914. mode: 'img',
  1915. content: '/qrcode.png',
  1916. },
  1917. {
  1918. icon: 'github',
  1919. mode: 'dom',
  1920. content:
  1921. '<img loading="lazy" src="https://lf3-static.bytednsdoc.com/obj/eden-cn/rjhwzy/ljhwZthlaukjlkulzlp/rspress/rspress-navbar-logo-0904.png" alt="logo" id="logo" class="mr-4 rspress-logo dark:hidden">',
  1922. },
  1923. ],
  1924. },
  1925. });
  1926. ```
  1927. * When in `link` mode, click the icon to jump to the link.
  1928. * When in `text` mode, when the mouse moves over the icon, a pop-up box will be displayed, and the content of the pop-up box is the entered text
  1929. * When in the `img` mode, moving the mouse over the icon will display a bullet box, and the content of the bullet box is the specified picture. It should be noted that the picture needs to be placed in the `public` directory.
  1930. * When in dom mode, html to render can be passed directly into the content field. Use '' for wrapping
  1931. Related links support the following types of images, which can be selected through the icon attribute:
  1932. ```ts
  1933. export type SocialLinkIcon =
  1934. | 'lark'
  1935. | 'discord'
  1936. | 'facebook'
  1937. | 'github'
  1938. | 'instagram'
  1939. | 'linkedin'
  1940. | 'slack'
  1941. | 'x'
  1942. | 'twitter'
  1943. | 'youtube'
  1944. | 'wechat'
  1945. | 'qq'
  1946. | 'juejin'
  1947. | 'zhihu'
  1948. | 'bilibili'
  1949. | 'weibo'
  1950. | 'gitlab'
  1951. | 'X'
  1952. | { svg: string };
  1953. ```
  1954. If you need to customize the icon, you can pass in an object with `svg attribute`, and the value of svg is the content of the custom icon, for example:
  1955. ```js
  1956. import { defineConfig } from 'rspress/config';
  1957. export default defineConfig({
  1958. themeConfig: {
  1959. socialLinks: [
  1960. {
  1961. icon: {
  1962. svg: '<svg>foo</svg>',
  1963. },
  1964. mode: 'link',
  1965. content: 'https://github.com/',
  1966. },
  1967. ],
  1968. },
  1969. });
  1970. ```
  1971. ## nextPageText
  1972. * Type: `string`
  1973. * Default: `Next Page`
  1974. Text for the next page. for example:
  1975. ```ts title="rspress.config.ts"
  1976. import { defineConfig } from 'rspress/config';
  1977. export default defineConfig({
  1978. themeConfig: {
  1979. nextPageText: 'Next Page',
  1980. },
  1981. });
  1982. ```
  1983. ## locales
  1984. * Type: `Array<LocaleConfig>`
  1985. * Default: `undefined`
  1986. I18n config. This config is an array, and every item of it is `LocaleConfig`, and the types are as follows:
  1987. ```ts
  1988. export interface LocaleConfig {
  1989. /**
  1990. * General locale config for site, which will have a higher priority than `locales`
  1991. */
  1992. // language name
  1993. lang?: string;
  1994. // HTML title, takes precedence over `themeConfig.title
  1995. title?: string;
  1996. // HTML description, takes precedence over `themeConfig.description`
  1997. description?: string;
  1998. // Display text for the corresponding language
  1999. label: string;
  2000. /**
  2001. * Locale config for theme.
  2002. */
  2003. // Right outline title
  2004. outlineTitle?: string;
  2005. // Whether to display the outline title
  2006. outline?: boolean;
  2007. // Whether to display the last update time
  2008. lastUpdated?: boolean;
  2009. // Last update time text
  2010. lastUpdatedText?: string;
  2011. // Previous text
  2012. prevPageText?: string;
  2013. // Next page text
  2014. nextPageText?: string;
  2015. // Search box placeholder text
  2016. searchPlaceholderText?: string;
  2017. // The text of no search result
  2018. searchNoResultsText?: string;
  2019. // The text of suggested query text when no search result
  2020. searchSuggestedQueryText?: string;
  2021. }
  2022. ```
  2023. `LocaleConfig` contains many of the same configuration options as the theme config, but the former will have a higher priority.
  2024. ## darkMode
  2025. * Type: `boolean`
  2026. * Default: `true`
  2027. Whether a Dark/Light mode toggle button appears. for example:
  2028. ```ts title="rspress.config.ts"
  2029. import { defineConfig } from 'rspress/config';
  2030. export default defineConfig({
  2031. themeConfig: {
  2032. darkMode: true,
  2033. },
  2034. });
  2035. ```
  2036. You can also specify the default theme mode through inject global variable into html template, for example:
  2037. ```ts title="rspress.config.ts"
  2038. import { defineConfig } from 'rspress/config';
  2039. export default defineConfig({
  2040. builderConfig: {
  2041. html: {
  2042. tags: [
  2043. {
  2044. tag: 'script',
  2045. // Specify the default theme mode, which can be `dark` or `light`
  2046. children: "window.RSPRESS_THEME = 'dark';",
  2047. },
  2048. ],
  2049. },
  2050. },
  2051. });
  2052. ```
  2053. ## hideNavbar
  2054. * Type: `"always" | "auto" | "never"`
  2055. * Default: `never`
  2056. Control the behavior of the hidden navigation bar. By default, the navigation bar will always display. You can set it to `auto` to **automatically hide when the page scrolls down**, or set it to `always` to hidden it all the time.
  2057. For example:
  2058. ```ts title="rspress.config.ts"
  2059. import { defineConfig } from 'rspress/config';
  2060. export default defineConfig({
  2061. themeConfig: {
  2062. hideNavbar: 'auto',
  2063. },
  2064. });
  2065. ```
  2066. ## enableContentAnimation
  2067. * Type: `boolean`
  2068. * Default: `false`
  2069. Whether there is animation effect when switching between pages. It is implemented with [View Transition API](https://developer.mozilla.org/docs/Web/API/View_Transitions_API). For example:
  2070. > The animation is not configurable for now.
  2071. ```ts title="rspress.config.ts"
  2072. import { defineConfig } from 'rspress/config';
  2073. export default defineConfig({
  2074. themeConfig: {
  2075. enableContentAnimation: true,
  2076. },
  2077. });
  2078. ```
  2079. ## enableAppearanceAnimation
  2080. * Type: `boolean`
  2081. * Default: `false`
  2082. Whether there is animation effect when switching between light and dark theme. It is implemented with [View Transition API](https://developer.mozilla.org/docs/Web/API/View_Transitions_API). For example:
  2083. > The animation is not configurable for now.
  2084. ```ts title="rspress.config.ts"
  2085. import { defineConfig } from 'rspress/config';
  2086. export default defineConfig({
  2087. themeConfig: {
  2088. enableAppearanceAnimation: true,
  2089. },
  2090. });
  2091. ```
  2092. ## search
  2093. * Type: `boolean`
  2094. * Default: `true`
  2095. Whether to display the search box. For example:
  2096. ```ts title="rspress.config.ts"
  2097. import { defineConfig } from 'rspress/config';
  2098. export default defineConfig({
  2099. themeConfig: {
  2100. search: false,
  2101. },
  2102. });
  2103. ```
  2104. ## sourceCodeText
  2105. * Type: `string`
  2106. * Default: `Source`
  2107. The text of the source code button. For example:
  2108. ```ts title="rspress.config.ts"
  2109. import { defineConfig } from 'rspress/config';
  2110. export default defineConfig({
  2111. themeConfig: {
  2112. sourceCodeText: 'Source',
  2113. },
  2114. });
  2115. ```
  2116. ## enableScrollToTop
  2117. * Type: `boolean`
  2118. * Default: `false`
  2119. Enable scroll to top button on documentation. For example:
  2120. ```ts title="rspress.config.ts"
  2121. import { defineConfig } from 'rspress/config';
  2122. export default defineConfig({
  2123. themeConfig: {
  2124. enableScrollToTop: true,
  2125. },
  2126. });
  2127. ```
  2128. ## localeRedirect
  2129. * Type: `'auto' | 'never'`
  2130. * Default: `'auto'`
  2131. Whether to redirect to the locale closest to `window.navigator.language` when the user visits the site, the default is `auto`, which means that the user will be redirected on the first visit. If you set it to `never`, the user will not be redirected. For example:
  2132. ```ts title="rspress.config.ts"
  2133. import { defineConfig } from 'rspress/config';
  2134. export default defineConfig({
  2135. themeConfig: {
  2136. localeRedirect: 'never',
  2137. },
  2138. });
  2139. ```
  2140. ---
  2141. url: /api/core/flow-document.md
  2142. ---
  2143. # FlowDocument
  2144. 流程数据文档 (固定布局), 存储流程的所有节点数据
  2145. [> API Detail](https://flowgram.ai/auto-docs/document/classes/FlowDocument.html)
  2146. ```ts pure
  2147. import { useClientContext } from '@flowgram.ai/fixed-layout-editor'
  2148. const ctx = useClientContext();
  2149. console.log(ctx.document)
  2150. ```
  2151. :::danger
  2152. 对节点的操作最好通过 [ctx.operation](/api/services/flow-operation-service.md) 进行操作, 这样才能绑定到 redo/undo
  2153. :::
  2154. ## root
  2155. 获取画布的根节点,所有节点都挂在根节点下边
  2156. ```ts pure
  2157. console.log(ctx.document.root);
  2158. ```
  2159. ## originTree
  2160. 画布真实的节点树
  2161. ```ts pure
  2162. // 监听节点树的变化,如 节点添加/删除/移动
  2163. const refresh = useRefresh()
  2164. useEffect(() => {
  2165. const toDispose = ctx.document.originTree.onTreeChange(() => {
  2166. // Tree Change
  2167. refresh()
  2168. });
  2169. return () => toDispose.dispose()
  2170. }, [])
  2171. ```
  2172. ## renderTree
  2173. 画布渲染时的节点树,为了提升性能,渲染的树会随着节点分支折叠而变化,并非真实的树
  2174. ## getAllNodes
  2175. 获取所有节点数据
  2176. ```ts pure
  2177. const nodes = ctx.document.getAllNodes();
  2178. ```
  2179. ## getNode
  2180. 通过指定 id 获取节点
  2181. ```ts pure
  2182. ctx.document.getNode('start')
  2183. ```
  2184. ## getNodeRegistry
  2185. 获取节点的定义, 节点定义可以根据业务自己扩展配置项
  2186. ```ts pure
  2187. const startNodeRegistry = ctx.document.getNodeRegistry<FlowNodeRegistry>('start')
  2188. ```
  2189. ## fromJSON/toJSON
  2190. 导入和导出数据
  2191. ```ts pure
  2192. const json = ctx.document.toJSON();
  2193. ctx.document.fromJSON(json);
  2194. ```
  2195. ## registerFlowNodes
  2196. 注册节点的配置项目, 支持继承
  2197. ```ts pure
  2198. const node1: FlowNodeRegistry = {
  2199. type: 'node1',
  2200. meta: {}
  2201. }
  2202. const node2: FlowNodeRegistry = {
  2203. type: 'node2',
  2204. extend: 'node1' // 继承 node1 的配置
  2205. }
  2206. ctx.document.registerFlowNodes(node1, node2)
  2207. ```
  2208. ## addNode
  2209. 添加节点
  2210. ```ts pure
  2211. ctx.document.addNode({
  2212. id: 'node1',
  2213. type: 'start',
  2214. meta: {},
  2215. data: {},
  2216. parent: ctx.document.root // 可以指定父节点
  2217. });
  2218. ```
  2219. ## addFromNode
  2220. 添加到指定节点的后边
  2221. ```ts pure
  2222. ctx.document.addFromNode(
  2223. ctx.document.getNode('start'),
  2224. { id: 'node1', type: 'custom', data: {} }
  2225. );
  2226. ```
  2227. ## addBlock
  2228. 为指定节点添加分支节点
  2229. ```ts pure
  2230. ctx.document.addBlock(ctx.document.getNode('condition'), { id: 'if_1', type: 'block', data: {} })
  2231. ```
  2232. ## removeNode
  2233. 删除节点
  2234. ```ts pure
  2235. ctx.document.removeNode('node1');
  2236. ```
  2237. ## onNodeCreate/onNodeUpdate/onNodeDispose
  2238. 节点创建/更新/销毁事件, 返回事件的注销函数
  2239. ```tsx pure
  2240. useEffect(() => {
  2241. const toDispose1 = ctx.document.onNodeCreate((node) => {
  2242. console.log('onNodeCreate', node);
  2243. });
  2244. const toDispose2 = ctx.document.onNodeUpdate((node) => {
  2245. console.log('onNodeUpdate', node);
  2246. });
  2247. const toDispose3 = ctx.document.onNodeDispose((node) => {
  2248. console.log('onNodeDispose', node);
  2249. });
  2250. return () => {
  2251. toDispose1.dispose()
  2252. toDispose2.dispose()
  2253. toDispose3.dispose()
  2254. }
  2255. }, []);
  2256. ```
  2257. ## traverse
  2258. 从指定节点遍历所有子节点, 默认根节点
  2259. ```ts pure
  2260. /**
  2261. *
  2262. * traverse all nodes, O(n)
  2263. * R
  2264. * |
  2265. * +---1
  2266. * | |
  2267. * | +---1.1
  2268. * | |
  2269. * | +---1.2
  2270. * | |
  2271. * | +---1.3
  2272. * | | |
  2273. * | | +---1.3.1
  2274. * | | |
  2275. * | | +---1.3.2
  2276. * | |
  2277. * | +---1.4
  2278. * |
  2279. * +---2
  2280. * |
  2281. * +---2.1
  2282. *
  2283. * sort: [1, 1.1, 1.2, 1.3, 1.3.1, 1.3.2, 1.4, 2, 2.1]
  2284. */
  2285. ctx.document.traverse((node, depth, index) => {
  2286. console.log(node.id);
  2287. }, ctx.document.root);
  2288. ```
  2289. ## toString
  2290. 返回节点结构的字符串快照
  2291. ```ts pure
  2292. console.log(ctx.document.toString())
  2293. ```
  2294. ---
  2295. url: /api/core/flow-node-entity.md
  2296. ---
  2297. # FlowNodeEntity/WorkflowNodeEntity
  2298. 节点实体,`WorkflowNodeEntity` 为节点别名用于自由布局节点, 节点实体采用 [ECS](/guide/concepts/ecs.md) 架构, 为 `Entity`
  2299. [> API Detail](https://flowgram.ai/auto-docs/document/classes/FlowNodeEntity-1.html)
  2300. ## Properties
  2301. * id: `string` 节点 id
  2302. * flowNodeType: `string` | `number` 节点类型
  2303. * version `number` 节点版本,可以用于判断节点状态是否更新
  2304. ## Accessors
  2305. * document: `FlowDocument | WorkflowDocument` 文档链接
  2306. * bounds: `Rectangle` 获取节点的 x,y,width,height, 等价于 `transform.bounds`
  2307. * blocks: `FlowNodeEntity[]` 获取子节点, 包含折叠的子节点, 等价于 `collapsedChildren`
  2308. * collapsedChildren: `FlowNodeEntity[]` 获取子节点, 包含折叠的子节点
  2309. * allCollapsedChildren: `FlowNodeEntity[]` 获取所有子节点,包括所有折叠的子节点
  2310. * children: `FlowNodeEntity[]` 获取子节点, 不包含折叠的子节点
  2311. * pre: `FlowNodeEntity | undefined` 获取上一个节点
  2312. * next: `FlowNodeEntity | undefined` 获取下一个节点
  2313. * parent: `FlowNodeEntity | undefined` 获取父节点
  2314. * originParent: `FlowNodeEntity | undefined` 获取原始父节点, 这个用于固定布局分支的第一个节点(orderIcon) 找到整个虚拟分支
  2315. * allChildren: `FlowNodeEntity[]` 获取所有子节点, 不包含折叠的子节点
  2316. * transform: [FlowNodeTransformData](https://flowgram.ai/auto-docs/document/classes/FlowNodeTransformData.html) 获取节点的 transform 矩阵数据
  2317. * renderData: [FlowNodeRenderData](https://flowgram.ai/auto-docs/document/classes/FlowNodeRenderData.html) 获取节点的渲染数据, 包含渲染状态等
  2318. ## Methods
  2319. ### getExtInfo
  2320. 获取节点的扩展信息, 可以通过 `updateExtInfo` 更新扩展信息
  2321. ```
  2322. node.getExtInfo<{ test: string }>()
  2323. ```
  2324. ### updateExtInfo
  2325. 更新扩展数据, 更新不会记录到 `redo/undo`, 如果需要记录,请实现 [history](/guide/advanced/history.md) 服务
  2326. ```
  2327. node.updateExtInfo<{ test: string }>({
  2328. test: 'test'
  2329. })
  2330. ```
  2331. ### getNodeRegistry
  2332. 获取节点注册器, 等价于 `ctx.document.getNodeRegistry(node.flowNodeType)`
  2333. ```ts pure
  2334. const nodeRegistry = node.getNodeRegistry<FlowNodeRegistry>()
  2335. ```
  2336. ### getData
  2337. 等价于 [ECS](/guide/concepts/ecs.md) 架构 里获取 Entity 的 Component
  2338. ```ts pure
  2339. node.getData(FlowNodeTransformData) // transform 矩阵数据, 包含节点的 x,y,width,height 等信息
  2340. node.getData(FlowNodeRenderData) // 节点的渲染数据, 包含渲染状态等数据
  2341. node.getData(WorkflowNodeLinesData) // 自由布局的线条数据
  2342. ```
  2343. ### addData
  2344. 等价于 [ECS](/guide/concepts/ecs.md) 架构 里添加 Entity 的 Component
  2345. ```ts pure
  2346. // 自定义 EntityData
  2347. class CustomEntityData extends EntityData<{ key0: string }> {
  2348. static type = 'CustomEntityData';
  2349. getDefaultData() {
  2350. return {
  2351. key0: 'test'
  2352. }
  2353. }
  2354. }
  2355. // 添加 Enitty Component
  2356. node.addData(CustomEntityData)
  2357. // 更新 Entity Component 数据
  2358. node.getData(CustomEntityData).update({ key0: 'new value' })
  2359. ```
  2360. ### getService
  2361. 节点访问 [IOC](/guide/concepts/ioc.md) 服务
  2362. ```ts pure
  2363. node.getService(SelectionService)
  2364. ```
  2365. ### dispose
  2366. 节点从画布中销毁
  2367. ### onDispose
  2368. 节点销毁事件
  2369. ```ts pure
  2370. useEffect(() => {
  2371. const toDispose = node.onDispose(() => {
  2372. console.log('Dispose node')
  2373. })
  2374. return () => toDispose.dispose()
  2375. }, [node])
  2376. ```
  2377. ### toJSON
  2378. 导出节点数据
  2379. :::note 节点数据基本结构:
  2380. * id: `string` 节点唯一标识, 必须保证唯一
  2381. * meta: `object` 节点的 ui 配置信息,如自由布局的 `position` 信息放这里
  2382. * type: `string | number` 节点类型,会和 `nodeRegistries` 中的 `type` 对应
  2383. * data: `object` 节点表单数据, 业务可自定义
  2384. * blocks: `array` 节点的分支, 采用 `block` 更贴近 `Gramming`
  2385. :::
  2386. ---
  2387. url: /api/core/workflow-document.md
  2388. ---
  2389. # WorkflowDocument (free)
  2390. 自由布局文档数据,继承自 [FlowDocument](/api/core/flow-document.md)
  2391. [> API Detail](https://flowgram.ai/auto-docs/free-layout-core/classes/WorkflowDocument.html)
  2392. ```ts pure
  2393. import { useClientContext } from '@flowgram.ai/free-layout-editor'
  2394. const ctx = useClientContext();
  2395. console.log(ctx.document)
  2396. ```
  2397. :::tip
  2398. 由于历史原因, 带 `Workflow` 前缀的都代表自由布局
  2399. :::
  2400. ## linesManager
  2401. 自由布局线条管理,见 [WorkflowLinesManager](/api/core/workflow-lines-manager.md)
  2402. ## createWorkflowNodeByType
  2403. 根据节点类型创建自由布局节点
  2404. ```ts pure
  2405. const node = ctx.document.createWorkflowNodeByType(
  2406. 'custom',
  2407. { x: 100, y: 100 },
  2408. {
  2409. id: 'xxxx',
  2410. data: {}
  2411. }
  2412. )
  2413. ```
  2414. ## onContentChange
  2415. 监听自由布局画布数据变化
  2416. ```ts pure
  2417. export enum WorkflowContentChangeType {
  2418. /**
  2419. * 添加节点
  2420. */
  2421. ADD_NODE = 'ADD_NODE',
  2422. /**
  2423. * 删除节点
  2424. */
  2425. DELETE_NODE = 'DELETE_NODE',
  2426. /**
  2427. * 移动节点
  2428. */
  2429. MOVE_NODE = 'MOVE_NODE',
  2430. /**
  2431. * 节点数据更新 (表单引擎数据 或者 extInfo 数据)
  2432. */
  2433. NODE_DATA_CHANGE = 'NODE_DATA_CHANGE',
  2434. /**
  2435. * 添加线条
  2436. */
  2437. ADD_LINE = 'ADD_LINE',
  2438. /**
  2439. * 删除线条
  2440. */
  2441. DELETE_LINE = 'DELETE_LINE',
  2442. /**
  2443. * 节点Meta信息变更
  2444. */
  2445. META_CHANGE = 'META_CHANGE',
  2446. }
  2447. export interface WorkflowContentChangeEvent {
  2448. type: WorkflowContentChangeType;
  2449. /**
  2450. * 当前触发的元素的json数据,toJSON 需要主动触发
  2451. */
  2452. toJSON: () => any;
  2453. /*
  2454. * 当前的事件的 entity
  2455. */
  2456. entity: WorkflowNodeEntity | WorkflowLineEntity;
  2457. }
  2458. ``
  2459. ```
  2460. ---
  2461. url: /api/core/workflow-lines-manager.md
  2462. ---
  2463. # WorkflowLinesManager (free)
  2464. 自由布局线条管理, 目前挂在自由布局 document 下边
  2465. [> API Detail](https://flowgram.ai/auto-docs/free-layout-core/classes/WorkflowLinesManager.html)
  2466. ```
  2467. import { useClientContext } from '@flowgram.ai/free-layout-editor'
  2468. const ctx = useClientContext();
  2469. console.log(ctx.document.linesManager)
  2470. ```
  2471. ## getAllLines
  2472. 获取所有线条的实体
  2473. ```ts pure
  2474. const allLines = ctx.document.linesManager.getAllLines()
  2475. ```
  2476. ## createLine
  2477. 创建线条
  2478. ```ts pure
  2479. // from和 to 为对应要连线的节点id, fromPort, toPort 为 端口 id, 如果节点为单个端口可以不指定
  2480. const line = ctx.document.linesManager.createLine({ from, to, fromPort, toPort })
  2481. ```
  2482. ## toJSON
  2483. 导出线条数据
  2484. ```ts pure
  2485. const json = ctx.document.linesManager.toJSON()
  2486. ```
  2487. ## onAvailableLinesChange
  2488. 监听所有线条的连线变化
  2489. ```ts pure
  2490. import { useEffect } from 'react'
  2491. import { useClientContext, useRefresh } from '@flowgram.ai/free-layout-editor'
  2492. function SomeReact() {
  2493. const refresh = useRefresh()
  2494. const linesManager = useClientContext().document.linesManager
  2495. useEffect(() => {
  2496. const dispose = linesManager.onAvailableLinesChange(() => refresh())
  2497. return () => dispose.dispose()
  2498. }, [])
  2499. console.log(ctx.document.linesManager.getAllLines())
  2500. }
  2501. ```
  2502. ---
  2503. url: /api/core/workflow-line-entity.md
  2504. ---
  2505. # WorkflowLineEntity (free)
  2506. 自由布局线条实体
  2507. [> API Detail](https://flowgram.ai/auto-docs/free-layout-core/classes/WorkflowLineEntity.html)
  2508. ---
  2509. url: /api/core/playground.md
  2510. ---
  2511. # Playground
  2512. 画布实例
  2513. [> API Detail](https://flowgram.ai/auto-docs/core/classes/Playground.html)
  2514. ```ts pure
  2515. const ctx = useClientContext()
  2516. console.log(ctx.playground)
  2517. ```
  2518. ## config
  2519. 画布配置, 提供 zoom、scroll 等状态
  2520. [> API Detail](https://flowgram.ai/auto-docs/core/classes/PlaygroundConfigEntity.html)
  2521. ### Properties
  2522. * zoom `number` 当前缩放比例
  2523. * scrollData `{ scrollX: number, scrollY: number }` 当前滚动位置
  2524. * readonlyOrDisabled 画布是否为 readonly 或 disabled 状态
  2525. * readonly
  2526. * disabled
  2527. ### fitView
  2528. 节点适应画布窗口, 需要传入节点的 bounds
  2529. ```ts pure
  2530. /**
  2531. * 适应大小
  2532. * @param bounds {Rectangle} 目标大小
  2533. * @param easing {number} 是否开启动画,默认开启
  2534. * @param padding {number} 边界空白
  2535. */
  2536. ctx.playground.config.fitView(ctx.document.root.bounds, true, 10)
  2537. ```
  2538. ### scrollToView
  2539. 指定节点位置并滚动到画布可见区域, 如果位置已经在可见区域则不会滚动,除非加上 `scrollToCenter` 强制滚动
  2540. ```ts pure
  2541. /**
  2542. * 详细参数说明
  2543. * @param opts {PlaygroundConfigRevealOpts}
  2544. **/
  2545. interface PlaygroundConfigRevealOpts {
  2546. entities?: Entity[]
  2547. position?: PositionSchema // 滚动到指定位置,并居中
  2548. bounds?: Rectangle // 滚动的 bounds
  2549. scrollDelta?: PositionSchema
  2550. zoom?: number // 需要缩放的比例
  2551. easing?: boolean // 是否开启缓动,默认开启
  2552. easingDuration?: number // 默认 500 ms
  2553. scrollToCenter?: boolean // 是否强制滚动到中心
  2554. }
  2555. ctx.playground.config.scrollToView({
  2556. bounds: ctx.document.getNode('start').bounds,
  2557. })
  2558. ```
  2559. ### zoomin
  2560. 放大画布
  2561. ### zoomout
  2562. 缩小画布
  2563. ### getPoseFromMouseEvent
  2564. 将浏览器鼠标位置转成画布坐标系
  2565. ```ts pure
  2566. const pos: { x: number, y: number } = ctx.playground.config.getPoseFromMouseEvent(domMouseEvent)
  2567. ```
  2568. ### scroll
  2569. 滚动画布, 需要传入滚动位置, 以及是否平滑滚动, 滚动时间
  2570. ```ts pure
  2571. ctx.playground.config.scroll({ scrollX: 100, scrollY: 100 }, true, 300)
  2572. ```
  2573. ### getViewport
  2574. 获取当前画布的视窗大小
  2575. ```ts pure
  2576. const viewport = ctx.playground.config.getViewport()
  2577. ```
  2578. ---
  2579. url: /api/hooks/use-client-context.md
  2580. ---
  2581. # useClientContext
  2582. 提供在 react 内部访问画布的上下文, 目前固定布局和 自由布局有一定区别
  2583. ## 固定布局
  2584. * Return: [FixedLayoutPluginContext](https://flowgram.ai/auto-docs/fixed-layout-editor/interfaces/FixedLayoutPluginContext.html)
  2585. ```ts pure
  2586. import { useClientContext } from '@flowgram.ai/fixed-layout-editor'
  2587. const ctx = useClientContext()
  2588. console.log(ctx.operation) // FlowOperationService 操作服务
  2589. console.log(ctx.document) // FlowDocument 数据文档
  2590. console.log(ctx.playground) // Playground 画布
  2591. console.log(ctx.history) // HistoryService 历史记录
  2592. console.log(ctx.clipboard) // ClipboardService 剪贴板
  2593. console.log(ctx.container) // Inversify IOC 容器
  2594. console.log(ctx.get(MyService)) // 获取任意的 IOC 模块,详细见 自定义 Service
  2595. ```
  2596. ## 自由布局
  2597. * Return: [FreeLayoutPluginContext](https://flowgram.ai/auto-docs/free-layout-editor/interfaces/FreeLayoutPluginContext.html)
  2598. ```ts pure
  2599. import { useClientContext } from '@flowgram.ai/free-layout-editor'
  2600. const ctx = useClientContext()
  2601. console.log(ctx.document) // WorkflowDocument 数据文档
  2602. console.log(ctx.playground) // Playground 画布
  2603. console.log(ctx.history) // HistoryService 历史记录
  2604. console.log(ctx.selection) // SelectionService 选择器服务
  2605. console.log(ctx.clipboard) // ClipboardService 剪贴板
  2606. console.log(ctx.container) // Inversify IOC 容器
  2607. console.log(ctx.get(MyService)) // 获取任意的 IOC 模块,详细见 自定义 Service
  2608. ```
  2609. ---
  2610. url: /api/hooks/use-node-render.md
  2611. ---
  2612. # useNodeRender
  2613. 提供节点渲染相关的方法, 返回结果的 form 等价于 [getNodeForm](/api/utils/get-node-form.md)
  2614. ## 固定布局
  2615. * Return: [NodeRenderReturnType](https://flowgram.ai/auto-docs/fixed-layout-editor/interfaces/NodeRenderReturnType.html)
  2616. ```tsx pure
  2617. import { FlowNodeEntity, useNodeRender } from '@flowgram.ai/fixed-layout-editor';
  2618. export const BaseNode = ({ node }: { node: FlowNodeEntity }) => {
  2619. /**
  2620. * 提供节点渲染相关的方法
  2621. */
  2622. const nodeRender = useNodeRender();
  2623. /**
  2624. * 只有在节点引擎开启时候才能使用表单
  2625. */
  2626. const form = nodeRender.form;
  2627. return (
  2628. <div
  2629. className="demo-fixed-node"
  2630. /*
  2631. * onMouseEnter 加到固定布局节点主要是为了监听 分支线条的 hover 高亮
  2632. **/
  2633. onMouseEnter={nodeRender.onMouseEnter}
  2634. onMouseLeave={nodeRender.onMouseLeave}
  2635. onMouseDown={e => {
  2636. // trigger drag node
  2637. nodeRender.startDrag(e);
  2638. e.stopPropagation();
  2639. }}
  2640. style={{
  2641. /**
  2642. * 用于精确控制分支节点的样式
  2643. * isBlockIcon: 整个 condition 分支的 头部节点
  2644. * isBlockOrderIcon: 分支的第一个节点
  2645. */
  2646. ...(nodeRender.isBlockOrderIcon || nodeRender.isBlockIcon ? { width: 260 } : {}),
  2647. }}
  2648. >
  2649. {form?.render()}
  2650. </div>
  2651. );
  2652. };
  2653. ```
  2654. ## 自由布局
  2655. * Return: [NodeRenderReturnType](https://flowgram.ai/auto-docs/free-layout-core/interfaces/NodeRenderReturnType.html)
  2656. ```tsx pure
  2657. import { WorkflowNodeRenderer, useNodeRender } from '@flowgram.ai/free-layout-editor';
  2658. export const BaseNode = () => {
  2659. const { form, node } = useNodeRender()
  2660. return (
  2661. <WorkflowNodeRenderer className="demo-free-node" node={node}>
  2662. {form?.render()}
  2663. </WorkflowNodeRenderer>
  2664. )
  2665. }
  2666. ```
  2667. ---
  2668. url: /api/hooks/use-playground-tools.md
  2669. ---
  2670. # usePlaygroundTools
  2671. 画布工具方法
  2672. ## 固定布局
  2673. * Return: [PlaygroundTools](https://flowgram.ai/auto-docs/fixed-layout-editor/interfaces/PlaygroundTools.html)
  2674. ```tsx pure
  2675. import { useEffect, useState } from 'react'
  2676. import { usePlaygroundTools, useClientContext } from '@flowgram.ai/fixed-layout-editor';
  2677. export function Tools() {
  2678. const { history } = useClientContext();
  2679. const tools = usePlaygroundTools();
  2680. const [canUndo, setCanUndo] = useState(false);
  2681. const [canRedo, setCanRedo] = useState(false);
  2682. useEffect(() => {
  2683. const disposable = history.undoRedoService.onChange(() => {
  2684. setCanUndo(history.canUndo());
  2685. setCanRedo(history.canRedo());
  2686. });
  2687. return () => disposable.dispose();
  2688. }, [history]);
  2689. return <div style={{ position: 'absolute', zIndex: 10, bottom: 16, left: 16, display: 'flex', gap: 8 }}>
  2690. <button onClick={() => tools.zoomin()}>ZoomIn</button>
  2691. <button onClick={() => tools.zoomout()}>ZoomOut</button>
  2692. <button onClick={() => tools.fitView()}>Fitview</button>
  2693. <button onClick={() => tools.changeLayout()}>ChangeLayout</button>
  2694. <button onClick={() => history.undo()} disabled={!canUndo}>Undo</button>
  2695. <button onClick={() => history.redo()} disabled={!canRedo}>Redo</button>
  2696. <span>{Math.floor(tools.zoom * 100)}%</span>
  2697. </div>
  2698. }
  2699. ```
  2700. ## 自由布局
  2701. * Return: [PlaygroundTools](https://flowgram.ai/auto-docs/free-layout-editor/interfaces/PlaygroundTools.html)
  2702. ```tsx pure
  2703. import { usePlaygroundTools, useClientContext } from '@flowgram.ai/free-layout-editor';
  2704. export function Tools() {
  2705. const { history } = useClientContext();
  2706. const tools = usePlaygroundTools();
  2707. const [canUndo, setCanUndo] = useState(false);
  2708. const [canRedo, setCanRedo] = useState(false);
  2709. useEffect(() => {
  2710. const disposable = history.undoRedoService.onChange(() => {
  2711. setCanUndo(history.canUndo());
  2712. setCanRedo(history.canRedo());
  2713. });
  2714. return () => disposable.dispose();
  2715. }, [history]);
  2716. return <div style={{ position: 'absolute', zIndex: 10, bottom: 16, left: 226, display: 'flex', gap: 8 }}>
  2717. <button onClick={() => tools.zoomin()}>ZoomIn</button>
  2718. <button onClick={() => tools.zoomout()}>ZoomOut</button>
  2719. <button onClick={() => tools.fitView()}>Fitview</button>
  2720. <button onClick={() => tools.autoLayout()}>AutoLayout</button>
  2721. <button onClick={() => history.undo()} disabled={!canUndo}>Undo</button>
  2722. <button onClick={() => history.redo()} disabled={!canRedo}>Redo</button>
  2723. <span>{Math.floor(tools.zoom * 100)}%</span>
  2724. </div>
  2725. }
  2726. ```
  2727. ---
  2728. url: /api/hooks/use-refresh.md
  2729. ---
  2730. # useRefresh
  2731. ## Source Code
  2732. ```ts
  2733. import { useCallback, useState } from 'react';
  2734. export function useRefresh(defaultValue?: any): (v?: any) => void {
  2735. const [, update] = useState<any>(defaultValue);
  2736. return useCallback((v?: any) => update(v !== undefined ? v : {}), []);
  2737. }
  2738. ```
  2739. ## Usage
  2740. ```tsx pure
  2741. import { useRefresh } from '@flowgram.ai/fixed-layout-editor';
  2742. function Demo() {
  2743. const refresh = useRefresh();
  2744. return (
  2745. <div>
  2746. <button onClick={() => refresh()}>Refresh</button>
  2747. </div>
  2748. )
  2749. }
  2750. ```
  2751. ---
  2752. url: /api/hooks/use-service.md
  2753. ---
  2754. # useService
  2755. 获取底层 [IOC](/guide/concepts/ioc.md) 的所有单例模块
  2756. ```ts pure
  2757. const playground = useService<Playground>(Playground)
  2758. const flowDocument = useService<FlowDocument>(FlowDocument)
  2759. const historyService = useService<HistoryService>(HistoryService)
  2760. // 等价
  2761. const playground1 = useClientContext().playground
  2762. // 等价
  2763. const playground3 = useClientContext().get<Playground>(Playground)
  2764. ```
  2765. ## 自定义 Service
  2766. ```tsx pure
  2767. /**
  2768. * inversify: https://github.com/inversify/InversifyJS
  2769. */
  2770. import { injectable, inject } from 'inversify'
  2771. import { FlowDocument } from '@flowgram.ai/fixed-layout-editor'
  2772. @injectable()
  2773. class MyService {
  2774. // 依赖注入单例模块
  2775. @inject(FlowDocument) flowDocument: FlowDocument
  2776. // ...
  2777. }
  2778. import { useMemo } from 'react';
  2779. import { type FixedLayoutProps } from '@flowgram.ai/fixed-layout-editor';
  2780. function BaseNode() {
  2781. const mySerivce = useService<MyService>(MyService)
  2782. }
  2783. export function useEditorProps(
  2784. ): FixedLayoutProps {
  2785. return useMemo<FixedLayoutProps>(
  2786. () => ({
  2787. // ....other props
  2788. onBind: ({ bind }) => {
  2789. bind(MyService).toSelf().inSingletonScope()
  2790. },
  2791. materials: {
  2792. renderDefaultNode: BaseNode
  2793. }
  2794. }),
  2795. [],
  2796. );
  2797. }
  2798. ```
  2799. ---
  2800. url: /api/components/editor-renderer.md
  2801. ---
  2802. # EditorRenderer
  2803. 画布渲染组件,需要 配合 `FixedLayoutEditorProvider` 或 `FreeLayoutEditorProvider` 使用
  2804. ```tsx pure
  2805. function App() {
  2806. return (
  2807. <FixedLayoutEditorProvider {...editorProps}>
  2808. <EditorRenderer className="demo-editor" style={{ /* style */}}>
  2809. {/* 如果提供 children,该内容会放到 画布 div 下边 */}
  2810. </EditorRenderer>
  2811. </FixedLayoutEditorProvider>
  2812. )
  2813. }
  2814. ```
  2815. ---
  2816. url: /api/components/fixed-layout-editor-provider.md
  2817. ---
  2818. # FixedLayoutEditorProvider
  2819. 固定布局画布配置器,支持 ref
  2820. ```tsx pure
  2821. import { FixedLayoutEditorProvider, FixedLayoutPluginContext, EditorRenderer } from '@flowgram.ai/fixed-layout-editor'
  2822. function App() {
  2823. const ref = useRef<FixedLayoutPluginContext | undefined>();
  2824. useEffect(() => {
  2825. console.log(ref.current.document.toJSON())
  2826. }, [])
  2827. return (
  2828. <FixedLayoutEditorProvider {...editorProps} ref={ref}>
  2829. <EditorRenderer className="demo-editor" />
  2830. </FixedLayoutEditorProvider>
  2831. )
  2832. }
  2833. ```
  2834. ---
  2835. url: /api/components/fixed-layout-editor.md
  2836. ---
  2837. # FixedLayoutEditor
  2838. 固定布局画布, 等价于 `FixedLayoutEditorProvider` 和 `EditorRenderer` 的组合
  2839. ```tsx pure
  2840. import { FixedLayoutEditor, FixedLayoutPluginContext } from '@flowgram.ai/fixed-layout-editor'
  2841. function App() {
  2842. const ref = useRef<FixedLayoutPluginContext | undefined>();
  2843. useEffect(() => {
  2844. console.log(ref.current.document.toJSON())
  2845. }, [])
  2846. return (
  2847. <FixedLayoutEditor className="demo-editor" {...editorProps} ref={ref} />
  2848. )
  2849. }
  2850. ```
  2851. ---
  2852. url: /api/components/free-layout-editor-provider.md
  2853. ---
  2854. # FreeLayoutEditorProvider
  2855. 自由布局画布配置器,支持 ref
  2856. ```tsx pure
  2857. import { FreeLayoutEditorProvider, FreeLayoutPluginContext, EditorRenderer } from '@flowgram.ai/free-layout-editor'
  2858. function App() {
  2859. const ref = useRef<FreeLayoutPluginContext | undefined>();
  2860. useEffect(() => {
  2861. console.log(ref.current.document.toJSON())
  2862. }, [])
  2863. return (
  2864. <FreeLayoutEditorProvider {...editorProps} ref={ref}>
  2865. <EditorRenderer className="demo-editor" />
  2866. </FreeLayoutEditorProvider>
  2867. )
  2868. }
  2869. ```
  2870. ---
  2871. url: /api/components/free-layout-editor.md
  2872. ---
  2873. # FreeLayoutEditor
  2874. 自由布局画布, 等价于 `FreeLayoutEditorProvider` 和 `EditorRenderer` 的组合
  2875. ```tsx pure
  2876. import { FreeLayoutEditor, FreeLayoutPluginContext } from '@flowgram.ai/free-layout-editor'
  2877. function App() {
  2878. const ref = useRef<FreeLayoutPluginContext | undefined>();
  2879. useEffect(() => {
  2880. console.log(ref.current.document.toJSON())
  2881. }, [])
  2882. return (
  2883. <FreeLayoutEditor className="demo-editor" {...editorProps} ref={ref} />
  2884. )
  2885. }
  2886. ```
  2887. ---
  2888. url: /api/components/workflow-node-renderer.md
  2889. ---
  2890. # WorkflowNodeRenderer(free)
  2891. 自由布局节点容器
  2892. ## Usage
  2893. ```tsx pure
  2894. import { useNodeRender, WorkflowNodeRenderer } from '@flowgram.ai/free-layout-editor';
  2895. export const BaseNode = () => {
  2896. /**
  2897. * 提供节点渲染相关的方法
  2898. */
  2899. const { form } = useNodeRender()
  2900. /**
  2901. * WorkflowNodeRenderer 会添加节点拖拽事件及 端口渲染,如果要深度定制,可以看该组件源代码:
  2902. * https://github.com/bytedance/flowgram.ai/blob/main/packages/client/free-layout-editor/src/components/workflow-node-renderer.tsx
  2903. */
  2904. return (
  2905. <WorkflowNodeRenderer className="demo-free-node" node={props.node}>
  2906. {
  2907. // 表单渲染通过 formMeta 生成
  2908. form?.render()
  2909. }
  2910. </WorkflowNodeRenderer>
  2911. )
  2912. };
  2913. ```
  2914. ---
  2915. url: /api/services/clipboard-service.md
  2916. ---
  2917. # ClipboardService
  2918. 剪贴板服务
  2919. [> API Detail](https://flowgram.ai/auto-docs/core/interfaces/ClipboardService.html)
  2920. ---
  2921. url: /api/services/command-service.md
  2922. ---
  2923. # CommandService
  2924. 指令服务,需要和 [Shortcuts](/guide/advanced/interactive/shortcuts.md) 一起使用
  2925. [> API Detail](https://flowgram.ai/auto-docs/command/interfaces/CommandService.html)
  2926. ```Usage
  2927. ctx.get(CommandService).execCommand('selectAll')
  2928. ```
  2929. ---
  2930. url: /api/services/flow-operation-service.md
  2931. ---
  2932. # FlowOperationService
  2933. 节点操作服务, 目前用于固定布局,自由布局现阶段可通过 WorkflowDocument 直接操作, 后续也会抽象出 operation
  2934. [> API Detail](https://flowgram.ai/auto-docs/fixed-layout-editor/interfaces/FlowOperationService.html)
  2935. ```typescript pure
  2936. const operationService = useService<FlowOperationService>(FlowOperationService)
  2937. operationService.addNode({ id: 'xxx', type: 'custom', data: {} })
  2938. // or
  2939. const ctx = useClientContext();
  2940. ctx.operation.addNode({ id: 'xxx', type: 'custom', data: {} })
  2941. ```
  2942. ## Interface
  2943. ```typescript pure
  2944. export interface FlowOperationBaseService extends Disposable {
  2945. /**
  2946. * 执行操作
  2947. * @param operation 可序列化的操作
  2948. * @returns 操作返回
  2949. */
  2950. apply(operation: FlowOperation): any;
  2951. /**
  2952. * 添加节点,如果节点已经存在则不会重复创建
  2953. * @param nodeJSON 节点数据
  2954. * @param config 配置
  2955. * @returns 成功添加的节点
  2956. */
  2957. addNode(nodeJSON: FlowNodeJSON, config?: AddNodeConfig): FlowNodeEntity;
  2958. /**
  2959. * 基于某一个起始节点往后面添加
  2960. * @param fromNode 起始节点
  2961. * @param nodeJSON 添加的节点JSON
  2962. */
  2963. addFromNode(fromNode: FlowNodeEntityOrId, nodeJSON: FlowNodeJSON): FlowNodeEntity;
  2964. /**
  2965. * 删除节点
  2966. * @param node 节点
  2967. * @returns
  2968. */
  2969. deleteNode(node: FlowNodeEntityOrId): void;
  2970. /**
  2971. * 批量删除节点
  2972. * @param nodes
  2973. */
  2974. deleteNodes(nodes: FlowNodeEntityOrId[]): void;
  2975. /**
  2976. * 添加块(分支)
  2977. * @param target 目标
  2978. * @param blockJSON 块数据
  2979. * @param config 配置
  2980. * @returns
  2981. */
  2982. addBlock(
  2983. target: FlowNodeEntityOrId,
  2984. blockJSON: FlowNodeJSON,
  2985. config?: AddBlockConfig,
  2986. ): FlowNodeEntity;
  2987. /**
  2988. * 移动节点
  2989. * @param node 被移动的节点
  2990. * @param config 移动节点配置
  2991. */
  2992. moveNode(node: FlowNodeEntityOrId, config?: MoveNodeConfig): void;
  2993. /**
  2994. * 拖拽节点
  2995. * @param param0
  2996. * @returns
  2997. */
  2998. dragNodes({ dropNode, nodes }: { dropNode: FlowNodeEntity; nodes: FlowNodeEntity[] }): void;
  2999. /**
  3000. * 添加节点的回调
  3001. */
  3002. onNodeAdd: Event<OnNodeAddEvent>;
  3003. }
  3004. export interface FlowOperationService extends FlowOperationBaseService {
  3005. /**
  3006. * 创建分组
  3007. * @param nodes 节点列表
  3008. */
  3009. createGroup(nodes: FlowNodeEntity[]): FlowNodeEntity | undefined;
  3010. /**
  3011. * 取消分组
  3012. * @param groupNode
  3013. */
  3014. ungroup(groupNode: FlowNodeEntity): void;
  3015. /**
  3016. * 开始事务
  3017. */
  3018. startTransaction(): void;
  3019. /**
  3020. * 结束事务
  3021. */
  3022. endTransaction(): void;
  3023. /**
  3024. * 修改表单数据
  3025. * @param node 节点
  3026. * @param path 属性路径
  3027. * @param value 值
  3028. */
  3029. setFormValue(node: FlowNodeEntityOrId, path: string, value: unknown): void;
  3030. }
  3031. ```
  3032. ---
  3033. url: /api/services/history-service.md
  3034. ---
  3035. ## HistoryService
  3036. [> API Detail](https://flowgram.ai/auto-docs/fixed-history-plugin/classes/HistoryService.html)
  3037. ## Redo/Undo
  3038. ```tsx pure
  3039. import { useEffect, useState } from 'react'
  3040. import { useClientContext } from '@flowgram.ai/fixed-layout-editor';
  3041. export function Tools() {
  3042. const { history } = useClientContext();
  3043. const [canUndo, setCanUndo] = useState(false);
  3044. const [canRedo, setCanRedo] = useState(false);
  3045. useEffect(() => {
  3046. const disposable = history.undoRedoService.onChange(() => {
  3047. setCanUndo(history.canUndo());
  3048. setCanRedo(history.canRedo());
  3049. });
  3050. return () => disposable.dispose();
  3051. }, [history]);
  3052. return <div>
  3053. <button onClick={() => history.undo()} disabled={!canUndo}>Undo</button>
  3054. <button onClick={() => history.redo()} disabled={!canRedo}>Redo</button>
  3055. </div>
  3056. }
  3057. ```
  3058. ## 渲染历史记录
  3059. ```tsx pure
  3060. import { useEffect } from 'react'
  3061. import { useRefresh, useClientContext } from '@flowgram.ai/fixed-layout-editor'
  3062. function HistoryListRender() {
  3063. const refresh = useRefresh()
  3064. const ctx = useClientContext()
  3065. useEffect(() => {
  3066. ctx.history.onApply(() => refresh())
  3067. }, [ctx])
  3068. return (
  3069. <div>
  3070. {ctx.history.historyManager.historyStack.items.map((record) => <HistoryOperations key={record.id} operations={record.operations} />)}
  3071. </div>
  3072. )
  3073. }
  3074. ```
  3075. ---
  3076. url: /api/services/selection-service.md
  3077. ---
  3078. # SelectionService
  3079. 用于控制选择的节点
  3080. [> API Detail](https://flowgram.ai/auto-docs/core/classes/SelectionService.html)
  3081. ## Usage
  3082. ```tsx pure
  3083. // Listen Selection Change
  3084. ctx.selection.onSelectionChanged((nodes) => {
  3085. })
  3086. // Select All Nodes
  3087. ctx.selection.selection = ctx.document.getAllNodes()
  3088. ```
  3089. ---
  3090. url: /api/utils/disposable-collection.md
  3091. ---
  3092. # DisposableCollection
  3093. ## Usage
  3094. ```ts pure
  3095. import { DisposableCollection, Disposable } from '@flowgram.ai/utils'
  3096. const disposable1: Disposable = {
  3097. dispose() {
  3098. console.log(1)
  3099. },
  3100. };
  3101. const disposable2: Disposable = {
  3102. dispose() {
  3103. console.log(2)
  3104. },
  3105. };
  3106. const dc = new DisposableCollection();
  3107. dc.onDispose(() => {
  3108. console.log('end')
  3109. });
  3110. dc.pushAll([disposable1, disposable2]);
  3111. dc.dispose(); // Log: 1, 2, dispose end
  3112. ```
  3113. ## Source Code
  3114. https://github.com/bytedance/flowgram.ai/blob/main/packages/common/utils/src/disposable.ts
  3115. ---
  3116. url: /api/utils/disposable.md
  3117. ---
  3118. # Disposable
  3119. ## Interface
  3120. ```ts
  3121. /**
  3122. * An object that performs a cleanup operation when `.dispose()` is called.
  3123. *
  3124. * Some examples of how disposables are used:
  3125. *
  3126. * - An event listener that removes itself when `.dispose()` is called.
  3127. * - The return value from registering a provider. When `.dispose()` is called, the provider is unregistered.
  3128. */
  3129. export interface Disposable {
  3130. dispose(): void;
  3131. }
  3132. ```
  3133. ## Source Code
  3134. https://github.com/bytedance/flowgram.ai/blob/main/packages/common/utils/src/disposable.ts
  3135. ---
  3136. url: /api/utils/emitter.md
  3137. ---
  3138. # Emitter
  3139. 事件模块
  3140. ## Usage
  3141. ```tsx pure
  3142. import { Emitter } from '@flowgram.ai/utils'
  3143. class Doc {
  3144. private _content = ''
  3145. private _onContentChangeEmitter = new Emitter<string>()
  3146. readonly onContentChange = this._onContentChangeEmitter.event
  3147. setContent(content: string) {
  3148. this._content = content
  3149. this._onContentChangeEmitter.fire(content)
  3150. }
  3151. get content() {
  3152. return this._content
  3153. }
  3154. }
  3155. function App() {
  3156. const doc1 = useMemo(() => new Doc(), [])
  3157. const [content, updateContent] = useState(doc1.content)
  3158. useEffect(() => {
  3159. const toDispose = doc1.onContentChange((content) => {
  3160. updateContent(content)
  3161. })
  3162. return () => toDispose.dispose()
  3163. }, [doc1])
  3164. return <div>{content}</div>
  3165. }
  3166. ```
  3167. ## Source Code
  3168. https://github.com/bytedance/flowgram.ai/blob/main/packages/common/utils/src/event.ts
  3169. ---
  3170. url: /api/utils/get-node-form.md
  3171. ---
  3172. # getNodeForm
  3173. 获取节点的表单能力,需要开启 节点引擎才能使用
  3174. [> API Detail](https://flowgram.ai/auto-docs/editor/functions/getNodeForm.html)
  3175. ## Usage
  3176. ```tsx pure
  3177. // 1. BaseNode
  3178. function BaseNode({ node }) {
  3179. const form = getNodeForm(node);
  3180. console.log(form.getValueIn('title'))
  3181. return <div>{form?.render()}</div>
  3182. }
  3183. // 2. useNodeRender
  3184. function BaseNode() {
  3185. const { form } = useNodeRender();
  3186. console.log(form.getValueIn('title'))
  3187. return <div>{form?.render()}</div>
  3188. }
  3189. ```
  3190. ## Return Inteface
  3191. ```ts pure
  3192. export interface NodeFormProps<TValues> {
  3193. /**
  3194. * The initialValues of the form.
  3195. */
  3196. initialValues: TValues;
  3197. /**
  3198. * Form values. Returns a deep copy of the data in the store.
  3199. */
  3200. values: TValues;
  3201. /**
  3202. * Form state
  3203. */
  3204. state: FormState;
  3205. /**
  3206. * Get value in certain path
  3207. * @param name path
  3208. */
  3209. getValueIn<TValue = FieldValue>(name: FieldName): TValue;
  3210. /**
  3211. * Set value in certain path.
  3212. * It will trigger the re-rendering of the Field Component if a Field is related to this path
  3213. * @param name path
  3214. */
  3215. setValueIn<TValue>(name: FieldName, value: TValue): void;
  3216. /**
  3217. * set form values
  3218. */
  3219. updateFormValues(values: any): void;
  3220. /**
  3221. * Render form
  3222. */
  3223. render: () => React.ReactNode;
  3224. /**
  3225. * Form value change event
  3226. */
  3227. onFormValuesChange: Event<OnFormValuesChangePayload>;
  3228. /**
  3229. * Trigger form validate
  3230. */
  3231. validate: () => Promise<boolean>;
  3232. /**
  3233. * Form validate event
  3234. */
  3235. onValidate: Event<FormState>;
  3236. /**
  3237. * Form field value change event
  3238. */
  3239. onFormValueChangeIn<TValue = FieldValue, TFormValue = FieldValue>(
  3240. name: FieldName,
  3241. callback: (payload: onFormValueChangeInPayload<TValue, TFormValue>) => void
  3242. ): Disposable;
  3243. }
  3244. ```
  3245. ---
  3246. url: /guide/advanced/custom-plugin.md
  3247. ---
  3248. # 自定义插件
  3249. ## 插件的生命周期说明
  3250. ```tsx pure
  3251. /**
  3252. * from: https://github.com/bytedance/flowgram.ai/blob/main/packages/canvas-engine/core/src/plugin/plugin.ts
  3253. */
  3254. import { ContainerModule, interfaces } from 'inversify';
  3255. export interface PluginBindConfig {
  3256. bind: interfaces.Bind;
  3257. unbind: interfaces.Unbind;
  3258. isBound: interfaces.IsBound;
  3259. rebind: interfaces.Rebind;
  3260. }
  3261. export interface PluginConfig<Opts, CTX extends PluginContext = PluginContext> {
  3262. /**
  3263. * 插件 IOC 注册, 等价于 containerModule
  3264. * @param ctx
  3265. */
  3266. onBind?: (bindConfig: PluginBindConfig, opts: Opts) => void;
  3267. /**
  3268. * 画布注册阶段
  3269. */
  3270. onInit?: (ctx: CTX, opts: Opts) => void;
  3271. /**
  3272. * 画布准备阶段,一般用于 dom 事件注册等
  3273. */
  3274. onReady?: (ctx: CTX, opts: Opts) => void;
  3275. /**
  3276. * 画布销毁阶段
  3277. */
  3278. onDispose?: (ctx: CTX, opts: Opts) => void;
  3279. /**
  3280. * 画布所有 layer 渲染结束
  3281. */
  3282. onAllLayersRendered?: (ctx: CTX, opts: Opts) => void;
  3283. /**
  3284. * IOC 模块,用于更底层的插件扩展
  3285. */
  3286. containerModules?: interfaces.ContainerModule[];
  3287. }
  3288. ```
  3289. ## 创建插件
  3290. ```tsx pure
  3291. /**
  3292. * 如果希望插件固定布局和自由布局都能使用, 请只用
  3293. * import { definePluginCreator } from '@flowgram.ai/core'
  3294. */
  3295. import { definePluginCreator, FixedLayoutPluginContext } from '@flowgram.ai/fixed-layout-editor'
  3296. export interface MyPluginOptions {
  3297. opt1: string;
  3298. }
  3299. export const createMyPlugin = definePluginCreator<MyPluginOptions, FixedLayoutPluginContext>({
  3300. onBind: (bindConfig, opts) => {
  3301. // 注册 IOC 模块, Service 如何定义 见 自定义 Service
  3302. bindConfig.bind(MyService).toSelf().inSingletonScope()
  3303. },
  3304. onInit: (ctx, opts) => {
  3305. // 插件配置
  3306. console.log(opts.opt1)
  3307. // ctx 对应 FixedLayoutPluginContext 或者 FreeLayoutPluginContext
  3308. console.log(ctx.document)
  3309. console.log(ctx.playground)
  3310. console.log(ctx.get<MyService>(MyService)) // 获取 IOC 模块
  3311. },
  3312. });
  3313. ```
  3314. ## 添加插件
  3315. ```tsx pure title="use-editor-props.ts"
  3316. // EditorProps
  3317. {
  3318. plugins: () => [
  3319. createMyPlugin({
  3320. opt1: 'xxx'
  3321. })
  3322. ]
  3323. }
  3324. ```
  3325. ---
  3326. url: /guide/advanced/custom-service.md
  3327. ---
  3328. # 自定义 Service
  3329. 业务中需要抽象出单例服务便于插件化管理
  3330. ```tsx pure
  3331. import { useMemo } from 'react';
  3332. import { FlowDocument, type FixedLayoutProps, inject, injectable } from '@flowgram.ai/fixed-layout-editor'
  3333. /**
  3334. * Docs: https://inversify.io/docs/introduction/getting-started/
  3335. * Warning: Use decorator legacy
  3336. * // rsbuild.config.ts
  3337. * {
  3338. * source: {
  3339. * decorators: {
  3340. * version: 'legacy'
  3341. * }
  3342. * }
  3343. * }
  3344. * Usage:
  3345. * 1.
  3346. * const myService = useService(MyService)
  3347. * myService.save()
  3348. * 2.
  3349. * const myService = useClientContext().get(MyService)
  3350. * 3.
  3351. * const myService = node.getService(MyService)
  3352. */
  3353. @injectable()
  3354. class MyService {
  3355. // 依赖注入单例模块
  3356. @inject(FlowDocument) flowDocument: FlowDocument
  3357. // ...
  3358. }
  3359. function BaseNode() {
  3360. const mySerivce = useService<MyService>(MyService)
  3361. }
  3362. export function useEditorProps(
  3363. ): FixedLayoutProps {
  3364. return useMemo<FixedLayoutProps>(
  3365. () => ({
  3366. // ....other props
  3367. onBind: ({ bind }) => {
  3368. bind(MyService).toSelf().inSingletonScope()
  3369. },
  3370. materials: {
  3371. renderDefaultNode: BaseNode
  3372. }
  3373. }),
  3374. [],
  3375. );
  3376. }
  3377. ```
  3378. ---
  3379. url: /guide/advanced/fixed-layout/composite-nodes.md
  3380. ---
  3381. # 复合节点
  3382. 复合节点由多个节点组合,并支持自定义线条,如 分支节点、Loop 节点、TryCatch 节点:
  3383. ## 使用
  3384. ```ts pure title="node-registries.ts"
  3385. import { FlowNodeRegistry } from '@flowgram.ai/fixed-layout-editor';
  3386. /**
  3387. * 节点注册
  3388. */
  3389. export const nodeRegistries: FlowNodeRegistry[] = [
  3390. {
  3391. type: 'yourCustomNodeType',
  3392. extend: 'dynamicSplit',
  3393. },
  3394. ];
  3395. ```
  3396. ## 内置的复合节点
  3397. Source Code
  3398. ---
  3399. url: /guide/advanced/fixed-layout/load.md
  3400. ---
  3401. # 加载与保存
  3402. 画布的数据通过 [FlowDocument](/api/core/flow-document.md) 来存储
  3403. ## 画布数据格式
  3404. 画布文档数据采用树形结构,支持嵌套
  3405. :::note 文档数据基本结构:
  3406. * nodes `array` 节点列表, 支持嵌套
  3407. :::
  3408. :::note 节点数据基本结构:
  3409. * id: `string` 节点唯一标识, 必须保证唯一
  3410. * meta: `object` 节点的 ui 配置信息,如自由布局的 `position` 信息放这里
  3411. * type: `string | number` 节点类型,会和 `nodeRegistries` 中的 `type` 对应
  3412. * data: `object` 节点表单数据
  3413. * blocks: `array` 节点的分支, 采用 `block` 更贴近 `Gramming`
  3414. :::
  3415. ```tsx pure title="initial-data.tsx"
  3416. import { FlowDocumentJSON } from '@flowgram.ai/fixed-layout-editor';
  3417. /**
  3418. * 配置流程数据,数据为 blocks 嵌套的格式
  3419. */
  3420. export const initialData: FlowDocumentJSON = {
  3421. nodes: [
  3422. // 开始节点
  3423. {
  3424. id: 'start_0',
  3425. type: 'start',
  3426. data: {
  3427. title: 'Start',
  3428. content: 'start content'
  3429. },
  3430. blocks: [],
  3431. },
  3432. // 分支节点
  3433. {
  3434. id: 'condition_0',
  3435. type: 'condition',
  3436. data: {
  3437. title: 'Condition'
  3438. },
  3439. blocks: [
  3440. {
  3441. id: 'branch_0',
  3442. type: 'block',
  3443. data: {
  3444. title: 'Branch 0',
  3445. content: 'branch 1 content'
  3446. },
  3447. blocks: [
  3448. {
  3449. id: 'custom_0',
  3450. type: 'custom',
  3451. data: {
  3452. title: 'Custom',
  3453. content: 'custrom content'
  3454. },
  3455. },
  3456. ],
  3457. },
  3458. {
  3459. id: 'branch_1',
  3460. type: 'block',
  3461. data: {
  3462. title: 'Branch 1',
  3463. content: 'branch 1 content'
  3464. },
  3465. blocks: [],
  3466. },
  3467. ],
  3468. },
  3469. // 结束节点
  3470. {
  3471. id: 'end_0',
  3472. type: 'end',
  3473. data: {
  3474. title: 'End',
  3475. content: 'end content'
  3476. },
  3477. },
  3478. ],
  3479. };
  3480. ```
  3481. ## 加载
  3482. * 通过 initialData 加载
  3483. ```tsx pure
  3484. import { FixedLayoutEditorProvider, FixedLayoutPluginContext, EditorRenderer } from '@flowgram.ai/fixed-layout-editor'
  3485. function App({ data }) {
  3486. return (
  3487. <FixedLayoutEditorProvider initialData={data} {...otherProps}>
  3488. <EditorRenderer className="demo-editor" />
  3489. </FixedLayoutEditorProvider>
  3490. )
  3491. }
  3492. ```
  3493. * 通过 ref 动态加载
  3494. ```tsx pure
  3495. import { FixedLayoutEditorProvider, FixedLayoutPluginContext, EditorRenderer } from '@flowgram.ai/fixed-layout-editor'
  3496. function App() {
  3497. const ref = useRef<FixedLayoutPluginContext | undefined>();
  3498. useEffect(async () => {
  3499. const data = await request('https://xxxx/getJSON')
  3500. ref.current.document.fromJSON(data)
  3501. setTimeout(() => {
  3502. // 加载后触发画布的 fitview 让节点自动居中
  3503. ref.current.playground.config.fitView(ref.current.document.root.bounds.pad(30));
  3504. }, 100)
  3505. }, [])
  3506. return (
  3507. <FixedLayoutEditorProvider ref={ref} {...otherProps}>
  3508. <EditorRenderer className="demo-editor" />
  3509. </FixedLayoutEditorProvider>
  3510. )
  3511. }
  3512. ```
  3513. * 动态 reload 数据
  3514. ```tsx pure
  3515. import { FixedLayoutEditorProvider, FixedLayoutPluginContext, EditorRenderer } from '@flowgram.ai/fixed-layout-editor'
  3516. function App({ data }) {
  3517. const ref = useRef<FixedLayoutPluginContext | undefined>();
  3518. useEffect(async () => {
  3519. // 当 data 变化时候重新加载画布数据
  3520. await ref.current.document.fromJSON(data)
  3521. setTimeout(() => {
  3522. // 加载后触发画布的 fitview 让节点自动居中
  3523. ref.current.playground.config.fitView(ref.current.document.root.bounds.pad(30));
  3524. }, 100)
  3525. }, [data])
  3526. return (
  3527. <FixedLayoutEditorProvider ref={ref} {...otherProps}>
  3528. <EditorRenderer className="demo-editor" />
  3529. </FixedLayoutEditorProvider>
  3530. )
  3531. }
  3532. ```
  3533. ## 监听变化并自动保存
  3534. ```tsx pure
  3535. import { FixedLayoutEditorProvider, FixedLayoutPluginContext, EditorRenderer } from '@flowgram.ai/fixed-layout-editor'
  3536. import { debounce } from 'lodash'
  3537. function App() {
  3538. const ref = useRef<FixedLayoutPluginContext | undefined>();
  3539. useEffect(() => {
  3540. // 监听画布变化 延迟 1 秒 保存数据, 避免画布频繁更新
  3541. const toDispose = ref.current.history.onApply(debounce(() => {
  3542. // 通过 toJSON 获取画布最新的数据
  3543. request('https://xxxx/save', {
  3544. data: ref.current.document.toJSON()
  3545. })
  3546. }, 1000))
  3547. return () => toDispose.dispose()
  3548. }, [])
  3549. return (
  3550. <FixedLayoutEditorProvider ref={ref} {...otherProps}>
  3551. <EditorRenderer className="demo-editor" />
  3552. </FixedLayoutEditorProvider>
  3553. )
  3554. }
  3555. ```
  3556. ---
  3557. url: /guide/advanced/fixed-layout/node.md
  3558. ---
  3559. # 节点
  3560. 节点通过 [FlowNodeEntity](/api/core/flow-node-entity.md) 定义
  3561. ## 节点数据
  3562. 通过 `node.toJSON()` 可以获取
  3563. :::note 基本结构:
  3564. * id: `string` 节点唯一标识, 必须保证唯一
  3565. * meta: `object` 节点的 ui 配置信息,如自由布局的 `position` 信息放这里
  3566. * type: `string | number` 节点类型,会和 `nodeRegistries` 中的 `type` 对应
  3567. * data: `object` 节点表单数据, 业务可自定义
  3568. * blocks: `array` 节点的分支, 采用 `block` 更贴近 `Gramming`
  3569. :::
  3570. ```ts pure
  3571. const nodeData: FlowNodeJSON = {
  3572. id: 'xxxx',
  3573. type: 'condition',
  3574. data: {
  3575. title: 'MyCondition',
  3576. desc: 'xxxxx'
  3577. },
  3578. }
  3579. ```
  3580. ## 节点定义
  3581. 声明节点可以用于确定节点的类型及渲染方式
  3582. ```tsx pure
  3583. import { FlowNodeRegistry, ValidateTrigger } from '@flowgram.ai/fixed-layout-editor';
  3584. /**
  3585. * 自定义节点注册
  3586. */
  3587. export const nodeRegistries: FlowNodeRegistry[] = [
  3588. {
  3589. /**
  3590. * 自定义节点类型
  3591. */
  3592. type: 'condition',
  3593. /**
  3594. * 自定义节点扩展:
  3595. * - loop: 扩展为循环节点
  3596. * - start: 扩展为开始节点
  3597. * - dynamicSplit: 扩展为分支节点
  3598. * - end: 扩展为结束节点
  3599. * - tryCatch: 扩展为 tryCatch 节点
  3600. * - default: 扩展为普通节点 (默认)
  3601. */
  3602. extend: 'dynamicSplit',
  3603. /**
  3604. * 节点配置信息
  3605. */
  3606. meta: {
  3607. // isStart: false, // 是否为开始节点
  3608. // isNodeEnd: false, // 是否为结束节点,结束节点后边无法再添加节点
  3609. // draggable: false, // 是否可拖拽,如开始节点和结束节点无法拖拽
  3610. // selectable: false, // 触发器等开始节点不能被框选
  3611. // deleteDisable: true, // 禁止删除
  3612. // copyDisable: true, // 禁止copy
  3613. // addDisable: true, // 禁止添加
  3614. },
  3615. /**
  3616. * 配置节点表单的校验及渲染,
  3617. * 注:validate 采用数据和渲染分离,保证节点即使不渲染也能对数据做校验
  3618. */
  3619. formMeta: {
  3620. validateTrigger: ValidateTrigger.onChange,
  3621. validate: {
  3622. title: ({ value }) => (value ? undefined : 'Title is required'),
  3623. },
  3624. /**
  3625. * Render form
  3626. */
  3627. render: () => (
  3628. <>
  3629. <Field name="title">
  3630. {({ field }) => <div className="demo-free-node-title">{field.value}</div>}
  3631. </Field>
  3632. <Field name="content">
  3633. {({ field }) => <input onChange={field.onChange} value={field.value}/>}
  3634. </Field>
  3635. </>
  3636. )
  3637. },
  3638. },
  3639. ];
  3640. ```
  3641. ## 当前渲染节点获取
  3642. 通过 [useNodeRender](/api/hooks/use-node-render.md) 获取节点相关方法
  3643. ## 创建节点
  3644. 通过 [FlowOperationService](/api/services/flow-operation-service.md) 创建
  3645. * 添加节点
  3646. ```ts pure
  3647. const ctx = useClientContext()
  3648. ctx.operation.addNode({
  3649. id: 'xxx', // 要保证画布内唯一
  3650. type: 'custom',
  3651. meta: {},
  3652. data: {}, // 表单相关数据
  3653. blocks: [], // 子节点
  3654. parent: someParent // 父亲节点,分支会用到
  3655. })
  3656. ```
  3657. * 在指定节点之后添加
  3658. ```ts pure
  3659. const ctx = useClientContext()
  3660. ctx.operation.addFromNode(targetNode, {
  3661. id: 'xxx', // 要保证画布内唯一
  3662. type: 'custom',
  3663. meta: {},
  3664. data: {}, // 表单相关数据
  3665. blocks: [], // 子节点
  3666. })
  3667. ```
  3668. * 添加分支节点 (用于条件分支)
  3669. ```ts pure
  3670. const ctx = useClientContext()
  3671. ctx.operation.addBlock(parentNode, {
  3672. id: 'xxx', // 要保证画布内唯一
  3673. type: 'block',
  3674. meta: {},
  3675. data: {}, // 表单相关数据
  3676. blocks: [], // 子节点
  3677. })
  3678. ```
  3679. ## 删除节点
  3680. ```tsx pure
  3681. function BaseNode({ node }) {
  3682. const ctx = useClientContext()
  3683. function onClick() {
  3684. ctx.operation.deleteNode(node)
  3685. }
  3686. return (
  3687. <button onClick={onClick}>Delete</button>
  3688. )
  3689. }
  3690. ```
  3691. ## 更新节点 data 数据
  3692. * 通过 [useNodeRender](/api/hooks/use-node-render.md) 或 [getNodeForm](/api/utils/get-node-form.md) 获取节点的 data 数据
  3693. ```tsx pure
  3694. function BaseNode() {
  3695. const { form } = useNodeRender();
  3696. // 对应节点的 data 数据
  3697. console.log(form.values)
  3698. // 监听节点的数据变化
  3699. useEffect(() => {
  3700. const toDispose = form.onFormValuesChange(() => {
  3701. // changed
  3702. })
  3703. return () => toDispose.dispose()
  3704. }, [form])
  3705. function onChange(e) {
  3706. form.setValueIn('title', e.target.value)
  3707. }
  3708. return <input value={form.getValueIn('title')} onChange={onChange}/>
  3709. }
  3710. ```
  3711. * 通过 Field 更新表单数据, 详细见 [表单的使用](/guide/advanced/form.md)
  3712. ```tsx pure
  3713. function FormRender() {
  3714. return (
  3715. <Field name="title">
  3716. <Input />
  3717. </Field>
  3718. )
  3719. }
  3720. ```
  3721. ## 更新节点的 extInfo 数据
  3722. extInfo 用于存储 一些 ui 状态, 如果未开启节点引擎,节点的 data 数据会默认存到 extInfo 里
  3723. ```tsx pure
  3724. function BaseNode({ node }) {
  3725. const times = node.getExtInfo()?.times || 0
  3726. function onClick() {
  3727. node.updateExtInfo({ times: times ++ })
  3728. }
  3729. return (
  3730. <div>
  3731. <span>Click Times: {times}</span>
  3732. <button onClick={onClick}>Click</button>
  3733. </div>
  3734. )
  3735. }
  3736. ```
  3737. ---
  3738. url: /guide/advanced/form-materials.md
  3739. ---
  3740. # 官方表单物料
  3741. ## 如何使用?
  3742. ### 通过包引用使用
  3743. 官方表单物料可以直接通过包引用使用:
  3744. ```tsx
  3745. import { JsonSchemaEditor } from '@flowgram.ai/form-materials'
  3746. ```
  3747. ### 通过 CLI 添加物料源代码使用
  3748. 如果业务对组件有定制诉求(如:更改文案、样式、业务逻辑),推荐 **通过 CLI 将物料源代码添加到项目中进行定制**:
  3749. ```bash
  3750. npx @flowgram.ai/form-materials@latest
  3751. ```
  3752. 运行后 CLI 会提示用户选择要添加到项目中的物料:
  3753. ```console
  3754. ? Select one material to add: (Use arrow keys)
  3755. ❯ components/json-schema-editor
  3756. components/type-selector
  3757. components/variable-selector
  3758. ```
  3759. 使用者也可以直接在 CLI 中添加指定物料的源代码:
  3760. ```bash
  3761. npx @flowgram.ai/form-materials@latest components/json-schema-editor
  3762. ```
  3763. CLI 运行成功后,相关物料会自动添加到当前项目下的 `src/form-materials` 目录下
  3764. :::warning 注意事项
  3765. 1. 官方物料目前底层基于 [Semi Design](https://semi.design/) 实现,业务如果有底层组件库的诉求,可以通过 CLI 复制源码进行替换
  3766. 2. 一些物料会依赖一些第三方 npm 库,这些库会在 CLI 运行时自动安装
  3767. 3. 一些物料会依赖另外一些官方物料,这些被依赖的物料源代码在 CLI 运行时会一起被添加到项目中去
  3768. :::
  3769. ## 当前支持的 Component 物料
  3770. ### TypeSelector
  3771. TypeSelector 用于变量类型选择
  3772. ### VariableSelector
  3773. VariableSelector 用于展示变量树,并从变量树中选择单个变量
  3774. ### JsonSchemaEditor
  3775. JsonSchemaEditor 用于可视化编辑 [JsonSchema](https://json-schema.org/)
  3776. 常用于可视化配置节点的输出变量
  3777. ### DynamicValueInput
  3778. DynamicValueInput 用于值(常量值 + 变量值)的配置
  3779. ### ConditionRow
  3780. ConditionRow 用于 **一行** 条件判断的配置
  3781. ## 当前支持的 Effect 物料
  3782. ### provideBatchInput
  3783. provideBatchInputEffect 用于配置循环批处理输入推导,会根据输入自动推导出两个变量:
  3784. * item:根据输入变量数组类型自动推导,代表循环的每一项
  3785. * index:数字类型,代表循环第几次
  3786. ### autoRenameRef
  3787. 当前置输出变量名发生变化时:
  3788. * 表单项中所有的对该变量的引用,自动重命名
  3789. ---
  3790. url: /guide/advanced/form.md
  3791. ---
  3792. # 节点表单
  3793. ## 术语
  3794. 节点表单
  3795. <td>特指流程节点内的表单或点击节点展开的表单,关联节点数据。</td>
  3796. 节点引擎
  3797. <td>FlowGram.ai 内置的引擎之一,它核心维护了节点数据的增删查改,并提供渲染、校验、副作用、画布或变量联动等能力, 除此之外,它还提供节点错误捕获渲染、无内容时的 placeholder 渲染等能力,见以下章节例子。</td>
  3798. ## 快速开始
  3799. ### 开启节点引擎
  3800. [> API Detail](https://github.com/bytedance/flowgram.ai/blob/main/packages/client/editor/src/preset/editor-props.ts#L54)
  3801. ```tsx pure title="use-editor-props.ts" {3}
  3802. // EditorProps
  3803. {
  3804. nodeEngine: {
  3805. /**
  3806. * 需要开启节点引擎才能使用
  3807. */
  3808. enable: true;
  3809. materials: {
  3810. /**
  3811. * 节点内部报错的渲染组件
  3812. */
  3813. nodeErrorRender?: NodeErrorRender;
  3814. /**
  3815. * 节点无内容时的渲染组件
  3816. */
  3817. nodePlaceholderRender?: NodePlaceholderRender;
  3818. }
  3819. }
  3820. }
  3821. ```
  3822. ### 配置表单
  3823. `formMeta` 是节点表单唯一配置入口,配置在每个节点的NodeRegistry 上。
  3824. [> node-registries.ts](https://github.com/bytedance/flowgram.ai/blob/main/apps/demo-fixed-layout-simple/src/node-registries.ts)
  3825. ```tsx pure title="node-registries.ts"
  3826. import { FlowNodeRegistry, ValidateTrigger } from '@flowgram.ai/fixed-layout-editor';
  3827. export const nodeRegistries: FlowNodeRegistry[] = [
  3828. {
  3829. type: 'start',
  3830. /**
  3831. * 配置节点表单的校验及渲染
  3832. */
  3833. formMeta: {
  3834. /**
  3835. * 配置校验在数据变更时触发
  3836. */
  3837. validateTrigger: ValidateTrigger.onChange,
  3838. /**
  3839. * 配置校验规则, 'content' 为字段路径,以下配置值对该路径下的数据进行校验。
  3840. */
  3841. validate: {
  3842. content: ({ value }) => (value ? undefined : 'Content is required'),
  3843. },
  3844. /**
  3845. * 配置表单渲染
  3846. */
  3847. render: () => (
  3848. <>
  3849. <Field<string> name="title">
  3850. {({ field }) => <div className="demo-free-node-title">{field.value}</div>}
  3851. </Field>
  3852. <Field<string> name="content">
  3853. {({ field, fieldState }) => (
  3854. <>
  3855. <input onChange={field.onChange} value={field.value}/>
  3856. {fieldState?.invalid && <Feedback errors={fieldState?.errors}/>}
  3857. </>
  3858. )}
  3859. </Field>
  3860. </>
  3861. )
  3862. },
  3863. }
  3864. ]
  3865. ```
  3866. [> 表单写法的基础例子](/examples/node-form/basic.md)
  3867. ### 渲染表单
  3868. [> base-node.tsx](https://github.com/bytedance/flowgram.ai/blob/main/apps/demo-fixed-layout-simple/src/components/base-node.tsx)
  3869. ```tsx pure title="base-node.tsx"
  3870. export const BaseNode = () => {
  3871. /**
  3872. * 提供节点渲染相关的方法
  3873. */
  3874. const { form } = useNodeRender()
  3875. return (
  3876. <div className="demo-free-node" className={form?.state.invalid && "error"}>
  3877. {
  3878. // 表单渲染通过 formMeta 生成
  3879. form?.render()
  3880. }
  3881. </div>
  3882. )
  3883. };
  3884. ```
  3885. ## 核心概念
  3886. ### FormMeta
  3887. 在 `NodeRegistry` 中,我们通过`formMeta` 来配置节点表单, 它遵循以下API。
  3888. [> FormMeta API](https://github.com/bytedance/flowgram.ai/blob/main/packages/node-engine/node/src/types.ts#L89)
  3889. 这里特别说明, 节点表单与通用表单有一个很大的区别,它的数据逻辑(如校验、数据变更后的副作用等)需要在表单不渲染的情况下依然生效,我们称 数据与渲染分离
  3890. 。所以这些数据逻辑需要配置在formMeta 中的非render 字段中,保证不渲染情况下节点引擎也可以调用到这些逻辑, 而通用表单引擎(如react-hook-form)则没有这个限制, 校验可以直接写在react组件中。
  3891. ### FormMeta.render (渲染)
  3892. `render` 字段用于配置表单的渲染逻辑
  3893. `render: (props: FormRenderProps<any>) => React.ReactElement;`
  3894. [> FormRenderProps](https://github.com/bytedance/flowgram.ai/blob/main/packages/node-engine/form/src/types/form.ts#L91)
  3895. 返回的 react 组件可使用以下表单组件和模型:
  3896. #### Field (组件)
  3897. `Field` 是表单字段的 React 高阶组件,封装了表单字段的通用逻辑,如数据与状态的注入,组件的刷新等。其核心必填参数为 `name`, 用于声明表单项的路径,在一个表单中具有唯一性。
  3898. [> Field Props API](https://github.com/bytedance/flowgram.ai/blob/main/packages/node-engine/form/src/types/field.ts#L106)
  3899. Field 的渲染部分,支持三种写法,如下:
  3900. ```tsx pure
  3901. const render = () => (
  3902. <div>
  3903. <Label> 1. 通过 children </Label>
  3904. {/* 该方式适用于简单场景,Field 会将 value onChange 等属性直接注入第一层children组件中 */}
  3905. <Field name="c">
  3906. <Input />
  3907. </Field>
  3908. <Label> 2. 通过 Render Props </Label>
  3909. {/* 该方式适用于复杂场景,当 return 的组件存在多层嵌套,用户可以主动将field 中的属性注入希望注入的组件中 */}
  3910. <Field name="a">
  3911. {({ field, fieldState, formState }: FieldRenderProps<string>) => <div><Input {...field} /><Feedbacks errors={fieldState.errors}/></div>}
  3912. </Field>
  3913. <Label> 3. 通过传 render 函数</Label>
  3914. {/* 该方式类似方式2,但通过props 传入 */}
  3915. <Field name="b" render={({ field }: FieldRenderProps<string>) => <Input {...field} />} />
  3916. </div>
  3917. );
  3918. ```
  3919. ```ts pure
  3920. interface FieldRenderProps<TValue> {
  3921. // Field 实例
  3922. field: Field<TValue>;
  3923. // Field 状态(响应式)
  3924. fieldState: Readonly<FieldState>;
  3925. // Form 状态
  3926. formState: Readonly<FormState>;
  3927. }
  3928. ```
  3929. [> FieldRenderProps API](https://github.com/bytedance/flowgram.ai/blob/main/packages/node-engine/form/src/types/field.ts#L125)
  3930. #### Field (模型)
  3931. `Field` 实例通常通过render props 传入(如上例子),或通过 `useCurrentField` hook 获取。它包含表单字段在渲染层面的常见API。
  3932. 注意: `Field` 是一个渲染模型,仅提供一般组件需要的API, 如 `value` `onChange` `onFocus` `onBlur`,如果是数据相关的API 请使用 `Form` 模型实例,如 `form.setValueIn(name, value)` 设置某字段的值。
  3933. [> Field 模型 API](https://github.com/bytedance/flowgram.ai/blob/main/packages/node-engine/form/src/types/field.ts#L34)
  3934. #### FieldArray (组件)
  3935. `FieldArray` 是数组类型字段的 React 高阶组件,封装了数组类型字段的通用逻辑,如数据与状态的注入,组件的刷新,以及数组项的遍历等。其核心必填参数为 `name`, 用于声明该表单项的路径,在一个表单中具有唯一性。
  3936. `FieldArray` 的基础用法可以参照以下例子:
  3937. [> 数组例子](https://flowgram.ai/examples/node-form/array.html)
  3938. #### FieldArray (模型)
  3939. `FieldArray` 继承于 `Field` ,是数组类型字段在渲染层的模型,除了包含渲染层的常见API,还包含数组的基本操作如 `FieldArray.map`, `FieldArray.remove`, `FieldArray.append` 等。API 的使用方法也可见上述[数组例子](https://flowgram.ai/examples/node-form/array.html)。
  3940. [> FieldArray 模型 API](https://github.com/bytedance/flowgram.ai/blob/main/packages/node-engine/form/src/types/field.ts#L69)
  3941. #### Form(组件)
  3942. `Form` 组件是表单的最外层高阶组件,上述 `Field` `FieldArray` 等能力仅在该高阶组件下可以使用。节点表单的渲染已经将`<Form />` 封装到了引擎内部,所以用户无需关注,可以直接在`render` 返回的 react 组件中直接使用 `Field`。但如果用户需要独立使用表单引擎,或者在节点之外独立再渲染一次表单,需要自行在表单内容外包上`Form`组件。
  3943. #### Form(模型)
  3944. `Form` 实例可通过`render` 函数的入参获得, 也可通过 hook `useForm` 获取,见[例子](#useform)。它是表单核心模型门面,用户可以通过Form 实例操作表单数据、监听变更、触发校验等。
  3945. [> Form 模型 API](https://github.com/bytedance/flowgram.ai/blob/main/packages/node-engine/form/src/types/form.ts#L58)
  3946. ### 校验
  3947. 基于[FormMeta](#formmeta)章节中提到的"数据与渲染分离"概念,校验逻辑需配置在 `FormMeta` 全局, 并通过路径匹配方式声明校验逻辑所作用的表单项,如下例子。
  3948. 路径支持模糊匹配,见[路径](#路径)章节。
  3949. ```tsx pure
  3950. export const renderValidateExample = ({ form }: FormRenderProps<FormData>) => (
  3951. <>
  3952. <Label> a (最大长度为 5)</Label>
  3953. <Field name="a">
  3954. {({ field: { value, onChange }, fieldState }: FieldRenderProps<string>) => (
  3955. <>
  3956. <Input value={value} onChange={onChange} />
  3957. <Feedback errors={fieldState?.errors} />
  3958. </>
  3959. )}
  3960. </Field>
  3961. <Label> b (如果a存在,b可以选填) </Label>
  3962. <Field
  3963. name="b"
  3964. render={({ field: { value, onChange }, fieldState }: FieldRenderProps<string>) => (
  3965. <>
  3966. <Input value={value} onChange={onChange} />
  3967. <Feedback errors={fieldState?.errors} />
  3968. </>
  3969. )}
  3970. />
  3971. </>
  3972. );
  3973. export const VALIDATE_EXAMPLE: FormMeta = {
  3974. render: renderValidateExample,
  3975. // 校验时机配置
  3976. validateTrigger: ValidateTrigger.onChange,
  3977. validate: {
  3978. // 单纯校验值
  3979. a: ({ value }) => (value.length > 5 ? '最大长度为5' : undefined),
  3980. // 校验依赖其他表单项的值
  3981. b: ({ value, formValues }) => {
  3982. if (formValues.a) {
  3983. return undefined;
  3984. } else {
  3985. return value ? 'undefined' : 'a 存在时 b 必填';
  3986. }
  3987. },
  3988. // 校验依赖节点或画布信息
  3989. c: ({ value, formValues, context }) => {
  3990. const { node, playgroundContext } = context;
  3991. // 此处逻辑省略
  3992. },
  3993. },
  3994. };
  3995. ```
  3996. #### 校验时机
  3997. `ValidateTrigger.onChange`
  3998. <td>表单数据变更时校验(不包含初始化数据)</td>
  3999. `ValidateTrigger.onBlur`
  4000. <td>表单项输入控件onBlur时校验。<br />注意,这里有两个前提:一是表单项的输入控件需要支持 `onBlur` 入参,二是要将 `Field.onBlur` 传入该控件: <br />`<Field>{({field})=><Input ... onBlur={field.onBlur}>}</Field>`</td>
  4001. `validateTrigger` 建议配置 `ValidateTrigger.onChange` 即数据变更时校验,如果配置 `ValidateTrigger.onBlur`, 校验只会在组件blur事件触发时触发。那么当节点表单不渲染的情况下,就算是数据变更了,也不会触发校验。
  4002. #### 主动触发校验
  4003. 1. 主动触发整个表单的校验
  4004. ```pure tsx
  4005. const form = useForm()
  4006. form.validate()
  4007. ```
  4008. 2. 主动触发单个表单项校验
  4009. ```pure tsx
  4010. const validate = useFieldValidate(name)
  4011. validate()
  4012. ```
  4013. `name` 不传则默认获取当前 `<Field />` 标签下的 `Field` 的 `validate`, 通过传 `name` 可获取 `<Form />` 下任意 `Field`。
  4014. ### 路径
  4015. 1. 表单路径以`.`为层级分隔符, 如 `a.b.c` 指向数据 `{a:{b:{c:1}}}` 下的 `1`
  4016. 2. 路径支持模糊匹配,在校验和副作用配置中会使用到。如下例子。通常在数组场景中使用较多。
  4017. 注意:\* 仅代表下钻一级
  4018. `arr.*`
  4019. <td>`arr` 字段的所有一级子项</td>
  4020. `arr.x.*`
  4021. <td>`arr.x` 的所有一级子项</td>
  4022. `arr.*.x`
  4023. <td>`arr` 所有一级子项下的 `x`</td>
  4024. ### 副作用 (effect)
  4025. 副作用是节点表单特有的概念,指在节点数据发生变更时需要执行的副作用。同样,遵循 "数据与渲染分离" 的原则,副作用和校验相似,也配置在 `FormMeta` 全局。
  4026. * 通过 key value 形式配置,key 表示表单项路径匹配规则,支持模糊匹配,value 为作用在该路径上的effect。
  4027. * value 为数组,即支持一个表单项有多个effect。
  4028. ```tsx pur
  4029. export const EFFECT_EXAMPLE: FormMeta = {
  4030. ...
  4031. effect: {
  4032. ['a.b']: [
  4033. {
  4034. event: DataEvent.onValueChange,
  4035. effect: ({ value }: EffectFuncProps<string, FormData>) => {
  4036. console.log('a.b value changed:', value);
  4037. },
  4038. },
  4039. ],
  4040. ['arr.*']:[
  4041. {
  4042. event: DataEvent.onValueInit,
  4043. effect: ({ value, name }: EffectFuncProps<string, FormData>) => {
  4044. console.log(name + ' value init:', value);
  4045. },
  4046. },
  4047. ]
  4048. }
  4049. };
  4050. ```
  4051. ```tsx pur
  4052. interface EffectFuncProps<TFieldValue = any, TFormValues = any> {
  4053. name: FieldName;
  4054. value: TFieldValue;
  4055. prevValue?: TFieldValue;
  4056. formValues: TFormValues;
  4057. form: IForm;
  4058. context: NodeContext;
  4059. }
  4060. ```
  4061. [Effect 相关 API](https://github.com/bytedance/flowgram.ai/blob/main/packages/node-engine/node/src/types.ts#L54)
  4062. #### 副作用时机
  4063. `DataEvent.onValueChange`
  4064. <td>数据变更时触发</td>
  4065. `DataEvent.onValueInit`
  4066. <td>数据初始化时触发</td>
  4067. `DataEvent.onValueInitOrChange`
  4068. <td>数据初始化和变更时都会触发</td>
  4069. ### 联动
  4070. [> 联动例子](/examples/node-form/dynamic.md)
  4071. ## hooks
  4072. ### 节点表单内
  4073. 以下hook 可在节点表单内部使用
  4074. #### useCurrentField
  4075. `() => Field`
  4076. 该 hook 需要在Field 标签内部使用
  4077. ```tsx pur
  4078. const field = useCurrentField()
  4079. ```
  4080. [> Field 模型 API](https://github.com/bytedance/flowgram.ai/blob/main/packages/node-engine/form/src/types/field.ts#L34)
  4081. #### useCurrentFieldState
  4082. `() => FieldState`
  4083. 该 hook 需要在Field 标签内部使用
  4084. ```tsx pur
  4085. const fieldState = useCurrentFieldState()
  4086. ```
  4087. [> FieldState API](https://github.com/bytedance/flowgram.ai/blob/main/packages/node-engine/form/src/types/field.ts#L158)
  4088. #### useFieldValidate
  4089. `(name?: FieldName) => () => Promise<void>`
  4090. 如果需要主动触发字段的校验,可以使用该hook 获取到 Field 的 validate 函数。
  4091. `name` 为 Field 的路径,不传则默认获取当前 `<Field />` 下的validate
  4092. ```tsx pur
  4093. const validate = useFieldValidate()
  4094. validate()
  4095. ```
  4096. #### useForm
  4097. `() => Form`
  4098. 用于获取 Form 实例。
  4099. 注意,该hook 在 `render` 函数第一层不生效,仅在 `render` 函数内的 react 组件内部才可使用。`render` 函数的入参中已经传入了 `form: Form`, 可以直接使用。
  4100. 1. 在 render 函数第一层直接使用 `props.form`
  4101. ```tsx pur
  4102. const formMeta = {
  4103. render: ({form}) =>
  4104. <div>
  4105. {form.getValueIn('my.path')}
  4106. </div>
  4107. }
  4108. ```
  4109. 2. 在组件内部可使用 `useForm`
  4110. ```tsx pur
  4111. const formMeta = {
  4112. render: () =>
  4113. <div>
  4114. <Field name={'my.path'}>
  4115. <MySelect />
  4116. </Field>
  4117. </div>
  4118. }
  4119. // MySelect.tsx
  4120. ...
  4121. const form = useForm()
  4122. const valueNeeded = form.getValueIn('my.other.path')
  4123. ...
  4124. ```
  4125. 注意:Form 的 api 不具备任何响应式能力,若需监听某字段值,可使用 [useWatch](#usewatch)&#x20;
  4126. #### useWatch
  4127. `<TValue = FieldValue>(name: FieldName) => TValue`
  4128. 该 hook 和上述 `useForm` 相似, 在 `render` 函数返回组件的第一层不生效,仅在封装过的组件内部可用。如果需要在 `render` 根级别使用,可以对 `render` 返回的内容做一层组件封装。
  4129. ```tsx pur
  4130. {
  4131. render: () =>
  4132. <div>
  4133. <Field name={'a'}><A /></Field>
  4134. <Field name={'b'}><B /></Field>
  4135. </div>
  4136. }
  4137. // A.tsx
  4138. ...
  4139. const b = useWatch('b')
  4140. // do something with b
  4141. ...
  4142. ```
  4143. ### 节点表单外
  4144. 以下 hook 用于在节点表单外部,如画布全局、相邻节点上需要去监听某个节点表单的数据或状态。通常需要传入 `node: FlowNodeEntity` 作为参数
  4145. #### useWatchFormValues
  4146. 监听 node 内整个表单的值
  4147. `<TFormValues = any>(node: FlowNodeEntity) => TFormValues | undefined`
  4148. ```tsx pur
  4149. const values = useWatchFormValues(node)
  4150. ```
  4151. #### useWatchFormValueIn
  4152. 监听 node 内某个表单项的值
  4153. `<TValue = any>(node: FlowNodeEntity,name: string) => TFormValues | undefined`
  4154. ```tsx pur
  4155. const value = useWatchFormValueIn(node, name)
  4156. ```
  4157. #### useWatchFormState
  4158. 监听 node 内表单的状态
  4159. `(node: FlowNodeEntity) => FormState | undefined`
  4160. ```tsx pur
  4161. const formState = useWatchFormState(node)
  4162. ```
  4163. #### useWatchFormErrors
  4164. 监听 node 内表单的 Errors
  4165. `(node: FlowNodeEntity) => Errors | undefined`
  4166. ```tsx pur
  4167. const errors = useWatchFormErrors(node)
  4168. ```
  4169. #### useWatchFormWarnings
  4170. 监听 node 内表单的 Warnings
  4171. `(node: FlowNodeEntity) => Warnings | undefined`
  4172. ```tsx pur
  4173. const warnings = useWatchFormErrors(node)
  4174. ```
  4175. ---
  4176. url: /guide/advanced/free-layout/line.md
  4177. ---
  4178. # 线条
  4179. * [WorkflowLinesManager](https://github.com/bytedance/flowgram.ai/blob/main/packages/canvas-engine/free-layout-core/src/workflow-lines-manager.ts) 管理所有的线条
  4180. * [WorkflowNodeLinesData](https://github.com/bytedance/flowgram.ai/blob/main/packages/canvas-engine/free-layout-core/src/entity-datas/workflow-node-lines-data.ts) 节点上连接的线条管理
  4181. * [WorkflowLineEntity](https://github.com/bytedance/flowgram.ai/blob/main/packages/canvas-engine/free-layout-core/src/entities/workflow-line-entity.ts) 线条实体
  4182. ## 获取所有线条的实体
  4183. ```ts pure
  4184. const allLines = ctx.document.linesManager.getAllLines() // 线条实体 包含 from/to 分别代表线条连接的节点
  4185. ```
  4186. ## 创建/删除线条
  4187. ```ts pure
  4188. // from和 to 为对应要连线的节点id, fromPort, toPort 为 端口 id, 如果节点为单个端口可以不指定
  4189. const line = ctx.document.linesManager.createLine({ from, to, fromPort, toPort })
  4190. // 删除线条
  4191. line.dispose()
  4192. ```
  4193. ## 导出线条数据
  4194. :::note 线条基本结构:
  4195. * sourceNodeID: `string` 开始节点 id
  4196. * targetNodeID: `string` 目标节点 id
  4197. * sourcePortID?: `string | number` 开始端口 id, 缺省则采用开始节点的默认端口
  4198. * targetPortID?: `string | number` 目标端口 id, 缺省则采用目标节点的默认端口
  4199. :::
  4200. ```ts pure
  4201. const json = ctx.document.linesManager.toJSON()
  4202. ```
  4203. ## 获取当前节点的输入/输出节点或线条
  4204. ```ts pure
  4205. import { WorkflowNodeLinesData } from '@flowgram.ai/free-layout-editor'
  4206. // 获取当前节点的输入节点(通过连接线计算)
  4207. node.getData(WorkflowNodeLinesData).inputNodes
  4208. // 获取所有输入节点 (会往上递归获取所有)
  4209. node.getData(WorkflowNodeLinesData).allInputNodes
  4210. // 获取输出节点
  4211. node.getData(WorkflowNodeLinesData).outputNodes
  4212. // 获取所有输出节点
  4213. node.getData(WorkflowNodeLinesData).allOutputNodes
  4214. // 输入线条
  4215. node.getData(WorkflowNodeLinesData).inputLines
  4216. // 输出线条
  4217. node.getData(WorkflowNodeLinesData).outputLines
  4218. ```
  4219. ## 线条配置
  4220. 我们提供丰富的线条配置参数, 给 `FreeLayoutEditorProvider`, 详细见 [FreeLayoutProps](https://github.com/bytedance/flowgram.ai/blob/main/packages/client/free-layout-editor/src/preset/free-layout-props.ts)
  4221. ```tsx pure
  4222. interface FreeLayoutProps {
  4223. /**
  4224. * 线条颜色配置
  4225. */
  4226. lineColor?: LineColor;
  4227. /**
  4228. * 判断线条是否标红
  4229. * @param ctx
  4230. * @param fromPort
  4231. * @param toPort
  4232. * @param lines
  4233. */
  4234. isErrorLine?: (ctx: FreeLayoutPluginContext, fromPort: WorkflowPortEntity, toPort: WorkflowPortEntity | undefined, lines: WorkflowLinesManager) => boolean;
  4235. /**
  4236. * 判断端口是否标红
  4237. * @param ctx
  4238. * @param port
  4239. */
  4240. isErrorPort?: (ctx: FreeLayoutPluginContext, port: WorkflowPortEntity) => boolean;
  4241. /**
  4242. * 判断端口是否禁用
  4243. * @param ctx
  4244. * @param port
  4245. */
  4246. isDisabledPort?: (ctx: FreeLayoutPluginContext, port: WorkflowPortEntity) => boolean;
  4247. /**
  4248. * 判断线条箭头是否反转
  4249. * @param ctx
  4250. * @param line
  4251. */
  4252. isReverseLine?: (ctx: FreeLayoutPluginContext, line: WorkflowLineEntity) => boolean;
  4253. /**
  4254. * 判断线条是否隐藏箭头
  4255. * @param ctx
  4256. * @param line
  4257. */
  4258. isHideArrowLine?: (ctx: FreeLayoutPluginContext, line: WorkflowLineEntity) => boolean;
  4259. /**
  4260. * 判断线条是否展示流动效果
  4261. * @param ctx
  4262. * @param line
  4263. */
  4264. isFlowingLine?: (ctx: FreeLayoutPluginContext, line: WorkflowLineEntity) => boolean;
  4265. /**
  4266. * 判断线条是否禁用
  4267. * @param ctx
  4268. * @param line
  4269. */
  4270. isDisabledLine?: (ctx: FreeLayoutPluginContext, line: WorkflowLineEntity) => boolean;
  4271. /**
  4272. * 判断线条是否竖向
  4273. * @param ctx
  4274. * @param line
  4275. */
  4276. isVerticalLine?: (ctx: FreeLayoutPluginContext, line: WorkflowLineEntity) => boolean;
  4277. /**
  4278. * 拖拽线条结束
  4279. * @param ctx
  4280. * @param params
  4281. */
  4282. onDragLineEnd?: (ctx: FreeLayoutPluginContext, params: onDragLineEndParams) => Promise<void>;
  4283. /**
  4284. * 设置线条渲染器类型
  4285. * @param ctx
  4286. * @param line
  4287. */
  4288. setLineRenderType?: (ctx: FreeLayoutPluginContext, line: WorkflowLineEntity) => LineRenderType | undefined;
  4289. /**
  4290. * 设置线条样式
  4291. * @param ctx
  4292. * @param line
  4293. */
  4294. setLineClassName?: (ctx: FreeLayoutPluginContext, line: WorkflowLineEntity) => string | undefined;
  4295. /**
  4296. * 是否允许创建线条
  4297. * @param ctx
  4298. * @param fromPort - 开始点
  4299. * @param toPort - 目标点
  4300. */
  4301. canAddLine?: (ctx: FreeLayoutPluginContext, fromPort: WorkflowPortEntity, toPort: WorkflowPortEntity, lines: WorkflowLinesManager, silent?: boolean) => boolean;
  4302. /**
  4303. *
  4304. * 是否允许删除线条
  4305. * @param ctx
  4306. * @param line - 目标线条
  4307. * @param newLineInfo - 新的线条信息
  4308. * @param silent - 如果为false,可以加 toast 弹窗
  4309. */
  4310. canDeleteLine?: (ctx: FreeLayoutPluginContext, line: WorkflowLineEntity, newLineInfo?: Required<WorkflowLinePortInfo>, silent?: boolean) => boolean;
  4311. /**
  4312. * 是否允许重置线条
  4313. * @param fromPort - 开始点
  4314. * @param oldToPort - 旧的连接点
  4315. * @param newToPort - 新的连接点
  4316. * @param lines - 线条管理器
  4317. */
  4318. canResetLine?: (ctx: FreeLayoutPluginContext, fromPort: WorkflowPortEntity, oldToPort: WorkflowPortEntity, newToPort: WorkflowPortEntity, lines: WorkflowLinesManager) => boolean;
  4319. }
  4320. ```
  4321. ### 1.自定义颜色
  4322. ```tsx pure
  4323. function App() {
  4324. const editorProps: FreeLayoutProps = {
  4325. lineColor: {
  4326. hidden: 'transparent',
  4327. default: '#4d53e8',
  4328. drawing: '#5DD6E3',
  4329. hovered: '#37d0ff',
  4330. selected: '#37d0ff',
  4331. error: 'red',
  4332. },
  4333. // ...others
  4334. }
  4335. return (
  4336. <FreeLayoutEditorProvider {...editorProps}>
  4337. <EditorRenderer className="demo-editor" />
  4338. </FreeLayoutEditorProvider>
  4339. )
  4340. }
  4341. ```
  4342. ### 2.让单个输出端口只能连一条线
  4343. ```tsx pure
  4344. function App() {
  4345. const editorProps: FreeLayoutProps = {
  4346. /*
  4347. * Check whether the line can be added
  4348. * 判断是否连线
  4349. */
  4350. canAddLine(ctx, fromPort, toPort) {
  4351. // not the same node
  4352. if (fromPort.node === toPort.node) {
  4353. return false;
  4354. }
  4355. // 控制线条添加数目
  4356. if (fromPort.availableLines.length >= 1) {
  4357. return false
  4358. }
  4359. return true;
  4360. },
  4361. // ...others
  4362. }
  4363. return (
  4364. <FreeLayoutEditorProvider {...editorProps}>
  4365. <EditorRenderer className="demo-editor" />
  4366. </FreeLayoutEditorProvider>
  4367. )
  4368. }
  4369. ```
  4370. ### 3.连接到空白地方添加节点
  4371. 代码见自由布局最佳实践
  4372. ```tsx pure
  4373. function App() {
  4374. const editorProps: FreeLayoutProps = {
  4375. /**
  4376. * Drag the end of the line to create an add panel (feature optional)
  4377. * 拖拽线条结束需要创建一个添加面板 (功能可选)
  4378. */
  4379. async onDragLineEnd(ctx, params) {
  4380. const { fromPort, toPort, mousePos, line, originLine } = params;
  4381. if (originLine || !line) {
  4382. return;
  4383. }
  4384. if (toPort) {
  4385. return;
  4386. }
  4387. // 这里可以根据 mousePos 打开添加面板
  4388. await ctx.get(WorkflowNodePanelService).call({
  4389. fromPort,
  4390. toPort: undefined,
  4391. panelPosition: mousePos,
  4392. enableBuildLine: true,
  4393. panelProps: {
  4394. enableNodePlaceholder: true,
  4395. enableScrollClose: true,
  4396. },
  4397. });
  4398. },
  4399. // ...others
  4400. }
  4401. return (
  4402. <FreeLayoutEditorProvider {...editorProps}>
  4403. <EditorRenderer className="demo-editor" />
  4404. </FreeLayoutEditorProvider>
  4405. )
  4406. }
  4407. ```
  4408. ## 在线条上添加 Label
  4409. 代码见自由布局最佳实践
  4410. ```ts pure
  4411. import { createFreeLinesPlugin } from '@flowgram.ai/free-lines-plugin';
  4412. const editorProps = {
  4413. plugins: () => [
  4414. /**
  4415. * Line render plugin
  4416. * 连线渲染插件
  4417. */
  4418. createFreeLinesPlugin({
  4419. renderInsideLine: LineAddButton,
  4420. }),
  4421. ]
  4422. }
  4423. ```
  4424. ## 节点监听自身的连线变化并刷新
  4425. ```tsx pure
  4426. import {
  4427. useRefresh,
  4428. WorkflowNodeLinesData,
  4429. } from '@flowgram.ai/free-layout-editor';
  4430. function NodeRender({ node }) {
  4431. const refresh = useRefresh()
  4432. const linesData = node.get(WorkflowNodeLinesData)
  4433. useEffect(() => {
  4434. const dispose = linesData.onDataChange(() => refresh())
  4435. return () => dispose.dispose()
  4436. }, [])
  4437. return <div>xxxx</div>
  4438. }
  4439. ```
  4440. ## 监听所有线条的连线变化
  4441. 这个场景用于当希望在外部组件监听线条连接情况
  4442. ```ts pure
  4443. import { useEffect } from 'react'
  4444. import { WorkflowLinesManager, useRefresh } from '@flowgram.ai/free-layout-editor'
  4445. function SomeReact() {
  4446. const refresh = useRefresh()
  4447. const linesManager = useService(WorkflowLinesManager)
  4448. useEffect(() => {
  4449. const dispose = linesManager.onAvailableLinesChange(() => refresh())
  4450. return () => dispose.dispose()
  4451. }, [])
  4452. console.log(ctx.document.linesManager.getAllLines())
  4453. }
  4454. ```
  4455. ---
  4456. url: /guide/advanced/free-layout/load.md
  4457. ---
  4458. # 加载与保存
  4459. 画布的数据通过 [WorkflowDocument](/api/core/workflow-document.md) 来存储
  4460. ## 画布数据
  4461. :::note 文档数据基本结构:
  4462. * nodes `array` 节点列表, 支持嵌套
  4463. * edges `array` 边列表
  4464. :::
  4465. :::note 节点数据基本结构:
  4466. * id: `string` 节点唯一标识, 必须保证唯一
  4467. * meta: `object` 节点的 ui 配置信息,如自由布局的 `position` 信息放这里
  4468. * type: `string | number` 节点类型,会和 `nodeRegistries` 中的 `type` 对应
  4469. * data: `object` 节点表单数据, 业务可自定义
  4470. * blocks: `array` 节点的分支, 采用 `block` 更贴近 `Gramming`, 目前会存子画布的节点
  4471. * edges: `array` 子画布的边数据
  4472. :::
  4473. :::note 边数据基本结构:
  4474. * sourceNodeID: `string` 开始节点 id
  4475. * targetNodeID: `string` 目标节点 id
  4476. * sourcePortID?: `string | number` 开始端口 id, 缺省则采用开始节点的默认端口
  4477. * targetPortID?: `string | number` 目标端口 id, 缺省则采用目标节点的默认端口
  4478. :::
  4479. ```tsx pure title="initial-data.ts"
  4480. import { WorkflowJSON } from '@flowgram.ai/free-layout-editor';
  4481. export const initialData: WorkflowJSON = {
  4482. nodes: [
  4483. {
  4484. id: 'start_0',
  4485. type: 'start',
  4486. meta: {
  4487. position: { x: 0, y: 0 },
  4488. },
  4489. data: {
  4490. title: 'Start',
  4491. content: 'Start content'
  4492. },
  4493. },
  4494. {
  4495. id: 'node_0',
  4496. type: 'custom',
  4497. meta: {
  4498. position: { x: 400, y: 0 },
  4499. },
  4500. data: {
  4501. title: 'Custom',
  4502. content: 'Custom node content'
  4503. },
  4504. },
  4505. {
  4506. id: 'end_0',
  4507. type: 'end',
  4508. meta: {
  4509. position: { x: 800, y: 0 },
  4510. },
  4511. data: {
  4512. title: 'End',
  4513. content: 'End content'
  4514. },
  4515. },
  4516. ],
  4517. edges: [
  4518. {
  4519. sourceNodeID: 'start_0',
  4520. targetNodeID: 'node_0',
  4521. },
  4522. {
  4523. sourceNodeID: 'node_0',
  4524. targetNodeID: 'end_0',
  4525. },
  4526. ],
  4527. };
  4528. ```
  4529. ## 加载
  4530. * 通过 initialData 加载
  4531. ```tsx pure
  4532. import { FreeLayoutEditorProvider, FreeLayoutPluginContext, EditorRenderer } from '@flowgram.ai/free-layout-editor'
  4533. function App({ data }) {
  4534. return (
  4535. <FreeLayoutEditorProvider initialData={data} {...otherProps}>
  4536. <EditorRenderer className="demo-editor" />
  4537. </FreeLayoutEditorProvider>
  4538. )
  4539. }
  4540. ```
  4541. * 通过 ref 动态加载
  4542. ```tsx pure
  4543. import { FreeLayoutEditorProvider, FreeLayoutPluginContext, EditorRenderer } from '@flowgram.ai/free-layout-editor'
  4544. function App() {
  4545. const ref = useRef<FreeLayoutPluginContext | undefined>();
  4546. useEffect(async () => {
  4547. const data = await request('https://xxxx/getJSON')
  4548. ref.current.document.fromJSON(data)
  4549. setTimeout(() => {
  4550. // 加载后触发画布的 fitview 让节点自动居中
  4551. ref.current.document.fitView()
  4552. }, 100)
  4553. }, [])
  4554. return (
  4555. <FreeLayoutEditorProvider ref={ref} {...otherProps}>
  4556. <EditorRenderer className="demo-editor" />
  4557. </FreeLayoutEditorProvider>
  4558. )
  4559. }
  4560. ```
  4561. * 动态 reload 所有数据
  4562. ```tsx pure
  4563. import { FreeLayoutEditorProvider, FreeLayoutPluginContext, EditorRenderer } from '@flowgram.ai/free-layout-editor'
  4564. function App({ data }) {
  4565. const ref = useRef<FreeLayoutPluginContext | undefined>();
  4566. useEffect(async () => {
  4567. // 当 data 变化时候重新加载画布数据
  4568. await ref.current.document.reload(data)
  4569. setTimeout(() => {
  4570. // 加载后触发画布的 fitview 让节点自动居中
  4571. ref.current.document.fitView()
  4572. }, 100)
  4573. }, [data])
  4574. return (
  4575. <FreeLayoutEditorProvider ref={ref} {...otherProps}>
  4576. <EditorRenderer className="demo-editor" />
  4577. </FreeLayoutEditorProvider>
  4578. )
  4579. }
  4580. ```
  4581. ## 监听变化并自动保存
  4582. ```tsx pure
  4583. import { FreeLayoutEditorProvider, FreeLayoutPluginContext, EditorRenderer } from '@flowgram.ai/free-layout-editor'
  4584. import { debounce } from 'lodash'
  4585. function App() {
  4586. const ref = useRef<FreeLayoutPluginContext | undefined>();
  4587. useEffect(() => {
  4588. // 监听画布变化 延迟 1 秒 保存数据, 避免画布频繁更新
  4589. const toDispose = ref.current.document.onContentChange(debounce(() => {
  4590. // 通过 toJSON 获取画布最新的数据
  4591. request('https://xxxx/save', {
  4592. data: ref.current.document.toJSON()
  4593. })
  4594. }, 1000))
  4595. return () => toDispose.dispose()
  4596. }, [])
  4597. return (
  4598. <FreeLayoutEditorProvider ref={ref} {...otherProps}>
  4599. <EditorRenderer className="demo-editor" />
  4600. </FreeLayoutEditorProvider>
  4601. )
  4602. }
  4603. ```
  4604. ---
  4605. url: /guide/advanced/free-layout/node.md
  4606. ---
  4607. # 节点
  4608. 节点通过 [FlowNodeEntity](/api/core/flow-node-entity.md) 定义
  4609. ## 节点数据
  4610. 通过 `node.toJSON()` 可以获取
  4611. :::note 基本结构:
  4612. * id: `string` 节点唯一标识, 必须保证唯一
  4613. * meta: `object` 节点的 ui 配置信息,如自由布局的 `position` 信息放这里
  4614. * type: `string | number` 节点类型,会和 `nodeRegistries` 中的 `type` 对应
  4615. * data: `object` 节点表单数据, 业务可自定义
  4616. * blocks: `array` 节点的分支, 采用 `block` 更贴近 `Gramming` 自由布局布局场景会用在子画布的子节点
  4617. * edges: `array` 子画布的边数据
  4618. :::
  4619. ```ts pure
  4620. const nodeData: FlowNodeJSON = {
  4621. id: 'xxxx',
  4622. type: 'condition',
  4623. data: {
  4624. title: 'MyCondition',
  4625. desc: 'xxxxx'
  4626. },
  4627. }
  4628. ```
  4629. ## 节点定义
  4630. 在自由布局场景,节点定义用于声明节点的初始化位置/大小,端口,表单渲染等, 详细见 [声明节点](/guide/getting-started/create-free-layout-simple.md#4-声明节点)
  4631. ## 当前渲染节点获取
  4632. 通过 [useNodeRender](/api/hooks/use-node-render.md) 获取节点相关方法
  4633. ## 创建节点
  4634. * 通过 [WorkflowDocument](/api/core/workflow-document.md) 创建
  4635. ```ts pure
  4636. const ctx = useClientContext()
  4637. ctx.document.createWorkflowNode({
  4638. id: 'xxx', // 要保证画布内唯一
  4639. type: 'custom',
  4640. meta: {
  4641. /**
  4642. * 如果不传入,则默认在画布中间创建
  4643. * 如果要通过鼠标位置获取 position (如点击画布任意位置创建节点),可通过 `ctx.playground.config.getPosFromMouseEvent(mouseEvent)` 转换
  4644. */
  4645. position: { x: 100, y: 100 } //
  4646. },
  4647. data: {}, // 表单相关数据
  4648. blocks: [], // 子画布的节点
  4649. edges: [] // 子画布的边
  4650. })
  4651. ```
  4652. * 通过 WorkflowDragService 创建, 见[自由布局基础用法](/examples/free-layout/free-layout-simple.md)
  4653. ```ts pure
  4654. const dragService = useService<WorkflowDragService>(WorkflowDragService);
  4655. // 这里的 mouseEvent 会自动转成 画布的 position
  4656. dragService.startDragCard(nodeType, mouseEvent, {
  4657. id: 'xxxx',
  4658. data: {}, // 表单相关数据
  4659. blocks: [], // 子画布的节点
  4660. edges: [] // 子画布的边
  4661. })
  4662. ```
  4663. ## 删除节点
  4664. 通过 `node.dispose` 删除节点
  4665. ```tsx pure
  4666. function BaseNode({ node }) {
  4667. function onClick() {
  4668. node.dispose()
  4669. }
  4670. return (
  4671. <button onClick={onClick}>Delete</button>
  4672. )
  4673. }
  4674. ```
  4675. ## 更新节点 data 数据
  4676. * 通过 [useNodeRender](/api/hooks/use-node-render.md) 或 [getNodeForm](/api/utils/get-node-form.md) 获取节点的 data 数据
  4677. ```tsx pure
  4678. function BaseNode() {
  4679. const { form } = useNodeRender();
  4680. // 对应节点的 data 数据
  4681. console.log(form.values)
  4682. // 监听节点的数据变化
  4683. useEffect(() => {
  4684. const toDispose = form.onFormValuesChange(() => {
  4685. // changed
  4686. })
  4687. return () => toDispose.dispose()
  4688. }, [form])
  4689. function onChange(e) {
  4690. form.setValueIn('title', e.target.value)
  4691. }
  4692. return <input value={form.getValueIn('title')} onChange={onChange}/>
  4693. }
  4694. ```
  4695. * 通过 Field 更新表单数据, 详细见 [表单的使用](/guide/advanced/form.md)
  4696. ```tsx pure
  4697. function FormRender() {
  4698. return (
  4699. <Field name="title">
  4700. <Input />
  4701. </Field>
  4702. )
  4703. }
  4704. ```
  4705. ## 更新节点的 extInfo 数据
  4706. extInfo 用于存储 一些 ui 状态, 如果未开启节点引擎,节点的 data 数据会默认存到 extInfo 里
  4707. ```tsx pure
  4708. function BaseNode({ node }) {
  4709. const times = node.getExtInfo()?.times || 0
  4710. function onClick() {
  4711. node.updateExtInfo({ times: times ++ })
  4712. }
  4713. return (
  4714. <div>
  4715. <span>Click Times: {times}</span>
  4716. <button onClick={onClick}>Click</button>
  4717. </div>
  4718. )
  4719. }
  4720. ```
  4721. ---
  4722. url: /guide/advanced/free-layout/port.md
  4723. ---
  4724. # 端口
  4725. * [WorkflowNodePortsData](https://github.com/bytedance/flowgram.ai/blob/main/packages/canvas-engine/free-layout-core/src/entity-datas/workflow-node-ports-data.ts) 管理节点的所有端口信息
  4726. * [WorkflowPortEntity](https://github.com/bytedance/flowgram.ai/blob/main/packages/canvas-engine/free-layout-core/src/entities/workflow-port-entity.ts) 端口实例
  4727. * [WorkflowPortRender](https://github.com/bytedance/flowgram.ai/blob/main/packages/plugins/free-lines-plugin/src/components/workflow-port-render/index.tsx) 端口渲染组件
  4728. ## 定义端口
  4729. * 静态端口
  4730. 节点声明添加 `defaultPorts` , 如 `{ type: 'input' }`, 则会在节点左侧加入输入端口
  4731. ```ts pure title="node-registries.ts"
  4732. {
  4733. type: 'start',
  4734. meta: {
  4735. defaultPorts: [{ type: 'output' }, { type: 'input'}]
  4736. },
  4737. }
  4738. ```
  4739. * 动态端口
  4740. 节点声明添加 `useDynamicPort` , 当设置为 true 则会到节点dom 上寻找 data-port-id 和 data-port-type 属性的 dom 作为端口
  4741. ```ts pure title="node-registries.ts"
  4742. {
  4743. type: 'condition',
  4744. meta: {
  4745. defaultPorts: [{ type: 'input'}]
  4746. useDynamicPort: true
  4747. },
  4748. }
  4749. ```
  4750. ```tsx pure
  4751. /**
  4752. * 动态端口通过 querySelectorAll('[data-port-id]') 查找端口位置
  4753. */
  4754. function BaseNode() {
  4755. return (
  4756. <div>
  4757. <div data-port-id="condition-if-0" data-port-type="output"></div>
  4758. <div data-port-id="condition-if-1" data-port-type="output"></div>
  4759. {/* others */}
  4760. </div>
  4761. )
  4762. }
  4763. ```
  4764. ## 端口渲染
  4765. 端口最终通过 `WorkflowPortRender` 组件渲染,支持自定义 style, 或者业务基于源码重新实现该组件, 参考 [自由布局最佳实践 - 节点渲染](https://github.com/bytedance/flowgram.ai/blob/main/apps/demo-free-layout/src/components/base-node/node-wrapper.tsx)
  4766. ```tsx pure
  4767. import { WorkflowPortRender, useNodeRender } from '@flowgram.ai/free-layout-editor';
  4768. function BaseNode() {
  4769. const { ports } = useNodeRender();
  4770. return (
  4771. <div>
  4772. <div data-port-id="condition-if-0" data-port-type="output"></div>
  4773. <div data-port-id="condition-if-1" data-port-type="output"></div>
  4774. {ports.map((p) => (
  4775. <WorkflowPortRender key={p.id} entity={p} className="xxx" style={{ /* custom style */}}/>
  4776. ))}
  4777. </div>
  4778. )
  4779. }
  4780. ```
  4781. ## 获取端口数据
  4782. ```ts pure
  4783. const ports = node.getData(WorkflowNodePortsData)
  4784. console.log(ports.inputPorts) // 获取当前节点的所有输入端口
  4785. console.log(ports.outputPorts) // 获取当前节点的所有输出端口
  4786. console.log(ports.inputPorts.map(port => port.availableLines)) // 通过端口找到连接的线条
  4787. ports.updateDynamicPorts() // 当动态端口修改了 dom 结构或位置,可以通过该方法手动刷新端口位置(dom 渲染有延迟,最好在 useEffect 或者 setTimeout 执行)
  4788. ```
  4789. ---
  4790. url: /guide/advanced/free-layout/sub-canvas.md
  4791. ---
  4792. # 子画布
  4793. 详细代码见 [自由布局最佳实践](/examples/free-layout/free-feature-overview.md)
  4794. ## 添加子画布插件
  4795. ```tsx pure
  4796. import { createContainerNodePlugin } from '@flowgram.ai/free-container-plugin';
  4797. function App() {
  4798. const editorProps = {
  4799. plugins: () => [
  4800. createContainerNodePlugin({}),
  4801. ]
  4802. // ..others
  4803. }
  4804. return (
  4805. <FreeLayoutEditorProvider {...editorProps}>
  4806. <EditorRenderer className="demo-editor" />
  4807. </FreeLayoutEditorProvider>
  4808. )
  4809. }
  4810. ```
  4811. ## 定义子画布节点
  4812. ```tsx pure
  4813. import { SubCanvasRender } from '@flowgram.ai/free-container-plugin';
  4814. export const LoopNodeRegistry: FlowNodeRegistry = {
  4815. type: 'loop',
  4816. info: {
  4817. icon: iconLoop,
  4818. description:
  4819. 'Used to repeatedly execute a series of tasks by setting the number of iterations and logic.',
  4820. },
  4821. meta: {
  4822. /**
  4823. * 子画布标记
  4824. */
  4825. isContainer: true,
  4826. /**
  4827. * 子画布默认大小设置
  4828. */
  4829. size: {
  4830. width: 560,
  4831. height: 400,
  4832. },
  4833. /**
  4834. * 子画布 padding 设置
  4835. */
  4836. padding: () => ({ // 容器 padding 设置
  4837. top: 150,
  4838. bottom: 100,
  4839. left: 100,
  4840. right: 100,
  4841. }),
  4842. /**
  4843. * 控制子画布内的节点选中状态
  4844. */
  4845. selectable(node: WorkflowNodeEntity, mousePos?: PositionSchema): boolean {
  4846. if (!mousePos) {
  4847. return true;
  4848. }
  4849. const transform = node.getData<FlowNodeTransformData>(FlowNodeTransformData);
  4850. // 鼠标开始时所在位置不包括当前节点时才可选中
  4851. return !transform.bounds.contains(mousePos.x, mousePos.y);
  4852. },
  4853. },
  4854. formMeta: {
  4855. render: () => (
  4856. <div>
  4857. { /* others */ }
  4858. <SubCanvasRender />
  4859. </div>
  4860. )
  4861. }
  4862. }
  4863. ```
  4864. ---
  4865. url: /guide/advanced/history.md
  4866. ---
  4867. # 历史记录
  4868. Undo/Redo 是 FlowGram.AI 的一个插件,在 @flowgram.ai/fixed-layout-editor 和 @flowgram.ai/free-layout-editor 两种模式的编辑器中均有提供该功能。
  4869. ## 1. 快速开始
  4870. [> Demo Detail](https://github.com/bytedance/flowgram.ai/blob/main/apps/demo-fixed-layout/src/hooks/use-editor-props.ts#L125)
  4871. ### 1.1. 开启 history
  4872. 使用 Undo/Redo 功能前需要先引入编辑器,以固定布局编辑器为例。
  4873. 1. package.json 添加依赖
  4874. ```tsx pure title="use-editor-props.tsx" {4}
  4875. export function useEditorProps() {
  4876. return useMemo(
  4877. () => ({
  4878. history: {
  4879. enable: true,
  4880. enableChangeNode: true // Listen Node engine data change
  4881. }
  4882. })
  4883. )
  4884. }
  4885. ```
  4886. 开启之后将获得以下能力:
  4887. 简介
  4888. <td>描述</td>
  4889. <td>自由布局</td>
  4890. <td>固定布局</td>
  4891. Undo/Redo 快捷键
  4892. <td>画布上使用 Cmd/Ctrl + Z 触发 Undo</td>
  4893. <td>✅</td>
  4894. <td>✅</td>
  4895. 画布上使用 Cmd/Ctrl + Shift + Z 触发 Redo
  4896. <td>✅</td>
  4897. <td>✅</td>
  4898. 画布节点操作支持undo/redo
  4899. <td>增删节点 </td>
  4900. <td>✅</td>
  4901. <td>✅</td>
  4902. 增删连线
  4903. <td>✅</td>
  4904. <td>❌</td>
  4905. 移动节点
  4906. <td>✅</td>
  4907. <td>✅</td>
  4908. 增删分支
  4909. <td>❌</td>
  4910. <td>✅</td>
  4911. 移动分支
  4912. <td>❌</td>
  4913. <td>✅</td>
  4914. 添加分组
  4915. <td>❌</td>
  4916. <td>✅</td>
  4917. 取消分组
  4918. <td>❌</td>
  4919. <td>✅</td>
  4920. 画布批量操作
  4921. <td>删除节点</td>
  4922. <td>✅</td>
  4923. <td>✅</td>
  4924. 移动节点
  4925. <td>✅</td>
  4926. <td>✅</td>
  4927. ### 1.2. 关闭 history
  4928. 如果某些系统触发的数据变更不希望被undo redo监听到,可以主动关掉 历史服务 操作完数据再重新启动
  4929. ```tsx pure
  4930. const { history } = useClientContext();
  4931. history.stop()
  4932. // 做一些不希望被捕获的操作, 这些变更不会被记录到操作栈
  4933. ...
  4934. history.start()
  4935. ```
  4936. ### 1.3. Undo/Redo 调用
  4937. 一般 Undo/Redo 会在界面上提供两个按钮入口,点击了能触发 Undo 和 Redo,按钮本身需要有是否可以 Undo/Redo 的状态。
  4938. ```tsx pure
  4939. export function useUndoRedo(): UndoRedo {
  4940. const { history } = useClientContext();
  4941. const [canUndo, setCanUndo] = useState(false);
  4942. const [canRedo, setCanRedo] = useState(false);
  4943. useEffect(() => {
  4944. const toDispose = history.undoRedoService.onChange(() => {
  4945. setCanUndo(history.canUndo());
  4946. setCanRedo(history.canRedo());
  4947. });
  4948. return () => {
  4949. toDispose.dispose();
  4950. };
  4951. }, []);
  4952. return {
  4953. canUndo,
  4954. canRedo,
  4955. undo: () => history.undo(),
  4956. redo: () => history.redo(),
  4957. };
  4958. }
  4959. ```
  4960. ## 2. 功能扩展
  4961. ### 2.1. 操作注册
  4962. 操作通过 operationMetas 去注册操作
  4963. ```tsx pure title="use-editor-props.tsx"
  4964. ...
  4965. history={{
  4966. enable: true,
  4967. operationMetas: [
  4968. {
  4969. type: 'addNode',
  4970. apply: () => { console.log('addNode')},
  4971. inverse: (op) => ({ type: 'deleteNode', value: op.value })
  4972. }
  4973. ]
  4974. }}
  4975. ```
  4976. `OperationMeta` 核心定义如下
  4977. * `type` 是操作的唯一标识
  4978. * `inverse` 是一个函数,该函数返回当前操作的逆操作
  4979. * `apply` 是操作被触发的时候执行的逻辑
  4980. ```tsx pure
  4981. export interface OperationMeta {
  4982. /**
  4983. * 操作类型 需要唯一
  4984. */
  4985. type: string;
  4986. /**
  4987. * 将一个操作转换成另一个逆操作, 如insert转成delete
  4988. * @param op 操作
  4989. * @returns 逆操作
  4990. */
  4991. inverse: (op: Operation) => Operation;
  4992. /**
  4993. * 执行操作
  4994. * @param operation 操作
  4995. */
  4996. apply(operation: Operation, source: any): void | Promise<void>;
  4997. }
  4998. ```
  4999. 假设我要做增删节点支持 Undo/Redo 的功能,我就需要添加两个操作
  5000. ```tsx pure
  5001. {
  5002. type: 'addNode',
  5003. inverse: op => ({ ...op, type: 'deleteNode' }),
  5004. apply(op, ctx) {
  5005. document = ctx.get(Document)
  5006. document.addNode(op.value)
  5007. },
  5008. }
  5009. ```
  5010. ```tsx pure
  5011. {
  5012. type: 'deleteNode',
  5013. inverse: op => ({ ...op, type: 'addNode' }),
  5014. apply(op, ctx) {
  5015. document = ctx.get(Document)
  5016. document.deleteNode(op.value.id)
  5017. },
  5018. }
  5019. ```
  5020. ### 2.2. 操作合并
  5021. operationMeta 支持 shouldMerge 来自定义合并策略,如果频繁触发的操作可以进行合并
  5022. :::warning shouldMerge 返回
  5023. * 返回 false 代表不合并
  5024. * 返回 true 代表合并进一个操作栈元素
  5025. * 返回 Operation 代表合并成一个操作
  5026. :::
  5027. 以下示例是一个合并 500ms 内对同一个字段编辑进行合并
  5028. ```tsx pure
  5029. {
  5030. type: 'changeData',
  5031. inverse: op => ({ ...op, type: 'changeData' }),
  5032. apply(op, ctx) {},
  5033. shouldMerge: (op, prev, element) => {
  5034. // 合并500ms内的操作
  5035. if (Date.now() - element.getTimestamp() < 500) {
  5036. if (
  5037. op.type === prev.type && // 相同类型
  5038. op.value.id === prev.value.id && // 相同节点
  5039. op.value?.path === prev.value?.path // 相同路径
  5040. ) {
  5041. return {
  5042. type: op.type,
  5043. value: {
  5044. ...op.value,
  5045. value: op.value.value,
  5046. oldValue: prev.value.oldValue,
  5047. },
  5048. };
  5049. }
  5050. }
  5051. return false;
  5052. }
  5053. }
  5054. ```
  5055. ### 2.3. 操作执行
  5056. 1. 单操作执行
  5057. 通过 pushOperation 触发, 如下示例使用方在业务中触发刚刚定义的操作
  5058. ```tsx pure
  5059. function handleAddNode () {
  5060. const { history } = useClientContext()
  5061. history.pushOperation({
  5062. type: 'addNode',
  5063. value: {
  5064. name: 'xx'
  5065. id: 'xxx'
  5066. }
  5067. })
  5068. }
  5069. ```
  5070. 2. 批量执行
  5071. 通过 transact 调用的函数中所有执行的操作都会被合并进一个栈元素, undo/redo 的时候会被一起执行
  5072. 如下是实现了一个批量删除的例子:
  5073. ```tsx pure
  5074. function deleteNodes(nodes: FlowNodeEntity[]) {
  5075. const { history } = useClientContext()
  5076. history.transact(() => {
  5077. nodes.forEach(node => {
  5078. history.pushOperation({
  5079. type: OperationType.deleteNode,
  5080. value: {
  5081. fromId: fromNode.id,
  5082. data: node.data,
  5083. },
  5084. });
  5085. });
  5086. });
  5087. }
  5088. ```
  5089. ### 2.4. 撤销重做
  5090. 1. 撤销重做
  5091. 撤销执行 history.undo 方法
  5092. 重做执行 history.redo 方法
  5093. ```tsx pure
  5094. function undo() {
  5095. const { history } = useClientContext();
  5096. history.undo();
  5097. }
  5098. function redo() {
  5099. const { history } = useClientContext();
  5100. history.redo();
  5101. }
  5102. ```
  5103. 2. 监听撤销重做
  5104. 监听 undoRedoService.onChange 的 onChange 事件即可
  5105. 如下是一个 undo/redo 触发后路由对应操作的uri(选中对应节点或表单项)
  5106. ```tsx pure
  5107. function listenHistoryChange() {
  5108. const { history } = useClientContext();
  5109. history.undoRedoService.onChange(
  5110. ({ type, element }) => {
  5111. if (type === UndoRedoChangeType.PUSH) {
  5112. return;
  5113. }
  5114. const op = element.getLastOperation();
  5115. if (!op) {
  5116. return;
  5117. }
  5118. if (op.uri) {
  5119. // goto somewhere
  5120. }
  5121. },
  5122. )
  5123. }
  5124. ```
  5125. ### 2.5. 操作历史
  5126. 1. 查看刷新
  5127. 可以通过 HistoryStack.items 获得历史记录, 通过监听 HistoryStack.onChange 事件来刷新界面
  5128. ```tsx pure
  5129. import React from 'react';
  5130. export function HistoryList() {
  5131. const { historyStack } = useService<HistoryManager>(HistoryManager)
  5132. const { refresh } = useRefresh()
  5133. let items = historyManager.historyStack.items;
  5134. useEffect(() => {
  5135. const disposable = historyStack.onChange(() => {
  5136. refresh()
  5137. ])
  5138. return () => {
  5139. disposable.dispose()
  5140. }
  5141. }, [])
  5142. return (
  5143. <ul>
  5144. {items.map((item, index) => (
  5145. <li key={index}>
  5146. <div>
  5147. {item.type}({item.id}):
  5148. {item.operations.map((o, index) => (
  5149. <Tooltip
  5150. key={index}
  5151. title={(o.description || '') + `----uri: ${o.uri?.displayName}`}
  5152. >
  5153. {o.label || o.type}
  5154. </Tooltip>
  5155. ))}
  5156. </div>
  5157. </li>
  5158. ))}
  5159. </ul>
  5160. );
  5161. }
  5162. ```
  5163. 2. 持久化
  5164. 持久化是通过 history-storage 插件实现
  5165. * databaseName: 数据库名称
  5166. * resourceStorageLimit: 资源存储限制数量
  5167. 引入 @flowgram.ai/history-storage 包后,可使用该插件
  5168. ```tsx pure
  5169. import { createHistoryStoragePlugin } from '@flowgram.ai/history-storage';
  5170. createHistoryStoragePlugin({
  5171. databaseName: 'your-history',
  5172. resourceStorageLimit: 50,
  5173. }),
  5174. ```
  5175. 通过 useStorageHistoryItems 查询数据库列表
  5176. ```tsx pure
  5177. import {
  5178. useStorageHistoryItems,
  5179. } from '@flowgram.ai/history-storage';
  5180. export const HistoryList = () => {
  5181. const { uri } = useCurrentWidget();
  5182. const { items } = useStorageHistoryItems(
  5183. storage,
  5184. uri.withoutQuery().toString(),
  5185. );
  5186. return <>
  5187. { JSON.stringify(items) }
  5188. </>
  5189. }
  5190. ```
  5191. ## 3. API 列表
  5192. ### 3.1. [OperationMeta](https://flowgram.ai/auto-docs/fixed-history-plugin/interfaces/OperationMeta.html)
  5193. 操作元数据,用以定义一个操作
  5194. ### 3.2. [Operation](https://flowgram.ai/auto-docs/fixed-history-plugin/interfaces/Operation.html)
  5195. 操作数据,通过 type 和 OperationMeta 关联
  5196. ### 3.3. [OperationService](https://flowgram.ai/auto-docs/fixed-history-plugin/classes/OperationService.html)
  5197. [onApply](https://flowgram.ai/auto-docs/fixed-history-plugin/classes/OperationService.html#onapply)
  5198. 想监听某个触发的操作可以使用onApply
  5199. ```tsx pure
  5200. useService(OperationService).onApply((op: Operation) => {
  5201. console.log(op)
  5202. // 此处可以根据type执行自己的业务逻辑
  5203. })
  5204. ```
  5205. ### 3.4. [HistoryService](https://flowgram.ai/auto-docs/fixed-history-plugin/classes/HistoryService.html)
  5206. History 模块核心 API 暴露的Service
  5207. ### 3.5. [UndoRedoService](https://flowgram.ai/auto-docs/fixed-history-plugin/classes/UndoRedoService.html)
  5208. 管理 UndoRedo 栈的服务
  5209. ### 3.6. [HistoryStack](https://flowgram.ai/auto-docs/fixed-history-plugin/classes/HistoryStack.html)
  5210. 历史栈,监听所有 push undo redo 操作,并记录到栈里面
  5211. ### 3.7. [HistoryDatabase](https://flowgram.ai/auto-docs/history-storage/classes/HistoryDatabase.html)
  5212. 持久化数据库操作
  5213. ---
  5214. url: /guide/advanced/minimap.md
  5215. ---
  5216. # 缩略图
  5217. ## EditorProps
  5218. ```ts pure
  5219. import { createMinimapPlugin } from '@flowgram.ai/minimap-plugin'
  5220. {
  5221. plugins: () => [
  5222. /**
  5223. * Minimap plugin
  5224. */
  5225. createMinimapPlugin({
  5226. disableLayer: true,
  5227. enableDisplayAllNodes: true,
  5228. canvasStyle: {
  5229. canvasWidth: 182,
  5230. canvasHeight: 102,
  5231. canvasPadding: 50,
  5232. canvasBackground: 'rgba(245, 245, 245, 1)',
  5233. canvasBorderRadius: 10,
  5234. viewportBackground: 'rgba(235, 235, 235, 1)',
  5235. viewportBorderRadius: 4,
  5236. viewportBorderColor: 'rgba(201, 201, 201, 1)',
  5237. viewportBorderWidth: 1,
  5238. viewportBorderDashLength: 2,
  5239. nodeColor: 'rgba(255, 255, 255, 1)',
  5240. nodeBorderRadius: 2,
  5241. nodeBorderWidth: 0.145,
  5242. nodeBorderColor: 'rgba(6, 7, 9, 0.10)',
  5243. overlayColor: 'rgba(255, 255, 255, 0)',
  5244. },
  5245. inactiveDebounceTime: 1,
  5246. }),
  5247. ]
  5248. }
  5249. ```
  5250. ## 缩略图组件
  5251. ```tsx pure
  5252. import { FlowMinimapService, MinimapRender } from '@flowgram.ai/minimap-plugin';
  5253. import { useService } from '@flowgram.ai/editor'; // 或 free-layout-editor
  5254. export const Minimap = () => {
  5255. const minimapService = useService(FlowMinimapService);
  5256. return (
  5257. <div
  5258. style={{
  5259. position: 'absolute',
  5260. left: 16,
  5261. bottom: 51,
  5262. zIndex: 100,
  5263. width: 182,
  5264. }}
  5265. >
  5266. <MinimapRender
  5267. service={minimapService}
  5268. containerStyles={{
  5269. pointerEvents: 'auto',
  5270. position: 'relative',
  5271. top: 'unset',
  5272. right: 'unset',
  5273. bottom: 'unset',
  5274. left: 'unset',
  5275. }}
  5276. inactiveStyle={{
  5277. opacity: 1,
  5278. scale: 1,
  5279. translateX: 0,
  5280. translateY: 0,
  5281. }}
  5282. />
  5283. </div>
  5284. );
  5285. };
  5286. ```
  5287. ---
  5288. url: /guide/advanced/shortcuts.md
  5289. ---
  5290. # 快捷键
  5291. ## 自定义快捷键
  5292. ```ts pure
  5293. // 添加到 EditorProps
  5294. {
  5295. shortcuts(shortcutsRegistry, ctx) {
  5296. // 按住 cmmand + a,选中所有节点
  5297. shortcutsRegistry.addHandlers({
  5298. commandId: 'selectAll',
  5299. shortcuts: ['meta a', 'ctrl a'],
  5300. isEnabled: (...args) => true,
  5301. execute(...args) {
  5302. const allNodes = ctx.document.getAllNodes();
  5303. ctx.playground.selectionService.selection = allNodes;
  5304. },
  5305. });
  5306. },
  5307. }
  5308. ```
  5309. ## 通过 CommandService 调用快捷键
  5310. ```ts pure
  5311. const commandService = useService(CommandService)
  5312. /**
  5313. * 调用命令服务, args 参数会透传给 execute 和 isEnabled
  5314. */
  5315. commandService.executeCommand('selectAll', ...args)
  5316. // OR
  5317. ctx.get(CommandService).executeCommand('selectAll', ...args)
  5318. ```
  5319. ---
  5320. url: /guide/advanced/variable/basic.md
  5321. ---
  5322. # 变量基础
  5323. ## 业务背景
  5324. 在 Workflow 编排中,节点与节点之间需要传递信息。为了实现这一点,我们使用**变量**来存储和管理这些信息。
  5325. :::warning 一个变量由三个主要部分组成:
  5326. 1. **唯一标识符**:变量的名字,用于区分不同的变量,以便在程序中可以准确地引用和使用它。如:`userName` 或 `totalAmount`。
  5327. 2. **值**:变量存储的数据。值可以是多种类型,比如数字(如 `42`)、字符串(如 `"Hello!"`)、布尔值(如 `true`)等。
  5328. 3. **类型**:变量可以存储的数据种类。类型决定了变量可以接受什么样的值。例如,一个变量可以是整数、浮点数、字符串或布尔值等。
  5329. :::
  5330. 下面是一个流程编排的例子:WebSearch 节点获取到知识,通过 natural\_language\_desc 传递到 LLM 节点进行分析
  5331. 在该例子中:
  5332. 1\. WebSearch 节点将信息(值)存在 natural\_language\_desc 为唯一标识符的变量内
  5333. 2\. LLM 节点通过 natural\_language\_desc 唯一标识符获取到知识库检索的信息(值),并传入 LLM 节点进行分析
  5334. 3\. natural\_language\_desc 变量的类型为字符串,代表在网络中检索到的信息内容,例如 "DeepSeek 今日有新模型发布"
  5335. ## 什么是变量引擎?
  5336. 变量引擎是 Flowgram 提供的一个可选内置功能,可以帮助 Workflow 设计时更高效地实现**变量信息编排**。它可以实现以下功能:
  5337. 作用域约束控制
  5338. <p className="rs-tip">通过变量引擎,你可以控制变量的作用域,确保变量在合适的范围内可用,避免不必要的冲突。</p>
  5339. 图中 Start 节点的 query 变量,可被后续的 LLM 节点和 End 节点访问
  5340. 图中 LLM 节点在 Condition 分支内,End 节点在 Condition 分支外;因此 End 节点的变量选择器无法选择到 LLM 节点上的 result 变量
  5341. 变量信息树的维护
  5342. <p className="rs-tip">变量引擎可以帮助你构建一个清晰的变量信息树,方便你查看和管理所有变量的状态和关系。</p>
  5343. 图中展示了多个节点 + 全局配置的输出变量;其中部分变量包含了多个子变量,形成了一棵树的结构
  5344. 变量类型自动联动推导
  5345. <p className="rs-tip">变量引擎能够根据上下文自动推导变量的类型,减少手动指定类型的工作量,提高开发效率。</p>
  5346. 图中的 Batch 节点对 Start 节点的 arr 变量进行了批处理,当 arr 变量的类型变动时,Batch 节点批处理输出的 item 变量类型也随之变动
  5347. ## 开启变量引擎
  5348. [> API Detail](https://flowgram.ai/auto-docs/editor/interfaces/VariablePluginOptions.html)
  5349. ```tsx pure title="use-editor-props.ts" {3}
  5350. // EditorProps
  5351. {
  5352. variableEngine: {
  5353. /**
  5354. * 需要开启变量引擎才能使用
  5355. */
  5356. enable: true
  5357. }
  5358. }
  5359. ```
  5360. ---
  5361. url: /guide/advanced/variable/variable-consume.md
  5362. ---
  5363. # 消费变量
  5364. ## 在节点内获取变量树
  5365. ### 获取变量列表
  5366. ```tsx pure title="use-variable-tree.tsx"
  5367. import {
  5368. type BaseVariableField,
  5369. useScopeAvailable,
  5370. } from '@flowgram.ai/fixed-layout-editor';
  5371. // .... Inside react hooks or component
  5372. const available = useScopeAvailable()
  5373. const renderVariable = (variable: BaseVariableField) => {
  5374. // ....
  5375. }
  5376. return available.variables.map(renderVariable)
  5377. // ....
  5378. ```
  5379. ### 获取 Object 类型变量的下钻
  5380. ```tsx pure title="use-variable-tree.tsx"
  5381. import {
  5382. type BaseVariableField,
  5383. ASTMatch,
  5384. } from '@flowgram.ai/fixed-layout-editor';
  5385. // ....
  5386. const renderVariable = (variable: BaseVariableField) => ({
  5387. title: variable.meta?.title,
  5388. key: variable.key,
  5389. // Only Object Type can drilldown
  5390. children: ASTMatch.isObject(type) ? type.properties.map(renderVariable) : [],
  5391. });
  5392. // ....
  5393. ```
  5394. ### 获取 Array 类型变量的下钻
  5395. ```tsx pure title="use-variable-tree.tsx"
  5396. import {
  5397. type BaseVariableField,
  5398. type BaseType,
  5399. ASTMatch,
  5400. } from '@flowgram.ai/fixed-layout-editor';
  5401. // ....
  5402. const getTypeChildren = (type?: BaseType): BaseVariableField[] => {
  5403. if (!type) return [];
  5404. // get properties of Object
  5405. if (ASTMatch.isObject(type)) return type.properties;
  5406. // get items type of Array
  5407. if (ASTMatch.isArray(type)) return getTypeChildren(type.items);
  5408. };
  5409. const renderVariable = (variable: BaseVariableField) => ({
  5410. title: variable.meta?.title,
  5411. key: variable.key,
  5412. // Only Object Type can drilldown
  5413. children: getTypeChildren(variable.type).map(renderVariable),
  5414. });
  5415. // ....
  5416. ```
  5417. ## 直接使用 VariableSelector 官方物料
  5418. 详见: [官方表单物料](/guide/advanced/form-materials.md)
  5419. [VariableSelector](https://github.com/bytedance/flowgram.ai/tree/main/packages/materials/form-materials/src/components/variable-selector/index.tsx) 组件用于选择单个变量
  5420. 通过包引用使用:
  5421. ```tsx
  5422. import { VariableSelector } from '@flowgram.ai/materials'
  5423. ```
  5424. 通过 CLI 复制源代码使用:
  5425. ```bash
  5426. npx @flowgram.ai/materials components/variable-selector
  5427. ```
  5428. ---
  5429. url: /guide/advanced/variable/variable-output.md
  5430. ---
  5431. # 输出变量
  5432. ## 输出节点变量
  5433. ### FlowNodeVariableData 输出变量
  5434. `Flowgram` 基于 [`ECS`](https://flowgram.ai/guide/concepts/ecs.html) (Entity-Component-System) 来实现节点信息的管理。
  5435. 其中 [`FlowNodeVariableData`](https://flowgram.ai/auto-docs/editor/classes/FlowNodeVariableData.html) 是节点 `FlowNodeEntity` 上的一个 `Component`,专门用于处理节点上输出的 **变量信息**。
  5436. 下面的 Demo 展示了:如何拿到 `FlowNodeVariableData`, 并且通过 `FlowNodeVariableData` 实现在节点上输出变量
  5437. ```tsx pure title="sync-variable-plugin.tsx"
  5438. import {
  5439. FlowNodeVariableData,
  5440. ASTFactory,
  5441. } from '@flowgram.ai/fixed-layout-editor';
  5442. // ....
  5443. flowDocument.onNodeCreate(({ node }) => {
  5444. const variableData = node.getData<FlowNodeVariableData>(FlowNodeVariableData);
  5445. // ....
  5446. // 1. Clear VariableData if No value
  5447. variableData.clearVar()
  5448. // 2. Set a String Variable as output
  5449. variableData.setVar(
  5450. ASTFactory.createVariableDeclaration({
  5451. meta: {
  5452. title: `Your Output Variable Title`,
  5453. },
  5454. key: `your_variable_global_unique_key_${node.id}`,
  5455. type: ASTFactory.createString(),
  5456. })
  5457. )
  5458. // 3. Set a Complicated Variable Data as output
  5459. variableData.setVar(
  5460. ASTFactory.createVariableDeclaration({
  5461. meta: {
  5462. title: `Your Output Variable Title`,
  5463. },
  5464. key: `your_variable_global_unique_key_${node.id}`,
  5465. type: ASTFactory.createArray({
  5466. items: ASTFactory.createObject({
  5467. properties: [
  5468. ASTFactory.createProperty({
  5469. key: 'stringType',
  5470. type: ASTFactory.createString(),
  5471. }),
  5472. ASTFactory.createProperty({
  5473. key: 'booleanType',
  5474. type: ASTFactory.createBoolean(),
  5475. }),
  5476. ASTFactory.createProperty({
  5477. key: 'numberType',
  5478. type: ASTFactory.createNumber(),
  5479. }),
  5480. ASTFactory.createProperty({
  5481. key: 'integerType',
  5482. type: ASTFactory.createInteger(),
  5483. }),
  5484. ],
  5485. }),
  5486. }),
  5487. })
  5488. );
  5489. // 4. Get Variable for current Node
  5490. console.log(variableData.getVar())
  5491. // ....
  5492. })
  5493. // ....
  5494. ```
  5495. 详见: [> Demo Detail](https://github.com/bytedance/flowgram.ai/blob/main/apps/demo-fixed-layout/src/plugins/sync-variable-plugin/sync-variable-plugin.ts#L25)
  5496. ### 一个节点设置多个输出变量
  5497. ```tsx pure title="sync-variable-plugin.tsx"
  5498. import {
  5499. FlowNodeVariableData,
  5500. ASTFactory,
  5501. } from '@flowgram.ai/fixed-layout-editor';
  5502. // ....
  5503. flowDocument.onNodeCreate(({ node }) => {
  5504. const variableData = node.getData<FlowNodeVariableData>(FlowNodeVariableData);
  5505. // ...
  5506. // 1. Create, Update, Read, Delete Variable in namespace_1
  5507. variableData.setVar(
  5508. 'namespace_1',
  5509. ASTFactory.createVariableDeclaration({
  5510. meta: {
  5511. title: `Your Output Variable Title`,
  5512. },
  5513. key: `your_variable_global_unique_key_${node.id}`,
  5514. type: ASTFactory.createString(),
  5515. })
  5516. )
  5517. console.log(variableData.getVar('namespace_1'))
  5518. variableData.clearVar('namespace_1')
  5519. // ....
  5520. // 2. Create, Update, Read, Delete Variable in namespace_2
  5521. variableData.setVar(
  5522. 'namespace_2',
  5523. ASTFactory.createVariableDeclaration({
  5524. meta: {
  5525. title: `Your Output Variable Title 2`,
  5526. },
  5527. key: `your_variable_global_unique_key_${node.id}_2`,
  5528. type: ASTFactory.createString(),
  5529. })
  5530. )
  5531. console.log(variableData.getVar('namespace_2'))
  5532. variableData.clearVar('namespace_2')
  5533. // ....
  5534. })
  5535. // ....
  5536. ```
  5537. 更多用法,详见:[Class: FlowNodeVariableData](https://flowgram.ai/auto-docs/editor/classes/FlowNodeVariableData.html)
  5538. ### 表单副作用设置输出变量
  5539. ```tsx pure title="node-registries.ts"
  5540. import {
  5541. FlowNodeRegistry,
  5542. createEffectFromVariableProvider,
  5543. ASTFactory,
  5544. type ASTNodeJSON
  5545. } from '@flowgram.ai/fixed-layout-editor';
  5546. export function createTypeFromValue(value: string): ASTNodeJSON | undefined {
  5547. switch (value) {
  5548. case 'string':
  5549. return ASTFactory.createString();
  5550. case 'number':
  5551. return ASTFactory.createNumber();
  5552. case 'boolean':
  5553. return ASTFactory.createBoolean();
  5554. case 'integer':
  5555. return ASTFactory.createInteger();
  5556. default:
  5557. return;
  5558. }
  5559. }
  5560. export const nodeRegistries: FlowNodeRegistry[] = [
  5561. {
  5562. type: 'start',
  5563. formMeta: {
  5564. effect: {
  5565. // Create first variable
  5566. // = variableData.setVar('path.to.value', ASTFactory.createVariableDeclaration(parse(v)))
  5567. 'path.to.value': createEffectFromVariableProvider({
  5568. // parse form value to variable
  5569. parse(v: string) {
  5570. return {
  5571. meta: {
  5572. title: `Your Output Variable Title`,
  5573. },
  5574. key: `your_variable_global_unique_key_${node.id}`,
  5575. type: createTypeFromValue(v)
  5576. }
  5577. }
  5578. }),
  5579. // Create second variable
  5580. // = variableData.setVar('path.to.value2', ASTFactory.createVariableDeclaration(parse(v)))
  5581. 'path.to.value2': createEffectFromVariableProvider({
  5582. // parse form value to variable
  5583. parse(v: string) {
  5584. return {
  5585. meta: {
  5586. title: `Your Output Variable Title 2`,
  5587. },
  5588. key: `your_variable_global_unique_key_${node.id}_2`,
  5589. type: createTypeFromValue(v)
  5590. }
  5591. }
  5592. }),
  5593. },
  5594. render: () => (
  5595. // ...
  5596. )
  5597. },
  5598. }
  5599. ]
  5600. ```
  5601. ## 输出全局变量
  5602. ### 获取全局变量作用域
  5603. 全局作用域可以在 Plugin 中通过 `ctx` 获取:
  5604. ```tsx pure title="sync-variable-plugin.tsx"
  5605. import {
  5606. GlobalScope,
  5607. definePluginCreator,
  5608. PluginCreator
  5609. } from '@flowgram.ai/fixed-layout-editor';
  5610. export const createSyncVariablePlugin: PluginCreator<SyncVariablePluginOptions> =
  5611. definePluginCreator<SyncVariablePluginOptions, FixedLayoutPluginContext>({
  5612. onInit(ctx, options) {
  5613. const globalScope = ctx.get(GlobalScope)
  5614. globalScope.setVar(
  5615. ASTFactory.createVariableDeclaration({
  5616. meta: {
  5617. title: `Your Output Variable Title`,
  5618. },
  5619. key: `your_variable_global_unique_key`,
  5620. type: ASTFactory.createString(),
  5621. })
  5622. )
  5623. }
  5624. })
  5625. ```
  5626. 也可以在画布中的 React 组件内,通过 `useService` 获取全局作用域:
  5627. ```tsx pure title="global-variable-component.tsx"
  5628. import {
  5629. GlobalScope,
  5630. useService,
  5631. } from '@flowgram.ai/fixed-layout-editor';
  5632. function GlobalVariableComponent() {
  5633. const globalScope = useService(GlobalScope)
  5634. // ...
  5635. const handleChange = (v: string) => {
  5636. globalScope.setVar(
  5637. ASTFactory.createVariableDeclaration({
  5638. meta: {
  5639. title: `Your Output Variable Title`,
  5640. },
  5641. key: `your_variable_global_unique_key_${v}`,
  5642. type: ASTFactory.createString(),
  5643. })
  5644. )
  5645. }
  5646. return <Input onChange={handleChange}/>
  5647. }
  5648. ```
  5649. ### 全局作用域输出变量
  5650. [`GlobalScope`](https://flowgram.ai/auto-docs/editor/classes/GlobalScope.html) 输出变量的 API 和 `FlowNodeVariableData` 类似:
  5651. ```tsx pure title="sync-variable-plugin.tsx"
  5652. import {
  5653. GlobalScope,
  5654. } from '@flowgram.ai/fixed-layout-editor';
  5655. // ...
  5656. onInit(ctx, options) {
  5657. const globalScope = ctx.get(GlobalScope);
  5658. // 1. Create, Update, Read, Delete Variable in GlobalScope
  5659. globalScope.setVar(
  5660. ASTFactory.createVariableDeclaration({
  5661. meta: {
  5662. title: `Your Output Variable Title`,
  5663. },
  5664. key: `your_variable_global_unique_key`,
  5665. type: ASTFactory.createString(),
  5666. })
  5667. )
  5668. console.log(globalScope.getVar())
  5669. globalScope.clearVar()
  5670. // 2. Create, Update, Read, Delete Variable in GlobalScope's namespace: 'namespace_1'
  5671. globalScope.setVar(
  5672. 'namespace_1',
  5673. ASTFactory.createVariableDeclaration({
  5674. meta: {
  5675. title: `Your Output Variable Title 2`,
  5676. },
  5677. key: `your_variable_global_unique_key_2`,
  5678. type: ASTFactory.createString(),
  5679. })
  5680. )
  5681. console.log(globalScope.getVar('namespace_1'))
  5682. globalScope.clearVar('namespace_1')
  5683. // ...
  5684. }
  5685. ```
  5686. 详见:[Class: GlobalScope](https://flowgram.ai/auto-docs/editor/classes/GlobalScope.html)
  5687. ---
  5688. url: /guide/advanced/without-form.md
  5689. ---
  5690. # 不使用表单
  5691. 当节点引擎不开启,节点的 data 数据会存在 `node.getExtInfo` 中, 如下
  5692. ```tsx pure
  5693. export const useEditorProps = () => {
  5694. return {
  5695. // ...
  5696. nodeEngine: {
  5697. enable: false, // 不开启节点引擎,则无法使用 form
  5698. },
  5699. history: {
  5700. enable: true,
  5701. enableChangeNode: false // 不再监听表单数据变化
  5702. },
  5703. materials: {
  5704. /**
  5705. * Render Node
  5706. */
  5707. renderDefaultNode: ({ node }: WorkflowNodeProps) => {
  5708. return (
  5709. <WorkflowNodeRenderer className="demo-free-node" node={node}>
  5710. <input value={node.getExtInfo()?.title} onChange={e => node.updateExtInfo({ title: e.target.value})}/>
  5711. </WorkflowNodeRenderer>
  5712. );
  5713. },
  5714. },
  5715. // /...
  5716. }
  5717. }
  5718. ```
  5719. ---
  5720. url: /guide/concepts/canvas-engine.md
  5721. ---
  5722. import image0 from "@/public/layer-uml.jpg"
  5723. # 画布引擎
  5724. ## Playground
  5725. 画布引擎底层会提供一套自己的坐标系, 主要由 Playground 驱动
  5726. ```ts
  5727. interface Playground {
  5728. node: HTMLDivElement // 画布挂载的dom节点
  5729. toReactComponent() // 渲染为react 节点
  5730. readonly: boolean // 只读模式
  5731. config: PlaygroundConfigEntity // 包含 zoom,scroll 等画布数据
  5732. }
  5733. // hook 快速获取
  5734. const { playground } = useClientContext()
  5735. ```
  5736. ## Layer
  5737. :::warning P.S.
  5738. * 渲染层在底层建立了一套自己的坐标系,基于这个坐标系实现模拟滚动、缩放等逻辑,在算viewport时候节点也需要转换到该坐标系上
  5739. * 渲染按画布被拆分成多个层 (Layer),分层设计是基于ECS的数据切割思想,不同 Layer 只监听自己想要的数据,独立渲染不干扰,Layer 可以理解为ECS的 System,即最终Entity数据消费的地方
  5740. * Layer 实现了类mobx的observer响应式动态依赖收集,数据更新会触发 autorun或render
  5741. :::
  5742. <img alt="切面编程" src={image0} />
  5743. * Layer 生命周期
  5744. ```ts
  5745. interface Layer {
  5746. /**
  5747. * 初始化时候触发
  5748. */
  5749. onReady?(): void;
  5750. /**
  5751. * playground 大小变化时候会触发
  5752. */
  5753. onResize?(size: PipelineDimension): void;
  5754. /**
  5755. * playground focus 时候触发
  5756. */
  5757. onFocus?(): void;
  5758. /**
  5759. * playground blur 时候触发
  5760. */
  5761. onBlur?(): void;
  5762. /**
  5763. * 监听缩放
  5764. */
  5765. onZoom?(scale: number): void;
  5766. /**
  5767. * 监听滚动
  5768. */
  5769. onScroll?(scroll: { scrollX: number; scrollY: number }): void;
  5770. /**
  5771. * viewport 更新触发
  5772. */
  5773. onViewportChange?(): void;
  5774. /**
  5775. * readonly 或 disable 状态变化
  5776. * @param state
  5777. */
  5778. onReadonlyOrDisabledChange?(state: { disabled: boolean; readonly: boolean }): void;
  5779. /**
  5780. * 数据更新自动触发react render,如果不提供则不会调用react渲染
  5781. */
  5782. render?(): JSX.Element
  5783. }
  5784. ```
  5785. Layer的定位其实和 Unity 游戏引擎 提供的 [MonoBehaviour](https://docs.unity3d.com/ScriptReference/MonoBehaviour.html) 类似, Unity 游戏引擎的脚本扩展都是基于这个,可以认为是最核心的设计,底层也是基于 C# 提供的反射 (Reflection) 能力的依赖注入
  5786. ```C#
  5787. using System.Collections;
  5788. using System.Collections.Generic;
  5789. using UnityEngine;
  5790. public class MyMonoBehavior : MonoBehaviour
  5791. {
  5792. void Awake()
  5793. {
  5794. Debug.Log("Awake method is always called before application starts.");
  5795. }
  5796. void Start()
  5797. {
  5798. Debug.Log("Start method is always called after Awake().");
  5799. }
  5800. void Update()
  5801. {
  5802. Debug.Log("Update method is called in every frame.");
  5803. }
  5804. }
  5805. ```
  5806. * Layer 的响应式更新
  5807. ```ts
  5808. export class DemoLayer extends Layer {
  5809. // 任意的inversify模块 的注入
  5810. @inject(FlowDocument) document: FlowDocument
  5811. // 监听单个Entity
  5812. @observeEntity(SomeEntity) entity: SomeEntity
  5813. // 监听多个Entity
  5814. @observeEntities(SomeEntity) entities: SomeEntity[]
  5815. // 监听 Entity的数据块(ECS - Component)变化
  5816. @observeEntityDatas(SomeEntity, SomeEntityData) transforms: SomeEntityData[]
  5817. autorun() {}
  5818. render() {
  5819. return <div></div>
  5820. }
  5821. }
  5822. ```
  5823. ## FlowNodeEntity
  5824. * 节点是一颗树, 包含子节点 (blocks) 和父亲节点, 节点采用 ECS 架构
  5825. ```ts
  5826. inteface FlowNodeEntity {
  5827. id: string
  5828. blocks: FlowNodeEntity[]
  5829. pre?: FlowNodeEntity
  5830. next?: FlowNodeEntity
  5831. parent?: FlowNodeEntity
  5832. collapsed: boolean // 是否展开
  5833. getData(dataRegistry): NodeEntityData
  5834. addData(dataRegistry)
  5835. }
  5836. ```
  5837. ## FlowNodeTransformData 节点的位置及大小数据
  5838. ```ts
  5839. class FlowNodeTransformData {
  5840. localTransform: Matrix, // 相对偏移, 只相对于同一个Block的上一个Sibling节点的偏移
  5841. worldTransform: Matrix, // 绝对偏移, 相对于Parent和Sibling节点叠加后的偏移
  5842. delta:Point // 居中居左偏移, 和Matrix独立,每个节点自己控制
  5843. getSize(): Size, // 由自己(独立节点) 或者 子分支节点宽高间距计算得出
  5844. getBounds(): Rectangle // 由worldMatix及 size 计算得出, 用于最终渲染,该范围也可用于确定高亮选中区域
  5845. inputPoint(): Point // 输入点位置,一般是Block的第一个节点的中上位置(居中布局)
  5846. outputPoint(): Point // 输出点位置,默认是节点中下位置,但条件分支,是由内置结束节点等具体逻辑判断得出
  5847. // ...others
  5848. }
  5849. ```
  5850. ## FlowNodeRenderData 节点内容渲染数据
  5851. ```ts
  5852. class FlowNodeRenderData {
  5853. node: HTMLDivElement // 当前节点的dom
  5854. expanded:boolean // 是否展开
  5855. activated: boolean // 是否激活
  5856. hidden: boolean // 是否隐藏
  5857. // ...others
  5858. }
  5859. ```
  5860. ## FlowDocument
  5861. ```ts
  5862. interface FLowDocument {
  5863. root: FlowNodeEntity // 画布的根节点
  5864. fromJSON(data): void // 导入数据
  5865. toJSON(): FlowDocumentJSON // 导出数据
  5866. addNode(type: string, meta: any): FlowNodeEntity // 添加节点
  5867. travese(fn: (node: flowNodeEntity) => void, startNode = this.root) // 遍历
  5868. }
  5869. ```
  5870. ---
  5871. url: /guide/concepts/ecs.md
  5872. ---
  5873. # ECS
  5874. ## 为什么需要 ECS
  5875. :::warning ECS (Entity-Component-System)
  5876. 适合解耦大的数据对象,常用于游戏,游戏的每个角色(Entity)数据都非常庞大,需要拆分成如物理引擎相关数据、皮肤相关、角色属性等 (多个 Component),供不同的子系统(System)消费。流程的数据结构复杂,很适合用ECS做拆解
  5877. :::
  5878. ## 方案对比
  5879. 我们对比两个数据方案:
  5880. ### 1. ReduxStore 方案
  5881. ```jsx pure
  5882. const store = () => ({
  5883. nodes: [{
  5884. position: any
  5885. form: any
  5886. data3: any
  5887. }],
  5888. edges: []
  5889. })
  5890. function Playground() {
  5891. const { nodes } = useStore(store)
  5892. return nodes.map(node => <Node data={node} />)
  5893. }
  5894. ```
  5895. 优点:
  5896. * 中心化数据管理使用简单
  5897. 缺点:
  5898. * 中心化数据管理无法精确更新,带来性能瓶颈
  5899. * 扩展性差,节点新增一个数据,都耦合到一个 大JSON 里
  5900. ### 2. ECS 方案
  5901. 备注:
  5902. * NodeData 对应的是 ECS - Component
  5903. * Layer 对应 ECS - System
  5904. ```jsx pure
  5905. /**
  5906. * 画布文档数据
  5907. */
  5908. class FlowDocument {
  5909. /**
  5910. * * 节点数据定义, 节点创建时候会把数据实例化
  5911. */
  5912. nodeDefines: [
  5913. NodePositionData,
  5914. NodeFormData,
  5915. NodeLineData
  5916. ]
  5917. nodeEntities: Entity[] = []
  5918. }
  5919. /**
  5920. * 节点
  5921. */
  5922. class FlowNodeEntity {
  5923. id: string // 只有id 不带数据
  5924. getData: (dataId: string) => EntityData
  5925. }
  5926. // 渲染线条
  5927. class LinesLayer {
  5928. /**
  5929. * 内部通过 node.getData(NodeLineData) 获取对应的数据,下同
  5930. */
  5931. @observeEntityData(FlowNodeEntity, NodeLineData) lines: NodeLineData[]
  5932. render() {
  5933. // 渲染线条
  5934. return this.lines.map(line => <Line data={line} />)
  5935. }
  5936. }
  5937. // 渲染节点位置
  5938. class NodePositionsLayer {
  5939. @observeEntityData(FlowNodeEntity, NodePositionData) positions: NodePositionData[]
  5940. render() {
  5941. // 渲染位置及排版
  5942. }
  5943. }
  5944. // 渲染节点表单
  5945. class NodeFormsLayer {
  5946. @observeEntityData(FlowNodeEntity, NodeFormData) contents: NodeFormData[]
  5947. render() {
  5948. // 渲染节点内容
  5949. }
  5950. }
  5951. /**
  5952. * 画布实例,通过 Layer 分层渲染
  5953. */
  5954. class Playground {
  5955. layers: [
  5956. LinesLayer, // 线条渲染
  5957. NodePositionsLayer, // 位置渲染
  5958. NodeFormsLayer // 内容渲染
  5959. ],
  5960. render() {
  5961. // 画布分层渲染
  5962. return this.layers.map(layer => layer.render())
  5963. }
  5964. }
  5965. ```
  5966. 优点:
  5967. * 节点数据拆开来单独控制渲染,性能可做到精确更新
  5968. * 扩展性强,新增一个节点数据,则新增一个 XXXData + XXXLayer
  5969. 缺点:
  5970. * 有一定学习成本
  5971. ---
  5972. url: /guide/concepts/index.md
  5973. ---
  5974. import image0 from "@/public/canvas-engine.png"
  5975. # 概念
  5976. <img alt="FlowGramAI 架构" src={image0} />
  5977. * CanvasEngine:画布引擎负责绘制“点-线”构成的图, 保障大规模节点时的流畅性
  5978. * NodeEngine: 节点引擎提供 渲染、校验、数据修改等表单能力
  5979. * VariableEngine: 变量引擎引入作用域模型, 抽象各业务场景的变量
  5980. * Material: 物料库包含默认 ICON 等 UI, 业务接入后可覆盖扩展
  5981. ---
  5982. url: /guide/concepts/ioc.md
  5983. ---
  5984. import image0 from "@/public/weaving.png"
  5985. # IOC
  5986. ## 为什么需要 IOC
  5987. :::warning 几个概念
  5988. * 控制反转: Inversion of Control, 是面向对象中的一种设计原则,可以用来降低代码模块之间的耦合度,其中最常见的方式叫做依赖注入(Dependency Injection,简称DI)
  5989. * 领域逻辑:Domain Logic,也可以叫 业务逻辑(Business Logic),这些业务逻辑与特定的产品功能相关
  5990. * 面向切面编程:AOP (Aspect-Oriented Programming),最核心的设计原则是将软件系统拆分为公用逻辑 (横切,有贯穿的意味) 和 领域逻辑 (纵切)的多个个方面 (Aspect),横切部分可以被所有的 纵切 部分 “按需消费”
  5991. :::
  5992. 回答这个问题之前先了解切面编程,切面编程目的是将领域逻辑的粒度拆的更细,横切部分可被纵切 “按需消费” ,横切和纵切的连接也叫 织入 (Weaving),而 IOC 就是扮演 Weaving 注入到纵切的角色
  5993. <img alt="切面编程" src={image0} />
  5994. 理想的切面编程
  5995. ```ts
  5996. - myAppliation 提供业务逻辑
  5997. - service 特定的业务逻辑服务
  5998. - customDomainLogicService
  5999. - contributionImplement 钩子的注册实例化
  6000. - MyApplicationContributionImpl
  6001. - component 业务组件
  6002. - core 提供通用逻辑
  6003. - model 通用模型
  6004. - contribution 钩子接口
  6005. - LifecycleContribution 应用的生命周期
  6006. - CommandContribution
  6007. - service 公用的service的服务
  6008. - CommandService
  6009. - ClipboardService
  6010. - component 公用的组件
  6011. ```
  6012. ```ts
  6013. // IOC 的注入
  6014. @injectable()
  6015. export class CustomDomainLogicService {
  6016. @inject(FlowContextService) protected flowContextService: FlowContextService;
  6017. @inject(CommandService) protected commandService: CommandService;
  6018. @inject(SelectionService) protected selectionService: SelectionService;
  6019. }
  6020. // IOC 的接口声明
  6021. interface LifecycleContribution {
  6022. onInit(): void
  6023. onStart(): void
  6024. onDispose(): void
  6025. }
  6026. // IOC 的接口实现
  6027. @injectable()
  6028. export class MyApplicationContributionImpl implements LifecycleContribution {
  6029. onStart(): void {
  6030. // 特定的业务逻辑代码
  6031. }
  6032. }
  6033. // 手动挂在到生命周期钩子
  6034. bind(LifecycleContribution).toService(MyApplicationContributionImpl)
  6035. ```
  6036. :::warning IOC是切面编程的一种手段,引入后,底层模块可以以接口形式暴露给外部注册,带来的好处:
  6037. * 实现微内核 + 插件化的设计,实现插件的可插拔按需消费
  6038. * 可以让包拆得更干净,实现 feature 式的拆包
  6039. :::
  6040. ---
  6041. url: /guide/concepts/node-engine.md
  6042. ---
  6043. # 节点引擎
  6044. 节点引擎 NodeEngine 是一个流程节点逻辑的书写框架,让业务专注于业务自身的渲染与数据逻辑,无需关注画布以及节点间联动的底层api。与此同时,节点引擎沉淀了最佳的节点书写范式,帮助业务解决流程业务中可能遇到的各种问题, 如数据逻辑与渲染耦合等。
  6045. 节点引擎是可选启用的。如果你不存在以下这些复杂的节点逻辑,可以选择不启用节点引擎,自己维护节点数据与渲染。复杂节点逻辑如:1)节点不渲染也能校验或触发数据副作用;2)节点间联动丰富;3)redo/undo; 等等。
  6046. ## 基础概念
  6047. FlowNodeEntity
  6048. 流程节点模型。
  6049. FlowNodeRegistry
  6050. 流程节点的静态配置。
  6051. FormMeta
  6052. 节点引擎的静态配置。 配置在 FlowNodeRegistry 中的 formMeta 字段。
  6053. Form
  6054. 节点引擎中的表单。它维护节点的数据并提供渲染、校验、副作用等能力。他的模型 FormModel 提供节点数据的访问和修改及触发校验等能力。
  6055. Field
  6056. 节点表单中的某个渲染字段。注意, Form 已经提供了数据层的逻辑,Field 更多是一个渲染层的模型,它尽在表单字段渲染后才存在。
  6057. validate
  6058. 表单校验。通常有对单个字段的校验也有整体表单校验。
  6059. effect
  6060. 表单数据的副作用。通常指在表单数据发生一些事件时要触发特定逻辑。 如在某字段的数据变更时要同步一些信息到某个store,这个可以被称为一个effect。
  6061. FormPlugin
  6062. 表单插件。可以配置在formMeta 中,插件可以对表单进行一系列深度操作。如变量插件。
  6063. ---
  6064. url: /guide/concepts/reactflow.md
  6065. ---
  6066. # 对比 ReactFlow
  6067. [Reactflow](https://reactflow.dev/) 是很优秀的开源项目,架构及代码清晰,但偏流程渲染引擎的底层架构 (Node、Edge、Handle),需要在上层开发大量功能才能适配复杂场景(如 固定布局,需要对数据建模写布局算法), 高级功能收费。
  6068. 相比 Reactflow,FlowGram 的目标是提供流程编辑一整套开箱即用的解决方案。
  6069. * 下边是 Reactflow 官方提供的 pro 收费能力
  6070. | 付费功能 | FlowGram 是否支持 | 未来计划支持 |
  6071. |----------------------------------|------------------------|--------------|
  6072. | 分组 | 支持 | |
  6073. | redo/undo | 支持 | |
  6074. | copy/paste | 支持 | |
  6075. | HelpLines 辅助线 | 支持 | |
  6076. | 自定义节点及形状 | 支持 | |
  6077. | 自定义线条 | 支持 | |
  6078. | AutoLayout,自动布局整理 | 支持 | |
  6079. | ForceLayout,节点排斥效果 | 不支持 | No |
  6080. | Expand/Collapse | 支持 | |
  6081. | Collaborative 多人协同 | 不支持 | Yes |
  6082. | WorkflowBuilder 相当于固定布局完整案例 | 支持 | |
  6083. * Reactflow 事件都是绑定在原子化的 dom 节点上,且内置,交互定制成本高,需要理解它的源码才能深度开发,如下,在画布缩放很小时候无法选到点位
  6084. 由于 事件是绑定在 svg 上,svg 在缩放后很容易点不到
  6085. FlowGram 的事件是一种全局监听 mousemove 变化,并通过计算及 Threshold 大致确定位置,即使缩放很小也能点到, 同时支持线条重连
  6086. ---
  6087. url: /guide/concepts/variable-engine.md
  6088. ---
  6089. import image0 from "@/public/variable-engine.png"
  6090. import image1 from "@/public/varaible-zone.png"
  6091. # 变量引擎
  6092. ## 整体设计
  6093. ### 架构分层
  6094. :::warning 架构分层
  6095. 变量引擎设计上遵循 DIP(依赖反转)原则,按照 代码稳定性、抽象层次 以及和 业务的远近 分为三层:
  6096. * 变量抽象层:变量架构中抽象层次最高,代码也最为稳定的部分
  6097. * 变量实现层:变量架构中变动较大,不同业务之间通常存在调整的部分
  6098. * 变量业务层:变量架构中提供给业务的 Facade ,与画布引擎、节点引擎联动的部分
  6099. :::
  6100. <img alt="架构分层图" src={image0} />
  6101. ### 术语表
  6102. #### 🌟 作用域(Scope)
  6103. :::warning ⭐️⭐️⭐️ 定义:
  6104. 一种约定的空间,空间内 通过 AST 来描述变量声明和消费情况
  6105. * 约定的空间:空间是什么,完全由业务定义
  6106. * 在低代码设计态中,可以是一个节点、一个组件、一个右侧面板...
  6107. * 在一段代码中,可以是一行 Statement、一段代码块、一个函数、一个文件...
  6108. :::
  6109. 作用域的空间是什么?可以由不同的业务来划定。
  6110. #### 🌟 抽象语法树(AST)
  6111. :::warning 定义:
  6112. ⭐️⭐️⭐️ 一种协议,通过树的形式,组合 AST 节点,实现对变量信息的显式/隐式 CRUD
  6113. * AST 节点:AST 中可响应式的协议节点
  6114. * 显式 CRUD,如:业务显示设定一个变量的变量类型
  6115. * 隐式 CRUD,如:业务声明一个变量,变量会根据其初始化参数自动推导变量类型
  6116. :::
  6117. :::warning 作用域里面的变量、类型、表达式、结构体 等等变量信息... 本质上都是 AST 节点的组合
  6118. * 变量 -> VariableDeclaration 节点
  6119. * 表达式 -> Expression 节点
  6120. * 类型 -> TypeNode 节点
  6121. * 结构体 -> StructDeclaration 节点
  6122. :::
  6123. 参考链接:https://ts-ast-viewer.com/
  6124. #### 变量(Variable)
  6125. :::warning 定义:
  6126. 一种用于声明新变量的 AST 节点,通过唯一标识符 指向一个 在特定集合范围内变动的值
  6127. * 在特定集合范围内变动的值:变量的值必须在 变量类型 描述的范围内
  6128. * 唯一标识符:变量必须有一个唯一的 Key 值
  6129. :::
  6130. [JavaScript中的变量,唯一 Key + 指向一个变动的值](/@/public/variable-code.png.md)
  6131. #### 变量类型(Variable Type)
  6132. :::warning 定义:
  6133. ⭐️⭐️⭐️ 一种 AST 节点,用于约束一个变量,被约束的变量值只能在预先设定的集合范围内变动
  6134. * 一个变量可以绑定一个变量类型
  6135. :::
  6136. ### 变量引擎的形象理解
  6137. :::warning 想像这样一个变量引擎的世界:
  6138. * 通过一个个 作用域 来划定出一个个 国家
  6139. * 每个国家包含三大公民:声明、类型、表达式
  6140. * 国家与国家之间通过 作用域链 来实现交流
  6141. :::
  6142. <img alt="图解" src={image1} />
  6143. ---
  6144. url: /guide/contact-us.md
  6145. ---
  6146. # 联系我们
  6147. * Issues: [Issues](https://github.com/bytedance/flowgram.ai/issues)
  6148. * Discord: https://discord.gg/SwDWdrgA9f
  6149. * Lark: 通过 [注册飞书](https://www.feishu.cn/en/) 并扫描下边的二维码加入飞书群
  6150. ---
  6151. url: /guide/getting-started/create-fixed-layout-simple.md
  6152. ---
  6153. # 创建固定布局画布
  6154. 本案例可通过 `npx @flowgram.ai/create-app@latest fixed-layout-simple` 安装,完整代码及效果见:
  6155. 固定布局基础用法
  6156. 文件结构:
  6157. ```
  6158. - hooks
  6159. - use-editor-props.ts # 画布配置
  6160. - components
  6161. - base-node.tsx # 节点渲染
  6162. - tools.tsx # 画布工具栏
  6163. - initial-data.ts # 初始化数据
  6164. - node-registries.ts # 节点配置
  6165. - app.tsx # 画布入口
  6166. ```
  6167. ### 1. 画布入口
  6168. * `FixedLayoutEditorProvider`: 画布配置器, 内部会生成 react-context 供子组件消费
  6169. * `EditorRenderer`: 为最终渲染的画布,可以包装在其他组件下边方便定制画布位置
  6170. ```tsx pure title="app.tsx"
  6171. import {
  6172. FixedLayoutEditorProvider,
  6173. EditorRenderer,
  6174. } from '@flowgram.ai/fixed-layout-editor';
  6175. import '@flowgram.ai/fixed-layout-editor/index.css'; // 加载样式
  6176. import { useEditorProps } from './hooks/use-editor-props' // 画布详细的 props 配置
  6177. import { Tools } from './components/tools' // 画布工具
  6178. function App() {
  6179. const editorProps = useEditorProps()
  6180. return (
  6181. <FixedLayoutEditorProvider {...editorProps}>
  6182. <EditorRenderer className="demo-editor" />
  6183. <Tools />
  6184. </FixedLayoutEditorProvider>
  6185. );
  6186. }
  6187. ```
  6188. ### 2. 配置画布
  6189. 画布配置采用声明式,提供 数据、渲染、事件、插件相关配置
  6190. ```tsx pure title="hooks/use-editor-props.tsx"
  6191. import { useMemo } from 'react';
  6192. import { type FixedLayoutProps } from '@flowgram.ai/fixed-layout-editor';
  6193. import { defaultFixedSemiMaterials } from '@flowgram.ai/fixed-semi-materials';
  6194. import { createMinimapPlugin } from '@flowgram.ai/minimap-plugin';
  6195. import { initialData } from './initial-data' // 初始化数据
  6196. import { nodeRegistries } from './node-registries' // 节点声明配置
  6197. import { BaseNode } from './base-node' // 节点渲染
  6198. export function useEditorProps(
  6199. ): FixedLayoutProps {
  6200. return useMemo<FixedLayoutProps>(
  6201. () => ({
  6202. /**
  6203. * 初始化数据
  6204. */
  6205. initialData,
  6206. /**
  6207. * 画布节点定义
  6208. */
  6209. nodeRegistries,
  6210. /**
  6211. * 可以通过 key 自定义 UI 组件, 比如添加按钮,这里提供了一套 semi 组件方便快速验证, 如果需要深度定制,参考:
  6212. * https://github.com/bytedance/flowgram.ai/blob/main/packages/materials/fixed-semi-materials/src/components/index.tsx
  6213. */
  6214. materials: {
  6215. components: {
  6216. ...defaultFixedSemiMaterials,
  6217. // [FlowRendererKey.ADDER]: NodeAdder,
  6218. // [FlowRendererKey.BRANCH_ADDER]: BranchAdder,
  6219. },
  6220. renderDefaultNode: BaseNode, // 节点渲染组件
  6221. },
  6222. /**
  6223. * 节点引擎, 用于渲染节点表单
  6224. */
  6225. nodeEngine: {
  6226. enable: true,
  6227. },
  6228. /**
  6229. * 画布历史记录, 用于控制 redo/undo
  6230. */
  6231. history: {
  6232. enable: true,
  6233. enableChangeNode: true, // 用于监听节点表单数据变化
  6234. },
  6235. /**
  6236. * 画布初始化回调
  6237. */
  6238. onInit: ctx => {
  6239. // 如果要动态加载数据,可以通过如下方法异步执行
  6240. // ctx.docuemnt.fromJSON(initialData)
  6241. },
  6242. /**
  6243. * 画布第一次渲染完成回调
  6244. */
  6245. onAllLayersRendered: (ctx) => {},
  6246. /**
  6247. * 画布销毁回调
  6248. */
  6249. onDispose: () => { },
  6250. plugins: () => [
  6251. /**
  6252. * 缩略图插件
  6253. */
  6254. createMinimapPlugin({}),
  6255. ],
  6256. }),
  6257. [],
  6258. );
  6259. }
  6260. ```
  6261. ### 3. 配置数据
  6262. 画布文档数据采用树形结构,支持嵌套
  6263. :::note 文档数据基本结构:
  6264. * nodes `array` 节点列表, 支持嵌套
  6265. :::
  6266. :::note 节点数据基本结构:
  6267. * id: `string` 节点唯一标识, 必须保证唯一
  6268. * meta: `object` 节点的 ui 配置信息,如自由布局的 `position` 信息放这里
  6269. * type: `string | number` 节点类型,会和 `nodeRegistries` 中的 `type` 对应
  6270. * data: `object` 节点表单数据
  6271. * blocks: `array` 节点的分支, 采用 `block` 更贴近 `Gramming`
  6272. :::
  6273. ```tsx pure title="initial-data.tsx"
  6274. import { FlowDocumentJSON } from '@flowgram.ai/fixed-layout-editor';
  6275. /**
  6276. * 配置流程数据,数据为 blocks 嵌套的格式
  6277. */
  6278. export const initialData: FlowDocumentJSON = {
  6279. nodes: [
  6280. // 开始节点
  6281. {
  6282. id: 'start_0',
  6283. type: 'start',
  6284. data: {
  6285. title: 'Start',
  6286. content: 'start content'
  6287. },
  6288. blocks: [],
  6289. },
  6290. // 分支节点
  6291. {
  6292. id: 'condition_0',
  6293. type: 'condition',
  6294. data: {
  6295. title: 'Condition'
  6296. },
  6297. blocks: [
  6298. {
  6299. id: 'branch_0',
  6300. type: 'block',
  6301. data: {
  6302. title: 'Branch 0',
  6303. content: 'branch 1 content'
  6304. },
  6305. blocks: [
  6306. {
  6307. id: 'custom_0',
  6308. type: 'custom',
  6309. data: {
  6310. title: 'Custom',
  6311. content: 'custrom content'
  6312. },
  6313. },
  6314. ],
  6315. },
  6316. {
  6317. id: 'branch_1',
  6318. type: 'block',
  6319. data: {
  6320. title: 'Branch 1',
  6321. content: 'branch 1 content'
  6322. },
  6323. blocks: [],
  6324. },
  6325. ],
  6326. },
  6327. // 结束节点
  6328. {
  6329. id: 'end_0',
  6330. type: 'end',
  6331. data: {
  6332. title: 'End',
  6333. content: 'end content'
  6334. },
  6335. },
  6336. ],
  6337. };
  6338. ```
  6339. ### 4. 声明节点
  6340. 声明节点可以用于确定节点的类型及渲染方式
  6341. ```tsx pure title="node-registries.tsx"
  6342. import { FlowNodeRegistry, ValidateTrigger } from '@flowgram.ai/fixed-layout-editor';
  6343. /**
  6344. * 自定义节点注册
  6345. */
  6346. export const nodeRegistries: FlowNodeRegistry[] = [
  6347. {
  6348. /**
  6349. * 自定义节点类型
  6350. */
  6351. type: 'condition',
  6352. /**
  6353. * 自定义节点扩展:
  6354. * - loop: 扩展为循环节点
  6355. * - start: 扩展为开始节点
  6356. * - dynamicSplit: 扩展为分支节点
  6357. * - end: 扩展为结束节点
  6358. * - tryCatch: 扩展为 tryCatch 节点
  6359. * - default: 扩展为普通节点 (默认)
  6360. */
  6361. extend: 'dynamicSplit',
  6362. /**
  6363. * 节点配置信息
  6364. */
  6365. meta: {
  6366. // isStart: false, // 是否为开始节点
  6367. // isNodeEnd: false, // 是否为结束节点,结束节点后边无法再添加节点
  6368. // draggable: false, // 是否可拖拽,如开始节点和结束节点无法拖拽
  6369. // selectable: false, // 触发器等开始节点不能被框选
  6370. // deleteDisable: true, // 禁止删除
  6371. // copyDisable: true, // 禁止copy
  6372. // addDisable: true, // 禁止添加
  6373. },
  6374. /**
  6375. * 配置节点表单的校验及渲染,
  6376. * 注:validate 采用数据和渲染分离,保证节点即使不渲染也能对数据做校验
  6377. */
  6378. formMeta: {
  6379. validateTrigger: ValidateTrigger.onChange,
  6380. validate: {
  6381. title: ({ value }) => (value ? undefined : 'Title is required'),
  6382. },
  6383. /**
  6384. * Render form
  6385. */
  6386. render: () => (
  6387. <>
  6388. <Field name="title">
  6389. {({ field }) => <div className="demo-free-node-title">{field.value}</div>}
  6390. </Field>
  6391. <Field name="content">
  6392. {({ field }) => <input onChange={field.onChange} value={field.value}/>}
  6393. </Field>
  6394. </>
  6395. )
  6396. },
  6397. },
  6398. ];
  6399. ```
  6400. ### 5. 渲染节点
  6401. 渲染节点用于添加样式、事件及表单渲染的位置
  6402. ```tsx pure title="components/base-node.tsx"
  6403. import { useNodeRender } from '@flowgram.ai/fixed-layout-editor';
  6404. export const BaseNode = () => {
  6405. /**
  6406. * 提供节点渲染相关的方法
  6407. */
  6408. const nodeRender = useNodeRender();
  6409. /**
  6410. * 只有在节点引擎开启时候才能使用表单
  6411. */
  6412. const form = nodeRender.form;
  6413. return (
  6414. <div
  6415. className="demo-fixed-node"
  6416. onMouseEnter={nodeRender.onMouseEnter}
  6417. onMouseLeave={nodeRender.onMouseLeave}
  6418. onMouseDown={e => {
  6419. // 触发拖拽
  6420. nodeRender.startDrag(e);
  6421. e.stopPropagation();
  6422. }}
  6423. style={{
  6424. // BlockOrderIcon 表示为分支的第一个节点,BlockIcon 则表示整个 condition 的头部节点
  6425. ...(nodeRender.isBlockOrderIcon || nodeRender.isBlockIcon ? { width: 260 } : {}),
  6426. outline: form?.state.invalid ? '1px solid red' : 'none', // 表单校验错误让边框标红
  6427. }}
  6428. >
  6429. {
  6430. // 表单渲染通过 formMeta 生成
  6431. form?.render()
  6432. }
  6433. </div>
  6434. );
  6435. };
  6436. ```
  6437. ### 6. 添加工具
  6438. 工具主要用于控制画布缩放等操作, 工具汇总在 `usePlaygroundTools` 中, 而 `useClientContext` 用于获取画布的上下文, 里边包含画布的核心模块如 `history`
  6439. ```tsx pure title="components/tools.tsx"
  6440. import { useEffect, useState } from 'react'
  6441. import { usePlaygroundTools, useClientContext } from '@flowgram.ai/fixed-layout-editor';
  6442. export function Tools() {
  6443. const { history } = useClientContext();
  6444. const tools = usePlaygroundTools();
  6445. const [canUndo, setCanUndo] = useState(false);
  6446. const [canRedo, setCanRedo] = useState(false);
  6447. useEffect(() => {
  6448. const disposable = history.undoRedoService.onChange(() => {
  6449. setCanUndo(history.canUndo());
  6450. setCanRedo(history.canRedo());
  6451. });
  6452. return () => disposable.dispose();
  6453. }, [history]);
  6454. return <div style={{ position: 'absolute', zIndex: 10, bottom: 16, left: 16, display: 'flex', gap: 8 }}>
  6455. <button onClick={() => tools.zoomin()}>ZoomIn</button>
  6456. <button onClick={() => tools.zoomout()}>ZoomOut</button>
  6457. <button onClick={() => tools.fitView()}>Fitview</button>
  6458. <button onClick={() => tools.changeLayout()}>ChangeLayout</button>
  6459. <button onClick={() => history.undo()} disabled={!canUndo}>Undo</button>
  6460. <button onClick={() => history.redo()} disabled={!canRedo}>Redo</button>
  6461. <span>{Math.floor(tools.zoom * 100)}%</span>
  6462. </div>
  6463. }
  6464. ```
  6465. ### 7. 效果
  6466. ---
  6467. url: /guide/getting-started/create-free-layout-simple.md
  6468. ---
  6469. # 创建自由布局画布
  6470. 本案例可通过 `npx @flowgram.ai/create-app@latest free-layout-simple` 安装,完整代码及效果见:
  6471. 自由布局基础用法
  6472. 文件结构:
  6473. ```
  6474. - hooks
  6475. - use-editor-props.ts # 画布配置
  6476. - components
  6477. - base-node.tsx # 节点渲染
  6478. - tools.tsx # 画布工具栏
  6479. - initial-data.ts # 初始化数据
  6480. - node-registries.ts # 节点配置
  6481. - app.tsx # 画布入口
  6482. ```
  6483. ### 1. 画布入口
  6484. * `FreeLayoutEditorProvider`: 画布配置器, 内部会生成 react-context 供子组件消费
  6485. * `EditorRenderer`: 为最终渲染的画布,可以包装在其他组件下边方便定制画布位置
  6486. ```tsx pure title="app.tsx"
  6487. import {
  6488. FreeLayoutEditorProvider,
  6489. EditorRenderer,
  6490. } from '@flowgram.ai/free-layout-editor';
  6491. import '@flowgram.ai/free-layout-editor/index.css'; // 加载样式
  6492. import { useEditorProps } from './use-editor-props' // 画布详细的 props 配置
  6493. import { Tools } from './components/tools' // 画布工具
  6494. function App() {
  6495. const editorProps = useEditorProps()
  6496. return (
  6497. <FreeLayoutEditorProvider {...editorProps}>
  6498. <EditorRenderer className="demo-editor" />
  6499. <Tools />
  6500. </FreeLayoutEditorProvider>
  6501. );
  6502. }
  6503. ```
  6504. ### 2. 配置画布
  6505. 画布配置采用声明式,提供 数据、渲染、事件、插件相关配置
  6506. ```tsx pure title="hooks/use-editor-props.tsx"
  6507. import { useMemo } from 'react';
  6508. import { type FreeLayoutProps } from '@flowgram.ai/free-layout-editor';
  6509. import { createMinimapPlugin } from '@flowgram.ai/minimap-plugin';
  6510. import { initialData } from './initial-data' // 初始化数据
  6511. import { nodeRegistries } from './node-registries' // 节点声明配置
  6512. import { BaseNode } from './components/base-node' // 节点渲染
  6513. export function useEditorProps(
  6514. ): FreeLayoutProps {
  6515. return useMemo<FreeLayoutProps>(
  6516. () => ({
  6517. /**
  6518. * 初始化数据
  6519. */
  6520. initialData,
  6521. /**
  6522. * 画布节点定义
  6523. */
  6524. nodeRegistries,
  6525. /**
  6526. * 物料
  6527. */
  6528. materials: {
  6529. renderDefaultNode: BaseNode, // 节点渲染组件
  6530. },
  6531. /**
  6532. * 节点引擎, 用于渲染节点表单
  6533. */
  6534. nodeEngine: {
  6535. enable: true,
  6536. },
  6537. /**
  6538. * 画布历史记录, 用于控制 redo/undo
  6539. */
  6540. history: {
  6541. enable: true,
  6542. enableChangeNode: true, // 用于监听节点表单数据变化
  6543. },
  6544. /**
  6545. * 画布初始化回调
  6546. */
  6547. onInit: ctx => {
  6548. // 如果要动态加载数据,可以通过如下方法异步执行
  6549. // ctx.docuemnt.fromJSON(initialData)
  6550. },
  6551. /**
  6552. * 画布第一次渲染完整回调
  6553. */
  6554. onAllLayersRendered: (ctx) => {},
  6555. /**
  6556. * 画布销毁回调
  6557. */
  6558. onDispose: () => { },
  6559. plugins: () => [
  6560. /**
  6561. * 缩略图插件
  6562. */
  6563. createMinimapPlugin({}),
  6564. ],
  6565. }),
  6566. [],
  6567. );
  6568. }
  6569. ```
  6570. ### 3. 配置数据
  6571. 画布文档数据采用树形结构,支持嵌套
  6572. :::note 文档数据基本结构:
  6573. * nodes `array` 节点列表, 支持嵌套
  6574. * edges `array` 边列表
  6575. :::
  6576. :::note 节点数据基本结构:
  6577. * id: `string` 节点唯一标识, 必须保证唯一
  6578. * meta: `object` 节点的 ui 配置信息,如自由布局的 `position` 信息放这里
  6579. * type: `string | number` 节点类型,会和 `nodeRegistries` 中的 `type` 对应
  6580. * data: `object` 节点表单数据, 业务可自定义
  6581. * blocks: `array` 节点的分支, 采用 `block` 更贴近 `Gramming`, 目前会存子画布的节点
  6582. * edges: `array` 子画布的边数据
  6583. :::
  6584. :::note 边数据基本结构:
  6585. * sourceNodeID: `string` 开始节点 id
  6586. * targetNodeID: `string` 目标节点 id
  6587. * sourcePortID?: `string | number` 开始端口 id, 缺省则采用开始节点的默认端口
  6588. * targetPortID?: `string | number` 目标端口 id, 缺省则采用目标节点的默认端口
  6589. :::
  6590. ```tsx pure title="initial-data.ts"
  6591. import { WorkflowJSON } from '@flowgram.ai/free-layout-editor';
  6592. export const initialData: WorkflowJSON = {
  6593. nodes: [
  6594. {
  6595. id: 'start_0',
  6596. type: 'start',
  6597. meta: {
  6598. position: { x: 0, y: 0 },
  6599. },
  6600. data: {
  6601. title: 'Start',
  6602. content: 'Start content'
  6603. },
  6604. },
  6605. {
  6606. id: 'node_0',
  6607. type: 'custom',
  6608. meta: {
  6609. position: { x: 400, y: 0 },
  6610. },
  6611. data: {
  6612. title: 'Custom',
  6613. content: 'Custom node content'
  6614. },
  6615. },
  6616. {
  6617. id: 'end_0',
  6618. type: 'end',
  6619. meta: {
  6620. position: { x: 800, y: 0 },
  6621. },
  6622. data: {
  6623. title: 'End',
  6624. content: 'End content'
  6625. },
  6626. },
  6627. ],
  6628. edges: [
  6629. {
  6630. sourceNodeID: 'start_0',
  6631. targetNodeID: 'node_0',
  6632. },
  6633. {
  6634. sourceNodeID: 'node_0',
  6635. targetNodeID: 'end_0',
  6636. },
  6637. ],
  6638. };
  6639. ```
  6640. ### 4. 声明节点
  6641. 声明节点可以用于确定节点的类型及渲染方式
  6642. ```tsx pure title="node-registries.tsx"
  6643. import { WorkflowNodeRegistry, ValidateTrigger } from '@flowgram.ai/free-layout-editor';
  6644. /**
  6645. * You can customize your own node registry
  6646. * 你可以自定义节点的注册器
  6647. */
  6648. export const nodeRegistries: WorkflowNodeRegistry[] = [
  6649. {
  6650. type: 'start',
  6651. meta: {
  6652. isStart: true, // 标记为开始节点
  6653. deleteDisable: true, // 开始节点不能删除
  6654. copyDisable: true, // 开始节点不能复制
  6655. defaultPorts: [{ type: 'output' }], // 用于定义节点的输入和输出端口, 开始节点只有输出端口
  6656. // useDynamicPort: true, // 用于动态端口,会寻找 data-port-id 和 data-port-type 属性的 dom 作为端口
  6657. },
  6658. /**
  6659. * 配置节点表单的校验及渲染,
  6660. * 注:validate 采用数据和渲染分离,保证节点即使不渲染也能对数据做校验
  6661. */
  6662. formMeta: {
  6663. validateTrigger: ValidateTrigger.onChange,
  6664. validate: {
  6665. title: ({ value }) => (value ? undefined : 'Title is required'),
  6666. },
  6667. /**
  6668. * Render form
  6669. */
  6670. render: () => (
  6671. <>
  6672. <Field name="title">
  6673. {({ field }) => <div className="demo-free-node-title">{field.value}</div>}
  6674. </Field>
  6675. <Field name="content">
  6676. {({ field }) => <input onChange={field.onChange} value={field.value}/>}
  6677. </Field>
  6678. </>
  6679. )
  6680. },
  6681. },
  6682. {
  6683. type: 'end',
  6684. meta: {
  6685. deleteDisable: true,
  6686. copyDisable: true,
  6687. defaultPorts: [{ type: 'input' }],
  6688. },
  6689. formMeta: {
  6690. // ...
  6691. }
  6692. },
  6693. {
  6694. type: 'custom',
  6695. meta: {
  6696. },
  6697. formMeta: {
  6698. // ...
  6699. },
  6700. defaultPorts: [{ type: 'output' }, { type: 'input' }], // 普通节点有两个端口
  6701. },
  6702. ];
  6703. ```
  6704. ### 5. 渲染节点
  6705. 渲染节点用于添加样式、事件及表单渲染的位置
  6706. ```tsx pure title="components/base-node.tsx"
  6707. import { useNodeRender, WorkflowNodeRenderer } from '@flowgram.ai/free-layout-editor';
  6708. export const BaseNode = () => {
  6709. /**
  6710. * 提供节点渲染相关的方法
  6711. */
  6712. const { form } = useNodeRender()
  6713. /**
  6714. * WorkflowNodeRenderer 会添加节点拖拽事件及 端口渲染,如果要深度定制,可以看该组件源代码:
  6715. * https://github.com/bytedance/flowgram.ai/blob/main/packages/client/free-layout-editor/src/components/workflow-node-renderer.tsx
  6716. */
  6717. return (
  6718. <WorkflowNodeRenderer className="demo-free-node" node={props.node}>
  6719. {
  6720. // 表单渲染通过 formMeta 生成
  6721. form?.render()
  6722. }
  6723. </WorkflowNodeRenderer>
  6724. )
  6725. };
  6726. ```
  6727. ### 6. 添加工具
  6728. 工具主要用于控制画布缩放等操作, 工具汇总在 `usePlaygroundTools` 中, 而 `useClientContext` 用于获取画布的上下文, 里边包含画布的核心模块如 `history`
  6729. ```tsx pure title="components/tools.tsx"
  6730. import { useEffect, useState } from 'react'
  6731. import { usePlaygroundTools, useClientContext } from '@flowgram.ai/free-layout-editor';
  6732. export function Tools() {
  6733. const { history } = useClientContext();
  6734. const tools = usePlaygroundTools();
  6735. const [canUndo, setCanUndo] = useState(false);
  6736. const [canRedo, setCanRedo] = useState(false);
  6737. useEffect(() => {
  6738. const disposable = history.undoRedoService.onChange(() => {
  6739. setCanUndo(history.canUndo());
  6740. setCanRedo(history.canRedo());
  6741. });
  6742. return () => disposable.dispose();
  6743. }, [history]);
  6744. return <div style={{ position: 'absolute', zIndex: 10, bottom: 16, left: 226, display: 'flex', gap: 8 }}>
  6745. <button onClick={() => tools.zoomin()}>ZoomIn</button>
  6746. <button onClick={() => tools.zoomout()}>ZoomOut</button>
  6747. <button onClick={() => tools.fitView()}>Fitview</button>
  6748. <button onClick={() => tools.autoLayout()}>AutoLayout</button>
  6749. <button onClick={() => history.undo()} disabled={!canUndo}>Undo</button>
  6750. <button onClick={() => history.redo()} disabled={!canRedo}>Redo</button>
  6751. <span>{Math.floor(tools.zoom * 100)}%</span>
  6752. </div>
  6753. }
  6754. ```
  6755. ### 7. 效果
  6756. ---
  6757. url: /guide/getting-started/install.md
  6758. ---
  6759. # 安装
  6760. ## 通过 npx 安装
  6761. ```shell
  6762. # 选择 demo
  6763. - fixed-layout # 固定布局最佳实践
  6764. - free-layout # 自由布局最佳实践
  6765. - fixed-layout-simple # 固定布局基础用法
  6766. - free-layout-simple # 自由布局基础用法
  6767. ```
  6768. ## 通过 npm 安装
  6769. ---
  6770. url: /guide/question.md
  6771. ---
  6772. # 常见问题
  6773. ## 运行报报错
  6774. ## 如何修改节点的数据
  6775. ## 是否支持 vue
  6776. ##
  6777. ---
  6778. url: /index.md
  6779. ---