From 5c8e3bb84c7ee090fc299c1e4fe93e38bfa4df3c Mon Sep 17 00:00:00 2001 From: ianshade Date: Tue, 31 Oct 2023 11:03:24 +0100 Subject: [PATCH] refactor: undo/redo with browser compatibility --- apps/app/package.json | 1 + apps/app/src/electron/ClientEventBus.ts | 15 +- apps/app/src/electron/EverythingService.ts | 134 ++++++++---------- apps/app/src/electron/SuperConductor.ts | 104 ++++++++------ apps/app/src/electron/UndoService.ts | 110 ++++++++++++++ apps/app/src/electron/api/ApiServer.ts | 2 +- apps/app/src/electron/api/ProjectService.ts | 12 +- apps/app/src/ipc/IPCAPI.ts | 81 ++++++----- apps/app/src/main.ts | 36 ++--- apps/app/src/models/project/Project.ts | 14 ++ apps/app/src/preload.ts | 8 ++ apps/app/src/react/App.tsx | 26 +++- apps/app/src/react/api/ApiClient.ts | 14 +- apps/app/src/react/api/ElectronApi.ts | 3 + .../app/src/react/api/RealtimeDataProvider.ts | 9 +- .../deviceStatuses/DeviceStatuses.tsx | 5 +- .../components/pages/homePage/HomePage.tsx | 2 +- apps/app/src/react/mobx/AnalogStore.ts | 1 - apps/app/src/react/mobx/AppStore.ts | 8 ++ .../src/react/mobx/GDDValidatorStoreStore.ts | 1 - apps/app/src/react/mobx/GroupPlayDataStore.ts | 1 - apps/app/src/react/mobx/GuiStore.ts | 3 +- .../react/mobx/ResourcesAndMetadataStore.ts | 1 - yarn.lock | 5 + 24 files changed, 395 insertions(+), 201 deletions(-) create mode 100644 apps/app/src/electron/UndoService.ts create mode 100644 apps/app/src/preload.ts create mode 100644 apps/app/src/react/api/ElectronApi.ts diff --git a/apps/app/package.json b/apps/app/package.json index 34869639..d44afb08 100644 --- a/apps/app/package.json +++ b/apps/app/package.json @@ -92,6 +92,7 @@ "deepmerge-ts": "^5.1.0", "electron-is-dev": "^2.0.0", "electron-updater": "^5.3.0", + "eventemitter3": "^5.0.1", "file-loader": "^6.2.0", "formik": "^2.2.9", "formik-mui": "^5.0.0-alpha.0", diff --git a/apps/app/src/electron/ClientEventBus.ts b/apps/app/src/electron/ClientEventBus.ts index 25a2ac24..d639f39b 100644 --- a/apps/app/src/electron/ClientEventBus.ts +++ b/apps/app/src/electron/ClientEventBus.ts @@ -11,10 +11,18 @@ import { ActiveAnalog } from '../models/rundown/Analog' import { AnalogInput } from '../models/project/AnalogInput' import { BridgeId } from '@shared/api' import { BridgePeripheralId } from '@shared/lib' -import { EventEmitter } from 'stream' +import EventEmitter from 'eventemitter3' +import { SerializableLedgers } from '../models/project/Project' + +type ClientEventBusEvents = { + callMethod: (...args: any[]) => void // legacy + updateUndoLedgers: (undoLedgers: SerializableLedgers) => void + updateRundown: (rundown: Rundown) => void + updateProject: (rundown: Project) => void +} // --- some of it might be needed, most of it hopefully not -export class ClientEventBus extends EventEmitter implements IPCClientMethods { +export class ClientEventBus extends EventEmitter implements IPCClientMethods { close(): void { // Nothing here } @@ -31,6 +39,9 @@ export class ClientEventBus extends EventEmitter implements IPCClientMethods { updateRundown(_fileName: string, rundown: Rundown): void { this.emit('updateRundown', rundown) // TODO: some type safety, please } + updateUndoLedgers(data: SerializableLedgers): void { + this.emit('updateUndoLedgers', data) // TODO: some type safety, please + } updateResourcesAndMetadata( resources: Array<{ id: ResourceId; resource: ResourceAny | null }>, metadata: SerializedProtectedMap diff --git a/apps/app/src/electron/EverythingService.ts b/apps/app/src/electron/EverythingService.ts index 7bc35d6a..83fce8ad 100644 --- a/apps/app/src/electron/EverythingService.ts +++ b/apps/app/src/electron/EverythingService.ts @@ -40,13 +40,7 @@ import { TSRTimelineContent, DeviceOptionsAny, } from 'timeline-state-resolver-types' -import { - ActionDescription, - IPCServerMethods, - MAX_UNDO_LEDGER_LENGTH, - UndoableResult, - UpdateAppDataOptions, -} from '../ipc/IPCAPI' +import { ActionDescription, IPCServerMethods, UndoableResult, UpdateAppDataOptions } from '../ipc/IPCAPI' import { GroupPreparedPlayData } from '../models/GUI/PreparedPlayhead' import { convertToFilename, ExportProjectData, StorageHandler } from './storageHandler' import { Rundown } from '../models/rundown/Rundown' @@ -64,8 +58,6 @@ import { assertNever, deepClone, getResourceIdFromTimelineObj, omit } from '@sha import { TimelineObj } from '../models/rundown/TimelineObj' import { Project, ProjectBase } from '../models/project/Project' import { AppData } from '../models/App/AppData' -import EventEmitter from 'events' -import TypedEmitter from 'typed-emitter' import { filterMapping, getMappingFromTimelineObject, @@ -88,21 +80,15 @@ import { TriggersHandler } from './triggersHandler' import { GDDSchema, ValidatorCache } from 'graphics-data-definition' import * as RundownActions from './rundownActions' import { SuperConductor } from './SuperConductor' +import { UndoLedgerKey, UndoLedgerService } from './UndoService' +import { SpecialLedgers } from '../models/project/Project' -type UndoLedger = Action[] -type UndoPointer = number -type UndoFunction = () => Promise | void -type UndoableFunction = (...args: any[]) => Promise> -interface Action { - description: ActionDescription - arguments: any[] - redo: UndoableFunction - undo: UndoFunction -} - -type IPCServerEvents = { - updatedUndoLedger: (undoLedger: Readonly, undoPointer: Readonly) => void -} +// type IPCServerEvents = { +// updatedUndoLedger: ( +// undoLedger: Readonly<{ [key: string | symbol]: UndoLedger }>, +// undoPointer: Readonly +// ) => void +// } export function isUndoable(result: unknown): result is UndoableResult { if (typeof result !== 'object' || result === null) { @@ -132,7 +118,9 @@ function Undoable(target: EverythingService, _key: string, descriptor: PropertyD const originalMethod = descriptor.value descriptor.value = async function (...args: any) { const result = await originalMethod.apply(this, args) - target.registerUndoable.call(this, args, originalMethod.bind(this), result) + if (isUndoable(result)) { + target.pushUndoable.call(this, result.ledgerKey, args, originalMethod.bind(this), result) + } return result } return descriptor @@ -142,13 +130,8 @@ function Undoable(target: EverythingService, _key: string, descriptor: PropertyD * This class is used server-side, to handle requests from the client * The methods in here will later be moved away to other Services */ -export class EverythingService - extends (EventEmitter as new () => TypedEmitter) - implements ConvertToServerSide -{ +export class EverythingService implements ConvertToServerSide { public triggers?: TriggersHandler - private undoLedger: UndoLedger = [] - private undoPointer: UndoPointer = -1 constructor( private _log: LoggerLike, @@ -156,6 +139,7 @@ export class EverythingService private storage: StorageHandler, private superConductor: SuperConductor, private session: SessionHandler, + private undoService: UndoLedgerService, private callbacks: { onClientConnected: () => void installUpdate: () => void @@ -169,25 +153,10 @@ export class EverythingService onAgreeToUserAgreement: () => void handleError: (error: string, stack?: string) => void } - ) { - super() - } + ) {} - // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types - public registerUndoable(args: any, fcn: () => any, result: UndoableResult): void { - this.undoLedger.splice(this.undoPointer + 1, this.undoLedger.length) - // Add the new action to the undo ledger: - this.undoLedger.push({ - description: result.description, - arguments: args, - undo: result.undo, - redo: fcn, - }) - if (this.undoLedger.length > MAX_UNDO_LEDGER_LENGTH) { - this.undoLedger.splice(0, this.undoLedger.length - MAX_UNDO_LEDGER_LENGTH) - } - this.undoPointer = this.undoLedger.length - 1 - this.emit('updatedUndoLedger', this.undoLedger, this.undoPointer) + public pushUndoable(key: UndoLedgerKey, args: unknown[], fcn: () => any, result: UndoableResult): void { + this.undoService.pushUndoable(key, args, fcn, result) } public getProject(): Project { @@ -254,34 +223,11 @@ export class EverythingService return { rundown, group, part } } - async undo(): Promise { - const action = this.undoLedger[this.undoPointer] - try { - await action.undo() - this.undoPointer-- - } catch (error) { - this._log.error('Error when undoing:', error) - - // Clear - this.undoLedger.splice(0, this.undoLedger.length) - this.undoPointer = -1 - } - this.emit('updatedUndoLedger', this.undoLedger, this.undoPointer) + async undo(key: string): Promise { + await this.undoService.undo(key) } - async redo(): Promise { - const action = this.undoLedger[this.undoPointer + 1] - try { - const redoResult = await action.redo(...action.arguments) - action.undo = redoResult.undo - this.undoPointer++ - } catch (error) { - this._log.error('Error when redoing:', error) - - // Clear - this.undoLedger.splice(0, this.undoLedger.length) - this.undoPointer = -1 - } - this.emit('updatedUndoLedger', this.undoLedger, this.undoPointer) + async redo(key: string): Promise { + await this.undoService.redo(key) } async log(arg: { level: LogLevel; params: any[] }): Promise { @@ -498,6 +444,7 @@ export class EverythingService this._saveUpdates({ rundownId: arg.rundownId, rundown, noEffectOnPlayout: true }) }, description: ActionDescription.SetPartTrigger, + ledgerKey: arg.rundownId, } } async stopGroup(arg: { rundownId: string; groupId: string }): Promise { @@ -654,6 +601,7 @@ export class EverythingService }, description: ActionDescription.NewPart, result, + ledgerKey: arg.rundownId, } } @Undoable @@ -753,6 +701,7 @@ export class EverythingService }, description: ActionDescription.InsertParts, result: inserted, + ledgerKey: arg.rundownId, } } else { return this._insertPartsAsTransparentGroup({ @@ -814,6 +763,7 @@ export class EverythingService return { ...r, result: inserted, + ledgerKey: arg.rundownId, } } @Undoable @@ -858,6 +808,7 @@ export class EverythingService this._saveUpdates({ rundownId: arg.rundownId, rundown }) }, description: ActionDescription.UpdatePart, + ledgerKey: arg.rundownId, } } @Undoable @@ -920,6 +871,7 @@ export class EverythingService } }, description: ActionDescription.UpsertPart, + ledgerKey: arg.rundownId, } } @Undoable @@ -964,6 +916,7 @@ export class EverythingService }, description: ActionDescription.NewGroup, result: newGroup.id, + ledgerKey: arg.rundownId, } } @Undoable @@ -1056,6 +1009,7 @@ export class EverythingService }, description: ActionDescription.InsertGroups, result: inserted, + ledgerKey: arg.rundownId, } } @Undoable @@ -1106,6 +1060,7 @@ export class EverythingService this._saveUpdates({ rundownId: arg.rundownId, rundown }) }, description: ActionDescription.UpdateGroup, + ledgerKey: arg.rundownId, } } @Undoable @@ -1196,6 +1151,7 @@ export class EverythingService } }, description: ActionDescription.UpsertGroup, + ledgerKey: arg.rundownId, } } @Undoable @@ -1262,6 +1218,7 @@ export class EverythingService this._saveUpdates({ rundownId: arg.rundownId, rundown }) }, description: ActionDescription.DeletePart, + ledgerKey: arg.rundownId, } } @Undoable @@ -1293,6 +1250,7 @@ export class EverythingService this._saveUpdates({ rundownId: arg.rundownId, rundown, group: deletedGroup }) }, description: ActionDescription.DeleteGroup, + ledgerKey: arg.rundownId, } } @Undoable @@ -1479,6 +1437,7 @@ export class EverythingService }, description: ActionDescription.MovePart, result: resultingParts, + ledgerKey: arg.to.rundownId, } } @Undoable @@ -1529,6 +1488,7 @@ export class EverythingService this._saveUpdates({ rundownId: arg.rundownId, rundown, group: newGroup ? undefined : group }) }, description: ActionDescription.DuplicatePart, + ledgerKey: arg.rundownId, } } @Undoable @@ -1573,6 +1533,7 @@ export class EverythingService this._saveUpdates({ rundownId: arg.rundownId, rundown }) }, description: ActionDescription.MoveGroup, + ledgerKey: arg.rundownId, } } @Undoable @@ -1599,6 +1560,7 @@ export class EverythingService this._saveUpdates({ rundownId: arg.rundownId, rundown }) }, description: ActionDescription.DuplicateGroup, + ledgerKey: arg.rundownId, } } @@ -1640,6 +1602,7 @@ export class EverythingService this._saveUpdates({ rundownId: arg.rundownId, rundown, group }) }, description: ActionDescription.UpdateTimelineObj, + ledgerKey: arg.rundownId, } } @@ -1683,6 +1646,7 @@ export class EverythingService this._saveUpdates({ rundownId: arg.rundownId, rundown, group }) }, description: ActionDescription.DeleteTimelineObj, + ledgerKey: arg.rundownId, } } @Undoable @@ -1809,6 +1773,7 @@ export class EverythingService }, description: ActionDescription.AddTimelineObj, result: inserted, + ledgerKey: arg.rundownId, } } @Undoable @@ -1863,6 +1828,7 @@ export class EverythingService this._saveUpdates({ project: updatedProject, rundownId: arg.rundownId, rundown, group }) }, description: ActionDescription.MoveTimelineObjToNewLayer, + ledgerKey: arg.rundownId, } } @@ -1986,6 +1952,7 @@ export class EverythingService this._saveUpdates({ project: updatedProject, rundownId: arg.rundownId, rundown }) }, description: ActionDescription.addResourcesToTimeline, + ledgerKey: arg.rundownId, } } @Undoable @@ -2017,6 +1984,7 @@ export class EverythingService this._saveUpdates({ rundownId: arg.rundownId, rundown, group }) }, description: ActionDescription.ToggleGroupLoop, + ledgerKey: arg.rundownId, } } @Undoable @@ -2048,6 +2016,7 @@ export class EverythingService this._saveUpdates({ rundownId: arg.rundownId, rundown, group }) }, description: ActionDescription.ToggleGroupAutoplay, + ledgerKey: arg.rundownId, } } @Undoable @@ -2090,6 +2059,7 @@ export class EverythingService this._saveUpdates({ rundownId: arg.rundownId, rundown, group }) }, description: ActionDescription.toggleGroupOneAtATime, + ledgerKey: arg.rundownId, } } @Undoable @@ -2121,6 +2091,7 @@ export class EverythingService this._saveUpdates({ rundownId: arg.rundownId, rundown, group }) }, description: ActionDescription.ToggleGroupDisable, + ledgerKey: arg.rundownId, } } @Undoable @@ -2141,6 +2112,7 @@ export class EverythingService this._saveUpdates({ rundownId: arg.rundownId, rundown, group, noEffectOnPlayout: true }) }, description: ActionDescription.ToggleGroupLock, + ledgerKey: arg.rundownId, } } @Undoable @@ -2165,6 +2137,7 @@ export class EverythingService this._saveUpdates({ rundownId: arg.rundownId, rundown, group, noEffectOnPlayout: true }) }, description: ActionDescription.ToggleGroupCollapse, + ledgerKey: arg.rundownId, } } @Undoable @@ -2193,6 +2166,7 @@ export class EverythingService this._saveUpdates({ rundownId: arg.rundownId, rundown, noEffectOnPlayout: true }) }, description: ActionDescription.ToggleAllGroupsCollapse, + ledgerKey: arg.rundownId, } } async refreshResources(): Promise { @@ -2248,6 +2222,7 @@ export class EverythingService }, description: ActionDescription.NewRundown, result: rundown, + ledgerKey: rundown.id, } } async deleteRundown(arg: { rundownId: string }): Promise { @@ -2278,6 +2253,7 @@ export class EverythingService this._saveUpdates({}) }, description: ActionDescription.OpenRundown, + ledgerKey: SpecialLedgers.APPLICATION, } } @Undoable @@ -2301,6 +2277,7 @@ export class EverythingService this._saveUpdates({}) }, description: ActionDescription.CloseRundown, + ledgerKey: SpecialLedgers.APPLICATION, } } @@ -2322,6 +2299,7 @@ export class EverythingService }, description: ActionDescription.RenameRundown, result: newRundownId, + ledgerKey: arg.rundownId, } } async isRundownPlaying(arg: { rundownId: string }): Promise { @@ -2432,6 +2410,7 @@ export class EverythingService } }, description: ActionDescription.CreateMissingMapping, + ledgerKey: arg.rundownId, } } @@ -2479,6 +2458,7 @@ export class EverythingService } }, description: ActionDescription.AddPeripheralArea, + ledgerKey: SpecialLedgers.PERIPHERALS, } } @Undoable @@ -2522,6 +2502,7 @@ export class EverythingService } }, description: ActionDescription.RemovePeripheralArea, + ledgerKey: SpecialLedgers.PERIPHERALS, } } @Undoable @@ -2570,6 +2551,7 @@ export class EverythingService } }, description: ActionDescription.UpdatePeripheralArea, + ledgerKey: SpecialLedgers.PERIPHERALS, } } @Undoable @@ -2609,6 +2591,7 @@ export class EverythingService this._saveUpdates({ project }) }, description: ActionDescription.AssignAreaToGroup, + ledgerKey: SpecialLedgers.PERIPHERALS, } } async startDefiningArea(arg: { bridgeId: BridgeId; deviceId: PeripheralId; areaId: string }): Promise { @@ -2676,6 +2659,7 @@ export class EverythingService this._saveUpdates({ appData, noEffectOnPlayout: true }) }, description: ActionDescription.SetApplicationTrigger, + ledgerKey: SpecialLedgers.APPLICATION, } } diff --git a/apps/app/src/electron/SuperConductor.ts b/apps/app/src/electron/SuperConductor.ts index e13866eb..2a843588 100644 --- a/apps/app/src/electron/SuperConductor.ts +++ b/apps/app/src/electron/SuperConductor.ts @@ -41,6 +41,7 @@ import { ActiveAnalog } from '../models/rundown/Analog' import { AnalogHandler } from './analogHandler' import { AnalogInput } from '../models/project/AnalogInput' import { SystemMessageOptions } from '../ipc/IPCAPI' +import { UndoLedgerService } from './UndoService' export class SuperConductor { ipcServer: EverythingService @@ -225,53 +226,66 @@ export class SuperConductor { }, }) - this.ipcServer = new EverythingService(this.log, this.renderLog, this.storage, this, this.session, { - refreshResources: () => { - this.refreshResources() - }, - refreshResourcesSetAuto: (interval: number) => { - const project = this.storage.getProject() - project.autoRefreshInterval = interval - this.storage.updateProject(project) - }, - onClientConnected: () => { - // Nothing here yet - }, - installUpdate: () => { - autoUpdater.autoRunAppAfterInstall = true - autoUpdater.quitAndInstall() - }, - updateTimeline: (group: Group): GroupPreparedPlayData | null => { - return this.updateTimeline(group) - }, - updatePeripherals: (): void => { - this.triggers?.triggerUpdatePeripherals() - this.analogHandler?.triggerUpdatePeripherals() - }, - setKeyboardKeys: (activeKeys: ActiveTrigger[]): void => { - this.triggers?.setKeyboardKeys(activeKeys) - }, - triggerHandleAutoFill: () => { - this.triggerHandleAutoFill() - }, - makeDevData: async () => { - await this.storage.makeDevData() - }, - onAgreeToUserAgreement: () => { - this.telemetryHandler.setUserHasAgreed() - this.telemetryHandler.onAcceptUserAgreement() - - if (!this.hasStoredStartupUserStatistics) { - this.hasStoredStartupUserStatistics = true - this.telemetryHandler.onStartup() - } - }, - handleError: (error: string, stack?: string) => { - this.log.error(error, stack) - this.telemetryHandler.onError(error, stack) - }, + const undoLedgerService = new UndoLedgerService(this.log) + undoLedgerService.on('updatedUndoLedger', (data) => { + this.clientEventBus.updateUndoLedgers(data) }) + this.ipcServer = new EverythingService( + this.log, + this.renderLog, + this.storage, + this, + this.session, + undoLedgerService, + { + refreshResources: () => { + this.refreshResources() + }, + refreshResourcesSetAuto: (interval: number) => { + const project = this.storage.getProject() + project.autoRefreshInterval = interval + this.storage.updateProject(project) + }, + onClientConnected: () => { + // Nothing here yet + }, + installUpdate: () => { + autoUpdater.autoRunAppAfterInstall = true + autoUpdater.quitAndInstall() + }, + updateTimeline: (group: Group): GroupPreparedPlayData | null => { + return this.updateTimeline(group) + }, + updatePeripherals: (): void => { + this.triggers?.triggerUpdatePeripherals() + this.analogHandler?.triggerUpdatePeripherals() + }, + setKeyboardKeys: (activeKeys: ActiveTrigger[]): void => { + this.triggers?.setKeyboardKeys(activeKeys) + }, + triggerHandleAutoFill: () => { + this.triggerHandleAutoFill() + }, + makeDevData: async () => { + await this.storage.makeDevData() + }, + onAgreeToUserAgreement: () => { + this.telemetryHandler.setUserHasAgreed() + this.telemetryHandler.onAcceptUserAgreement() + + if (!this.hasStoredStartupUserStatistics) { + this.hasStoredStartupUserStatistics = true + this.telemetryHandler.onStartup() + } + }, + handleError: (error: string, stack?: string) => { + this.log.error(error, stack) + this.telemetryHandler.onError(error, stack) + }, + } + ) + this.triggers = new TriggersHandler(this.log, this.storage, this.ipcServer, this.bridgeHandler, this.session) this.triggers.on('error', (e) => this.log.error(e)) this.triggers.on('failedGlobalTriggers', (failedGlobalTriggers) => { diff --git a/apps/app/src/electron/UndoService.ts b/apps/app/src/electron/UndoService.ts new file mode 100644 index 00000000..376a236c --- /dev/null +++ b/apps/app/src/electron/UndoService.ts @@ -0,0 +1,110 @@ +import { LoggerLike } from '@shared/api' +import { ActionDescription, UndoableResult } from '../ipc/IPCAPI' +import EventEmitter from 'eventemitter3' +import _ from 'lodash' +import { SerializableLedgers } from '../models/project/Project' +import { SpecialLedgers } from '../models/project/Project' + +export const MAX_UNDO_LEDGER_LENGTH = 100 + +export interface UndoLedger { + actions: Action[] + pointer: UndoPointer +} +export type UndoLedgerKey = string | SpecialLedgers +type UndoPointer = number +type UndoFunction = () => Promise | void +export type UndoableFunction = (...args: any[]) => Promise> +interface Action { + description: ActionDescription + arguments: unknown[] + redo: UndoableFunction + undo: UndoFunction +} + +type UndoServiceEvents = { + updatedUndoLedger: (undoLedgers: SerializableLedgers) => void +} + +export class UndoLedgerService extends EventEmitter { + constructor(private _log: LoggerLike) { + super() + } + private undoLedgers = new Map() + + public pushUndoable(key: UndoLedgerKey, args: unknown[], fcn: () => any, result: UndoableResult): void { + let ledger = this.undoLedgers.get(key) + if (!ledger) { + ledger = this.makeEmptyLedger() + this.undoLedgers.set(key, ledger) + } + ledger.actions.splice(ledger.pointer + 1, ledger.actions.length) + // Add the new action to the undo ledger: + ledger.actions.push({ + description: result.description, + arguments: args, + undo: result.undo, + redo: fcn, + }) + if (ledger.actions.length > MAX_UNDO_LEDGER_LENGTH) { + ledger.actions.splice(0, ledger.actions.length - MAX_UNDO_LEDGER_LENGTH) + } + ledger.pointer = ledger.actions.length - 1 + this.emitUpdate() + } + + private emitUpdate() { + this.emit('updatedUndoLedger', this.toSerializable()) + } + + public async undo(key: UndoLedgerKey): Promise { + const ledger = this.undoLedgers.get(key) + const action = ledger?.actions?.[ledger?.pointer] + if (!action) return + try { + await action.undo() + ledger.pointer-- + } catch (error) { + this._log.error('Error when undoing:', error) + + // Clear + this.undoLedgers.set(key, this.makeEmptyLedger()) + } + this.emitUpdate() + } + + public async redo(key: UndoLedgerKey): Promise { + const ledger = this.undoLedgers.get(key) + const action = ledger?.actions?.[ledger?.pointer + 1] + if (!action) return + try { + const redoResult = await action.redo(...action.arguments) + action.undo = redoResult.undo + ledger.pointer++ + } catch (error) { + this._log.error('Error when redoing:', error) + + // Clear + this.undoLedgers.set(key, this.makeEmptyLedger()) + } + this.emitUpdate() + } + + private makeEmptyLedger(): UndoLedger { + return { + actions: [], + pointer: -1, + } + } + + private toSerializable(): SerializableLedgers { + return _.mapValues(Object.fromEntries(this.undoLedgers.entries()), (ledger: UndoLedger) => ({ + undo: ledger.actions[ledger.pointer] + ? { description: ledger.actions[ledger.pointer]?.description } + : undefined, + redo: ledger.actions[ledger.pointer + 1] + ? { description: ledger.actions[ledger.pointer + 1]?.description } + : undefined, + })) + } +} diff --git a/apps/app/src/electron/api/ApiServer.ts b/apps/app/src/electron/api/ApiServer.ts index d70893cc..3ab73c58 100644 --- a/apps/app/src/electron/api/ApiServer.ts +++ b/apps/app/src/electron/api/ApiServer.ts @@ -41,7 +41,7 @@ export class ApiServer { this.app.use(ServiceName.PROJECTS, new ProjectService(this.app, ipcServer, clientEventBus), { methods: ClientMethods[ServiceName.PROJECTS], - serviceEvents: ['created', ProjectsEvents.UPDATED, 'deleted'], + serviceEvents: ['created', ProjectsEvents.UPDATED, 'deleted', ProjectsEvents.UNDO_LEDGERS_UPDATED], }) this.app.use(ServiceName.PARTS, new PartService(this.app, ipcServer, clientEventBus), { diff --git a/apps/app/src/electron/api/ProjectService.ts b/apps/app/src/electron/api/ProjectService.ts index 9f23ab78..8c164ab2 100644 --- a/apps/app/src/electron/api/ProjectService.ts +++ b/apps/app/src/electron/api/ProjectService.ts @@ -4,6 +4,7 @@ import EventEmitter from 'node:events' import { ProjectsEvents, ServiceTypes } from '../../ipc/IPCAPI' import { Project, ProjectBase } from '../../models/project/Project' import { ClientEventBus } from '../ClientEventBus' +import { SerializableLedgers } from '../../models/project/Project' export const PROJECTS_CHANNEL_PREFIX = 'projects' export class ProjectService extends EventEmitter { @@ -16,6 +17,9 @@ export class ProjectService extends EventEmitter { clientEventBus.on('updateProject', (project: Project) => { this.emit(ProjectsEvents.UPDATED, project) }) + clientEventBus.on('updateUndoLedgers', (ledgers: SerializableLedgers) => { + this.emit(ProjectsEvents.UNDO_LEDGERS_UPDATED, ledgers) + }) } async get(_id: string): Promise { @@ -66,15 +70,15 @@ export class ProjectService extends EventEmitter { return await this.everythingService.importProject() } - async undo(): Promise { + async undo(data: { key: string }): Promise { // TODO: likely not the best place for this method // TODO: access control - return await this.everythingService.undo() + return await this.everythingService.undo(data.key) } - async redo(): Promise { + async redo(data: { key: string }): Promise { // TODO: likely not the best place for this method // TODO: access control - return await this.everythingService.redo() + return await this.everythingService.redo(data.key) } } diff --git a/apps/app/src/ipc/IPCAPI.ts b/apps/app/src/ipc/IPCAPI.ts index 22f8c8bd..428bfcc2 100644 --- a/apps/app/src/ipc/IPCAPI.ts +++ b/apps/app/src/ipc/IPCAPI.ts @@ -1,6 +1,6 @@ import { PartialDeep } from 'type-fest' import { BridgeStatus } from '../models/project/Bridge' -import { Project } from '../models/project/Project' +import { Project, SerializableLedger } from '../models/project/Project' import { ResourceAny, ResourceId, MetadataAny, SerializedProtectedMap, TSRDeviceId } from '@shared/models' import { Rundown } from '../models/rundown/Rundown' import { TimelineObj } from '../models/rundown/TimelineObj' @@ -23,8 +23,7 @@ import { type ProjectService } from '../electron/api/ProjectService' import { type ReportingService } from '../electron/api/ReportingService' import { type RundownService } from '../electron/api/RundownService' import { type GroupService } from '../electron/api/GroupService' - -export const MAX_UNDO_LEDGER_LENGTH = 100 +import { type SpecialLedgers } from '../models/project/Project' export enum ServiceName { GROUPS = 'groups', @@ -117,50 +116,55 @@ export const ClientMethods: ServiceKeyArrays = { } export const enum ActionDescription { - NewPart = 'create new part', - InsertParts = 'insert part(s)', - UpdatePart = 'update part', + NewPart = 'Create new part', + InsertParts = 'Insert part(s)', + UpdatePart = 'Update part', SetPartTrigger = 'Assign trigger', - NewGroup = 'create new group', - InsertGroups = 'insert group(s)', - UpdateGroup = 'update group', - DeletePart = 'delete part', - DeleteGroup = 'delete group', - MovePart = 'move part', - MoveGroup = 'move group', - UpdateTimelineObj = 'update timeline object', - DeleteTimelineObj = 'delete timeline object', - AddTimelineObj = 'add timeline obj', - addResourcesToTimeline = 'add resource to timeline', - ToggleGroupLoop = 'toggle group loop', - ToggleGroupAutoplay = 'toggle group autoplay', - toggleGroupOneAtATime = 'toggle group one-at-a-time', - ToggleGroupDisable = 'toggle group disable', - ToggleGroupLock = 'toggle group lock', - ToggleGroupCollapse = 'toggle group collapse', - ToggleAllGroupsCollapse = 'toggle all groups collapse', - NewRundown = 'new rundown', - DeleteRundown = 'delete rundown', - OpenRundown = 'open rundown', - CloseRundown = 'close rundown', - RenameRundown = 'rename rundown', - MoveTimelineObjToNewLayer = 'move timeline object to new layer', - CreateMissingMapping = 'create missing layer', - DuplicateGroup = 'duplicate group', - DuplicatePart = 'duplicate part', + NewGroup = 'Create new group', + InsertGroups = 'Insert group(s)', + UpdateGroup = 'Update group', + DeletePart = 'Delete part', + DeleteGroup = 'Delete group', + MovePart = 'Move part', + MoveGroup = 'Move group', + UpdateTimelineObj = 'Update timeline object', + DeleteTimelineObj = 'Delete timeline object', + AddTimelineObj = 'Add timeline obj', + addResourcesToTimeline = 'Add resource to timeline', + ToggleGroupLoop = 'Toggle group loop', + ToggleGroupAutoplay = 'Toggle group autoplay', + toggleGroupOneAtATime = 'Toggle group one-at-a-time', + ToggleGroupDisable = 'Toggle group disable', + ToggleGroupLock = 'Toggle group lock', + ToggleGroupCollapse = 'Toggle group collapse', + ToggleAllGroupsCollapse = 'Toggle all groups collapse', + NewRundown = 'New rundown', + DeleteRundown = 'Delete rundown', + OpenRundown = 'Open rundown', + CloseRundown = 'Close rundown', + RenameRundown = 'Rename rundown', + MoveTimelineObjToNewLayer = 'Move timeline object to new layer', + CreateMissingMapping = 'Create missing layer', + DuplicateGroup = 'Duplicate group', + DuplicatePart = 'Duplicate part', AddPeripheralArea = 'Add button area', UpdatePeripheralArea = 'Update button area', RemovePeripheralArea = 'Remove button area', AssignAreaToGroup = 'Assign Area to Group', // eslint-disable-next-line @typescript-eslint/no-duplicate-enum-values SetApplicationTrigger = 'Assign trigger', - UpsertGroup = 'upsert group', - UpsertPart = 'upsert part', + UpsertGroup = 'Upsert group', + UpsertPart = 'Upsert part', } export type UndoFunction = () => Promise | void -export type UndoableResult = { undo: UndoFunction; description: ActionDescription; result?: T } +export type UndoableResult = { + ledgerKey: string | SpecialLedgers + undo: UndoFunction + description: ActionDescription + result?: T +} export type UndoableFunction = (...args: any[]) => Promise @@ -171,6 +175,10 @@ export interface Action { undo: UndoFunction } +export interface ElectronAPI { + updateUndoLedger: (key: string, data: SerializableLedger) => void +} + // --- legacy /** Methods that can be called on the server, by the client */ export interface IPCServerMethods { @@ -369,4 +377,5 @@ export enum RundownsEvents { } export enum ProjectsEvents { UPDATED = 'updated', + UNDO_LEDGERS_UPDATED = 'undo_ledgers_updated', } diff --git a/apps/app/src/main.ts b/apps/app/src/main.ts index 942576dc..86b88124 100644 --- a/apps/app/src/main.ts +++ b/apps/app/src/main.ts @@ -1,5 +1,5 @@ import { literal, stringifyError } from '@shared/lib' -import { app, BrowserWindow, dialog, Menu, shell, screen } from 'electron' +import { app, BrowserWindow, dialog, Menu, shell, screen, ipcMain } from 'electron' import isDev from 'electron-is-dev' import { autoUpdater } from 'electron-updater' import { CURRENT_VERSION } from './electron/bridgeHandler' @@ -9,6 +9,7 @@ import { createLoggers } from './lib/logging' import { baseFolder } from './lib/baseFolder' import path from 'path' import winston from 'winston' +import { SerializableLedger } from './models/project/Project' function createWindow(log: winston.Logger, superConductor: SuperConductor): void { const appData = superConductor.storage.getAppData() @@ -20,8 +21,9 @@ function createWindow(log: winston.Logger, superConductor: SuperConductor): void height: appData.windowPosition.height, webPreferences: { - nodeIntegration: true, - contextIsolation: false, + nodeIntegration: false, + contextIsolation: true, + preload: path.join(__dirname, 'preload.js'), }, title: 'SuperConductor', }) @@ -64,12 +66,10 @@ function createWindow(log: winston.Logger, superConductor: SuperConductor): void undoEnabled: false, redoLabel: 'Redo', redoEnabled: false, - onUndoClick: () => { - superConductor.ipcServer.undo().catch(log.error) - }, - onRedoClick: () => { - superConductor.ipcServer.redo().catch(log.error) - }, + // eslint-disable-next-line @typescript-eslint/no-empty-function + onUndoClick: () => {}, + // eslint-disable-next-line @typescript-eslint/no-empty-function + onRedoClick: () => {}, onAboutClick: () => { // TODO: this should probably become a client-side only action // handler.ipcClient.displayAboutDialog() @@ -113,13 +113,17 @@ function createWindow(log: winston.Logger, superConductor: SuperConductor): void const menu = generateMenu(menuOpts, log) Menu.setApplicationMenu(menu) - superConductor.ipcServer.on('updatedUndoLedger', (undoLedger, undoPointer) => { - const undoAction = undoLedger[undoPointer] - const redoAction = undoLedger[undoPointer + 1] - menuOpts.undoLabel = undoAction ? `Undo ${undoAction.description}` : 'Undo' - menuOpts.undoEnabled = Boolean(undoAction) - menuOpts.redoLabel = redoAction ? `Redo ${redoAction.description}` : 'Redo' - menuOpts.redoEnabled = Boolean(redoAction) + ipcMain.on('updateUndoLedger', (_event, key: string, undoLedger: SerializableLedger) => { + menuOpts.undoLabel = undoLedger.undo ? `Undo ${undoLedger.undo.description}` : 'Undo' + menuOpts.undoEnabled = Boolean(undoLedger.undo) + menuOpts.onUndoClick = () => { + superConductor.ipcServer.undo(key).catch(log.error) + } + menuOpts.redoLabel = undoLedger.redo ? `Redo ${undoLedger.redo.description}` : 'Redo' + menuOpts.redoEnabled = Boolean(undoLedger.redo) + menuOpts.onRedoClick = () => { + superConductor.ipcServer.redo(key).catch(log.error) + } const menu = generateMenu(menuOpts, log) Menu.setApplicationMenu(menu) }) diff --git a/apps/app/src/models/project/Project.ts b/apps/app/src/models/project/Project.ts index 47501c2f..78f3cf0c 100644 --- a/apps/app/src/models/project/Project.ts +++ b/apps/app/src/models/project/Project.ts @@ -40,3 +40,17 @@ export interface AnalogInputSetting { relativeMaxCap?: number absoluteOffset?: number } + +export interface SerializableLedger { + undo?: { description?: string } + redo?: { description?: string } +} + +export interface SerializableLedgers { + [key: string]: SerializableLedger +} + +export enum SpecialLedgers { + APPLICATION = 'application', + PERIPHERALS = 'peripherals', +} diff --git a/apps/app/src/preload.ts b/apps/app/src/preload.ts new file mode 100644 index 00000000..5d176046 --- /dev/null +++ b/apps/app/src/preload.ts @@ -0,0 +1,8 @@ +import { contextBridge, ipcRenderer } from 'electron' +import { ElectronAPI } from './ipc/IPCAPI' + +contextBridge.exposeInMainWorld('electronAPI', { + updateUndoLedger: (key, data) => { + ipcRenderer.send('updateUndoLedger', key, data) + }, +} satisfies ElectronAPI) diff --git a/apps/app/src/react/App.tsx b/apps/app/src/react/App.tsx index 65580273..4d0b3487 100644 --- a/apps/app/src/react/App.tsx +++ b/apps/app/src/react/App.tsx @@ -1,5 +1,4 @@ import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react' -// const { ipcRenderer } = window.require('electron') import '@fontsource/barlow/300.css' import '@fontsource/barlow/400.css' @@ -15,7 +14,7 @@ import { Sidebar } from './components/sidebar/Sidebar' import sorensen from '@sofie-automation/sorensen' import { RealtimeDataProvider } from './api/RealtimeDataProvider' import { ApiClient } from './api/ApiClient' -import { Project } from '../models/project/Project' +import { Project, SpecialLedgers } from '../models/project/Project' import { IPCServerContext } from './contexts/IPCServer' import { ProjectContext } from './contexts/Project' import { HotkeyContext, IHotkeyContext, TriggersEmitter } from './contexts/Hotkey' @@ -50,6 +49,7 @@ import { TextBtn } from './components/inputs/textBtn/TextBtn' import { HiOutlineX, HiDotsVertical } from 'react-icons/hi' import { protectString } from '@shared/models' import { PERIPHERAL_KEYBOARD } from '../models/project/Peripheral' +import { ElectronApi } from './api/ElectronApi' /** * Used to remove unnecessary cruft from error messages. @@ -491,6 +491,13 @@ export const App = observer(function App() { } }, [sorensenInitialized, handleError, gui, currentRundownId, deleteSelectedTimelineObjs]) + const undoLedgerKey = useMemoComputedValue(() => { + let key: string = SpecialLedgers.APPLICATION + if (gui.isPeripheralPopoverOpen) key = SpecialLedgers.PERIPHERALS + if (currentRundownId && !gui.isHomeSelected()) key = currentRundownId + return key + }, [gui, currentRundownId]) + useEffect(() => { if (!sorensenInitialized) { return @@ -521,13 +528,11 @@ export const App = observer(function App() { } function onUndo(): void { setUserAgreementScreenOpen(false) - // eslint-disable-next-line @typescript-eslint/unbound-method - serverAPI.undo().catch(handleError) + if (undoLedgerKey) serverAPI.undo({ key: undoLedgerKey }).catch(handleError) } function onRedo(): void { setUserAgreementScreenOpen(false) - // eslint-disable-next-line @typescript-eslint/unbound-method - serverAPI.redo().catch(handleError) + if (undoLedgerKey) serverAPI.redo({ key: undoLedgerKey }).catch(handleError) } sorensen.bind('Escape', onEscapeKey, { up: false, @@ -548,7 +553,14 @@ export const App = observer(function App() { sorensen.unbind('Control+KeyZ', onUndo) sorensen.unbind('Control+KeyY', onRedo) } - }, [sorensenInitialized, handleError, gui, currentRundownId, serverAPI]) + }, [sorensenInitialized, handleError, gui, currentRundownId, serverAPI, undoLedgerKey]) + + useEffect(() => { + if (ElectronApi) { + const ledger = store.appStore.undoLedgers[undoLedgerKey] ?? {} + ElectronApi.updateUndoLedger(undoLedgerKey, JSON.parse(JSON.stringify(ledger))) + } + }, [undoLedgerKey, store.appStore.undoLedgers]) useMemoComputedValue(() => { if (!project) return diff --git a/apps/app/src/react/api/ApiClient.ts b/apps/app/src/react/api/ApiClient.ts index f44b449f..380fe34f 100644 --- a/apps/app/src/react/api/ApiClient.ts +++ b/apps/app/src/react/api/ApiClient.ts @@ -21,9 +21,9 @@ type PartArg = Parameters = Parameters< ServiceTypes[ServiceName.RUNDOWNS][T] >[0] -// type ProjectArg = Parameters< -// ServiceTypes[ServiceName.PROJECTS][T] -// >[0] +type ProjectArg = Parameters< + ServiceTypes[ServiceName.PROJECTS][T] +>[0] type ReportingArg = Parameters< ServiceTypes[ServiceName.REPORTING][T] >[0] @@ -330,10 +330,10 @@ export class ApiClient { async setApplicationTrigger(...args: ServerArgs<'setApplicationTrigger'>): ServerReturn<'setApplicationTrigger'> { return this.invokeServerMethod('setApplicationTrigger', ...args) } - async undo(): Promise { - return this.projectService.undo() + async undo(data: ProjectArg<'undo'>): Promise { + return this.projectService.undo(data) } - async redo(): Promise { - return this.projectService.redo() + async redo(data: ProjectArg<'redo'>): Promise { + return this.projectService.redo(data) } } diff --git a/apps/app/src/react/api/ElectronApi.ts b/apps/app/src/react/api/ElectronApi.ts new file mode 100644 index 00000000..d26a3245 --- /dev/null +++ b/apps/app/src/react/api/ElectronApi.ts @@ -0,0 +1,3 @@ +import { ElectronAPI } from '../../ipc/IPCAPI' + +export const ElectronApi = (window as any).electronAPI ? ((window as any).electronAPI as ElectronAPI) : undefined diff --git a/apps/app/src/react/api/RealtimeDataProvider.ts b/apps/app/src/react/api/RealtimeDataProvider.ts index 01283b12..5f5b9102 100644 --- a/apps/app/src/react/api/RealtimeDataProvider.ts +++ b/apps/app/src/react/api/RealtimeDataProvider.ts @@ -13,6 +13,7 @@ import { AnalogInput } from '../../models/project/AnalogInput' import { BridgeId } from '@shared/api' import { BridgePeripheralId } from '@shared/lib' import { app } from './ApiClient' +import { SerializableLedgers } from '../../models/project/Project' /** This class is used client-side, to handle messages from the server */ export class RealtimeDataProvider { @@ -37,6 +38,7 @@ export class RealtimeDataProvider { updateDefiningArea?: (definingArea: DefiningArea | null) => void updateFailedGlobalTriggers?: (identifiers: string[]) => void updateAnalogInput?: (fullIdentifier: string, analogInput: AnalogInput | null) => void + updateUndoLedgers?: (data: SerializableLedgers) => void } ) { // this is new: @@ -44,7 +46,9 @@ export class RealtimeDataProvider { this.updateRundown(rundown.id, rundown) ) app.service(ServiceName.PROJECTS).on(ProjectsEvents.UPDATED, (project) => this.updateProject(project)) - // app.service('project').on(...) etc. + app.service(ServiceName.PROJECTS).on(ProjectsEvents.UNDO_LEDGERS_UPDATED, (undoLedgers) => + this.updateUndoLedgers(undoLedgers) + ) // this is temporary: app.service(ServiceName.LEGACY).on('callMethod', (args) => this.handleCallMethod(args[0], args.slice(1))) @@ -73,6 +77,9 @@ export class RealtimeDataProvider { updateRundown(fileName: string, rundown: Rundown): void { this.callbacks.updateRundown?.(fileName, rundown) } + updateUndoLedgers(data: SerializableLedgers): void { + this.callbacks.updateUndoLedgers?.(data) + } updateResourcesAndMetadata( resources: Array<{ id: ResourceId; resource: ResourceAny | null }>, metadata: SerializedProtectedMap diff --git a/apps/app/src/react/components/headerBar/deviceStatuses/DeviceStatuses.tsx b/apps/app/src/react/components/headerBar/deviceStatuses/DeviceStatuses.tsx index 5f1bd699..366dd99a 100644 --- a/apps/app/src/react/components/headerBar/deviceStatuses/DeviceStatuses.tsx +++ b/apps/app/src/react/components/headerBar/deviceStatuses/DeviceStatuses.tsx @@ -18,6 +18,7 @@ import { getPeripheralId } from '@shared/lib' export const DeviceStatuses: React.FC = observer(function DeviceStatuses() { const project = useContext(ProjectContext) const appStore = store.appStore + const gui = store.guiStore const [submenuPopover, setSubmenuPopover] = React.useState<{ anchorEl: HTMLAnchorElement @@ -26,7 +27,8 @@ export const DeviceStatuses: React.FC = observer(function DeviceStatuses() { } | null>(null) const closeSubMenu = useCallback(() => { setSubmenuPopover(null) - }, []) + gui.isPeripheralPopoverOpen = true + }, [gui]) const [disabledPeripheralsPopover, setDisabledPeripheralsPopover] = React.useState<{ anchorEl: HTMLButtonElement @@ -144,6 +146,7 @@ export const DeviceStatuses: React.FC = observer(function DeviceStatuses() { bridgeId: peripheral.bridgeId, deviceId: peripheral.id, }) + gui.isPeripheralPopoverOpen = true }} /> ) diff --git a/apps/app/src/react/components/pages/homePage/HomePage.tsx b/apps/app/src/react/components/pages/homePage/HomePage.tsx index 17a8c493..bc8ff8b2 100644 --- a/apps/app/src/react/components/pages/homePage/HomePage.tsx +++ b/apps/app/src/react/components/pages/homePage/HomePage.tsx @@ -1,5 +1,5 @@ import React from 'react' -import { Project } from 'src/models/project/Project' +import { Project } from '../../../../models/project/Project' import { observer } from 'mobx-react-lite' import { store } from '../../../mobx/store' import { ProjectPage } from './projectPage/ProjectPage' diff --git a/apps/app/src/react/mobx/AnalogStore.ts b/apps/app/src/react/mobx/AnalogStore.ts index 7e300d3e..c20f9de5 100644 --- a/apps/app/src/react/mobx/AnalogStore.ts +++ b/apps/app/src/react/mobx/AnalogStore.ts @@ -4,7 +4,6 @@ import { ActiveAnalog } from '../../models/rundown/Analog' import { RealtimeDataProvider } from '../api/RealtimeDataProvider' import { ApiClient } from '../api/ApiClient' import { ClientSideLogger } from '../api/logger' -// const { ipcRenderer } = window.require('electron') export class AnalogStore { private analogInputs = new Map() 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/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"