pear-electron
Version:
Pear User-Interface Library for Electron
915 lines (518 loc) • 25.9 kB
Markdown
# pear-electron
> Pear User-Interface Library for Electron
**Status: WIP**
## Installation
```sh
npm install pear-electron
```
## Usage
Instantiate a `pear-electron` runtime instance from a Pear Application's entrypoint JavaScript file:
```js
import Runtime from 'pear-electron'
import Bridge from 'pear-bridge'
const runtime = new Runtime()
const bridge = new Bridge()
await bridge.ready()
const pipe = runtime.start({ bridge })
Pear.teardown(() => pipe.end())
```
Call `runtime.start` to open the UI.
## Initialization API
### `new Runtime() -> runtime`
Create the runtime instances with `new Runtime()`.
### `runtime.ready()`
Prepare the runtime, runtime binaries for the runtime version may be bootstrapped peer-to-peer at this point. This only runs once per version and any prior bootstraps can be reused for subsequent versions where state hasn't changed. In a production scenario any bootstrapping would be performed in advance by the application distributable.
### `runtime.start(opts)`
Opens the UI.
#### Options
* `bridge` - An instance of `pear-bridge`.
## User-Interface API
```js
const ui = require('pear-electron')
```
### `ui.app <Object>`
UI Application controls
### `const success = await app.focus()`
Resolves to: `<Boolean>`
Focus current view or window.
### `const success = await app.blur()`
Resolves to: `<Boolean>`
Blur current view or window.
### `const success = await app.show()`
Resolves to: `<Boolean>`
Show current view or window.
### `const success = await app.hide()`
Resolves to: `<Boolean>`
Hide current view or window.
### `const sourceId = await app.getMediaSourceId()`
Get the sourceId of the current window or view.
**References**
* [win.getMediaSourceId()](const-sourceId--await-wingetMediaSourceId)
### `const success = await app.minimize()`
Resolves to: `<Boolean>`
Minimize current window.
### `const success = await app.maximize()`
Resolves to: `<Boolean>`
Maximize current window.
### `const success = await app.restore()`
Resolves to: `<Boolean>`
Unmaximize/unminimize the current window if it is currently maximized/minimized.
### `const success = await app.close()`
Resolves to: `<Boolean>`
Closes the current view or window.
### `const isVisible = await app.isVisible()`
Resolves to: `<Boolean>`
Whether the current window or view is visible.
### `const isMaximized = await app.isMaximized()`
Resolves to: `<Boolean>`
### `const isMinimized = await app.isMinimized()`
Resolves to: `<Boolean>`
### `const found = await app.find(options <Object>)`
Resolves to: `<Found> extends <streamx.Readable>`
Find and select text, emit matches as data events.
**Options**
* text `<String>` - search term
* forward `<Boolean>` - search forward (`true`) or backward (`false`). Defaults `true`.
* matchCase `<Boolean>` - case-sensitivity. Default `false`.
#### `await found.proceed()`
Find & select next match, emit result as stream data.
#### `await found.clear()`
Stop search and clear matching text selection. Implies destroy.
#### `await found.keep()`
Stop search and convert matching text selection to text highlight. Implies destroy.
#### `await found.activate()`
Stop search and simulate a click event on the selected match. Implies destroy.
### `ui.media <Object>`
Media interface
#### `const status = await ui.media.status.microphone()`
Resolves to: `<String>`
If access to the microphone is available, resolved value will be `'granted'`.
Any other string indicates lack of permission. Possible values are `'granted'`, `'not-determined'`, `'denied'`, `'restricted'`, `'unknown'`.
#### `const status = await ui.media.status.camera()`
Resolves to: `<String>`
If access to the camera is available, resolved value will be `'granted'`.
Any other string indicates lack of permission. Possible values are `'granted'`, `'not-determined'`, `'denied'`, `'restricted'`, `'unknown'`.
#### `const status = await ui.media.status.screen()`
Resolves to: `<String>`
If access to the screen is available, resolved value will be `'granted'`.
Any other string indicates lack of permission. Possible values are `'granted'`, `'not-determined'`, `'denied'`, `'restricted'`, `'unknown'`.
#### `const success = await ui.media.access.microphone()`
Resolves to: `<Boolean>`
Request access to the microphone. Resolves to `true` if permission is granted.
#### `const success = await ui.media.access.camera()`
Resolves to: `<Boolean>`
Request access to the camera. Resolves to `true` if permission is granted.
#### `const success = await ui.media.access.screen()`
Resolves to: `<Boolean>`
Request access to screen sharing. Resolves to `true` if permission is granted.
#### `const sources = await ui.media.desktopSources(options <Object>)`
Captures available desktop sources. Resolves to an array of objects with shape `{ id <String>, name <String>, thumbnail <NativeImage>, display_id <String>, appIcon <NativeImage> }`. The `id` is the window or screen identifier. The `name` is the window title or `'Screen <index>'` in multiscreen scenarios or else `Entire Screen`. The `display_id` identifies the screen. The thumbnail is a scaled down screen capture of the window/screen.
**Options**
* `types <Array<String>>` - Default: `['screen', 'window']`. Filter by types. Types are `'screen'` and `'window'`.
* `thumbnailSize <Object>` - Default: `{width: 150, height: 150}`. Set thumbnail scaling (pixels)
* `fetchWindowIcons <Boolean>` - Default: `false`. Populate `appIcon` with Window icons, or else `null`.
**References**
* [win.getMediaSourceId()](#const-sourceid--await-wingetmediasourceid)
* [view.getMediaSourceId()](#const-sourceid--await-viewgetmediasourceid)
* [self.getMediaSourceId()](#const-sourceid--await-selfgetmediasourceid)
* [parent.getMediaSourceId()](#const-sourceid--await-parentgetmediasourceid)
* https://www.electronjs.org/docs/latest/api/desktop-capturer#desktopcapturergetsourcesoptions
* https://www.electronjs.org/docs/latest/api/structures/desktop-capturer-source
* [`<NativeImage>`](https://www.electronjs.org/docs/latest/api/native-image)
Exits the process with the provided exit code.
### `const win = new ui.Window(entry <String>, options <Object>)`
Desktop Applications only.
Create a new `Window` instance.
**Options**
* `show <Boolean>` Default: `true` - show the window as soon as it has been opened
* `x <Integer>` - the horizontal position of left side of the window (pixels)
* `y <Integer>` - vertical window position (pixels)
* `width <Integer>` - the width of the window (pixels)
* `height <Integer>` - the height of the window (pixels)
* `animate <Boolean>` Default: `false` - animate the dimensional change. MacOS only, ignored on other OS's.
* `center <Boolean` - center the window upon opening
* `minWidth <Integer>` - window minimum width (pixels)
* `minHeight <Integer>` - window minimum height (pixels)
* `maxWidth <Integer>` - window maximum width (pixels)
* `maxHeight <Integer>` - window maximum height (pixels)
* `resizable <Boolean>` - window resizability
* `movable <Boolean>` - window movability
* `minimizable <Boolean>` - window minimizability
* `maximizable <Boolean>` - window maximizability
* `closable <Boolean>` - window closability
* `focusable <Boolean>` - window focusability
* `alwaysOnTop <Boolean>` - Set window to always be on top
* `fullscreen <Boolean>` - Set window to fullscreen upon open
* `kiosk <Boolean>` - Set window to enter kiosk mode upon open
* `autoHideMenuBar <Boolean>` - Hide menu bar unless Alt key is pressed (Linux, Windows)
* `hasShadow <Boolean>` - Set window shadow
* `opacity <Number>` - Set window opacity (0.0 - 1.0) (Windows, macOS)
* `transparent <Boolean>` - Set window transparency
* `backgroundColor <String>` Default: `'#FFF'` - window default background color. Hex, RGB, RGBA, HSL HSLA, CSS color
### `win.on[ce]('message', (...args) => { })`
### `for await (const [ ...args ] of win)`
Receive a message from the window. The received `args` array is deserialized via `JSON.parse`.
**References**
* [`win.send()`](#await-winsendargs)
### `const success = await win.open(options <Object>)`
Resolves to: `<Boolean>`
Open the window.
**Options**
* `show` Default: `true` - show the window as soon as it has been opened
* `x <Integer>` - the horizontal position of left side of the window (pixels)
* `y <Integer>` - vertical window position (pixels)
* `width <Integer>` - the width of the window (pixels)
* `height <Integer>` - the height of the window (pixels)
* `animate <Boolean>` Default: `false` - animate the dimensional change. MacOS only, ignored on other OS's.
* `center <Boolean` - center the window upon opening
* `minWidth <Integer>` - window minimum width (pixels)
* `minHeight <Integer>` - window minimum height (pixels)
* `maxWidth <Integer>` - window maximum width (pixels)
* `maxHeight <Integer>` - window maximum height (pixels)
* `resizable <Boolean>` - window resizability
* `movable <Boolean>` - window movability
* `minimizable <Boolean>` - window minimizability
* `maximizable <Boolean>` - window maximizability
* `closable <Boolean>` - window closability
* `focusable <Boolean>` - window focusability
* `alwaysOnTop <Boolean>` - Set window to always be on top
* `fullscreen <Boolean>` - Set window to fullscreen upon open
* `kiosk <Boolean>` - Set window to enter kiosk mode upon open
* `autoHideMenuBar <Boolean>` - Hide menu bar unless Alt key is pressed (Linux, Windows)
* `hasShadow <Boolean>` - Set window shadow
* `opacity <Number>` - Set window opacity (0.0 - 1.0) (Windows, macOS)
* `transparent <Boolean>` - Set window transparency
* `backgroundColor <String>` Default: `'#FFF'` - window default background color. Hex, RGB, RGBA, HSL HSLA, CSS color
### `const success = await win.close()`
Resolves to: `<Boolean>`
Close the window.
### `const success = await win.show()`
Resolves to: `<Boolean>`
Show the window.
### `const success = await win.hide()`
Resolves to: `<Boolean>`
Hide the window.
### `const success = await win.focus(options <Object>)`
Resolves to: `<Boolean>`
Focus the window.
**Options**
* `steal` Default: `true` - brings the window to the foreground and attempts to take focus, even if another application is currently active, or the window is hidden or minimized.
### `const success = await win.blur()`
Resolves to: `<Boolean>`
Blur the window.
### `const success = await win.minimize()`
Resolves to: `<Boolean>`
Minimize the window.
### `const success = await win.maximize()`
Resolves to: `<Boolean>`
Maximize the window.
### `const success = await win.restore()`
Resolves to: `<Boolean>`
Unmaximize/unminimize the window if it is currently maximized/minimized.
### `const sourceId = await win.getMediaSourceId()`
Resolves to: `<String>`
Correlates to the `id` property of objects in the array returned from [ui.media.desktopSources](#const-sources---await-appmediadesktopsources-options).
**References**
* [ui.media.desktopSources](#const-sources--await-appmediadesktopsourcesoptions-object)
* https://www.electronjs.org/docs/latest/api/browser-window#wingetmediasourceid
### `await win.send(...args)`
Send arguments to the window. They will be serialized with `JSON.stringify`.
### `const found = await win.find(options <Object>)`
Resolves to: `<Found> extends <streamx.Readable>`
Find and select text, emit matches as data events.
**Options**
* text `<String>` - search term
* forward `<Boolean>` - search forward (`true`) or backward (`false`). Defaults `true`.
* matchCase `<Boolean>` - case-sensitivity. Default `false`.
#### `await found.proceed()`
Find & select next match, emit result as stream data.
#### `await found.clear()`
Stop search and clear matching text selection. Implies destroy.
#### `await found.keep()`
Stop search and convert matching text selection to text highlight. Implies destroy.
#### `await found.activate()`
Stop search and simulate a click event on the selected match. Implies destroy.
### `const dimensions = await win.dimensions()`
Resolves to: `{x <Integer>, y <Integer>, width <Integer>, height <Integer>} | null`.
The height, width, horizontal (`x`), vertical (`y`) position of the window relative to the screen.
All units are (pixels)
If the window is closed this will resolve to `null`.
**References**
* [await win.dimensions(options)](#await-windimensionsoptions-object)
### `await win.dimensions(options <Object>)`
```js
const win = new ui.Window('./some.html', {
x: 10,
y: 450,
width: 300,
height: 350
})
await win.open()
await new Promise((resolve) => setTimeout(resolve, 1000))
await win.dimensions({
x: 20,
y: 50,
width: 550,
height: 300,
animate: true // only has an effect on macOS
})
```
Sets the dimensions of the window.
**Options**
* `x <Integer>` - the horizontal position of left side of the window (pixels)
* `y <Integer>` - the vertical position of the top of the window (pixels)
* `width <Integer>` - the width of the window (pixels)
* `height <Integer>` - the height of the window (pixels)
* `animate <Boolean>` Default: `false` - animate the dimensional change. MacOS only, ignored on other OS's.
* `position <String>` - may be `'center'` to set the window in the center of the screen or else `undefined`.
**References**
* [const dimensions = await win.dimensions()](#const-dimensions-await-windimensions)
### `const visible = await win.isVisible()`
Resolves to: `<Boolean>`
Whether the window is visible.
### `const minimized = await win.isMinimized()`
Resolves to: `<Boolean>`
Whether the window is minimized.
### `const maximized = await win.isMaximized()`
Resolves to: `<Boolean>`
Whether the window is maximized.
### `const closed = await win.isClosed()`
Resolves to: `<Boolean>`
Whether the window is closed.
### `const view = new ui.View(options <Object>)`
Desktop Applications only.
Create a new `View` instance. Views provide isolated content views. Frameless, chromeless windows that can be embedded inside other windows and views.
**Options**
* `x <Integer>` - the horizontal position of left side of the view (pixels)
* `y <Integer>` - vertical view position (pixels)
* `width <Integer>` - the width of the view (pixels)
* `height <Integer>` - the height of the view (pixels)
* `backgroundColor <String>` Default: `'#FFF'` - view default background color. Hex, RGB, RGBA, HSL HSLA, CSS color
* `autoresize <Object>` Default `{ width=true, height=true, vertical=false, horizontal=false }` - dimensions for the view to autoresize alongside. For example, if `width` is `true` and the view container increases/decreases in width, the view will increase/decrease in width at the same rate.
**References**
* https://www.electronjs.org/docs/latest/api/browser-view#viewsetautoresizeoptions-experimental
* https://www.electronjs.org/docs/latest/api/browser-view#viewsetbackgroundcolorcolor-experimental
### `view.on[ce]('message', (...args) => { })`
### `for await (const [ ...args ] of view)`
Receive a message from the view. The received `args` array is deserialized via `JSON.parse`.
**References**
* [`view.send()`](#await-viewsendargs)
### `const success = await view.open(options <Object>)`
Resolves to: `<Boolean>`
Open the view.
**Options**
* `x <Integer>` - the horizontal position of left side of the view (pixels)
* `y <Integer>` - vertical view position (pixels)
* `width <Integer>` - the width of the view (pixels)
* `height <Integer>` - the height of the view (pixels)
* `backgroundColor <String>` Default: `'#FFF'` - view default background color. Hex, RGB, RGBA, HSL HSLA, CSS color
* `autoresize <Object>` Default `{ width=true, height=true, vertical=false, horizontal=false }` - dimensions for the view to autoresize alongside. For example, if `width` is `true` and the view container increases/decreases in width, the view will increase/decrease in width at the same rate.
### `const success = await view.close()`
Resolves to: `<Boolean>`
Close the view.
### `const success = await view.show()`
Resolves to: `<Boolean>`
Show the view.
### `const success = await view.hide()`
Resolves to: `<Boolean>`
Hide the view.
### `const success = await view.focus()`
Resolves to: `<Boolean>`
Focus the view.
### `const success = await view.blur()`
Resolves to: `<Boolean>`
Blur the view.
### `const sourceId = await view.getMediaSourceId()`
Resolves to: `<String>`
Supplies the `id` property of objects in the array returned from [ui.media.desktopSources](#const-sources---await-appmediadesktopsources-options).
**References**
* [ui.media.desktopSources](#const-sources---await-appmediadesktopsources-options)
* https://www.electronjs.org/docs/latest/api/browser-window#wingetmediasourceid
### `await view.send(...args)`
Send arguments to the view. They will be serialized with `JSON.stringify`.
### `const found = await win.find(options <Object>)`
Resolves to: `<Found> extends <streamx.Readable>`
Find and select text, emit matches as data events.
**Options**
* text `<String>` - search term
* forward `<Boolean>` - search forward (`true`) or backward (`false`). Defaults `true`.
* matchCase `<Boolean>` - case-sensitivity. Default `false`.
#### `await found.proceed()`
Find & select next match, emit result as stream data.
#### `await found.clear()`
Stop search and clear matching text selection. Implies destroy.
#### `await found.keep()`
Stop search and convert matching text selection to text highlight. Implies destroy.
#### `await found.activate()`
Stop search and simulate a click event on the selected match. Implies destroy.
### `const dimensions = await view.dimensions()`
Resolves to: `{x <Integer>, y <Integer>, width <Integer>, height <Integer>} | null`.
The height, width, horizontal (`x`), vertical (`y`) position of the window relative to the screen.
All units are (pixels)
If the Window is closed this will resolve to `null`.
**References**
* [await view.dimensions(options)](#await-viewdimensionsoptions-object)
### `await view.dimensions(options <Object>)`
```js
const view = new ui.View('./some.html', {
x: 10,
y: 450,
width: 300,
height: 350
})
await view.open()
await new Promise((resolve) => setTimeout(resolve, 1000))
await view.dimensions({
x: 20,
y: 50,
width: 550,
height: 300
})
```
Sets the dimensions of the view.
**Options**
* `x <Integer>` - the horizontal position of left side of the window (pixels)
* `y <Integer>` - the vertical position of the top of the window (pixels)
* `width <Integer>` - the width of the window (pixels)
* `height <Integer>` - the height of the window (pixels)
**References**
* [const dimensions = await view.dimensions()](#const-dimensions--await-viewdimensions)
### `const visible = await view.isVisible()`
Resolves to: `<Boolean>`
Whether the view is visible.
### `const closed = await view.isClosed()`
Resolves to: `<Boolean>`
Whether the view is closed.
### `const { self } = ui.Window` `const { self } = ui.View`
> DEPRECATED use `ui.app`.
### `const { parent } = ui.Window` `const { parent } = ui.View`
### `parent.on[ce]('message', (...args) => { })`
### `for await (const [ ...args ] of parent)`
Receive a message from the parent window or view. The received `args` array is deserialized via `JSON.parse`.
### `await parent.send(...args)`
Send arguments to the parent view or window. They will be serialized with `JSON.stringify`.
### `const success = await parent.focus()`
Resolves to: `<Boolean>`
Focus parent view or window.
### `const success = await parent.blur()`
Resolves to: `<Boolean>`
Blur parent view or window.
### `const success = await parent.show()`
Resolves to: `<Boolean>`
Show parent view or window.
### `const success = await parent.hide()`
Resolves to: `<Boolean>`
Hide parent view or window.
### `const sourceId = await parent.getMediaSourceId()`
Get the sourceId of the parent window or view.
**References**
* [win.getMediaSourceId()](#const-sourceId--await-wingetMediaSourceId)
### `const success = await parent.minimize()`
Resolves to: `<Boolean>`
Minimize parent window.
Throws a `TypeError` if `parent` is a view.
### `const success = await parent.maximize()`
Resolves to: `<Boolean>`
Maximize parent window.
Throws a `TypeError` if `parent` is a view.
### `const success = await parent.restore()`
Resolves to: `<Boolean>`
Unmaximize/unminimize the parent window if it is currently maximized/minimized.
Throws a `TypeError` if `parent` is a view.
### `const success = await parent.close()`
Resolves to: `<Boolean>`
Closes the parent view or window.
### `const isVisible = await parent.isVisible()`
Resolves to: `<Boolean>`
Whether the parent window or view is visible.
### `const isMaximized = await parent.isMaximized()`
Resolves to: `<Boolean>`
Whether the parent window is maximized. Throws a `TypeError` if `parent` is a view.
### `const isMinimized = await parent.isMinimized()`
Resolves to: `<Boolean>`
Whether the parent window is minimized. Throws a `TypeError` if `parent` is a view.
### `const found = await parent.find(options <Object>)`
Resolves to: `<Found> extends <streamx.Readable>`
Find and select text, emit matches as data events.
**Options**
* text `<String>` - search term
* forward `<Boolean>` - search forward (`true`) or backward (`false`). Defaults `true`.
* matchCase `<Boolean>` - case-sensitivity. Default `false`.
#### `await found.proceed()`
Find & select next match, emit result as stream data.
#### `await found.clear()`
Stop search and clear matching text selection. Implies destroy.
#### `await found.keep()`
Stop search and convert matching text selection to text highlight. Implies destroy.
#### `await found.activate()`
Stop search and simulate a click event on the selected match. Implies destroy.
## Web APIs
Most [Web APIs](https://developer.mozilla.org/en-US/docs/Web/API) will work as-is.
This section details deviations in behavior from and notable aspects of Web APIs as they relate to `pear-electron`.
### `window.open`
The [`window.open`](https://developer.mozilla.org/en-US/docs/Web/API/Window/open) Web API function will ignore all arguments except for the URL parameter.
In browsers, `window.open` opens a new browser window. The opened window belongs to the same browser from which `window.open` is called.
With `pear-electron` UI Library, `window.open` loads the URL in the **default system browser**. It does *not* create a new application window (use `Pear.Window` to create application windows).
Therefore Pear's `window.open` only supports a single URL argument. The `target` and `windowFeatures` parameters that browsers support are discarded.
### Scripts and Modules
Like browsers, there is no support for CommonJS (e.g. the `require` function as used by Node.js is not supported in Pear Applications).
Like browsers, there is support for native EcmaScript Modules (ESM). A JavaScript Script has no module capabilities. A JavaScript Module has ESM capabilities.
Use `<script type="module" src="path/to/my-file.js">` to load a JavaScript Module.
Use `<script src="path/to/my-file.js">` to load a JavaScript Script.
## Graphical User Interface Options
GUI options for an application are set in the application `package.json` `pear.gui` field.
### `width <Number>`
Window width (pixels).
### `height <Number>`
Window height (pixels).
### `x <Number>`
Horizontal window position (pixels).
### `y <Number>`
Vertical window position (pixels).
### `minWidth <Number>`
Window minimum width (pixels).
### `minHeight <Number>`
Window minimum height (pixels).
### `maxWidth <Number>`
Window maximum width (pixels).
### `maxHeight <Number>`
Window maximum height (pixels).
### `center <Boolean>` (default: `false`)
Center window.
### `resizable <Boolean>` (default: `true`)
Window resizability.
### `movable <Boolean>` (default: `true`)
Window movability.
### `minimizable <Boolean>` (default: `true`)
Window minimizability.
### `maximizable <Boolean>` (default: `true`)
Window maximizability.
### `closable <Boolean>` (default: `true`)
Window closability.
### `focusable <Boolean>` (default: `true`)
Window focusability.
### `alwaysOnTop <Boolean>` (default: `false`)
Set window to always be on top.
### `fullscreen <Boolean>` (default: `false`)
Set window to fullscreen on start.
### `kiosk <Boolean>` (default: `false`)
Set window to enter kiosk mode on start.
### `autoHideMenuBar <Boolean>` (default: `false`)
Hide menu bar unless Alt key is pressed (Linux, Windows).
### `hasShadow <Boolean>` (default: `true`)
Window shadow.
### `opacity <Number>` (default: `1`)
Set window opacity (0.0 - 1.0) (Windows, macOS).
### `transparent <Boolean>` (default: `false`)
Enable transparency. Must be set for opacity to work.
### `backgroundColor <String>` (default: "#000" non-transparent, "#00000000" transparent)
Background color (Hex, RGB, RGBA, HSL, HSLA, CSS color).
## Development
The `pear-electron` library is a Pear User Interface Runtime Library, as such `pear-electron` (and any Pear UI Lib.) is multifaceted and behaves differently depending on context.
* When loaded into a UI, `pear-electron` is the UI API
* When loaded into non-UI (i.e app entrypoint js file), `pear-electron` is the runtime initializor
* When there is no runtime binary on the system, `pear-electron` performs bootstrapping of the UI runtime executable, into `<pear-dir>/interfaces/pear-electron/<semver>`
* The `pear-electron` repo is also self-bootstrapping and generates the runtime drive (with `by-arch`, `prebuilds` and `boot.bundle`), which can then be staged with Pear. The pear link for the staged `pear-electron` contents in `pear-electron` `package.json` `pear.gui.runtime` field is then set, with fork and length included. This locks runtime builds for a given semver to a specific runtime drive checkout.
* This is what `pear-electron` bootstraps from during `runtime.ready()`.
# LICENSE
Apache 2.0