Compare commits

..

1 Commits

Author SHA1 Message Date
Carlos Valente e6e1c38355 refactor: UI indexes are 1 based 2025-03-28 22:18:22 +01:00
16 changed files with 39 additions and 82 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@getontime/cli",
"version": "3.14.5",
"version": "3.14.3",
"author": "Carlos Valente",
"description": "Time keeping for live events",
"repository": "https://github.com/cpvalente/ontime",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "ontime-ui",
"version": "3.14.5",
"version": "3.14.3",
"private": true,
"type": "module",
"dependencies": {
@@ -4,10 +4,10 @@ import type { MultiselectOptions, ParamField } from './types';
export const makeOptionsFromCustomFields = (
customFields: CustomFields,
additionalOptions: Readonly<Record<string, string>> = {},
additionalOptions: Record<string, string> = {},
filterImageType = true,
) => {
const options = { ...additionalOptions };
const options = structuredClone(additionalOptions);
for (const [key, value] of Object.entries(customFields)) {
if (filterImageType && value.type === 'image') {
continue;
+1 -3
View File
@@ -48,12 +48,10 @@ export const connectSocket = () => {
websocket.onclose = () => {
console.warn('WebSocket disconnected');
setOnlineStatus(false);
if (shouldReconnect) {
reconnectTimeout = setTimeout(() => {
if (reconnectAttempts > 2) {
setOnlineStatus(false);
}
console.warn('WebSocket: attempting reconnect');
if (websocket && websocket.readyState === WebSocket.CLOSED) {
reconnectAttempts += 1;
@@ -94,7 +94,7 @@ export default function ReportSettings() {
})();
return (
<tr key={entry.index}>
<th>{entry.index}</th>
<th>{entry.index + 1}</th>
<th>{entry.cue}</th>
<th>{entry.title}</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 { cx } from '../../../common/utils/styleUtils';
import TooltipActionBtn from '../../../common/components/buttons/TooltipActionBtn';
import { tooltipDelayMid } from '../../../ontimeConfig';
import style from './InputRow.module.scss';
@@ -10,53 +12,50 @@ interface InputRowProps {
placeholder: string;
text: string;
visible: boolean;
actionHandler: () => void;
changeHandler: (newValue: string) => void;
}
export default function InputRow(props: PropsWithChildren<InputRowProps>) {
const { label, placeholder, text, visible, changeHandler, children } = props;
export default function InputRow(props: InputRowProps) {
const { label, placeholder, text, visible, actionHandler, changeHandler } = props;
const [value, setValue] = useState(text);
const inputRef = useRef<HTMLInputElement>(null);
const cursorPositionRef = useRef(0);
// sync cursor position with text
useEffect(() => {
if (inputRef.current && inputRef.current !== document.activeElement) {
if (inputRef.current) {
inputRef.current.selectionStart = cursorPositionRef.current;
inputRef.current.selectionEnd = cursorPositionRef.current;
}
}, [text]);
// synchronise external text
useEffect(() => {
if (inputRef.current !== document.activeElement) {
setValue(text);
}
}, [text]);
const handleInputChange = (event: React.ChangeEvent<HTMLInputElement>) => {
cursorPositionRef.current = event.target.selectionStart ?? 0;
setValue(event.target.value);
changeHandler(event.target.value);
};
return (
<div className={style.inputRow}>
<label className={cx([style.label, visible ?? style.active])} htmlFor={label}>
{label}
</label>
<label className={`${style.label} ${visible ? style.active : ''}`}>{label}</label>
<div className={style.inputItems}>
<Input
id={label}
ref={inputRef}
size='sm'
variant='ontime-filled'
value={value}
value={text}
onChange={handleInputChange}
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>
);
@@ -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 { tooltipDelayMid } from '../../../ontimeConfig';
import InputRow from './InputRow';
import TimerControlsPreview from './TimerViewControl';
@@ -27,17 +23,8 @@ function TimerMessageInput() {
text={text}
visible={visible}
changeHandler={(newValue) => setMessage.timerText(newValue)}
>
<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>
actionHandler={() => setMessage.timerVisible(!visible)}
/>
);
}
@@ -59,16 +46,7 @@ function ExternalInput() {
text={text}
visible={visible}
changeHandler={(newValue) => setMessage.externalText(newValue)}
>
<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>
actionHandler={toggleExternal}
/>
);
}
+1 -2
View File
@@ -300,8 +300,7 @@ export default function Rundown({ data }: RundownProps) {
isNextDay = checkIsNextDay(entry, lastEvent);
if (!isPast) {
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 && !lastEvent?.countToEnd;
isLinkedToLoaded = isLinkedToLoaded && entry.linkStart !== null;
}
if (isNewLatest(entry, lastEvent)) {
// populate previous entry
@@ -140,7 +140,7 @@ export const ScheduleProvider = ({
let selectedEventIndex = events.findIndex((event) => event.id === selectedEventId);
// we want to show the event after the current
const viewEvents = events.slice(selectedEventIndex + 1);
const viewEvents = events.toSpliced(0, selectedEventIndex + 1);
selectedEventIndex = 0;
return (
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "ontime-electron",
"version": "3.14.5",
"version": "3.14.3",
"author": "Carlos Valente",
"description": "Time keeping for live events",
"repository": "https://github.com/cpvalente/ontime",
+1 -1
View File
@@ -2,7 +2,7 @@
"name": "ontime-server",
"type": "module",
"main": "src/index.ts",
"version": "3.14.5",
"version": "3.14.3",
"exports": "./src/index.js",
"dependencies": {
"@googleapis/sheets": "^5.0.5",
+1 -1
View File
@@ -124,6 +124,6 @@ function setSessionCookie(res: Response, token: string) {
httpOnly: false, // allow websocket to access cookie
secure: true,
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 { didMutate } = await scopedMutation({ eventIds });
if (!didMutate) {
if (didMutate === false) {
return;
}
@@ -146,7 +146,7 @@ export async function editEvent(patch: PatchWithId) {
const { newEvent, didMutate } = await scopedMutation({ patch, eventId: patch.id });
// short circuit if nothing changed
if (!didMutate) {
if (didMutate === false) {
return newEvent;
}
+1 -18
View File
@@ -4,7 +4,7 @@ import type { Server } from 'http';
import { networkInterfaces } from 'os';
import type { AddressInfo } from 'net';
import { isDocker, isOntimeCloud, isProduction } from '../externals.js';
import { isDocker, isProduction } from '../externals.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
*/
export function serverTryDesiredPort(server: Server, desiredPort: number): Promise<number> {
if (isOntimeCloud) {
return forceCloudPort(server);
}
return new Promise((resolve, reject) => {
server.once('error', (error) => {
// 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
*/
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "ontime",
"version": "3.14.5",
"version": "3.14.3",
"description": "Time keeping for live events",
"keywords": [
"ontime",
+1 -1
View File
@@ -31,7 +31,7 @@ export function insertAtIndex<T>(index: number, item: T, array: T[]): T[] {
* @param array
*/
export function deleteAtIndex<T>(index: number, array: T[]) {
return array.filter((_, i) => i !== index);
return array.toSpliced(index, 1);
}
/**