Skip to content

Commit

Permalink
Move shortcuts to the bridge event api
Browse files Browse the repository at this point in the history
Signed-off-by: Axel Boberg <[email protected]>
  • Loading branch information
axelboberg committed Feb 17, 2024
1 parent 66bb341 commit 3e37d7f
Show file tree
Hide file tree
Showing 12 changed files with 217 additions and 53 deletions.
23 changes: 19 additions & 4 deletions api/browser/client.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,28 +5,42 @@
const MissingIdentityError = require('../error/MissingIdentityError')
const state = require('../state')

const LazyValue = require('../classes/LazyValue')

/**
* The client's
* current identity
* @type { String? }
* @type { LazyValue }
*/
let _identity
const _identity = new LazyValue()

/**
* @private
* Set the client's identity
* @param { String } identity
*/
function setIdentity (identity) {
_identity = identity
_identity.set(identity)
}

/**
* Get the current identity
* @returns { String? }
*/
function getIdentity () {
return _identity
return _identity.get()
}

/**
* Await the identity to be set,
* will return immediately if an
* identity is already set
* or otherwise return a
* Promise
* @returns { String | Promise.<String> }
*/
function awaitIdentity () {
return _identity.getLazy()
}

/**
Expand Down Expand Up @@ -186,6 +200,7 @@ async function getSelection () {
module.exports = {
setIdentity,
getIdentity,
awaitIdentity,
setSelection,
getSelection,
addSelection,
Expand Down
65 changes: 65 additions & 0 deletions api/classes/LazyValue.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
// SPDX-FileCopyrightText: 2024 Sveriges Television AB
//
// SPDX-License-Identifier: MIT

/**
* @class LazyValue
* @description A wrapper around any value that can be awaited
*/
class LazyValue {
#value

/**
* @typedef {{
* resolve: () => {}
* }} PromiseInit
*
* @type { PromiseInit[] }
*/
#promises = []

/**
* Set a new value,
* will resolve any waiting promises
* @param { any } value
*/
set (value) {
this.#value = value

/*
Resolve any waiting promises
*/
for (const promise of this.#promises) {
promise.resolve(value)
}
this.#promises = []
}

/**
* Get the current value
* @returns { any }
*/
get () {
return this.#value
}

/**
* Get the value lazily
* through a promise,
*
* the promise will be resolved
* as soon as a value is set
*
* @returns { Promise.<any> }
*/
getLazy () {
if (this.#value) {
return this.#value
}
return new Promise(resolve => {
this.#promises.push({ resolve })
})
}
}

module.exports = LazyValue
60 changes: 45 additions & 15 deletions api/events.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,35 @@ function appendToMapArray (map, key, item) {
map.set(key, [...items, item])
}

/**
* @private
*
* Call all local handlers for an event,
* this should be called whenever an event
* is emitted so that all listeners
* are executed
*
* @param { String } event The name of the event to emit
* @param { ...any } args Any data to pass along with the event
*/
async function callLocalHandlers (event, ...args) {
let _args = args

/*
Let any intercepts do their thing
before calling the event handlers
*/
const interceptFns = intercepts.get(event) || []
for (const { fn } of interceptFns) {
_args = await fn(..._args)
}

const handlers = localHandlers.get(event)
for (const { handler } of handlers) {
handler(..._args)
}
}

/**
* Emit an event
* @param { String } event The name of the event to emit
Expand All @@ -46,6 +75,21 @@ function emit (event, ...args) {
}
exports.emit = emit

/**
* Emit an event but only call local handlers
*
* This will NOT trigger any listeners outside
* this client. If that is what you want,
* use the standard 'emit' function instead.
*
* @param { String } event The name of the event to emit
* @param { ...any } args Any data to pass along with the event
*/
function emitLocally (event, ...args) {
callLocalHandlers(event, ...args)
}
exports.emitLocally = emitLocally

/**
* Register a function that intercepts a certain event
* before calling any handlers with its result
Expand Down Expand Up @@ -110,21 +154,7 @@ async function on (event, handler, opts) {
all local handlers of the event
*/
commands.registerCommand(command, async (...args) => {
let _args = args

/*
Let any intercepts do their thing
before calling the event handlers
*/
const interceptFns = intercepts.get(event) || []
for (const { fn } of interceptFns) {
_args = await fn(..._args)
}

const handlers = localHandlers.get(event)
for (const { handler } of handlers) {
handler(..._args)
}
callLocalHandlers(event, ...args)
}, false)

const handlerId = await commands.executeCommand('events.triggerCommand', event, command)
Expand Down
30 changes: 23 additions & 7 deletions app/utils/shortcuts.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,19 @@ function normalize (key) {
return key
}

async function dispatchShortcutEvent (target, id) {
/* const event = new window.CustomEvent('shortcut', {
detail: {
id: id
},
bubbles: true
})
target.dispatchEvent(event) */

const bridge = await api.load()
bridge.events.emitLocally('shortcut', id)
}

