{
- modifiableObjects.forEach((o) => {
- ipcServer
- .updateTimelineObj({
- rundownId: rundownId,
- groupId: o.groupId,
- partId: o.partId,
- timelineObjId: o.timelineObjId,
- timelineObj: {
- obj: updateObj,
- },
- })
- .catch(handleError)
- })
- }}
- />
+ {(editCustomLabel || hasCustomLabel) && (
+
+ o.timelineObj.customLabel, undefined)}
+ onChange={(v) => {
+ onSave({ customLabel: (v ?? '').trim() })
+ }}
+ allowUndefined={true}
+ />
+
+ )}
+
+
()
diff --git a/apps/app/src/react/mobx/AppStore.ts b/apps/app/src/react/mobx/AppStore.ts
index 1f492138..312eb49b 100644
--- a/apps/app/src/react/mobx/AppStore.ts
+++ b/apps/app/src/react/mobx/AppStore.ts
@@ -9,10 +9,13 @@ import { setConstants } from '../constants'
import { BridgeId } from '@shared/api'
import { TSRDeviceId, protectString } from '@shared/models'
import { BridgePeripheralId } from '@shared/lib'
+import { SerializableLedgers, SpecialLedgers } from '../../models/project/Project'
export class AppStore {
bridgeStatuses = new Map()
peripherals = new Map()
+ undoLedgers: SerializableLedgers = {}
+ undoLedgerCurrentKey: string = SpecialLedgers.APPLICATION
serverAPI: ApiClient
logger: ClientSideLogger
@@ -30,6 +33,7 @@ export class AppStore {
this.updateBridgeStatus(bridgeId, status),
updatePeripheral: (peripheralId: BridgePeripheralId, peripheral: PeripheralStatus | null) =>
this.updatePeripheral(peripheralId, peripheral),
+ updateUndoLedgers: (data: SerializableLedgers) => this.updateUndoLedgers(data),
})
makeAutoObservable(this)
@@ -69,6 +73,10 @@ export class AppStore {
}
}
+ updateUndoLedgers(data: SerializableLedgers): void {
+ this.undoLedgers = data
+ }
+
private _updateAllDeviceStatuses() {
for (const bridgeStatus of this.bridgeStatuses.values()) {
for (const [deviceId, deviceStatus] of Object.entries(bridgeStatus.devices)) {
diff --git a/apps/app/src/react/mobx/GDDValidatorStoreStore.ts b/apps/app/src/react/mobx/GDDValidatorStoreStore.ts
index 2c95d336..cee7d1bd 100644
--- a/apps/app/src/react/mobx/GDDValidatorStoreStore.ts
+++ b/apps/app/src/react/mobx/GDDValidatorStoreStore.ts
@@ -1,7 +1,6 @@
import { makeAutoObservable, runInAction } from 'mobx'
import { SchemaValidator, setupSchemaValidator, ValidatorCache } from 'graphics-data-definition'
import { ApiClient } from '../api/ApiClient'
-// const { ipcRenderer } = window.require('electron')
export class GDDValidatorStore {
private isInitialized = false
diff --git a/apps/app/src/react/mobx/GroupPlayDataStore.ts b/apps/app/src/react/mobx/GroupPlayDataStore.ts
index 4399deab..c2c43449 100644
--- a/apps/app/src/react/mobx/GroupPlayDataStore.ts
+++ b/apps/app/src/react/mobx/GroupPlayDataStore.ts
@@ -5,7 +5,6 @@ import { RealtimeDataProvider } from '../api/RealtimeDataProvider'
import { Rundown } from '../../models/rundown/Rundown'
import { ApiClient } from '../api/ApiClient'
import { ClientSideLogger } from '../api/logger'
-// const { ipcRenderer } = window.require('electron')
export class GroupPlayDataStore {
groups: Map = new Map()
diff --git a/apps/app/src/react/mobx/GuiStore.ts b/apps/app/src/react/mobx/GuiStore.ts
index b9610caf..5aebab84 100644
--- a/apps/app/src/react/mobx/GuiStore.ts
+++ b/apps/app/src/react/mobx/GuiStore.ts
@@ -10,7 +10,6 @@ import {
} from '../../lib/GUI'
import { DefiningArea } from '../../lib/triggers/keyDisplay/keyDisplay'
import { ApiClient } from '../api/ApiClient'
-// const { ipcRenderer } = window.require('electron')
/**
* Store contains only information about user interface
@@ -91,6 +90,8 @@ export class GuiStore {
this._activeTabId = id
}
+ public isPeripheralPopoverOpen = false
+
/** A list of all selected items */
get selected(): Readonly {
return this._selected
diff --git a/apps/app/src/react/mobx/ResourcesAndMetadataStore.ts b/apps/app/src/react/mobx/ResourcesAndMetadataStore.ts
index 4102380f..e51ab8a2 100644
--- a/apps/app/src/react/mobx/ResourcesAndMetadataStore.ts
+++ b/apps/app/src/react/mobx/ResourcesAndMetadataStore.ts
@@ -1,6 +1,5 @@
import { makeAutoObservable } from 'mobx'
import { ApiClient } from '../api/ApiClient'
-// const { ipcRenderer } = window.require('electron')
import { RealtimeDataProvider } from '../api/RealtimeDataProvider'
import {
MetadataAny,
diff --git a/apps/app/src/react/styles/sidebar/sidebar.scss b/apps/app/src/react/styles/sidebar/sidebar.scss
index b79fafd4..e9f6da9d 100644
--- a/apps/app/src/react/styles/sidebar/sidebar.scss
+++ b/apps/app/src/react/styles/sidebar/sidebar.scss
@@ -23,6 +23,15 @@
display: flex;
justify-content: space-between;
padding: 0.6em;
+
+ a {
+ color: inherit;
+ text-decoration: none;
+ }
+ a:hover {
+ text-decoration: underline;
+ cursor: text;
+ }
}
}
> .sidebar__content {
diff --git a/shared/packages/tsr-bridge/src/TSR.ts b/shared/packages/tsr-bridge/src/TSR.ts
index b78d8de4..3a1746df 100644
--- a/shared/packages/tsr-bridge/src/TSR.ts
+++ b/shared/packages/tsr-bridge/src/TSR.ts
@@ -1,5 +1,12 @@
import _ from 'lodash'
-import { Conductor, ConductorOptions, DeviceOptionsAny, DeviceType, OSCDeviceType } from 'timeline-state-resolver'
+import {
+ AbortError,
+ Conductor,
+ ConductorOptions,
+ DeviceOptionsAny,
+ DeviceType,
+ OSCDeviceType,
+} from 'timeline-state-resolver'
import { MetadataAny, ResourceAny, TSRDeviceId, unprotectString } from '@shared/models'
import { BridgeAPI, LoggerLike } from '@shared/api'
import { CasparCGSideload } from './sideload/CasparCG'
@@ -18,7 +25,7 @@ export class TSR {
public newConnection = false
public conductor: Conductor
public send: (message: BridgeAPI.FromBridge.Any) => void
- private devices = new Map()
+ private devices = new Map()
private sideLoadedDevices = new Map()
@@ -106,17 +113,21 @@ export class TSR {
private async _updateDevices(): Promise {
// Added/updated:
const deviceOptions = this.deviceOptions
- for (const [deviceId, newDevice] of deviceOptions.entries()) {
- if (newDevice.disable) continue
+ for (const [deviceId, newDeviceOptions] of deviceOptions.entries()) {
+ if (newDeviceOptions.disable) continue
const existingDevice = this.devices.get(deviceId)
- if (!existingDevice || !_.isEqual(existingDevice, newDevice)) {
+ if (!existingDevice || !_.isEqual(existingDevice.options, newDeviceOptions)) {
if (existingDevice) {
+ existingDevice.abortController.abort()
await this.conductor.removeDevice(unprotectString(deviceId))
}
+ await this._removeSideloadDevice(deviceId)
- this.devices.set(deviceId, newDevice)
+ const abortController = new AbortController()
+
+ this.devices.set(deviceId, { options: newDeviceOptions, abortController })
this.onDeviceStatus(deviceId, {
statusCode: StatusCode.UNKNOWN,
messages: ['Initializing'],
@@ -125,10 +136,12 @@ export class TSR {
// Run async so as not to block other devices from being processed.
;(async () => {
- this.sideLoadDevice(deviceId, newDevice)
+ this.sideLoadDevice(deviceId, newDeviceOptions)
// Create the device, but don't initialize it:
- const devicePr = this.conductor.createDevice(unprotectString(deviceId), newDevice)
+ const devicePr = this.conductor.createDevice(unprotectString(deviceId), newDeviceOptions, {
+ signal: abortController.signal,
+ })
this.onDeviceStatus(deviceId, {
active: true,
@@ -171,16 +184,21 @@ export class TSR {
})
// now initialize it
- await this.conductor.initDevice(unprotectString(deviceId), newDevice)
+ await this.conductor.initDevice(unprotectString(deviceId), newDeviceOptions, undefined, {
+ signal: abortController.signal,
+ })
this.onDeviceStatus(deviceId, await device.device.getStatus())
- })().catch((error) => this.log.error('TSR device error: ' + stringifyError(error)))
+ })().catch((error) => {
+ if (!(error instanceof AbortError)) this.log.error('TSR device error: ' + stringifyError(error))
+ })
}
}
// Removed:
- for (const deviceId of this.devices.keys()) {
+ for (const [deviceId, oldDevice] of this.devices.entries()) {
const newDevice = deviceOptions.get(deviceId)
if (!newDevice || newDevice.disable) {
+ oldDevice.abortController.abort()
await this._removeDevice(deviceId)
this.reportRemovedDevice(deviceId)
@@ -192,11 +210,7 @@ export class TSR {
}
private async _removeDevice(deviceId: TSRDeviceId): Promise {
// Delete the sideloaded device, if any
- const sideLoadedDevice = this.sideLoadedDevices.get(deviceId)
- if (sideLoadedDevice) {
- await sideLoadedDevice.close()
- this.sideLoadedDevices.delete(deviceId)
- }
+ await this._removeSideloadDevice(deviceId)
// HACK: There are some scenarios in which this method will never return.
// For example, when trying to remove a CasparCG device that has never connected.
@@ -207,6 +221,15 @@ export class TSR {
this.devices.delete(deviceId)
this.deviceStatus.delete(deviceId)
}
+
+ private async _removeSideloadDevice(deviceId: TSRDeviceId) {
+ const sideLoadedDevice = this.sideLoadedDevices.get(deviceId)
+ if (sideLoadedDevice) {
+ await sideLoadedDevice.close()
+ this.sideLoadedDevices.delete(deviceId)
+ }
+ }
+
public refreshResourcesAndMetadata(
cb: (deviceId: TSRDeviceId, resources: ResourceAny[], metadata: MetadataAny) => void
): void {
@@ -290,7 +313,8 @@ export class TSR {
if (status && device) {
// Hack to get rid of warnings for UDP OSC devices, which always have an UNKNOWN status code.
- const isOscUdp = device.type === DeviceType.OSC && device.options?.type === OSCDeviceType.UDP
+ const isOscUdp =
+ device.options.type === DeviceType.OSC && device.options.options?.type === OSCDeviceType.UDP
const ok = isOscUdp ? true : status.statusCode === StatusCode.GOOD
const message = status.messages?.join(', ') ?? ''
this.send({
diff --git a/tsconfig.build.json b/tsconfig.build.json
index b1ec65ce..b546f770 100644
--- a/tsconfig.build.json
+++ b/tsconfig.build.json
@@ -17,7 +17,7 @@
"target": "es6" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */,
// "lib": ["es6"] /* Specify a set of bundled library declaration files that describe the target runtime environment. */,
// "jsx": "preserve", /* Specify what JSX code is generated. */
- // "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */
+ "experimentalDecorators": true /* Enable experimental support for TC39 stage 2 draft decorators. */,
// "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
// "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h' */
// "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
diff --git a/yarn.lock b/yarn.lock
index 9c970e7a..ef54bcc6 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -6433,6 +6433,11 @@ eventemitter3@^4.0.0, eventemitter3@^4.0.4, eventemitter3@^4.0.7:
resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f"
integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==
+eventemitter3@^5.0.1:
+ version "5.0.1"
+ resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-5.0.1.tgz#53f5ffd0a492ac800721bb42c66b841de96423c4"
+ integrity sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==
+
events@^3.2.0, events@^3.3.0:
version "3.3.0"
resolved "https://registry.yarnpkg.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400"