Skip to main content
Frontendové komponenty jsou React komponenty, které se vykreslují přímo v uživatelském rozhraní Twenty. Běží v izolovaném Web Workeru s využitím Remote DOM — váš kód se spouští uvnitř sandboxovaného iframe s nejasným původem (opaque-origin), ale jeho UI se stále vykresluje nativně na stránce, místo aby bylo omezené na tento iframe.
Komponenty Front jsou stále aktivně vyvíjeny. Váš kód běží nad částečným DOMem, nikoli nad skutečnou stránkou prohlížeče, takže pokročilé způsoby použití mohou selhávat, často bez zjevných chyb. Viz Aktuální omezení.

Kde lze použít frontendové komponenty

Frontendové komponenty se mohou vykreslovat na třech místech v rámci Twenty:
  • Postranní panel — Frontendové komponenty, které nejsou headless, se otevírají v pravém postranním panelu. Toto je výchozí chování, když je frontendová komponenta vyvolána z příkazového menu.
  • Widgety (nástěnky a stránky záznamů) — front komponenty lze vkládat jako widgety do rozložení stránky. Při konfiguraci nástěnky nebo rozložení stránky záznamu mohou uživatelé přidat widget frontendové komponenty.
  • Nastavení aplikace — Definovaná pomocí defineSettingsFrontComponent(), frontendová komponenta se vykreslí jako sekce na kartě Settings (Nastavení) aplikace, místo výchozího uživatelského rozhraní pro konfiguraci proměnných.
Samotná frontendová komponenta není z uživatelského rozhraní dostupná — je potřeba ji zpřístupnit. Tři způsoby, jak to udělat, jsou:
  • Spárujte ji s položkou příkazové nabídky — zaregistruje ji v příkazové nabídce (Cmd+K) a volitelně také jako připnutou rychlou akci.
  • Vložte ji jako widget do rozložení stránky — umístí ji na detailní stránku záznamu nebo na nástěnku.
  • Definujte ji pomocí defineSettingsFrontComponent() — vykreslí ji jako sekci na kartě Settings (Nastavení) aplikace, místo výchozího uživatelského rozhraní pro konfiguraci proměnných.

Základní příklad

Nejrychlejší způsob, jak vidět front komponentu v akci, je spárovat ji s defineCommandMenuItem, aby se objevila jako tlačítko rychlé akce v pravém horním rohu stránky:
src/front-components/hello-world.tsx
src/command-menu-items/hello-world.command-menu-item.ts
Po synchronizaci pomocí yarn twenty dev (nebo po jednorázovém spuštění yarn twenty apply) se rychlá akce zobrazí v pravém horním rohu stránky:
Kliknutím na něj vykreslíte komponentu přímo ve stránce.

Konfigurační pole

Umístění frontendové komponenty na stránku

Mimo příkazy můžete frontendovou komponentu vložit přímo na stránku záznamu přidáním jako widget v rozvržení stránky. Podrobnosti viz Rozložení stránek.

Vlastní komponenta nastavení

Chcete-li nahradit automaticky generované uživatelské rozhraní pro konfiguraci proměnných na kartě Settings (Nastavení) vaší aplikace vlastní komponentou, definujte ji pomocí defineSettingsFrontComponent místo defineFrontComponent. Používá stejná konfigurační pole (kromě isHeadless, který není podporován, protože komponenta nastavení vždy vykresluje viditelné uživatelské rozhraní) a zároveň označuje komponentu jako uživatelské rozhraní nastavení aplikace. Komponenta se vykreslí jako sekce uvnitř karty Settings, nikoli jako náhrada celé karty. Systémem spravované sekce Twenty — automatická aktualizace, App URL a připojení — se vždy zobrazují nad ní a aplikace je nemůže přebít.
src/front-components/app-settings.tsx
Na jednu aplikaci je povolena pouze jedna frontová komponenta nastavení; deklarace více než jedné způsobí selhání sestavení. Je-li přítomna, karta Settings aplikace vykreslí tuto komponentu místo výchozího uživatelského rozhraní pro konfiguraci proměnných.

Headless vs. ne-headless

