Skip to main content

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​

PropertyTypeDescription
active?booleanWhether the pointer is over the sender's stage.
buttons?numberPressed mouse buttons, PointerEvent.buttons bitmask (1=left, 2=right). Lets receivers animate the cursor while the peer clicks or drags.
point?[number, number, number]Where the pointer is, in world coordinates on the sender's view plane.

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;
}
NameTypeDescription
namestringIts name.
peerIdstringThe 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​

PropertyTypeDescription
accentstringA medium tone, for borders and frames.
bgstringA light pastel ground: a comment's chip, a thread's panel.
solidstringA saturated tone, for what is drawn over the canvas: a camera's frustum, a name tag, a pointer, a swatch.
textstringA dark tone of the same hue, which always reads on bg.

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​

PropertyTypeDescription
height?numberVisible world height of the sender's viewport (viewportPx / zoom).
name?stringThe peer's name, for its name tag.
position?[number, number, number]Where the camera is (world coordinates).
target?[number, number, number]What it looks at: the orbit's target.
up?[number, number, number]Its up.
zoom?numberIts zoom (an orthographic camera's).

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​

ParameterTypeDescription
tokenstringAn 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​

ParameterTypeDescription
ownreadonly 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​

useSessionConfig

Properties​

PropertyTypeDescription
saveFormats?string[]The formats a model may be saved in from this session (upper case: 'STP', 'STL', 'OFB'). A host whose engine saves formats beyond ClassCAD's names them too ('GLB', 'OBJ'): the apps offer them after their own and ask for them with v1.common.save.

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​

ParameterTypeDescription
clientAwvNodeClient | nullThe 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.SessionConfig


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​

ParameterTypeDescription
namestringThe 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​

FollowState


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​

ParameterTypeDescription
namestringThe display name: an invite's name, Host, unnamed, or a name the user set.

Returns​

IdentityColors

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​

ParameterTypeDescription
peerIdstringThe peer's connection id, as useViewpoints keys it.
namestringIts 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​

ParameterType
radiusnumber | 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​

ParameterTypeDescription
clientAwvNodeClientThe 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​

FollowState


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>