mirror of
https://github.com/cpvalente/ontime.git
synced 2026-08-11 10:23:54 +00:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e6e1c38355 |
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@getontime/cli",
|
"name": "@getontime/cli",
|
||||||
"version": "3.14.5",
|
"version": "3.14.3",
|
||||||
"author": "Carlos Valente",
|
"author": "Carlos Valente",
|
||||||
"description": "Time keeping for live events",
|
"description": "Time keeping for live events",
|
||||||
"repository": "https://github.com/cpvalente/ontime",
|
"repository": "https://github.com/cpvalente/ontime",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "ontime-ui",
|
"name": "ontime-ui",
|
||||||
"version": "3.14.5",
|
"version": "3.14.3",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|||||||
@@ -4,10 +4,10 @@ import type { MultiselectOptions, ParamField } from './types';
|
|||||||
|
|
||||||
export const makeOptionsFromCustomFields = (
|
export const makeOptionsFromCustomFields = (
|
||||||
customFields: CustomFields,
|
customFields: CustomFields,
|
||||||
additionalOptions: Readonly<Record<string, string>> = {},
|
additionalOptions: Record<string, string> = {},
|
||||||
filterImageType = true,
|
filterImageType = true,
|
||||||
) => {
|
) => {
|
||||||
const options = { ...additionalOptions };
|
const options = structuredClone(additionalOptions);
|
||||||
for (const [key, value] of Object.entries(customFields)) {
|
for (const [key, value] of Object.entries(customFields)) {
|
||||||
if (filterImageType && value.type === 'image') {
|
if (filterImageType && value.type === 'image') {
|
||||||
continue;
|
continue;
|
||||||
|
|||||||
@@ -48,12 +48,10 @@ export const connectSocket = () => {
|
|||||||
|
|
||||||
websocket.onclose = () => {
|
websocket.onclose = () => {
|
||||||
console.warn('WebSocket disconnected');
|
console.warn('WebSocket disconnected');
|
||||||
|
setOnlineStatus(false);
|
||||||
|
|
||||||
if (shouldReconnect) {
|
if (shouldReconnect) {
|
||||||
reconnectTimeout = setTimeout(() => {
|
reconnectTimeout = setTimeout(() => {
|
||||||
if (reconnectAttempts > 2) {
|
|
||||||
setOnlineStatus(false);
|
|
||||||
}
|
|
||||||
console.warn('WebSocket: attempting reconnect');
|
console.warn('WebSocket: attempting reconnect');
|
||||||
if (websocket && websocket.readyState === WebSocket.CLOSED) {
|
if (websocket && websocket.readyState === WebSocket.CLOSED) {
|
||||||
reconnectAttempts += 1;
|
reconnectAttempts += 1;
|
||||||
|
|||||||
+1
-1
@@ -94,7 +94,7 @@ export default function ReportSettings() {
|
|||||||
})();
|
})();
|
||||||
return (
|
return (
|
||||||
<tr key={entry.index}>
|
<tr key={entry.index}>
|
||||||
<th>{entry.index}</th>
|
<th>{entry.index + 1}</th>
|
||||||
<th>{entry.cue}</th>
|
<th>{entry.cue}</th>
|
||||||
<th>{entry.title}</th>
|
<th>{entry.title}</th>
|
||||||
<th className={cx([start && style[start]])}>{formatTime(entry.scheduledStart)}</th>
|
<th className={cx([start && style[start]])}>{formatTime(entry.scheduledStart)}</th>
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
import { PropsWithChildren, useEffect, useRef, useState } from 'react';
|
import { useEffect, useRef } from 'react';
|
||||||
|
import { IoEye, IoEyeOffOutline } from 'react-icons/io5';
|
||||||
import { Input } from '@chakra-ui/react';
|
import { Input } from '@chakra-ui/react';
|
||||||
|
|
||||||
import { cx } from '../../../common/utils/styleUtils';
|
import TooltipActionBtn from '../../../common/components/buttons/TooltipActionBtn';
|
||||||
|
import { tooltipDelayMid } from '../../../ontimeConfig';
|
||||||
|
|
||||||
import style from './InputRow.module.scss';
|
import style from './InputRow.module.scss';
|
||||||
|
|
||||||
@@ -10,53 +12,50 @@ interface InputRowProps {
|
|||||||
placeholder: string;
|
placeholder: string;
|
||||||
text: string;
|
text: string;
|
||||||
visible: boolean;
|
visible: boolean;
|
||||||
|
actionHandler: () => void;
|
||||||
changeHandler: (newValue: string) => void;
|
changeHandler: (newValue: string) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function InputRow(props: PropsWithChildren<InputRowProps>) {
|
export default function InputRow(props: InputRowProps) {
|
||||||
const { label, placeholder, text, visible, changeHandler, children } = props;
|
const { label, placeholder, text, visible, actionHandler, changeHandler } = props;
|
||||||
|
|
||||||
const [value, setValue] = useState(text);
|
|
||||||
const inputRef = useRef<HTMLInputElement>(null);
|
const inputRef = useRef<HTMLInputElement>(null);
|
||||||
const cursorPositionRef = useRef(0);
|
const cursorPositionRef = useRef(0);
|
||||||
|
|
||||||
// sync cursor position with text
|
// sync cursor position with text
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (inputRef.current && inputRef.current !== document.activeElement) {
|
if (inputRef.current) {
|
||||||
inputRef.current.selectionStart = cursorPositionRef.current;
|
inputRef.current.selectionStart = cursorPositionRef.current;
|
||||||
inputRef.current.selectionEnd = cursorPositionRef.current;
|
inputRef.current.selectionEnd = cursorPositionRef.current;
|
||||||
}
|
}
|
||||||
}, [text]);
|
}, [text]);
|
||||||
|
|
||||||
// synchronise external text
|
|
||||||
useEffect(() => {
|
|
||||||
if (inputRef.current !== document.activeElement) {
|
|
||||||
setValue(text);
|
|
||||||
}
|
|
||||||
}, [text]);
|
|
||||||
|
|
||||||
const handleInputChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
const handleInputChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
cursorPositionRef.current = event.target.selectionStart ?? 0;
|
cursorPositionRef.current = event.target.selectionStart ?? 0;
|
||||||
setValue(event.target.value);
|
|
||||||
changeHandler(event.target.value);
|
changeHandler(event.target.value);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={style.inputRow}>
|
<div className={style.inputRow}>
|
||||||
<label className={cx([style.label, visible ?? style.active])} htmlFor={label}>
|
<label className={`${style.label} ${visible ? style.active : ''}`}>{label}</label>
|
||||||
{label}
|
|
||||||
</label>
|
|
||||||
<div className={style.inputItems}>
|
<div className={style.inputItems}>
|
||||||
<Input
|
<Input
|
||||||
id={label}
|
|
||||||
ref={inputRef}
|
ref={inputRef}
|
||||||
size='sm'
|
size='sm'
|
||||||
variant='ontime-filled'
|
variant='ontime-filled'
|
||||||
value={value}
|
value={text}
|
||||||
onChange={handleInputChange}
|
onChange={handleInputChange}
|
||||||
placeholder={placeholder}
|
placeholder={placeholder}
|
||||||
/>
|
/>
|
||||||
{children}
|
<TooltipActionBtn
|
||||||
|
clickHandler={actionHandler}
|
||||||
|
tooltip={visible ? 'Make invisible' : 'Make visible'}
|
||||||
|
aria-label={`Toggle ${label}`}
|
||||||
|
openDelay={tooltipDelayMid}
|
||||||
|
icon={visible ? <IoEye size='18px' /> : <IoEyeOffOutline size='18px' />}
|
||||||
|
variant={visible ? 'ontime-filled' : 'ontime-subtle'}
|
||||||
|
size='sm'
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,8 +1,4 @@
|
|||||||
import { IoEye, IoEyeOffOutline } from 'react-icons/io5';
|
|
||||||
|
|
||||||
import TooltipActionBtn from '../../../common/components/buttons/TooltipActionBtn';
|
|
||||||
import { setMessage, useExternalMessageInput, useTimerMessageInput } from '../../../common/hooks/useSocket';
|
import { setMessage, useExternalMessageInput, useTimerMessageInput } from '../../../common/hooks/useSocket';
|
||||||
import { tooltipDelayMid } from '../../../ontimeConfig';
|
|
||||||
|
|
||||||
import InputRow from './InputRow';
|
import InputRow from './InputRow';
|
||||||
import TimerControlsPreview from './TimerViewControl';
|
import TimerControlsPreview from './TimerViewControl';
|
||||||
@@ -27,17 +23,8 @@ function TimerMessageInput() {
|
|||||||
text={text}
|
text={text}
|
||||||
visible={visible}
|
visible={visible}
|
||||||
changeHandler={(newValue) => setMessage.timerText(newValue)}
|
changeHandler={(newValue) => setMessage.timerText(newValue)}
|
||||||
>
|
actionHandler={() => setMessage.timerVisible(!visible)}
|
||||||
<TooltipActionBtn
|
/>
|
||||||
clickHandler={() => setMessage.timerVisible(!visible)}
|
|
||||||
tooltip={visible ? 'Make invisible' : 'Make visible'}
|
|
||||||
aria-label='Toggle timer message visibility'
|
|
||||||
openDelay={tooltipDelayMid}
|
|
||||||
icon={visible ? <IoEye size='18px' /> : <IoEyeOffOutline size='18px' />}
|
|
||||||
variant={visible ? 'ontime-filled' : 'ontime-subtle'}
|
|
||||||
size='sm'
|
|
||||||
/>
|
|
||||||
</InputRow>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -59,16 +46,7 @@ function ExternalInput() {
|
|||||||
text={text}
|
text={text}
|
||||||
visible={visible}
|
visible={visible}
|
||||||
changeHandler={(newValue) => setMessage.externalText(newValue)}
|
changeHandler={(newValue) => setMessage.externalText(newValue)}
|
||||||
>
|
actionHandler={toggleExternal}
|
||||||
<TooltipActionBtn
|
/>
|
||||||
clickHandler={toggleExternal}
|
|
||||||
tooltip={visible ? 'Make invisible' : 'Make visible'}
|
|
||||||
aria-label='Toggle external message visibility'
|
|
||||||
openDelay={tooltipDelayMid}
|
|
||||||
icon={visible ? <IoEye size='18px' /> : <IoEyeOffOutline size='18px' />}
|
|
||||||
variant={visible ? 'ontime-filled' : 'ontime-subtle'}
|
|
||||||
size='sm'
|
|
||||||
/>
|
|
||||||
</InputRow>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -300,8 +300,7 @@ export default function Rundown({ data }: RundownProps) {
|
|||||||
isNextDay = checkIsNextDay(entry, lastEvent);
|
isNextDay = checkIsNextDay(entry, lastEvent);
|
||||||
if (!isPast) {
|
if (!isPast) {
|
||||||
totalGap += entry.gap;
|
totalGap += entry.gap;
|
||||||
// We also include countToEnd in this test as the behavior of a linked event coming after a countToEnd is simelar to an unlinked event
|
isLinkedToLoaded = isLinkedToLoaded && entry.linkStart !== null;
|
||||||
isLinkedToLoaded = isLinkedToLoaded && entry.linkStart !== null && !lastEvent?.countToEnd;
|
|
||||||
}
|
}
|
||||||
if (isNewLatest(entry, lastEvent)) {
|
if (isNewLatest(entry, lastEvent)) {
|
||||||
// populate previous entry
|
// populate previous entry
|
||||||
|
|||||||
@@ -140,7 +140,7 @@ export const ScheduleProvider = ({
|
|||||||
let selectedEventIndex = events.findIndex((event) => event.id === selectedEventId);
|
let selectedEventIndex = events.findIndex((event) => event.id === selectedEventId);
|
||||||
|
|
||||||
// we want to show the event after the current
|
// we want to show the event after the current
|
||||||
const viewEvents = events.slice(selectedEventIndex + 1);
|
const viewEvents = events.toSpliced(0, selectedEventIndex + 1);
|
||||||
selectedEventIndex = 0;
|
selectedEventIndex = 0;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "ontime-electron",
|
"name": "ontime-electron",
|
||||||
"version": "3.14.5",
|
"version": "3.14.3",
|
||||||
"author": "Carlos Valente",
|
"author": "Carlos Valente",
|
||||||
"description": "Time keeping for live events",
|
"description": "Time keeping for live events",
|
||||||
"repository": "https://github.com/cpvalente/ontime",
|
"repository": "https://github.com/cpvalente/ontime",
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
"name": "ontime-server",
|
"name": "ontime-server",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "src/index.ts",
|
"main": "src/index.ts",
|
||||||
"version": "3.14.5",
|
"version": "3.14.3",
|
||||||
"exports": "./src/index.js",
|
"exports": "./src/index.js",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@googleapis/sheets": "^5.0.5",
|
"@googleapis/sheets": "^5.0.5",
|
||||||
|
|||||||
@@ -124,6 +124,6 @@ function setSessionCookie(res: Response, token: string) {
|
|||||||
httpOnly: false, // allow websocket to access cookie
|
httpOnly: false, // allow websocket to access cookie
|
||||||
secure: true,
|
secure: true,
|
||||||
path: '/', // allow cookie to be accessed from any path
|
path: '/', // allow cookie to be accessed from any path
|
||||||
sameSite: 'none', // allow cookies to be sent in cross-origin requests (e.g., iframes)
|
sameSite: 'strict',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -108,7 +108,7 @@ export async function deleteEvent(eventIds: string[]) {
|
|||||||
const scopedMutation = cache.mutateCache(cache.remove);
|
const scopedMutation = cache.mutateCache(cache.remove);
|
||||||
const { didMutate } = await scopedMutation({ eventIds });
|
const { didMutate } = await scopedMutation({ eventIds });
|
||||||
|
|
||||||
if (!didMutate) {
|
if (didMutate === false) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -146,7 +146,7 @@ export async function editEvent(patch: PatchWithId) {
|
|||||||
const { newEvent, didMutate } = await scopedMutation({ patch, eventId: patch.id });
|
const { newEvent, didMutate } = await scopedMutation({ patch, eventId: patch.id });
|
||||||
|
|
||||||
// short circuit if nothing changed
|
// short circuit if nothing changed
|
||||||
if (!didMutate) {
|
if (didMutate === false) {
|
||||||
return newEvent;
|
return newEvent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import type { Server } from 'http';
|
|||||||
import { networkInterfaces } from 'os';
|
import { networkInterfaces } from 'os';
|
||||||
import type { AddressInfo } from 'net';
|
import type { AddressInfo } from 'net';
|
||||||
|
|
||||||
import { isDocker, isOntimeCloud, isProduction } from '../externals.js';
|
import { isDocker, isProduction } from '../externals.js';
|
||||||
import { logger } from '../classes/Logger.js';
|
import { logger } from '../classes/Logger.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -42,10 +42,6 @@ export function getNetworkInterfaces(): { name: string; address: string }[] {
|
|||||||
* @throws any other server errors will result in a throw
|
* @throws any other server errors will result in a throw
|
||||||
*/
|
*/
|
||||||
export function serverTryDesiredPort(server: Server, desiredPort: number): Promise<number> {
|
export function serverTryDesiredPort(server: Server, desiredPort: number): Promise<number> {
|
||||||
if (isOntimeCloud) {
|
|
||||||
return forceCloudPort(server);
|
|
||||||
}
|
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
server.once('error', (error) => {
|
server.once('error', (error) => {
|
||||||
// we should only move ports if we are in a desktop environment
|
// we should only move ports if we are in a desktop environment
|
||||||
@@ -88,19 +84,6 @@ export function serverTryDesiredPort(server: Server, desiredPort: number): Promi
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function forceCloudPort(server: Server): Promise<number> {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
server.listen(4001, '0.0.0.0', () => {
|
|
||||||
const address = server.address();
|
|
||||||
if (!isAddressInfo(address)) {
|
|
||||||
reject(new Error('Unknown port type, unable to proceed'));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
resolve(address.port);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Guard verifies that the given address is a usable AddressInfo object
|
* Guard verifies that the given address is a usable AddressInfo object
|
||||||
*/
|
*/
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "ontime",
|
"name": "ontime",
|
||||||
"version": "3.14.5",
|
"version": "3.14.3",
|
||||||
"description": "Time keeping for live events",
|
"description": "Time keeping for live events",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"ontime",
|
"ontime",
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ export function insertAtIndex<T>(index: number, item: T, array: T[]): T[] {
|
|||||||
* @param array
|
* @param array
|
||||||
*/
|
*/
|
||||||
export function deleteAtIndex<T>(index: number, array: T[]) {
|
export function deleteAtIndex<T>(index: number, array: T[]) {
|
||||||
return array.filter((_, i) => i !== index);
|
return array.toSpliced(index, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in New Issue
Block a user