Front-endové komponenty existují ve dvou režimech vykreslování řízených volbou isHeadless: Ne-headless (výchozí) — Komponenta vykreslí viditelné uživatelské rozhraní. Po vyvolání z menu příkazů se otevře v postranním panelu. Toto je výchozí chování, když je isHeadless false nebo když tato volba není uvedena. Headless (isHeadless: true) — Komponenta se neviditelně inicializuje na pozadí. Neotevírá postranní panel. Headless komponenty jsou určené pro akce, které provedou logiku a poté se odpojí — například spuštění asynchronního úkolu, navigaci na stránku nebo zobrazení potvrzovacího modálního okna. Přirozeně se hodí ke komponentám SDK Command popsaným níže.
src/front-components/sync-tracker.tsx
Protože komponenta vrací null, Twenty přeskočí vykreslení kontejneru — v rozvržení se neobjeví žádné prázdné místo. Komponenta má však stále přístup ke všem hookům a API komunikace s hostitelem.

Komponenty SDK Command

Balíček twenty-sdk poskytuje čtyři pomocné komponenty Command navržené pro headless front-endové komponenty. Každá komponenta při připojení provede akci, chyby zpracuje zobrazením oznámení ve snackbaru a po dokončení automaticky odpojí front-endovou komponentu. Importujte je z twenty-sdk/front-component:
  • Command — Spustí asynchronní callback přes prop execute.
  • CommandLink — Naviguje na cestu v aplikaci. Props: to, params, queryParams, options.
  • CommandModal — Otevře potvrzovací modální okno. Pokud uživatel potvrdí, provede callback execute. Props: title, subtitle, execute, confirmButtonText, confirmButtonAccent.
  • CommandOpenSidePanelPage — Otevře stránku postranního panelu. Props závisí na page — např. ViewRecord bere recordId + objectNameSingular (plus volitelné id tab pro otevření záznamu na konkrétní záložce), ostatní stránky berou pageTitle + pageIcon.
Zde je kompletní příklad headless front-endové komponenty, která pomocí Command spouští akci z menu příkazů:
src/front-components/run-action.tsx
src/command-menu-items/run-action.command-menu-item.ts
A příklad s použitím CommandModal k vyžádání potvrzení před provedením:
src/front-components/delete-draft.tsx
A zde je příklad použití CommandOpenSidePanelPage k otevření aktuálního záznamu v postranním panelu na konkrétní záložce. tab je ID záložky rozvržení stránky (výchozí rozvržení používají ID jako company-tab-emails nebo company-tab-timeline; vlastní rozvržení používají vlastní ID záložky). Pokud ID v rozvržení záznamu neexistuje, místo toho se otevře výchozí záložka:
src/front-components/open-company-emails.tsx

Volání logické funkce

Front komponenty běží v prohlížeči v sandboxovaném Web Workeru uvnitř iframe s nejasným původem (opaque-origin), zatímco logické funkce běží na serveru. Neexistuje mezi nimi žádné přímé volání v rámci jednoho procesu — místo toho se front komponenta k logické funkci připojuje přes HTTP. Logická funkce deklarovaná pomocí httpRouteTriggerSettings je přes HTTP dostupná na své cestě (route path). RestApiClient považuje cesty začínající na /s/ za aplikační trasy, převede je na URL, ze které jsou vaše funkce poskytovány, a autentizuje je pomocí TWENTY_APP_ACCESS_TOKEN.
V Twenty Cloud jsou logické funkce spouštěné přes HTTP poskytovány na vyhrazené doméně pro každý workspace na adrese https://\<your-workspace-subdomain>.withtwenty.com\<path>. Pro externí volající zkopírujte přesnou URL z nastavení funkce HTTP trigger nebo z karty Settings aplikace.
Headless front komponenta může volání spustit při mountu přes komponentu Command a poté se automaticky odmountovat:
src/front-components/sync-prs.tsx
Cesta předaná RestApiClient je httpRouteTriggerSettings.path logické funkce s předponou /s. Ponechte isAuthRequired: true; TWENTY_APP_ACCESS_TOKEN, který Twenty vygeneruje pro vaši komponentu, požadavek autentizuje:
src/logic-functions/fetch-prs.logic-function.ts
TWENTY_APP_ACCESS_TOKEN je vložen automaticky — viz Proměnné aplikace. Protože tajné proměnné aplikace nejsou nikdy vystaveny front komponentám, ponechte API klíče a další citlivou logiku v logické funkci, ne ve front komponentě.

