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'
;
Toolbar button properties:
icon: This can either be a Pexip-provided icon or a custom one as a raw svg string
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 asicon: {custom: {main: string, hover: string}}
tooltip: Button tooltip and
alt
prop where a render isn't possibleroles: 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 tofalse
, 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
- buttonState: Refer to Add a button
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
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
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.
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
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