Unde pot fi utilizate componentele front-end
Componentele front-end pot fi afișate în trei locații în cadrul Twenty:- Panou lateral — Componentele front-end care nu sunt headless se deschid în panoul lateral din dreapta. Acesta este comportamentul implicit atunci când o componentă front-end este declanșată din meniul de comenzi.
- Widgeturi (tablouri de bord și pagini de înregistrare) — Componentele frontale pot fi încorporate ca widgeturi în machetele de pagină. La configurarea unui tablou de bord sau a machetei unei pagini de înregistrare, utilizatorii pot adăuga un widget de componentă front-end.
- Setările aplicației — Definită cu
defineSettingsFrontComponent(), componenta front-end este afișată ca o secțiune în interiorul filei Settings a aplicației, în locul interfeței UI implicite de configurare a variabilelor.
- Asociază-l cu un element de meniu de comenzi — îl înregistrează în meniul de comenzi (Cmd+K) și, opțional, ca acțiune rapidă fixată.
- Încorporează-l ca widget într-o machetă de pagină — îl plasează pe pagina de detalii a unei înregistrări sau pe un tablou de bord.
- Definește-o cu
defineSettingsFrontComponent()— o afișează ca o secțiune în interiorul filei Settings a aplicației, în locul interfeței UI implicite de configurare a variabilelor.
Exemplu de bază
Cel mai rapid mod de a vedea o componentă frontală în acțiune este să o asociezi cu undefineCommandMenuItem, astfel încât să apară ca un buton de acțiune rapidă în colțul din dreapta sus al paginii:
src/front-components/hello-world.tsx
src/command-menu-items/hello-world.command-menu-item.ts
yarn twenty dev (sau prin rularea o singură dată a comenzii yarn twenty apply), acțiunea rapidă apare în colțul din dreapta sus al paginii:
Faceți clic pe el pentru a afișa componenta inline.
Câmpuri de configurare
Plasarea unei componente front-end pe o pagină
Dincolo de comenzi, puteți încorpora o componentă front-end direct într-o pagină de înregistrare adăugând-o ca widget într-un layout de pagină. Vezi Machete de pagină pentru detalii.Componentă de setări personalizată
Pentru a înlocui interfața UI de configurare a variabilelor generată automat din fila Settings a aplicației cu propria ta componentă, definește-o cudefineSettingsFrontComponent în loc de defineFrontComponent. Acesta folosește aceleași câmpuri de configurare (cu excepția lui isHeadless, care nu este acceptat deoarece o componentă de setări afișează întotdeauna o interfață vizibilă) și, în plus, marchează componenta ca interfața de setări a aplicației.
Componenta este afișată ca o secțiune în interiorul filei Settings, nu ca un înlocuitor pentru întreaga filă. Secțiunile gestionate de sistem ale Twenty — actualizare automată, URL aplicație și conexiuni — sunt întotdeauna afișate deasupra și nu pot fi suprascrise de aplicație.
src/front-components/app-settings.tsx
Headless vs non-headless
Componentele front-end au două moduri de randare controlate de opțiuneaisHeadless:
Non-headless (implicit) — Componenta afișează o interfață vizibilă. Când este declanșată din meniul de comenzi, se deschide în panoul lateral. Acesta este comportamentul implicit când isHeadless este false sau omis.
Headless (isHeadless: true) — Componenta se montează invizibil în fundal. Nu deschide panoul lateral. Componentele headless sunt concepute pentru acțiuni care execută logică și apoi se demontează — de exemplu, rularea unei sarcini asincrone, navigarea la o pagină sau afișarea unui modal de confirmare. Se potrivesc în mod natural cu componentele Command din SDK descrise mai jos.
src/front-components/sync-tracker.tsx
null, Twenty omite redarea unui container pentru ea — nu apare spațiu gol în layout. Componenta are în continuare acces la toate hook-urile și la API-ul de comunicare cu gazda.
Componentele Command din SDK
Pachetultwenty-sdk oferă patru componente ajutătoare Command, concepute pentru componente front-end headless. Fiecare componentă execută o acțiune la montare, gestionează erorile afișând o notificare snackbar și demontează automat componenta front-end la final.
Importați-le din twenty-sdk/front-component:
Command— Rulează un callback asincron prin prop-ulexecute.CommandLink— Navighează către o rută a aplicației. Props:to,params,queryParams,options.CommandModal— Deschide un modal de confirmare. Dacă utilizatorul confirmă, execută callback-ulexecute. Props:title,subtitle,execute,confirmButtonText,confirmButtonAccent.CommandOpenSidePanelPage— Deschide o pagină din panoul lateral. Props depind depage— de ex.ViewRecordprimeșterecordId+objectNameSingular(plus un idtabopțional pentru a deschide înregistrarea într-un anumit tab), alte pagini primescpageTitle+pageIcon.
Command pentru a rula o acțiune din meniul de comenzi:
src/front-components/run-action.tsx
src/command-menu-items/run-action.command-menu-item.ts
CommandModal pentru a cere confirmarea înainte de execuție:
src/front-components/delete-draft.tsx
CommandOpenSidePanelPage pentru a deschide înregistrarea curentă în panoul lateral, pe un tab specific. tab este un id de tab al layout-ului paginii (layout-urile implicite folosesc id-uri precum company-tab-emails sau company-tab-timeline; layout-urile personalizate folosesc propriul id al tab-ului). Dacă id-ul nu există în layout-ul înregistrării, se deschide în schimb tab-ul implicit:
src/front-components/open-company-emails.tsx
Apelarea unei funcții logice
Componentele de front rulează în browser, într-un Web Worker sandboxat în interiorul unui iframe cu origine opacă, în timp ce funcțiile logice rulează pe server. Nu există un apel direct în același proces între cele două — în schimb, o componentă de front apelează o funcție logică prin HTTP. O funcție logică declarată cuhttpRouteTriggerSettings este accesibilă prin HTTP la ruta sa. RestApiClient tratează căile care încep cu /s/ ca rute ale aplicației, le rezolvă către URL-ul de la care sunt deservite funcțiile tale și le autentifică folosind TWENTY_APP_ACCESS_TOKEN.
În Twenty Cloud, funcțiile logice declanșate prin HTTP sunt deservite pe un domeniu dedicat pentru fiecare spațiu de lucru la https://\<your-workspace-subdomain>.withtwenty.com\<path>. Pentru apelanții externi, copiază URL-ul exact din setările HTTP trigger ale funcției sau din fila Settings a aplicației.
O componentă de front headless poate efectua apelul la montare prin componenta Command, apoi se demontează automat:
src/front-components/sync-prs.tsx
RestApiClient este proprietatea httpRouteTriggerSettings.path a funcției logice, cu prefixul /s. Păstrează isAuthRequired: true; TWENTY_APP_ACCESS_TOKEN pe care Twenty îl generează pentru componenta ta autentifică cererea:
src/logic-functions/fetch-prs.logic-function.ts
TWENTY_APP_ACCESS_TOKEN este injectat automat — vezi Application variables. Deoarece variabilele de aplicație secrete nu sunt niciodată expuse componentelor de front, păstrează cheile API și altă logică sensibilă în funcția logică, nu în componenta de front.Apelarea API-ului REST Twenty
Pentru a apela rute HTTP ale aplicației sau pentru a citi și scrie înregistrări Twenty dintr-un front component, foloseșteRestApiClient din twenty-client-sdk/rest. Trimite căile de forma /s/... către URL-ul de bază al funcțiilor spațiului tău de lucru, iar orice altă cale, inclusiv /rest/..., către TWENTY_API_URL.
Acționează întotdeauna ca persoana care se uită la pagină. runAs: 'application' este o opțiune exclusivă pentru funcțiile logice: o componentă nu primește niciodată propriul token al aplicației tale, astfel că solicitarea lui aici generează o eroare. Pune activitatea care necesită propriul acces al aplicației în spatele unei funcții logice și apeleaz-o în schimb pe aceasta.
options acceptă headers, query (un „record” de parametri de query-string; valorile nule sau nedefinite sunt omise) și un AbortSignal prin signal. Un obiect body care nu este de tip FormData este serializat automat în JSON. La un 401, clientul reîmprospătează o dată tokenul de acces prin gazdă și reîncearcă cererea.
URL-ul de bază și tokenul sunt rezolvate din mediu în mod implicit. Transmite suprascrieri către constructor atunci când este necesar — de exemplu, în teste:
RestApiClientError care expune status, statusText, url și body analizat:
Accesarea contextului de rulare
În interiorul componentei, folosiți hook-urile SDK pentru a accesa utilizatorul curent, înregistrarea curentă și instanța componentei:src/front-components/record-info.tsx
Variabile de aplicație
Variabilele de aplicație definite îndefineApplication() cu isSecret: false sunt disponibile în componentele de interfață prin utilitarul getApplicationVariable:
src/front-components/greeting.tsx
getApplicationVariable returnează întotdeauna un string (sau undefined), indiferent de type‑ul declarat al variabilei. Stringul este serializat în mod consecvent în funcție de tip (valorile boolean ca "true" / "false", numerele ca stringuri zecimale, array‑urile / obiectele ca JSON), în același format folosit pentru process.env în funcțiile logice — parsează‑l tu însuți (Number(...), JSON.parse(...), === 'true'). Vezi Tipuri de variabile.
Următoarele variabile de sistem sunt întotdeauna disponibile prin process.env:
TWENTY_FUNCTIONS_URL
Twenty injectează, de asemenea, TWENTY_FUNCTIONS_URL în front components și în funcțiile logice: URL-ul de bază de la care sunt deservite funcțiile logice ale aplicației tale declanșate prin HTTP.
Există deoarece acel URL nu este întotdeauna chiar serverul Twenty. În Twenty Cloud, rutele aplicației sunt deservite pe un domeniu dedicat pentru fiecare spațiu de lucru (https://\<your-workspace-subdomain>.withtwenty.com sau domeniul public principal al aplicației atunci când este configurat unul), astfel încât răspunsurile generate de aplicație să ruleze pe o origine izolată, nu pe originea aplicației Twenty. Instanțele self-hosted și locale deservesc rutele aplicației sub prefixul /s chiar pe server și este posibil să nu seteze deloc variabila. Deoarece URL-ul de bază variază în funcție de spațiul de lucru și de instanță, codul tău nu îl poate hardcoda — serverul injectează valoarea corectă la runtime.
Rareori ai nevoie să o citești direct. Apelează-ți rutele prin RestApiClient folosind o cale prefixată cu /s/, iar clientul îți rezolvă URL-ul: elimină prefixul /s și țintește TWENTY_FUNCTIONS_URL, folosind \<TWENTY_API_URL>/s ca rezervă atunci când variabila nu este setată. Folosește resolveUrl('/s/\<path>') pentru a obține URL-ul absolut fără a trimite o cerere, de exemplu pentru un link. Citește variabila direct doar atunci când construiești manual un URL:
API-ul de comunicare cu gazda
Componentele front-end pot declanșa navigare, ferestre modale și notificări folosind funcții dintwenty-sdk:
Iată un exemplu care folosește API-ul gazdei pentru a afișa un snackbar și a închide panoul lateral după finalizarea unei acțiuni:
src/front-components/archive-record.tsx
Stocare
localStorage și sessionStorage funcționează la fel ca într-o pagină normală, cu API-ul sincron standard. Cheile tale sunt limitate la instalarea aplicației tale și la utilizatorul autentificat: nicio altă aplicație nu le poate citi, iar un alt utilizator care se conectează în același browser pornește de la un spațiu de stocare gol. Valorile scrise în localStorage rămân pe dispozitiv între reîncărcări; sessionStorage durează pe durata sesiunii de browser.
src/front-components/note-draft.tsx
QuotaExceededError, la fel ca API-ul de browser.
Lucrul cu mai multe înregistrări
FolosițiuseSelectedRecordIds() pentru a gestiona mai multe înregistrări selectate. Acest lucru este util pentru operațiuni în masă:
src/front-components/bulk-export.tsx
src/command-menu-items/bulk-export.command-menu-item.ts
Resurse publice
Componentele front-end pot accesa fișiere din directorulpublic/ al aplicației folosind getPublicAssetUrl:
Partajare dependențe între componentele din față
În mod implicit, fiecare componentă din față grupează propria copie a bibliotecilor pe care le importă, astfel încât o aplicație cu cinci componente expediază React de cinci ori. Declară dependențe partajate în fișierulpachet al aplicației tale. son pentru a construi acele biblioteci o dată și a avea fiecare componentă a aplicației să le încarce dintr-un singur fișier cache:
package.json
src/front-components/counter.tsx
- Un pachet de dependențe partajate pentru fiecare aplicație. Pachetul este construit din propriile dependențe ale aplicației dvs., așa că țineți controlul deplin asupra versiunilor pe care le expediați.
- Listați specificatorii pe care îi importați.
douăzeci și ui/inputșidouăzeci și ui/afișeazăsunt două intrări; doar un nume de pachet nu acoperă subcăile sale. Listareareactacoperă automatreact/jsx-runtime. - Distribuie
react-dom/clientalături dereact. Fiecare componentă redă princreateRoot, astfel lăsând-o înseamnă că fiecare componentă încă lipsește React DOM. - **Pachetul este cacheat. * Este servită sub un URL content-hash cu o geocutie mutabilă de lungă durată, astfel încât acesta este descărcat o dată și reutilizat pe toate componentele aplicației până când una dintre dependențele sale se schimbă.
- Componente care importă niciunul dintre pachetele partajate nu îl descarcă niciodată.
Stilizare
Componentele front-end acceptă mai multe abordări de stilizare. Puteți folosi:- Stiluri inline —
style={{ color: 'red' }} - Componente UI Twenty — biblioteca proprie de componente a Twenty; vezi Folosirea componentelor UI Twenty mai jos
- Emotion — CSS-in-JS cu
@emotion/react - Styled-components — pattern-uri
styled.div - Tailwind CSS — clase utilitare
- Orice bibliotecă CSS-in-JS compatibilă cu React
Folosirea componentelor UI Twenty
Twenty livrează biblioteca sa de componente ca pachetultwenty-ui. Componentele frontend îl pot folosi pentru butoane, etichete, pastile de stare, chips, avataruri, pictograme, tipografie și tokeni de temă care se potrivesc automat cu tema luminoasă și întunecată a spațiului de lucru.
Instalare
Adaugă pachetul în aplicația ta, fixat la versiunea cu care este livrată instanța ta de Twenty:twenty-ui este inclus în componenta ta frontend la momentul build-ului, astfel încât trebuie să fie doar o dependență a aplicației tale — nu este nimic de configurat la runtime.
Importarea componentelor
Importă din subpath-ul corespunzător, nu din rădăcina pachetului, astfel încât doar componentele pe care le folosești să ajungă în bundle-ul tău:Pictograme
Importă pictograme individuale dintwenty-ui/icon:
IconsProvider, useIcons și iconsState — acestea încarcă întregul set de pictograme Tabler (câțiva MB).
Teme și tokeni de temă
Componentele Twenty UI se potrivesc automat cu tema luminoasă și întunecată a spațiului de lucru — renderer-ul aplică schema de culori activă pe gazdă, iar componentele își determină culorile în funcție de aceasta. Pentru a folosi aceiași tokeni de design în propriile tale stiluri inline, apelează hook-uluseTheme(). Acesta returnează tokenii de temă ai Twenty (spațiere, culori, raze, fonturi) conectați la tema activă, fără a necesita vreo configurare ThemeProvider în componenta ta:
useTheme() este un hook, citești tokenii în interiorul corpului componentei, astfel încât valorile reflectă întotdeauna tema activă în timp real. Aceeași hartă de tokeni este exportată și ca o constantă themeCssVariables, dar preferă useTheme() în componentele frontend — o constantă la nivel de modul care dereferențiază themeCssVariables poate fi nedefinită în timp ce manifestul aplicației este extras.
Pentru a ramifica explicit în funcție de schema activă, citește-o cu useColorScheme() din twenty-sdk/front-component, care returnează 'light' sau 'dark'.
Limitări actuale
Componentele Front sunt în curs de dezvoltare activă. Redarea, stilizarea, gestionarea evenimentelor, măsurarea elementelor și spațiul de stocare al browserului funcționează bine. Orice ajunge dincolo de acestea (apelarea unei metode DOM pe un ref, observarea redimensionărilor elementelor, crearea unui portal în afara arborelui tău) lipsește sau este incomplet astăzi, iar majoritatea eșuează în tăcere: fără excepție și fără eroare TypeScript, deoarece scheletul este tipizat pentru întregul DOM al browserului. Dacă unul dintre aceste lucruri te blochează, deschide un tichet ca să fie prioritar.Layout și măsurare
Elementele se pot măsura singure: gazda reflectă geometria în sandbox, astfel încât citirile sunt efectuate local, dar pot avea un decalaj de până la un cadru, iar prima citire a unui element nemăsurat anterior returnează zerouri. După scriere, recitește într-un callbackrequestAnimationFrame sau într-un efect.
Poziționarea prin
getBoundingClientRect funcționează acum, dar orice urmărește modificările de dimensiune prin ResizeObserver (recharts ResponsiveContainer, autoUpdate din Floating UI) tot nu funcționează. Oricum, preferă CSS pentru layout: stylesheet-ul tău ajunge la pagina reală, astfel încât flexbox, grid, aspect-ratio, clamp() și @container se comportă normal, fără întârziere de cadru.
requestAnimationFrame, fetch, setTimeout și queueMicrotask funcționează fără prefixul window.. Numai window.requestAnimationFrame(...) și cele similare aruncă o excepție.Acces DOM
Unref îți oferă un element din sandbox, nu un HTMLElement.
Golul portalului este motivul pentru care popover-urile Radix, Headless UI, MUI și react-select nu redau nimic în mod implicit. Majoritatea acceptă o proprietate de tip container; indică-i un element pe care l-ai redat.
Evenimente
Mouse, pointer, touch, drag, tastatură, focus,input/change/submit, scroll/wheel/contextmenu și animationend/transitionend trec către gazdă, plus câteva per element: load/error pe img, clipboard și compoziție pe input/textarea, media pe video/audio, toggle pe details/dialog. Orice altceva (onAuxClick, onSelect, onInvalid, onReset, onAnimationStart, pointer capture, onLoad de pe img) este eliminat fără avertisment.
document.addEventListener() și window.addEventListener() se înregistrează fără eroare și nu se declanșează niciodată, motiv pentru care un drag se oprește imediat ce pointerul părăsește elementul de pe care a început. event.preventDefault() nu trece nici el; trimiterea formularelor, dragover/drop și clicurile pe linkuri sunt deja protejate pentru tine.
Atribute și stilizare
Fiecare element își transmite propriile proprietăți către DOM-ul gazdă (href pe a, src/alt pe img, value/placeholder/disabled pe input etc.), plus un set comun pe fiecare element: id, className, style, title, tabIndex, role, draggable și orice atribut aria-* / data-* (cu cratimă, astfel încât ariaLabel este omis). Orice în afara acestora este ignorat în tăcere, așa că exprimă starea personalizată ca data-*.
CSS-ul componentelor, fie din import './styles.css', CSS-in-JS sau un element style, este injectat în head-ul paginii gazdă fără scope (unscoped). Astfel numele de clase intră în coliziune cu cele ale Twenty (prefixează-le și nu scrie niciodată un selector simplu div { ... }), iar @media se potrivește cu fereastra browserului, nu cu widgetul tău (folosește @container cu propriul tău container-type). Proprietățile style inline nu sunt afectate.
Stocare și rețea
localStorage și sessionStorage sunt furnizate de Twenty, nu de browser: componenta rulează într-un worker cu o origine opacă, astfel încât gazda stochează valorile în numele aplicației tale. Consultă secțiunea storage pentru aria lor de aplicare și limite. IndexedDB, cookie-urile, Cache API și BroadcastChannel rămân indisponibile. Pentru a păstra starea între dispozitive, apelează o logic function și folosește key-value store-ul acesteia.
fetch funcționează, cu unele rezerve:
- Apelurile către Twenty API și către rutele aplicației tale sunt proxate de gazdă, așa că preferă
RestApiClient. La apelurile proxate,AbortSignalși celelalte opțiuniRequestInitsunt eliminate, iar doar corpurile de tipstringșiURLSearchParamssunt acceptate. - Alte origini părăsesc sandbox-ul cu
Origin: null, astfel încât un API terț răspunde doar dacă trimiteAccess-Control-Allow-Origin: *. Apelează-l dintr-o logic function în schimb. fetch('/rest/people')nu este niciodată asociat cu Twenty API, deoarece sandbox-ul nu are un URL de pagină față de care să rezolve o cale relativă.
Captură media
navigator.mediaDevices.getUserMedia() și MediaRecorder funcționează în componentele front prin sandbox polyfills, astfel încât codul standard de înregistrare rulează neschimbat, iar MediaRecorder.isTypeSupported răspunde pentru combinații obișnuite de container/codec. Obiectele detaliate de constrângeri getUserMedia sunt acceptate, dar nu sunt transmise mai departe — gazda capturează cu valorile implicite pentru tipurile solicitate — și doar o singură captură poate fi activă la un moment dat, în toate aplicațiile. Stochează un Blob înregistrat cu funcția gazdă uploadFile.
Alte lacune
- Conținutul fișierului. Un
inputde tipfileoferă handlerului tău doar metadatele fișierului, nu și octeții, astfel încâtFileReadernu este disponibil. Pentru a încărca unBlobpe care codul tău îl deține deja — de exemplu, unul produs deMediaRecorder— folosește funcția gazdăuploadFile. - Payload-uri drag-and-drop. Evenimentele de tip drag sunt declanșate, dar
event.dataTransferesteundefined. - Built-in-uri Node.
fs,pathșinode:cryptoeșuează la build, așa că mută acea logică într-o logic function. Web Crypto,fetch,TextEncoderșiURLsunt disponibile. iframeeste întotdeauna re-sandboxat fărăallow-same-origin, astfel încât o încorporare care se bazează pe propria sesiune se afișează ca delogat. Nu are nicionLoad.