mirror of
https://github.com/cpvalente/ontime.git
synced 2026-08-03 22:47:59 +00:00
Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 03e68c03a9 | |||
| 44048a889d | |||
| f72db62e08 | |||
| 333f2623a5 | |||
| 3ebb3451e4 | |||
| 73afbacdd7 | |||
| d08452d613 | |||
| 4ebe6ede59 | |||
| 09a32e7612 | |||
| d81f5fcfe1 |
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@getontime/cli",
|
||||
"version": "3.14.2",
|
||||
"version": "3.14.4",
|
||||
"author": "Carlos Valente",
|
||||
"description": "Time keeping for live events",
|
||||
"repository": "https://github.com/cpvalente/ontime",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "ontime-ui",
|
||||
"version": "3.14.2",
|
||||
"version": "3.14.4",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
@@ -13,7 +13,6 @@
|
||||
"@emotion/styled": "^11.10.6",
|
||||
"@fontsource/open-sans": "^5.0.28",
|
||||
"@mantine/hooks": "^7.13.3",
|
||||
"@react-icons/all-files": "^4.1.0",
|
||||
"@sentry/react": "^8.43.0",
|
||||
"@table-nav/react": "^0.0.7",
|
||||
"@tanstack/react-query": "^5.62.7",
|
||||
@@ -29,6 +28,7 @@
|
||||
"react-dom": "^18.3.1",
|
||||
"react-fast-compare": "^3.2.2",
|
||||
"react-hook-form": "^7.53.1",
|
||||
"react-icons": "5.4.0",
|
||||
"react-qr-code": "^2.0.12",
|
||||
"react-router-dom": "^6.3.0",
|
||||
"web-vitals": "^3.1.1",
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { useState } from 'react';
|
||||
import { IoArrowForward } from 'react-icons/io5';
|
||||
import {
|
||||
IconButton,
|
||||
Input,
|
||||
@@ -10,7 +11,6 @@ import {
|
||||
ModalOverlay,
|
||||
Select,
|
||||
} from '@chakra-ui/react';
|
||||
import { IoArrowForward } from '@react-icons/all-files/io5/IoArrowForward';
|
||||
|
||||
import { navigatorConstants } from '../../../viewerConfig';
|
||||
import { setClientRemote } from '../../hooks/useSocket';
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
// https://github.com/lukasbach/chakra-ui-contextmenu/blob/main/src/ContextMenu.tsx
|
||||
|
||||
import { ReactElement } from 'react';
|
||||
import { IconType } from 'react-icons';
|
||||
import { Menu, MenuButton, MenuGroup, MenuList } from '@chakra-ui/react';
|
||||
import { IconType } from '@react-icons/all-files';
|
||||
import { create } from 'zustand';
|
||||
|
||||
import { ContextMenuOption } from './ContextMenuOption';
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { PropsWithChildren, useState } from 'react';
|
||||
import { IoCheckmark } from 'react-icons/io5';
|
||||
import { IoCopy } from 'react-icons/io5';
|
||||
import { Button, ButtonGroup, IconButton, Tooltip } from '@chakra-ui/react';
|
||||
import { IoCheckmark } from '@react-icons/all-files/io5/IoCheckmark';
|
||||
import { IoCopy } from '@react-icons/all-files/io5/IoCopy';
|
||||
|
||||
import { tooltipDelayFast } from '../../../ontimeConfig';
|
||||
import { Size } from '../../models/Util.type';
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { IoChevronDown, IoChevronUp } from 'react-icons/io5';
|
||||
import { Tooltip } from '@chakra-ui/react';
|
||||
import { IoChevronDown } from '@react-icons/all-files/io5/IoChevronDown';
|
||||
import { IoChevronUp } from '@react-icons/all-files/io5/IoChevronUp';
|
||||
|
||||
import { tooltipDelayFast } from '../../../ontimeConfig';
|
||||
import { millisToDelayString } from '../../utils/dateConfig';
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { PropsWithChildren } from 'react';
|
||||
import { IoAlertCircle } from '@react-icons/all-files/io5/IoAlertCircle';
|
||||
import { IoAlertCircle } from 'react-icons/io5';
|
||||
|
||||
import { cx } from '../../utils/styleUtils';
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { IoBan } from '@react-icons/all-files/io5/IoBan';
|
||||
import { IoBan } from 'react-icons/io5';
|
||||
|
||||
import { cx } from '../../../utils/styleUtils';
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useCallback } from 'react';
|
||||
import { useController, UseControllerProps } from 'react-hook-form';
|
||||
import { IoEyedrop } from '@react-icons/all-files/io5/IoEyedrop';
|
||||
import { IoEyedrop } from 'react-icons/io5';
|
||||
import { ViewSettings } from 'ontime-types';
|
||||
|
||||
import { debounce } from '../../../utils/debounce';
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { MouseEvent, ReactNode } from 'react';
|
||||
import { IoOpenOutline } from '@react-icons/all-files/io5/IoOpenOutline';
|
||||
import { IoOpenOutline } from 'react-icons/io5';
|
||||
|
||||
import { openLink } from '../../../utils/linkUtils';
|
||||
import { cx } from '../../../utils/styleUtils';
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { IoApps } from '@react-icons/all-files/io5/IoApps';
|
||||
import { IoSettingsOutline } from '@react-icons/all-files/io5/IoSettingsOutline';
|
||||
import { IoApps } from 'react-icons/io5';
|
||||
import { IoSettingsOutline } from 'react-icons/io5';
|
||||
|
||||
import { useFadeOutOnInactivity } from '../../hooks/useFadeOutOnInactivity';
|
||||
import { cx } from '../../utils/styleUtils';
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { memo, PropsWithChildren } from 'react';
|
||||
import { createPortal } from 'react-dom';
|
||||
import { IoArrowUp, IoContract, IoExpand, IoLockClosedOutline, IoSwapVertical } from 'react-icons/io5';
|
||||
import { Link, useLocation } from 'react-router-dom';
|
||||
import {
|
||||
Drawer,
|
||||
@@ -11,11 +12,6 @@ import {
|
||||
useDisclosure,
|
||||
} from '@chakra-ui/react';
|
||||
import { useFullscreen } from '@mantine/hooks';
|
||||
import { IoArrowUp } from '@react-icons/all-files/io5/IoArrowUp';
|
||||
import { IoContract } from '@react-icons/all-files/io5/IoContract';
|
||||
import { IoExpand } from '@react-icons/all-files/io5/IoExpand';
|
||||
import { IoLockClosedOutline } from '@react-icons/all-files/io5/IoLockClosedOutline';
|
||||
import { IoSwapVertical } from '@react-icons/all-files/io5/IoSwapVertical';
|
||||
|
||||
import { isLocalhost } from '../../../externals';
|
||||
import { navigatorConstants } from '../../../viewerConfig';
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { IoLockClosedOutline } from '@react-icons/all-files/io5/IoLockClosedOutline';
|
||||
import { IoLockClosedOutline } from 'react-icons/io5';
|
||||
|
||||
import { useFadeOutOnInactivity } from '../../hooks/useFadeOutOnInactivity';
|
||||
import { cx } from '../../utils/styleUtils';
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
import { IoPause, IoPlay, IoStop } from 'react-icons/io5';
|
||||
import { Tooltip } from '@chakra-ui/react';
|
||||
import { IoPause } from '@react-icons/all-files/io5/IoPause';
|
||||
import { IoPlay } from '@react-icons/all-files/io5/IoPlay';
|
||||
import { IoStop } from '@react-icons/all-files/io5/IoStop';
|
||||
import { Playback } from 'ontime-types';
|
||||
|
||||
import { tooltipDelayFast } from '../../../ontimeConfig';
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { IoCheckmark } from 'react-icons/io5';
|
||||
import { IconButton, PinInput, PinInputField } from '@chakra-ui/react';
|
||||
import { IoCheckmark } from '@react-icons/all-files/io5/IoCheckmark';
|
||||
|
||||
import style from './ProtectRoute.module.scss';
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { useState } from 'react';
|
||||
import { IoChevronDown } from 'react-icons/io5';
|
||||
import { useSearchParams } from 'react-router-dom';
|
||||
import {
|
||||
Button,
|
||||
@@ -13,7 +14,6 @@ import {
|
||||
Select,
|
||||
Switch,
|
||||
} from '@chakra-ui/react';
|
||||
import { IoChevronDown } from '@react-icons/all-files/io5/IoChevronDown';
|
||||
|
||||
import { isStringBoolean } from '../../../features/viewers/common/viewUtils';
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { IoChevronDown } from 'react-icons/io5';
|
||||
import { useLocalStorage } from '@mantine/hooks';
|
||||
import { IoChevronDown } from '@react-icons/all-files/io5/IoChevronDown';
|
||||
|
||||
import { cx } from '../../utils/styleUtils';
|
||||
|
||||
|
||||
@@ -4,10 +4,10 @@ import type { MultiselectOptions, ParamField } from './types';
|
||||
|
||||
export const makeOptionsFromCustomFields = (
|
||||
customFields: CustomFields,
|
||||
additionalOptions: Record<string, string> = {},
|
||||
additionalOptions: Readonly<Record<string, string>> = {},
|
||||
filterImageType = true,
|
||||
) => {
|
||||
const options = structuredClone(additionalOptions);
|
||||
const options = { ...additionalOptions };
|
||||
for (const [key, value] of Object.entries(customFields)) {
|
||||
if (filterImageType && value.type === 'image') {
|
||||
continue;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { OffsetMode, RuntimeStore, SimpleDirection, SimplePlayback, TimerMessage } from 'ontime-types';
|
||||
import { RuntimeStore, SimpleDirection, SimplePlayback, TimerMessage } from 'ontime-types';
|
||||
|
||||
import { useRuntimeStore } from '../stores/runtime';
|
||||
import { socketSendJson } from '../utils/socket';
|
||||
@@ -160,7 +160,7 @@ export const useRuntimePlaybackOverview = createSelector((state: RuntimeStore) =
|
||||
|
||||
numEvents: state.runtime.numEvents,
|
||||
selectedEventIndex: state.runtime.selectedEventIndex,
|
||||
offset: state.runtime.offsetMode === OffsetMode.Absolute ? state.runtime.offset : state.runtime.relativeOffset,
|
||||
offset: state.runtime.offset,
|
||||
|
||||
currentBlock: state.currentBlock,
|
||||
}));
|
||||
@@ -172,11 +172,8 @@ export const useTimelineStatus = createSelector((state: RuntimeStore) => ({
|
||||
|
||||
export const useTimeUntilData = createSelector((state: RuntimeStore) => ({
|
||||
clock: state.clock,
|
||||
offset: state.runtime.offsetMode === OffsetMode.Absolute ? state.runtime.offset : state.runtime.relativeOffset,
|
||||
offsetMode: state.runtime.offsetMode,
|
||||
offset: state.runtime.offset,
|
||||
currentDay: state.eventNow?.dayOffset ?? 0, //The day of the currently running event
|
||||
actualStart: state.runtime.actualStart,
|
||||
plannedStart: state.runtime.plannedStart,
|
||||
}));
|
||||
|
||||
export const useRuntimeOffset = createSelector((state: RuntimeStore) => ({
|
||||
@@ -192,12 +189,6 @@ export const useIsOnline = createSelector((state: RuntimeStore) => ({
|
||||
isOnline: state.ping > 0,
|
||||
}));
|
||||
|
||||
export const useOffsetMode = createSelector((state: RuntimeStore) => ({
|
||||
offsetMode: state.runtime.offsetMode,
|
||||
}));
|
||||
|
||||
export const setOffsetMode = (payload: OffsetMode) => socketSendJson('offsetmode', payload);
|
||||
|
||||
export const usePlayback = () => {
|
||||
const featureSelector = (state: RuntimeStore) => ({
|
||||
playback: state.timer.playback,
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { EndAction, EventCustomFields, OntimeEvent, SupportedEvent, TimerType } from 'ontime-types';
|
||||
import { EndAction, EventCustomFields, OntimeEvent, SupportedEvent, TimerType, TimeStrategy } from 'ontime-types';
|
||||
|
||||
import { cloneEvent } from '../eventsManager';
|
||||
|
||||
describe('cloneEvent()', () => {
|
||||
it('creates a stem from a given event', () => {
|
||||
const original = {
|
||||
const original: OntimeEvent = {
|
||||
id: 'unique',
|
||||
type: SupportedEvent.Event,
|
||||
title: 'title',
|
||||
@@ -14,6 +14,9 @@ describe('cloneEvent()', () => {
|
||||
duration: 10,
|
||||
timeEnd: 10,
|
||||
timerType: TimerType.CountDown,
|
||||
timeStrategy: TimeStrategy.LockEnd,
|
||||
linkStart: null,
|
||||
countToEnd: false,
|
||||
endAction: EndAction.None,
|
||||
isPublic: false,
|
||||
skip: false,
|
||||
@@ -21,29 +24,39 @@ describe('cloneEvent()', () => {
|
||||
revision: 10,
|
||||
timeWarning: 120000,
|
||||
timeDanger: 60000,
|
||||
delay: 0,
|
||||
dayOffset: 0,
|
||||
gap: 0,
|
||||
custom: {
|
||||
lighting: '3',
|
||||
} as EventCustomFields,
|
||||
} as OntimeEvent;
|
||||
};
|
||||
|
||||
const cloned = cloneEvent(original);
|
||||
expect(cloned).not.toBe(original);
|
||||
// @ts-expect-error -- safeguarding this
|
||||
expect(cloned?.id).toBe(undefined);
|
||||
expect(cloned.title).toBe(original.title);
|
||||
expect(cloned.note).toBe(original.note);
|
||||
expect(cloned.endAction).toBe(original.endAction);
|
||||
expect(cloned.timerType).toBe(original.timerType);
|
||||
expect(cloned.timeStart).toBe(original.timeStart);
|
||||
expect(cloned.timeEnd).toBe(original.timeEnd);
|
||||
expect(cloned.duration).toBe(original.duration);
|
||||
expect(cloned.isPublic).toBe(original.isPublic);
|
||||
expect(cloned.skip).toBe(original.skip);
|
||||
expect(cloned.colour).toBe(original.colour);
|
||||
expect(cloned.type).toBe(SupportedEvent.Event);
|
||||
expect(cloned.revision).toBe(0);
|
||||
expect(cloned.timeWarning).toBe(original.timeWarning);
|
||||
expect(cloned.timeDanger).toBe(original.timeDanger);
|
||||
expect(cloned.custom).toStrictEqual({});
|
||||
expect(cloned.custom).not.toBe(original.custom);
|
||||
|
||||
expect(cloned).toMatchObject({
|
||||
type: SupportedEvent.Event,
|
||||
title: original.title,
|
||||
note: original.note,
|
||||
timeStart: original.timeStart,
|
||||
duration: original.duration,
|
||||
timeEnd: original.timeEnd,
|
||||
timerType: original.timerType,
|
||||
timeStrategy: original.timeStrategy,
|
||||
countToEnd: original.countToEnd,
|
||||
linkStart: original.linkStart,
|
||||
endAction: original.endAction,
|
||||
isPublic: original.isPublic,
|
||||
skip: original.skip,
|
||||
colour: original.colour,
|
||||
revision: 0,
|
||||
delay: original.delay,
|
||||
dayOffset: original.dayOffset,
|
||||
gap: 0,
|
||||
timeWarning: original.timeWarning,
|
||||
timeDanger: original.timeDanger,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
import { OffsetMode } from 'ontime-types';
|
||||
import { dayInMs } from 'ontime-utils';
|
||||
|
||||
import { calculateTimeUntilStart, formatTime, nowInMillis } from '../time';
|
||||
|
||||
describe('nowInMillis()', () => {
|
||||
@@ -43,265 +40,82 @@ describe('formatTime()', () => {
|
||||
});
|
||||
|
||||
describe('calculateTimeUntilStart()', () => {
|
||||
describe('Absolute offset mode', () => {
|
||||
test('ontime', () => {
|
||||
const test = {
|
||||
timeStart: 100,
|
||||
dayOffset: 0,
|
||||
delay: 0,
|
||||
currentDay: 0,
|
||||
totalGap: 0,
|
||||
clock: 90,
|
||||
offset: 0,
|
||||
offsetMode: OffsetMode.Absolute,
|
||||
actualStart: null,
|
||||
plannedStart: null,
|
||||
};
|
||||
|
||||
expect(calculateTimeUntilStart({ ...test, isLinkedToLoaded: false })).toBe(10);
|
||||
expect(calculateTimeUntilStart({ ...test, isLinkedToLoaded: true })).toBe(10);
|
||||
});
|
||||
|
||||
test('running behind', () => {
|
||||
const test = {
|
||||
timeStart: 100,
|
||||
dayOffset: 0,
|
||||
delay: 0,
|
||||
currentDay: 0,
|
||||
totalGap: 0,
|
||||
clock: 90,
|
||||
offset: -20,
|
||||
offsetMode: OffsetMode.Absolute,
|
||||
actualStart: null,
|
||||
plannedStart: null,
|
||||
};
|
||||
|
||||
expect(calculateTimeUntilStart({ ...test, isLinkedToLoaded: false })).toBe(30);
|
||||
expect(calculateTimeUntilStart({ ...test, isLinkedToLoaded: true })).toBe(30);
|
||||
});
|
||||
|
||||
test('running ahead', () => {
|
||||
const test = {
|
||||
timeStart: 100,
|
||||
dayOffset: 0,
|
||||
delay: 0,
|
||||
currentDay: 0,
|
||||
totalGap: 0,
|
||||
clock: 80,
|
||||
offset: 10,
|
||||
offsetMode: OffsetMode.Absolute,
|
||||
actualStart: null,
|
||||
plannedStart: null,
|
||||
};
|
||||
|
||||
expect(calculateTimeUntilStart({ ...test, isLinkedToLoaded: false })).toBe(20); // <-- when running ahead the unlinked timer stays put
|
||||
expect(calculateTimeUntilStart({ ...test, isLinkedToLoaded: true })).toBe(10);
|
||||
});
|
||||
|
||||
test('running behind with enough gaps', () => {
|
||||
const test = {
|
||||
timeStart: 100,
|
||||
dayOffset: 0,
|
||||
delay: 0,
|
||||
currentDay: 0,
|
||||
totalGap: 20,
|
||||
clock: 50,
|
||||
offset: -20,
|
||||
offsetMode: OffsetMode.Absolute,
|
||||
actualStart: null,
|
||||
plannedStart: null,
|
||||
};
|
||||
|
||||
expect(calculateTimeUntilStart({ ...test, isLinkedToLoaded: false })).toBe(50); // <-- when gap is enough to compensate for the running behind
|
||||
expect(calculateTimeUntilStart({ ...test, isLinkedToLoaded: true })).toBe(70); // This should not be possible
|
||||
});
|
||||
|
||||
test('running behind with too little gaps', () => {
|
||||
const test = {
|
||||
timeStart: 100,
|
||||
dayOffset: 0,
|
||||
delay: 0,
|
||||
currentDay: 0,
|
||||
totalGap: 10,
|
||||
clock: 50,
|
||||
offset: -20,
|
||||
offsetMode: OffsetMode.Absolute,
|
||||
actualStart: 0,
|
||||
plannedStart: 0,
|
||||
};
|
||||
|
||||
expect(calculateTimeUntilStart({ ...test, isLinkedToLoaded: false })).toBe(60); // <-- when gap is not enough to compensate for the running behind it absorbs at much as possible
|
||||
expect(calculateTimeUntilStart({ ...test, isLinkedToLoaded: true })).toBe(70); // This should not be possible
|
||||
});
|
||||
});
|
||||
|
||||
describe('Relative offset mode', () => {
|
||||
test('basic function', () => {
|
||||
const test = {
|
||||
timeStart: 0,
|
||||
dayOffset: 0,
|
||||
delay: 0,
|
||||
currentDay: 0,
|
||||
totalGap: 0,
|
||||
clock: 100,
|
||||
actualStart: 100,
|
||||
plannedStart: 0,
|
||||
offset: 0, // relative
|
||||
// offset: -100, // absolute
|
||||
offsetMode: OffsetMode.Relative,
|
||||
};
|
||||
|
||||
const timeStartEvent2 = 10;
|
||||
const timeStartEvent3 = 20;
|
||||
|
||||
//event 1 is the currently running event
|
||||
|
||||
//event 2
|
||||
expect(calculateTimeUntilStart({ ...test, timeStart: timeStartEvent2, isLinkedToLoaded: true })).toBe(10);
|
||||
expect(calculateTimeUntilStart({ ...test, timeStart: timeStartEvent2, isLinkedToLoaded: false })).toBe(10);
|
||||
|
||||
//event 3
|
||||
expect(calculateTimeUntilStart({ ...test, timeStart: timeStartEvent3, isLinkedToLoaded: true })).toBe(20);
|
||||
expect(calculateTimeUntilStart({ ...test, timeStart: timeStartEvent3, isLinkedToLoaded: false })).toBe(20);
|
||||
|
||||
// When clock advances by 5ms, time until start should decrease by 5ms
|
||||
test.clock = 105;
|
||||
|
||||
//event 2
|
||||
expect(calculateTimeUntilStart({ ...test, timeStart: timeStartEvent2, isLinkedToLoaded: true })).toBe(5);
|
||||
expect(calculateTimeUntilStart({ ...test, timeStart: timeStartEvent2, isLinkedToLoaded: false })).toBe(5);
|
||||
|
||||
//event 3
|
||||
expect(calculateTimeUntilStart({ ...test, timeStart: timeStartEvent3, isLinkedToLoaded: true })).toBe(15);
|
||||
expect(calculateTimeUntilStart({ ...test, timeStart: timeStartEvent3, isLinkedToLoaded: false })).toBe(15);
|
||||
});
|
||||
|
||||
test('gaps', () => {
|
||||
const test = {
|
||||
timeStart: 20,
|
||||
dayOffset: 0,
|
||||
delay: 0,
|
||||
currentDay: 0,
|
||||
totalGap: 10,
|
||||
clock: 100,
|
||||
actualStart: 100,
|
||||
plannedStart: 0,
|
||||
offset: 0, // relative
|
||||
// offset: -100, // absolute
|
||||
offsetMode: OffsetMode.Relative,
|
||||
};
|
||||
|
||||
expect(calculateTimeUntilStart({ ...test, isLinkedToLoaded: true })).toBe(20);
|
||||
expect(calculateTimeUntilStart({ ...test, isLinkedToLoaded: false })).toBe(20);
|
||||
|
||||
// When clock advances by 5ms, time until start should decrease by 5ms
|
||||
test.clock = 105;
|
||||
expect(calculateTimeUntilStart({ ...test, isLinkedToLoaded: true })).toBe(15);
|
||||
expect(calculateTimeUntilStart({ ...test, isLinkedToLoaded: false })).toBe(15);
|
||||
});
|
||||
|
||||
test('added/remove time', () => {
|
||||
const test = {
|
||||
timeStart: 20,
|
||||
dayOffset: 0,
|
||||
delay: 0,
|
||||
currentDay: 0,
|
||||
totalGap: 0,
|
||||
clock: 100,
|
||||
actualStart: 100,
|
||||
plannedStart: 0,
|
||||
offset: 0, // relative
|
||||
// offset: -100, // absolute
|
||||
offsetMode: OffsetMode.Relative,
|
||||
};
|
||||
|
||||
expect(calculateTimeUntilStart({ ...test, isLinkedToLoaded: true })).toBe(20);
|
||||
expect(calculateTimeUntilStart({ ...test, isLinkedToLoaded: false })).toBe(20);
|
||||
|
||||
test.offset = 5; // remove 5 with addtime - we are ahead of time
|
||||
|
||||
expect(calculateTimeUntilStart({ ...test, isLinkedToLoaded: true })).toBe(15);
|
||||
expect(calculateTimeUntilStart({ ...test, isLinkedToLoaded: false })).toBe(20); // unlocked evets will stay on schedule
|
||||
|
||||
test.offset = -5; // add 5 with addtime - we are behind
|
||||
|
||||
expect(calculateTimeUntilStart({ ...test, isLinkedToLoaded: true })).toBe(25);
|
||||
expect(calculateTimeUntilStart({ ...test, isLinkedToLoaded: false })).toBe(25);
|
||||
});
|
||||
|
||||
test('next day', () => {
|
||||
const test = {
|
||||
// timeStart: 10,
|
||||
// dayOffset: 0,
|
||||
delay: 0,
|
||||
currentDay: 0,
|
||||
// totalGap: 0,
|
||||
clock: 100,
|
||||
actualStart: 100,
|
||||
plannedStart: 0,
|
||||
offset: 0, // relative
|
||||
// offset: -100, // absolute
|
||||
offsetMode: OffsetMode.Relative,
|
||||
};
|
||||
|
||||
// this event will start the current day
|
||||
expect(
|
||||
calculateTimeUntilStart({
|
||||
...test,
|
||||
timeStart: 10,
|
||||
dayOffset: 0,
|
||||
totalGap: 0,
|
||||
isLinkedToLoaded: false,
|
||||
}),
|
||||
).toBe(10);
|
||||
|
||||
// this event will start the next day
|
||||
// in absolute mode this would start in dayInMs - 100 since the gap would compensate
|
||||
// but in relative mode with and actual start that is 100 offset it starts in dayInMs
|
||||
expect(
|
||||
calculateTimeUntilStart({
|
||||
...test,
|
||||
timeStart: 0,
|
||||
dayOffset: 1,
|
||||
totalGap: dayInMs - 20,
|
||||
isLinkedToLoaded: false,
|
||||
}),
|
||||
).toBe(dayInMs);
|
||||
|
||||
// advancing 100ms
|
||||
test.clock = 200;
|
||||
|
||||
expect(
|
||||
calculateTimeUntilStart({
|
||||
...test,
|
||||
timeStart: 0,
|
||||
dayOffset: 1,
|
||||
totalGap: dayInMs - 20,
|
||||
isLinkedToLoaded: false,
|
||||
}),
|
||||
).toBe(dayInMs - 100);
|
||||
});
|
||||
});
|
||||
|
||||
test('overlap with negative total gap', () => {
|
||||
test('ontime', () => {
|
||||
const test = {
|
||||
// timeStart: 20,
|
||||
timeStart: 100,
|
||||
dayOffset: 0,
|
||||
delay: 0,
|
||||
currentDay: 0,
|
||||
// totalGap: 10,
|
||||
clock: 100,
|
||||
actualStart: 100,
|
||||
plannedStart: 0,
|
||||
offset: 0, // relative
|
||||
// offset: -100, // absolute
|
||||
offsetMode: OffsetMode.Relative,
|
||||
isLinkedToLoaded: false,
|
||||
totalGap: 0,
|
||||
clock: 90,
|
||||
offset: 0,
|
||||
};
|
||||
|
||||
// the overlap will be pushed out to the expected available time
|
||||
expect(calculateTimeUntilStart({ ...test, timeStart: 5, totalGap: -5 })).toBe(10);
|
||||
|
||||
test.clock = 105;
|
||||
expect(calculateTimeUntilStart({ ...test, isLinkedToLoaded: false })).toBe(10);
|
||||
expect(calculateTimeUntilStart({ ...test, isLinkedToLoaded: true })).toBe(10);
|
||||
});
|
||||
|
||||
test('running behind', () => {
|
||||
const test = {
|
||||
timeStart: 100,
|
||||
dayOffset: 0,
|
||||
delay: 0,
|
||||
currentDay: 0,
|
||||
totalGap: 0,
|
||||
clock: 90,
|
||||
offset: -20,
|
||||
};
|
||||
|
||||
expect(calculateTimeUntilStart({ ...test, isLinkedToLoaded: false })).toBe(30);
|
||||
expect(calculateTimeUntilStart({ ...test, isLinkedToLoaded: true })).toBe(30);
|
||||
});
|
||||
|
||||
test('running ahead', () => {
|
||||
const test = {
|
||||
timeStart: 100,
|
||||
dayOffset: 0,
|
||||
delay: 0,
|
||||
currentDay: 0,
|
||||
totalGap: 0,
|
||||
clock: 80,
|
||||
offset: 10,
|
||||
};
|
||||
|
||||
expect(calculateTimeUntilStart({ ...test, isLinkedToLoaded: false })).toBe(20); // <-- when running ahead the unlinked timer stays put
|
||||
expect(calculateTimeUntilStart({ ...test, isLinkedToLoaded: true })).toBe(10);
|
||||
});
|
||||
|
||||
test('running behind with enough gaps', () => {
|
||||
const test = {
|
||||
timeStart: 100,
|
||||
dayOffset: 0,
|
||||
delay: 0,
|
||||
currentDay: 0,
|
||||
totalGap: 20,
|
||||
clock: 50,
|
||||
offset: -20,
|
||||
};
|
||||
|
||||
expect(calculateTimeUntilStart({ ...test, isLinkedToLoaded: false })).toBe(50);
|
||||
expect(calculateTimeUntilStart({ ...test, isLinkedToLoaded: true })).toBe(70); // This should not be possible
|
||||
});
|
||||
|
||||
test('running behind with to little gaps', () => {
|
||||
const test = {
|
||||
timeStart: 100,
|
||||
dayOffset: 0,
|
||||
delay: 0,
|
||||
currentDay: 0,
|
||||
totalGap: 10,
|
||||
clock: 50,
|
||||
offset: -20,
|
||||
};
|
||||
|
||||
expect(calculateTimeUntilStart({ ...test, isLinkedToLoaded: false })).toBe(60);
|
||||
expect(calculateTimeUntilStart({ ...test, isLinkedToLoaded: true })).toBe(70); // This should not be possible
|
||||
});
|
||||
|
||||
//TODO: more indepth testing,
|
||||
// including day offset handling
|
||||
// and more?
|
||||
});
|
||||
|
||||
@@ -24,11 +24,11 @@ export const cloneEvent = (event: OntimeEvent): ClonedEvent => {
|
||||
skip: event.skip,
|
||||
colour: event.colour,
|
||||
revision: 0,
|
||||
delay: 0,
|
||||
dayOffset: 0,
|
||||
delay: event.delay, // the events will be collocated, so having the same metadata is a good start
|
||||
dayOffset: event.dayOffset,
|
||||
gap: 0,
|
||||
timeWarning: event.timeWarning,
|
||||
timeDanger: event.timeDanger,
|
||||
custom: {},
|
||||
custom: { ...event.custom },
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { MaybeNumber, OffsetMode, OntimeEvent, Settings, TimeFormat } from 'ontime-types';
|
||||
import { MaybeNumber, OntimeEvent, Settings, TimeFormat } from 'ontime-types';
|
||||
import { dayInMs, formatFromMillis, MILLIS_PER_HOUR, MILLIS_PER_MINUTE, MILLIS_PER_SECOND } from 'ontime-utils';
|
||||
|
||||
import { FORMAT_12, FORMAT_24 } from '../../viewerConfig';
|
||||
@@ -140,8 +140,8 @@ export function useTimeUntilStart(
|
||||
isLinkedToLoaded: boolean;
|
||||
},
|
||||
): number {
|
||||
const { offset, clock, currentDay, offsetMode, actualStart, plannedStart } = useTimeUntilData();
|
||||
return calculateTimeUntilStart({ ...data, currentDay, clock, offset, offsetMode, actualStart, plannedStart });
|
||||
const { offset, clock, currentDay } = useTimeUntilData();
|
||||
return calculateTimeUntilStart({ ...data, currentDay, clock, offset });
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -160,24 +160,9 @@ export function calculateTimeUntilStart(
|
||||
isLinkedToLoaded: boolean;
|
||||
clock: number;
|
||||
offset: number;
|
||||
offsetMode: OffsetMode;
|
||||
actualStart: MaybeNumber;
|
||||
plannedStart: MaybeNumber;
|
||||
},
|
||||
): number {
|
||||
const {
|
||||
timeStart,
|
||||
dayOffset,
|
||||
currentDay,
|
||||
totalGap,
|
||||
isLinkedToLoaded,
|
||||
clock,
|
||||
offset,
|
||||
delay,
|
||||
offsetMode,
|
||||
actualStart,
|
||||
plannedStart,
|
||||
} = data;
|
||||
const { timeStart, dayOffset, currentDay, totalGap, isLinkedToLoaded, clock, offset, delay } = data;
|
||||
|
||||
//How many days from the currently running event to this one
|
||||
const relativeDayOffset = dayOffset - currentDay;
|
||||
@@ -187,23 +172,20 @@ export function calculateTimeUntilStart(
|
||||
//The normalised start time of this event relative to the currently running event
|
||||
const normalisedTimeStart = delayedStart + relativeDayOffset * dayInMs;
|
||||
|
||||
let relativeStartOffset = 0;
|
||||
|
||||
if (offsetMode === OffsetMode.Relative) {
|
||||
relativeStartOffset = (actualStart ?? 0) - (plannedStart ?? 0);
|
||||
}
|
||||
|
||||
const scheduledTimeUntil = normalisedTimeStart - clock + relativeStartOffset;
|
||||
|
||||
const offsetTimeUntil = scheduledTimeUntil - offset;
|
||||
const offsetTimestart = normalisedTimeStart - offset;
|
||||
const offsetTimeUntil = offsetTimestart - clock;
|
||||
|
||||
if (isLinkedToLoaded) {
|
||||
//if we are directly linked back to the loaded event we just follow the offset
|
||||
return offsetTimeUntil;
|
||||
}
|
||||
|
||||
const gapsCanCompensateForOffset = totalGap + offset >= 0;
|
||||
if (gapsCanCompensateForOffset) {
|
||||
const scheduledTimeUntil = normalisedTimeStart - clock;
|
||||
|
||||
const isAheadOfSchedule = offset >= 0;
|
||||
const gapsCanCompensadeForOffset = totalGap + offset >= 0;
|
||||
|
||||
if (isAheadOfSchedule || gapsCanCompensadeForOffset) {
|
||||
// if we are ahead of schedule or the gap can compensate for the amount we are behind then expect to start at the scheduled time
|
||||
return scheduledTimeUntil;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { PropsWithChildren } from 'react';
|
||||
import { IoClose } from 'react-icons/io5';
|
||||
import { Button } from '@chakra-ui/react';
|
||||
import { IoClose } from '@react-icons/all-files/io5/IoClose';
|
||||
|
||||
import style from './PanelContent.module.scss';
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { HTMLAttributes, PropsWithChildren, ReactNode } from 'react';
|
||||
import { IoAdd } from 'react-icons/io5';
|
||||
import { Button } from '@chakra-ui/react';
|
||||
import { IoAdd } from '@react-icons/all-files/io5/IoAdd';
|
||||
|
||||
import { cx } from '../../../common/utils/styleUtils';
|
||||
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { useEffect, useMemo } from 'react';
|
||||
import { Controller, useFieldArray, useForm } from 'react-hook-form';
|
||||
import { IoAdd, IoTrash } from 'react-icons/io5';
|
||||
import { Button, IconButton, Input, Radio, RadioGroup, Select } from '@chakra-ui/react';
|
||||
import { IoAdd } from '@react-icons/all-files/io5/IoAdd';
|
||||
import { IoTrash } from '@react-icons/all-files/io5/IoTrash';
|
||||
import {
|
||||
Automation,
|
||||
AutomationDTO,
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import { Fragment, useState } from 'react';
|
||||
import { IoAdd, IoPencil, IoTrash } from 'react-icons/io5';
|
||||
import { Button, IconButton } from '@chakra-ui/react';
|
||||
import { IoAdd } from '@react-icons/all-files/io5/IoAdd';
|
||||
import { IoPencil } from '@react-icons/all-files/io5/IoPencil';
|
||||
import { IoTrash } from '@react-icons/all-files/io5/IoTrash';
|
||||
import { AutomationDTO, NormalisedAutomation } from 'ontime-types';
|
||||
|
||||
import { deleteAutomation } from '../../../../common/api/automation';
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Fragment, useMemo, useState } from 'react';
|
||||
import { IoAdd } from 'react-icons/io5';
|
||||
import { Button } from '@chakra-ui/react';
|
||||
import { IoAdd } from '@react-icons/all-files/io5/IoAdd';
|
||||
import { NormalisedAutomation, Trigger } from 'ontime-types';
|
||||
|
||||
import { deleteTrigger } from '../../../../common/api/automation';
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import { useState } from 'react';
|
||||
import { IoPencil, IoTrash, IoWarningOutline } from 'react-icons/io5';
|
||||
import { IconButton } from '@chakra-ui/react';
|
||||
import { IoPencil } from '@react-icons/all-files/io5/IoPencil';
|
||||
import { IoTrash } from '@react-icons/all-files/io5/IoTrash';
|
||||
import { IoWarningOutline } from '@react-icons/all-files/io5/IoWarningOutline';
|
||||
import { NormalisedAutomation, TimerLifeCycle } from 'ontime-types';
|
||||
|
||||
import Tag from '../../../../common/components/tag/Tag';
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
import { useMemo } from 'react';
|
||||
import { IoTrashBin } from 'react-icons/io5';
|
||||
import { Button } from '@chakra-ui/react';
|
||||
import { IoTrashBin } from '@react-icons/all-files/io5/IoTrashBin';
|
||||
|
||||
import { deleteAllReport } from '../../../../common/api/report';
|
||||
import { createBlob, downloadBlob } from '../../../../common/api/utils';
|
||||
|
||||
+1
-3
@@ -1,9 +1,7 @@
|
||||
import { useEffect } from 'react';
|
||||
import { useFieldArray, useForm } from 'react-hook-form';
|
||||
import { IoAdd, IoOpenOutline, IoTrash } from 'react-icons/io5';
|
||||
import { Button, IconButton, Input, Switch } from '@chakra-ui/react';
|
||||
import { IoAdd } from '@react-icons/all-files/io5/IoAdd';
|
||||
import { IoOpenOutline } from '@react-icons/all-files/io5/IoOpenOutline';
|
||||
import { IoTrash } from '@react-icons/all-files/io5/IoTrash';
|
||||
import { URLPreset } from 'ontime-types';
|
||||
|
||||
import { postUrlPresets } from '../../../../common/api/urlPresets';
|
||||
|
||||
+1
-2
@@ -1,7 +1,6 @@
|
||||
import { useState } from 'react';
|
||||
import { IoPencil, IoTrash } from 'react-icons/io5';
|
||||
import { IconButton } from '@chakra-ui/react';
|
||||
import { IoPencil } from '@react-icons/all-files/io5/IoPencil';
|
||||
import { IoTrash } from '@react-icons/all-files/io5/IoTrash';
|
||||
import { CustomField, CustomFieldLabel } from 'ontime-types';
|
||||
|
||||
import CopyTag from '../../../../../common/components/copy-tag/CopyTag';
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
import { useState } from 'react';
|
||||
import { IoAdd } from 'react-icons/io5';
|
||||
import { Button } from '@chakra-ui/react';
|
||||
import { IoAdd } from '@react-icons/all-files/io5/IoAdd';
|
||||
import { CustomField, CustomFieldLabel } from 'ontime-types';
|
||||
|
||||
import { deleteCustomField, editCustomField, postCustomField } from '../../../../../common/api/customFields';
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { PropsWithChildren, useState } from 'react';
|
||||
import { UseFormRegister } from 'react-hook-form';
|
||||
import { IoEyeOutline } from 'react-icons/io5';
|
||||
import { IconButton, Input, InputGroup, InputRightElement } from '@chakra-ui/react';
|
||||
import { IoEyeOutline } from '@react-icons/all-files/io5/IoEyeOutline';
|
||||
import { Settings } from 'ontime-types';
|
||||
|
||||
import { isAlphanumeric } from '../../../../common/utils/regex';
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { IoArrowUp } from '@react-icons/all-files/io5/IoArrowUp';
|
||||
import { IoArrowUp } from 'react-icons/io5';
|
||||
|
||||
import CopyTag from '../../../../common/components/copy-tag/CopyTag';
|
||||
import useInfo from '../../../../common/hooks-query/useInfo';
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { MouseEvent } from 'react';
|
||||
import { IoArrowUp } from 'react-icons/io5';
|
||||
import { Button } from '@chakra-ui/react';
|
||||
import { IoArrowUp } from '@react-icons/all-files/io5/IoArrowUp';
|
||||
|
||||
import { handleLinks } from '../../../../common/utils/linkUtils';
|
||||
import Log from '../../../log/Log';
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { ChangeEvent, useRef, useState } from 'react';
|
||||
import { IoAdd } from 'react-icons/io5';
|
||||
import { useSearchParams } from 'react-router-dom';
|
||||
import { Button, Input } from '@chakra-ui/react';
|
||||
import { IoAdd } from '@react-icons/all-files/io5/IoAdd';
|
||||
|
||||
import { uploadProjectFile } from '../../../../common/api/db';
|
||||
import { invalidateAllCaches, maybeAxiosError } from '../../../../common/api/utils';
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { ChangeEvent, useEffect, useRef } from 'react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { IoDownloadOutline, IoTrash } from 'react-icons/io5';
|
||||
import { Button, Input, Textarea } from '@chakra-ui/react';
|
||||
import { IoDownloadOutline } from '@react-icons/all-files/io5/IoDownloadOutline';
|
||||
import { IoTrash } from '@react-icons/all-files/io5/IoTrash';
|
||||
import { type ProjectData } from 'ontime-types';
|
||||
|
||||
import { projectLogoPath } from '../../../../common/api/constants';
|
||||
@@ -97,7 +96,7 @@ export default function ProjectData() {
|
||||
const isLoading = status === 'pending';
|
||||
|
||||
return (
|
||||
<Panel.Section as='form' onSubmit={handleSubmit(onSubmit)} onKeyDown={(event) => preventEscape(event, onReset)}>
|
||||
<Panel.Section as='form' onSubmit={handleSubmit(onSubmit)} onKeyDown={(event) => preventEscape(event, onReset)}>
|
||||
<Panel.Card>
|
||||
<Panel.SubHeader>
|
||||
Project data
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useState } from 'react';
|
||||
import { IoEllipsisHorizontal } from 'react-icons/io5';
|
||||
import { IconButton, Menu, MenuButton, MenuItem, MenuList } from '@chakra-ui/react';
|
||||
import { IoEllipsisHorizontal } from '@react-icons/all-files/io5/IoEllipsisHorizontal';
|
||||
|
||||
import {
|
||||
deleteProject,
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { ChangeEvent, useEffect, useState } from 'react';
|
||||
import { IoCheckmark, IoShieldCheckmarkOutline } from 'react-icons/io5';
|
||||
import { Button, Input, Spinner } from '@chakra-ui/react';
|
||||
import { IoCheckmark } from '@react-icons/all-files/io5/IoCheckmark';
|
||||
import { IoShieldCheckmarkOutline } from '@react-icons/all-files/io5/IoShieldCheckmarkOutline';
|
||||
|
||||
import { getWorksheetNames } from '../../../../common/api/sheets';
|
||||
import { maybeAxiosError } from '../../../../common/api/utils';
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { ChangeEvent, useRef, useState } from 'react';
|
||||
import { IoCloudOutline, IoDownloadOutline } from 'react-icons/io5';
|
||||
import { Button, Input } from '@chakra-ui/react';
|
||||
import { IoCloudOutline } from '@react-icons/all-files/io5/IoCloudOutline';
|
||||
import { IoDownloadOutline } from '@react-icons/all-files/io5/IoDownloadOutline';
|
||||
import { getErrorMessage, ImportMap } from 'ontime-utils';
|
||||
|
||||
import {
|
||||
|
||||
+1
-2
@@ -1,8 +1,7 @@
|
||||
import { useState } from 'react';
|
||||
import { useFieldArray, useForm } from 'react-hook-form';
|
||||
import { IoAdd, IoTrash } from 'react-icons/io5';
|
||||
import { Button, IconButton, Input, Select, Tooltip } from '@chakra-ui/react';
|
||||
import { IoAdd } from '@react-icons/all-files/io5/IoAdd';
|
||||
import { IoTrash } from '@react-icons/all-files/io5/IoTrash';
|
||||
import { ImportMap, isAlphanumericWithSpace } from 'ontime-utils';
|
||||
|
||||
import * as Panel from '../../../panel-utils/PanelUtils';
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
import { Fragment } from 'react';
|
||||
import { IoLink } from '@react-icons/all-files/io5/IoLink';
|
||||
import { IoLink } from 'react-icons/io5';
|
||||
import { CustomFields, isOntimeBlock, isOntimeEvent, OntimeRundown } from 'ontime-types';
|
||||
import { millisToString } from 'ontime-utils';
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { useEffect, useRef } from 'react';
|
||||
import { IoEye, IoEyeOffOutline } from 'react-icons/io5';
|
||||
import { Input } from '@chakra-ui/react';
|
||||
import { IoEye } from '@react-icons/all-files/io5/IoEye';
|
||||
import { IoEyeOffOutline } from '@react-icons/all-files/io5/IoEyeOffOutline';
|
||||
|
||||
import TooltipActionBtn from '../../../common/components/buttons/TooltipActionBtn';
|
||||
import { tooltipDelayMid } from '../../../ontimeConfig';
|
||||
|
||||
@@ -1,9 +1,5 @@
|
||||
import { IoArrowDown, IoArrowUp, IoBan, IoFlag, IoTime } from 'react-icons/io5';
|
||||
import { Tooltip } from '@chakra-ui/react';
|
||||
import { IoArrowDown } from '@react-icons/all-files/io5/IoArrowDown';
|
||||
import { IoArrowUp } from '@react-icons/all-files/io5/IoArrowUp';
|
||||
import { IoBan } from '@react-icons/all-files/io5/IoBan';
|
||||
import { IoFlag } from '@react-icons/all-files/io5/IoFlag';
|
||||
import { IoTime } from '@react-icons/all-files/io5/IoTime';
|
||||
import { TimerPhase, TimerType } from 'ontime-types';
|
||||
|
||||
import { useMessagePreview } from '../../../common/hooks/useSocket';
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { IoAdd, IoRemove } from 'react-icons/io5';
|
||||
import { Tooltip } from '@chakra-ui/react';
|
||||
import { useLocalStorage } from '@mantine/hooks';
|
||||
import { IoAdd } from '@react-icons/all-files/io5/IoAdd';
|
||||
import { IoRemove } from '@react-icons/all-files/io5/IoRemove';
|
||||
import { Playback } from 'ontime-types';
|
||||
import { MILLIS_PER_HOUR, MILLIS_PER_SECOND, parseUserTime } from 'ontime-utils';
|
||||
|
||||
|
||||
@@ -1,8 +1,4 @@
|
||||
import { IoArrowDown } from '@react-icons/all-files/io5/IoArrowDown';
|
||||
import { IoArrowUp } from '@react-icons/all-files/io5/IoArrowUp';
|
||||
import { IoPause } from '@react-icons/all-files/io5/IoPause';
|
||||
import { IoPlay } from '@react-icons/all-files/io5/IoPlay';
|
||||
import { IoStop } from '@react-icons/all-files/io5/IoStop';
|
||||
import { IoArrowDown, IoArrowUp, IoPause, IoPlay, IoStop } from 'react-icons/io5';
|
||||
import { Playback, SimpleDirection, SimplePlayback } from 'ontime-types';
|
||||
import { parseUserTime } from 'ontime-utils';
|
||||
|
||||
|
||||
@@ -1,12 +1,6 @@
|
||||
import { useMemo } from 'react';
|
||||
import { IoPause, IoPlay, IoPlaySkipBack, IoPlaySkipForward, IoReload, IoStop, IoTime } from 'react-icons/io5';
|
||||
import { Tooltip } from '@chakra-ui/react';
|
||||
import { IoPause } from '@react-icons/all-files/io5/IoPause';
|
||||
import { IoPlay } from '@react-icons/all-files/io5/IoPlay';
|
||||
import { IoPlaySkipBack } from '@react-icons/all-files/io5/IoPlaySkipBack';
|
||||
import { IoPlaySkipForward } from '@react-icons/all-files/io5/IoPlaySkipForward';
|
||||
import { IoReload } from '@react-icons/all-files/io5/IoReload';
|
||||
import { IoStop } from '@react-icons/all-files/io5/IoStop';
|
||||
import { IoTime } from '@react-icons/all-files/io5/IoTime';
|
||||
import { Playback, TimerPhase } from 'ontime-types';
|
||||
import { validatePlayback } from 'ontime-utils';
|
||||
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
import { lazy, useCallback, useEffect } from 'react';
|
||||
import { IoApps, IoClose, IoSettingsOutline } from 'react-icons/io5';
|
||||
import { IconButton, useDisclosure } from '@chakra-ui/react';
|
||||
import { useHotkeys } from '@mantine/hooks';
|
||||
import { IoApps } from '@react-icons/all-files/io5/IoApps';
|
||||
import { IoClose } from '@react-icons/all-files/io5/IoClose';
|
||||
import { IoSettingsOutline } from '@react-icons/all-files/io5/IoSettingsOutline';
|
||||
|
||||
import NavigationMenu from '../../common/components/navigation-menu/NavigationMenu';
|
||||
import { useElectronListener } from '../../common/hooks/useElectronEvent';
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { HTMLAttributes, LabelHTMLAttributes } from 'react';
|
||||
import { IoArrowUp } from '@react-icons/all-files/io5/IoArrowUp';
|
||||
import { type IconBaseProps } from '@react-icons/all-files/lib';
|
||||
import { IconBaseProps } from 'react-icons';
|
||||
import { IoArrowUp } from 'react-icons/io5';
|
||||
|
||||
import { cx } from '../../../common/utils/styleUtils';
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { IoLocate } from '@react-icons/all-files/io5/IoLocate';
|
||||
import { IoLocate } from 'react-icons/io5';
|
||||
|
||||
import { cx } from '../../../common/utils/styleUtils';
|
||||
|
||||
|
||||
@@ -170,7 +170,7 @@ function RuntimeOverview() {
|
||||
|
||||
return (
|
||||
<>
|
||||
<TimeColumn label='Offset' value={offsetText} className={offsetClasses} testId='offset' />
|
||||
<TimeColumn label='Offset' value={offsetText} className={offsetClasses} />
|
||||
<TimeColumn label='Time now' value={formatedTime(clock)} />
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -10,16 +10,13 @@ interface TimeLayoutProps {
|
||||
muted?: boolean;
|
||||
daySpan?: number;
|
||||
className?: string;
|
||||
testId?: string;
|
||||
}
|
||||
|
||||
export function TimeColumn({ label, value, muted, className, testId }: TimeLayoutProps) {
|
||||
export function TimeColumn({ label, value, muted, className }: TimeLayoutProps) {
|
||||
return (
|
||||
<div className={style.column}>
|
||||
<span className={style.label}>{label}</span>
|
||||
<span className={cx([style.clock, muted && style.muted, className])} data-testid={testId}>
|
||||
{value}
|
||||
</span>
|
||||
<span className={cx([style.clock, muted && style.muted, className])}>{value}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -300,7 +300,8 @@ export default function Rundown({ data }: RundownProps) {
|
||||
isNextDay = checkIsNextDay(entry, lastEvent);
|
||||
if (!isPast) {
|
||||
totalGap += entry.gap;
|
||||
isLinkedToLoaded = isLinkedToLoaded && entry.linkStart !== null;
|
||||
// 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;
|
||||
}
|
||||
if (isNewLatest(entry, lastEvent)) {
|
||||
// populate previous entry
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { IoAdd } from 'react-icons/io5';
|
||||
import { Button } from '@chakra-ui/react';
|
||||
import { IoAdd } from '@react-icons/all-files/io5/IoAdd';
|
||||
|
||||
import Empty from '../../common/components/state/Empty';
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useRef } from 'react';
|
||||
import { IoReorderTwo } from 'react-icons/io5';
|
||||
import { useSortable } from '@dnd-kit/sortable';
|
||||
import { CSS } from '@dnd-kit/utilities';
|
||||
import { IoReorderTwo } from '@react-icons/all-files/io5/IoReorderTwo';
|
||||
import { OntimeBlock } from 'ontime-types';
|
||||
|
||||
import { cx } from '../../../common/utils/styleUtils';
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { IoTrash } from 'react-icons/io5';
|
||||
import { IconButton } from '@chakra-ui/react';
|
||||
import { IoTrash } from '@react-icons/all-files/io5/IoTrash';
|
||||
|
||||
import { AppMode, useAppMode } from '../../../common/stores/appModeStore';
|
||||
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
import { useEffect, useRef } from 'react';
|
||||
import { IoCheckmarkDone, IoClose, IoReorderTwo } from 'react-icons/io5';
|
||||
import { Button } from '@chakra-ui/react';
|
||||
import { useSortable } from '@dnd-kit/sortable';
|
||||
import { CSS } from '@dnd-kit/utilities';
|
||||
import { IoCheckmarkDone } from '@react-icons/all-files/io5/IoCheckmarkDone';
|
||||
import { IoClose } from '@react-icons/all-files/io5/IoClose';
|
||||
import { IoReorderTwo } from '@react-icons/all-files/io5/IoReorderTwo';
|
||||
import { OntimeDelay } from 'ontime-types';
|
||||
|
||||
import DelayInput from '../../../common/components/input/delay-input/DelayInput';
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
import { MouseEvent, useEffect, useLayoutEffect, useRef, useState } from 'react';
|
||||
import {
|
||||
IoAdd,
|
||||
IoDuplicateOutline,
|
||||
IoLink,
|
||||
IoPeople,
|
||||
IoPeopleOutline,
|
||||
IoReorderTwo,
|
||||
IoSwapVertical,
|
||||
IoTrash,
|
||||
IoUnlink,
|
||||
} from 'react-icons/io5';
|
||||
import { useSortable } from '@dnd-kit/sortable';
|
||||
import { CSS } from '@dnd-kit/utilities';
|
||||
import { IoAdd } from '@react-icons/all-files/io5/IoAdd';
|
||||
import { IoDuplicateOutline } from '@react-icons/all-files/io5/IoDuplicateOutline';
|
||||
import { IoLink } from '@react-icons/all-files/io5/IoLink';
|
||||
import { IoPeople } from '@react-icons/all-files/io5/IoPeople';
|
||||
import { IoPeopleOutline } from '@react-icons/all-files/io5/IoPeopleOutline';
|
||||
import { IoReorderTwo } from '@react-icons/all-files/io5/IoReorderTwo';
|
||||
import { IoSwapVertical } from '@react-icons/all-files/io5/IoSwapVertical';
|
||||
import { IoTrash } from '@react-icons/all-files/io5/IoTrash';
|
||||
import { IoUnlink } from '@react-icons/all-files/io5/IoUnlink';
|
||||
import { EndAction, MaybeString, OntimeEvent, Playback, TimerType, TimeStrategy } from 'ontime-types';
|
||||
|
||||
import { useContextMenu } from '../../../common/hooks/useContextMenu';
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
import { memo, useEffect, useState } from 'react';
|
||||
import {
|
||||
IoArrowDown,
|
||||
IoArrowUp,
|
||||
IoBan,
|
||||
IoFlag,
|
||||
IoPeople,
|
||||
IoPlay,
|
||||
IoPlayForward,
|
||||
IoPlaySkipForward,
|
||||
IoStop,
|
||||
IoTime,
|
||||
} from 'react-icons/io5';
|
||||
import { Tooltip } from '@chakra-ui/react';
|
||||
import { IoArrowDown } from '@react-icons/all-files/io5/IoArrowDown';
|
||||
import { IoArrowUp } from '@react-icons/all-files/io5/IoArrowUp';
|
||||
import { IoBan } from '@react-icons/all-files/io5/IoBan';
|
||||
import { IoFlag } from '@react-icons/all-files/io5/IoFlag';
|
||||
import { IoPeople } from '@react-icons/all-files/io5/IoPeople';
|
||||
import { IoPlay } from '@react-icons/all-files/io5/IoPlay';
|
||||
import { IoPlayForward } from '@react-icons/all-files/io5/IoPlayForward';
|
||||
import { IoPlaySkipForward } from '@react-icons/all-files/io5/IoPlaySkipForward';
|
||||
import { IoStop } from '@react-icons/all-files/io5/IoStop';
|
||||
import { IoTime } from '@react-icons/all-files/io5/IoTime';
|
||||
import { EndAction, MaybeString, Playback, TimerType, TimeStrategy } from 'ontime-types';
|
||||
|
||||
import { cx } from '../../../common/utils/styleUtils';
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useMemo } from 'react';
|
||||
import { IoCheckmarkCircle } from 'react-icons/io5';
|
||||
import { Tooltip } from '@chakra-ui/react';
|
||||
import { IoCheckmarkCircle } from '@react-icons/all-files/io5/IoCheckmarkCircle';
|
||||
import { isPlaybackActive, MILLIS_PER_MINUTE, MILLIS_PER_SECOND } from 'ontime-utils';
|
||||
|
||||
import { usePlayback } from '../../../../common/hooks/useSocket';
|
||||
|
||||
@@ -1,9 +1,5 @@
|
||||
import { memo, MouseEvent } from 'react';
|
||||
import { IoPause } from '@react-icons/all-files/io5/IoPause';
|
||||
import { IoPlay } from '@react-icons/all-files/io5/IoPlay';
|
||||
import { IoReload } from '@react-icons/all-files/io5/IoReload';
|
||||
import { IoRemoveCircle } from '@react-icons/all-files/io5/IoRemoveCircle';
|
||||
import { IoRemoveCircleOutline } from '@react-icons/all-files/io5/IoRemoveCircleOutline';
|
||||
import { IoPause, IoPlay, IoReload, IoRemoveCircle, IoRemoveCircleOutline } from 'react-icons/io5';
|
||||
|
||||
import TooltipActionBtn from '../../../../common/components/buttons/TooltipActionBtn';
|
||||
import { useEventAction } from '../../../../common/hooks/useEventAction';
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { memo } from 'react';
|
||||
import { IoInformationCircle } from 'react-icons/io5';
|
||||
import { Select, Switch, Tooltip } from '@chakra-ui/react';
|
||||
import { IoInformationCircle } from '@react-icons/all-files/io5/IoInformationCircle';
|
||||
import { EndAction, MaybeString, TimerType, TimeStrategy } from 'ontime-types';
|
||||
import { millisToString, parseUserTime } from 'ontime-utils';
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { memo, useCallback, useRef } from 'react';
|
||||
import { IoAdd } from 'react-icons/io5';
|
||||
import { Button } from '@chakra-ui/react';
|
||||
import { IoAdd } from '@react-icons/all-files/io5/IoAdd';
|
||||
import { SupportedEvent } from 'ontime-types';
|
||||
|
||||
import { useEventAction } from '../../../common/hooks/useEventAction';
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
import { Button, ButtonGroup } from '@chakra-ui/react';
|
||||
import { OffsetMode } from 'ontime-types';
|
||||
|
||||
import { setOffsetMode, useOffsetMode } from '../../../common/hooks/useSocket';
|
||||
import { AppMode, useAppMode } from '../../../common/stores/appModeStore';
|
||||
|
||||
import RundownMenu from './RundownMenu';
|
||||
@@ -14,8 +12,6 @@ export default function RundownHeader() {
|
||||
const setRunMode = () => setAppMode(AppMode.Run);
|
||||
const setEditMode = () => setAppMode(AppMode.Edit);
|
||||
|
||||
const { offsetMode } = useOffsetMode();
|
||||
|
||||
return (
|
||||
<div className={style.header}>
|
||||
<ButtonGroup isAttached>
|
||||
@@ -26,22 +22,6 @@ export default function RundownHeader() {
|
||||
Edit
|
||||
</Button>
|
||||
</ButtonGroup>
|
||||
<ButtonGroup isAttached>
|
||||
<Button
|
||||
size='sm'
|
||||
variant={offsetMode === OffsetMode.Absolute ? 'ontime-filled' : 'ontime-subtle'}
|
||||
onClick={() => setOffsetMode(OffsetMode.Absolute)}
|
||||
>
|
||||
Absolute
|
||||
</Button>
|
||||
<Button
|
||||
size='sm'
|
||||
variant={offsetMode === OffsetMode.Relative ? 'ontime-filled' : 'ontime-subtle'}
|
||||
onClick={() => setOffsetMode(OffsetMode.Relative)}
|
||||
>
|
||||
Relative
|
||||
</Button>
|
||||
</ButtonGroup>
|
||||
<RundownMenu />
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { useCallback, useRef } from 'react';
|
||||
import { IoTrash } from 'react-icons/io5';
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogBody,
|
||||
@@ -9,7 +10,6 @@ import {
|
||||
Button,
|
||||
useDisclosure,
|
||||
} from '@chakra-ui/react';
|
||||
import { IoTrash } from '@react-icons/all-files/io5/IoTrash';
|
||||
|
||||
import { useEventAction } from '../../../common/hooks/useEventAction';
|
||||
import { useAppMode } from '../../../common/stores/appModeStore';
|
||||
|
||||
@@ -1,10 +1,6 @@
|
||||
import { memo } from 'react';
|
||||
import { IoAlertCircleOutline, IoLink, IoLockClosed, IoLockOpenOutline, IoUnlink } from 'react-icons/io5';
|
||||
import { InputRightElement, Tooltip } from '@chakra-ui/react';
|
||||
import { IoAlertCircleOutline } from '@react-icons/all-files/io5/IoAlertCircleOutline';
|
||||
import { IoLink } from '@react-icons/all-files/io5/IoLink';
|
||||
import { IoLockClosed } from '@react-icons/all-files/io5/IoLockClosed';
|
||||
import { IoLockOpenOutline } from '@react-icons/all-files/io5/IoLockOpenOutline';
|
||||
import { IoUnlink } from '@react-icons/all-files/io5/IoUnlink';
|
||||
import { MaybeString, TimeField, TimeStrategy } from 'ontime-types';
|
||||
import { dayInMs } from 'ontime-utils';
|
||||
|
||||
|
||||
@@ -2,7 +2,8 @@
|
||||
background-color: $ui-black;
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
padding: 1rem 0.5rem;
|
||||
padding-inline: 0.5rem;
|
||||
padding-block: 0.5rem 1rem;
|
||||
|
||||
display: grid;
|
||||
grid-template-rows: 3rem auto auto 1fr;
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { useCallback, useMemo, useState } from 'react';
|
||||
import { IoApps, IoSettingsOutline } from 'react-icons/io5';
|
||||
import { IconButton, Modal, ModalContent, ModalOverlay, useDisclosure } from '@chakra-ui/react';
|
||||
import { IoApps } from '@react-icons/all-files/io5/IoApps';
|
||||
import { IoSettingsOutline } from '@react-icons/all-files/io5/IoSettingsOutline';
|
||||
|
||||
import NavigationMenu from '../../common/components/navigation-menu/NavigationMenu';
|
||||
import useViewEditor from '../../common/components/navigation-menu/useViewEditor';
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { memo, MutableRefObject, useLayoutEffect, useRef, useState } from 'react';
|
||||
import { IoEllipsisHorizontal } from '@react-icons/all-files/io5/IoEllipsisHorizontal';
|
||||
import { IoEllipsisHorizontal } from 'react-icons/io5';
|
||||
import { flexRender, Table } from '@tanstack/react-table';
|
||||
import Color from 'color';
|
||||
import { OntimeEvent, OntimeRundownEntry } from 'ontime-types';
|
||||
|
||||
+1
-6
@@ -1,10 +1,5 @@
|
||||
import { IoAdd, IoArrowDown, IoArrowUp, IoDuplicateOutline, IoOptions, IoTrash } from 'react-icons/io5';
|
||||
import { MenuDivider, MenuItem, MenuList } from '@chakra-ui/react';
|
||||
import { IoAdd } from '@react-icons/all-files/io5/IoAdd';
|
||||
import { IoArrowDown } from '@react-icons/all-files/io5/IoArrowDown';
|
||||
import { IoArrowUp } from '@react-icons/all-files/io5/IoArrowUp';
|
||||
import { IoDuplicateOutline } from '@react-icons/all-files/io5/IoDuplicateOutline';
|
||||
import { IoOptions } from '@react-icons/all-files/io5/IoOptions';
|
||||
import { IoTrash } from '@react-icons/all-files/io5/IoTrash';
|
||||
import { isOntimeEvent, SupportedEvent } from 'ontime-types';
|
||||
|
||||
import { useEventAction } from '../../../../common/hooks/useEventAction';
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "ontime-electron",
|
||||
"version": "3.14.2",
|
||||
"version": "3.14.4",
|
||||
"author": "Carlos Valente",
|
||||
"description": "Time keeping for live events",
|
||||
"repository": "https://github.com/cpvalente/ontime",
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"name": "ontime-server",
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
"version": "3.14.2",
|
||||
"version": "3.14.4",
|
||||
"exports": "./src/index.js",
|
||||
"dependencies": {
|
||||
"@googleapis/sheets": "^5.0.5",
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { MessageState, OffsetMode, OntimeEvent, SimpleDirection, SimplePlayback } from 'ontime-types';
|
||||
import { MessageState, OntimeEvent, SimpleDirection, SimplePlayback } from 'ontime-types';
|
||||
import { MILLIS_PER_HOUR, MILLIS_PER_SECOND } from 'ontime-utils';
|
||||
|
||||
import { DeepPartial } from 'ts-essentials';
|
||||
@@ -17,7 +17,6 @@ import { throttle } from '../utils/throttle.js';
|
||||
import { willCauseRegeneration } from '../services/rundown-service/rundownCacheUtils.js';
|
||||
|
||||
import { handleLegacyMessageConversion } from './integration.legacy.js';
|
||||
import { coerceEnum } from '../utils/coerceType.js';
|
||||
|
||||
const throttledUpdateEvent = throttle(updateEvent, 20);
|
||||
let lastRequest: Date | null = null;
|
||||
@@ -287,11 +286,6 @@ const actionHandlers: Record<string, ActionHandler> = {
|
||||
|
||||
throw new Error('No matching method provided');
|
||||
},
|
||||
offsetmode: (payload) => {
|
||||
const mode = coerceEnum<OffsetMode>(payload, OffsetMode);
|
||||
runtimeService.setOffsetMode(mode);
|
||||
return { payload: 'success' };
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -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: 'strict',
|
||||
sameSite: 'none', // allow cookies to be sent in cross-origin requests (e.g., iframes)
|
||||
});
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ import { EndAction, Playback, TimeStrategy, TimerPhase, TimerType } from 'ontime
|
||||
import {
|
||||
getCurrent,
|
||||
getExpectedFinish,
|
||||
getRelativeOffset,
|
||||
getRuntimeOffset,
|
||||
getTimerPhase,
|
||||
normaliseEndTime,
|
||||
@@ -1047,84 +1046,6 @@ describe('getRuntimeOffset()', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('getRelativeOffset()', () => {
|
||||
it('relative offset is 0 when starting at the planed time', () => {
|
||||
const state = {
|
||||
eventNow: {
|
||||
id: '1',
|
||||
timeStart: 150,
|
||||
},
|
||||
timer: {
|
||||
startedAt: 150,
|
||||
addedTime: 0,
|
||||
current: 0,
|
||||
},
|
||||
_timer: {
|
||||
pausedAt: null,
|
||||
},
|
||||
runtime: {
|
||||
actualStart: 150,
|
||||
plannedStart: 150,
|
||||
},
|
||||
} as RuntimeState;
|
||||
|
||||
state.runtime.offset = getRuntimeOffset(state);
|
||||
expect(state.runtime.offset).toBe(0);
|
||||
const relativeOffsetoffset = getRelativeOffset(state);
|
||||
expect(relativeOffsetoffset).toBe(0);
|
||||
});
|
||||
it('relative offset is 0 when starting after the planed time', () => {
|
||||
const state = {
|
||||
eventNow: {
|
||||
id: '1',
|
||||
timeStart: 100,
|
||||
},
|
||||
timer: {
|
||||
startedAt: 150,
|
||||
addedTime: 0,
|
||||
current: 0,
|
||||
},
|
||||
_timer: {
|
||||
pausedAt: null,
|
||||
},
|
||||
runtime: {
|
||||
actualStart: 150,
|
||||
plannedStart: 100,
|
||||
},
|
||||
} as RuntimeState;
|
||||
|
||||
state.runtime.offset = getRuntimeOffset(state);
|
||||
expect(state.runtime.offset).toBe(-50);
|
||||
const relativeOffsetoffset = getRelativeOffset(state);
|
||||
expect(relativeOffsetoffset).toBe(0);
|
||||
});
|
||||
it('relative offset is 0 when starting before the planed time', () => {
|
||||
const state = {
|
||||
eventNow: {
|
||||
id: '1',
|
||||
timeStart: 150,
|
||||
},
|
||||
timer: {
|
||||
startedAt: 100,
|
||||
addedTime: 0,
|
||||
current: 0,
|
||||
},
|
||||
_timer: {
|
||||
pausedAt: null,
|
||||
},
|
||||
runtime: {
|
||||
actualStart: 100,
|
||||
plannedStart: 150,
|
||||
},
|
||||
} as RuntimeState;
|
||||
|
||||
state.runtime.offset = getRuntimeOffset(state);
|
||||
expect(state.runtime.offset).toBe(50);
|
||||
const relativeOffsetoffset = getRelativeOffset(state);
|
||||
expect(relativeOffsetoffset).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getTimerPhase()', () => {
|
||||
it('should be None if the timer is not running', () => {
|
||||
const state = {
|
||||
@@ -1248,10 +1169,8 @@ describe('getTimerPhase()', () => {
|
||||
},
|
||||
_timer: {
|
||||
forceFinish: null,
|
||||
pausedAt: null,
|
||||
},
|
||||
_rundown: {
|
||||
totalDelay: 0,
|
||||
pausedAt: null,
|
||||
},
|
||||
} as RuntimeState;
|
||||
|
||||
@@ -1289,10 +1208,8 @@ describe('getTimerPhase()', () => {
|
||||
},
|
||||
_timer: {
|
||||
forceFinish: null,
|
||||
pausedAt: null,
|
||||
},
|
||||
_rundown: {
|
||||
totalDelay: 0,
|
||||
pausedAt: null,
|
||||
},
|
||||
} as RuntimeState;
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@ import {
|
||||
isPlayableEvent,
|
||||
LogOrigin,
|
||||
MaybeNumber,
|
||||
OffsetMode,
|
||||
OntimeEvent,
|
||||
Playback,
|
||||
TimerLifeCycle,
|
||||
@@ -69,11 +68,6 @@ class RuntimeService {
|
||||
RuntimeService.previousState = {} as RuntimeState;
|
||||
}
|
||||
|
||||
@broadcastResult
|
||||
setOffsetMode(mode: OffsetMode) {
|
||||
runtimeState.setOffsetMode(mode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks result of an update and notifies integrations as needed
|
||||
* This is the only exception of a private method that has broadcast result
|
||||
@@ -703,14 +697,11 @@ function broadcastResult(_target: any, _propertyKey: string, descriptor: Propert
|
||||
// for the very fist run there will be nothing in the previousState so we force an update
|
||||
const justStarted = !RuntimeService.previousState?.timer;
|
||||
|
||||
// offset mode has been changed
|
||||
const offsetModeChanged = RuntimeService.previousState?.runtime?.offsetMode !== state.runtime.offsetMode;
|
||||
|
||||
// if playback changes most things should update
|
||||
const hasChangedPlayback = RuntimeService.previousState.timer?.playback !== state.timer.playback;
|
||||
|
||||
// combine all big changes
|
||||
const hasImmediateChanges = hasNewLoaded || justStarted || hasChangedPlayback || offsetModeChanged;
|
||||
const hasImmediateChanges = hasNewLoaded || justStarted || hasChangedPlayback;
|
||||
|
||||
/**
|
||||
* Timer should be updated if
|
||||
|
||||
@@ -156,23 +156,6 @@ export function getRuntimeOffset(state: RuntimeState): number {
|
||||
return startOffset - addedTime - pausedTime + overtime;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates relative offset
|
||||
* should always be calculated after the absolute offset
|
||||
*/
|
||||
export function getRelativeOffset(state: RuntimeState): number {
|
||||
const { actualStart, plannedStart, offset } = state.runtime;
|
||||
// eslint-disable-next-line no-unused-labels -- dev code path
|
||||
DEV: {
|
||||
// we know actualStart and plannedStart exists as long as a timer is running
|
||||
if (actualStart === null || plannedStart === null) {
|
||||
throw new Error('timerUtils.calculate: actualStart and plannedStart must be set');
|
||||
}
|
||||
}
|
||||
const relativeStartOffset = actualStart - plannedStart;
|
||||
return offset + relativeStartOffset;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the expected end of the rundown
|
||||
*/
|
||||
@@ -181,7 +164,7 @@ export function getExpectedEnd(state: RuntimeState): MaybeNumber {
|
||||
if (state.runtime.actualStart === null || state.runtime.plannedEnd === null) {
|
||||
return null;
|
||||
}
|
||||
return state.runtime.plannedEnd - state.runtime.offset + state._rundown.totalDelay;
|
||||
return state.runtime.plannedEnd - state.runtime.offset + state._timer.totalDelay;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { TimerPhase, Playback, OffsetMode } from 'ontime-types';
|
||||
import { TimerPhase, Playback } from 'ontime-types';
|
||||
import { deepmerge } from 'ontime-utils';
|
||||
import type { RuntimeState } from '../runtimeState.js';
|
||||
|
||||
@@ -16,12 +16,10 @@ const baseState: RuntimeState = {
|
||||
selectedEventIndex: null,
|
||||
numEvents: 0,
|
||||
offset: 0,
|
||||
relativeOffset: 0,
|
||||
plannedStart: 0,
|
||||
plannedEnd: 0,
|
||||
actualStart: null,
|
||||
expectedEnd: null,
|
||||
offsetMode: OffsetMode.Absolute,
|
||||
},
|
||||
timer: {
|
||||
addedTime: 0,
|
||||
@@ -37,12 +35,10 @@ const baseState: RuntimeState = {
|
||||
},
|
||||
_timer: {
|
||||
forceFinish: null,
|
||||
totalDelay: 0,
|
||||
pausedAt: null,
|
||||
secondaryTarget: null,
|
||||
},
|
||||
_rundown: {
|
||||
totalDelay: 0,
|
||||
},
|
||||
};
|
||||
|
||||
export function makeRuntimeStateData(patch?: Partial<RuntimeState>): RuntimeState {
|
||||
|
||||
@@ -4,7 +4,7 @@ import { deepmerge } from 'ontime-utils';
|
||||
import {
|
||||
type RuntimeState,
|
||||
addTime,
|
||||
hardClear,
|
||||
clear,
|
||||
getState,
|
||||
load,
|
||||
loadBlock,
|
||||
@@ -71,7 +71,7 @@ beforeAll(() => {
|
||||
|
||||
describe('mutation on runtimeState', () => {
|
||||
beforeEach(() => {
|
||||
hardClear();
|
||||
clear();
|
||||
|
||||
vi.mock('../../services/rundown-service/RundownService.js', async (importOriginal) => {
|
||||
const actual = (await importOriginal()) as object;
|
||||
@@ -246,7 +246,7 @@ describe('roll mode', () => {
|
||||
beforeEach(() => {
|
||||
vi.useFakeTimers();
|
||||
vi.setSystemTime('jan 1 00:00');
|
||||
hardClear();
|
||||
clear();
|
||||
});
|
||||
afterEach(() => {
|
||||
vi.useRealTimers();
|
||||
|
||||
@@ -3,7 +3,6 @@ import {
|
||||
isPlayableEvent,
|
||||
MaybeNumber,
|
||||
MaybeString,
|
||||
OffsetMode,
|
||||
OntimeEvent,
|
||||
OntimeRundown,
|
||||
PlayableEvent,
|
||||
@@ -28,7 +27,6 @@ import {
|
||||
getCurrent,
|
||||
getExpectedEnd,
|
||||
getExpectedFinish,
|
||||
getRelativeOffset,
|
||||
getRuntimeOffset,
|
||||
getTimerPhase,
|
||||
} from '../services/timerUtils.js';
|
||||
@@ -47,12 +45,10 @@ export type RuntimeState = {
|
||||
// private properties of the timer calculations
|
||||
_timer: {
|
||||
forceFinish: MaybeNumber; // whether we should declare an event as finished, will contain the finish time
|
||||
totalDelay: number; // this value comes from rundown service
|
||||
pausedAt: MaybeNumber;
|
||||
secondaryTarget: MaybeNumber;
|
||||
};
|
||||
_rundown: {
|
||||
totalDelay: number; // this value comes from rundown service
|
||||
};
|
||||
};
|
||||
|
||||
const runtimeState: RuntimeState = {
|
||||
@@ -66,12 +62,10 @@ const runtimeState: RuntimeState = {
|
||||
timer: { ...runtimeStorePlaceholder.timer },
|
||||
_timer: {
|
||||
forceFinish: null,
|
||||
totalDelay: 0,
|
||||
pausedAt: null,
|
||||
secondaryTarget: null,
|
||||
},
|
||||
_rundown: {
|
||||
totalDelay: 0,
|
||||
},
|
||||
};
|
||||
|
||||
export function getState(): Readonly<RuntimeState> {
|
||||
@@ -85,36 +79,10 @@ export function getState(): Readonly<RuntimeState> {
|
||||
runtime: { ...runtimeState.runtime },
|
||||
timer: { ...runtimeState.timer },
|
||||
_timer: { ...runtimeState._timer },
|
||||
_rundown: { totalDelay: 0 },
|
||||
};
|
||||
}
|
||||
|
||||
//TODO: find other things that dose not need clearing
|
||||
export function softClear() {
|
||||
runtimeState.eventNow = null;
|
||||
runtimeState.publicEventNow = null;
|
||||
runtimeState.eventNext = null;
|
||||
runtimeState.publicEventNext = null;
|
||||
|
||||
// runtimeState.currentBlock.block = null;
|
||||
// runtimeState.currentBlock.startedAt = null;
|
||||
|
||||
runtimeState.runtime.offset = 0;
|
||||
runtimeState.runtime.relativeOffset = 0;
|
||||
runtimeState.runtime.expectedEnd = null;
|
||||
runtimeState.runtime.selectedEventIndex = null;
|
||||
|
||||
runtimeState.timer.playback = Playback.Stop;
|
||||
runtimeState.clock = clock.timeNow();
|
||||
runtimeState.timer = { ...runtimeStorePlaceholder.timer };
|
||||
|
||||
// when clearing, we maintain the total delay from the rundown
|
||||
runtimeState._timer.forceFinish = null;
|
||||
runtimeState._timer.pausedAt = null;
|
||||
runtimeState._timer.secondaryTarget = null;
|
||||
}
|
||||
|
||||
export function hardClear() {
|
||||
export function clear() {
|
||||
runtimeState.eventNow = null;
|
||||
runtimeState.publicEventNow = null;
|
||||
runtimeState.eventNext = null;
|
||||
@@ -125,7 +93,6 @@ export function hardClear() {
|
||||
runtimeState.publicEventNext = null;
|
||||
|
||||
runtimeState.runtime.offset = 0;
|
||||
runtimeState.runtime.relativeOffset = 0;
|
||||
runtimeState.runtime.actualStart = null;
|
||||
runtimeState.runtime.expectedEnd = null;
|
||||
runtimeState.runtime.selectedEventIndex = null;
|
||||
@@ -170,7 +137,7 @@ type RundownData = {
|
||||
*/
|
||||
export function updateRundownData(rundownData: RundownData) {
|
||||
// we keep this in private state since there is no UI use case for it
|
||||
runtimeState._rundown.totalDelay = rundownData.totalDelay;
|
||||
runtimeState._timer.totalDelay = rundownData.totalDelay;
|
||||
|
||||
runtimeState.runtime.numEvents = rundownData.numEvents;
|
||||
runtimeState.runtime.plannedStart = rundownData.firstStart;
|
||||
@@ -187,7 +154,10 @@ export function load(
|
||||
rundown: OntimeRundown,
|
||||
initialData?: Partial<TimerState & RestorePoint>,
|
||||
): boolean {
|
||||
softClear();
|
||||
// we need to persist the current block state across loads
|
||||
const prevCurrentBlock = { ...runtimeState.currentBlock };
|
||||
clear();
|
||||
runtimeState.currentBlock = prevCurrentBlock;
|
||||
|
||||
// filter rundown
|
||||
const timedEvents = filterTimedEvents(rundown);
|
||||
@@ -215,7 +185,6 @@ export function load(
|
||||
if (firstStart === null || typeof firstStart === 'number') {
|
||||
runtimeState.runtime.actualStart = firstStart;
|
||||
runtimeState.runtime.offset = getRuntimeOffset(runtimeState);
|
||||
runtimeState.runtime.relativeOffset = getRelativeOffset(runtimeState);
|
||||
runtimeState.runtime.expectedEnd = getExpectedEnd(runtimeState);
|
||||
}
|
||||
if (typeof initialData.blockStartAt === 'number') {
|
||||
@@ -421,7 +390,6 @@ export function start(state: RuntimeState = runtimeState): boolean {
|
||||
|
||||
// update offset
|
||||
state.runtime.offset = getRuntimeOffset(state);
|
||||
state.runtime.relativeOffset = getRelativeOffset(state);
|
||||
state.runtime.expectedEnd = state.runtime.plannedEnd - state.runtime.offset;
|
||||
return true;
|
||||
}
|
||||
@@ -441,7 +409,9 @@ export function stop(state: RuntimeState = runtimeState): boolean {
|
||||
if (state.timer.playback === Playback.Stop) {
|
||||
return false;
|
||||
}
|
||||
hardClear();
|
||||
clear();
|
||||
runtimeState.runtime.actualStart = null;
|
||||
runtimeState.runtime.expectedEnd = null;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -483,7 +453,6 @@ export function addTime(amount: number) {
|
||||
|
||||
// update runtime delays: over - under
|
||||
runtimeState.runtime.offset = getRuntimeOffset(runtimeState);
|
||||
runtimeState.runtime.relativeOffset = getRelativeOffset(runtimeState);
|
||||
runtimeState.runtime.expectedEnd = getExpectedEnd(runtimeState);
|
||||
|
||||
return true;
|
||||
@@ -529,7 +498,6 @@ export function update(): UpdateResult {
|
||||
|
||||
// update runtime, needs up-to-date timer state
|
||||
runtimeState.runtime.offset = getRuntimeOffset(runtimeState);
|
||||
runtimeState.runtime.relativeOffset = getRelativeOffset(runtimeState);
|
||||
runtimeState.runtime.expectedEnd = getExpectedEnd(runtimeState);
|
||||
|
||||
const finishedNow =
|
||||
@@ -636,7 +604,9 @@ export function roll(rundown: OntimeRundown, offset = 0): { eventId: MaybeString
|
||||
}
|
||||
|
||||
// we need to persist the current block state across loads
|
||||
softClear();
|
||||
const prevCurrentBlock = { ...runtimeState.currentBlock };
|
||||
clear();
|
||||
runtimeState.currentBlock = prevCurrentBlock;
|
||||
|
||||
//account for offset but we only keep it if passed to us
|
||||
runtimeState.runtime.offset = offset;
|
||||
@@ -726,7 +696,3 @@ export function loadBlock(rundown: OntimeRundown, state = runtimeState) {
|
||||
// update the block anyway
|
||||
state.currentBlock.block = newCurrentBlock === null ? null : { ...newCurrentBlock };
|
||||
}
|
||||
|
||||
export function setOffsetMode(mode: OffsetMode) {
|
||||
runtimeState.runtime.offsetMode = mode;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { expect, test } from '@playwright/test';
|
||||
|
||||
test('time until absolute', async ({ page }) => {
|
||||
test('linked time until', async ({ page }) => {
|
||||
await page.goto('http://localhost:4001/editor');
|
||||
|
||||
await page.getByRole('button', { name: 'Clear rundown' }).click();
|
||||
@@ -11,46 +11,7 @@ test('time until absolute', async ({ page }) => {
|
||||
await page.getByRole('button', { name: 'Event', exact: true }).nth(1).click();
|
||||
await page.getByRole('button', { name: 'Event', exact: true }).nth(1).click();
|
||||
|
||||
await page.getByRole('button', { name: 'Absolute' }).click();
|
||||
await page.getByTestId('entry-1').getByLabel('Start event').click();
|
||||
await expect(page.getByTestId('offset')).not.toContainText('00:00:00'); // This might be a bad test requires that the test is not run at 0h
|
||||
await page.getByLabel('Pause event').click();
|
||||
|
||||
await expect(page.getByTestId('entry-2').locator('#event-block')).toContainText('9m');
|
||||
await expect(page.getByTestId('entry-3').locator('#event-block')).toContainText('19m');
|
||||
await expect(page.getByTestId('entry-4').locator('#event-block')).toContainText('29m');
|
||||
|
||||
await page.getByTestId('entry-1').getByTestId('time-input-duration').click();
|
||||
await page.getByTestId('entry-1').getByTestId('time-input-duration').fill('6h');
|
||||
await page.getByTestId('entry-1').getByTestId('time-input-duration').press('Enter');
|
||||
|
||||
await expect(page.getByTestId('entry-2').locator('#event-block')).toContainText('5h59m');
|
||||
await expect(page.getByTestId('entry-3').locator('#event-block')).toContainText('6h9m');
|
||||
await expect(page.getByTestId('entry-4').locator('#event-block')).toContainText('6h19m');
|
||||
|
||||
await page.getByTestId('entry-1').getByTestId('time-input-duration').click();
|
||||
await page.getByTestId('entry-1').getByTestId('time-input-duration').fill('30s');
|
||||
await page.getByTestId('entry-1').getByTestId('time-input-duration').press('Enter');
|
||||
|
||||
await expect(page.getByTestId('entry-2').locator('#event-block')).toContainText('29s');
|
||||
await expect(page.getByTestId('entry-3').locator('#event-block')).toContainText('10m');
|
||||
await expect(page.getByTestId('entry-4').locator('#event-block')).toContainText('20m');
|
||||
});
|
||||
|
||||
test('time until relative', async ({ page }) => {
|
||||
await page.goto('http://localhost:4001/editor');
|
||||
|
||||
await page.getByRole('button', { name: 'Clear rundown' }).click();
|
||||
await page.getByRole('button', { name: 'Delete all' }).click();
|
||||
|
||||
await page.getByRole('button', { name: 'Create Event' }).click();
|
||||
await page.getByRole('button', { name: 'Event' }).nth(4).click();
|
||||
await page.getByRole('button', { name: 'Event', exact: true }).nth(1).click();
|
||||
await page.getByRole('button', { name: 'Event', exact: true }).nth(1).click();
|
||||
|
||||
await page.getByRole('button', { name: 'Relative' }).click();
|
||||
await page.getByTestId('entry-1').getByLabel('Start event').click();
|
||||
await expect(page.getByTestId('offset')).toContainText('00:00:00'); // This might be a bad test as it ruires the evaluation to happen within 1s
|
||||
await page.getByLabel('Pause event').click();
|
||||
|
||||
await expect(page.getByTestId('entry-2').locator('#event-block')).toContainText('9m');
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "ontime",
|
||||
"version": "3.14.2",
|
||||
"version": "3.14.4",
|
||||
"description": "Time keeping for live events",
|
||||
"keywords": [
|
||||
"ontime",
|
||||
|
||||
@@ -1,18 +1,11 @@
|
||||
import type { MaybeNumber } from '../../utils/utils.type.js';
|
||||
|
||||
export enum OffsetMode {
|
||||
Absolute = 'absolute',
|
||||
Relative = 'relative',
|
||||
}
|
||||
|
||||
export type Runtime = {
|
||||
numEvents: number;
|
||||
selectedEventIndex: MaybeNumber;
|
||||
offset: number;
|
||||
relativeOffset: number;
|
||||
plannedStart: MaybeNumber;
|
||||
actualStart: MaybeNumber;
|
||||
plannedEnd: MaybeNumber;
|
||||
expectedEnd: MaybeNumber;
|
||||
offsetMode: OffsetMode;
|
||||
};
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import { SimpleDirection, SimplePlayback } from './AuxTimer.type.js';
|
||||
import { Playback } from './Playback.type.js';
|
||||
import { OffsetMode } from './Runtime.type.js';
|
||||
import type { RuntimeStore } from './RuntimeStore.type.js';
|
||||
import { TimerPhase } from './TimerState.type.js';
|
||||
|
||||
export const runtimeStorePlaceholder: Readonly<RuntimeStore> = {
|
||||
export const runtimeStorePlaceholder: RuntimeStore = {
|
||||
clock: 0,
|
||||
timer: {
|
||||
addedTime: 0,
|
||||
@@ -33,12 +32,10 @@ export const runtimeStorePlaceholder: Readonly<RuntimeStore> = {
|
||||
selectedEventIndex: null, // changes if rundown changes or we load a new event
|
||||
numEvents: 0, // change initiated by user
|
||||
offset: 0, // changes at runtime
|
||||
relativeOffset: 0, // changes at runtime
|
||||
plannedStart: 0, // only changes if event changes
|
||||
plannedEnd: 0, // only changes if event changes, overflows over dayInMs
|
||||
actualStart: null, // set once we start the timer
|
||||
expectedEnd: null, // changes with runtime, based on offset, overflows over dayInMs
|
||||
offsetMode: OffsetMode.Absolute,
|
||||
},
|
||||
currentBlock: {
|
||||
block: null,
|
||||
|
||||
@@ -87,7 +87,6 @@ export { TimerLifeCycle, timerLifecycleValues } from './definitions/core/TimerLi
|
||||
export type { TimerMessage, MessageState, SecondarySource } from './definitions/runtime/MessageControl.type.js';
|
||||
|
||||
export type { Runtime } from './definitions/runtime/Runtime.type.js';
|
||||
export { OffsetMode } from './definitions/runtime/Runtime.type.js';
|
||||
export type { RuntimeStore } from './definitions/runtime/RuntimeStore.type.js';
|
||||
export { runtimeStorePlaceholder } from './definitions/runtime/RuntimeStore.js';
|
||||
export { type TimerState, TimerPhase } from './definitions/runtime/TimerState.type.js';
|
||||
|
||||
@@ -276,20 +276,32 @@ export function getPreviousEventNormal(
|
||||
export const swapEventData = (eventA: OntimeEvent, eventB: OntimeEvent): { newA: OntimeEvent; newB: OntimeEvent } => {
|
||||
const newA = {
|
||||
...eventB,
|
||||
// events keep the ID
|
||||
id: eventA.id,
|
||||
// events keep the schedule
|
||||
timeStart: eventA.timeStart,
|
||||
timeEnd: eventA.timeEnd,
|
||||
duration: eventA.duration,
|
||||
linkStart: eventA.linkStart,
|
||||
// keep schedule metadata
|
||||
delay: eventA.delay,
|
||||
gap: eventA.gap,
|
||||
dayOffset: eventA.dayOffset,
|
||||
};
|
||||
|
||||
const newB = {
|
||||
...eventA,
|
||||
// events keep the ID
|
||||
id: eventB.id,
|
||||
// events keep the schedule
|
||||
timeStart: eventB.timeStart,
|
||||
timeEnd: eventB.timeEnd,
|
||||
duration: eventB.duration,
|
||||
linkStart: eventB.linkStart,
|
||||
// keep schedule metadata
|
||||
delay: eventB.delay,
|
||||
gap: eventB.gap,
|
||||
dayOffset: eventB.dayOffset,
|
||||
};
|
||||
|
||||
return { newA, newB };
|
||||
|
||||
Generated
+21
-34
@@ -113,9 +113,6 @@ importers:
|
||||
'@mantine/hooks':
|
||||
specifier: ^7.13.3
|
||||
version: 7.13.3(react@18.3.1)
|
||||
'@react-icons/all-files':
|
||||
specifier: ^4.1.0
|
||||
version: 4.1.0(react@18.3.1)
|
||||
'@sentry/react':
|
||||
specifier: ^8.43.0
|
||||
version: 8.45.0(react@18.3.1)
|
||||
@@ -161,6 +158,9 @@ importers:
|
||||
react-hook-form:
|
||||
specifier: ^7.53.1
|
||||
version: 7.53.1(react@18.3.1)
|
||||
react-icons:
|
||||
specifier: 5.4.0
|
||||
version: 5.4.0(react@18.3.1)
|
||||
react-qr-code:
|
||||
specifier: ^2.0.12
|
||||
version: 2.0.12(react@18.3.1)
|
||||
@@ -1670,11 +1670,6 @@ packages:
|
||||
'@popperjs/core@2.11.8':
|
||||
resolution: {integrity: sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==}
|
||||
|
||||
'@react-icons/all-files@4.1.0':
|
||||
resolution: {integrity: sha512-hxBI2UOuVaI3O/BhQfhtb4kcGn9ft12RWAFVMUeNjqqhLsHvFtzIkFaptBJpFDANTKoDfdVoHTKZDlwKCACbMQ==}
|
||||
peerDependencies:
|
||||
react: '*'
|
||||
|
||||
'@remix-run/router@1.2.1':
|
||||
resolution: {integrity: sha512-XiY0IsyHR+DXYS5vBxpoBe/8veTeoRpMHP+vDosLZxL5bnpetzI0igkxkLZS235ldLzyfkxF+2divEwWHP3vMQ==}
|
||||
engines: {node: '>=14'}
|
||||
@@ -2069,9 +2064,6 @@ packages:
|
||||
'@types/multer@1.4.11':
|
||||
resolution: {integrity: sha512-svK240gr6LVWvv3YGyhLlA+6LRRWA4mnGIU7RcNmgjBYFl6665wcXrRfxGp5tEPVHUNm5FMcmq7too9bxCwX/w==}
|
||||
|
||||
'@types/node@20.14.10':
|
||||
resolution: {integrity: sha512-MdiXf+nDuMvY0gJKxyfZ7/6UFsETO7mGKF54MVD/ekJS6HdFtpZFBgrh6Pseu64XTb2MLyFPlbW6hj8HYRQNOQ==}
|
||||
|
||||
'@types/node@20.17.16':
|
||||
resolution: {integrity: sha512-vOTpLduLkZXePLxHiHsBLp98mHGnl8RptV4YAO3HfKO5UHjDvySGbxKtpYfy8Sx5+WKcgc45qNreJJRVM3L6mw==}
|
||||
|
||||
@@ -4232,6 +4224,11 @@ packages:
|
||||
peerDependencies:
|
||||
react: ^16.8.0 || ^17 || ^18 || ^19
|
||||
|
||||
react-icons@5.4.0:
|
||||
resolution: {integrity: sha512-7eltJxgVt7X64oHh6wSWNwwbKTCtMfK35hcjvJS0yxEAhPM8oUKdS3+kqaW1vicIltw+kR2unHaa12S9pPALoQ==}
|
||||
peerDependencies:
|
||||
react: '*'
|
||||
|
||||
react-is@16.13.1:
|
||||
resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==}
|
||||
|
||||
@@ -4797,9 +4794,6 @@ packages:
|
||||
unbox-primitive@1.0.2:
|
||||
resolution: {integrity: sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==}
|
||||
|
||||
undici-types@5.26.5:
|
||||
resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==}
|
||||
|
||||
undici-types@6.19.8:
|
||||
resolution: {integrity: sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==}
|
||||
|
||||
@@ -6456,10 +6450,6 @@ snapshots:
|
||||
|
||||
'@popperjs/core@2.11.8': {}
|
||||
|
||||
'@react-icons/all-files@4.1.0(react@18.3.1)':
|
||||
dependencies:
|
||||
react: 18.3.1
|
||||
|
||||
'@remix-run/router@1.2.1': {}
|
||||
|
||||
'@rollup/pluginutils@5.1.0(rollup@4.17.2)':
|
||||
@@ -6772,7 +6762,7 @@ snapshots:
|
||||
'@types/body-parser@1.19.2':
|
||||
dependencies:
|
||||
'@types/connect': 3.4.35
|
||||
'@types/node': 20.17.16
|
||||
'@types/node': 22.10.10
|
||||
|
||||
'@types/cacheable-request@6.0.3':
|
||||
dependencies:
|
||||
@@ -6793,11 +6783,11 @@ snapshots:
|
||||
|
||||
'@types/connect@3.4.35':
|
||||
dependencies:
|
||||
'@types/node': 20.17.16
|
||||
'@types/node': 22.10.10
|
||||
|
||||
'@types/cors@2.8.17':
|
||||
dependencies:
|
||||
'@types/node': 20.17.16
|
||||
'@types/node': 22.10.10
|
||||
|
||||
'@types/debug@4.1.12':
|
||||
dependencies:
|
||||
@@ -6807,7 +6797,7 @@ snapshots:
|
||||
|
||||
'@types/express-serve-static-core@4.17.33':
|
||||
dependencies:
|
||||
'@types/node': 20.17.16
|
||||
'@types/node': 22.10.10
|
||||
'@types/qs': 6.9.7
|
||||
'@types/range-parser': 1.2.4
|
||||
|
||||
@@ -6844,10 +6834,6 @@ snapshots:
|
||||
dependencies:
|
||||
'@types/express': 4.17.17
|
||||
|
||||
'@types/node@20.14.10':
|
||||
dependencies:
|
||||
undici-types: 5.26.5
|
||||
|
||||
'@types/node@20.17.16':
|
||||
dependencies:
|
||||
undici-types: 6.19.8
|
||||
@@ -6891,18 +6877,18 @@ snapshots:
|
||||
'@types/serve-static@1.15.0':
|
||||
dependencies:
|
||||
'@types/mime': 3.0.1
|
||||
'@types/node': 20.17.16
|
||||
'@types/node': 22.10.10
|
||||
|
||||
'@types/verror@1.10.9':
|
||||
optional: true
|
||||
|
||||
'@types/websocket@1.0.5':
|
||||
dependencies:
|
||||
'@types/node': 20.17.16
|
||||
'@types/node': 22.10.10
|
||||
|
||||
'@types/ws@8.5.10':
|
||||
dependencies:
|
||||
'@types/node': 20.17.16
|
||||
'@types/node': 22.10.10
|
||||
|
||||
'@types/yauzl@2.10.3':
|
||||
dependencies:
|
||||
@@ -7451,7 +7437,6 @@ snapshots:
|
||||
dependencies:
|
||||
es-errors: 1.3.0
|
||||
function-bind: 1.1.2
|
||||
optional: true
|
||||
|
||||
call-bind@1.0.2:
|
||||
dependencies:
|
||||
@@ -7795,7 +7780,7 @@ snapshots:
|
||||
|
||||
dunder-proto@1.0.1:
|
||||
dependencies:
|
||||
call-bind-apply-helpers: 1.0.1
|
||||
call-bind-apply-helpers: 1.0.2
|
||||
es-errors: 1.3.0
|
||||
gopd: 1.2.0
|
||||
|
||||
@@ -7853,7 +7838,7 @@ snapshots:
|
||||
electron@31.2.0:
|
||||
dependencies:
|
||||
'@electron/get': 2.0.3
|
||||
'@types/node': 20.14.10
|
||||
'@types/node': 20.17.16
|
||||
extract-zip: 2.0.1
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
@@ -9401,6 +9386,10 @@ snapshots:
|
||||
dependencies:
|
||||
react: 18.3.1
|
||||
|
||||
react-icons@5.4.0(react@18.3.1):
|
||||
dependencies:
|
||||
react: 18.3.1
|
||||
|
||||
react-is@16.13.1: {}
|
||||
|
||||
react-qr-code@2.0.12(react@18.3.1):
|
||||
@@ -10004,8 +9993,6 @@ snapshots:
|
||||
has-symbols: 1.0.3
|
||||
which-boxed-primitive: 1.0.2
|
||||
|
||||
undici-types@5.26.5: {}
|
||||
|
||||
undici-types@6.19.8: {}
|
||||
|
||||
undici-types@6.20.0: {}
|
||||
|
||||
Reference in New Issue
Block a user