Volání Twenty REST API

Pro volání aplikačních HTTP tras nebo čtení a zápis záznamů Twenty z front komponenty použijte RestApiClient z twenty-client-sdk/rest. Odesílá cesty /s/... na základní URL funkcí vašeho workspace a všechny ostatní cesty, včetně /rest/..., na TWENTY_API_URL. Vždy jedná jako osoba, která si stránku prohlíží. runAs: 'application' je pouze volba funkce logiky: komponenta nikdy neobdrží vlastní token vaší aplikace, takže požadavek na něj zde vyvolá chybu. Práci, která vyžaduje vlastní přístup aplikace, vložte do funkce logiky a místo toho volejte ji. options přijímá headers, query (záznam parametrů dotazovacího řetězce; hodnoty typu nullish jsou vynechány) a AbortSignal prostřednictvím signal. Objekt body, který není typu FormData, je automaticky serializován do JSON. Při 401 klient jednou obnoví přístupový token prostřednictvím hostitele a požadavek znovu odešle. Základní URL a token jsou ve výchozím nastavení odvozeny z prostředí. Podle potřeby předávejte konstruktoru přepsané hodnoty — například v testech:
Neúspěšné požadavky vyvolají RestApiClientError, který zpřístupňuje status, statusText, url a parsované body:

Přístup k běhovému kontextu

Uvnitř komponenty použijte hooky SDK pro přístup k aktuálnímu uživateli, záznamu a instanci komponenty:
src/front-components/record-info.tsx
Dostupné hooky:

Aplikační proměnné

Aplikační proměnné definované v defineApplication() s isSecret: false jsou k dispozici ve front-endových komponentách prostřednictvím pomocné funkce getApplicationVariable:
src/front-components/greeting.tsx
Tajné proměnné (isSecret: true) nejsou zpřístupněny front-endovým komponentám. Jsou k dispozici pouze v logických funkcích, které běží na straně serveru. Tím se zabrání odesílání citlivých hodnot, jako jsou API klíče, do prohlížeče.
getApplicationVariable vždy vrací string (nebo undefined), bez ohledu na deklarovaný type proměnné. Řetězec je serializován konzistentně podle typu (logické hodnoty jako "true" / "false", čísla jako desetinné řetězce, pole / objekty jako JSON), ve stejném formátu, jaký používá process.env v logických funkcích — zpracujte jej sami (Number(...), JSON.parse(...), === 'true'). Viz Typy proměnných. Následující systémové proměnné jsou vždy dostupné přes process.env:

TWENTY_FUNCTIONS_URL

