Skip to content

Commit

Permalink
Add a client api for main processes and allow for the current selecti…
Browse files Browse the repository at this point in the history
…on to be triggered by OSC

Signed-off-by: Axel Boberg <[email protected]>
  • Loading branch information
axelboberg committed Mar 29, 2024
1 parent 990eb0e commit e5637c0
Show file tree
Hide file tree
Showing 5 changed files with 186 additions and 6 deletions.
2 changes: 1 addition & 1 deletion api/client.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

const client = (function () {
if (module.parent) {
return undefined
return require('./node/client')
}
if (typeof window !== 'undefined') {
return require('./browser/client')
Expand Down
116 changes: 116 additions & 0 deletions api/node/client.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
// SPDX-FileCopyrightText: 2024 Sveriges Television AB
//
// SPDX-License-Identifier: MIT

const MissingArgumentError = require('../error/MissingArgumentError')
const InvalidArgumentError = require('../error/InvalidArgumentError')
const state = require('../state')

/**
* @typedef {{
* id: String,
* role: Number,
* heartbeat: Number,
* isPersistent: Boolean,
* isEditingLayout: Boolean
* }} Connection
*/

/**
* Roles that a
* client can assume
*/
const ROLES = {
satellite: 0,
main: 1
}

/**
* Get all clients
* from the state
* @returns { Promise.<Connection[]> }
*/
async function getAllConnections () {
return Object.entries((await state.get('_connections')) || {})
.map(([id, connection]) => ({
id,
...connection,
role: (connection.role == null ? ROLES.satellite : connection.role)
}))
}

/**
* Set the role of a
* client by its id
* @param { String } id
* @param { Number } role
*/
async function setRole (id, role) {
if (!id || typeof id !== 'string') {
throw new InvalidArgumentError('Invalid argument \'id\', must be a string')
}

if (!Object.values(ROLES).includes(role)) {
throw new InvalidArgumentError('Invalid argument \'role\', must be a valid role')
}

const set = {
_connections: {
[id]: {
role
}
}
}

/*
There can only be one client with the main role,
if set, demote all other mains to satellite
*/
if (role === ROLES.main) {
(await getConnectionsByRole(ROLES.main))
/*
Don't reset the role of the
connection we're currently setting
*/
.filter(connection => connection.id !== id)
.forEach(connection => { set._connections[connection.id] = { role: ROLES.satellite } })
}

state.apply(set)
}

/**
* Get an array of all clients that
* have assumed a certain role
* @param { Number } role A valid role
* @returns { Promise.<Connection[]> }
*/
async function getConnectionsByRole (role) {
if (!Object.values(ROLES).includes(role)) {
throw new InvalidArgumentError('Invalid argument \'role\', must be a valid role')
}

return (await getAllConnections())
.filter(connection => connection.role === role)
}

/**
* Get the current selection
* of a connection by its id
* @param { String } connectionId
* @returns { Promise.<String[]> }
*/
async function getSelection (connectionId) {
if (!connectionId) {
throw new MissingArgumentError('Missing required argument \'connectionId\'')
}
return (await state.get(`_connections.${connectionId}.selection`)) || []
}

module.exports = {
roles: ROLES,
setRole,
getSelection,
getAllConnections,
getConnectionsByRole
}
43 changes: 38 additions & 5 deletions docs/api/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -332,20 +332,53 @@ Play an item and set its state to 'playing'.
Stop an item and set its state to 'stopped'.

## Client
**The client api is only available within renderer processes**
Control aspects of the current client
**The client api between the renerer and main processes**
Control aspects of clients

### `bridge.client.awaitIdentity(): Promise<String>`
**Only available within the render process**
Await the current identity to be set, will return as soon as the identity is set or immediately if it's already set

### `bridge.client.getIdentity(): String?`
**Only available within the render process**
Get the client's identity as set by the host app. This may be undefined if it has not yet been set. It's useful for manually getting client parameters if optimizing queries to the state.

### `bridge.client.setSelection(items)`
Select one or multiple items, will clear the current selection
### `bridge.client.setSelection(itemIds)`
**Only available within the render process**
Select one or multiple items, will clear the current selection.

### `bridge.client.addSelection(itemId|itemIds)`
**Only available within the render process**
Add one or more items to the selecton by their ids.

### `bridge.client.subtractSelection(itemId|itemIds)`
**Only available within the render process**
Subtract one or more items to the selecton by their ids.

### `bridge.client.isSelected(itemId): Boolean`
**Only available within the render process**
Check whether or not an item is selected by the current client.

### `bridge.client.clearSelection()`
**Only available within the render process**
Clear the current selection

### `bridge.client.getSelection(): Promise<String[]>`
Get the current selection
**Only available within the render process**
Get the current selection

### `bridge.client.getSelection(connectionId): Promise<String[]>`
**Only available within main processes**
Get the current selection of a connection by its id

### `bridge.client.setRole(id, role)`
Set the role of a specified connection, if assigning the main role to a connection, any other main connection will be denoted to a satellite

### `bridge.client.getAllConnections(): Promise<Connection[]>`
Get an array of all current connections

### `bridge.client.getAllConnectionsByRole(role): Promise<Connection[]>`
Get an array of all current connections with a specific role

## Keyboard shortcuts
Keyboard shortcuts SHOULD be registered with the API to give the user an index of which commands are available.
Expand Down
6 changes: 6 additions & 0 deletions plugins/osc/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,9 @@ Stop an item
| Index | Type | Description |
| --- | --- | --- |
| 0 | String | The id of the item to stop |

### `/api/client/selection/play`
Play the main client's current selection

### `/api/client/selection/stop`
Stop the main client's current selection
25 changes: 25 additions & 0 deletions plugins/osc/lib/handlers.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,25 @@

const bridge = require('bridge')

async function getMainSelection () {
const connections = await bridge.client.getConnectionsByRole(bridge.client.roles.main)
return connections[0]?.selection || []
}

async function playSelection () {
const selection = await getMainSelection()
for (const id of selection) {
bridge.items.playItem(id)
}
}

async function stopSelection () {
const selection = await getMainSelection()
for (const id of selection) {
bridge.items.stopItem(id)
}
}

/*
Define any available osc paths
*/
Expand All @@ -15,6 +34,12 @@ module.exports = {
'/items': {
'/playItem': message => bridge.items.playItem(message?.args?.[0].value),
'/stopItem': message => bridge.items.stopItem(message?.args?.[0].value)
},
'/client': {
'/selection': {
'/play': message => playSelection(),
'/stop': message => stopSelection()
}
}
}
}

0 comments on commit e5637c0

Please sign in to comment.