-
Notifications
You must be signed in to change notification settings - Fork 1
PAPI
Platform.Bible API (PAPI) is a set of TypeScript APIs that you can use in your extension.
Each workspace contains a set of functions and variables provided to extensions for communicating across the PAPI and performing various tasks.
You can call these workspaces in your extension like:
papi.<workspace_name>.<member_name>
The services are unified modules that make importing the tools of the PAPI easy. There are frontend, backend, and a shared service. An extension file should either use papi-frontend or papi-backend, but not both.
src/shared/services/papi.service.ts
This is a unified module for accessing API features in extensions. The classes, functions, and workspaces exported from this module are shared between the frontend and backend. That means you can access all of these through the frontend and backend services, and you should not access it directly. This service exports the following:
const papi = {
//Classes
EventEmitter: papiEventEmitter,
//Functions
fetch: internetService.fetch,
//Services/Workspaces
commands: commandService,
util: papiUtil,
webViews: papiWebViewService,
network: papiNetworkService,
logger,
internet: internetService,
dataProvider: dataProviderService
};
provides a way to get anything from the Internet
provides a general logging system. The items that you log appear in the Platform.Bible console and go to file.
- macOS: ~/Library/Logs/
{app name}
/main.log - Linux: ~/.config/
{app name}
/logs/main.log - Windows: %USERPROFILE%\AppData\Roaming\
{app name}
\logs\main.log
Currently, this module only has fetch
making it a longer way to get to fetch
. The rest of the functions and workspaces are described in the sections below.
src/renderer/services/papi-frontend.service.ts
This is a unified module for accessing PAPI features in the web view. This module exports all of the shared services as well as the PAPI React context and React hooks. These two features are for use inside of React web views.
You can import the papi-frontend into your extension like:
import papi from "papi-frontend";
/src/extension-host/services/papi-backend.service.ts
This is a unified module for accessing API features in the extension backend. This module exports all of the shared services as well as the storage service. The ExtensionStorageService
provides extensions in the extension host the ability to read/write data based on the extension identity and current user. This service does not work in the renderer process.
You can import the papi-backend into your extension like:
import papi from "papi-backend";
/src/shared/models/papi-event-emitter.model.ts
This file contains the interfaces, classes, and functions related to events and event emitters. The PapiEventEmitter
is an event manager that accepts subscriptions to an event and runs the subscription callbacks when the event is emitted.
EventEmitter
is a class that allows you to create events and subscribe to them. This class is local which means that it cannot cross or go between the four processes (main, renderer, extension-host, c#). There are no predefined events for extensions, they are unique to the design of your extension.
/src/shared/models/papi-event.model.ts
This file contains declarations for PapiEvent
, PapiEventHandler
, and PapiEventAsync
.
/src/shared/models/papi-network-event-emitter.model.ts
This file contains the networked version of EventEmitter
.
/src/shared/services/command.service.ts
The command service handles registering, sending, and receiving commands with the Platform.Bible backend in a unified format. The command service is exposed on the PAPI so that extensions can initialize their own commands and extensions can use commands from other extensions.
Commands allow you to register a command that tells you to do something and then you would emit an event saying you did that thing. Commands are one to one whereas events are one to many. Commands follow a request-response pattern. The examples below are from paranext-extension-template
.
How to register a command:
papi.commands.registerCommand(
"extensionTemplate.doStuff",
(message: string) => {
return `The template did stuff! ${message}`;
}
)
How to send a command:
papi.commands.sendCommand(
"extensionTemplate.doStuff",
"Extension Template React Component"
);
This function sends a command to the backend.
export const sendCommand = async <CommandName extends CommandNames>(
commandName: CommandName,
...args: Parameters<CommandHandlers[CommandName]>
): Promise<Awaited<ReturnType<CommandHandlers[CommandName]>>> => { }
```
Parameter |
Description |
commandName
|
Command name for command function |
…args
|
Arguments of command to send with function |
Return | Description |
sendCommandUnsafe(commandName, …args)
|
Function to call with arguments of command that sends the command and resolves with the result of the command. |
This function registers a command on the PAPI to be handled here.
export const registerCommand: <CommandName extends CommandNames>(
commandName: CommandName,
handler: CommandHandlers[CommandName],
) => Promise<UnsubscriberAsync> = createSafeRegisterFn( ... );
Parameter |
Description |
commandName
|
Command name for command function |
handler
|
Handler function to run when the command is invoked |
Return | Description |
True
|
If successfully registered |
Error
|
If unsuccessful |
/src/shared/utils/papi-util.ts
This file contains functions to handle subscribe and unsubscribe actions. These functions are exposed on the PAPI for use in your extension, and specifically cleanup in the activate
function of the entry file.
/src/shared/utils/util.ts
This file contains a mismatch of utility functions. Currently, the functions included are not expected to be of much use to extension developers.
/src/renderer/hooks/papi-hooks/index.ts
React hooks let you use state and other React features without writing a class. There are built-in hooks that come with React or you may build your own. You can read more about React hooks in the React docs. The PAPI has exposed hooks for you to use in your React web views.
Gets a data provider with specified provider name
Subscribes to run a callback on a data provider’s data with specified selector on any data type that data provider serves
Adds an event handler to an asynchronously subscribing/unsubscribing event so the event handler runs when the event is emitted
Adds an event handler to an event so the event handler runs when the event is emitted
Awaits a promise and returns a loading value while promise is unresolved
src/shared/services/data-provider.service.ts
Data providers allow you to interact with PAPI data and hold resources to manage. It outlines what data you are sending, and what to expect back. The Data Provider service handles registering data providers and serving data around the PAPI. A data provider can provide multiple types of data, but they should be closely associated. Data is generally associated to a project, but it doesn’t have to be.
Data Provider model
/src/shared/models/data-provider.model.ts
Data Provider interface
src/shared/models/data-provider.interface.ts
Data Provider Engine model
/src/shared/models/data-provider-engine.model.ts
shared/services/network.service.ts
The network service handles requests, responses, subscriptions, etc. to the backend. It serves as a connection between extensions. An example of this connection would be an extension using the c# process to access usfm data provider. The parts of this service that are exposed on the PAPI are included in papiNetworkService
, and listed below.
Event that emits with clientId when a client connects
Event that emits with clientId when a client disconnects
Creates an event emitter that works properly over the network. Other connections receive this event when it is emitted.
Gets the network event with the specified type. Creates the emitter if it does not exist.
shared/services/web-view.service.ts
Handles registering web view providers and serving web views around the papi. The parts of this service that are exposed on the PAPI are included in PapiWebViewService
, and listed below.
Event that emits with webView info when a webView is added
Creates a new web view or gets an existing one depending on if you request an existing one and if the web view provider decides to give that existing one to you.
This function sets up the WebViewService
.
shared/services/web-view-provider.service.ts
Service that handles webview-related operations. The parts of this service that are exposed on the PAPI are included in PapiWebViewProviderService
, and listed below.
Register a web view provider to serve webViews for a specified type of webViews
These components allow developers of Platform.Bible extensions to have the same look and feel as the main application. Some are wrapped from Material UI, and others are unique to Platform.Bible. These will have style and functionality presets so that standalone extensions can mimic bundled extensions and the core. The component files are located in this folder. The currently offered components are:
Button a user can click to do something
id?: string;
isDisabled?: boolean;
className?: string;
onClick?: MouseEventHandler<HTMLButtonElement>;
onContextMenu?: MouseEventHandler<HTMLButtonElement>;
See descriptions here
import { Button } from 'papi-components';
<Button onClick={() => {
throw new Error(`${NAME} test exception!`)
}}
>
Throw test exception
</Button>
Allow users to select one or more items from a set
id?: string;
isChecked?: boolean;
labelText?: string;
labelPosition?: LabelPosition;
isIndeterminate?: boolean;
isDefaultChecked?: boolean;
isDisabled?: boolean;
hasError?: boolean;
className?: string;
onChange?: (event: ChangeEvent<HTMLInputElement>) => void;
See descriptions here
import { Checkbox } from 'papi-components';
<Checkbox labelText="Test Me"/>
Dropdown selector displaying various options from which to choose
id?: string;
title?: string;
isDisabled?: boolean;
isClearable?: boolean;
hasError?: boolean;
isFullWidth?: boolean;
width?: number;
options?: readonly T[];
className?: string;
onChange?: (
event: SyntheticEvent<Element, Event>,
value: ComboBoxValue<T, boolean | undefined, boolean | undefined, boolean | undefined>,
reason?: ComboBoxChangeReason,
details?: ComboBoxChangeDetails<T> | undefined,
) => void;
onFocus?: FocusEventHandler<HTMLDivElement>;
onBlur?: FocusEventHandler<HTMLDivElement>;
See descriptions here
import { ComboBox } from 'papi-components';
<ComboBox title="Test Me" options={['option 1', 'option 2']} />
Grid component that accepts an array of columns as a prop.
commandHandler: CommandHandler;
className?: string;
columns: MenuColumnInfo[];
id?: string;
See descriptions here
import { GridMenu } from 'papi-components';
<GridMenu commandHandler={toolbarCommandHandler} columns={menu.columns} />
Each item displayed in a menu
hasAutoFocus?: boolean;
className?: string;
isDense?: boolean;
hasDisabledGutters?: boolean;
hasDivider?: boolean;
focusVisibleClassName?: string;
onClick?: MouseEventHandler<HTMLLIElement> | undefined;
See descriptions here
import { MenuItem } from 'papi-components';
<MenuItem
className="menu-item"
isDense
onClick={() => {
sendCommand('Download/Install Resources');
}}
>
Download/Install Resources...
</MenuItem>
Scripture reference selector
scrRef: ScriptureReference;
handleSubmit: (scrRef: ScriptureReference) => void;
id?: string;
See descriptions here
import { RefSelector } from 'papi-components';
<RefSelector
scrRef={{ bookNum: 1, chapterNum: 1, verseNum: 1 }}
handleSubmit={(): void => {}}
/>
Slider that allows selecting a value from a range
id?: string;
isDisabled?: boolean;
orientation?: 'horizontal' | 'vertical';
min?: number;
max?: number;
step?: number;
showMarks?: boolean;
defaultValue?: number;
valueLabelDisplay?: 'on' | 'auto' | 'off';
className?: string;
onChange?: (event: Event, value: number | number[], activeThumb: number) => void;
onChangeCommitted?: (
event: Event | SyntheticEvent<Element, Event>,
value: number | number[],
) => void;
value?: number | number[]
See descriptions here
import { Slider } from 'papi-components';
<Slider
className="papi-slider paratext"
max={100}
step={10}
value={sliderVal}
defaultValue={1}
valueLabelDisplay="auto"
onChange={handleSliderChange}
/>
Provides brief notifications, also known as a “toast”
id?: string;
isOpen?: boolean;
autoHideDuration?: number | null;
className?: string;
onClose?: (event: SyntheticEvent<any> | Event, reason: CloseReason) => void;
anchorOrigin?: AnchorOrigin;
children?: ReactElement<any, any>;
ContentProps?: SnackbarContentProps;
SnackbarContentProps
:
action?: ReactNode;
message?: ReactNode;
className?: string;
See descriptions here
import { Snackbar } from 'papi-components';
<Snackbar
isOpen={snackState}
ContentProps={{ message: "Hello", className: 'papi-snackbar alert' }}
onClose={() => setSnackState(false)}
autoHideDuration={6000}
/>
Switch to toggle on and off
id?: string;
isChecked?: boolean;
isDisabled?: boolean;
hasError?: boolean;
className?: string;
onChange?: (event: ChangeEvent<HTMLInputElement>) => void;
See descriptions here
import { Switch } from 'papi-components';
<Switch />
Tables display sets of data
columns: readonly TableColumn<R>[];
enableSelectColumn?: boolean;
selectColumnWidth?: number;
sortColumns?: readonly TableSortColumn[];
onSortColumnsChange?: (sortColumns: TableSortColumn[]) => void;
onColumnResize?: (idx: number, width: number) => void;
defaultColumnWidth?: number;
defaultColumnMinWidth?: number;
defaultColumnMaxWidth?: number;
defaultColumnSortable?: boolean;
defaultColumnResizable?: boolean;
rows: readonly R[];
rowKeyGetter?: (row: R) => Key;
rowHeight?: number;
headerRowHeight?: number;
selectedRows?: ReadonlySet<Key>;
onSelectedRowsChange?: (selectedRows: Set<Key>) => void;
onRowsChange?: (rows: R[], data: TableRowsChangeData<R>) => void;
onCellClick?: (args: TableCellClickArgs<R>, event: TableCellMouseEvent) => void;
onCellDoubleClick?: (args: TableCellClickArgs<R>, event: TableCellMouseEvent) => void;
onCellContextMenu?: (args: TableCellClickArgs<R>, event: TableCellMouseEvent) => void;
onCellKeyDown?: (args: TableCellKeyDownArgs<R>, event: TableCellKeyboardEvent) => void;
direction?: 'ltr' | 'rtl';
enableVirtualization?: boolean;
onScroll?: (event: UIEvent<HTMLDivElement>) => void;
onCopy?: (event: TableCopyEvent<R>) => void;
onPaste?: (event: TablePasteEvent<R>) => R;
className?: string;
id?: string;
See descriptions here
<Table<Row>
columns={[
{
key: 'id',
name: 'ID',
},
{
key: 'title',
name: 'Title',
editable: true,
},
{
key: 'subtitle',
name: 'Subtitle',
editable: true,
},
]}
rows={rows}
rowKeyGetter={(row: Row) => {
return row.id;
}}
selectedRows={selectedRows}
onSelectedRowsChange={(currentlySelectedRows: Set<Key>) =>
setSelectedRows(currentlySelectedRows)
}
onRowsChange={(changedRows: Row[]) => setRows(changedRows)}
enableSelectColumn
selectColumnWidth={60}
rowHeight={60}
headerRowHeight={50}
/>
Text input field
variant?: 'outlined' | 'filled';
id?: string;
isDisabled?: boolean;
hasError?: boolean;
isFullWidth?: boolean;
helperText?: string;
label?: string;
placeholder?: string;
isRequired?: boolean;
className?: string;
defaultValue?: unknown;
value?: unknown;
onChange?: ChangeEventHandler<HTMLInputElement>;
onFocus?: FocusEventHandler<HTMLInputElement>;
onBlur?: FocusEventHandler<HTMLInputElement>;
See descriptions here
import { TextField } from 'papi-components';
<TextField label="Test Me" />
Displays children with an inline display.
commandHandler: CommandHandler;
dataHandler?: DataHandler;
id?: string;
menu?: GridMenuInfo;
className?: string;
See descriptions here
import { Toolbar } from 'papi-components';
<Toolbar className="toolbar" dataHandler={handleMenuData} commandHandler={handleMenuCommand}>
<RefSelector handleSubmit={referenceChanged} scrRef={scrRef} />
</Toolbar>
Note that code style and other such documentation is stored in the Paranext wiki and covers all Paranext repositories.