Skip to content
jolierabideau edited this page Aug 14, 2023 · 15 revisions

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>

Services

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.

Shared Service

src/shared/services/papi.service.ts

Link to File in GitHub

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
};

fetch

provides a way to get anything from the Internet

logger

provides a general logging system. The items that you log appear in the Platform.Bible console and go to file.

Location of log file

  • macOS: ~/Library/Logs/{app name}/main.log
  • Linux: ~/.config/{app name}/logs/main.log
  • Windows: %USERPROFILE%\AppData\Roaming\ {app name}\logs\main.log

internet

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.

Frontend

src/renderer/services/papi-frontend.service.ts

Link to File in GitHub

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";

Backend

/src/extension-host/services/papi-backend.service.ts

Link to File in GitHub

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";

Events

/src/shared/models/papi-event-emitter.model.ts

Link to File in GitHub

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

Link to File in GitHub

This file contains declarations for PapiEvent, PapiEventHandler, and PapiEventAsync.

/src/shared/models/papi-network-event-emitter.model.ts

Link to File in GitHub

This file contains the networked version of EventEmitter.

Commands

/src/shared/services/command.service.ts

Link to File in GitHub

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"
);

sendCommand

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.

registerCommand

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

Utils

/src/shared/utils/papi-util.ts

Link to File in GitHub

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.

Utility Functions

/src/shared/utils/util.ts

Link to File in GitHub

This file contains a mismatch of utility functions. Currently, the functions included are not expected to be of much use to extension developers.

Hooks

/src/renderer/hooks/papi-hooks/index.ts

Link to File in GitHub

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.

useDataProvider

Gets a data provider with specified provider name

useData

Subscribes to run a callback on a data provider’s data with specified selector on any data type that data provider serves

useEventAsync

Adds an event handler to an asynchronously subscribing/unsubscribing event so the event handler runs when the event is emitted

useEvent

Adds an event handler to an event so the event handler runs when the event is emitted

usePromise

Awaits a promise and returns a loading value while promise is unresolved

Data Provider

src/shared/services/data-provider.service.ts

Link to File in GitHub

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

Link to File in GitHub

Data Provider interface

src/shared/models/data-provider.interface.ts

Link to File in GitHub

Data Provider Engine model

/src/shared/models/data-provider-engine.model.ts

Link to File in GitHub

Network

shared/services/network.service.ts

Link to file in Github

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.

onDidClientConnect

Event that emits with clientId when a client connects

onDidClientDisconnect

Event that emits with clientId when a client disconnects

createNetworkEventEmitter

Creates an event emitter that works properly over the network. Other connections receive this event when it is emitted.

getNetworkEvent

Gets the network event with the specified type. Creates the emitter if it does not exist.

Web View

Service

shared/services/web-view.service.ts

Link to file in GitHub

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.

onDidAddWebView

Event that emits with webView info when a webView is added

getWebView

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.

initialize

This function sets up the WebViewService.

Provider Service

shared/services/web-view-provider.service.ts

Link to file in GitHub

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

Register a web view provider to serve webViews for a specified type of webViews

Components

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

Button a user can click to do something

Exposed Props

id?: string;
isDisabled?: boolean;
className?: string;
onClick?: MouseEventHandler<HTMLButtonElement>;
onContextMenu?: MouseEventHandler<HTMLButtonElement>;

See descriptions here

How to use

import { Button } from 'papi-components';

<Button onClick={() => { 
   throw new Error(`${NAME} test exception!`)
}}
>
   Throw test exception
</Button>

Checkbox

Allow users to select one or more items from a set

Exposed Props

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

How to use

import { Checkbox } from 'papi-components';

<Checkbox labelText="Test Me"/>

Combobox

Dropdown selector displaying various options from which to choose

Exposed Props

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

How to use

import { ComboBox } from 'papi-components';

<ComboBox title="Test Me" options={['option 1', 'option 2']} />

Grid Menu

Grid component that accepts an array of columns as a prop.

Exposed Props

commandHandler: CommandHandler;
className?: string;
columns: MenuColumnInfo[];
id?: string;

See descriptions here

How to use

import { GridMenu } from 'papi-components';

<GridMenu commandHandler={toolbarCommandHandler} columns={menu.columns} />

MenuItem

Each item displayed in a menu

Exposed Props

hasAutoFocus?: boolean;
className?: string;
isDense?: boolean;
hasDisabledGutters?: boolean;
hasDivider?: boolean;
focusVisibleClassName?: string;
onClick?: MouseEventHandler<HTMLLIElement> | undefined;

See descriptions here

How to use

import { MenuItem } from 'papi-components';

<MenuItem
   className="menu-item"
   isDense
   onClick={() => {
      sendCommand('Download/Install Resources');
   }}
>
Download/Install Resources...
</MenuItem>

Reference Selector

Scripture reference selector

Exposed Props

scrRef: ScriptureReference;
handleSubmit: (scrRef: ScriptureReference) => void;
id?: string;

See descriptions here

How to use

import { RefSelector } from 'papi-components';

<RefSelector
   scrRef={{ bookNum: 1, chapterNum: 1, verseNum: 1 }}
   handleSubmit={(): void => {}}
/>

Slider

Slider that allows selecting a value from a range

Exposed Props

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

How to use

import { Slider } from 'papi-components';

<Slider
   className="papi-slider paratext"
   max={100}
   step={10}
   value={sliderVal}
   defaultValue={1}
   valueLabelDisplay="auto"
   onChange={handleSliderChange}
/>

Snackbar

Provides brief notifications, also known as a “toast”

Exposed Props

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

How to use

import { Snackbar } from 'papi-components';

<Snackbar
   isOpen={snackState}
   ContentProps={{ message: "Hello", className: 'papi-snackbar alert' }}
   onClose={() => setSnackState(false)}
   autoHideDuration={6000}
/>

Switch

Switch to toggle on and off

Exposed Props

id?: string;
isChecked?: boolean;
isDisabled?: boolean;
hasError?: boolean;
className?: string;
onChange?: (event: ChangeEvent<HTMLInputElement>) => void;

See descriptions here

How to use

import { Switch } from 'papi-components';

<Switch />

Table

Tables display sets of data

Exposed Props

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

How to use

<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}
/>

TextField

Text input field

Exposed Props

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

How to use

import { TextField } from 'papi-components';

<TextField label="Test Me" />

Toolbar

Displays children with an inline display.

Exposed Props

commandHandler: CommandHandler;
dataHandler?: DataHandler;
id?: string;
menu?: GridMenuInfo;
className?: string;

See descriptions here

How to use

import { Toolbar } from 'papi-components';

<Toolbar className="toolbar" dataHandler={handleMenuData} commandHandler={handleMenuCommand}>
      <RefSelector handleSubmit={referenceChanged} scrRef={scrRef} />
    </Toolbar>