Fejlesztőknek
Kezdj bele

UI

Hogyan épül fel és fut egy natív worker. Célpontok, életciklus, használható komponensek és hookok, az egyes hívások korlátai, valamint blokkbeállítások és fordítások.

Ezen az oldalon

A worker egy olyan React-kód, amely HTML helyett a Poltura komponenseivel rajzol. Különálló, biztonságos környezetben fut, nem az adminfelület vagy a webáruház oldalán, és leírást küld a platformnak arról, mit kell megjeleníteni; az adminfelület vagy a téma pedig a saját komponenseivel rajzolja ki azt. A worker ezért nem éri el az oldal DOM-ját, a cookie-kat vagy a hálózatot, és éppen ezért néz ki natívnak bárhol, ahol megjelenik.

Minden worker-fájl ugyanúgy kezdődik: először importáld a @poltura/app-ui/worker csomagot, majd jelenítsd meg a root komponenst a @poltura/app-ui/worker-runtime csomag startUiWorker függvényével.

Célpontok

Adminfelület: admin.page, admin.home, admin.product, admin.collection, admin.order, admin.customer.

Webáruház-sablonok: storefront.home, storefront.products, storefront.product, storefront.cart, storefront.checkout, storefront.login, storefront.register, storefront.account, storefront.contact, storefront.page.

Layout: storefront.layout.header, storefront.layout.footer.

A header zónák önálló, teljes szélességű sort alkotnak a navigáció alatt, a rögzített headeren kívül, mobilon is: a magasabb worker lefelé tolja az oldalt ahelyett, hogy kitakarná. A lábléczónák szintén a dokumentumfolyamban helyezkednek el.

App-oldal: storefront.app-page.

Egy release legfeljebb 16 workert tartalmazhat, amelyek mérete egyenként legfeljebb 1048576 bájt. Egy renderelt snapshot legfeljebb 32768 bájt és 8 szint mély lehet, valamint legfeljebb 128 node-ot és node-onként 24 gyermekelemet tartalmazhat.

Életciklus

Amikor egy worker csatolódik, a platform elküldi neki a munkamenetet és a kontextusát (nyelvi beállítás, beállítások, téma). A startUiWorker rendereli a komponenst, és minden renderelés után snapshotot küld. Ettől kezdve a worker eseményeket fogad (egy press, egy change), kontextusfrissítéseket kap a beállítások változásakor, valamint megkapja a tool calljaira adott válaszokat. Amikor a kereskedő vagy a vásárló távozik, a worker megsemmisül. A platform minden tool callt újra ellenőriz a telepítés alapján, mielőtt végrehajtaná azt.

A teljes példa adminfelületi oldala:

