mirror of
https://github.com/cpvalente/ontime.git
synced 2026-08-17 13:23:35 +00:00
V3 (#657)
* refactor: cleanup routes * style: smaller base font * chore: upgrade dependencies * chore: lock node version to electron * refactor: pass HTTP to integration controller (#652) * refactor: deprecate onair control * refactor: remove playback router * Several project files user folder (#617) * chore: automated screenshots (#667) * feat: app settings (#658) * refactor: remove deprecated event data (#674) * Studio clock (#663) --------- Co-authored-by: Carlos Valente <carlosvalente@pm.me> * Feat: reorder events with alt+ctrl + arrow up/down (#645) * Warning and danger per event (#677) --------- Co-authored-by: Fabian Posenau <fabian@fphome.de> * refactor: stabilise actionHandler (#683) Co-authored-by: Fabian Posenau <fabian@fphome.de> * improvement: hide seconds (#675) * wip: overview (#688) * fix: focus cursor (#695) * refactor: update lower third (#665) * Refactor/time formatting (#696) --------- Co-authored-by: Carlos Valente <34649812+cpvalente@users.noreply.github.com> Co-authored-by: Carlos Valente <carlosvalente@pm.me> * feat: multiple selection (#703) --------- Co-authored-by: asharonbaltazar <asharonbaltazar@outlook.com> Co-authored-by: Alex <ac@omnivox.dk> * fix: test - go to `Edit mode` befor tying to click `Event options` button (#708) * refactor: runtime service (#715) * fix: issue with loosing cursor position on message (#719) * remove info panel (#721) * Event editor continue (#722) * update API - part (#709) --------- Co-authored-by: Carlos Valente <34649812+cpvalente@users.noreply.github.com> Co-authored-by: Carlos Valente <carlosvalente@pm.me> * refactor: update timers (#729) * feat: many timers (#706) --------- Co-authored-by: arc-alex <ac@omnivox.dk> * refactor: excel cleanup (#734) * refactor: allow import of blocks and skip import (#735) * Project manager (#697) * refactor: UI for linking events (#763) * upgraded pipeline actions (#777) * Over under (#771) * custom fields (#744) --------- Co-authored-by: Carlos Valente <carlosvalente@pm.me> * Sheets settings (#774) --------- Co-authored-by: arc-alex <ac@omnivox.dk> * style: tweaks to lower thirds (#785) * refactor: delays account for gaps (#784) * refactor: partial state updates (#780) * feat: generate crash report (#787) * Sheet use limited input device auth flow (#782) --------- Co-authored-by: cv <34649812+cpvalente@users.noreply.github.com> Co-authored-by: Carlos Valente <carlosvalente@pm.me> * Custom fields views (#789) * refactor: deprecate presenter and subtitle (#795) * refactor: organise API around resources (#798) --------- Co-authored-by: Bianca Procopio <biancahprocopio@gmail.com> * Time to end (#804) * Skip fixes (#805) * fix: onair derives from playback * Param nav (#822) --------- Co-authored-by: Alex Christoffer Rasmussen <ac@omnivox.dk> * refactor: download files from interface (#831) * Quick options (#814) * End pause (#832) * chore: bump node version in docker (#834) * refactor: follow in run mode (#840) * fix: uncaught error in http integration (#837) * Apply project (#843) Co-authored-by: Matteo Gheza <matteo.gheza07@gmail.com> Co-authored-by: Ary <arylmoraesn@gmail.com> Co-authored-by: Alex Christoffer Rasmussen <ac@omnivox.dk> Co-authored-by: Fabian Posenau <19673098+kellhogs@users.noreply.github.com> Co-authored-by: Fabian Posenau <fabian@fphome.de> Co-authored-by: Alex Rohleder <alexrohleder96@gmail.com> Co-authored-by: asharonbaltazar <asharonbaltazar@outlook.com> Co-authored-by: Bianca Procopio <biancahprocopio@gmail.com> Co-authored-by: Fabian Posenau <fabianpos99+github@gmail.com>
This commit is contained in:
@@ -1,18 +1,16 @@
|
||||
import { Message } from 'ontime-types';
|
||||
import { DeepPartial, Message, TimerMessage, MessageState } from 'ontime-types';
|
||||
|
||||
import { TimerMessage } from 'ontime-types/src/definitions/runtime/MessageControl.type.js';
|
||||
import { throttle } from '../../utils/throttle.js';
|
||||
|
||||
import type { PublishFn } from '../../stores/EventStore.js';
|
||||
|
||||
let instance;
|
||||
let instance: MessageService | null = null;
|
||||
|
||||
class MessageService {
|
||||
timerMessage: TimerMessage;
|
||||
publicMessage: Message;
|
||||
lowerMessage: Message;
|
||||
externalMessage: Message;
|
||||
onAir: boolean;
|
||||
timer: TimerMessage;
|
||||
public: Message;
|
||||
lower: Message;
|
||||
external: Message;
|
||||
|
||||
private throttledSet: PublishFn;
|
||||
private publish: PublishFn | null;
|
||||
@@ -25,165 +23,62 @@ class MessageService {
|
||||
// eslint-disable-next-line @typescript-eslint/no-this-alias -- this logic is used to ensure singleton
|
||||
instance = this;
|
||||
|
||||
this.timerMessage = {
|
||||
text: '',
|
||||
visible: false,
|
||||
timerBlink: false,
|
||||
timerBlackout: false,
|
||||
};
|
||||
|
||||
this.publicMessage = {
|
||||
text: '',
|
||||
visible: false,
|
||||
};
|
||||
|
||||
this.lowerMessage = {
|
||||
text: '',
|
||||
visible: false,
|
||||
};
|
||||
|
||||
this.externalMessage = {
|
||||
text: '',
|
||||
visible: false,
|
||||
};
|
||||
|
||||
this.onAir = false;
|
||||
this.throttledSet = () => {
|
||||
throw new Error('Published called before initialisation');
|
||||
};
|
||||
|
||||
this.clear();
|
||||
}
|
||||
|
||||
clear() {
|
||||
this.timer = {
|
||||
text: '',
|
||||
visible: false,
|
||||
blink: false,
|
||||
blackout: false,
|
||||
};
|
||||
|
||||
this.public = {
|
||||
text: '',
|
||||
visible: false,
|
||||
};
|
||||
|
||||
this.lower = {
|
||||
text: '',
|
||||
visible: false,
|
||||
};
|
||||
|
||||
this.external = {
|
||||
text: '',
|
||||
visible: false,
|
||||
};
|
||||
}
|
||||
|
||||
init(publish: PublishFn) {
|
||||
this.publish = publish;
|
||||
this.throttledSet = throttle((key, value) => this.publish(key, value), 100);
|
||||
this.throttledSet = throttle((key, value) => this.publish?.(key, value), 100);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description sets message on stage timer screen
|
||||
*/
|
||||
setExternalText(payload: string) {
|
||||
if (this.externalMessage.text !== payload) {
|
||||
this.externalMessage.text = payload;
|
||||
this.throttledSet('externalMessage', this.externalMessage);
|
||||
}
|
||||
return this.getAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* @description sets message visibility on stage timer screen
|
||||
*/
|
||||
setExternalVisibility(status: boolean) {
|
||||
this.externalMessage.visible = status;
|
||||
this.throttledSet('externalMessage', this.externalMessage);
|
||||
return this.getAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* @description sets message on stage timer screen
|
||||
*/
|
||||
setTimerText(payload: string) {
|
||||
this.timerMessage.text = payload;
|
||||
this.throttledSet('timerMessage', this.timerMessage);
|
||||
return this.getAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* @description sets message visibility on stage timer screen
|
||||
*/
|
||||
setTimerVisibility(status: boolean) {
|
||||
this.timerMessage.visible = status;
|
||||
this.throttledSet('timerMessage', this.timerMessage);
|
||||
return this.getAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* @description sets message on public screen
|
||||
*/
|
||||
setPublicText(payload: string) {
|
||||
this.publicMessage.text = payload;
|
||||
this.throttledSet('publicMessage', this.publicMessage);
|
||||
return this.getAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* @description sets message visibility on public screen
|
||||
*/
|
||||
setPublicVisibility(status: boolean) {
|
||||
this.publicMessage.visible = status;
|
||||
this.throttledSet('publicMessage', this.publicMessage);
|
||||
return this.getAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* @description sets message on lower third screen
|
||||
*/
|
||||
setLowerText(payload: string) {
|
||||
this.lowerMessage.text = payload;
|
||||
this.throttledSet('lowerMessage', this.lowerMessage);
|
||||
return this.getAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* @description sets message visibility on lower third screen
|
||||
*/
|
||||
setLowerVisibility(status: boolean) {
|
||||
this.lowerMessage.visible = status;
|
||||
this.throttledSet('lowerMessage', this.lowerMessage);
|
||||
return this.getAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* @description set state of onAir, toggles if parameters are offered
|
||||
*/
|
||||
setOnAir(status?: boolean) {
|
||||
if (typeof status === 'undefined') {
|
||||
this.onAir = !this.onAir;
|
||||
} else {
|
||||
this.onAir = status;
|
||||
}
|
||||
this.throttledSet('onAir', this.onAir);
|
||||
return this.getAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* @description set state of timer blink, toggles if parameters are offered
|
||||
*/
|
||||
|
||||
setTimerBlink(status?: boolean) {
|
||||
if (typeof status === 'undefined') {
|
||||
this.timerMessage.timerBlink = !this.timerMessage.timerBlink;
|
||||
} else {
|
||||
this.timerMessage.timerBlink = status;
|
||||
}
|
||||
this.throttledSet('timerMessage', this.timerMessage);
|
||||
return this.getAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* @description set state of timer blackout, toggles if parameters are offered
|
||||
*/
|
||||
|
||||
setTimerBlackout(status?: boolean) {
|
||||
if (typeof status === 'undefined') {
|
||||
this.timerMessage.timerBlackout = !this.timerMessage.timerBlackout;
|
||||
} else {
|
||||
this.timerMessage.timerBlackout = status;
|
||||
}
|
||||
this.throttledSet('timerMessage', this.timerMessage);
|
||||
return this.getAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Returns feature data
|
||||
*/
|
||||
getAll() {
|
||||
getState(): MessageState {
|
||||
return {
|
||||
timerMessage: this.timerMessage,
|
||||
publicMessage: this.publicMessage,
|
||||
lowerMessage: this.lowerMessage,
|
||||
onAir: this.onAir,
|
||||
timer: this.timer,
|
||||
public: this.public,
|
||||
lower: this.lower,
|
||||
external: this.external,
|
||||
};
|
||||
}
|
||||
|
||||
patch(message: DeepPartial<MessageState>) {
|
||||
if (message.timer) this.timer = { ...this.timer, ...message.timer };
|
||||
if (message.public) this.public = { ...this.public, ...message.public };
|
||||
if (message.lower) this.lower = { ...this.lower, ...message.lower };
|
||||
if (message.external) this.external = { ...this.external, ...message.external };
|
||||
|
||||
const newState = this.getState();
|
||||
|
||||
this.throttledSet('message', newState);
|
||||
return newState;
|
||||
}
|
||||
}
|
||||
|
||||
export const messageService = new MessageService();
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
import { messageService } from '../MessageService.js';
|
||||
|
||||
describe('MessageService', () => {
|
||||
const publishFunction = () => {};
|
||||
|
||||
beforeAll(() => {
|
||||
messageService.init(publishFunction);
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
messageService.clear();
|
||||
});
|
||||
|
||||
it('should patch the message state', () => {
|
||||
const message = {
|
||||
timer: { text: 'new text', visible: true },
|
||||
public: { text: 'public text', visible: false },
|
||||
lower: { text: 'lower text' },
|
||||
external: { visible: true },
|
||||
};
|
||||
|
||||
const newState = messageService.patch(message);
|
||||
|
||||
expect(newState).toEqual({
|
||||
timer: { text: 'new text', visible: true, blackout: false, blink: false },
|
||||
public: { text: 'public text', visible: false },
|
||||
lower: {
|
||||
text: 'lower text',
|
||||
visible: false,
|
||||
},
|
||||
external: {
|
||||
text: '',
|
||||
visible: true,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should not affect other properties when patching', () => {
|
||||
const initialMessage = {
|
||||
timer: { text: 'initial text', visible: true },
|
||||
public: { text: 'public text', visible: false },
|
||||
};
|
||||
|
||||
const newState = messageService.patch(initialMessage);
|
||||
|
||||
expect(newState).toEqual({
|
||||
timer: { text: 'initial text', visible: true, blackout: false, blink: false },
|
||||
public: { text: 'public text', visible: false },
|
||||
lower: {
|
||||
text: '',
|
||||
visible: false,
|
||||
},
|
||||
external: {
|
||||
text: '',
|
||||
visible: false,
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,55 @@
|
||||
import { validateMessage, validateTimerMessage } from '../messageUtils.js';
|
||||
|
||||
describe('validateMessage()', () => {
|
||||
it('returns a valid Message object', () => {
|
||||
const payload = {
|
||||
text: '12312',
|
||||
visible: 'true',
|
||||
};
|
||||
const expected = {
|
||||
text: '12312',
|
||||
visible: true,
|
||||
};
|
||||
|
||||
expect(validateMessage(payload)).toEqual(expected);
|
||||
});
|
||||
it('skips keys not given', () => {
|
||||
const payload = {
|
||||
visible: 'true',
|
||||
};
|
||||
const expected = {
|
||||
visible: true,
|
||||
};
|
||||
|
||||
expect(validateMessage(payload)).toStrictEqual(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe('validateTimerMessage()', () => {
|
||||
it('returns a valid Timer Message object', () => {
|
||||
const payload = {
|
||||
text: '12312',
|
||||
visible: 'true',
|
||||
blink: 'true',
|
||||
blackout: 'true',
|
||||
};
|
||||
const expected = {
|
||||
text: '12312',
|
||||
visible: true,
|
||||
blink: true,
|
||||
blackout: true,
|
||||
};
|
||||
|
||||
expect(validateTimerMessage(payload)).toEqual(expected);
|
||||
});
|
||||
it('skips keys not given', () => {
|
||||
const payload = {
|
||||
visible: 'true',
|
||||
};
|
||||
const expected = {
|
||||
visible: true,
|
||||
};
|
||||
|
||||
expect(validateTimerMessage(payload)).toStrictEqual(expected);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,35 @@
|
||||
import { Message, TimerMessage } from 'ontime-types';
|
||||
|
||||
import * as assert from '../../utils/assert.js';
|
||||
import { coerceBoolean, coerceString } from '../../utils/coerceType.js';
|
||||
|
||||
/**
|
||||
* Creates a valid Message object from a payload
|
||||
* @throws if the payload is not an object
|
||||
*/
|
||||
export function validateMessage(message: unknown): Partial<Message> {
|
||||
assert.isObject(message);
|
||||
|
||||
const result: Partial<Message> = {};
|
||||
if ('text' in message) result.text = coerceString(message.text);
|
||||
if ('visible' in message) result.visible = coerceBoolean(message.visible);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a valid Timer Message object from a payload
|
||||
* @throws if the payload is not an object
|
||||
*/
|
||||
export function validateTimerMessage(message: unknown): Partial<TimerMessage> {
|
||||
assert.isObject(message);
|
||||
|
||||
const result: Partial<TimerMessage> = {};
|
||||
|
||||
if ('text' in message) result.text = coerceString(message.text);
|
||||
if ('visible' in message) result.visible = coerceBoolean(message.visible);
|
||||
if ('blink' in message) result.blink = coerceBoolean(message.blink);
|
||||
if ('blackout' in message) result.blackout = coerceBoolean(message.blackout);
|
||||
|
||||
return result;
|
||||
}
|
||||
Reference in New Issue
Block a user