Twenty také vkládá TWENTY_FUNCTIONS_URL do front komponent a logických funkcí: základní URL, ze které jsou poskytovány vaše logické funkce spouštěné přes HTTP. Existuje proto, že tato URL není vždy samotný server Twenty. V Twenty Cloud jsou aplikační trasy poskytovány na vyhrazené doméně pro každý workspace (https://\<your-workspace-subdomain>.withtwenty.com, nebo hlavní veřejná doména aplikace, pokud je nakonfigurována), aby odpovědi vytvořené aplikací běžely na odděleném původu, a nikoli na původu aplikace Twenty. Self-hostované a lokální instance poskytují aplikační trasy pod předponou /s přímo na serveru a proměnnou nemusí vůbec nastavovat. Protože se základní URL liší podle workspace a instance, váš kód ji nemůže napevno zakódovat — server správnou hodnotu vloží za běhu. Jen zřídka ji potřebujete číst přímo. Volání svých tras provádějte přes RestApiClient s cestou s předponou /s/ a klient za vás URL vyřeší: odstraní předponu /s a zacílí na TWENTY_FUNCTIONS_URL, přičemž pokud proměnná není nastavena, použije jako zálohu \<TWENTY_API_URL>/s. Použijte resolveUrl('/s/\<path>') pro získání absolutní URL bez odeslání požadavku, např. pro odkaz. Proměnnou čtěte přímo pouze při ručním sestavování URL:

API komunikace s hostitelem

Frontendové komponenty mohou pomocí funkcí z twenty-sdk vyvolávat navigaci, modály a oznámení: Zde je příklad, který používá hostitelské API k zobrazení snackbaru a zavření postranního panelu po dokončení akce:
src/front-components/archive-record.tsx

Úložiště

localStorage a sessionStorage fungují stejně jako na běžné stránce, se standardním synchronním API. Vaše klíče jsou omezené na instalaci vaší aplikace a přihlášeného uživatele: žádná jiná aplikace je nemůže číst a jiný uživatel, který se přihlásí ve stejném prohlížeči, začne s prázdným úložištěm. Hodnoty zapsané do localStorage zůstávají v zařízení i po opětovném načtení; sessionStorage trvá po dobu relace prohlížeče.
src/front-components/note-draft.tsx
Twenty ukládá hodnoty jménem vaší aplikace, takže zápis se provede lokálně okamžitě a na pozadí se uloží. Operace čtení nikdy nečekají na hostitele. Nic se nesynchronizuje: hodnoty uživatele se s ním nepřenášejí do jiného prohlížeče nebo počítače, takže pro cokoli, co musí přežít změnu zařízení, použijte úložiště klíč–hodnota logické funkce. Zápisy mají limity a všechny limity počítají znaky místo bajtů: klíče mohou mít nejvýše 512 znaků, jedna hodnota nejvýše 262 144 znaků a každé úložiště nejvýše 1 048 576 znaků na aplikaci a uživatele. Zápis, který překročí některý limit, vyvolá QuotaExceededError, podobně jako rozhraní API prohlížeče.

Práce s více záznamy

Použijte useSelectedRecordIds() pro zpracování více vybraných záznamů. To je užitečné pro hromadné operace:
src/front-components/bulk-export.tsx
Zobrazte ji pomocí položky příkazové nabídky omezené na výběry záznamů:
src/command-menu-items/bulk-export.command-menu-item.ts

Veřejné soubory

Frontendové komponenty mohou přistupovat k souborům ze složky aplikace public/ pomocí getPublicAssetUrl:
Podrobnosti viz sekci veřejných souborů.

Sdílení závislostí mezi front komponentami

Ve výchozím nastavení si každá front komponenta přibalí vlastní kopii knihoven, které importuje, takže aplikace s pěti komponentami dodává React pětkrát. Deklarujte sdílené závislosti v package.json vaší aplikace, aby se tyto knihovny sestavily jen jednou a každá komponenta aplikace je načítala z jednoho společného souboru v mezipaměti:
package.json
Každá komponenta pak importuje své závislosti úplně stejně jako dříve — ve vašem komponentním kódu se nic nemění:
src/front-components/counter.tsx
Několik věcí, které je dobré vědět:
  • Jeden sdílený balík závislostí na aplikaci. Balík je sestaven z vlastních závislostí vaší aplikace, takže máte plnou kontrolu nad verzemi, které dodáváte.
  • Uveďte přesné specifikátory, které importujete. twenty-ui/input a twenty-ui/display jsou dvě položky; samotný název balíčku nepokrývá jeho podcesty. Uvedení react automaticky pokrývá react/jsx-runtime.
  • Sdílejte react-dom/client spolu s react. Každá komponenta renderuje přes createRoot, takže pokud ho vynecháte, každá komponenta si stále přibalí vlastní React DOM.
  • Balík je cachovaný. Je poskytován pod URL s hashovaným obsahem s dlouhodobou neměnnou mezipamětí, takže se stáhne jednou a znovu používá ve všech komponentách aplikace, dokud se některá z jeho závislostí nezmění.
  • Komponenty, které neimportují žádný ze sdílených balíčků, ho nikdy nestáhnou.

Stylování

Frontendové komponenty podporují více přístupů ke stylování. Můžete použít:
  • Inline stylystyle={{ color: 'red' }}
  • Komponenty Twenty UI — vlastní knihovna komponent Twenty; podívejte se níže na Používání komponent Twenty UI
  • Emotion — CSS-in-JS s @emotion/react
  • Styled-components — vzory styled.div
  • Tailwind CSS — utilitní třídy
  • Jakákoli CSS-in-JS knihovna kompatibilní s Reactem

Používání komponent Twenty UI

Twenty dodává svou knihovnu komponent jako balíček twenty-ui. Frontendové komponenty jej mohou používat pro tlačítka, tagy, stavové štítky, čipy, avatary, ikony, typografii a tokeny motivu, které se automaticky přizpůsobují světlému a tmavému motivu pracovního prostoru.

Instalace

Přidejte balíček do své aplikace, připnutý k verzi, se kterou je dodána vaše instance Twenty:
twenty-ui je zabalen do vaší frontendové komponenty při sestavení, takže stačí, aby byl závislostí vaší aplikace — za běhu není třeba nic konfigurovat.

Import komponent

Importujte z odpovídající podcesty místo z kořene balíčku, aby se do vašeho bundlu dostaly jen komponenty, které používáte:

Ikony

Importujte jednotlivé ikony z twenty-ui/icon:
Každá pojmenovaná ikona je odstraňována tree-shakingem, takže import několika málo ikon přidá do vašeho bundlu jen minimum navíc. Vyhněte se IconsProvider, useIcons a iconsState — natáhnou celou sadu ikon Tabler (několik MB).

Témování a tokeny motivu

Komponenty Twenty UI se automaticky přizpůsobí světlému a tmavému motivu pracovního prostoru — renderer použije na hostiteli aktivní barevné schéma a komponenty podle něj odvodí své barvy. Chcete-li ve svých vlastních inline stylech používat stejné design tokeny, zavolejte hook useTheme(). Vrací tokeny motivu Twenty (odsazení, barvy, poloměry, písma) napojené na aktivní motiv, aniž by bylo potřeba v komponentě nastavovat ThemeProvider:
Protože useTheme() je hook, čtete tokeny uvnitř těla komponenty, takže hodnoty vždy odrážejí aktuální motiv. Stejná mapa tokenů je také exportována jako konstanta themeCssVariables, ale ve frontendových komponentách preferujte useTheme() — modulová konstanta, která dereferencuje themeCssVariables, může být během extrakce manifestu aplikace nedefinovaná. Chcete-li se explicitně větvit podle aktivního schématu, načtěte jej pomocí useColorScheme() z twenty-sdk/front-component, která vrací 'light' nebo 'dark'.

Aktuální omezení

Komponenty Front jsou aktivně vyvíjeny. Renderování, stylování, obsluha událostí, měření prvků a úložiště prohlížeče fungují dobře. Cokoli, co sahá mimo tyto oblasti (volání metody DOM na refu, sledování změn velikosti prvku, vytváření portálu mimo váš strom) dnes chybí nebo je nekompletní a většinou selhává tiše: bez vyvolání výjimky a bez chyby TypeScriptu, protože kostra je typovaná proti plnému DOMu prohlížeče. Pokud vás něco z toho blokuje, vytvořte issue, aby to bylo upřednostněno.

Rozvržení a měření

Prvky se mohou měřit samy: hostitel zrcadlí geometrii do sandboxu, takže čtení jsou obsloužena lokálně, ale mohou být zastaralá až o jeden snímek, a první čtení nikdy dříve neměřeného prvku vrátí nuly. Po zápisu znovu přečtěte v callbacku requestAnimationFrame nebo v efektu. Pozicování pomocí getBoundingClientRect nyní funguje, ale vše, co sleduje změny velikosti přes ResizeObserver (recharts ResponsiveContainer, autoUpdate z Floating UI), stále nefunguje. Stejně dávejte přednost CSS pro rozvržení: váš stylesheet se dostane ke skutečné stránce, takže flexbox, grid, aspect-ratio, clamp() a @container se chovají normálně, bez zpoždění o snímek.
requestAnimationFrame, fetch, setTimeout a queueMicrotask fungují bez prefixu window.. Pouze window.requestAnimationFrame(...) a podobné volání vyvolají výjimku.

Přístup k DOMu

ref vám dává sandboxový prvek, ne HTMLElement. Kvůli této mezeře v portálu popovery Radix, Headless UI, MUI a react-select ve výchozím stavu nic nevyrenderují. Většina z nich přijímá prop pro kontejner; nasměrujte ho na prvek, který jste vyrenderovali.

Události

Události myši, ukazatele, dotyku, přetažení, klávesnice, fokusu, input/change/submit, scroll/wheel/contextmenu a animationend/transitionend se předávají hostiteli, a navíc několik dalších podle typu prvku: load/error na img, schránka a skládání (composition) na input/textarea, média na video/audio, toggle na details/dialog. Cokoli dalšího (onAuxClick, onSelect, onInvalid, onReset, onAnimationStart, zachycení ukazatele (pointer capture), onLoad na img) je bez varování zahazováno. document.addEventListener() a window.addEventListener() se zaregistrují bez chyby, ale nikdy se nespustí, což je důvod, proč se přetažení zastaví, jakmile ukazatel opustí prvek, na kterém začalo. Ani event.preventDefault() se nepřenáší; odeslání formuláře, dragover/drop a kliknutí na odkazy jsou už za vás ošetřené.

Atributy a stylování

Každý prvek předává své vlastní vlastnosti do hostitelského DOM (href na a, src/alt na img, value/placeholder/disabled na input atd.), a navíc má každý prvek společnou sadu: id, className, style, title, tabIndex, role, draggable a jakýkoli atribut aria-* / data-* (s pomlčkou; ariaLabel je proto ignorována). Cokoli mimo tento rozsah je tiše zahazováno, takže vlastní stav vyjádřete jako data-*. Komponentové CSS, ať už z import './styles.css', CSS-in-JS nebo z prvku style, je do head hostitelské stránky vkládáno bez ohraničení (unscoped). Třídy se tak střetávají s vlastními třídami Twenty (přidávejte jim předpony a nikdy nepište holé div { ... } selektory) a @media se vztahuje k oknu prohlížeče, nikoli k vašemu widgetu (použijte @container s vlastním container-type). Inline propy style nejsou ovlivněny.

Úložiště a síť

localStorage a sessionStorage poskytuje Twenty, nikoli prohlížeč: komponenta běží ve workeru v neprůhledném původu, takže hostitel ukládá hodnoty jménem vaší aplikace. Jejich rozsah a limity najdete v části úložiště. IndexedDB, cookies, Cache API a BroadcastChannel jsou nadále nedostupné. Pro uchování stavu mezi zařízeními zavolejte logickou funkci a použijte její úložiště klíč–hodnota. fetch funguje, s výhradami:
  • Volání na Twenty API a trasy (routes) vaší aplikace jsou proxyována hostitelem, proto upřednostněte RestApiClient. U proxyovaných volání jsou AbortSignal a ostatní volby RequestInit zahazovány a jsou podporována pouze těla typu string a URLSearchParams.
  • Jiné původy opouštějí sandbox s Origin: null, takže API třetí strany odpoví jen tehdy, pokud posílá Access-Control-Allow-Origin: *. Místo toho jej volejte z logické funkce.
  • fetch('/rest/people') se nikdy nespáruje s Twenty API, protože sandbox nemá URL stránky, podle které by vyhodnotil relativní cestu.

Zachytávání médií

navigator.mediaDevices.getUserMedia() a MediaRecorder fungují uvnitř frontendových komponent díky sandboxovým polyfillům, takže standardní kód pro nahrávání běží beze změn a MediaRecorder.isTypeSupported udává, zda jsou podporovány běžné kombinace kontejneru/kodeku. Detailní objekty omezení getUserMedia jsou akceptovány, ale nejsou předávány dál — hostitel zachytává s výchozím nastavením pro požadované druhy — a napříč aplikacemi může být v daný čas aktivní pouze jedno zachytávání. Uložte zaznamenaný Blob pomocí hostitelské funkce uploadFile.

Další omezení

  • Obsah souborů. input typu file poskytuje vašemu handleru pouze metadata souboru, nikoli samotné bajty, takže FileReader není k dispozici. Chcete-li nahrát Blob, který váš kód již má k dispozici — např. jeden vytvořený pomocí MediaRecorder — použijte hostitelskou funkci uploadFile.
  • Payloady drag-and-drop. Události tažení se spouštějí, ale event.dataTransfer je undefined.
  • Vestavěné moduly Node. fs, path a node:crypto způsobí chybu při sestavení, takže tuto práci přesuňte do logické funkce. Web Crypto, fetch, TextEncoder a URL jsou k dispozici.
  • iframe je vždy znovu zasandboxováno bez allow-same-origin, takže vložený obsah spoléhající se na vlastní relaci se vykreslí jako odhlášený. Nemá ani onLoad.