export function AdminPage(): ReactNode {
  const copy = copyFor(useLocale());
  const settings = useSettings();
  const navigate = useNavigate();
  const toast = useToast();
  const pick = useResourcePicker();
  const saveFeatured = useAction('save_featured');
  const heading = String(settings.heading ?? copy.heading);
  const [picked, setPicked] = useState('');

  return (
    <Card className='offer-panel rounded-theme bg-surface text-foreground p-4'>
      <Stack>
        <Heading>{heading}</Heading>
        <Text>{copy.adminIntro}</Text>
        <TextField label={copy.featuredLabel} value={picked} onChange={setPicked} />
        <Inline>
          <Button
            label={copy.pickProducts}
            onPress={async () => {
              const ids = await pick('product', true);
              if (ids === null) return;
              setPicked(ids.join(','));
              await toast('success', copy.pickedToast);
            }}
          />
          <Button
            label={copy.save}
            onPress={async () => {
              const productIds = picked
                .split(',')
                .map((value) => value.trim())
                .filter((value) => value.length > 0);
              await saveFeatured({
                productIds,
                expectedVersion: 0,
                idempotencyKey: crypto.randomUUID(),
              });
              await toast('success', copy.savedToast);
            }}
          />
          <Button
            label={copy.openPage}
            onPress={() => navigate({ kind: 'app_page', pageId: RECOMMENDATIONS_PAGE })}
          />
        </Inline>
      </Stack>
    </Card>
  );
}
export function AdminProduct(): ReactNode {
  const copy = copyFor(useLocale());
  const selected = useSelectedEntity();
  const navigate = useNavigate();
  if (selected.status !== 'ready' || selected.entity.kind !== 'product') {
    return <Text>{copy.unavailable}</Text>;
  }
  return (
    <Card className='rounded-theme bg-surface text-foreground p-4'>
      <Stack>
        <Heading>{selected.entity.product.title}</Heading>
        <Button
          label={copy.openSettings}
          onPress={() => navigate({ kind: 'app_page', pageId: RECOMMENDATIONS_PAGE })}
        />
      </Stack>
    </Card>
  );
}
export function StorefrontProduct(): ReactNode {
  const copy = copyFor(useLocale());
  const settings = useSettings();
  const theme = useTheme();
  const cart = useCart();
  const toast = useToast();
  const host = useStorefrontContext();
  const recommend = useAction('recommend');
  const heading = String(settings.heading ?? copy.alsoLike);
  const storeName = theme?.settings?.store_name;
  const [contextLabel, setContextLabel] = useState(copy.alsoLike);

  useEffect(() => {
    void host.read().then(async (context) => {
      if (context.kind === 'product') setContextLabel(context.product.title);
      const suggestion = await recommend({
        productId: context.kind === 'product' ? context.product.id : null,
      });
      const title = z.object({ title: z.string().min(1) }).safeParse(suggestion);
      if (title.success) setContextLabel(title.data.title);
    });
  }, [host, recommend]);

  return (
    <Card className='offer-panel rounded-theme bg-surface text-foreground p-4'>
      <Stack>
        <Heading className='font-display'>{heading}</Heading>
        <Text>{z.string().min(1).safeParse(storeName).data ?? contextLabel}</Text>
        <Button
          label={copy.addToCart}
          className='bg-primary text-on-primary'
          onPress={async () => {
            const parsed = VariantId.safeParse(settings.variant_id);
            if (!parsed.success) {
              await toast.show({ tone: 'warning', message: copy.cartRejected });
              return;
            }
            const reply = await cart.add({ variantId: parsed.data, quantity: 1 });
            if (reply.status === 'ok') {
              await toast.show({ tone: 'success', message: copy.added });
              return;
            }
            await toast.show({ tone: 'warning', message: copy.cartRejected });
          }}
        />
      </Stack>
    </Card>
  );
}

Primitive-ek

Node type-ok: Stack, Inline, Card, Text, Heading, Button, TextField, Select, Checkbox, Alert, Divider. A className legfeljebb 2048 karakter, control character-ek nélkül. A Text/Heading/Alert gyermekelemei content tartalommá válnak (legfeljebb 2000 karakter). A Heading level értéke 2 | 3 | 4 lehet. A Button label legfeljebb 160 karakter. A Select options 1 és 24 között lehet. Az onChange értéket kap, nem DOM eventet. A HTML és a DOM libraryk nem támogatottak.

Hookok

HookBelépési pontVisszatérési érték
useSettingsadminfelület, webáruházFeloldott elhelyezési beállítások (Record<string, string | number | boolean | null>, max. 64 kulcs)
useLocaleadminfelület, webáruházLocaleTag string
useThemeadminfelület, webáruházOpcionális UiAppearance (mód, tokenek, téma, deklarált nyilvános beállítások)
useExtensionContextadminfelület, webáruházTeljes UiExtensionContext
useSelectedEntityadminfelület{ status: 'loading' | 'unavailable' } vagy { status: 'ready', entity } product | collection | order | customer esetén
useHostContextadminfelület, webáruház{ status, context?: UiTargetContext }
useNavigateadminfelület(destination: UiNavigateDestination) => Promise<void> (app_page, host egy host célponttal, storefront_path)
useNavigatewebáruház{ to(path): Promise<void> } (StorefrontNavigatePath)
useToastadminfelület(tone: UiToastTone, message) => Promise<void> (üzenet max. 200 karakter)
useToastwebáruház{ show({ tone, message }) }
useResourcePickeradminfelület(kind: 'product' | 'collection', multiple) => Promise<string[] | null> (azonosítók max. 50)
useCartwebáruház{ read, add, update, remove }
useStorefrontContextwebáruház{ read(): Promise<UiTargetContext> }

Képességek és szabályzat

Minden képesség rendelkezik timeouttal, a folyamatban lévő hívások korlátjával, egy request window-val, valamint az elérhető felületek listájával:

MűvelettimeoutMsmaxPendingwindowMswindowRequestsfelületek
read_selected_entity50002100008adminfelület
read_context50002100008adminfelület, webáruház
navigate30001100008adminfelület, webáruház
toast200021000016adminfelület, webáruház
pick_resource150001100004adminfelület
cart.add50002100008webáruház
cart.update50002100008webáruház
cart.remove50002100008webáruház
cart.read300021000016webáruház

