V2 fix socket (#296)

* refactor: rename timer endpoint

* refactor: typescript

* refactor: rename eventData and viewSettings

* refactor: message manager uses event store
This commit is contained in:
Carlos Valente
2023-02-24 22:47:07 +01:00
committed by GitHub
parent 03552056cb
commit edac1d7f75
102 changed files with 836 additions and 715 deletions
@@ -2,6 +2,8 @@
* Class Event Provider is a mediator for handling the local db
* and adds logic specific to ontime data
*/
import { EventData, ViewSettings } from 'ontime-types';
import { data, db } from '../../modules/loadDb.js';
import { safeMerge } from './DataProvider.utils.js';
@@ -10,14 +12,14 @@ export class DataProvider {
return data;
}
static async setEventData(newData) {
data.event = { ...data.event, ...newData };
static async setEventData(newData: EventData) {
data.eventData = { ...data.eventData, ...newData };
await this.persist();
return data.event;
return data.eventData;
}
static getEventData() {
return data.event;
return data.eventData;
}
static async setRundown(newData) {
@@ -40,7 +42,6 @@ export class DataProvider {
}
static async deleteEvent(eventId) {
// @ts-expect-error -- this will go away once we type db
data.rundown = Array.from(data.rundown).filter((e) => e.id !== eventId);
await this.persist();
}
@@ -128,12 +129,12 @@ export class DataProvider {
return { ...data.userFields };
}
static getViews() {
return { ...data.views };
static getViewSettings() {
return { ...data.viewSettings };
}
static async setViews(newData) {
data.views = { ...newData };
static async setViewSettings(newData: ViewSettings) {
data.viewSettings = { ...newData };
await this.persist();
}
@@ -158,8 +159,9 @@ export class DataProvider {
static async mergeIntoData(newData) {
const mergedData = safeMerge(data, newData);
data.event = mergedData.event;
data.eventData = mergedData.event;
data.settings = mergedData.settings;
data.viewSettings = mergedData.viewSettings;
data.osc = mergedData.osc;
data.http = mergedData.http;
data.aliases = mergedData.aliases;
@@ -4,15 +4,13 @@
* @param {object} newData
*/
export function safeMerge(existing, newData) {
const { rundown, event, settings, osc, http, aliases, userFields } = newData || {};
const { rundown, event, settings, viewSettings, osc, http, aliases, userFields } = newData || {};
return {
...existing,
rundown: rundown ?? existing.rundown,
event: { ...existing.event, ...event },
settings: { ...existing.settings, ...settings },
views: {
overrideStyles: false,
},
viewSettings: { ...existing.viewSettings, ...viewSettings },
aliases: aliases ?? existing.aliases,
userFields: {
...existing.userFields,
@@ -1,12 +1,34 @@
import { DataProvider } from '../data-provider/DataProvider.ts';
import { DataProvider } from '../data-provider/DataProvider.js';
import { getRollTimers } from '../../services/rollUtils.js';
import { eventStore } from '../../stores/EventStore.js';
let instance;
type TitleBlock = {
titleNow: string | null;
subtitleNow: string | null;
presenterNow: string | null;
noteNow: string | null;
titleNext: string | null;
subtitleNext: string | null;
presenterNext: string | null;
noteNext: string | null;
};
/**
* Manages business logic around loading events
*/
export class EventLoader {
loadedEvent: object | null;
numEvents: number | null;
selectedEventIndex: number | null;
selectedEventId: string | null;
selectedPublicEventId: string | null;
nextEventId: string | null;
nextPublicEventId: string | null;
titles: TitleBlock;
titlesPublic: TitleBlock;
constructor() {
if (instance) {
throw new Error('There can be only one');
@@ -14,7 +36,7 @@ export class EventLoader {
// eslint-disable-next-line @typescript-eslint/no-this-alias -- this logic is used to ensure singleton
instance = this;
this.reset();
this.reset(false);
this.loadedEvent = null;
}
@@ -119,11 +141,7 @@ export class EventLoader {
*/
findNext() {
const timedEvents = EventLoader.getPlayableEvents();
if (
timedEvents === null ||
!timedEvents.length ||
this.selectedEventIndex === this.numEvents - 1
) {
if (timedEvents === null || !timedEvents.length || this.selectedEventIndex === this.numEvents - 1) {
return null;
}
@@ -145,15 +163,8 @@ export class EventLoader {
return null;
}
const {
nowIndex,
timers,
timeToNext,
nextEvent,
nextPublicEvent,
currentEvent,
currentPublicEvent,
} = getRollTimers(timedEvents, timeNow);
const { nowIndex, timers, timeToNext, nextEvent, nextPublicEvent, currentEvent, currentPublicEvent } =
getRollTimers(timedEvents, timeNow);
this.loadedEvent = currentEvent;
this.selectedEventIndex = nowIndex;
@@ -187,7 +198,10 @@ export class EventLoader {
};
}
reset() {
/**
* Resets instance state
*/
reset(emit?: boolean) {
this.loadedEvent = null;
this.selectedEventIndex = null;
this.selectedEventId = null;
@@ -209,10 +223,17 @@ export class EventLoader {
titleNow: null,
subtitleNow: null,
presenterNow: null,
noteNow: null,
titleNext: null,
subtitleNext: null,
presenterNext: null,
noteNext: null,
};
// workaround for socket not being ready in constructor
if (emit) {
this._loadEvent();
}
}
/**
@@ -236,16 +257,26 @@ export class EventLoader {
this._loadTitlesNow(event, playableEvents);
this._loadTitlesNext(playableEvents);
this._loadEvent();
return this.getLoaded();
}
/**
* Handle side effects from event loading
*/
private _loadEvent() {
eventStore.set('titles', this.titles);
eventStore.set('titlesPublic', this.titlesPublic);
}
/**
* @description loads given title (now)
* @private
* @param {object} event
* @param {array} rundown
*/
_loadTitlesNow(event, rundown) {
private _loadTitlesNow(event, rundown) {
// private title is always current
// check if current is also public
if (event.isPublic) {
@@ -276,8 +307,7 @@ export class EventLoader {
* @description look for next titles to load
* @private
*/
_loadTitlesNext(rundown) {
// Todo: is there a scenario where this gets called without an event?
private _loadTitlesNext(rundown) {
// maybe there is nothing to load
if (this.selectedEventIndex === null) return;
@@ -324,7 +354,7 @@ export class EventLoader {
* @param type
* @private
*/
_loadThisTitles(event, type) {
private _loadThisTitles(event, type) {
if (!event) {
return;
}
@@ -336,6 +366,7 @@ export class EventLoader {
this.titlesPublic.titleNow = event.title;
this.titlesPublic.subtitleNow = event.subtitle;
this.titlesPublic.presenterNow = event.presenter;
this.titlesPublic.noteNow = event.note;
this.selectedPublicEventId = event.id;
// private
@@ -350,6 +381,7 @@ export class EventLoader {
this.titlesPublic.titleNow = event.title;
this.titlesPublic.subtitleNow = event.subtitle;
this.titlesPublic.presenterNow = event.presenter;
this.titlesPublic.noteNow = event.note;
this.selectedPublicEventId = event.id;
break;
@@ -367,6 +399,7 @@ export class EventLoader {
this.titlesPublic.titleNext = event.title;
this.titlesPublic.subtitleNext = event.subtitle;
this.titlesPublic.presenterNext = event.presenter;
this.titlesPublic.noteNext = event.note;
this.nextPublicEventId = event.id;
// private
@@ -381,6 +414,7 @@ export class EventLoader {
this.titlesPublic.titleNext = event.title;
this.titlesPublic.subtitleNext = event.subtitle;
this.titlesPublic.presenterNext = event.presenter;
this.titlesPublic.noteNext = event.note;
this.nextPublicEventId = event.id;
break;
@@ -1,104 +0,0 @@
let instance;
class MessageService {
constructor() {
if (instance) {
throw new Error('There can be only one');
}
// eslint-disable-next-line @typescript-eslint/no-this-alias -- this logic is used to ensure singleton
instance = this;
this.socket = null;
this.presenter = {
text: '',
visible: false,
};
this.public = {
text: '',
visible: false,
};
this.lower = {
text: '',
visible: false,
};
this.onAir = false;
}
/**
* @description sets message on stage timer screen
* @param payload {string}
*/
setTimerText(payload) {
this.presenter.text = payload;
return this.getAll();
}
/**
* @description sets message visibility on stage timer screen
* @param status {boolean}
*/
setTimerVisibility(status) {
this.presenter.visible = status;
return this.getAll();
}
/**
* @description sets message on public screen
* @param payload {string}
*/
setPublicText(payload) {
this.public.text = payload;
return this.getAll();
}
/**
* @description sets message visibility on public screen
* @param status {boolean}
*/
setPublicVisibility(status) {
this.public.visible = status;
return this.getAll();
}
/**
* @description sets message on lower third screen
* @param payload {string}
*/
setLowerText(payload) {
this.lower.text = payload;
return this.getAll();
}
/**
* @description sets message visibility on lower third screen
* @param status {boolean}
*/
setLowerVisibility(status) {
this.lower.visible = status;
return this.getAll();
}
/**
* @description set state of onAir
* @param status {boolean}
*/
setOnAir(status) {
this.onAir = status;
return this.getAll();
}
/**
* @description Returns feature data
*/
getAll() {
return {
presenter: this.presenter,
public: this.public,
lower: this.lower,
onAir: this.onAir,
};
}
}
export const messageManager = new MessageService();
@@ -0,0 +1,109 @@
import { MessageControl } from 'ontime-types';
import { eventStore } from '../../stores/EventStore.js';
let instance;
class MessageService {
messages: MessageControl;
onAir: boolean;
constructor() {
if (instance) {
throw new Error('There can be only one');
}
// eslint-disable-next-line @typescript-eslint/no-this-alias -- this logic is used to ensure singleton
instance = this;
this.messages = {
presenter: {
text: '',
visible: false,
},
public: {
text: '',
visible: false,
},
lower: {
text: '',
visible: false,
},
};
this.onAir = false;
}
/**
* @description sets message on stage timer screen
*/
setTimerText(payload: string) {
this.messages.presenter.text = payload;
eventStore.set('feat-messagecontrol', { messages: this.messages });
return this.getAll();
}
/**
* @description sets message visibility on stage timer screen
*/
setTimerVisibility(status: boolean) {
this.messages.presenter.visible = status;
eventStore.set('feat-messagecontrol', { messages: this.messages });
return this.getAll();
}
/**
* @description sets message on public screen
*/
setPublicText(payload: string) {
this.messages.public.text = payload;
eventStore.set('feat-messagecontrol', { messages: this.messages });
return this.getAll();
}
/**
* @description sets message visibility on public screen
*/
setPublicVisibility(status: boolean) {
this.messages.public.visible = status;
eventStore.set('feat-messagecontrol', { messages: this.messages });
return this.getAll();
}
/**
* @description sets message on lower third screen
*/
setLowerText(payload: string) {
this.messages.lower.text = payload;
eventStore.set('feat-messagecontrol', { messages: this.messages });
return this.getAll();
}
/**
* @description sets message visibility on lower third screen
*/
setLowerVisibility(status: boolean) {
this.messages.lower.visible = status;
eventStore.set('feat-messagecontrol', { messages: this.messages });
return this.getAll();
}
/**
* @description set state of onAir
*/
setOnAir(status: boolean) {
this.onAir = status;
return this.getAll();
}
/**
* @description Returns feature data
*/
getAll() {
return {
messages: this.messages,
onAir: this.onAir,
};
}
}
export const messageManager = new MessageService();
@@ -6,8 +6,7 @@ import { stringFromMillis } from '../../utils/time.js';
import { messageManager } from '../message-manager/MessageManager.js';
import { PlaybackService } from '../../services/PlaybackService.js';
import { ADDRESS_MESSAGE_CONTROL } from './socketConfig.js';
import { eventTimer, TimerService } from '../../services/TimerService.ts';
import { eventTimer, TimerService } from '../../services/TimerService.js';
import { EventLoader, eventLoader } from '../event-loader/EventLoader.js';
class SocketController {
@@ -55,9 +54,7 @@ class SocketController {
// keep track of connections
this.numClients++;
this._clientNames[socket.id] = getRandomName();
const message = `${this.numClients} Clients with new connection: ${
this._clientNames[socket.id]
}`;
const message = `${this.numClients} Clients with new connection: ${this._clientNames[socket.id]}`;
this.info('CLIENT', message);
// Todo: review in favour of features
@@ -78,9 +75,7 @@ class SocketController {
*/
socket.on('disconnect', () => {
this.numClients--;
const message = `${this.numClients} Clients with disconnection: ${
this._clientNames[socket.id]
}`;
const message = `${this.numClients} Clients with disconnection: ${this._clientNames[socket.id]}`;
delete this._clientNames[socket.id];
this.info('CLIENT', message);
});
@@ -215,12 +210,10 @@ class SocketController {
try {
const featureData = messageManager.setOnAir(data);
this.info('PLAYBACK', featureData.onAir ? 'Going On Air' : 'Going Off Air');
this.socket.emit(ADDRESS_MESSAGE_CONTROL, featureData);
} catch (error) {
this.error('RX', `Failed to parse message ${data} : ${error}`);
}
}
this.send('onAir', messageManager.onAir);
});
// Presenter message
@@ -228,16 +221,14 @@ class SocketController {
if (typeof data !== 'string') {
return;
}
const featureData = messageManager.setTimerText(data);
this.socket.emit(ADDRESS_MESSAGE_CONTROL, featureData);
messageManager.setTimerText(data);
});
socket.on('set-timer-message-visible', (data) => {
if (typeof data !== 'boolean') {
return;
}
const featureData = messageManager.setTimerVisibility(data);
this.socket.emit(ADDRESS_MESSAGE_CONTROL, featureData);
messageManager.setTimerVisibility(data);
});
/*******************************************/
@@ -246,16 +237,14 @@ class SocketController {
if (typeof data !== 'string') {
return;
}
const featureData = messageManager.setPublicText(data);
this.socket.emit(ADDRESS_MESSAGE_CONTROL, featureData);
messageManager.setPublicText(data);
});
socket.on('set-public-message-visible', (data) => {
if (typeof data !== 'boolean') {
return;
}
const featureData = messageManager.setPublicVisibility(data);
this.socket.emit(ADDRESS_MESSAGE_CONTROL, featureData);
messageManager.setPublicVisibility(data);
});
/*******************************************/
@@ -264,16 +253,14 @@ class SocketController {
if (typeof data !== 'string') {
return;
}
const featureData = messageManager.setLowerText(data);
this.socket.emit(ADDRESS_MESSAGE_CONTROL, featureData);
messageManager.setLowerText(data);
});
socket.on('set-lower-message-visible', (data) => {
if (typeof data !== 'boolean') {
return;
}
const featureData = messageManager.setLowerVisibility(data);
this.socket.emit(ADDRESS_MESSAGE_CONTROL, featureData);
messageManager.setLowerVisibility(data);
});
/* MOLECULAR ENDPOINTS
@@ -312,8 +299,9 @@ class SocketController {
});
// 6. TIMER
socket.on('get-ontime-timer', () => {
this.broadcastTimer();
socket.on('get-timer', () => {
// TODO: Not ideal workaround
socket.emit('timer', eventTimer.timer);
});
});
}
@@ -373,7 +361,7 @@ class SocketController {
*/
broadcastFeatureMessageControl() {
const featureData = messageManager.getAll();
this.send(ADDRESS_MESSAGE_CONTROL, featureData);
this.send('feat-messagecontrol', featureData);
}
/**
@@ -416,21 +404,14 @@ class SocketController {
this.send('feat-cuesheet', featureData);
}
/**
* Broadcast Timer feature
*/
broadcastTimer() {
const featureData = eventTimer.timer;
this.send('ontime-timer', featureData);
}
// TODO: ouch, services should update the store
// make middleware to maintain the features OR remove the feature endpoints
broadcastState() {
this.broadcastFeatureRundown();
this.broadcastFeatureMessageControl();
this.broadcastFeaturePlaybackControl();
this.broadcastFeatureInfo();
this.broadcastFeatureCuesheet();
this.broadcastTimer();
}
/**
@@ -1 +0,0 @@
export const ADDRESS_MESSAGE_CONTROL = 'feat-messagecontrol';