Session
A shared session: the client whose session the page shares, its role in it, and the peers' cameras and pointers.
Types
CursorData
type CursorData = {
active?: boolean;
buttons?: number;
point?: [number, number, number];
};
A peer's pointer, broadcast on the 'cursor' presence channel. point is
world-space, projected onto the sender's view plane (the plane through the
orbit target perpendicular to the view direction) — meaningful for any
receiver, and exact when the receiver follows the sender's camera.
Properties
FollowState
type FollowState =
| {
name: string;
peerId: string;
}
| null;
Whose camera this page looks through, if anyone's: the peer that is followed (viewpoints.useFollow()).
Union Members
Type Literal
{
name: string;
peerId: string;
}
| Name | Type | Description |
|---|---|---|
name | string | Its name. |
peerId | string | The followed peer's connection id. |
null
IdentityColors
type IdentityColors = {
accent: string;
bg: string;
solid: string;
text: string;
};
The colours of one participant of a shared session, in tones that go together: one hue, picked from its name.
They are CSS colours (hsl(…)), and do not change with the app's theme: bg and text are a pair that reads on
its own. viewpoints.identityColors(name) gives them.
Properties
ViewData
type ViewData = {
height?: number;
name?: string;
position?: [number, number, number];
target?: [number, number, number];
up?: [number, number, number];
zoom?: number;
};
A peer's camera, broadcast on the 'view' presence channel: where it is, what it looks at, and its name.
viewpoints keeps the last of every peer's (viewpoints.useViewpoints()).
Properties
sessionClient
The session this page shares, for react-cad's components: which client it is, this page's role in it, and what its host offers.
A thin React layer over the sharing API of @buerli.io/classcad (session/share.ts); any buerli app can build its
own UI on that API without react-cad. Which client is "the session client" is decided by the app, with
{@link setSessionClient}: a WSClient (the session is a ClassCAD server's, or a ClassCAD MCP's) or a WASMClient
(the page hosts it itself). Both have the same surface (role, peers, invites, createInvite/revokeInvite,
presence). The Session Management panel, read-only views for a viewer and the export menu's formats follow it.
Example
import { init, onClient, syncSelection, WSClient } from '@buerli.io/classcad'
import { elements, sessionClient, SessionManagement, viewpoints } from '@buerli.io/react-cad'
init((drawingId) => new WSClient('wss://classcad.example.com', drawingId), {
elements,
globalPlugins: [SessionManagement],
})
// every client, before it connects: its session is the page's; the peers' cameras and selections are heard
onClient((client) => {
sessionClient.setSessionClient(client)
viewpoints.syncViewpoints(client)
syncSelection(client)
})
sessionClient.buildInviteUrl()
function buildInviteUrl(token): string;
Builds a shareable URL for a token (?invite=<token>), preserving the current origin and path.
Parameters
| Parameter | Type | Description |
|---|---|---|
token | string | An invite's token: its invite, as createInvite returns it and the client's invites list it. |
Returns
string
Example
import { sessionClient } from '@buerli.io/react-cad'
// a button of the host's: an invite to view the session, its link copied
function ShareButton() {
const client = sessionClient.useSessionClient()
if (!client?.shareKind || sessionClient.getInviteFromUrl()) return null
const share = async () => {
const { invite } = await client.createInvite('view', 'Review')
await navigator.clipboard.writeText(sessionClient.buildInviteUrl(invite))
}
return <button onClick={share}>Share for review</button>
}
sessionClient.getInviteFromUrl()
function getInviteFromUrl(): string | undefined;
The share token this page was opened with, if any: then it is a guest in somebody else's session.
Returns
string | undefined
sessionClient.offeredTypes()
function offeredTypes(own, saveFormats?): string[];
The file types (lower case: 'stp') a menu offers for saving: the app's own, less what the session's host keeps back, then what the host offers beyond them.
Parameters
| Parameter | Type | Description |
|---|---|---|
own | readonly string[] | The types the app saves itself, such as ['ofb', 'stp', 'stl']. |
saveFormats? | string[] | What the host offers (SessionConfig.saveFormats); without it, the app's own. |
Returns
string[]
Example
import { createApi } from '@buerli.io/classcad'
import type { DrawingID } from '@buerli.io/core'
import { Menu, sessionClient } from '@buerli.io/react-cad'
import type { MenuItems } from '@buerli.io/react-cad'
// an export menu with what the app saves, as far as the session's host allows, and what the host adds
function ExportMenu({ drawingId }: { drawingId: DrawingID }) {
const { saveFormats } = sessionClient.useSessionConfig()
const types = sessionClient.offeredTypes(['ofb', 'stp', 'stl'], saveFormats)
const items: MenuItems = Object.fromEntries(
types.map((type) => [type, { caption: type.toUpperCase(), callback: () => createApi(drawingId).v1.common.save({ format: type.toUpperCase() as 'STP', encoding: 'base64' }) }]),
)
return <Menu items={items} />
}
sessionClient.SessionConfig
type sessionClient.SessionConfig = {
saveFormats?: string[];
};
What a session's host says its guests are offered. It travels on the 'config' presence channel: only a host may publish there, and the server keeps the frame for those who join later. A session whose host says nothing offers everything the app can do.
See
Properties
sessionClient.setSessionClient()
function setSessionClient(client): void;
Registers the client whose session this page shares (null: none). The app calls it when it creates the client, before connecting; from then on the hooks below follow that client, and what its session's host offers is heard.
Parameters
| Parameter | Type | Description |
|---|---|---|
client | AwvNodeClient | null | The drawing's client, such as the one onClient (@buerli.io/classcad) hands over. |
Returns
void
sessionClient.useSessionClient()
function useSessionClient(): AwvNodeClient | null;
Returns the session client the app registered (setSessionClient), or null when it registered none; and renders again when another is registered.
Returns
AwvNodeClient | null
sessionClient.useSessionConfig()
function useSessionConfig(): sessionClient.SessionConfig;
Returns what the host of the session this page is in offers its guests (SessionConfig), and renders again when the host says otherwise.
Returns
sessionClient.useSessionRole()
function useSessionRole(): SessionRole;
This connection's role within a shared session, kept reactive. Guests get
their role from the server's SessionJoined frame; hosts and non-sharing
clients are always 'edit'.
Returns
SessionRole
Example
import type { DrawingID } from '@buerli.io/core'
import { Drawing, sessionClient } from '@buerli.io/react-cad'
// a viewer of a shared session browses the model, but has no tools to change it
function Editor({ drawingId, children }: { drawingId: DrawingID; children: React.ReactNode }) {
const readOnly = sessionClient.useSessionRole() === 'view'
return (
<Drawing drawingId={drawingId} readOnly={readOnly}>
{children}
</Drawing>
)
}
viewpoints
What the peers of a shared session show of themselves besides the model: their cameras and pointers, and whose camera this page follows.
A small store for the whole page, fed by the session's presence frames once {@link syncViewpoints} is attached to the client: 'view' (a peer's camera, {@link ViewData}), 'cursor' (its pointer, {@link CursorData}) and 'leave'. The canvas components (a peer's camera as a marker, a camera that follows one) and the page around the canvas (a banner that says whom it follows) read the same store with its hooks, across the canvas' boundary. The colours a peer is drawn in are {@link identityColors} of its name.
Example
import { viewpoints } from '@buerli.io/react-cad'
// the peers of the session, each in its colour; a click looks through that peer's camera
function Peers() {
const views = viewpoints.useViewpoints()
const follow = viewpoints.useFollow()
return (
<ul>
{Object.entries(views).map(([peerId, view]) => (
<li key={peerId} style={{ color: viewpoints.colorFor(view.name ?? '') }}
onClick={() => (follow?.peerId === peerId ? viewpoints.clearFollow() : viewpoints.setFollow(peerId, view.name ?? 'unnamed'))}>
{view.name ?? 'unnamed'} {follow?.peerId === peerId && '(following)'}
</li>
))}
</ul>
)
}
viewpoints.clearFollow()
function clearFollow(): void;
Stops following a peer's camera.
Returns
void
viewpoints.colorFor()
function colorFor(name): string;
The solid colour of a participant (identityColors(name).solid): for frustums, name tags, cursors, swatches.
Parameters
| Parameter | Type | Description |
|---|---|---|
name | string | The display name. |
Returns
string
viewpoints.getCursors()
function getCursors(): Record<string, CursorData>;
The last pointer of every peer, by peer id, outside of a component (useCursors in one).
Returns
Record<string, CursorData>
viewpoints.getFollow()
function getFollow(): FollowState;
Whose camera this page follows, outside of a component (useFollow in one).
Returns
viewpoints.getModelRadius()
function getModelRadius(): number | null;
The radius of the sphere around the model, as setModelRadius gave it, outside of a component.
Returns
number | null
viewpoints.getViewpoints()
function getViewpoints(): Record<string, ViewData>;
The last camera of every peer, by peer id, outside of a component (useViewpoints in one).
Returns
Record<string, ViewData>
viewpoints.identityColors()
function identityColors(name): IdentityColors;
The colours a participant of a shared session is shown in, picked from its display name.
The name, not the connection's peer id: the same person gets the same colours on every client, in every session, and in everything that shows them. The pick is a hash of the name (case and outer spaces aside) into a palette of twelve hues, so every client agrees on it. An empty name is grey.
Parameters
| Parameter | Type | Description |
|---|---|---|
name | string | The display name: an invite's name, Host, unnamed, or a name the user set. |
Returns
Example
import { viewpoints } from '@buerli.io/react-cad'
// a name tag in a participant's colours: the same on every client
function NameTag({ name }: { name: string }) {
const { bg, text, accent } = viewpoints.identityColors(name)
return <span style={{ background: bg, color: text, border: `1px solid ${accent}`, borderRadius: 4 }}>{name}</span>
}
viewpoints.IdentityColors
Renames and re-exports IdentityColors
viewpoints.setFollow()
function setFollow(peerId, name): void;
Follows a peer's camera: this page's camera is to look through it, until clearFollow, or until the peer leaves.
The store only says whom to follow; the canvas component that moves the camera reads it with useFollow.
Parameters
| Parameter | Type | Description |
|---|---|---|
peerId | string | The peer's connection id, as useViewpoints keys it. |
name | string | Its name, for whatever says whom the page follows. |
Returns
void
viewpoints.setModelRadius()
function setModelRadius(radius): void;
Says how large the model is: the radius of the sphere around it (world units), or null while there is none.
The peers' camera markers are placed on a ring of that size around the model, not where their zoom would put them.
The app feeds it from the drawing's structure (api.structure.calculateProductBounds).
Parameters
| Parameter | Type |
|---|---|
radius | number | null |
Returns
void
viewpoints.syncViewpoints()
function syncViewpoints(client): () => void;
Attaches the store to a WSClient: 'view' presence updates the peer's viewpoint, 'cursor' its pointer, 'leave' removes both (and exits follow mode if that peer was being followed). Returns a detach function.
Only one client is attached at a time: attaching a new one detaches the previous client and clears the store first, so peers of a replaced session (new file / new drawing) never linger as stale markers.
Attach it before the client connects: the peers' cameras arrive right after it has joined, before any component has mounted. (See the example of sessionClient.)
Parameters
| Parameter | Type | Description |
|---|---|---|
client | AwvNodeClient | The session's client: a WSClient or a WASMClient of @buerli.io/classcad. |
Returns
A function that detaches it again, and clears the store.
() => void
viewpoints.useCursors()
function useCursors(): Record<string, CursorData>;
Returns the last pointer of every peer, by peer id, and renders again when one moves, comes or goes.
Returns
Record<string, CursorData>
viewpoints.useFollow()
function useFollow(): FollowState;
Returns whose camera this page follows (null: no one's), and renders again when that changes.
Returns
viewpoints.useModelRadius()
function useModelRadius(): number | null;
Returns the model's radius (setModelRadius), and renders again when it changes.
Returns
number | null
viewpoints.useViewpoints()
function useViewpoints(): Record<string, ViewData>;
Returns the last camera of every peer, by peer id, and renders again when one moves, comes or goes.
Returns
Record<string, ViewData>