Skip to main content

Visual Elements

Overview

{
/**
* @throws UIError
*/
addButton: (payload: RPCCallPayload<'ui:button:add'>) => Promise<Button>;
/**
* @throws UIError
*/
addForm: <P extends RPCCallPayload<'ui:form:open'>>(payload: P) =>
Promise<Form<P>>;
/**
* @throws UIError
*/
showForm: <K extends RPCCallPayload<'ui:form:open'>>(payload: K) =>
Promise<MapFormReturn<K['form']['elements']>>;
/**
* @throws UIError
*/
addPrompt: (payload: RPCCallPayload<'ui:prompt:open'>) => Promise<Prompt>;
/**
* @throws UIError
*/
showPrompt: (payload: RPCCallPayload<'ui:prompt:open'>) =>
Promise<EventPayload<'ui:prompt:input'>['input']>;
/**
* @throws UIError
*/
showToast: (payload: RPCCallPayload<'ui:toast:show'>) => Promise<void>;
/**
* @throws UIError
*/
togglePlugin: (
payload: RPCCallPayload<'ui:plugin:toggle'>,
) => Promise<void>;
}

As seen above, all functions that create UI elements can throw errors when something is wrong. The information in the error will provide useful information, hinting at the problem

export type UIError = {reason: string};

Button

  • Toolbar
  • ParticipantAction
  • SettingsMenu

Add a button

Syntax

const button = await plugin.ui.addButton(buttonState: ToolbarButton | ParticipantActionsButton | SettingsMenuButton)

Parameter Details

interface ToolbarButton {
position: 'toolbar';
icon: string | {custom: {main: string; hover: string}};
tooltip: string;
roles: Array<'chair' | 'guest'>;
opensPopup?: PopupRequest;
isActive?: boolean;
}

interface GroupButtonPayload extends Omit<ToolbarButton, 'position'> {
id: string;
}

// what you need to send for a toolbar button
interface ToolbarButtonPayload extends ToolbarButton {
group?: GroupButtonPayload[];
}

// what you need to send for a participant action
interface ParticipantActionsButtonPayload {
position: 'participantActions';
participantIDs?: string[];
label: string;
opensPopup?: PopupRequest;
}

// what you need to send for a settings menu button
interface SettingsMenuButtonPayload {
position: 'settingsMenu';
label: string;
inMeetingOnly: boolean;
roles?: Array<'chair' | 'guest'>;
opensPopup?: PopupRequest;
}
  • position: Where the button should be rendered. Accepted values are 'toolbar' or 'participantActions';

opensPopup

Toolbar button properties:

  • icon: This can either be a Pexip-provided icon or a custom one as a raw svg string

    • List of pexip icons

    • For a custom icon, once you've got a valid .svg file, a good idea is to run the file through https://jakearchibald.github.io/svgomg/ or a similar tool that optimises your svg and provides a valid string representation. You can then copy the optimised svg as a string and that's what you can pass as icon: {custom: {main: string, hover: string}}

  • tooltip: Button tooltip and alt prop where a render isn't possible

  • roles: Restricts availability to particular participant roles

  • isActive: Visually indicates that the action provided by the button is ongoing

  • group: A list of buttons that will appear when the current button is clicked

Participant actions button properties:

  • label: The text displayed for you participant action

  • participantIDs: Which participant IDs should this button be displayed for. All participants will render the button if the property is left undefined.

    For example, given participants A, B and C, If you add a button to participant A, then update the button leaving participantIDs undefined - the button will render for A, B and C.

Settings menu button properties:

  • label: The text displayed for you participant action

  • inMeetingOnly: If set to true, the option is only rendered when in a meeting. When set to false, it is rendered in any app state.

  • roles: Restricts availability to particular participant roles

Return Value

// If ToolbarButton:
interface Button {
/**
* When the toolbar button has a group, it does not have a click event. It's click event is instead used to show the group
* @param buttonId - Identifies which group button was clicked. Obsolete when the toolbar button does not have a group.
*/
onClick: Signal<{buttonId: string}>;
/**
* @throws UIError
*/
update: (payload: ButtonRPCPayload[K]['add']) => Promise<void>;
/**
* @throws UIError
*/
remove: () => Promise<void>;
}

