Get started

Build a studio

Mount the studio

The elements draw an itinerary. The studio is what you edit one with: the map, the chrome over it, the floating panels, the co-pilot, and every dialog the editor raises for itself.

Two packages make it up. @mountaya/studio holds the rules and renders nothing. It publishes the 253 tool contracts, the stores a surface reads, and the vocabularies behind them. @mountaya/studio-react draws those surfaces with Mantine. Install and set up covers both.

Every colour, corner and typeface above belongs to Cairn Outdoors, a company we made up. The accent, the pill controls, the paper panels and the serif wordmark are --mtya-* tokens and one header prop. Open the Code tab to read all of them.

Rename a segment and the row, the profile band and the table cell change together. Each of those is one ToolBus.dispatch returning a ToolResult, which the panel under the studio shows. The demo holds a publishable key, so nothing persists and a reload restores the fixture.

Mount the editor

MountayaStudioHost owns the shared state: the UI store, the tool bus, the tool context, the cache a write invalidates, and the confirm gate. Every published handler is registered on that bus. MountayaStudio is the editor itself.

import { Mountaya } from "@mountaya/client";
import { MountayaStudio, MountayaStudioHost } from "@mountaya/studio-react";

const client = new Mountaya({ credential: { publishableKey, sessionToken } });

<MountayaStudioHost client={client} itineraryId={itineraryId}>
  <MountayaStudio />
</MountayaStudioHost>;

The studio writes as one member: the user your server minted the session token for, with user_id set to the person your own sign-in vouches for. The token is the whole statement of who acts, and every write the API receives is held to that member's role. Under a token minted with no user_id the studio is read-only throughout, whatever its grants say, and shows what your organization publishes rather than its drafts, until a token naming a user replaces it. MountayaStudioHost refuses at mount a publishable key given no session token, with a MountayaConfigurationError and before it sends anything. See acting as a member.

The publishable key needs three scopes, each with its product on for your organization. studio-embedded reads and writes what the editor shows, and it is enabled by agreement. tiles draws the map, because a tile requested from your own domain is checked against it. data routes, searches, and reads the ground. A key carrying studio-embedded always carries the other two: creating or updating one without tiles or data is a 400, one entry per missing scope. See authentication.

Every studio host runs on a session token minted with surface: "studio-embedded", the editor and a read-only MountayaStudioViewer alike. It is the only token a studio-embedded operation accepts, and the host reads the surface off the token its client carries and refuses any other with a MountayaConfigurationError before the studio sends anything. A client carrying a share token, as a share page's does, is not asked for one, and neither are Mountaya's web application and hosted embed. Everything the studio asks under it, its map's tiles and its Data API requests included, is recorded apart and never counted toward your organization's limits, allowances, or invoice, while your organization holds Embedded Studio. A viewer's token names nobody: leave user_id out of its mint, and keep surface. See a token is minted for a surface.

Those two props are the shortest call that works. The map draws through the host's client and derives its tile transform from it. Set mapClient and transformRequest on MountayaStudio only where the map's credential differs from the one the reads run on. A signed-in studio rides a session cookie for tiles and a key for everything else.

Chrome you mount outside MountayaStudio reads the same store and dispatches the same bus. A sidenav beside the map drives that map.

Configure the host

Everything else on the host arrives in four groups, so a prop sits where you would look for it.

GroupWhat you set there
dataThe viewer, the grants, the activity catalog, the organization roster, the query client.
behaviorYour handlers and seams, confirm, notify, the URL and router bindings, onDispatch.
appearanceThe colour scheme, the density, the tokens, the Mantine theme, the map paint.
i18nThe language, the locale, and both string bundles.

Handlers you supply spread over the published ones, so one key replaces one verb and the other 252 stand. Studio handlers is the reference for all of them.

Show it read-only

MountayaStudioViewer is the same map, the same controls and the same docked profile. Every authoring surface is withheld rather than disabled. Mount it on a share page, where the share token is its credential, or anywhere else under a key and a session token minted for studio-embedded that names nobody. That token reads as the public does, what a publishable key alone reaches: each itinerary as it was last published, never its draft, and each zone too, public-only listings, and tiles cut from published content. Your backend hands it to anyone who opens the page, so mint it for a key of its own: studio-embedded, with the tiles and data every key carrying it holds, and no management, as no key carrying a product scope can: see serving anonymous viewers. To preview drafts in a viewer, mint its token for the member who looks.

It takes a resolved embedConfig, a mapClient, and a zoomRange the publisher decides. The client and the tile transform must authenticate as the same audience. Otherwise a publisher previews what a visitor never gets. safetyRules clamps any override the surrounding page sends to the rules you applied when you resolved the config. Omit it and no override is accepted at all.

Wire your own map

MountayaStudio builds its map for you. Reach for the registry when you assemble the parts yourself. A map surface publishes itself through useStudioHostRegistry, and that is what ctx.map resolves to at dispatch time.

import { EditorMapView } from "@mountaya/elements-react/editor";
import { useStudioHostRegistry } from "@mountaya/studio-react";

const StudioMap = () => {
  const { onClientDestroy, onClientReady } = useStudioHostRegistry();

  return (
    <EditorMapView
      client={client}
      onClientDestroy={onClientDestroy}
      onClientReady={onClientReady}
    />
  );
};

The hook throws outside a host, because a register call that goes nowhere has no symptom: every map verb after it is a silent no-op. The video wizard's second map and the collaboration session register through calls of their own. A verb aimed at one never reaches another.

File into collections

A collection groups the itineraries and zones somebody owns. The assembled studio mounts the collection dialogs once, and each renders nothing until a verb opens it. A host that places a control of its own dispatches the verb:

import { useStudioDispatch } from "@mountaya/studio-react/headless";

const NewCollectionButton = () => {
  const { dispatch } = useStudioDispatch();

  return (
    <button onClick={() => void dispatch("studio.collection.createOpen", {})} type="button">
      New collection
    </button>
  );
};

studio.collection.createOpen creates into the active scope, the organization whose folders the surface beside it is showing. studio.collection.manage.open opens the dialog keyed to one itinerary or zone, which edits which folders hold it, and studio.collection.editOpen renames one.

studio.share.open opens the share sheet for one itinerary or one collection, named as { kind, id }. An itinerary's link reaches that itinerary alone, and a collection's reaches the collection and everything filed in it. A personal itinerary or collection is refused: only an organization's resources can be shared by link.

The sheet reads the link on open and creates nothing: opening it hands out no link that was not already there. Where the resource has one, the sheet shows it; where it has none, it offers a button that creates one, through studio.shareLink.mint, and only when the reader presses it. Its footer offers Reset and Disable, and the corner X closes it. Creating, resetting and disabling are bus dispatches, and the confirm for the two destructive ones is raised from each tool's own policy. Reading the link takes the edit grant too, a write role in the organization that owns the resource: for a reader whose role there reads only, the sheet sends no read and says so in place of the link. A link to an unpublished itinerary hands out nothing.

ShareLinkEngine of @mountaya/studio/engines is the machine behind the sheet, for a share surface of your own: it reads on its first subscriber, and its mint, rotate and disable run the three verbs.

ShareViewModal is what a visitor sees: the link and the destinations, with no lifecycle controls.

Next steps