Egy képesség eredménye legfeljebb 16384 bájt lehet. A kosár mennyisége 1–99. UiCartRejectionReason: out_of_stock, invalid_variant, limit, unavailable. Válaszok: ok, unavailable, cancelled, rejected.

Beállítások és fordítások

A worker beállításai az element definition settings mezőjében találhatók, amelyeket a @poltura/app-sdk/elements csomag defineBlock és field.* függvényeivel írhatsz meg (ugyanabban a formátumban, amelyet a theme blockok használnak). A poltura app build statikusan olvassa be a definíciót, soha nem futtatja, és beírja a worker element mezőjébe (a nyilvános element definition a @poltura/app-contracts/ui-element csomagban) a type kulcs nélkül. A témaszerkesztő a label, description, icon és category értékeket jeleníti meg a választóban, a beállításokat az ellenőrzőpanelen, a style értékeket a Stílus lapon, valamint a presets elemeket beszúráskor: a sima csempe csak a default azonosítójú presetet alkalmazza, és minden más preset saját külön csempeként jelenik meg utána.

SzabálySzerződés
Field type-oktext, textarea, number, range, checkbox, select, radio, color, url, header, paragraph
Építőkfield.text, textarea, number, range, checkbox, select, radio, color, url, group (header), note (paragraph)
Setting ID-ksnake_case vagy kebab-case, max. 64 karakter; a hitelesítő adatra emlékeztető és a fenntartott nevek hibát adnak
Mezőkmax. 32; a teljes definíció legfeljebb 16384 bájt
Értékektext, textarea, url, color max. 2000 karakter; a select és a radio egy opció value értékét veszi fel; a number és range a min/max között marad; a checkbox logikai
Opciók1–20 egyedi érték
Stílus laplayout, spacing, size, typography, color, background, border, effects, visibility
Ikonok és kategóriákA platform ikon- és kategórialistájából választva
Presetekmax. 8; a preset value-knak a deklarált mezők érvényes értékeinek kell lenniük

Média, rich text, resource pickerek, darkDefault, gyermekelemek (blocks, slots) és templates nem érhetők el: a worker célpontja dönti el a sablont. A header és a paragraph nem hordoz értéket. A default nélküli mezők hiányoznak, amíg a kereskedő be nem állítja őket. Az element definition nélküli worker nem kap beállításokat.

A címkék, leírások, option labelek és preset labelek képezik az alapnyelvi szövegedet; a platform compile-olja őket a bolt minden nyelvére. A kereskedők a settings_i18n mezőben fordíthatják le a szöveges értékeket nyelvenként, amit a rendszer a téma mentésekor ugyanazon definíció alapján ellenőriz.

A manifest translations[] tömbje a displayName / description mezőket fedi le. Az oldalak translations[] mezője a címeket fedi le. A worker szövegezése a saját JSON-fájlod; a useLocale() a host environment nyelvi beállítását adja vissza. Ez a példa a src/locales/en.json és a src/locales/hu.json fájlokat tölti be:

{
  "heading": "Recommendations",
  "adminIntro": "Pick catalog products, then save. Native workers cannot call your origin; useAction save_featured relays to your backend.",
  "featuredLabel": "Featured product ids",
  "pickProducts": "Pick products",
  "save": "Save",
  "savedToast": "Featured products saved.",
  "pickedToast": "Products selected.",
  "openPage": "Open app page",
  "unavailable": "No product is selected.",
  "openSettings": "Open recommendations",
  "alsoLike": "You may also like",
  "addToCart": "Add to cart",
  "added": "Added to cart.",
  "cartRejected": "The cart could not add that variant.",
  "emailLabel": "Email",
  "subscribe": "Subscribe",
  "invalidEmail": "Enter a valid email address.",
  "subscribed": "Subscription captured.",
  "pageIntro": "This storefront app page is /apps/recommendations from storefrontPages slug recommendations.",
  "shopProducts": "Browse products",
  "footerHelp": "Footer capture stays on this mount. It does not call /apps-api/v1."
}
export function copyFor(locale: string): CopyTable {
  const table = locale.toLowerCase().startsWith('hu') ? hu : en;
  return CopyTable.parse(table);
}

Hostolt keretek

Az adminFrame.actions az alábbi bridge actionök részhalmaza: toast, set_loading, navigate_host, pick_resource, resize, get_session_token, get_appearance. Azok a hostolt oldalak, amelyek ugyanezeket a primitive-eket szeretnék használni worker nélkül, a @poltura/app-ui/dom csomagot és a get_appearance műveletet használják. Lásd: Adminfelület.

Következő lépések

Utoljára frissítve