// Participant Actions
{
onClick: Signal<{participantUuid: string}>;
/**
* @throws UIError
*/
update: (payload: ButtonRPCPayload[K]['add']) => Promise<void>;
/**
* @throws UIError
*/
remove: () => Promise<void>;
}

// Settings Menu
{
onClick: Signal<void>;
/**
* @throws UIError
*/
update: (payload: ButtonRPCPayload[K]['add']) => Promise<void>;
/**
* @throws UIError
*/
remove: () => Promise<void>;
}

onClick

Syntax

// ToolbarButton
button.onClick.add(({buttonId: string}) => {
console.log('button clicked');
});

// ParticipantAction Button
button.onClick.add(({participantUuid}) => {
console.log('button clicked for participant with id ', participantUuid);
});

Update the button

Syntax

button.update(buttonState)

Parameter Details

Return Value

void

Remove the button

Syntax

button.remove()

Return Value

void

Example

const button = await plugin.ui.addButton({
position: 'toolbar',
icon: 'IconPlayRound',
tooltip: 'Record',
roles: ['chair'],
});

button.onClick.add(async () => {
await button.update({
position: 'toolbar',
icon: 'IconStopRound',
tooltip: 'Stop Recording',
roles: ['chair'],
});
});

Form

Approach 1: Show a form & get input

Shows form and asynchronously resolves with the user input, after which, the form is automatically unloaded from the UI. This is the preferred way unless an explicit reference to the form is needed.

Syntax

await plugin.ui.showForm(formContent);

Parameter details

{
title: string;
description?: string;
form: {
elements: Record<string, FormElement>;
submitBtnTitle: string;
};
opensPopup?: PopupRequest;
}
  • title: Title of the form

  • description: Subtitle of the form

  • form:

    • submitBtnTitle: Submit button text

    • elements: Form content

  • opensPopup

interface FormElementBase {
name: string;
autoComplete?: string;
isOptional?: boolean;
}

export interface InputElement extends FormElementBase {
type: 'number' | 'text' | 'password' | 'email' | 'tel' | 'url';
placeholder?: string;
value?: string;
}

export interface TextArea extends FormElementBase {
type: 'textarea';
placeholder?: string;
value?: string;
}

export interface SelectElement extends FormElementBase {
type: 'select';
options: Array<{id: string; label: string}>;
selected?: string; // id of one of the defined options
required?: boolean;
}

export type ChecklistOption = {id: string; label: string; checked?: boolean};
export interface ChecklistElement extends FormElementBase {
type: 'checklist';
options: ChecklistOption[];
}

export type RadioOption = {
id: string;
label: string;
value: string;
isDisabled?: boolean;
};

export interface RadioGroupElement extends FormElementBase {
type: 'radio';
options: RadioOption[] | Readonly<RadioOption[]>;
groupName: string;
checked?: string;
}

export interface RangeSliderElement
extends Omit<FormElementBase, 'isOptional'> {
type: 'range';
max?: number;
min?: number;
step?: number;
selectedValue?: number;
}

export type FormElement =
| InputElement
| SelectElement
| ChecklistElement
| TextArea
| RadioGroupElement
| RangeSliderElement;

Return Value

Input from the form. Object with keys based on the element's key and value types based on the element's type.

Example

export const FormContent = {
title: 'This is an example form',
description:
'In this example form, we show all possible input elements you can make use of through the plugin system💪',
form: {
elements: {
randomText: {
name: 'Enter some random text',
type: 'text',
},
email: {
name: 'Enter your email address',
type: 'email',
autoComplete: 'email',
},
favoriteNumber: {
name: 'What is your favorite number?',
type: 'number',
},
secretPassword: {
name: 'Enter a very secret password😈',
type: 'password',
},
telephoneNumber: {
name: 'What is your phone number?',
type: 'tel',
},
url: {name: 'Enter a url', type: 'url'},
favoriteMeal: {
name: 'What is your favorite meal?',
type: 'select',
options: [
{id: '0', label: 'taco🌮'},
{id: '1', label: 'pizza🍕'},
{id: '2', label: 'hamburger🍔'},
],
},
sleepNights: {
name: 'Which nights are you going to stay?',
type: 'checklist',
options: [
{id: '0', label: '24'},
{id: '1', label: '25'},
{id: '2', label: '26', checked: true},
{id: '3', label: '27'},
{id: '4', label: '28'},
{id: '5', label: '29'},
],
},
},
submitBtnTitle: 'Submit your input!',
},
} as const;