/**
* Register a key down event,
* will try to find a shortcut
Expand Down Expand Up @@ -87,13 +100,7 @@ export async function registerKeyDown (e) {
}

for (const shortcut of matchedShortcuts) {
const event = new window.CustomEvent('shortcut', {
detail: {
id: shortcut.id
},
bubbles: true
})
e.target.dispatchEvent(event)
dispatchShortcutEvent(e.target, shortcut.id)
}

/*
Expand All @@ -113,3 +120,12 @@ export async function registerKeyDown (e) {
export function registerKeyUp (e) {
keys.clear()
}

;(async function () {
const bridge = await api.load()
const identity = await bridge.client.awaitIdentity()

bridge.events.on(`local.${identity}.shortcut`, shortcut => {
dispatchShortcutEvent(document.activeElement, shortcut)
})
})()
5 changes: 2 additions & 3 deletions docs/api/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -357,9 +357,8 @@ import bridge from 'bridge'

const el = document.createElement('div')

el.addEventListener('shortcut', e => {
console.log('Shortcut was triggered with id:', e.detail.id)

bridge.events.on('shortcut', id => {
console.log('Shortcut was triggered with id:', id)
// React to action
})
```
Expand Down
24 changes: 24 additions & 0 deletions lib/Workspace.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,15 @@ const template = require('./template/template.json')
const SOCKET_KEEPALIVE_TIMEOUT_MS = 20000
const SOCKET_CLEANUP_INTERVAL_MS = 1000

/**
* @class Workspace
*
* @typedef {{
* heartbeat: Number,
* isPersistent: Boolean,
* isEditingLayout: Boolean
* }} Connection
*/
class Workspace extends EventEmitter {
/**
* The application's unique id
Expand Down Expand Up @@ -220,6 +229,21 @@ class Workspace extends EventEmitter {
})
}

/**
* List all connections currently
* connected to this workspace
* @returns { Connection[] }
*/
getConnections () {
return Object.entries(this.state.data?._connections || {})
.map(([id, connection]) => {
return {
...connection,
id
}
})
}

/**
* Clean up sockets that should
* be removed in order to prevent
Expand Down
24 changes: 20 additions & 4 deletions lib/electron.js
Original file line number Diff line number Diff line change
Expand Up @@ -73,9 +73,9 @@ const MENU_TEMPLATE = [
{ label: 'Undo', accelerator: 'CmdOrCtrl+Z', selector: 'undo:' },
{ label: 'Redo', accelerator: 'Shift+CmdOrCtrl+Z', selector: 'redo:' },
{ type: 'separator' },
{ label: 'Cut', accelerator: 'CmdOrCtrl+X', selector: 'cut:' },
{ label: 'Copy', accelerator: 'CmdOrCtrl+C', selector: 'copy:' },
{ label: 'Paste', accelerator: 'CmdOrCtrl+V', selector: 'paste:' },
{ label: 'Cut', accelerator: 'CmdOrCtrl+X', selector: 'cut:', click: () => performShortcut(BrowserWindow.getFocusedWindow(), 'cut') },
{ label: 'Copy', accelerator: 'CmdOrCtrl+C', selector: 'copy:', click: () => performShortcut(BrowserWindow.getFocusedWindow(), 'copy') },
{ label: 'Paste', accelerator: 'CmdOrCtrl+V', selector: 'paste:', click: () => performShortcut(BrowserWindow.getFocusedWindow(), 'paste') },
{ label: 'Select All', accelerator: 'CmdOrCtrl+A', selector: 'selectAll:' }
]
},
Expand Down Expand Up @@ -204,7 +204,8 @@ function initWindow (url) {
contextIsolation: true,
enableRemoteModule: false,
nodeIntegration: false,
webviewTag: false
webviewTag: false,
sandbox: true
}
}

Expand Down Expand Up @@ -280,6 +281,21 @@ async function openWithDialog () {
open(filePaths[0])
}

/**
* Perform a shortcut on a
* window's persistent clients
* (a.k.a. those running in Electron)
* @param { BrowserWindow } window
* @param { String } shortcut
*/
async function performShortcut (window, shortcut) {
const workspace = await electronWindowManager.getWorkspaceFromWindow(window)
const connections = workspace.getConnections()
const persistentConnection = connections.find(({ isPersistent }) => isPersistent)

workspace.api.events.emit(`local.${persistentConnection.id}.shortcut`, shortcut)
}

/**
* Open a project file in a window
* @param { String } filePath
Expand Down
8 changes: 4 additions & 4 deletions plugins/inspector/app/views/Inspector.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@ export function Inspector () {
within the inspector
*/
React.useEffect(() => {
async function onShortcut (e) {
async function onShortcut (shortcut) {
const selection = await bridge.client.getSelection()
switch (e.detail.id) {
switch (shortcut) {
case 'bridge.rundown.play':
selection.forEach(itemId => bridge.items.playItem(itemId))
break
Expand All @@ -25,9 +25,9 @@ export function Inspector () {
}
}

window.addEventListener('shortcut', onShortcut)
bridge.events.on('shortcut', onShortcut)
return () => {
window.removeEventListener('shortcut', onShortcut)
bridge.events.off('shortcut', onShortcut)
}
}, [store?.selection])

Expand Down
8 changes: 4 additions & 4 deletions plugins/rundown/app/components/RundownGroupItem/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,8 @@ export function RundownGroupItem ({ index, item }) {
}

React.useEffect(() => {
function onShortcut (e) {
switch (e.detail.id) {
function onShortcut (shortcut) {
switch (shortcut) {
case 'bridge.rundown.collapse':
setCollapsed(true)
break
Expand All @@ -52,9 +52,9 @@ export function RundownGroupItem ({ index, item }) {
}
}

window.addEventListener('shortcut', onShortcut)
bridge.events.on('shortcut', onShortcut)
return () => {
window.removeEventListener('shortcut', onShortcut)
bridge.events.off('shortcut', onShortcut)
}
}, [elRef, item])

Expand Down
Loading

0 comments on commit 3e37d7f

Please sign in to comment.