mirror of
https://github.com/cpvalente/ontime.git
synced 2026-08-10 18:03:47 +00:00
Compare commits
17 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e6e1c38355 | |||
| 73afbacdd7 | |||
| d08452d613 | |||
| 4ebe6ede59 | |||
| 09a32e7612 | |||
| d81f5fcfe1 | |||
| 0a6542df76 | |||
| b2eb140ee2 | |||
| e28f692d3d | |||
| dc73e7e637 | |||
| b81e3a4ba5 | |||
| 6fc7b15d23 | |||
| 99dd4b4d5c | |||
| b60b1beeec | |||
| eab916f410 | |||
| 069a003787 | |||
| 9b08080b7a |
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@getontime/cli",
|
"name": "@getontime/cli",
|
||||||
"version": "3.14.0",
|
"version": "3.14.3",
|
||||||
"author": "Carlos Valente",
|
"author": "Carlos Valente",
|
||||||
"description": "Time keeping for live events",
|
"description": "Time keeping for live events",
|
||||||
"repository": "https://github.com/cpvalente/ontime",
|
"repository": "https://github.com/cpvalente/ontime",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "ontime-ui",
|
"name": "ontime-ui",
|
||||||
"version": "3.14.0",
|
"version": "3.14.3",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -13,7 +13,6 @@
|
|||||||
"@emotion/styled": "^11.10.6",
|
"@emotion/styled": "^11.10.6",
|
||||||
"@fontsource/open-sans": "^5.0.28",
|
"@fontsource/open-sans": "^5.0.28",
|
||||||
"@mantine/hooks": "^7.13.3",
|
"@mantine/hooks": "^7.13.3",
|
||||||
"@react-icons/all-files": "^4.1.0",
|
|
||||||
"@sentry/react": "^8.43.0",
|
"@sentry/react": "^8.43.0",
|
||||||
"@table-nav/react": "^0.0.7",
|
"@table-nav/react": "^0.0.7",
|
||||||
"@tanstack/react-query": "^5.62.7",
|
"@tanstack/react-query": "^5.62.7",
|
||||||
@@ -29,6 +28,7 @@
|
|||||||
"react-dom": "^18.3.1",
|
"react-dom": "^18.3.1",
|
||||||
"react-fast-compare": "^3.2.2",
|
"react-fast-compare": "^3.2.2",
|
||||||
"react-hook-form": "^7.53.1",
|
"react-hook-form": "^7.53.1",
|
||||||
|
"react-icons": "5.4.0",
|
||||||
"react-qr-code": "^2.0.12",
|
"react-qr-code": "^2.0.12",
|
||||||
"react-router-dom": "^6.3.0",
|
"react-router-dom": "^6.3.0",
|
||||||
"web-vitals": "^3.1.1",
|
"web-vitals": "^3.1.1",
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
|
import { IoArrowForward } from 'react-icons/io5';
|
||||||
import {
|
import {
|
||||||
IconButton,
|
IconButton,
|
||||||
Input,
|
Input,
|
||||||
@@ -10,7 +11,6 @@ import {
|
|||||||
ModalOverlay,
|
ModalOverlay,
|
||||||
Select,
|
Select,
|
||||||
} from '@chakra-ui/react';
|
} from '@chakra-ui/react';
|
||||||
import { IoArrowForward } from '@react-icons/all-files/io5/IoArrowForward';
|
|
||||||
|
|
||||||
import { navigatorConstants } from '../../../viewerConfig';
|
import { navigatorConstants } from '../../../viewerConfig';
|
||||||
import { setClientRemote } from '../../hooks/useSocket';
|
import { setClientRemote } from '../../hooks/useSocket';
|
||||||
|
|||||||
@@ -2,8 +2,8 @@
|
|||||||
// https://github.com/lukasbach/chakra-ui-contextmenu/blob/main/src/ContextMenu.tsx
|
// https://github.com/lukasbach/chakra-ui-contextmenu/blob/main/src/ContextMenu.tsx
|
||||||
|
|
||||||
import { ReactElement } from 'react';
|
import { ReactElement } from 'react';
|
||||||
|
import { IconType } from 'react-icons';
|
||||||
import { Menu, MenuButton, MenuGroup, MenuList } from '@chakra-ui/react';
|
import { Menu, MenuButton, MenuGroup, MenuList } from '@chakra-ui/react';
|
||||||
import { IconType } from '@react-icons/all-files';
|
|
||||||
import { create } from 'zustand';
|
import { create } from 'zustand';
|
||||||
|
|
||||||
import { ContextMenuOption } from './ContextMenuOption';
|
import { ContextMenuOption } from './ContextMenuOption';
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { PropsWithChildren, useState } from 'react';
|
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 { 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 { tooltipDelayFast } from '../../../ontimeConfig';
|
||||||
import { Size } from '../../models/Util.type';
|
import { Size } from '../../models/Util.type';
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
|
import { IoChevronDown, IoChevronUp } from 'react-icons/io5';
|
||||||
import { Tooltip } from '@chakra-ui/react';
|
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 { tooltipDelayFast } from '../../../ontimeConfig';
|
||||||
import { millisToDelayString } from '../../utils/dateConfig';
|
import { millisToDelayString } from '../../utils/dateConfig';
|
||||||
|
|||||||
@@ -26,17 +26,22 @@ class ErrorBoundary extends React.Component {
|
|||||||
error,
|
error,
|
||||||
errorInfo: info,
|
errorInfo: info,
|
||||||
});
|
});
|
||||||
|
this.reportContent = `${error} ${info.componentStack}`;
|
||||||
|
|
||||||
|
const appState = runtimeStore.getState();
|
||||||
|
if (appState.ping < 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
Sentry.withScope((scope) => {
|
Sentry.withScope((scope) => {
|
||||||
scope.setExtras({
|
scope.setExtras({
|
||||||
error,
|
error,
|
||||||
store: runtimeStore.getState(),
|
store: appState,
|
||||||
hasSocket: { hasConnected, shouldReconnect, reconnectAttempts },
|
hasSocket: { hasConnected, shouldReconnect, reconnectAttempts },
|
||||||
});
|
});
|
||||||
const eventId = Sentry.captureException(error);
|
const eventId = Sentry.captureException(error);
|
||||||
this.setState({ eventId, info });
|
this.setState({ eventId, info });
|
||||||
});
|
});
|
||||||
this.reportContent = `${error} ${info.componentStack}`;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { PropsWithChildren } from 'react';
|
import { PropsWithChildren } from 'react';
|
||||||
import { IoAlertCircle } from '@react-icons/all-files/io5/IoAlertCircle';
|
import { IoAlertCircle } from 'react-icons/io5';
|
||||||
|
|
||||||
import { cx } from '../../utils/styleUtils';
|
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';
|
import { cx } from '../../../utils/styleUtils';
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { useCallback } from 'react';
|
import { useCallback } from 'react';
|
||||||
import { useController, UseControllerProps } from 'react-hook-form';
|
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 { ViewSettings } from 'ontime-types';
|
||||||
|
|
||||||
import { debounce } from '../../../utils/debounce';
|
import { debounce } from '../../../utils/debounce';
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { MouseEvent, ReactNode } from 'react';
|
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 { openLink } from '../../../utils/linkUtils';
|
||||||
import { cx } from '../../../utils/styleUtils';
|
import { cx } from '../../../utils/styleUtils';
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { IoApps } from '@react-icons/all-files/io5/IoApps';
|
import { IoApps } from 'react-icons/io5';
|
||||||
import { IoSettingsOutline } from '@react-icons/all-files/io5/IoSettingsOutline';
|
import { IoSettingsOutline } from 'react-icons/io5';
|
||||||
|
|
||||||
import { useFadeOutOnInactivity } from '../../hooks/useFadeOutOnInactivity';
|
import { useFadeOutOnInactivity } from '../../hooks/useFadeOutOnInactivity';
|
||||||
import { cx } from '../../utils/styleUtils';
|
import { cx } from '../../utils/styleUtils';
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { memo, PropsWithChildren } from 'react';
|
import { memo, PropsWithChildren } from 'react';
|
||||||
import { createPortal } from 'react-dom';
|
import { createPortal } from 'react-dom';
|
||||||
|
import { IoArrowUp, IoContract, IoExpand, IoLockClosedOutline, IoSwapVertical } from 'react-icons/io5';
|
||||||
import { Link, useLocation } from 'react-router-dom';
|
import { Link, useLocation } from 'react-router-dom';
|
||||||
import {
|
import {
|
||||||
Drawer,
|
Drawer,
|
||||||
@@ -11,11 +12,6 @@ import {
|
|||||||
useDisclosure,
|
useDisclosure,
|
||||||
} from '@chakra-ui/react';
|
} from '@chakra-ui/react';
|
||||||
import { useFullscreen } from '@mantine/hooks';
|
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 { isLocalhost } from '../../../externals';
|
||||||
import { navigatorConstants } from '../../../viewerConfig';
|
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 { useFadeOutOnInactivity } from '../../hooks/useFadeOutOnInactivity';
|
||||||
import { cx } from '../../utils/styleUtils';
|
import { cx } from '../../utils/styleUtils';
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
|
import { IoPause, IoPlay, IoStop } from 'react-icons/io5';
|
||||||
import { Tooltip } from '@chakra-ui/react';
|
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 { Playback } from 'ontime-types';
|
||||||
|
|
||||||
import { tooltipDelayFast } from '../../../ontimeConfig';
|
import { tooltipDelayFast } from '../../../ontimeConfig';
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { useCallback, useEffect, useState } from 'react';
|
import { useCallback, useEffect, useState } from 'react';
|
||||||
|
import { IoCheckmark } from 'react-icons/io5';
|
||||||
import { IconButton, PinInput, PinInputField } from '@chakra-ui/react';
|
import { IconButton, PinInput, PinInputField } from '@chakra-ui/react';
|
||||||
import { IoCheckmark } from '@react-icons/all-files/io5/IoCheckmark';
|
|
||||||
|
|
||||||
import style from './ProtectRoute.module.scss';
|
import style from './ProtectRoute.module.scss';
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
|
import { IoChevronDown } from 'react-icons/io5';
|
||||||
import { useSearchParams } from 'react-router-dom';
|
import { useSearchParams } from 'react-router-dom';
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
@@ -13,7 +14,6 @@ import {
|
|||||||
Select,
|
Select,
|
||||||
Switch,
|
Switch,
|
||||||
} from '@chakra-ui/react';
|
} from '@chakra-ui/react';
|
||||||
import { IoChevronDown } from '@react-icons/all-files/io5/IoChevronDown';
|
|
||||||
|
|
||||||
import { isStringBoolean } from '../../../features/viewers/common/viewUtils';
|
import { isStringBoolean } from '../../../features/viewers/common/viewUtils';
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
|
import { IoChevronDown } from 'react-icons/io5';
|
||||||
import { useLocalStorage } from '@mantine/hooks';
|
import { useLocalStorage } from '@mantine/hooks';
|
||||||
import { IoChevronDown } from '@react-icons/all-files/io5/IoChevronDown';
|
|
||||||
|
|
||||||
import { cx } from '../../utils/styleUtils';
|
import { cx } from '../../utils/styleUtils';
|
||||||
|
|
||||||
|
|||||||
@@ -170,6 +170,12 @@ export const useTimelineStatus = createSelector((state: RuntimeStore) => ({
|
|||||||
offset: state.runtime.offset,
|
offset: state.runtime.offset,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
export const useTimeUntilData = createSelector((state: RuntimeStore) => ({
|
||||||
|
clock: state.clock,
|
||||||
|
offset: state.runtime.offset,
|
||||||
|
currentDay: state.eventNow?.dayOffset ?? 0, //The day of the currently running event
|
||||||
|
}));
|
||||||
|
|
||||||
export const useRuntimeOffset = createSelector((state: RuntimeStore) => ({
|
export const useRuntimeOffset = createSelector((state: RuntimeStore) => ({
|
||||||
offset: state.runtime.offset,
|
offset: state.runtime.offset,
|
||||||
}));
|
}));
|
||||||
|
|||||||
@@ -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';
|
import { cloneEvent } from '../eventsManager';
|
||||||
|
|
||||||
describe('cloneEvent()', () => {
|
describe('cloneEvent()', () => {
|
||||||
it('creates a stem from a given event', () => {
|
it('creates a stem from a given event', () => {
|
||||||
const original = {
|
const original: OntimeEvent = {
|
||||||
id: 'unique',
|
id: 'unique',
|
||||||
type: SupportedEvent.Event,
|
type: SupportedEvent.Event,
|
||||||
title: 'title',
|
title: 'title',
|
||||||
@@ -14,6 +14,9 @@ describe('cloneEvent()', () => {
|
|||||||
duration: 10,
|
duration: 10,
|
||||||
timeEnd: 10,
|
timeEnd: 10,
|
||||||
timerType: TimerType.CountDown,
|
timerType: TimerType.CountDown,
|
||||||
|
timeStrategy: TimeStrategy.LockEnd,
|
||||||
|
linkStart: null,
|
||||||
|
countToEnd: false,
|
||||||
endAction: EndAction.None,
|
endAction: EndAction.None,
|
||||||
isPublic: false,
|
isPublic: false,
|
||||||
skip: false,
|
skip: false,
|
||||||
@@ -21,29 +24,39 @@ describe('cloneEvent()', () => {
|
|||||||
revision: 10,
|
revision: 10,
|
||||||
timeWarning: 120000,
|
timeWarning: 120000,
|
||||||
timeDanger: 60000,
|
timeDanger: 60000,
|
||||||
|
delay: 0,
|
||||||
|
dayOffset: 0,
|
||||||
|
gap: 0,
|
||||||
custom: {
|
custom: {
|
||||||
lighting: '3',
|
lighting: '3',
|
||||||
} as EventCustomFields,
|
} as EventCustomFields,
|
||||||
} as OntimeEvent;
|
};
|
||||||
|
|
||||||
const cloned = cloneEvent(original);
|
const cloned = cloneEvent(original);
|
||||||
expect(cloned).not.toBe(original);
|
expect(cloned).not.toBe(original);
|
||||||
// @ts-expect-error -- safeguarding this
|
expect(cloned.custom).not.toBe(original.custom);
|
||||||
expect(cloned?.id).toBe(undefined);
|
|
||||||
expect(cloned.title).toBe(original.title);
|
expect(cloned).toMatchObject({
|
||||||
expect(cloned.note).toBe(original.note);
|
type: SupportedEvent.Event,
|
||||||
expect(cloned.endAction).toBe(original.endAction);
|
title: original.title,
|
||||||
expect(cloned.timerType).toBe(original.timerType);
|
note: original.note,
|
||||||
expect(cloned.timeStart).toBe(original.timeStart);
|
timeStart: original.timeStart,
|
||||||
expect(cloned.timeEnd).toBe(original.timeEnd);
|
duration: original.duration,
|
||||||
expect(cloned.duration).toBe(original.duration);
|
timeEnd: original.timeEnd,
|
||||||
expect(cloned.isPublic).toBe(original.isPublic);
|
timerType: original.timerType,
|
||||||
expect(cloned.skip).toBe(original.skip);
|
timeStrategy: original.timeStrategy,
|
||||||
expect(cloned.colour).toBe(original.colour);
|
countToEnd: original.countToEnd,
|
||||||
expect(cloned.type).toBe(SupportedEvent.Event);
|
linkStart: original.linkStart,
|
||||||
expect(cloned.revision).toBe(0);
|
endAction: original.endAction,
|
||||||
expect(cloned.timeWarning).toBe(original.timeWarning);
|
isPublic: original.isPublic,
|
||||||
expect(cloned.timeDanger).toBe(original.timeDanger);
|
skip: original.skip,
|
||||||
expect(cloned.custom).toStrictEqual({});
|
colour: original.colour,
|
||||||
|
revision: 0,
|
||||||
|
delay: original.delay,
|
||||||
|
dayOffset: original.dayOffset,
|
||||||
|
gap: 0,
|
||||||
|
timeWarning: original.timeWarning,
|
||||||
|
timeDanger: original.timeDanger,
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { formatTime, nowInMillis } from '../time';
|
import { calculateTimeUntilStart, formatTime, nowInMillis } from '../time';
|
||||||
|
|
||||||
describe('nowInMillis()', () => {
|
describe('nowInMillis()', () => {
|
||||||
it('should return the current time in milliseconds', () => {
|
it('should return the current time in milliseconds', () => {
|
||||||
@@ -38,3 +38,84 @@ describe('formatTime()', () => {
|
|||||||
expect(time).toStrictEqual('-01:00');
|
expect(time).toStrictEqual('-01:00');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('calculateTimeUntilStart()', () => {
|
||||||
|
test('ontime', () => {
|
||||||
|
const test = {
|
||||||
|
timeStart: 100,
|
||||||
|
dayOffset: 0,
|
||||||
|
delay: 0,
|
||||||
|
currentDay: 0,
|
||||||
|
totalGap: 0,
|
||||||
|
clock: 90,
|
||||||
|
offset: 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
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,
|
skip: event.skip,
|
||||||
colour: event.colour,
|
colour: event.colour,
|
||||||
revision: 0,
|
revision: 0,
|
||||||
delay: 0,
|
delay: event.delay, // the events will be collocated, so having the same metadata is a good start
|
||||||
dayOffset: 0,
|
dayOffset: event.dayOffset,
|
||||||
gap: 0,
|
gap: 0,
|
||||||
timeWarning: event.timeWarning,
|
timeWarning: event.timeWarning,
|
||||||
timeDanger: event.timeDanger,
|
timeDanger: event.timeDanger,
|
||||||
custom: {},
|
custom: { ...event.custom },
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
import { MaybeNumber, Settings, TimeFormat } from 'ontime-types';
|
import { MaybeNumber, OntimeEvent, Settings, TimeFormat } from 'ontime-types';
|
||||||
import { formatFromMillis, MILLIS_PER_HOUR, MILLIS_PER_MINUTE, MILLIS_PER_SECOND } from 'ontime-utils';
|
import { dayInMs, formatFromMillis, MILLIS_PER_HOUR, MILLIS_PER_MINUTE, MILLIS_PER_SECOND } from 'ontime-utils';
|
||||||
|
|
||||||
import { FORMAT_12, FORMAT_24 } from '../../viewerConfig';
|
import { FORMAT_12, FORMAT_24 } from '../../viewerConfig';
|
||||||
import { APP_SETTINGS } from '../api/constants';
|
import { APP_SETTINGS } from '../api/constants';
|
||||||
|
import { useTimeUntilData } from '../hooks/useSocket';
|
||||||
import { ontimeQueryClient } from '../queryClient';
|
import { ontimeQueryClient } from '../queryClient';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -125,3 +126,71 @@ export function formatDuration(duration: number, hideSeconds = true): string {
|
|||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param totalGap accumulated gap from the current event
|
||||||
|
* @param isLinkedToLoaded is this event part of a chain linking back to the current loaded event
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export function useTimeUntilStart(
|
||||||
|
// typed like this to make it very clear what the data is
|
||||||
|
data: Pick<OntimeEvent, 'timeStart' | 'dayOffset' | 'delay'> & {
|
||||||
|
totalGap: number;
|
||||||
|
isLinkedToLoaded: boolean;
|
||||||
|
},
|
||||||
|
): number {
|
||||||
|
const { offset, clock, currentDay } = useTimeUntilData();
|
||||||
|
return calculateTimeUntilStart({ ...data, currentDay, clock, offset });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param currentDay the day offset of the urrently running event
|
||||||
|
* @param totalGap accumulated gap from the current event
|
||||||
|
* @param isLinkedToLoaded is this event part of a chain linking back to the current loaded event
|
||||||
|
* @param clock
|
||||||
|
* @param offset
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export function calculateTimeUntilStart(
|
||||||
|
data: Pick<OntimeEvent, 'timeStart' | 'dayOffset' | 'delay'> & {
|
||||||
|
currentDay: number;
|
||||||
|
totalGap: number;
|
||||||
|
isLinkedToLoaded: boolean;
|
||||||
|
clock: number;
|
||||||
|
offset: number;
|
||||||
|
},
|
||||||
|
): number {
|
||||||
|
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;
|
||||||
|
|
||||||
|
const delayedStart = Math.max(0, timeStart + delay);
|
||||||
|
|
||||||
|
//The normalised start time of this event relative to the currently running event
|
||||||
|
const normalisedTimeStart = delayedStart + relativeDayOffset * dayInMs;
|
||||||
|
|
||||||
|
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 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
// otherwise consume as much of the offset as possible with the gap
|
||||||
|
const offsetTimeUntilBufferedByGaps = offsetTimeUntil - totalGap;
|
||||||
|
return offsetTimeUntilBufferedByGaps;
|
||||||
|
}
|
||||||
|
|||||||
@@ -19,9 +19,10 @@ export const buyMeACoffeeUrl = 'https://buymeacoffee.com/cpvalente';
|
|||||||
export const appVersion = version;
|
export const appVersion = version;
|
||||||
export const isProduction = import.meta.env.MODE === 'production';
|
export const isProduction = import.meta.env.MODE === 'production';
|
||||||
export const isDev = !isProduction;
|
export const isDev = !isProduction;
|
||||||
export const isLocalhost = window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1';
|
export const currentHostName = window.location.hostname;
|
||||||
|
export const isLocalhost = currentHostName === 'localhost' || currentHostName === '127.0.0.1';
|
||||||
export const isDockerImage = Boolean(import.meta.env.VITE_IS_DOCKER);
|
export const isDockerImage = Boolean(import.meta.env.VITE_IS_DOCKER);
|
||||||
export const isOntimeCloud = window.location.hostname.includes('cloud.getontime.no');
|
export const isOntimeCloud = currentHostName.includes('cloud.getontime.no');
|
||||||
|
|
||||||
// resolve entrypoint URLs
|
// resolve entrypoint URLs
|
||||||
|
|
||||||
@@ -75,4 +76,4 @@ function resolveBaseURI(): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return base;
|
return base;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { PropsWithChildren } from 'react';
|
import { PropsWithChildren } from 'react';
|
||||||
|
import { IoClose } from 'react-icons/io5';
|
||||||
import { Button } from '@chakra-ui/react';
|
import { Button } from '@chakra-ui/react';
|
||||||
import { IoClose } from '@react-icons/all-files/io5/IoClose';
|
|
||||||
|
|
||||||
import style from './PanelContent.module.scss';
|
import style from './PanelContent.module.scss';
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { HTMLAttributes, PropsWithChildren, ReactNode } from 'react';
|
import { HTMLAttributes, PropsWithChildren, ReactNode } from 'react';
|
||||||
|
import { IoAdd } from 'react-icons/io5';
|
||||||
import { Button } from '@chakra-ui/react';
|
import { Button } from '@chakra-ui/react';
|
||||||
import { IoAdd } from '@react-icons/all-files/io5/IoAdd';
|
|
||||||
|
|
||||||
import { cx } from '../../../common/utils/styleUtils';
|
import { cx } from '../../../common/utils/styleUtils';
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
import { useEffect, useMemo } from 'react';
|
import { useEffect, useMemo } from 'react';
|
||||||
import { Controller, useFieldArray, useForm } from 'react-hook-form';
|
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 { 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 {
|
import {
|
||||||
Automation,
|
Automation,
|
||||||
AutomationDTO,
|
AutomationDTO,
|
||||||
@@ -31,7 +30,7 @@ import OntimeActionForm from './OntimeActionForm';
|
|||||||
|
|
||||||
import style from './AutomationForm.module.scss';
|
import style from './AutomationForm.module.scss';
|
||||||
|
|
||||||
const integrationsDocsUrl = 'https://docs.getontime.no/api/integrations/#using-variables-in-integrations';
|
const integrationsDocsUrl = 'https://docs.getontime.no/api/automation/#using-variables-in-automation';
|
||||||
|
|
||||||
interface AutomationFormProps {
|
interface AutomationFormProps {
|
||||||
automation: Automation | AutomationDTO;
|
automation: Automation | AutomationDTO;
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
import { Fragment, useState } from 'react';
|
import { Fragment, useState } from 'react';
|
||||||
|
import { IoAdd, IoPencil, IoTrash } from 'react-icons/io5';
|
||||||
import { Button, IconButton } from '@chakra-ui/react';
|
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 { AutomationDTO, NormalisedAutomation } from 'ontime-types';
|
||||||
|
|
||||||
import { deleteAutomation } from '../../../../common/api/automation';
|
import { deleteAutomation } from '../../../../common/api/automation';
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { Fragment, useMemo, useState } from 'react';
|
import { Fragment, useMemo, useState } from 'react';
|
||||||
|
import { IoAdd } from 'react-icons/io5';
|
||||||
import { Button } from '@chakra-ui/react';
|
import { Button } from '@chakra-ui/react';
|
||||||
import { IoAdd } from '@react-icons/all-files/io5/IoAdd';
|
|
||||||
import { NormalisedAutomation, Trigger } from 'ontime-types';
|
import { NormalisedAutomation, Trigger } from 'ontime-types';
|
||||||
|
|
||||||
import { deleteTrigger } from '../../../../common/api/automation';
|
import { deleteTrigger } from '../../../../common/api/automation';
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
|
import { IoPencil, IoTrash, IoWarningOutline } from 'react-icons/io5';
|
||||||
import { IconButton } from '@chakra-ui/react';
|
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 { NormalisedAutomation, TimerLifeCycle } from 'ontime-types';
|
||||||
|
|
||||||
import Tag from '../../../../common/components/tag/Tag';
|
import Tag from '../../../../common/components/tag/Tag';
|
||||||
|
|||||||
+2
-2
@@ -1,6 +1,6 @@
|
|||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
|
import { IoTrashBin } from 'react-icons/io5';
|
||||||
import { Button } from '@chakra-ui/react';
|
import { Button } from '@chakra-ui/react';
|
||||||
import { IoTrashBin } from '@react-icons/all-files/io5/IoTrashBin';
|
|
||||||
|
|
||||||
import { deleteAllReport } from '../../../../common/api/report';
|
import { deleteAllReport } from '../../../../common/api/report';
|
||||||
import { createBlob, downloadBlob } from '../../../../common/api/utils';
|
import { createBlob, downloadBlob } from '../../../../common/api/utils';
|
||||||
@@ -94,7 +94,7 @@ export default function ReportSettings() {
|
|||||||
})();
|
})();
|
||||||
return (
|
return (
|
||||||
<tr key={entry.index}>
|
<tr key={entry.index}>
|
||||||
<th>{entry.index}</th>
|
<th>{entry.index + 1}</th>
|
||||||
<th>{entry.cue}</th>
|
<th>{entry.cue}</th>
|
||||||
<th>{entry.title}</th>
|
<th>{entry.title}</th>
|
||||||
<th className={cx([start && style[start]])}>{formatTime(entry.scheduledStart)}</th>
|
<th className={cx([start && style[start]])}>{formatTime(entry.scheduledStart)}</th>
|
||||||
|
|||||||
+1
-3
@@ -1,9 +1,7 @@
|
|||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
import { useFieldArray, useForm } from 'react-hook-form';
|
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 { 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 { URLPreset } from 'ontime-types';
|
||||||
|
|
||||||
import { postUrlPresets } from '../../../../common/api/urlPresets';
|
import { postUrlPresets } from '../../../../common/api/urlPresets';
|
||||||
|
|||||||
+1
-2
@@ -1,7 +1,6 @@
|
|||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
|
import { IoPencil, IoTrash } from 'react-icons/io5';
|
||||||
import { IconButton } from '@chakra-ui/react';
|
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 { CustomField, CustomFieldLabel } from 'ontime-types';
|
||||||
|
|
||||||
import CopyTag from '../../../../../common/components/copy-tag/CopyTag';
|
import CopyTag from '../../../../../common/components/copy-tag/CopyTag';
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
|
import { IoAdd } from 'react-icons/io5';
|
||||||
import { Button } from '@chakra-ui/react';
|
import { Button } from '@chakra-ui/react';
|
||||||
import { IoAdd } from '@react-icons/all-files/io5/IoAdd';
|
|
||||||
import { CustomField, CustomFieldLabel } from 'ontime-types';
|
import { CustomField, CustomFieldLabel } from 'ontime-types';
|
||||||
|
|
||||||
import { deleteCustomField, editCustomField, postCustomField } from '../../../../../common/api/customFields';
|
import { deleteCustomField, editCustomField, postCustomField } from '../../../../../common/api/customFields';
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { PropsWithChildren, useState } from 'react';
|
import { PropsWithChildren, useState } from 'react';
|
||||||
import { UseFormRegister } from 'react-hook-form';
|
import { UseFormRegister } from 'react-hook-form';
|
||||||
|
import { IoEyeOutline } from 'react-icons/io5';
|
||||||
import { IconButton, Input, InputGroup, InputRightElement } from '@chakra-ui/react';
|
import { IconButton, Input, InputGroup, InputRightElement } from '@chakra-ui/react';
|
||||||
import { IoEyeOutline } from '@react-icons/all-files/io5/IoEyeOutline';
|
|
||||||
import { Settings } from 'ontime-types';
|
import { Settings } from 'ontime-types';
|
||||||
|
|
||||||
import { isAlphanumeric } from '../../../../common/utils/regex';
|
import { isAlphanumeric } from '../../../../common/utils/regex';
|
||||||
|
|||||||
@@ -3,3 +3,12 @@
|
|||||||
background: $ui-white;
|
background: $ui-white;
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.column {
|
||||||
|
margin-top: 1rem;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.5rem;
|
||||||
|
color: $label-gray;
|
||||||
|
user-select: text;
|
||||||
|
}
|
||||||
|
|||||||
@@ -6,13 +6,12 @@ import { Button, Select, Switch } from '@chakra-ui/react';
|
|||||||
import { generateUrl } from '../../../../common/api/session';
|
import { generateUrl } from '../../../../common/api/session';
|
||||||
import { maybeAxiosError } from '../../../../common/api/utils';
|
import { maybeAxiosError } from '../../../../common/api/utils';
|
||||||
import Info from '../../../../common/components/info/Info';
|
import Info from '../../../../common/components/info/Info';
|
||||||
import ExternalLink from '../../../../common/components/link/external-link/ExternalLink';
|
|
||||||
import useInfo from '../../../../common/hooks-query/useInfo';
|
import useInfo from '../../../../common/hooks-query/useInfo';
|
||||||
import useUrlPresets from '../../../../common/hooks-query/useUrlPresets';
|
import useUrlPresets from '../../../../common/hooks-query/useUrlPresets';
|
||||||
import copyToClipboard from '../../../../common/utils/copyToClipboard';
|
import copyToClipboard from '../../../../common/utils/copyToClipboard';
|
||||||
import { preventEscape } from '../../../../common/utils/keyEvent';
|
import { preventEscape } from '../../../../common/utils/keyEvent';
|
||||||
import { linkToOtherHost } from '../../../../common/utils/linkUtils';
|
import { linkToOtherHost } from '../../../../common/utils/linkUtils';
|
||||||
import { serverURL } from '../../../../externals';
|
import { currentHostName, isOntimeCloud, serverURL } from '../../../../externals';
|
||||||
import * as Panel from '../../panel-utils/PanelUtils';
|
import * as Panel from '../../panel-utils/PanelUtils';
|
||||||
|
|
||||||
import style from './GenerateLinkForm.module.scss';
|
import style from './GenerateLinkForm.module.scss';
|
||||||
@@ -39,6 +38,12 @@ export default function GenerateLinkForm() {
|
|||||||
formState: { errors },
|
formState: { errors },
|
||||||
} = useForm<GenerateLinkFormOptions>({
|
} = useForm<GenerateLinkFormOptions>({
|
||||||
mode: 'onChange',
|
mode: 'onChange',
|
||||||
|
defaultValues: {
|
||||||
|
baseUrl: currentHostName,
|
||||||
|
path: '',
|
||||||
|
lock: false,
|
||||||
|
authenticate: false,
|
||||||
|
},
|
||||||
resetOptions: {
|
resetOptions: {
|
||||||
keepDirtyValues: true,
|
keepDirtyValues: true,
|
||||||
},
|
},
|
||||||
@@ -72,8 +77,11 @@ export default function GenerateLinkForm() {
|
|||||||
</Info>
|
</Info>
|
||||||
<Panel.ListGroup>
|
<Panel.ListGroup>
|
||||||
<Panel.ListItem>
|
<Panel.ListItem>
|
||||||
<Panel.Field title='Host IP' description='Which IP address will be used' />
|
<Panel.Field
|
||||||
<Select variant='ontime' size='sm' {...register('baseUrl')}>
|
title='Host IP'
|
||||||
|
description={`Which IP address will be used${isOntimeCloud ? ' (not applicable in Ontime Cloud)' : ''}`}
|
||||||
|
/>
|
||||||
|
<Select variant='ontime' isDisabled={isOntimeCloud} size='sm' {...register('baseUrl')}>
|
||||||
{infoData.networkInterfaces.map((nif) => {
|
{infoData.networkInterfaces.map((nif) => {
|
||||||
return (
|
return (
|
||||||
<option key={nif.name} value={nif.address}>
|
<option key={nif.name} value={nif.address}>
|
||||||
@@ -128,9 +136,9 @@ export default function GenerateLinkForm() {
|
|||||||
>
|
>
|
||||||
{formState === 'success' ? 'Link copied to clipboard!' : 'Update share link'}
|
{formState === 'success' ? 'Link copied to clipboard!' : 'Update share link'}
|
||||||
</Button>
|
</Button>
|
||||||
<div style={{ marginTop: '1rem', display: 'flex', flexDirection: 'column', gap: '0.25rem' }}>
|
<div className={style.column}>
|
||||||
<QRCode size={172} value={url} className={style.qrCode} />
|
<QRCode size={172} value={url} className={style.qrCode} />
|
||||||
<ExternalLink href={url}>{url}</ExternalLink>
|
<div>{url}</div>
|
||||||
</div>
|
</div>
|
||||||
</Panel.ListItem>
|
</Panel.ListItem>
|
||||||
</Panel.ListGroup>
|
</Panel.ListGroup>
|
||||||
|
|||||||
@@ -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 CopyTag from '../../../../common/components/copy-tag/CopyTag';
|
||||||
import useInfo from '../../../../common/hooks-query/useInfo';
|
import useInfo from '../../../../common/hooks-query/useInfo';
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { MouseEvent } from 'react';
|
import { MouseEvent } from 'react';
|
||||||
|
import { IoArrowUp } from 'react-icons/io5';
|
||||||
import { Button } from '@chakra-ui/react';
|
import { Button } from '@chakra-ui/react';
|
||||||
import { IoArrowUp } from '@react-icons/all-files/io5/IoArrowUp';
|
|
||||||
|
|
||||||
import { handleLinks } from '../../../../common/utils/linkUtils';
|
import { handleLinks } from '../../../../common/utils/linkUtils';
|
||||||
import Log from '../../../log/Log';
|
import Log from '../../../log/Log';
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { useEffect } from 'react';
|
|||||||
import useScrollIntoView from '../../../../common/hooks/useScrollIntoView';
|
import useScrollIntoView from '../../../../common/hooks/useScrollIntoView';
|
||||||
import { usePing } from '../../../../common/hooks/useSocket';
|
import { usePing } from '../../../../common/hooks/useSocket';
|
||||||
import { socketSendJson } from '../../../../common/utils/socket';
|
import { socketSendJson } from '../../../../common/utils/socket';
|
||||||
import { isDockerImage } from '../../../../externals';
|
import { isDockerImage, isOntimeCloud } from '../../../../externals';
|
||||||
import type { PanelBaseProps } from '../../panel-list/PanelList';
|
import type { PanelBaseProps } from '../../panel-list/PanelList';
|
||||||
import * as Panel from '../../panel-utils/PanelUtils';
|
import * as Panel from '../../panel-utils/PanelUtils';
|
||||||
import ClientControlPanel from '../client-control-panel/ClientControlPanel';
|
import ClientControlPanel from '../client-control-panel/ClientControlPanel';
|
||||||
@@ -30,8 +30,12 @@ export default function NetworkLogPanel({ location }: PanelBaseProps) {
|
|||||||
<Panel.Card>
|
<Panel.Card>
|
||||||
<Panel.SubHeader>Share Ontime Link</Panel.SubHeader>
|
<Panel.SubHeader>Share Ontime Link</Panel.SubHeader>
|
||||||
<Panel.Divider />
|
<Panel.Divider />
|
||||||
<Panel.Paragraph>Ontime is streaming on the following network interfaces</Panel.Paragraph>
|
{!isOntimeCloud && (
|
||||||
<InfoNif />
|
<>
|
||||||
|
<Panel.Paragraph>Ontime is streaming on the following network interfaces</Panel.Paragraph>
|
||||||
|
<InfoNif />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
<GenerateLinkForm />
|
<GenerateLinkForm />
|
||||||
</Panel.Card>
|
</Panel.Card>
|
||||||
</Panel.Section>
|
</Panel.Section>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { ChangeEvent, useRef, useState } from 'react';
|
import { ChangeEvent, useRef, useState } from 'react';
|
||||||
|
import { IoAdd } from 'react-icons/io5';
|
||||||
import { useSearchParams } from 'react-router-dom';
|
import { useSearchParams } from 'react-router-dom';
|
||||||
import { Button, Input } from '@chakra-ui/react';
|
import { Button, Input } from '@chakra-ui/react';
|
||||||
import { IoAdd } from '@react-icons/all-files/io5/IoAdd';
|
|
||||||
|
|
||||||
import { uploadProjectFile } from '../../../../common/api/db';
|
import { uploadProjectFile } from '../../../../common/api/db';
|
||||||
import { invalidateAllCaches, maybeAxiosError } from '../../../../common/api/utils';
|
import { invalidateAllCaches, maybeAxiosError } from '../../../../common/api/utils';
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
import { ChangeEvent, useEffect, useRef } from 'react';
|
import { ChangeEvent, useEffect, useRef } from 'react';
|
||||||
import { useForm } from 'react-hook-form';
|
import { useForm } from 'react-hook-form';
|
||||||
|
import { IoDownloadOutline, IoTrash } from 'react-icons/io5';
|
||||||
import { Button, Input, Textarea } from '@chakra-ui/react';
|
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 { type ProjectData } from 'ontime-types';
|
||||||
|
|
||||||
import { projectLogoPath } from '../../../../common/api/constants';
|
import { projectLogoPath } from '../../../../common/api/constants';
|
||||||
@@ -97,7 +96,7 @@ export default function ProjectData() {
|
|||||||
const isLoading = status === 'pending';
|
const isLoading = status === 'pending';
|
||||||
|
|
||||||
return (
|
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.Card>
|
||||||
<Panel.SubHeader>
|
<Panel.SubHeader>
|
||||||
Project data
|
Project data
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
|
import { IoEllipsisHorizontal } from 'react-icons/io5';
|
||||||
import { IconButton, Menu, MenuButton, MenuItem, MenuList } from '@chakra-ui/react';
|
import { IconButton, Menu, MenuButton, MenuItem, MenuList } from '@chakra-ui/react';
|
||||||
import { IoEllipsisHorizontal } from '@react-icons/all-files/io5/IoEllipsisHorizontal';
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
deleteProject,
|
deleteProject,
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import { ChangeEvent, useEffect, useState } from 'react';
|
import { ChangeEvent, useEffect, useState } from 'react';
|
||||||
|
import { IoCheckmark, IoShieldCheckmarkOutline } from 'react-icons/io5';
|
||||||
import { Button, Input, Spinner } from '@chakra-ui/react';
|
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 { getWorksheetNames } from '../../../../common/api/sheets';
|
||||||
import { maybeAxiosError } from '../../../../common/api/utils';
|
import { maybeAxiosError } from '../../../../common/api/utils';
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import { ChangeEvent, useRef, useState } from 'react';
|
import { ChangeEvent, useRef, useState } from 'react';
|
||||||
|
import { IoCloudOutline, IoDownloadOutline } from 'react-icons/io5';
|
||||||
import { Button, Input } from '@chakra-ui/react';
|
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 { getErrorMessage, ImportMap } from 'ontime-utils';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
|||||||
+1
-2
@@ -1,8 +1,7 @@
|
|||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { useFieldArray, useForm } from 'react-hook-form';
|
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 { 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 { ImportMap, isAlphanumericWithSpace } from 'ontime-utils';
|
||||||
|
|
||||||
import * as Panel from '../../../panel-utils/PanelUtils';
|
import * as Panel from '../../../panel-utils/PanelUtils';
|
||||||
|
|||||||
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
import { Fragment } from 'react';
|
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 { CustomFields, isOntimeBlock, isOntimeEvent, OntimeRundown } from 'ontime-types';
|
||||||
import { millisToString } from 'ontime-utils';
|
import { millisToString } from 'ontime-utils';
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import { useEffect, useRef } from 'react';
|
import { useEffect, useRef } from 'react';
|
||||||
|
import { IoEye, IoEyeOffOutline } from 'react-icons/io5';
|
||||||
import { Input } from '@chakra-ui/react';
|
import { Input } from '@chakra-ui/react';
|
||||||
import { 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 TooltipActionBtn from '../../../common/components/buttons/TooltipActionBtn';
|
||||||
import { tooltipDelayMid } from '../../../ontimeConfig';
|
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 { 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 { TimerPhase, TimerType } from 'ontime-types';
|
||||||
|
|
||||||
import { useMessagePreview } from '../../../common/hooks/useSocket';
|
import { useMessagePreview } from '../../../common/hooks/useSocket';
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
|
import { IoAdd, IoRemove } from 'react-icons/io5';
|
||||||
import { Tooltip } from '@chakra-ui/react';
|
import { Tooltip } from '@chakra-ui/react';
|
||||||
import { useLocalStorage } from '@mantine/hooks';
|
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 { Playback } from 'ontime-types';
|
||||||
import { MILLIS_PER_HOUR, MILLIS_PER_SECOND, parseUserTime } from 'ontime-utils';
|
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 { IoArrowDown, IoArrowUp, IoPause, IoPlay, IoStop } from 'react-icons/io5';
|
||||||
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 { Playback, SimpleDirection, SimplePlayback } from 'ontime-types';
|
import { Playback, SimpleDirection, SimplePlayback } from 'ontime-types';
|
||||||
import { parseUserTime } from 'ontime-utils';
|
import { parseUserTime } from 'ontime-utils';
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,6 @@
|
|||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
|
import { IoPause, IoPlay, IoPlaySkipBack, IoPlaySkipForward, IoReload, IoStop, IoTime } from 'react-icons/io5';
|
||||||
import { Tooltip } from '@chakra-ui/react';
|
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 { Playback, TimerPhase } from 'ontime-types';
|
||||||
import { validatePlayback } from 'ontime-utils';
|
import { validatePlayback } from 'ontime-utils';
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
import { lazy, useCallback, useEffect } from 'react';
|
import { lazy, useCallback, useEffect } from 'react';
|
||||||
|
import { IoApps, IoClose, IoSettingsOutline } from 'react-icons/io5';
|
||||||
import { IconButton, useDisclosure } from '@chakra-ui/react';
|
import { IconButton, useDisclosure } from '@chakra-ui/react';
|
||||||
import { useHotkeys } from '@mantine/hooks';
|
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 NavigationMenu from '../../common/components/navigation-menu/NavigationMenu';
|
||||||
import { useElectronListener } from '../../common/hooks/useElectronEvent';
|
import { useElectronListener } from '../../common/hooks/useElectronEvent';
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import type { HTMLAttributes, LabelHTMLAttributes } from 'react';
|
import type { HTMLAttributes, LabelHTMLAttributes } from 'react';
|
||||||
import { IoArrowUp } from '@react-icons/all-files/io5/IoArrowUp';
|
import { IconBaseProps } from 'react-icons';
|
||||||
import { type IconBaseProps } from '@react-icons/all-files/lib';
|
import { IoArrowUp } from 'react-icons/io5';
|
||||||
|
|
||||||
import { cx } from '../../../common/utils/styleUtils';
|
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';
|
import { cx } from '../../../common/utils/styleUtils';
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import {
|
|||||||
isOntimeEvent,
|
isOntimeEvent,
|
||||||
isPlayableEvent,
|
isPlayableEvent,
|
||||||
MaybeString,
|
MaybeString,
|
||||||
OntimeEvent,
|
|
||||||
PlayableEvent,
|
PlayableEvent,
|
||||||
Playback,
|
Playback,
|
||||||
RundownCached,
|
RundownCached,
|
||||||
@@ -272,7 +271,7 @@ export default function Rundown({ data }: RundownProps) {
|
|||||||
let isNextDay = false;
|
let isNextDay = false;
|
||||||
let totalGap = 0;
|
let totalGap = 0;
|
||||||
const isEditMode = appMode === AppMode.Edit;
|
const isEditMode = appMode === AppMode.Edit;
|
||||||
let currentDay = 0;
|
let isLinkedToLoaded = true; //check if the event can link all the way back to the currently playing event
|
||||||
return (
|
return (
|
||||||
<div className={style.rundownContainer} ref={scrollRef} data-testid='rundown'>
|
<div className={style.rundownContainer} ref={scrollRef} data-testid='rundown'>
|
||||||
<DndContext onDragEnd={handleOnDragEnd} sensors={sensors} collisionDetection={closestCenter}>
|
<DndContext onDragEnd={handleOnDragEnd} sensors={sensors} collisionDetection={closestCenter}>
|
||||||
@@ -299,7 +298,10 @@ export default function Rundown({ data }: RundownProps) {
|
|||||||
|
|
||||||
if (isPlayableEvent(entry)) {
|
if (isPlayableEvent(entry)) {
|
||||||
isNextDay = checkIsNextDay(entry, lastEvent);
|
isNextDay = checkIsNextDay(entry, lastEvent);
|
||||||
totalGap += !isPast ? entry.gap : 0;
|
if (!isPast) {
|
||||||
|
totalGap += entry.gap;
|
||||||
|
isLinkedToLoaded = isLinkedToLoaded && entry.linkStart !== null;
|
||||||
|
}
|
||||||
if (isNewLatest(entry, lastEvent)) {
|
if (isNewLatest(entry, lastEvent)) {
|
||||||
// populate previous entry
|
// populate previous entry
|
||||||
thisEvent = entry;
|
thisEvent = entry;
|
||||||
@@ -313,7 +315,6 @@ export default function Rundown({ data }: RundownProps) {
|
|||||||
const hasCursor = entry.id === cursor;
|
const hasCursor = entry.id === cursor;
|
||||||
if (isLoaded) {
|
if (isLoaded) {
|
||||||
isPast = false;
|
isPast = false;
|
||||||
currentDay = (entry as OntimeEvent).dayOffset;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -336,7 +337,7 @@ export default function Rundown({ data }: RundownProps) {
|
|||||||
isRolling={featureData.playback === Playback.Roll}
|
isRolling={featureData.playback === Playback.Roll}
|
||||||
isNextDay={isNextDay}
|
isNextDay={isNextDay}
|
||||||
totalGap={totalGap}
|
totalGap={totalGap}
|
||||||
currentDay={currentDay}
|
isLinkedToLoaded={isLinkedToLoaded}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
|
import { IoAdd } from 'react-icons/io5';
|
||||||
import { Button } from '@chakra-ui/react';
|
import { Button } from '@chakra-ui/react';
|
||||||
import { IoAdd } from '@react-icons/all-files/io5/IoAdd';
|
|
||||||
|
|
||||||
import Empty from '../../common/components/state/Empty';
|
import Empty from '../../common/components/state/Empty';
|
||||||
|
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ interface RundownEntryProps {
|
|||||||
playback?: Playback; // we only care about this if this event is playing
|
playback?: Playback; // we only care about this if this event is playing
|
||||||
isRolling: boolean; // we need to know even if not related to this event
|
isRolling: boolean; // we need to know even if not related to this event
|
||||||
totalGap: number;
|
totalGap: number;
|
||||||
currentDay: number;
|
isLinkedToLoaded: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function RundownEntry(props: RundownEntryProps) {
|
export default function RundownEntry(props: RundownEntryProps) {
|
||||||
@@ -56,7 +56,7 @@ export default function RundownEntry(props: RundownEntryProps) {
|
|||||||
eventIndex,
|
eventIndex,
|
||||||
isNextDay,
|
isNextDay,
|
||||||
totalGap,
|
totalGap,
|
||||||
currentDay,
|
isLinkedToLoaded,
|
||||||
} = props;
|
} = props;
|
||||||
const { emitError } = useEmitLog();
|
const { emitError } = useEmitLog();
|
||||||
const { addEvent, updateEvent, batchUpdateEvents, deleteEvent, swapEvents } = useEventAction();
|
const { addEvent, updateEvent, batchUpdateEvents, deleteEvent, swapEvents } = useEventAction();
|
||||||
@@ -178,8 +178,9 @@ export default function RundownEntry(props: RundownEntryProps) {
|
|||||||
isRolling={isRolling}
|
isRolling={isRolling}
|
||||||
gap={data.gap}
|
gap={data.gap}
|
||||||
isNextDay={isNextDay}
|
isNextDay={isNextDay}
|
||||||
dayOffset={data.dayOffset - currentDay}
|
dayOffset={data.dayOffset}
|
||||||
totalGap={totalGap}
|
totalGap={totalGap}
|
||||||
|
isLinkedToLoaded={isLinkedToLoaded}
|
||||||
actionHandler={actionHandler}
|
actionHandler={actionHandler}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { useRef } from 'react';
|
import { useRef } from 'react';
|
||||||
|
import { IoReorderTwo } from 'react-icons/io5';
|
||||||
import { useSortable } from '@dnd-kit/sortable';
|
import { useSortable } from '@dnd-kit/sortable';
|
||||||
import { CSS } from '@dnd-kit/utilities';
|
import { CSS } from '@dnd-kit/utilities';
|
||||||
import { IoReorderTwo } from '@react-icons/all-files/io5/IoReorderTwo';
|
|
||||||
import { OntimeBlock } from 'ontime-types';
|
import { OntimeBlock } from 'ontime-types';
|
||||||
|
|
||||||
import { cx } from '../../../common/utils/styleUtils';
|
import { cx } from '../../../common/utils/styleUtils';
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
|
import { IoTrash } from 'react-icons/io5';
|
||||||
import { IconButton } from '@chakra-ui/react';
|
import { IconButton } from '@chakra-ui/react';
|
||||||
import { IoTrash } from '@react-icons/all-files/io5/IoTrash';
|
|
||||||
|
|
||||||
import { AppMode, useAppMode } from '../../../common/stores/appModeStore';
|
import { AppMode, useAppMode } from '../../../common/stores/appModeStore';
|
||||||
|
|
||||||
|
|||||||
@@ -51,6 +51,8 @@ export default function EditableBlockTitle(props: TitleEditorProps) {
|
|||||||
fontWeight='600'
|
fontWeight='600'
|
||||||
letterSpacing='0.25px'
|
letterSpacing='0.25px'
|
||||||
paddingLeft='0'
|
paddingLeft='0'
|
||||||
|
size='sm'
|
||||||
|
fontSize='1rem'
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,8 @@
|
|||||||
import { useEffect, useRef } from 'react';
|
import { useEffect, useRef } from 'react';
|
||||||
|
import { IoCheckmarkDone, IoClose, IoReorderTwo } from 'react-icons/io5';
|
||||||
import { Button } from '@chakra-ui/react';
|
import { Button } from '@chakra-ui/react';
|
||||||
import { useSortable } from '@dnd-kit/sortable';
|
import { useSortable } from '@dnd-kit/sortable';
|
||||||
import { CSS } from '@dnd-kit/utilities';
|
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 { OntimeDelay } from 'ontime-types';
|
||||||
|
|
||||||
import DelayInput from '../../../common/components/input/delay-input/DelayInput';
|
import DelayInput from '../../../common/components/input/delay-input/DelayInput';
|
||||||
|
|||||||
@@ -15,10 +15,11 @@ $skip-opacity: 0.2;
|
|||||||
'binder ... ... ...';
|
'binder ... ... ...';
|
||||||
|
|
||||||
grid-template-columns: $block-binder-width 3rem 1fr auto;
|
grid-template-columns: $block-binder-width 3rem 1fr auto;
|
||||||
grid-template-rows: 0.25rem 2.25rem 2.25rem 2.25rem 0.25rem;
|
grid-template-rows: 0.125rem 2rem 2rem 2rem 0.125rem;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding-right: $block-clearance;
|
padding-right: $block-clearance;
|
||||||
gap: 2px;
|
gap: 2px;
|
||||||
|
row-gap: 0.25rem;
|
||||||
|
|
||||||
transition-property: background-color;
|
transition-property: background-color;
|
||||||
transition-duration: $transition-time-feedback;
|
transition-duration: $transition-time-feedback;
|
||||||
@@ -120,7 +121,7 @@ $skip-opacity: 0.2;
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
margin: 0 0.5rem;
|
margin: 0 0.5rem;
|
||||||
gap: 0.5rem;
|
gap: 0.25rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.eventTimers {
|
.eventTimers {
|
||||||
|
|||||||
@@ -1,15 +1,17 @@
|
|||||||
import { MouseEvent, useEffect, useLayoutEffect, useRef, useState } from 'react';
|
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 { useSortable } from '@dnd-kit/sortable';
|
||||||
import { CSS } from '@dnd-kit/utilities';
|
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 { EndAction, MaybeString, OntimeEvent, Playback, TimerType, TimeStrategy } from 'ontime-types';
|
||||||
|
|
||||||
import { useContextMenu } from '../../../common/hooks/useContextMenu';
|
import { useContextMenu } from '../../../common/hooks/useContextMenu';
|
||||||
@@ -51,6 +53,7 @@ interface EventBlockProps {
|
|||||||
isNextDay: boolean;
|
isNextDay: boolean;
|
||||||
dayOffset: number;
|
dayOffset: number;
|
||||||
totalGap: number;
|
totalGap: number;
|
||||||
|
isLinkedToLoaded: boolean;
|
||||||
actionHandler: (
|
actionHandler: (
|
||||||
action: EventItemActions,
|
action: EventItemActions,
|
||||||
payload?:
|
payload?:
|
||||||
@@ -91,6 +94,7 @@ export default function EventBlock(props: EventBlockProps) {
|
|||||||
isNextDay,
|
isNextDay,
|
||||||
dayOffset,
|
dayOffset,
|
||||||
totalGap,
|
totalGap,
|
||||||
|
isLinkedToLoaded,
|
||||||
actionHandler,
|
actionHandler,
|
||||||
} = props;
|
} = props;
|
||||||
const { selectedEventId, setSelectedEventId, clearSelectedEventId } = useEventIdSwapping();
|
const { selectedEventId, setSelectedEventId, clearSelectedEventId } = useEventIdSwapping();
|
||||||
@@ -310,6 +314,7 @@ export default function EventBlock(props: EventBlockProps) {
|
|||||||
dayOffset={dayOffset}
|
dayOffset={dayOffset}
|
||||||
isPast={isPast}
|
isPast={isPast}
|
||||||
totalGap={totalGap}
|
totalGap={totalGap}
|
||||||
|
isLinkedToLoaded={isLinkedToLoaded}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,17 +1,18 @@
|
|||||||
import { memo, useEffect, useState } from 'react';
|
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 { 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 { EndAction, MaybeString, Playback, TimerType, TimeStrategy } from 'ontime-types';
|
||||||
import { dayInMs } from 'ontime-utils';
|
|
||||||
|
|
||||||
import { cx } from '../../../common/utils/styleUtils';
|
import { cx } from '../../../common/utils/styleUtils';
|
||||||
import { tooltipDelayMid } from '../../../ontimeConfig';
|
import { tooltipDelayMid } from '../../../ontimeConfig';
|
||||||
@@ -47,6 +48,7 @@ interface EventBlockInnerProps {
|
|||||||
dayOffset: number;
|
dayOffset: number;
|
||||||
isPast: boolean;
|
isPast: boolean;
|
||||||
totalGap: number;
|
totalGap: number;
|
||||||
|
isLinkedToLoaded: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
function EventBlockInner(props: EventBlockInnerProps) {
|
function EventBlockInner(props: EventBlockInnerProps) {
|
||||||
@@ -72,6 +74,7 @@ function EventBlockInner(props: EventBlockInnerProps) {
|
|||||||
dayOffset,
|
dayOffset,
|
||||||
isPast,
|
isPast,
|
||||||
totalGap,
|
totalGap,
|
||||||
|
isLinkedToLoaded,
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
const [renderInner, setRenderInner] = useState(false);
|
const [renderInner, setRenderInner] = useState(false);
|
||||||
@@ -120,11 +123,13 @@ function EventBlockInner(props: EventBlockInnerProps) {
|
|||||||
<EventBlockChip
|
<EventBlockChip
|
||||||
className={style.chipSection}
|
className={style.chipSection}
|
||||||
id={eventId}
|
id={eventId}
|
||||||
trueTimeStart={timeStart + dayOffset * dayInMs}
|
timeStart={timeStart}
|
||||||
|
delay={delay}
|
||||||
|
dayOffset={dayOffset}
|
||||||
|
isLinkedToLoaded={isLinkedToLoaded}
|
||||||
isPast={isPast}
|
isPast={isPast}
|
||||||
isLoaded={loaded}
|
isLoaded={loaded}
|
||||||
totalGap={totalGap}
|
totalGap={totalGap}
|
||||||
isLinkedAndNext={isNext && linkStart !== null}
|
|
||||||
duration={duration}
|
duration={duration}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -1,29 +1,31 @@
|
|||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
|
import { IoCheckmarkCircle } from 'react-icons/io5';
|
||||||
import { Tooltip } from '@chakra-ui/react';
|
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 { isPlaybackActive, MILLIS_PER_MINUTE, MILLIS_PER_SECOND } from 'ontime-utils';
|
||||||
|
|
||||||
import { usePlayback, useTimelineStatus } from '../../../../common/hooks/useSocket';
|
import { usePlayback } from '../../../../common/hooks/useSocket';
|
||||||
import useReport from '../../../../common/hooks-query/useReport';
|
import useReport from '../../../../common/hooks-query/useReport';
|
||||||
import { cx } from '../../../../common/utils/styleUtils';
|
import { cx } from '../../../../common/utils/styleUtils';
|
||||||
import { formatDuration, formatTime } from '../../../../common/utils/time';
|
import { formatDuration, formatTime, useTimeUntilStart } from '../../../../common/utils/time';
|
||||||
import { tooltipDelayFast } from '../../../../ontimeConfig';
|
import { tooltipDelayFast } from '../../../../ontimeConfig';
|
||||||
|
|
||||||
import style from './EventBlockChip.module.scss';
|
import style from './EventBlockChip.module.scss';
|
||||||
|
|
||||||
interface EventBlockChipProps {
|
interface EventBlockChipProps {
|
||||||
id: string;
|
id: string;
|
||||||
trueTimeStart: number;
|
timeStart: number;
|
||||||
|
delay: number;
|
||||||
|
dayOffset: number;
|
||||||
isPast: boolean;
|
isPast: boolean;
|
||||||
isLoaded: boolean;
|
isLoaded: boolean;
|
||||||
className: string;
|
className: string;
|
||||||
totalGap: number;
|
totalGap: number;
|
||||||
isLinkedAndNext: boolean;
|
|
||||||
duration: number;
|
duration: number;
|
||||||
|
isLinkedToLoaded: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function EventBlockChip(props: EventBlockChipProps) {
|
export default function EventBlockChip(props: EventBlockChipProps) {
|
||||||
const { trueTimeStart, isPast, isLoaded, className, totalGap, isLinkedAndNext, id, duration } = props;
|
const { timeStart, delay, dayOffset, isPast, isLoaded, className, totalGap, id, duration, isLinkedToLoaded } = props;
|
||||||
const { playback } = usePlayback();
|
const { playback } = usePlayback();
|
||||||
|
|
||||||
if (isLoaded) {
|
if (isLoaded) {
|
||||||
@@ -41,7 +43,13 @@ export default function EventBlockChip(props: EventBlockChipProps) {
|
|||||||
return (
|
return (
|
||||||
<Tooltip label='Expected time until start' openDelay={tooltipDelayFast}>
|
<Tooltip label='Expected time until start' openDelay={tooltipDelayFast}>
|
||||||
<div className={className}>
|
<div className={className}>
|
||||||
<EventUntil trueTimeStart={trueTimeStart} totalGap={totalGap} isLinkedAndNext={isLinkedAndNext} />
|
<EventUntil
|
||||||
|
timeStart={timeStart}
|
||||||
|
delay={delay}
|
||||||
|
dayOffset={dayOffset}
|
||||||
|
totalGap={totalGap}
|
||||||
|
isLinkedToLoaded={isLinkedToLoaded}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
);
|
);
|
||||||
@@ -51,18 +59,17 @@ export default function EventBlockChip(props: EventBlockChipProps) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface EventUntilProps {
|
interface EventUntilProps {
|
||||||
trueTimeStart: number;
|
timeStart: number;
|
||||||
|
delay: number;
|
||||||
|
dayOffset: number;
|
||||||
totalGap: number;
|
totalGap: number;
|
||||||
isLinkedAndNext: boolean;
|
isLinkedToLoaded: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
function EventUntil(props: EventUntilProps) {
|
function EventUntil(props: EventUntilProps) {
|
||||||
const { trueTimeStart, totalGap, isLinkedAndNext } = props;
|
const { timeStart, delay, dayOffset, totalGap, isLinkedToLoaded } = props;
|
||||||
const { clock, offset } = useTimelineStatus();
|
|
||||||
|
|
||||||
const consumedOffset = isLinkedAndNext ? offset : Math.min(offset + totalGap, 0);
|
const timeUntil = useTimeUntilStart({ timeStart, delay, dayOffset, totalGap, isLinkedToLoaded });
|
||||||
const offsetTimestart = trueTimeStart - consumedOffset;
|
|
||||||
const timeUntil = offsetTimestart - clock;
|
|
||||||
const isDue = timeUntil < MILLIS_PER_SECOND;
|
const isDue = timeUntil < MILLIS_PER_SECOND;
|
||||||
|
|
||||||
const timeUntilString = isDue ? 'DUE' : `${formatDuration(Math.abs(timeUntil), timeUntil > 2 * MILLIS_PER_MINUTE)}`;
|
const timeUntilString = isDue ? 'DUE' : `${formatDuration(Math.abs(timeUntil), timeUntil > 2 * MILLIS_PER_MINUTE)}`;
|
||||||
|
|||||||
@@ -1,9 +1,5 @@
|
|||||||
import { memo, MouseEvent } from 'react';
|
import { memo, MouseEvent } from 'react';
|
||||||
import { IoPause } from '@react-icons/all-files/io5/IoPause';
|
import { IoPause, IoPlay, IoReload, IoRemoveCircle, IoRemoveCircleOutline } from 'react-icons/io5';
|
||||||
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 TooltipActionBtn from '../../../../common/components/buttons/TooltipActionBtn';
|
import TooltipActionBtn from '../../../../common/components/buttons/TooltipActionBtn';
|
||||||
import { useEventAction } from '../../../../common/hooks/useEventAction';
|
import { useEventAction } from '../../../../common/hooks/useEventAction';
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { memo } from 'react';
|
import { memo } from 'react';
|
||||||
|
import { IoInformationCircle } from 'react-icons/io5';
|
||||||
import { Select, Switch, Tooltip } from '@chakra-ui/react';
|
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 { EndAction, MaybeString, TimerType, TimeStrategy } from 'ontime-types';
|
||||||
import { millisToString, parseUserTime } from 'ontime-utils';
|
import { millisToString, parseUserTime } from 'ontime-utils';
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { memo, useCallback, useRef } from 'react';
|
import { memo, useCallback, useRef } from 'react';
|
||||||
|
import { IoAdd } from 'react-icons/io5';
|
||||||
import { Button } from '@chakra-ui/react';
|
import { Button } from '@chakra-ui/react';
|
||||||
import { IoAdd } from '@react-icons/all-files/io5/IoAdd';
|
|
||||||
import { SupportedEvent } from 'ontime-types';
|
import { SupportedEvent } from 'ontime-types';
|
||||||
|
|
||||||
import { useEventAction } from '../../../common/hooks/useEventAction';
|
import { useEventAction } from '../../../common/hooks/useEventAction';
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { useCallback, useRef } from 'react';
|
import { useCallback, useRef } from 'react';
|
||||||
|
import { IoTrash } from 'react-icons/io5';
|
||||||
import {
|
import {
|
||||||
AlertDialog,
|
AlertDialog,
|
||||||
AlertDialogBody,
|
AlertDialogBody,
|
||||||
@@ -9,7 +10,6 @@ import {
|
|||||||
Button,
|
Button,
|
||||||
useDisclosure,
|
useDisclosure,
|
||||||
} from '@chakra-ui/react';
|
} from '@chakra-ui/react';
|
||||||
import { IoTrash } from '@react-icons/all-files/io5/IoTrash';
|
|
||||||
|
|
||||||
import { useEventAction } from '../../../common/hooks/useEventAction';
|
import { useEventAction } from '../../../common/hooks/useEventAction';
|
||||||
import { useAppMode } from '../../../common/stores/appModeStore';
|
import { useAppMode } from '../../../common/stores/appModeStore';
|
||||||
|
|||||||
@@ -1,10 +1,6 @@
|
|||||||
import { memo } from 'react';
|
import { memo } from 'react';
|
||||||
|
import { IoAlertCircleOutline, IoLink, IoLockClosed, IoLockOpenOutline, IoUnlink } from 'react-icons/io5';
|
||||||
import { InputRightElement, Tooltip } from '@chakra-ui/react';
|
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 { MaybeString, TimeField, TimeStrategy } from 'ontime-types';
|
||||||
import { dayInMs } from 'ontime-utils';
|
import { dayInMs } from 'ontime-utils';
|
||||||
|
|
||||||
|
|||||||
@@ -81,10 +81,18 @@ export default function Backstage(props: BackstageProps) {
|
|||||||
const clock = formatTime(time.clock);
|
const clock = formatTime(time.clock);
|
||||||
const isPendingStart = getIsPendingStart(time.playback, time.phase);
|
const isPendingStart = getIsPendingStart(time.playback, time.phase);
|
||||||
const startedAt = isPendingStart ? formatTime(time.secondaryTimer) : formatTime(time.startedAt);
|
const startedAt = isPendingStart ? formatTime(time.secondaryTimer) : formatTime(time.startedAt);
|
||||||
const scheduledStart =
|
|
||||||
hasEvents && showNow ? '' : formatTime(runtime.plannedStart, { format12: 'hh:mm a', format24: 'HH:mm' });
|
const scheduledStart = (() => {
|
||||||
const scheduledEnd =
|
if (showNow) return undefined;
|
||||||
hasEvents && showNow ? '' : formatTime(runtime.plannedEnd, { format12: 'hh:mm a', format24: 'HH:mm' });
|
if (!hasEvents) return undefined;
|
||||||
|
return formatTime(runtime.plannedStart, { format12: 'hh:mm a', format24: 'HH:mm' });
|
||||||
|
})();
|
||||||
|
|
||||||
|
const scheduledEnd = (() => {
|
||||||
|
if (showNow) return undefined;
|
||||||
|
if (!hasEvents) return undefined;
|
||||||
|
return formatTime(runtime.plannedEnd, { format12: 'hh:mm a', format24: 'HH:mm' });
|
||||||
|
})();
|
||||||
|
|
||||||
let displayTimer = millisToString(time.current, { fallback: timerPlaceholderMin });
|
let displayTimer = millisToString(time.current, { fallback: timerPlaceholderMin });
|
||||||
displayTimer = removeLeadingZero(displayTimer);
|
displayTimer = removeLeadingZero(displayTimer);
|
||||||
@@ -93,6 +101,7 @@ export default function Backstage(props: BackstageProps) {
|
|||||||
const qrSize = Math.max(window.innerWidth / 15, 72);
|
const qrSize = Math.max(window.innerWidth / 15, 72);
|
||||||
const showProgress = getShowProgressBar(time.playback);
|
const showProgress = getShowProgressBar(time.playback);
|
||||||
const showSchedule = hasEvents && screenHeight > 420; // in vertical screens we may not have space
|
const showSchedule = hasEvents && screenHeight > 420; // in vertical screens we may not have space
|
||||||
|
const showPending = scheduledStart && scheduledEnd;
|
||||||
|
|
||||||
// gather option data
|
// gather option data
|
||||||
const defaultFormat = getDefaultFormat(settings?.timeFormat);
|
const defaultFormat = getDefaultFormat(settings?.timeFormat);
|
||||||
@@ -143,7 +152,7 @@ export default function Backstage(props: BackstageProps) {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{!showNow && hasEvents && (
|
{showPending && (
|
||||||
<div className='event'>
|
<div className='event'>
|
||||||
<div className='title-card__placeholder'>{getLocalizedString('countdown.waiting')}</div>
|
<div className='title-card__placeholder'>{getLocalizedString('countdown.waiting')}</div>
|
||||||
<div className='timer-group'>
|
<div className='timer-group'>
|
||||||
|
|||||||
@@ -2,7 +2,8 @@
|
|||||||
background-color: $ui-black;
|
background-color: $ui-black;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
padding: 1rem 0.5rem;
|
padding-inline: 0.5rem;
|
||||||
|
padding-block: 0.5rem 1rem;
|
||||||
|
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-rows: 3rem auto auto 1fr;
|
grid-template-rows: 3rem auto auto 1fr;
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import { useCallback, useMemo, useState } from 'react';
|
import { useCallback, useMemo, useState } from 'react';
|
||||||
|
import { IoApps, IoSettingsOutline } from 'react-icons/io5';
|
||||||
import { IconButton, Modal, ModalContent, ModalOverlay, useDisclosure } from '@chakra-ui/react';
|
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 NavigationMenu from '../../common/components/navigation-menu/NavigationMenu';
|
||||||
import useViewEditor from '../../common/components/navigation-menu/useViewEditor';
|
import useViewEditor from '../../common/components/navigation-menu/useViewEditor';
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { memo, MutableRefObject, useLayoutEffect, useRef, useState } from 'react';
|
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 { flexRender, Table } from '@tanstack/react-table';
|
||||||
import Color from 'color';
|
import Color from 'color';
|
||||||
import { OntimeEvent, OntimeRundownEntry } from 'ontime-types';
|
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 { 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 { isOntimeEvent, SupportedEvent } from 'ontime-types';
|
||||||
|
|
||||||
import { useEventAction } from '../../../../common/hooks/useEventAction';
|
import { useEventAction } from '../../../../common/hooks/useEventAction';
|
||||||
|
|||||||
@@ -64,11 +64,16 @@ export default function Public(props: BackstageProps) {
|
|||||||
// gather timer data
|
// gather timer data
|
||||||
const clock = formatTime(time.clock);
|
const clock = formatTime(time.clock);
|
||||||
const isPendingStart = getIsPendingStart(time.playback, time.phase);
|
const isPendingStart = getIsPendingStart(time.playback, time.phase);
|
||||||
const scheduledStart = hasEvents && showNow ? '' : getFirstStartTime(events[0]);
|
const scheduledStart = (() => {
|
||||||
|
if (showNow) return undefined;
|
||||||
|
if (!hasEvents) return undefined;
|
||||||
|
return getFirstStartTime(events[0]);
|
||||||
|
})();
|
||||||
|
|
||||||
// gather presentation styles
|
// gather presentation styles
|
||||||
const qrSize = Math.max(window.innerWidth / 15, 72);
|
const qrSize = Math.max(window.innerWidth / 15, 72);
|
||||||
const showSchedule = hasEvents && screenHeight > 700; // in vertical screens we may not have space
|
const showSchedule = hasEvents && screenHeight > 700; // in vertical screens we may not have space
|
||||||
|
const showPending = scheduledStart !== undefined;
|
||||||
|
|
||||||
// gather option data
|
// gather option data
|
||||||
const defaultFormat = getDefaultFormat(settings?.timeFormat);
|
const defaultFormat = getDefaultFormat(settings?.timeFormat);
|
||||||
@@ -92,7 +97,7 @@ export default function Public(props: BackstageProps) {
|
|||||||
{showNow && hasEvents && (
|
{showNow && hasEvents && (
|
||||||
<TitleCard className='event now' label='now' title={nowMain} secondary={nowSecondary} />
|
<TitleCard className='event now' label='now' title={nowMain} secondary={nowSecondary} />
|
||||||
)}
|
)}
|
||||||
{!showNow && scheduledStart && (
|
{showPending && (
|
||||||
<div className='event'>
|
<div className='event'>
|
||||||
<div className='title-card__placeholder'>{getLocalizedString('countdown.waiting')}</div>
|
<div className='title-card__placeholder'>{getLocalizedString('countdown.waiting')}</div>
|
||||||
<div className='timer-group'>
|
<div className='timer-group'>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "ontime-electron",
|
"name": "ontime-electron",
|
||||||
"version": "3.14.0",
|
"version": "3.14.3",
|
||||||
"author": "Carlos Valente",
|
"author": "Carlos Valente",
|
||||||
"description": "Time keeping for live events",
|
"description": "Time keeping for live events",
|
||||||
"repository": "https://github.com/cpvalente/ontime",
|
"repository": "https://github.com/cpvalente/ontime",
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
"name": "ontime-server",
|
"name": "ontime-server",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "src/index.ts",
|
"main": "src/index.ts",
|
||||||
"version": "3.14.0",
|
"version": "3.14.3",
|
||||||
"exports": "./src/index.js",
|
"exports": "./src/index.js",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@googleapis/sheets": "^5.0.5",
|
"@googleapis/sheets": "^5.0.5",
|
||||||
|
|||||||
@@ -0,0 +1,50 @@
|
|||||||
|
import { generateAuthenticatedUrl } from '../session.service.js';
|
||||||
|
|
||||||
|
describe('generateAuthenticatedUrl()', () => {
|
||||||
|
describe('for local IP addresses', () => {
|
||||||
|
it('generates a link without locking or authentication', () => {
|
||||||
|
const localhostNotLocked = generateAuthenticatedUrl('http://localhost:3000', 'timer', false, false);
|
||||||
|
expect(localhostNotLocked.toString()).toBe('http://localhost:3000/timer');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('generates a link with IP locking enabled', () => {
|
||||||
|
const ipLocked = generateAuthenticatedUrl('http://192.168.10.173:4001', 'timer', true, false);
|
||||||
|
expect(ipLocked.toString()).toBe('http://192.168.10.173:4001/timer?locked=true');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('generates a link with authentication token and IP locking', () => {
|
||||||
|
const withAuth = generateAuthenticatedUrl('http://192.168.10.173:4001', 'timer', true, true, undefined, '1234');
|
||||||
|
expect(withAuth.toString()).toBe('http://192.168.10.173:4001/timer?token=1234&locked=true');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('for ontime-cloud URLs', () => {
|
||||||
|
it('generates a link without locking or authentication', () => {
|
||||||
|
const cloudNotLocked = generateAuthenticatedUrl(
|
||||||
|
'https://cloud.getontime.no/userhash',
|
||||||
|
'timer',
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
'prefix',
|
||||||
|
);
|
||||||
|
expect(cloudNotLocked.toString()).toBe('https://cloud.getontime.no/prefix/timer');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('generates a link with IP locking enabled', () => {
|
||||||
|
const ipLocked = generateAuthenticatedUrl('https://cloud.getontime.no/prefix', 'timer', true, false, 'prefix');
|
||||||
|
expect(ipLocked.toString()).toBe('https://cloud.getontime.no/prefix/timer?locked=true');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('generates a link with authentication token and IP locking', () => {
|
||||||
|
const withAuth = generateAuthenticatedUrl(
|
||||||
|
'https://cloud.getontime.no/prefix',
|
||||||
|
'timer',
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
'prefix',
|
||||||
|
'1234',
|
||||||
|
);
|
||||||
|
expect(withAuth.toString()).toBe('https://cloud.getontime.no/prefix/timer?token=1234&locked=true');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -8,7 +8,7 @@ import { getLastLoadedProject } from '../../services/app-state-service/AppStateS
|
|||||||
import { runtimeService } from '../../services/runtime-service/RuntimeService.js';
|
import { runtimeService } from '../../services/runtime-service/RuntimeService.js';
|
||||||
import { getNetworkInterfaces } from '../../utils/network.js';
|
import { getNetworkInterfaces } from '../../utils/network.js';
|
||||||
import { getTimezoneLabel } from '../../utils/time.js';
|
import { getTimezoneLabel } from '../../utils/time.js';
|
||||||
import { password } from '../../externals.js';
|
import { password, routerPrefix } from '../../externals.js';
|
||||||
import { hashPassword } from '../../utils/hash.js';
|
import { hashPassword } from '../../utils/hash.js';
|
||||||
|
|
||||||
const startedAt = new Date();
|
const startedAt = new Date();
|
||||||
@@ -55,10 +55,19 @@ export const hashedPassword = hasPassword ? hashPassword(password as string) : u
|
|||||||
/**
|
/**
|
||||||
* Generates a pre-authenticated URL by injecting a token in the URL params
|
* Generates a pre-authenticated URL by injecting a token in the URL params
|
||||||
*/
|
*/
|
||||||
export function generateAuthenticatedUrl(baseUrl: string, path: string, lock: boolean, authenticate: boolean): URL {
|
export function generateAuthenticatedUrl(
|
||||||
const url = new URL(path, baseUrl);
|
baseUrl: string,
|
||||||
if (authenticate && hashedPassword) {
|
path: string,
|
||||||
url.searchParams.append('token', hashedPassword);
|
lock: boolean,
|
||||||
|
authenticate: boolean,
|
||||||
|
prefix = routerPrefix,
|
||||||
|
hash = hashedPassword,
|
||||||
|
): URL {
|
||||||
|
const url = new URL(baseUrl);
|
||||||
|
url.pathname = prefix ? `${prefix}/${path}` : path;
|
||||||
|
|
||||||
|
if (authenticate && hash) {
|
||||||
|
url.searchParams.append('token', hash);
|
||||||
}
|
}
|
||||||
if (lock) {
|
if (lock) {
|
||||||
url.searchParams.append('locked', 'true');
|
url.searchParams.append('locked', 'true');
|
||||||
|
|||||||
@@ -16,13 +16,14 @@ export const isDocker = env === 'docker';
|
|||||||
export const isProduction = isDocker || (env === 'production' && !isTest);
|
export const isProduction = isDocker || (env === 'production' && !isTest);
|
||||||
export const isOntimeCloud = Boolean(process.env.IS_CLOUD);
|
export const isOntimeCloud = Boolean(process.env.IS_CLOUD);
|
||||||
export const password = process.env.SESSION_PASSWORD;
|
export const password = process.env.SESSION_PASSWORD;
|
||||||
|
export const routerPrefix = process.env.ROUTER_PREFIX;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Updates the router prefix in the index.html file
|
* Updates the router prefix in the index.html file
|
||||||
* This is only needed in the cloud environment where the client is not at the root segment
|
* This is only needed in the cloud environment where the client is not at the root segment
|
||||||
* ie: https://cloud.getontime.com/client-hash/timer
|
* ie: https://cloud.getontime.com/client-hash/timer
|
||||||
*/
|
*/
|
||||||
export function updateRouterPrefix(prefix: string | undefined = process.env.ROUTER_PREFIX): string {
|
export function updateRouterPrefix(prefix: string | undefined = routerPrefix): string {
|
||||||
if (!prefix) {
|
if (!prefix) {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,5 +39,6 @@ export function getShouldTimerUpdate(previousValue: MaybeNumber, currentValue: M
|
|||||||
export function getForceUpdate(previousUpdate: number, now: number): boolean {
|
export function getForceUpdate(previousUpdate: number, now: number): boolean {
|
||||||
const isClockBehind = now < previousUpdate;
|
const isClockBehind = now < previousUpdate;
|
||||||
const hasExceededRate = now - previousUpdate >= timerConfig.notificationRate;
|
const hasExceededRate = now - previousUpdate >= timerConfig.notificationRate;
|
||||||
return isClockBehind || hasExceededRate;
|
const newSeconds = millisToSeconds(previousUpdate) !== millisToSeconds(now);
|
||||||
|
return isClockBehind || hasExceededRate || newSeconds;
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "ontime",
|
"name": "ontime",
|
||||||
"version": "3.14.0",
|
"version": "3.14.3",
|
||||||
"description": "Time keeping for live events",
|
"description": "Time keeping for live events",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"ontime",
|
"ontime",
|
||||||
|
|||||||
@@ -276,20 +276,32 @@ export function getPreviousEventNormal(
|
|||||||
export const swapEventData = (eventA: OntimeEvent, eventB: OntimeEvent): { newA: OntimeEvent; newB: OntimeEvent } => {
|
export const swapEventData = (eventA: OntimeEvent, eventB: OntimeEvent): { newA: OntimeEvent; newB: OntimeEvent } => {
|
||||||
const newA = {
|
const newA = {
|
||||||
...eventB,
|
...eventB,
|
||||||
|
// events keep the ID
|
||||||
id: eventA.id,
|
id: eventA.id,
|
||||||
|
// events keep the schedule
|
||||||
timeStart: eventA.timeStart,
|
timeStart: eventA.timeStart,
|
||||||
timeEnd: eventA.timeEnd,
|
timeEnd: eventA.timeEnd,
|
||||||
duration: eventA.duration,
|
duration: eventA.duration,
|
||||||
|
linkStart: eventA.linkStart,
|
||||||
|
// keep schedule metadata
|
||||||
delay: eventA.delay,
|
delay: eventA.delay,
|
||||||
|
gap: eventA.gap,
|
||||||
|
dayOffset: eventA.dayOffset,
|
||||||
};
|
};
|
||||||
|
|
||||||
const newB = {
|
const newB = {
|
||||||
...eventA,
|
...eventA,
|
||||||
|
// events keep the ID
|
||||||
id: eventB.id,
|
id: eventB.id,
|
||||||
|
// events keep the schedule
|
||||||
timeStart: eventB.timeStart,
|
timeStart: eventB.timeStart,
|
||||||
timeEnd: eventB.timeEnd,
|
timeEnd: eventB.timeEnd,
|
||||||
duration: eventB.duration,
|
duration: eventB.duration,
|
||||||
|
linkStart: eventB.linkStart,
|
||||||
|
// keep schedule metadata
|
||||||
delay: eventB.delay,
|
delay: eventB.delay,
|
||||||
|
gap: eventB.gap,
|
||||||
|
dayOffset: eventB.dayOffset,
|
||||||
};
|
};
|
||||||
|
|
||||||
return { newA, newB };
|
return { newA, newB };
|
||||||
|
|||||||
Generated
+21
-34
@@ -113,9 +113,6 @@ importers:
|
|||||||
'@mantine/hooks':
|
'@mantine/hooks':
|
||||||
specifier: ^7.13.3
|
specifier: ^7.13.3
|
||||||
version: 7.13.3(react@18.3.1)
|
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':
|
'@sentry/react':
|
||||||
specifier: ^8.43.0
|
specifier: ^8.43.0
|
||||||
version: 8.45.0(react@18.3.1)
|
version: 8.45.0(react@18.3.1)
|
||||||
@@ -161,6 +158,9 @@ importers:
|
|||||||
react-hook-form:
|
react-hook-form:
|
||||||
specifier: ^7.53.1
|
specifier: ^7.53.1
|
||||||
version: 7.53.1(react@18.3.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:
|
react-qr-code:
|
||||||
specifier: ^2.0.12
|
specifier: ^2.0.12
|
||||||
version: 2.0.12(react@18.3.1)
|
version: 2.0.12(react@18.3.1)
|
||||||
@@ -1670,11 +1670,6 @@ packages:
|
|||||||
'@popperjs/core@2.11.8':
|
'@popperjs/core@2.11.8':
|
||||||
resolution: {integrity: sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==}
|
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':
|
'@remix-run/router@1.2.1':
|
||||||
resolution: {integrity: sha512-XiY0IsyHR+DXYS5vBxpoBe/8veTeoRpMHP+vDosLZxL5bnpetzI0igkxkLZS235ldLzyfkxF+2divEwWHP3vMQ==}
|
resolution: {integrity: sha512-XiY0IsyHR+DXYS5vBxpoBe/8veTeoRpMHP+vDosLZxL5bnpetzI0igkxkLZS235ldLzyfkxF+2divEwWHP3vMQ==}
|
||||||
engines: {node: '>=14'}
|
engines: {node: '>=14'}
|
||||||
@@ -2069,9 +2064,6 @@ packages:
|
|||||||
'@types/multer@1.4.11':
|
'@types/multer@1.4.11':
|
||||||
resolution: {integrity: sha512-svK240gr6LVWvv3YGyhLlA+6LRRWA4mnGIU7RcNmgjBYFl6665wcXrRfxGp5tEPVHUNm5FMcmq7too9bxCwX/w==}
|
resolution: {integrity: sha512-svK240gr6LVWvv3YGyhLlA+6LRRWA4mnGIU7RcNmgjBYFl6665wcXrRfxGp5tEPVHUNm5FMcmq7too9bxCwX/w==}
|
||||||
|
|
||||||
'@types/node@20.14.10':
|
|
||||||
resolution: {integrity: sha512-MdiXf+nDuMvY0gJKxyfZ7/6UFsETO7mGKF54MVD/ekJS6HdFtpZFBgrh6Pseu64XTb2MLyFPlbW6hj8HYRQNOQ==}
|
|
||||||
|
|
||||||
'@types/node@20.17.16':
|
'@types/node@20.17.16':
|
||||||
resolution: {integrity: sha512-vOTpLduLkZXePLxHiHsBLp98mHGnl8RptV4YAO3HfKO5UHjDvySGbxKtpYfy8Sx5+WKcgc45qNreJJRVM3L6mw==}
|
resolution: {integrity: sha512-vOTpLduLkZXePLxHiHsBLp98mHGnl8RptV4YAO3HfKO5UHjDvySGbxKtpYfy8Sx5+WKcgc45qNreJJRVM3L6mw==}
|
||||||
|
|
||||||
@@ -4232,6 +4224,11 @@ packages:
|
|||||||
peerDependencies:
|
peerDependencies:
|
||||||
react: ^16.8.0 || ^17 || ^18 || ^19
|
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:
|
react-is@16.13.1:
|
||||||
resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==}
|
resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==}
|
||||||
|
|
||||||
@@ -4797,9 +4794,6 @@ packages:
|
|||||||
unbox-primitive@1.0.2:
|
unbox-primitive@1.0.2:
|
||||||
resolution: {integrity: sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==}
|
resolution: {integrity: sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==}
|
||||||
|
|
||||||
undici-types@5.26.5:
|
|
||||||
resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==}
|
|
||||||
|
|
||||||
undici-types@6.19.8:
|
undici-types@6.19.8:
|
||||||
resolution: {integrity: sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==}
|
resolution: {integrity: sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==}
|
||||||
|
|
||||||
@@ -6456,10 +6450,6 @@ snapshots:
|
|||||||
|
|
||||||
'@popperjs/core@2.11.8': {}
|
'@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': {}
|
'@remix-run/router@1.2.1': {}
|
||||||
|
|
||||||
'@rollup/pluginutils@5.1.0(rollup@4.17.2)':
|
'@rollup/pluginutils@5.1.0(rollup@4.17.2)':
|
||||||
@@ -6772,7 +6762,7 @@ snapshots:
|
|||||||
'@types/body-parser@1.19.2':
|
'@types/body-parser@1.19.2':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/connect': 3.4.35
|
'@types/connect': 3.4.35
|
||||||
'@types/node': 20.17.16
|
'@types/node': 22.10.10
|
||||||
|
|
||||||
'@types/cacheable-request@6.0.3':
|
'@types/cacheable-request@6.0.3':
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -6793,11 +6783,11 @@ snapshots:
|
|||||||
|
|
||||||
'@types/connect@3.4.35':
|
'@types/connect@3.4.35':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/node': 20.17.16
|
'@types/node': 22.10.10
|
||||||
|
|
||||||
'@types/cors@2.8.17':
|
'@types/cors@2.8.17':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/node': 20.17.16
|
'@types/node': 22.10.10
|
||||||
|
|
||||||
'@types/debug@4.1.12':
|
'@types/debug@4.1.12':
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -6807,7 +6797,7 @@ snapshots:
|
|||||||
|
|
||||||
'@types/express-serve-static-core@4.17.33':
|
'@types/express-serve-static-core@4.17.33':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/node': 20.17.16
|
'@types/node': 22.10.10
|
||||||
'@types/qs': 6.9.7
|
'@types/qs': 6.9.7
|
||||||
'@types/range-parser': 1.2.4
|
'@types/range-parser': 1.2.4
|
||||||
|
|
||||||
@@ -6844,10 +6834,6 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
'@types/express': 4.17.17
|
'@types/express': 4.17.17
|
||||||
|
|
||||||
'@types/node@20.14.10':
|
|
||||||
dependencies:
|
|
||||||
undici-types: 5.26.5
|
|
||||||
|
|
||||||
'@types/node@20.17.16':
|
'@types/node@20.17.16':
|
||||||
dependencies:
|
dependencies:
|
||||||
undici-types: 6.19.8
|
undici-types: 6.19.8
|
||||||
@@ -6891,18 +6877,18 @@ snapshots:
|
|||||||
'@types/serve-static@1.15.0':
|
'@types/serve-static@1.15.0':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/mime': 3.0.1
|
'@types/mime': 3.0.1
|
||||||
'@types/node': 20.17.16
|
'@types/node': 22.10.10
|
||||||
|
|
||||||
'@types/verror@1.10.9':
|
'@types/verror@1.10.9':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@types/websocket@1.0.5':
|
'@types/websocket@1.0.5':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/node': 20.17.16
|
'@types/node': 22.10.10
|
||||||
|
|
||||||
'@types/ws@8.5.10':
|
'@types/ws@8.5.10':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/node': 20.17.16
|
'@types/node': 22.10.10
|
||||||
|
|
||||||
'@types/yauzl@2.10.3':
|
'@types/yauzl@2.10.3':
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -7451,7 +7437,6 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
es-errors: 1.3.0
|
es-errors: 1.3.0
|
||||||
function-bind: 1.1.2
|
function-bind: 1.1.2
|
||||||
optional: true
|
|
||||||
|
|
||||||
call-bind@1.0.2:
|
call-bind@1.0.2:
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -7795,7 +7780,7 @@ snapshots:
|
|||||||
|
|
||||||
dunder-proto@1.0.1:
|
dunder-proto@1.0.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
call-bind-apply-helpers: 1.0.1
|
call-bind-apply-helpers: 1.0.2
|
||||||
es-errors: 1.3.0
|
es-errors: 1.3.0
|
||||||
gopd: 1.2.0
|
gopd: 1.2.0
|
||||||
|
|
||||||
@@ -7853,7 +7838,7 @@ snapshots:
|
|||||||
electron@31.2.0:
|
electron@31.2.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@electron/get': 2.0.3
|
'@electron/get': 2.0.3
|
||||||
'@types/node': 20.14.10
|
'@types/node': 20.17.16
|
||||||
extract-zip: 2.0.1
|
extract-zip: 2.0.1
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
@@ -9401,6 +9386,10 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
react: 18.3.1
|
react: 18.3.1
|
||||||
|
|
||||||
|
react-icons@5.4.0(react@18.3.1):
|
||||||
|
dependencies:
|
||||||
|
react: 18.3.1
|
||||||
|
|
||||||
react-is@16.13.1: {}
|
react-is@16.13.1: {}
|
||||||
|
|
||||||
react-qr-code@2.0.12(react@18.3.1):
|
react-qr-code@2.0.12(react@18.3.1):
|
||||||
@@ -10004,8 +9993,6 @@ snapshots:
|
|||||||
has-symbols: 1.0.3
|
has-symbols: 1.0.3
|
||||||
which-boxed-primitive: 1.0.2
|
which-boxed-primitive: 1.0.2
|
||||||
|
|
||||||
undici-types@5.26.5: {}
|
|
||||||
|
|
||||||
undici-types@6.19.8: {}
|
undici-types@6.19.8: {}
|
||||||
|
|
||||||
undici-types@6.20.0: {}
|
undici-types@6.20.0: {}
|
||||||
|
|||||||
Reference in New Issue
Block a user