const input = await plugin.ui.showForm(FormContent);
input.favoriteNumber; // number
input.email; // string
input.sleepNights; // {[id]: boolean}

Approach 2: Add a form & interact with the form

Adds an UI form element. Developer has the task to register for events and manage the form's lifecycle.

Syntax

const form = await plugin.ui.addForm(formContent)

Parameter details

Same as showForm

Return Value

export interface Form extends PluginElement {
onInput: Signal<Event<'ui:form:input'>>;
/**
* @throws UIError
*/
remove: () => Promise<void>;
}

onInput

Object with keys based on the element's key and value types based on the element's type.

Syntax

form.onInput.add(formInput => {
//do stuff
});

Remove the form

Syntax

form.remove()

Return Value

void

Example

const form = await plugin.addForm(formContent);
form.onInput.add(formInput => {
//do stuff
form.remove();
});

Prompt

Approach 1: Show a prompt & get input

Shows a promp and asynchronously resolves with the user input, after which, the prompt is automatically unloaded from the UI. This is the preferred way unless an explicit reference to the prompt is needed.

plugin.ui.showPrompt(promptContent)

Parameters

{
title: string;
description?: string;
prompt: {
primaryAction: string;
secondaryAction?: string;
};
opensPopup?: PopupRequest;
}
  • title: Title of the prompt

  • description: Subtitle of the prompt

  • prompt:

    • primaryAction: Primary action text

    • secondaryAction?: Secondary action text. Optional.

  • opensPopup

Return Value: string

Example

const formPrompt: RPCCallPayload<'ui:prompt:open'> = {
title: 'This is a prompt',
description:
'We need some info about you, and we hope you can fill out a form for us',
prompt: {
primaryAction: 'Go to form',
secondaryAction: 'Dismiss',
},
};

const input = await plugin.ui.showPrompt(formPrompt);
if (input === 'Go to form') {
plugin.ui.showForm(FormContent);
}

Approach 2: Add a prompt & interact with the form

Adds an UI prompt element. Developer has the task to register for events and manage the form's lifecycle.

plugin.ui.addPrompt(promptContent)

Parameter details

Same as showPrompt

Return Value

export interface Prompt extends PluginElement {
onInput: Signal<Event<'ui:prompt:input'>>;
/**
* @throws UIError
*/
remove: () => Promise<void>;
}

onInput

Syntax

prompt.onInput.add(string => {
//do stuff
});

Remove the prompt

Syntax

prompt.remove()

Return Value

void

Example

const prompt = await plugin.addPrompt(promptContent);
prompt.onInput.add(string => {
//do stuff
prompt.remove();
});

Toast

A basic Toast component with the sidenote that requests are queued up and the next one shows when the previous times out.

Parameters

export type NotificationToastMessage = {
message: string;
isDanger?: boolean;
/**
* A toast that can interrupt will jump the queue and remove the
* currently rendering toast and be rendered instead of it. Currently
* rendering toasts that have a timeout of 0 cannot be interrupted, the
* toast that tries to interrupt it is discarded and not queued.
*
* An example when this can be useful: if many
* toasts are sent rapidly in response to UI state changes;
* User mutes, unmute & mutes again and toasts are displayed to reflect these states,
* the toasts will display without a long timeout between each.
*/
isInterrupt?: boolean;
position?: 'topCenter' | 'bottomCenter';
timeout?: number;
canDismiss?: boolean;
};
  • timeout: How long should the Toast stay for. Default of 5000ms

Example

await plugin.ui.showToast({message: string});

Custom Iframe (v35)

Detailed description of what a custom iframe is can be found here

Example

await plugin.ui.togglePlugin({id: string});

id here is the id of the custom iframe, as described in IframeManager