Compare commits

..

17 Commits

Author SHA1 Message Date
arc-alex 6397d33eaa feat: option to select all events for countdown 2025-09-16 16:59:29 +02:00
arc-alex 9288566a7f chore: remove unused code 2025-09-16 16:38:11 +02:00
Alex Christoffer Rasmussen 7fd9f83fb0 fix docker ignore (#1780) 2025-09-16 16:27:00 +02:00
Carlos Valente e621f1386e fix(cuesheet): infinite render loop on resizing columns 2025-09-16 06:43:19 +02:00
Carlos Valente b51e7cbd2d fix: maintain multiline in cuesheet cells 2025-09-14 14:19:18 +02:00
Carlos Valente 7cd92ce5f7 docs: add contribution guidelines 2025-09-14 14:19:18 +02:00
Carlos Valente f6a02abf36 fix: skip nested events 2025-09-14 14:19:18 +02:00
Carlos Valente e4b0df42cf feat: allow finding milestones 2025-09-14 14:19:18 +02:00
Carlos Valente ded8bddb2d refactor: improve automated following 2025-09-14 14:19:18 +02:00
Carlos Valente 8d3ce46c56 fix: allow moving a group after another 2025-09-14 14:19:18 +02:00
Carlos Valente 578cb2b244 refactor: improve pin styling 2025-09-14 14:19:18 +02:00
Carlos Valente c79ee193c9 fix: prevent layout reflow on different entry types 2025-09-14 14:19:18 +02:00
Carlos Valente 5810ae0d54 refactor: add default value to secondary source 2025-09-14 14:19:18 +02:00
Carlos Valente bfbf8574e3 fix: phase style overrides in timer 2025-09-14 14:19:18 +02:00
Alex Christoffer Rasmussen 490e429f44 cleanup (#1776)
* chore: cleanup leftover console log

* chore: prevent error in client tsconfig
2025-09-13 16:31:56 +02:00
Alex Christoffer Rasmussen 2e950965e0 fix: sheet default import map (#1775)
* fix: default import map

* fix: update test
2025-09-08 09:49:01 +02:00
arc-alex cc34db775a chore: bump version 2025-09-08 06:51:07 +02:00
89 changed files with 575 additions and 1605 deletions
-42
View File
@@ -1,42 +0,0 @@
name: Ontime Resolver build
on:
release:
types: [published]
workflow_dispatch:
jobs:
build_cli:
permissions:
id-token: write
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Use Node.js
uses: actions/setup-node@v4
with:
node-version: 22
registry-url: 'https://registry.npmjs.org'
- name: Setup pnpm
uses: pnpm/action-setup@v3
with:
version: 10
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Build project packages
run: pnpm turbo @getontime/resolver#build
- name: Publish to NPM
run: pnpm publish --access public --no-git-checks
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
NPM_CONFIG_PROVENANCE: true
working-directory: ./apps/resolver
-1
View File
@@ -8,4 +8,3 @@ playwright-report
**/*.toml **/*.toml
**/*.yml **/*.yml
**/*.json **/*.json
!tsconfig.common.json
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@getontime/cli", "name": "@getontime/cli",
"version": "4.0.0-beta.4", "version": "4.0.0-beta.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 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "ontime-ui", "name": "ontime-ui",
"version": "4.0.0-beta.4", "version": "4.0.0-beta.3",
"private": true, "private": true,
"type": "module", "type": "module",
"dependencies": { "dependencies": {
-2
View File
@@ -8,7 +8,6 @@ import IdentifyOverlay from './common/components/identify-overlay/IdentifyOverla
import { AppContextProvider } from './common/context/AppContext'; import { AppContextProvider } from './common/context/AppContext';
import { ontimeQueryClient } from './common/queryClient'; import { ontimeQueryClient } from './common/queryClient';
import { connectSocket } from './common/utils/socket'; import { connectSocket } from './common/utils/socket';
import KeepAwake from './features/keep-awake/KeepAwake';
import { TranslationProvider } from './translation/TranslationProvider'; import { TranslationProvider } from './translation/TranslationProvider';
import AppRouter from './AppRouter'; import AppRouter from './AppRouter';
import { baseURI } from './externals'; import { baseURI } from './externals';
@@ -25,7 +24,6 @@ function App() {
<ErrorBoundary> <ErrorBoundary>
<TranslationProvider> <TranslationProvider>
<IdentifyOverlay /> <IdentifyOverlay />
<KeepAwake />
<AppRouter /> <AppRouter />
</TranslationProvider> </TranslationProvider>
</ErrorBoundary> </ErrorBoundary>
@@ -1,11 +1,10 @@
import { memo } from 'react'; import { memo } from 'react';
import { IoClose, IoContract, IoExpand, IoEye, IoLockClosedOutline, IoSwapVertical } from 'react-icons/io5'; import { IoClose, IoContract, IoExpand, IoLockClosedOutline, IoSwapVertical } from 'react-icons/io5';
import { useLocation } from 'react-router'; import { useLocation } from 'react-router';
import { Dialog } from '@base-ui-components/react/dialog'; import { Dialog } from '@base-ui-components/react/dialog';
import { useDisclosure, useFullscreen } from '@mantine/hooks'; import { useDisclosure, useFullscreen } from '@mantine/hooks';
import { isLocalhost } from '../../../externals'; import { isLocalhost } from '../../../externals';
import { useKeepAwakeOptions } from '../../../features/keep-awake/KeepAwake';
import { navigatorConstants } from '../../../viewerConfig'; import { navigatorConstants } from '../../../viewerConfig';
import { useClientStore } from '../../stores/clientStore'; import { useClientStore } from '../../stores/clientStore';
import { useViewOptionsStore } from '../../stores/viewOptions'; import { useViewOptionsStore } from '../../stores/viewOptions';
@@ -32,7 +31,6 @@ function NavigationMenu({ isOpen, onClose }: NavigationMenuProps) {
const [isRenameOpen, handlers] = useDisclosure(false); const [isRenameOpen, handlers] = useDisclosure(false);
const { fullscreen, toggle } = useFullscreen(); const { fullscreen, toggle } = useFullscreen();
const { mirror, toggleMirror } = useViewOptionsStore(); const { mirror, toggleMirror } = useViewOptionsStore();
const { keepAwake, toggleKeepAwake } = useKeepAwakeOptions();
const location = useLocation(); const location = useLocation();
return ( return (
@@ -64,13 +62,6 @@ function NavigationMenu({ isOpen, onClose }: NavigationMenuProps) {
<IoSwapVertical /> <IoSwapVertical />
{mirror && <span className={style.note}>Active</span>} {mirror && <span className={style.note}>Active</span>}
</NavigationMenuItem> </NavigationMenuItem>
{window.isSecureContext && (
<NavigationMenuItem active={keepAwake} onClick={toggleKeepAwake}>
Keep Awake
<IoEye />
{keepAwake && <span className={style.note}>Active</span>}
</NavigationMenuItem>
)}
<NavigationMenuItem onClick={handlers.open}>Rename Client</NavigationMenuItem> <NavigationMenuItem onClick={handlers.open}>Rename Client</NavigationMenuItem>
<hr className={style.separator} /> <hr className={style.separator} />
@@ -28,7 +28,6 @@
bottom: 0; bottom: 0;
width: 40rem; width: 40rem;
max-width: 100vw;
height: 100vh; height: 100vh;
display: flex; display: flex;
+19 -40
View File
@@ -164,6 +164,24 @@ export const useProgressData = createSelector((state: RuntimeStore) => ({
timeDanger: state.eventNow?.timeDanger ?? null, timeDanger: state.eventNow?.timeDanger ?? null,
})); }));
export const useRundownOverview = createSelector((state: RuntimeStore) => ({
plannedStart: state.rundown.plannedStart,
actualStart: state.rundown.actualStart,
plannedEnd: state.rundown.plannedEnd,
expectedEnd: state.offset.expectedRundownEnd,
}));
export const useRuntimePlaybackOverview = createSelector((state: RuntimeStore) => ({
playback: state.timer.playback,
clock: state.clock,
numEvents: state.rundown.numEvents,
selectedEventIndex: state.rundown.selectedEventIndex,
offset: state.offset.mode === OffsetMode.Absolute ? state.offset.absolute : state.offset.relative,
groupExpectedEnd: state.offset.expectedGroupEnd,
}));
export const useTimelineStatus = createSelector((state: RuntimeStore) => ({ export const useTimelineStatus = createSelector((state: RuntimeStore) => ({
clock: state.clock, clock: state.clock,
offset: state.offset.absolute, offset: state.offset.absolute,
@@ -172,7 +190,7 @@ export const useTimelineStatus = createSelector((state: RuntimeStore) => ({
export const useExpectedStartData = createSelector((state: RuntimeStore) => ({ export const useExpectedStartData = createSelector((state: RuntimeStore) => ({
offset: state.offset.mode === OffsetMode.Absolute ? state.offset.absolute : state.offset.relative, offset: state.offset.mode === OffsetMode.Absolute ? state.offset.absolute : state.offset.relative,
mode: state.offset.mode, mode: state.offset.mode,
currentDay: state.rundown.currentDay ?? 0, currentDay: state.eventNow?.dayOffset ?? 0,
actualStart: state.rundown.actualStart, actualStart: state.rundown.actualStart,
plannedStart: state.rundown.plannedStart, plannedStart: state.rundown.plannedStart,
clock: state.clock, clock: state.clock,
@@ -209,45 +227,6 @@ export const usePlayback = () => {
return useRuntimeStore(featureSelector); return useRuntimeStore(featureSelector);
}; };
/* ======================= Overview data subscriptions ======================= */
export const useRundownOverview = createSelector((state: RuntimeStore) => ({
plannedStart: state.rundown.plannedStart,
actualStart: state.rundown.actualStart,
plannedEnd: state.rundown.plannedEnd,
expectedEnd: state.offset.expectedRundownEnd,
}));
export const useProgressOverview = createSelector((state: RuntimeStore) => ({
numEvents: state.rundown.numEvents,
selectedEventIndex: state.rundown.selectedEventIndex,
}));
export const useOffsetOverview = createSelector((state: RuntimeStore) => ({
offset: state.offset.mode === OffsetMode.Absolute ? state.offset.absolute : state.offset.relative,
playback: state.timer.playback,
}));
export const useGroupTimerOverView = createSelector((state: RuntimeStore) => ({
clock: state.clock,
mode: state.offset.mode,
groupExpectedEnd: state.offset.expectedGroupEnd,
// we can force these numbers to 0 fo this use case to avoid null checks
actualGroupStart: state.rundown.actualGroupStart ?? 0,
currentDay: state.eventNow?.dayOffset ?? 0,
playback: state.timer.playback,
}));
export const useFlagTimerOverView = createSelector((state: RuntimeStore) => ({
clock: state.clock,
mode: state.offset.mode,
// we can force these numbers to 0 fo this use case to avoid null checks
actualStart: state.rundown.actualStart ?? 0,
plannedStart: state.rundown.plannedStart ?? 0,
currentDay: state.eventNow?.dayOffset ?? 0,
playback: state.timer.playback,
}));
/* ======================= View specific subscriptions ======================= */ /* ======================= View specific subscriptions ======================= */
export const useTimerSocket = createSelector((state: RuntimeStore) => ({ export const useTimerSocket = createSelector((state: RuntimeStore) => ({
@@ -0,0 +1 @@
export type Size = 'xs' | 'sm' | 'md' | 'lg';
@@ -0,0 +1,89 @@
import { create } from 'zustand';
type Target = 'cuesheet' | 'timer' | 'clock' | 'countdown' | 'backstage' | 'studio';
interface SelectionState {
[key: string]: boolean;
}
interface ColumnPermissions {
read: string[];
write: string[];
}
interface CuesheetLinksState {
target: Target | null;
readSelected: SelectionState;
writeSelected: SelectionState;
setTarget: (target: Target | null) => void;
setField: (field: 'read' | 'write', key: string, value: boolean) => void;
toggleField: (field: 'read' | 'write', key: string) => void;
selectAll: (field: 'read' | 'write', keys: string[]) => void;
clearAll: (field: 'read' | 'write', keys: string[]) => void;
// Returns arrays of column keys that have read/write permissions if target is 'cuesheet'
getSelections: () => ColumnPermissions | null;
}
export const useCuesheetLinksStore = create<CuesheetLinksState>((set, get) => ({
target: null,
readSelected: {},
writeSelected: {},
setTarget: (target) => set({ target }),
setField: (field, key, value) =>
set((state) => ({
...(field === 'read'
? { readSelected: { ...state.readSelected, [key]: value } }
: { writeSelected: { ...state.writeSelected, [key]: value } }),
})),
toggleField: (field, key) =>
set((state) => ({
...(field === 'read'
? { readSelected: { ...state.readSelected, [key]: !state.readSelected[key] } }
: { writeSelected: { ...state.writeSelected, [key]: !state.writeSelected[key] } }),
})),
selectAll: (field, keys) =>
set((_state) => ({
...(field === 'read'
? {
readSelected: keys.reduce((acc, key) => {
acc[key] = true;
return acc;
}, {} as SelectionState),
}
: {
writeSelected: keys.reduce((acc, key) => {
acc[key] = true;
return acc;
}, {} as SelectionState),
}),
})),
clearAll: (field, keys) =>
set((_state) => ({
...(field === 'read'
? {
readSelected: keys.reduce((acc, key) => {
acc[key] = false;
return acc;
}, {} as SelectionState),
}
: {
writeSelected: keys.reduce((acc, key) => {
acc[key] = false;
return acc;
}, {} as SelectionState),
}),
})),
getSelections: () => {
const state = get();
if (state.target !== 'cuesheet') return null;
return {
read: Object.entries(state.readSelected)
.filter(([_, selected]) => selected)
.map(([key]) => key),
write: Object.entries(state.writeSelected)
.filter(([_, selected]) => selected)
.map(([key]) => key),
};
},
}));
@@ -0,0 +1,8 @@
import { MaybeString } from 'ontime-types';
export default function safeParseNumber(value: MaybeString, defaultValue: number = 0): number {
if (!value) return defaultValue;
const number = Number(value);
if (isNaN(number)) return defaultValue;
return number;
}
+2 -2
View File
@@ -1,5 +1,5 @@
import { import {
ApiActionTag, ApiAction,
Log, Log,
MessageTag, MessageTag,
RefetchKey, RefetchKey,
@@ -199,7 +199,7 @@ export const connectSocket = () => {
}; };
}; };
export function sendSocket<T extends MessageTag | ApiActionTag>( export function sendSocket<T extends MessageTag | ApiAction>(
tag: T, tag: T,
payload: T extends MessageTag ? Pick<WsPacketToServer & { tag: T }, 'payload'>['payload'] : unknown, payload: T extends MessageTag ? Pick<WsPacketToServer & { tag: T }, 'payload'>['payload'] : unknown,
): void { ): void {
+5 -5
View File
@@ -33,8 +33,8 @@ export function validateProjectFile(file: File) {
} }
// Limit file size of a project file to around 1MB // Limit file size of a project file to around 1MB
if (file.size > 2_000_000) { if (file.size > 1_000_000) {
throw new Error('File size limit (2MB) exceeded'); throw new Error('File size limit (1MB) exceeded');
} }
} }
@@ -56,8 +56,8 @@ export function validateLogo(file: File) {
throw new Error('File is empty'); throw new Error('File is empty');
} }
// Limit file size of a project file to around 1.5MB // Limit file size of a project file to around 1MB
if (file.size > 1_500_000) { if (file.size > 1_000_000) {
throw new Error('File size limit (1.5MB) exceeded'); throw new Error('File size limit (1MB) exceeded');
} }
} }
@@ -0,0 +1,29 @@
/* eslint-disable react/display-name */
import { ComponentType, useEffect } from 'react';
import { useLocation, useNavigate } from 'react-router';
import useUrlPresets from '../common/hooks-query/useUrlPresets';
import { getRouteFromPreset } from '../common/utils/urlPresets';
const withPreset = <P extends object>(Component: ComponentType<P>) => {
return (props: Partial<P>) => {
const { data } = useUrlPresets();
const navigate = useNavigate();
const location = useLocation();
// navigate if is alias route
useEffect(() => {
if (!data) return;
const destination = getRouteFromPreset(location, data);
// navigate to this destination if its not null
if (destination) {
navigate(destination);
}
}, [data, navigate, location]);
return <Component {...(props as P)} />;
};
};
export default withPreset;
@@ -107,9 +107,6 @@ export default function AutomationsList(props: AutomationsListProps) {
</IconButton> </IconButton>
</Panel.InlineElements> </Panel.InlineElements>
</tr> </tr>
</Fragment>
);
})}
{deleteError && ( {deleteError && (
<tr> <tr>
<td colSpan={5}> <td colSpan={5}>
@@ -117,6 +114,9 @@ export default function AutomationsList(props: AutomationsListProps) {
</td> </td>
</tr> </tr>
)} )}
</Fragment>
);
})}
</tbody> </tbody>
</Panel.Table> </Panel.Table>
</Panel.Card> </Panel.Card>
@@ -2,7 +2,6 @@ import { useState } from 'react';
import { CustomFields, Rundown } from 'ontime-types'; import { CustomFields, Rundown } from 'ontime-types';
import Button from '../../../../../common/components/buttons/Button'; import Button from '../../../../../common/components/buttons/Button';
import useRundown from '../../../../../common/hooks-query/useRundown';
import * as Panel from '../../../panel-utils/PanelUtils'; import * as Panel from '../../../panel-utils/PanelUtils';
import PreviewSpreadsheet from './preview/PreviewRundown'; import PreviewSpreadsheet from './preview/PreviewRundown';
@@ -18,7 +17,7 @@ interface ImportReviewProps {
export default function ImportReview(props: ImportReviewProps) { export default function ImportReview(props: ImportReviewProps) {
const { rundown, customFields, onFinished, onCancel } = props; const { rundown, customFields, onFinished, onCancel } = props;
const { data: currentRundown } = useRundown();
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const { importRundown } = useGoogleSheet(); const { importRundown } = useGoogleSheet();
const resetPreview = useSheetStore((state) => state.resetPreview); const resetPreview = useSheetStore((state) => state.resetPreview);
@@ -30,12 +29,9 @@ export default function ImportReview(props: ImportReviewProps) {
const applyImport = async () => { const applyImport = async () => {
setLoading(true); setLoading(true);
// we need to import on-top of the currently loaded rundown
// so the id needs to match
await importRundown( await importRundown(
{ {
[currentRundown.id]: { ...rundown, id: currentRundown.id, title: currentRundown.title }, [rundown.id]: rundown,
}, },
customFields, customFields,
); );
@@ -27,7 +27,6 @@ export default function ProjectData() {
reset, reset,
formState: { isSubmitting, isValid, isDirty, errors }, formState: { isSubmitting, isValid, isDirty, errors },
setError, setError,
clearErrors,
watch, watch,
control, control,
setValue, setValue,
@@ -54,7 +53,6 @@ export default function ProjectData() {
const handleUploadProjectLogo = async (event: ChangeEvent<HTMLInputElement>) => { const handleUploadProjectLogo = async (event: ChangeEvent<HTMLInputElement>) => {
const file = event.target.files?.[0]; const file = event.target.files?.[0];
clearErrors('logo');
if (!file) { if (!file) {
return; return;
@@ -95,7 +93,6 @@ export default function ProjectData() {
const onSubmit = async (formData: ProjectData) => { const onSubmit = async (formData: ProjectData) => {
try { try {
clearErrors();
await updateProjectData(formData); await updateProjectData(formData);
} catch (error) { } catch (error) {
const message = maybeAxiosError(error); const message = maybeAxiosError(error);
@@ -1,84 +0,0 @@
import { use, useCallback, useEffect, useMemo, useState } from 'react';
import { useSearchParams } from 'react-router';
import { PresetContext } from '../../common/context/PresetContext';
/** @url https://developer.mozilla.org/en-US/docs/Web/API/WakeLock */
export default function KeepAwake() {
const { keepAwake } = useKeepAwakeOptions();
const [wakeLockSentinel, setWakeLockSentinel] = useState<WakeLockSentinel | null>(null);
const removeLock = () => {
if (wakeLockSentinel) wakeLockSentinel.release().finally(() => setWakeLockSentinel(null));
};
const acquireLock = () => {
if (!wakeLockSentinel || wakeLockSentinel.released) {
setWakeLockSentinel(null);
navigator.wakeLock
.request('screen')
.then((sentinel) => {
setWakeLockSentinel(sentinel);
})
.catch(console.error);
}
};
useEffect(() => {
const controller = new AbortController();
if (keepAwake) {
acquireLock();
document.addEventListener(
'visibilitychange',
() => {
if (wakeLockSentinel !== null && document.visibilityState === 'visible') {
acquireLock();
}
},
{ signal: controller.signal },
);
} else {
removeLock();
}
return () => {
controller.abort();
removeLock();
};
}, [keepAwake]);
return <></>;
}
const keepAwakeKey = 'keep-awake';
function getOptionsFromParams(searchParams: URLSearchParams, defaultValues?: URLSearchParams) {
// Helper to get value from either source, prioritizing defaultValues
return defaultValues?.has(keepAwakeKey) || searchParams.has(keepAwakeKey);
}
/**
* Hook exposes the keep awake options
*/
export function useKeepAwakeOptions() {
const [searchParams, setSearchParams] = useSearchParams();
const maybePreset = use(PresetContext);
const keepAwake = useMemo(() => {
const defaultValues = maybePreset ? new URLSearchParams(maybePreset.search) : undefined;
return getOptionsFromParams(searchParams, defaultValues);
}, [maybePreset, searchParams]);
const toggleKeepAwake = useCallback(() => {
setSearchParams((searchParams) => {
if (keepAwake) {
searchParams.delete(keepAwakeKey);
} else {
searchParams.set(keepAwakeKey, '1');
}
return searchParams;
});
}, [keepAwake]);
return { keepAwake, toggleKeepAwake };
}
@@ -47,10 +47,9 @@
.daySpan { .daySpan {
&::after { &::after {
content: "+"attr(data-day-offset); content: '*';
vertical-align: super; vertical-align: super;
font-size: 0.6em; font-size: 0.75em;
letter-spacing: 0;
color: $info-blue; color: $info-blue;
} }
} }
@@ -68,10 +67,3 @@
font-size: calc(1rem - 2px); font-size: calc(1rem - 2px);
text-align: right; text-align: right;
} }
.dueTime {
text-transform: capitalize;
font-size: 1rem;
letter-spacing: 0;
color: $playback-over;
}
@@ -8,26 +8,23 @@ import {
TbFolderPin, TbFolderPin,
TbFolderStar, TbFolderStar,
} from 'react-icons/tb'; } from 'react-icons/tb';
import { OffsetMode, OntimeEvent, OntimeGroup, TimerPhase, TimerType } from 'ontime-types'; import { OntimeEvent, OntimeGroup, TimerPhase, TimerType } from 'ontime-types';
import { dayInMs, isPlaybackActive, millisToString } from 'ontime-utils'; import { isPlaybackActive, millisToString } from 'ontime-utils';
import Tooltip from '../../../common/components/tooltip/Tooltip'; import Tooltip from '../../../common/components/tooltip/Tooltip';
import { import {
useClock, useClock,
useCurrentGroupId, useCurrentGroupId,
useFlagTimerOverView,
useGroupTimerOverView,
useNextFlag, useNextFlag,
useOffsetOverview,
useProgressOverview,
useRundownOverview, useRundownOverview,
useRuntimePlaybackOverview,
useTimer, useTimer,
} from '../../../common/hooks/useSocket'; } from '../../../common/hooks/useSocket';
import { useEntry } from '../../../common/hooks-query/useRundown'; import { useEntry } from '../../../common/hooks-query/useRundown';
import { getOffsetState, getOffsetText } from '../../../common/utils/offset'; import { getOffsetState, getOffsetText } from '../../../common/utils/offset';
import { cx, enDash, timerPlaceholder } from '../../../common/utils/styleUtils'; import { cx, enDash, timerPlaceholder } from '../../../common/utils/styleUtils';
import { formatTime } from '../../../common/utils/time'; import { formatTime } from '../../../common/utils/time';
import { calculateEndAndDaySpan, formatDueTime, formattedTime } from '../overview.utils'; import { calculateEndAndDaySpan, formattedTime } from '../overview.utils';
import { OverUnder, TimeColumn } from './TimeLayout'; import { OverUnder, TimeColumn } from './TimeLayout';
@@ -61,8 +58,8 @@ export function StartTimes() {
<Tooltip text='Planned end time' render={<TbCalendarPin className={style.icon} />} /> <Tooltip text='Planned end time' render={<TbCalendarPin className={style.icon} />} />
{maybePlannedDaySpan > 0 ? ( {maybePlannedDaySpan > 0 ? (
<Tooltip <Tooltip
text={`Rundown spans over ${maybePlannedDaySpan + 1} days`} text={`Event spans over ${maybePlannedDaySpan + 1} days`}
render={<span className={cx([style.time, style.daySpan])} data-day-offset={maybePlannedDaySpan} />} render={<span className={cx([style.time, style.daySpan])} />}
> >
{plannedEndText} {plannedEndText}
</Tooltip> </Tooltip>
@@ -74,8 +71,8 @@ export function StartTimes() {
<Tooltip text='Expected end time' render={<TbCalendarStar className={style.icon} />} /> <Tooltip text='Expected end time' render={<TbCalendarStar className={style.icon} />} />
{maybeExpectedEnd !== null && maybeExpectedDaySpan > 0 ? ( {maybeExpectedEnd !== null && maybeExpectedDaySpan > 0 ? (
<Tooltip <Tooltip
text={`Rundown spans over ${maybeExpectedDaySpan + 1} days`} text={`Event spans over ${maybeExpectedDaySpan + 1} days`}
render={<span className={cx([style.time, style.daySpan])} data-day-offset={maybeExpectedDaySpan} />} render={<span className={cx([style.time, style.daySpan])} />}
> >
{formattedTime(maybeExpectedEnd)} {formattedTime(maybeExpectedEnd)}
</Tooltip> </Tooltip>
@@ -99,110 +96,61 @@ export function MetadataTimes() {
); );
} }
//TODO: there a some things here we still need to think about, mainly what to do whit the planed group duration in relation to the events
function GroupTimes() { function GroupTimes() {
const { clock, mode, groupExpectedEnd, actualGroupStart, currentDay, playback } = useGroupTimerOverView(); const { clock, groupExpectedEnd } = useRuntimePlaybackOverview();
const { currentGroupId } = useCurrentGroupId(); const { currentGroupId } = useCurrentGroupId();
const group = useEntry(currentGroupId) as OntimeGroup | null; const group = useEntry(currentGroupId) as OntimeGroup | null;
const active = isPlaybackActive(playback); // the group end time dose not encode any day offsets
const plannedGroupEnd = group && group.timeStart !== null ? group.timeStart + group.duration - clock : null;
// the group end time dose not encode any day offsets so it is calculated with group start time and duration const plannedTimeUntilGroupEnd = formattedTime(plannedGroupEnd, 3, TimerType.CountDown);
const plannedGroupEnd = (() => {
if (!active) return null;
if (!group || group.timeStart === null) return null;
const normalizedClock = clock + currentDay * dayInMs;
return mode === OffsetMode.Absolute
? group.timeStart + group.duration - normalizedClock
: actualGroupStart + group.duration - normalizedClock;
})();
const plannedTimeUntilGroupEnd = formatDueTime(plannedGroupEnd, 3, TimerType.CountDown);
const expectedGroupEnd = groupExpectedEnd !== null ? groupExpectedEnd - clock : null; const expectedGroupEnd = groupExpectedEnd !== null ? groupExpectedEnd - clock : null;
const expectedTimeUntilGroupEnd = formatDueTime(expectedGroupEnd, 3, TimerType.CountDown); const expectedTimeUntilGroupEnd = formattedTime(expectedGroupEnd, 3, TimerType.CountDown);
const groupTitle = group?.title ?? null;
return ( return (
<div className={style.metadataRow}> <div className={style.metadataRow}>
<span className={group?.title ? style.labelTitle : style.label}>{`${group?.title || 'Group'} `}</span> <span className={groupTitle ? style.labelTitle : style.label}>{`${groupTitle ? groupTitle : 'Group'} `}</span>
<div className={style.labelledElement}> <div className={style.labelledElement}>
<Tooltip text='Time to planned group end' render={<TbFolderPin className={style.icon} />} /> <Tooltip text='Time to planned group end' render={<TbFolderPin className={style.icon} />} />
<span <span className={cx([style.time, !group && style.muted])}>{plannedTimeUntilGroupEnd}</span>
className={cx([
style.time,
(!group || !active) && style.muted,
plannedTimeUntilGroupEnd === 'due' && style.dueTime,
])}
>
{plannedTimeUntilGroupEnd}
</span>
</div> </div>
<div className={style.labelledElement}> <div className={style.labelledElement}>
<Tooltip text='Time to expected group end' render={<TbFolderStar className={style.icon} />} /> <Tooltip text='Time to expected group end' render={<TbFolderStar className={style.icon} />} />
<span <span className={cx([style.time, groupExpectedEnd === null && style.muted])}>{expectedTimeUntilGroupEnd}</span>
className={cx([
style.time,
!groupExpectedEnd && style.muted,
expectedTimeUntilGroupEnd === 'due' && style.dueTime,
])}
>
{expectedTimeUntilGroupEnd}
</span>
</div> </div>
</div> </div>
); );
} }
function FlagTimes() { function FlagTimes() {
const { clock, mode, actualStart, plannedStart, playback, currentDay } = useFlagTimerOverView(); const { clock } = useClock();
const { id, expectedStart } = useNextFlag(); const { id, expectedStart } = useNextFlag();
const entry = useEntry(id) as OntimeEvent | null; const entry = useEntry(id) as OntimeEvent | null;
const active = isPlaybackActive(playback); const plannedFlagStart = entry ? entry.timeStart - clock : null;
const plannedTimeUntilDisplay = formattedTime(plannedFlagStart, 3, TimerType.CountDown);
const plannedFlagStart = (() => {
if (!active) return null;
if (!entry) return null;
const normalizedTimeStart = entry.timeStart + entry.dayOffset * dayInMs;
const normalizedClock = clock + currentDay * dayInMs;
return mode === OffsetMode.Absolute
? normalizedTimeStart - normalizedClock
: normalizedTimeStart + actualStart - plannedStart - normalizedClock;
})();
const plannedTimeUntilDisplay = formatDueTime(plannedFlagStart, 3, TimerType.CountDown);
const expectedTimeUntil = expectedStart !== null ? expectedStart - clock : null; const expectedTimeUntil = expectedStart !== null ? expectedStart - clock : null;
const expectedTimeUntilDisplay = formatDueTime(expectedTimeUntil, 3, TimerType.CountDown); const expectedTimeUntilDisplay = formattedTime(expectedTimeUntil, 3, TimerType.CountDown);
const title = entry?.title ?? null; const title = entry?.title ?? null;
return ( return (
<div className={style.metadataRow}> <div className={style.metadataRow}>
<span className={title ? style.labelTitle : style.label}>{`${title || 'Flag'} `}</span> <span className={title ? style.labelTitle : style.label}>{`${title ? title : 'Flag'} `}</span>
<div className={style.labelledElement}> <div className={style.labelledElement}>
<Tooltip text='Time to next flag planned start' render={<TbFlagPin className={style.icon} />} /> <Tooltip text='Time to next flag planned start' render={<TbFlagPin className={style.icon} />} />
<span <span data-testid='flag-plannedStart' className={cx([style.time, !entry && style.muted])}>
data-testid='flag-plannedStart'
className={cx([
style.time,
(!entry || !active) && style.muted,
plannedTimeUntilDisplay === 'due' && style.dueTime,
])}
>
{plannedTimeUntilDisplay} {plannedTimeUntilDisplay}
</span> </span>
</div> </div>
<div className={style.labelledElement}> <div className={style.labelledElement}>
<Tooltip text='Time to next flag expected start' render={<TbFlagStar className={style.icon} />} /> <Tooltip text='Time to next flag expected start' render={<TbFlagStar className={style.icon} />} />
<span <span data-testid='flag-expectedStart' className={cx([style.time, expectedTimeUntil === null && style.muted])}>
data-testid='flag-expectedStart'
className={cx([
style.time,
expectedTimeUntil === null && style.muted,
expectedTimeUntilDisplay === 'due' && style.dueTime,
])}
>
{expectedTimeUntilDisplay} {expectedTimeUntilDisplay}
</span> </span>
</div> </div>
@@ -211,7 +159,7 @@ function FlagTimes() {
} }
export function ProgressOverview() { export function ProgressOverview() {
const { numEvents, selectedEventIndex } = useProgressOverview(); const { numEvents, selectedEventIndex } = useRuntimePlaybackOverview();
const current = selectedEventIndex !== null ? selectedEventIndex + 1 : enDash; const current = selectedEventIndex !== null ? selectedEventIndex + 1 : enDash;
const progressText = numEvents ? `${current} of ${numEvents || enDash}` : enDash; const progressText = numEvents ? `${current} of ${numEvents || enDash}` : enDash;
@@ -220,7 +168,7 @@ export function ProgressOverview() {
} }
export function OffsetOverview() { export function OffsetOverview() {
const { offset, playback } = useOffsetOverview(); const { offset, playback } = useRuntimePlaybackOverview();
const isPlaying = isPlaybackActive(playback); const isPlaying = isPlaybackActive(playback);
const offsetState = getOffsetState(isPlaying ? offset : null); const offsetState = getOffsetState(isPlaying ? offset : null);
@@ -3,23 +3,6 @@ import { dayInMs, millisToString } from 'ontime-utils';
import { timerPlaceholder, timerPlaceholderMin } from '../../common/utils/styleUtils'; import { timerPlaceholder, timerPlaceholderMin } from '../../common/utils/styleUtils';
/**
* Composition to stop negative timers from being formatted
* They should show a due string instead
*
* This is used for cases when a negative timer is unwanted
* eg: count down to a milestone
*/
export function formatDueTime(
time: MaybeNumber,
segments: number = 3,
direction?: TimerType.CountDown | TimerType.CountUp,
dueString = 'due',
): string {
if (time !== null && time <= 0) return dueString;
return formattedTime(time, segments, direction);
}
/** /**
* Encapsulates the logic for formatting time in overview * Encapsulates the logic for formatting time in overview
*/ */
@@ -73,7 +73,9 @@ interface EventUntilProps {
isLinkedToLoaded: boolean; isLinkedToLoaded: boolean;
} }
function EventUntil({ timeStart, delay, dayOffset, totalGap, isLinkedToLoaded }: EventUntilProps) { function EventUntil(props: EventUntilProps) {
const { timeStart, delay, dayOffset, totalGap, isLinkedToLoaded } = props;
const timeUntil = useTimeUntilExpectedStart({ timeStart, delay, dayOffset }, { totalGap, isLinkedToLoaded }); const timeUntil = useTimeUntilExpectedStart({ timeStart, delay, dayOffset }, { totalGap, isLinkedToLoaded });
const isDue = timeUntil < MILLIS_PER_SECOND; const isDue = timeUntil < MILLIS_PER_SECOND;
+1
View File
@@ -17,6 +17,7 @@ $header-font-size: clamp(24px, 2.5vw, 48px);
// General styling // General styling
$accent-color: $red-500; // --accent-color-override $accent-color: $red-500; // --accent-color-override
$delay-color: $ontime-delay-text;
$viewer-label-color: rgba(white, 25%); $viewer-label-color: rgba(white, 25%);
// Main Properties of a viewer // Main Properties of a viewer
@@ -10,14 +10,13 @@ import {
makeProjectDataOptions, makeProjectDataOptions,
} from '../../common/components/view-params-editor/viewParams.utils'; } from '../../common/components/view-params-editor/viewParams.utils';
import { PresetContext } from '../../common/context/PresetContext'; import { PresetContext } from '../../common/context/PresetContext';
import { getScheduleOptions } from '../common/schedule/schedule.options'; import { scheduleOptions } from '../common/schedule/schedule.options';
export const getBackstageOptions = ( export const getBackstageOptions = (
timeFormat: string, timeFormat: string,
customFields: CustomFields, customFields: CustomFields,
projectData: ProjectData, projectData: ProjectData,
): ViewOption[] => { ): ViewOption[] => {
const customFieldOptions = makeOptionsFromCustomFields(customFields, []);
const secondaryOptions = makeOptionsFromCustomFields(customFields, [ const secondaryOptions = makeOptionsFromCustomFields(customFields, [
{ value: 'none', label: 'None' }, { value: 'none', label: 'None' },
{ value: 'note', label: 'Note' }, { value: 'note', label: 'Note' },
@@ -40,7 +39,7 @@ export const getBackstageOptions = (
}, },
], ],
}, },
getScheduleOptions(customFieldOptions), scheduleOptions,
{ {
title: OptionTitle.ElementVisibility, title: OptionTitle.ElementVisibility,
collapsible: true, collapsible: true,
@@ -1,5 +1,14 @@
import { createContext, PropsWithChildren, RefObject, use, useEffect, useLayoutEffect, useRef, useState } from 'react'; import {
import { EntryId, isOntimeEvent, OntimeEntry, OntimeEvent } from 'ontime-types'; createContext,
PropsWithChildren,
RefObject,
useContext,
useEffect,
useLayoutEffect,
useRef,
useState,
} from 'react';
import { isOntimeEvent, OntimeEntry, OntimeEvent } from 'ontime-types';
import { usePartialRundown } from '../../../common/hooks-query/useRundown'; import { usePartialRundown } from '../../../common/hooks-query/useRundown';
@@ -16,18 +25,13 @@ interface ScheduleContextState {
const ScheduleContext = createContext<ScheduleContextState | undefined>(undefined); const ScheduleContext = createContext<ScheduleContextState | undefined>(undefined);
interface ScheduleProviderProps { interface ScheduleProviderProps {
selectedEventId: EntryId | null; selectedEventId: string | null;
} }
export const ScheduleProvider = ({ children, selectedEventId }: PropsWithChildren<ScheduleProviderProps>) => { export const ScheduleProvider = ({ children, selectedEventId }: PropsWithChildren<ScheduleProviderProps>) => {
const { cycleInterval, stopCycle, filter } = useScheduleOptions(); const { cycleInterval, stopCycle } = useScheduleOptions();
const { data: events } = usePartialRundown((entry: OntimeEntry) => { const { data: events } = usePartialRundown((event: OntimeEntry) => {
if (filter) { return isOntimeEvent(event);
// custom keys are prepended with custom-
const customKey = filter.startsWith('custom-') ? filter.slice('custom-'.length) : filter;
return isOntimeEvent(entry) && Boolean(entry.custom[customKey]);
}
return isOntimeEvent(entry);
}); });
const [firstIndex, setFirstIndex] = useState(-1); const [firstIndex, setFirstIndex] = useState(-1);
@@ -131,7 +135,7 @@ export const ScheduleProvider = ({ children, selectedEventId }: PropsWithChildre
selectedEventIndex = 0; selectedEventIndex = 0;
return ( return (
<ScheduleContext <ScheduleContext.Provider
value={{ value={{
events: viewEvents as OntimeEvent[], events: viewEvents as OntimeEvent[],
selectedEventId, selectedEventId,
@@ -141,12 +145,12 @@ export const ScheduleProvider = ({ children, selectedEventId }: PropsWithChildre
}} }}
> >
{children} {children}
</ScheduleContext> </ScheduleContext.Provider>
); );
}; };
export const useSchedule = () => { export const useSchedule = () => {
const context = use(ScheduleContext); const context = useContext(ScheduleContext);
if (!context) { if (!context) {
throw new Error('useSchedule() can only be used inside a ScheduleContext'); throw new Error('useSchedule() can only be used inside a ScheduleContext');
} }
@@ -22,7 +22,8 @@ interface ScheduleItemProps {
delay: number; delay: number;
} }
export default function ScheduleItem({ timeStart, timeEnd, title, colour, skip, delay }: ScheduleItemProps) { export default function ScheduleItem(props: ScheduleItemProps) {
const { timeStart, timeEnd, title, colour, skip, delay } = props;
const { showExpected } = useScheduleOptions(); const { showExpected } = useScheduleOptions();
if (showExpected) { if (showExpected) {
@@ -66,7 +67,9 @@ export default function ScheduleItem({ timeStart, timeEnd, title, colour, skip,
); );
} }
function DelayedScheduleItem({ timeStart, timeEnd, title, colour, skip, delay }: ScheduleItemProps) { function DelayedScheduleItem(props: ScheduleItemProps) {
const { timeStart, timeEnd, title, colour, skip, delay } = props;
const start = formatTime(timeStart, formatOptions); const start = formatTime(timeStart, formatOptions);
const end = formatTime(timeEnd, formatOptions); const end = formatTime(timeEnd, formatOptions);
const delayedStart = formatTime(timeStart + delay, formatOptions); const delayedStart = formatTime(timeStart + delay, formatOptions);
@@ -92,7 +95,9 @@ function DelayedScheduleItem({ timeStart, timeEnd, title, colour, skip, delay }:
); );
} }
function ExpectedScheduleItem({ timeStart, timeEnd, title, colour, skip, delay }: ScheduleItemProps) { function ExpectedScheduleItem(props: ScheduleItemProps) {
const { timeStart, timeEnd, title, colour, skip, delay } = props;
return ( return (
<li className={cx(['entry', skip && 'entry--skip'])}> <li className={cx(['entry', skip && 'entry--skip'])}>
<div className='entry-times'> <div className='entry-times'>
@@ -111,11 +116,12 @@ interface ExpectedTimeProps {
delay: number; delay: number;
} }
function ExpectedTime({ time, delay }: ExpectedTimeProps) { function ExpectedTime(props: ExpectedTimeProps) {
const { time, delay } = props;
const { offset } = useRuntimeOffset(); const { offset } = useRuntimeOffset();
const expectedOffset = offset - delay; const expectedOffset = offset - delay;
const expectedTime = formatTime(time + offset, formatOptions); const expectedTime = formatTime(time - offset, formatOptions);
const expectedState = getOffsetState(expectedOffset); const expectedState = getOffsetState(expectedOffset);
return <SuperscriptTime className={`entry-times--${expectedState}`} time={expectedTime} />; return <SuperscriptTime className={`entry-times--${expectedState}`} time={expectedTime} />;
@@ -1,23 +1,14 @@
import { useMemo } from 'react'; import { useMemo } from 'react';
import { useSearchParams } from 'react-router'; import { useSearchParams } from 'react-router';
import { SelectOption } from '../../../common/components/select/Select';
import { OptionTitle } from '../../../common/components/view-params-editor/constants'; import { OptionTitle } from '../../../common/components/view-params-editor/constants';
import type { ViewOption } from '../../../common/components/view-params-editor/viewParams.types'; import { ViewOption } from '../../../common/components/view-params-editor/viewParams.types';
import { isStringBoolean } from '../../../features/viewers/common/viewUtils'; import { isStringBoolean } from '../../../features/viewers/common/viewUtils';
export const getScheduleOptions = (customFieldOptions: SelectOption[]): ViewOption => ({ export const scheduleOptions: ViewOption = {
title: OptionTitle.Schedule, title: OptionTitle.Schedule,
collapsible: true, collapsible: true,
options: [ options: [
{
id: 'filter',
title: 'Filter',
description: 'Hide events without data in the selected custom field',
type: 'option',
values: customFieldOptions,
defaultValue: 'None',
},
{ {
id: 'stopCycle', id: 'stopCycle',
title: 'Stop cycling through event pages', title: 'Stop cycling through event pages',
@@ -40,10 +31,9 @@ export const getScheduleOptions = (customFieldOptions: SelectOption[]): ViewOpti
defaultValue: false, defaultValue: false,
}, },
], ],
}); };
type ScheduleOptions = { type ScheduleOptions = {
filter: string | null;
cycleInterval: number; cycleInterval: number;
stopCycle: boolean; stopCycle: boolean;
showExpected: boolean; showExpected: boolean;
@@ -51,7 +41,6 @@ type ScheduleOptions = {
function getScheduleOptionsFromParams(searchParams: URLSearchParams): ScheduleOptions { function getScheduleOptionsFromParams(searchParams: URLSearchParams): ScheduleOptions {
return { return {
filter: searchParams.get('filter'),
cycleInterval: Number(searchParams.get('cycleInterval')) || 10, cycleInterval: Number(searchParams.get('cycleInterval')) || 10,
stopCycle: isStringBoolean(searchParams.get('stopCycle')), stopCycle: isStringBoolean(searchParams.get('stopCycle')),
showExpected: isStringBoolean(searchParams.get('showExpected')), showExpected: isStringBoolean(searchParams.get('showExpected')),
@@ -141,7 +141,7 @@ $item-height: 3.5rem;
} }
.sub__schedule--delayed { .sub__schedule--delayed {
color: $ontime-delay-text; color: $delay-color;
} }
.sub__schedule--strike { .sub__schedule--strike {
@@ -1,6 +1,6 @@
import { useMemo, useState } from 'react'; import { useMemo, useState } from 'react';
import { IoAdd } from 'react-icons/io5'; import { IoAdd } from 'react-icons/io5';
import { EntryId, isOntimeEvent, isPlayableEvent, OntimeEvent, OntimeView } from 'ontime-types'; import { isOntimeEvent, isPlayableEvent, OntimeEvent, OntimeView } from 'ontime-types';
import Button from '../../common/components/buttons/Button'; import Button from '../../common/components/buttons/Button';
import Empty from '../../common/components/state/Empty'; import Empty from '../../common/components/state/Empty';
@@ -16,7 +16,7 @@ import { useTranslation } from '../../translation/TranslationProvider';
import Loader from '../common/loader/Loader'; import Loader from '../common/loader/Loader';
import { getCountdownOptions, useCountdownOptions } from './countdown.options'; import { getCountdownOptions, useCountdownOptions } from './countdown.options';
import { getOrderedSubscriptions } from './countdown.utils'; import { CountdownSubscription, getOrderedSubscriptions } from './countdown.utils';
import CountdownSelect from './CountdownSelect'; import CountdownSelect from './CountdownSelect';
import CountdownSubscriptions from './CountdownSubscriptions'; import CountdownSubscriptions from './CountdownSubscriptions';
import SingleEventCountdown from './SingleEventCountdown'; import SingleEventCountdown from './SingleEventCountdown';
@@ -59,6 +59,8 @@ function Countdown({ customFields, rundownData, projectData, isMirrored, setting
[defaultFormat, customFields, subscriptions], [defaultFormat, customFields, subscriptions],
); );
console.log(subscriptions)
return ( return (
<div className={`countdown ${isMirrored ? 'mirror' : ''}`} data-testid='countdown-view'> <div className={`countdown ${isMirrored ? 'mirror' : ''}`} data-testid='countdown-view'>
<ViewParamsEditor target={OntimeView.Countdown} viewOptions={countdownOptions} /> <ViewParamsEditor target={OntimeView.Countdown} viewOptions={countdownOptions} />
@@ -87,7 +89,7 @@ function Countdown({ customFields, rundownData, projectData, isMirrored, setting
interface CountdownContentsProps { interface CountdownContentsProps {
playableEvents: ExtendedEntry<OntimeEvent>[]; playableEvents: ExtendedEntry<OntimeEvent>[];
subscriptions: EntryId[]; subscriptions: CountdownSubscription;
goToEditMode: () => void; goToEditMode: () => void;
} }
@@ -1,5 +1,5 @@
import { useState } from 'react'; import { useState } from 'react';
import { IoArrowBack, IoClose, IoSaveOutline } from 'react-icons/io5'; import { IoArrowBack, IoClose, IoSaveOutline, IoAlbumsOutline } from 'react-icons/io5';
import { useNavigate } from 'react-router'; import { useNavigate } from 'react-router';
import { EntryId, OntimeEvent } from 'ontime-types'; import { EntryId, OntimeEvent } from 'ontime-types';
@@ -7,18 +7,19 @@ import Button from '../../common/components/buttons/Button';
import { cx } from '../../common/utils/styleUtils'; import { cx } from '../../common/utils/styleUtils';
import ClockTime from '../../features/viewers/common/clock-time/ClockTime'; import ClockTime from '../../features/viewers/common/clock-time/ClockTime';
import { makeSubscriptionsUrl } from './countdown.utils'; import { CountdownSubscription, makeSubscriptionsUrl } from './countdown.utils';
import './Countdown.scss'; import './Countdown.scss';
interface CountdownSelectProps { interface CountdownSelectProps {
events: OntimeEvent[]; events: OntimeEvent[];
subscriptions: EntryId[]; subscriptions: CountdownSubscription;
disableEdit: () => void; disableEdit: () => void;
} }
export default function CountdownSelect({ events, subscriptions, disableEdit }: CountdownSelectProps) { export default function CountdownSelect({ events, subscriptions, disableEdit }: CountdownSelectProps) {
const [selected, setSelected] = useState<EntryId[]>(subscriptions); const maybeAllSubscriptions: EntryId[] = subscriptions === 'all' ? events.map((event) => event.id) : subscriptions;
const [selected, setSelected] = useState<EntryId[]>(maybeAllSubscriptions);
const navigate = useNavigate(); const navigate = useNavigate();
/** /**
@@ -47,6 +48,18 @@ export default function CountdownSelect({ events, subscriptions, disableEdit }:
navigate(url.search.toString()); navigate(url.search.toString());
}; };
/**
* Creates a URL with all
* and navigates to it
*/
const applyAll = () => {
// we remove events that no longer exist to avoid stale subscriptions
const url = makeSubscriptionsUrl(window.location.href, 'all');
disableEdit();
setSelected([]);
navigate(url.search.toString());
};
// make a copy of the selected array for quick lookup // make a copy of the selected array for quick lookup
const selectedIds = new Set(selected); const selectedIds = new Set(selected);
@@ -86,6 +99,10 @@ export default function CountdownSelect({ events, subscriptions, disableEdit }:
<Button variant='subtle' size='xlarge' onClick={disableEdit}> <Button variant='subtle' size='xlarge' onClick={disableEdit}>
<IoArrowBack /> Go back <IoArrowBack /> Go back
</Button> </Button>
<Button variant='subtle' size='xlarge' onClick={applyAll}>
{/* TODO: icon ??? */}
<IoAlbumsOutline /> Use All
</Button>
<Button variant='subtle' size='xlarge' onClick={() => setSelected([])} disabled={selected.length === 0}> <Button variant='subtle' size='xlarge' onClick={() => setSelected([])} disabled={selected.length === 0}>
<IoClose /> Clear <IoClose /> Clear
</Button> </Button>
@@ -8,11 +8,12 @@ import { ViewOption } from '../../common/components/view-params-editor/viewParam
import { makeOptionsFromCustomFields } from '../../common/components/view-params-editor/viewParams.utils'; import { makeOptionsFromCustomFields } from '../../common/components/view-params-editor/viewParams.utils';
import { PresetContext } from '../../common/context/PresetContext'; import { PresetContext } from '../../common/context/PresetContext';
import { isStringBoolean } from '../../features/viewers/common/viewUtils'; import { isStringBoolean } from '../../features/viewers/common/viewUtils';
import { CountdownSubscription } from './countdown.utils';
export const getCountdownOptions = ( export const getCountdownOptions = (
timeFormat: string, timeFormat: string,
customFields: CustomFields, customFields: CustomFields,
persistedSubscriptions: EntryId[], persistedSubscriptions: CountdownSubscription,
): ViewOption[] => { ): ViewOption[] => {
const secondaryOptions = makeOptionsFromCustomFields(customFields, [ const secondaryOptions = makeOptionsFromCustomFields(customFields, [
{ value: 'none', label: 'None' }, { value: 'none', label: 'None' },
@@ -55,7 +56,7 @@ export const getCountdownOptions = (
id: 'sub', id: 'sub',
title: 'Event subscription', title: 'Event subscription',
description: 'The events to follow', description: 'The events to follow',
values: persistedSubscriptions, values: persistedSubscriptions === 'all' ? ['all'] : persistedSubscriptions,
type: 'persist', type: 'persist',
}, },
], ],
@@ -64,7 +65,7 @@ export const getCountdownOptions = (
}; };
type CountdownOptions = { type CountdownOptions = {
subscriptions: EntryId[]; subscriptions: CountdownSubscription;
secondarySource: keyof OntimeEvent | null; secondarySource: keyof OntimeEvent | null;
showExpected: boolean; showExpected: boolean;
}; };
@@ -85,8 +86,10 @@ function getOptionsFromParams(searchParams: URLSearchParams, defaultValues?: URL
return searchParams.getAll(key) as EntryId[]; return searchParams.getAll(key) as EntryId[];
}; };
const subscriptions = getArrayValues('sub');
return { return {
subscriptions: getArrayValues('sub'), subscriptions: subscriptions.at(0) === 'all' ? 'all' : subscriptions,
secondarySource: getValue('secondary-src') as keyof OntimeEvent | null, secondarySource: getValue('secondary-src') as keyof OntimeEvent | null,
showExpected: isStringBoolean(getValue('showExpected')), showExpected: isStringBoolean(getValue('showExpected')),
}; };
@@ -14,6 +14,8 @@ export function sanitiseTitle(title: string | null) {
return title ?? '{no title}'; return title ?? '{no title}';
} }
export type CountdownSubscription = EntryId[] | 'all';
export const preferredFormat12 = 'h:mm a'; export const preferredFormat12 = 'h:mm a';
export const preferredFormat24 = 'HH:mm'; export const preferredFormat24 = 'HH:mm';
@@ -120,7 +122,7 @@ export function useSubscriptionDisplayData(
/** /**
* Adds a set of subscriptions to the URL parameters * Adds a set of subscriptions to the URL parameters
*/ */
export function makeSubscriptionsUrl(urlRef: string, subscriptions: EntryId[]) { export function makeSubscriptionsUrl(urlRef: string, subscriptions: CountdownSubscription) {
const url = new URL(urlRef); const url = new URL(urlRef);
const newParams = new URLSearchParams(); const newParams = new URLSearchParams();
@@ -131,10 +133,14 @@ export function makeSubscriptionsUrl(urlRef: string, subscriptions: EntryId[]) {
} }
} }
if (subscriptions === 'all') {
newParams.append('sub', 'all');
} else {
// add new subscriptions // add new subscriptions
subscriptions.forEach((id) => { subscriptions.forEach((id) => {
newParams.append('sub', id); newParams.append('sub', id);
}); });
}
url.search = newParams.toString(); url.search = newParams.toString();
@@ -146,38 +152,14 @@ export function makeSubscriptionsUrl(urlRef: string, subscriptions: EntryId[]) {
* Since the original array is already ordered, we simply filter out the events * Since the original array is already ordered, we simply filter out the events
* which are not in the subscriptions list. * which are not in the subscriptions list.
*/ */
export function getOrderedSubscriptions<T extends OntimeEntry>(subscriptions: EntryId[], playableEvents: T[]): T[] { export function getOrderedSubscriptions<T extends OntimeEntry>(
subscriptions: CountdownSubscription,
playableEvents: T[],
): T[] {
if (subscriptions === 'all') return playableEvents;
return playableEvents.filter((event) => subscriptions.includes(event.id)); return playableEvents.filter((event) => subscriptions.includes(event.id));
} }
/**
* Checks through the rundown whether the current event is linked to the loaded event
*/
export function isLinkedToLoadedEvent(events: OntimeEvent[], loadedId: EntryId | null, currentId: EntryId): boolean {
// if nothing is loaded, we return true to simplify the logic
if (!loadedId) {
return true;
}
const loadedIndex = events.findIndex((event) => event.id === loadedId);
if (loadedIndex === -1) {
return true;
}
for (let i = loadedIndex; i < events.length; i++) {
const event = events[i];
if (event.id === currentId) {
return true;
}
if (event.linkStart === null) {
return false;
}
}
return true;
}
export function isOutsideRange(a: number, b: number): boolean { export function isOutsideRange(a: number, b: number): boolean {
return Math.abs(a - b) > MILLIS_PER_MINUTE; return Math.abs(a - b) > MILLIS_PER_MINUTE;
} }
@@ -0,0 +1,51 @@
import { useVisibleRowsStore } from './visibleRowsStore';
let observer: IntersectionObserver | null = null;
function getObserver(): IntersectionObserver {
if (!observer) {
const options: IntersectionObserverInit = {
root: null,
rootMargin: '400px 0px', // prevent unmounting rows too early
threshold: 0.25,
};
const handleOnIntersect: IntersectionObserverCallback = (entries) => {
const visibleRows = useVisibleRowsStore.getState();
entries.forEach((entry) => {
const targetId = entry.target.id;
if (entry.isIntersecting) {
visibleRows.addVisibleRow(targetId);
} else {
visibleRows.removeVisibleRow(targetId);
}
});
};
observer = new IntersectionObserver(handleOnIntersect, options);
}
return observer;
}
/**
* register a row element in the observer
*/
export function observeRow(element: HTMLElement) {
getObserver().observe(element);
}
/**
* unregister a row element in the observer
*/
export function unobserveRow(element: HTMLElement) {
getObserver().unobserve(element);
}
/**
* cleanup observer, should be called when the table component unmounts
*/
export function cleanup() {
observer?.disconnect();
observer = null;
}
@@ -0,0 +1,18 @@
import { create } from 'zustand';
interface VisibleRowsStore {
visibleRows: Set<string>;
addVisibleRow: (id: string) => void;
removeVisibleRow: (id: string) => void;
}
export const useVisibleRowsStore = create<VisibleRowsStore>((set) => ({
visibleRows: new Set(),
addVisibleRow: (id) => set((state) => ({ visibleRows: new Set(state.visibleRows).add(id) })),
removeVisibleRow: (id) =>
set((state) => {
const newSet = new Set(state.visibleRows);
newSet.delete(id);
return { visibleRows: newSet };
}),
}));
@@ -105,7 +105,7 @@ $timeline-color: color-mix(in srgb, transparent 60%, var(--background-color-over
} }
.delay { .delay {
color: $ontime-delay-text; color: $delay-color;
} }
.timeOverview { .timeOverview {
@@ -117,7 +117,7 @@ $timeline-color: color-mix(in srgb, transparent 60%, var(--background-color-over
.cross { .cross {
text-decoration: line-through; text-decoration: line-through;
text-decoration-thickness: 2px; text-decoration-thickness: 2px;
text-decoration-color: $ontime-delay-text; text-decoration-color: $delay-color;
} }
.separeLeft { .separeLeft {
@@ -183,7 +183,7 @@ export function getUpcomingEvents(events: PlayableEvent[], selectedId: MaybeStri
* Utility function calculates time to start * Utility function calculates time to start
*/ */
export function getTimeToStart(now: number, start: number, delay: number, offset: number): number { export function getTimeToStart(now: number, start: number, delay: number, offset: number): number {
return start + delay - now + offset; return start + delay - now - offset;
} }
interface TimelineLayout { interface TimelineLayout {
+15 -2
View File
@@ -1,7 +1,6 @@
{ {
"extends": "../../tsconfig.common.json",
"compilerOptions": { "compilerOptions": {
"target": "esnext", "target": "ESNext",
"lib": [ "lib": [
"dom", "dom",
"dom.iterable", "dom.iterable",
@@ -14,7 +13,21 @@
], ],
"module": "esnext", "module": "esnext",
"moduleResolution": "node", "moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"skipLibCheck": true,
"allowSyntheticDefaultImports": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"noImplicitAny": true,
"noImplicitReturns": true,
"allowUnreachableCode": false,
"allowUnusedLabels": false, "allowUnusedLabels": false,
"noImplicitThis": true,
"strictNullChecks": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"removeComments": true, "removeComments": true,
"preserveConstEnums": true, "preserveConstEnums": true,
"allowJs": true, "allowJs": true,
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "ontime-electron", "name": "ontime-electron",
"version": "4.0.0-beta.4", "version": "4.0.0-beta.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
View File
@@ -1 +0,0 @@
*.tgz
-17
View File
@@ -1,17 +0,0 @@
# Ontime Resolver
Congratulations! You got this far into Ontime's rabbit hole and want to manage your installation.
The Resolver is an attempt to expose our ontime's api so it is easier to integrate with
## Links
- [Ontime's repository](https://github.com/cpvalente/ontime)
- [Ontime's documentation](https://docs.getontime.no/)
- [Ontime's website](https://getontime.no/)
## Sponsoring
You can help the development of this project or say thank you with a one time donation. \
See the [terms of donations](https://github.com/cpvalente/ontime/blob/master/SPONSOR.md)
[![](https://img.shields.io/static/v1?label=Sponsor&message=%E2%9D%A4&logo=GitHub&color=%23fe8e86)](https://github.com/sponsors/cpvalente)
[![](https://img.shields.io/static/v1?label=Buy%20me%20a%20coffee&message=%E2%9D%A4&logo=buymeacoffee&color=%23fe8e86)](https://www.buymeacoffee.com/cpvalente)
-28
View File
@@ -1,28 +0,0 @@
{
"name": "@getontime/resolver",
"version": "4.0.0-beta.4",
"type": "module",
"repository": "https://github.com/cpvalente/ontime",
"types": "./dist/main.d.ts",
"main": "./dist/main.js",
"description": "shared typings for ontime",
"scripts": {
"lint": "eslint . --quiet",
"prebuild": "pnpm rimraf ./dist",
"build": "tsup && pnpm rimraf ./dist/index.js",
"postbuild": "pnpm rimraf ./dist/index.d.ts"
},
"keywords": ["ontime", "resolver", "parser"],
"author": "",
"license": "AGPL-3.0-only",
"devDependencies": {
"@sprout2000/esbuild-copy-plugin": "^1.1.19",
"@typescript-eslint/parser": "catalog:",
"eslint": "catalog:",
"tsup": "^8.5.0",
"rimraf": "catalog:",
"typescript": "catalog:",
"ontime-types": "workspace:^4.0.0"
},
"files": ["dist"]
}
-20
View File
@@ -1,20 +0,0 @@
// api
export { MessageTag, RefetchKey } from 'ontime-types';
export type { ApiAction, ApiActionTag, ApiResponse } from 'ontime-types';
export type { WsPacketToClient, WsPacketToServer } from 'ontime-types';
// stores
export type { RuntimeStore, TimerState, MessageState, RundownState, Offset } from 'ontime-types';
export { TimerPhase, Playback, runtimeStorePlaceholder, OffsetMode } from 'ontime-types';
// aux timer
export type { SimpleTimerState } from 'ontime-types';
export { SimplePlayback, SimpleDirection } from 'ontime-types';
// entries
export type { OntimeEvent, OntimeGroup, EntryCustomFields, CustomFields, Rundown } from 'ontime-types';
export { SupportedEntry, isOntimeEvent, isOntimeGroup, isOntimeDelay, isOntimeMilestone } from 'ontime-types';
// functions
export { isWsPacketToClient } from './websocket.js';
export type { SocketSender } from './websocket.js';
-18
View File
@@ -1,18 +0,0 @@
import { ApiAction, ApiActionTag, MessageTag, WsPacketToClient, WsPacketToServer } from 'ontime-types';
/**
* A helper type for sending correct websocket messages to ontime
*/
export type SocketSender = <T extends MessageTag | ApiActionTag>(
tag: T,
payload: T extends MessageTag
? Pick<WsPacketToServer & { tag: T }, 'payload'>['payload']
: Pick<ApiAction & { tag: T }, 'payload'>['payload'],
) => void;
/**
* a soft type guard for WS packets
*/
export function isWsPacketToClient(data: unknown): data is WsPacketToClient {
return typeof data === 'object' && data !== null && 'tag' in data && 'payload' in data;
}
-22
View File
@@ -1,22 +0,0 @@
{
"extends": "../../tsconfig.common.json",
"compilerOptions": {
"target": "esnext",
"module": "preserve",
"moduleResolution": "bundler",
"allowUnusedLabels": false,
"removeComments": true,
"preserveConstEnums": true,
"allowJs": true,
"declaration": true,
"outDir": "dist",
"sourceMap": true,
"baseUrl": "src",
},
"include": [
"src",
],
"exclude": [
"node_modules",
]
}
-20
View File
@@ -1,20 +0,0 @@
import { defineConfig } from 'tsup';
import copyPlugin from '@sprout2000/esbuild-copy-plugin';
export default defineConfig({
clean: false, //we can't use clean as i dose so after the copy plugin runs
entry: ['src/main.ts'],
outDir: 'dist',
bundle: true,
dts: { resolve: true },
format: 'esm',
target: 'esnext',
platform: 'node',
esbuildPlugins: [
copyPlugin.copyPlugin({
src: './node_modules/ontime-types/dist',
dest: './dist',
}),
],
});
+3 -2
View File
@@ -2,7 +2,7 @@
"name": "ontime-server", "name": "ontime-server",
"type": "module", "type": "module",
"main": "src/index.ts", "main": "src/index.ts",
"version": "4.0.0-beta.4", "version": "4.0.0-beta.3",
"exports": "./src/index.js", "exports": "./src/index.js",
"dependencies": { "dependencies": {
"@googleapis/sheets": "^5.0.5", "@googleapis/sheets": "^5.0.5",
@@ -20,6 +20,7 @@
"ontime-utils": "workspace:*", "ontime-utils": "workspace:*",
"osc-min": "2.1.2", "osc-min": "2.1.2",
"sanitize-filename": "^1.6.3", "sanitize-filename": "^1.6.3",
"steno": "^4.0.2",
"ws": "^8.18.0", "ws": "^8.18.0",
"xlsx": "^0.18.5" "xlsx": "^0.18.5"
}, },
@@ -40,7 +41,7 @@
"prettier": "catalog:", "prettier": "catalog:",
"server-timing": "^3.3.3", "server-timing": "^3.3.3",
"shx": "^0.3.4", "shx": "^0.3.4",
"ts-essentials": "catalog:", "ts-essentials": "^10.0.3",
"tsx": "^4.19.2", "tsx": "^4.19.2",
"typescript": "catalog:", "typescript": "catalog:",
"vitest": "catalog:" "vitest": "catalog:"
@@ -1,6 +1,6 @@
import { TriggerDTO, TimerLifeCycle, AutomationDTO, Automation, ProjectRundowns } from 'ontime-types'; import { TriggerDTO, TimerLifeCycle, AutomationDTO, Automation, EntryId } from 'ontime-types';
import { makeOntimeEvent } from '../../rundown/__mocks__/rundown.mocks.js'; import { makeRundown } from '../../rundown/__mocks__/rundown.mocks.js';
import { import {
addTrigger, addTrigger,
@@ -203,29 +203,10 @@ describe('deleteAutomation()', () => {
const automations = getAutomations(); const automations = getAutomations();
expect(Object.keys(automations).length).toEqual(1); expect(Object.keys(automations).length).toEqual(1);
const projectRundowns: ProjectRundowns = { const rundown = makeRundown({});
'rundown-1': { const timedEventOrder: EntryId[] = [];
id: 'rundown-1',
title: 'Rundown 1', await deleteAutomation(rundown, timedEventOrder, Object.keys(automations)[0]);
order: ['1'],
flatOrder: ['1'],
entries: {
'1': makeOntimeEvent({
id: '1',
triggers: [
{
id: 'trigger-1',
title: 'Trigger 1',
trigger: TimerLifeCycle.onClock,
automationId: 'test-automation',
},
],
}),
},
revision: 1,
},
};
await deleteAutomation(projectRundowns, Object.keys(automations)[0]);
const removed = getAutomations(); const removed = getAutomations();
expect(Object.keys(removed).length).toEqual(0); expect(Object.keys(removed).length).toEqual(0);
}); });
@@ -1,7 +1,5 @@
import { ProjectRundowns, TimerLifeCycle } from 'ontime-types'; import { TimerLifeCycle } from 'ontime-types';
import { makeOntimeEvent, makeRundown } from '../../rundown/__mocks__/rundown.mocks.js';
import { makeOntimeEvent } from '../../rundown/__mocks__/rundown.mocks.js';
import { isAutomationUsed, parseTemplateNested, stringToOSCArgs } from '../automation.utils.js'; import { isAutomationUsed, parseTemplateNested, stringToOSCArgs } from '../automation.utils.js';
describe('parseTemplateNested()', () => { describe('parseTemplateNested()', () => {
@@ -252,12 +250,7 @@ describe('test stringToOSCArgs()', () => {
describe('isAutomationUsed()', () => { describe('isAutomationUsed()', () => {
it('returns the first event which uses an automation', () => { it('returns the first event which uses an automation', () => {
const projectRundowns: ProjectRundowns = { const rundown = makeRundown({
'rundown-1': {
id: 'rundown-1',
title: 'Rundown 1',
order: ['1'],
flatOrder: ['1'],
entries: { entries: {
'1': makeOntimeEvent({ '1': makeOntimeEvent({
id: '1', id: '1',
@@ -271,71 +264,17 @@ describe('isAutomationUsed()', () => {
], ],
}), }),
}, },
revision: 1,
},
};
const automationId = 'test-automation';
const result = isAutomationUsed(projectRundowns, automationId);
expect(result).toStrictEqual(['Rundown 1', '1']);
}); });
it('finds usages in any rundown', () => { const timedEventOrder = ['1'];
const projectRundowns: ProjectRundowns = { const automationId = 'test-automation';
'rundown-1': {
id: 'rundown-1',
title: 'Rundown 1',
order: ['1'],
flatOrder: ['1'],
entries: {
'1': makeOntimeEvent({
id: '1',
triggers: [
{
id: 'trigger-1',
title: 'Trigger 1',
trigger: TimerLifeCycle.onClock,
automationId: 'test-automation',
},
],
}),
},
revision: 1,
},
'rundown-2': {
id: 'rundown-2',
title: 'Rundown 2',
order: ['1'],
flatOrder: ['1'],
entries: {
'1': makeOntimeEvent({
id: '1',
triggers: [
{
id: 'trigger-1',
title: 'Trigger 1',
trigger: TimerLifeCycle.onClock,
automationId: 'in-the-second-rundown',
},
],
}),
},
revision: 1,
},
};
const automationId = 'in-the-second-rundown';
const result = isAutomationUsed(projectRundowns, automationId); const result = isAutomationUsed(rundown, timedEventOrder, automationId);
expect(result).toStrictEqual(['Rundown 2', '1']); expect(result).toBe('1');
}); });
it('returns returns undefined if there are no matches', () => { it('returns returns undefined if there are no matches', () => {
const projectRundowns: ProjectRundowns = { const rundown = makeRundown({
'rundown-1': {
id: 'rundown-1',
title: 'Rundown 1',
order: ['1'],
flatOrder: ['1'],
entries: { entries: {
'1': makeOntimeEvent({ '1': makeOntimeEvent({
id: '1', id: '1',
@@ -349,12 +288,12 @@ describe('isAutomationUsed()', () => {
], ],
}), }),
}, },
revision: 1, });
},
}; const timedEventOrder = ['1'];
const automationId = 'does-not-exist'; const automationId = 'does-not-exist';
const result = isAutomationUsed(projectRundowns, automationId); const result = isAutomationUsed(rundown, timedEventOrder, automationId);
expect(result).toBeUndefined(); expect(result).toBeUndefined();
}); });
}); });
@@ -4,7 +4,8 @@ import { Automation, AutomationSettings, ErrorResponse, Trigger } from 'ontime-t
import type { Request, Response } from 'express'; import type { Request, Response } from 'express';
import { oscServer } from '../../adapters/OscAdapter.js'; import { oscServer } from '../../adapters/OscAdapter.js';
import { getDataProvider } from '../../classes/data-provider/DataProvider.js';
import { getCurrentRundown, getRundownMetadata } from '../rundown/rundown.dao.js';
import * as automationDao from './automation.dao.js'; import * as automationDao from './automation.dao.js';
import * as automationService from './automation.service.js'; import * as automationService from './automation.service.js';
@@ -107,8 +108,10 @@ export async function editAutomation(req: Request, res: Response<Automation | Er
export async function deleteAutomation(req: Request, res: Response<void | ErrorResponse>) { export async function deleteAutomation(req: Request, res: Response<void | ErrorResponse>) {
try { try {
const projectRundowns = getDataProvider().getProjectRundowns(); const rundown = getCurrentRundown();
await automationDao.deleteAutomation(projectRundowns, req.params.id); const { timedEventOrder } = getRundownMetadata();
await automationDao.deleteAutomation(rundown, timedEventOrder, req.params.id);
res.status(204).send(); res.status(204).send();
} catch (error) { } catch (error) {
const message = getErrorMessage(error); const message = getErrorMessage(error);
@@ -2,8 +2,9 @@ import type {
Automation, Automation,
AutomationDTO, AutomationDTO,
AutomationSettings, AutomationSettings,
EntryId,
NormalisedAutomation, NormalisedAutomation,
ProjectRundowns, Rundown,
Trigger, Trigger,
TriggerDTO, TriggerDTO,
} from 'ontime-types'; } from 'ontime-types';
@@ -135,15 +136,15 @@ export async function editAutomation(id: string, newAutomation: AutomationDTO):
/** /**
* Deletes a automation given its ID * Deletes a automation given its ID
*/ */
export async function deleteAutomation(projectRundowns: ProjectRundowns, automationId: string): Promise<void> { export async function deleteAutomation(rundown: Rundown, timedEventOrder: EntryId[], id: string): Promise<void> {
const automations = getAutomations(); const automations = getAutomations();
// ignore request if automation does not exist // ignore request if automation does not exist
if (!Object.hasOwn(automations, automationId)) { if (!Object.hasOwn(automations, id)) {
return; return;
} }
// prevent deleting a automation that is in use in triggers // prevent deleting a automation that is in use in triggers
const triggers = getAutomationTriggers().filter((trigger) => trigger.automationId === automationId); const triggers = getAutomationTriggers().filter((trigger) => trigger.automationId === id);
if (triggers.length) { if (triggers.length) {
throw new Error( throw new Error(
`Unable to delete automation used in trigger ${triggers[0].title}${triggers.length > 1 ? ` and ${triggers.length - 1} more` : ''}`, `Unable to delete automation used in trigger ${triggers[0].title}${triggers.length > 1 ? ` and ${triggers.length - 1} more` : ''}`,
@@ -151,12 +152,12 @@ export async function deleteAutomation(projectRundowns: ProjectRundowns, automat
} }
// prevent deleting a automation that is in use in events // prevent deleting a automation that is in use in events
const isInUse = isAutomationUsed(projectRundowns, automationId); const isInUse = isAutomationUsed(rundown, timedEventOrder, id);
if (isInUse) { if (isInUse) {
throw new Error(`Unable to delete automation used in rundown: ${isInUse[0]}, in event with ID: ${isInUse[1]}`); throw new Error(`Unable to delete automation used in event with ID ${isInUse}`);
} }
delete automations[automationId]; delete automations[id];
await saveChanges({ automations }); await saveChanges({ automations });
} }
@@ -1,13 +1,4 @@
import { import { EntryId, FilterRule, isOntimeEvent, MaybeNumber, OntimeAction, ontimeActionKeyValues, Rundown } from 'ontime-types';
EntryId,
FilterRule,
isOntimeEvent,
MaybeNumber,
OntimeAction,
ontimeActionKeyValues,
ProjectRundowns,
RundownEntries,
} from 'ontime-types';
import { millisToString, removeLeadingZero, splitWhitespace, getPropertyFromPath } from 'ontime-utils'; import { millisToString, removeLeadingZero, splitWhitespace, getPropertyFromPath } from 'ontime-utils';
import type { OscArgOrArrayInput, OscArgInput } from 'osc-min'; import type { OscArgOrArrayInput, OscArgInput } from 'osc-min';
@@ -207,40 +198,22 @@ export function isBooleanEquals(a: boolean, b: string): boolean {
/** /**
* Checks is an automation is used in a rundown * Checks is an automation is used in a rundown
*/ * TODO(v4): this currently only checks the current rundown, we will need to check all rundowns in the future
function isAutomationUsedInRundown(
entries: RundownEntries,
flatOrder: EntryId[],
automationId: string,
): EntryId | undefined {
for (let i = 0; i < flatOrder.length; i++) {
const eventId = flatOrder[i];
const entry = entries[eventId];
// only ontime events can contain triggers
if (isOntimeEvent(entry) && entry.triggers) {
for (const trigger of entry.triggers) {
if (trigger.automationId === automationId) {
return entry.id;
}
}
}
}
}
/**
* Checks if an automation is used in any of the project rundowns
*/ */
export function isAutomationUsed( export function isAutomationUsed(
projectRundowns: ProjectRundowns, rundown: Rundown,
timedEventOrder: EntryId[],
automationId: string, automationId: string,
): [string, EntryId] | undefined { ): EntryId | undefined {
for (const rundownId in projectRundowns) { for (let i = 0; i < timedEventOrder.length; i++) {
const rundown = projectRundowns[rundownId]; const eventId = timedEventOrder[i];
const usedInEvent = isAutomationUsedInRundown(rundown.entries, rundown.flatOrder, automationId); const event = rundown.entries[eventId];
if (isOntimeEvent(event) && event.triggers) {
if (usedInEvent) { for (const trigger of event.triggers) {
return [rundown.title, usedInEvent]; if (trigger.automationId === automationId) {
return eventId;
}
}
} }
} }
} }
@@ -31,6 +31,7 @@ export async function patchPartialProjectFile(req: Request, res: Response<Databa
}; };
const newData = await projectService.patchCurrentProject(patchDb); const newData = await projectService.patchCurrentProject(patchDb);
res.status(200).send(newData); res.status(200).send(newData);
} catch (error) { } catch (error) {
const message = getErrorMessage(error); const message = getErrorMessage(error);
@@ -1,5 +1,5 @@
import { import {
ApiActionTag, ApiAction,
MessageState, MessageState,
OffsetMode, OffsetMode,
OntimeEvent, OntimeEvent,
@@ -30,7 +30,7 @@ let lastRequest: Date | null = null;
export function dispatchFromAdapter(tag: string, payload: unknown, _source?: 'osc' | 'ws' | 'http') { export function dispatchFromAdapter(tag: string, payload: unknown, _source?: 'osc' | 'ws' | 'http') {
const action = tag.toLowerCase(); const action = tag.toLowerCase();
const handler = actionHandlers[action as ApiActionTag]; const handler = actionHandlers[action as ApiAction];
lastRequest = new Date(); lastRequest = new Date();
if (handler) { if (handler) {
@@ -46,7 +46,7 @@ export function getLastRequest() {
type ActionHandler = (payload: unknown) => { payload: unknown }; type ActionHandler = (payload: unknown) => { payload: unknown };
const actionHandlers: Record<ApiActionTag, ActionHandler> = { const actionHandlers: Record<ApiAction, ActionHandler> = {
/* General */ /* General */
version: () => ({ payload: ONTIME_VERSION }), version: () => ({ payload: ONTIME_VERSION }),
poll: () => ({ poll: () => ({
@@ -1,8 +1,7 @@
import { dayInMs, MILLIS_PER_HOUR, millisToString } from 'ontime-utils'; import { dayInMs, millisToString } from 'ontime-utils';
import { EndAction, Playback, TimeStrategy, TimerPhase, TimerType } from 'ontime-types'; import { EndAction, Playback, TimeStrategy, TimerPhase, TimerType } from 'ontime-types';
import { import {
findDayOffset,
getCurrent, getCurrent,
getExpectedFinish, getExpectedFinish,
getRuntimeOffset, getRuntimeOffset,
@@ -727,7 +726,6 @@ describe('getRuntimeOffset()', () => {
eventNow: { eventNow: {
id: '1', id: '1',
timeStart: 100, timeStart: 100,
dayOffset: 0,
}, },
timer: { timer: {
startedAt: 150, startedAt: 150,
@@ -740,10 +738,7 @@ describe('getRuntimeOffset()', () => {
rundown: { rundown: {
actualStart: 150, actualStart: 150,
plannedStart: 100, plannedStart: 100,
currentDay: 0,
}, },
clock: 150,
_startDayOffset: 0,
} as RuntimeState; } as RuntimeState;
const { absolute } = getRuntimeOffset(state); const { absolute } = getRuntimeOffset(state);
@@ -755,7 +750,6 @@ describe('getRuntimeOffset()', () => {
eventNow: { eventNow: {
id: '1', id: '1',
timeStart: 100, timeStart: 100,
dayOffset: 0,
}, },
timer: { timer: {
startedAt: 150, // we started 50ms delayed startedAt: 150, // we started 50ms delayed
@@ -768,9 +762,7 @@ describe('getRuntimeOffset()', () => {
rundown: { rundown: {
actualStart: 150, actualStart: 150,
plannedStart: 100, plannedStart: 100,
currentDay: 0,
}, },
_startDayOffset: 0,
} as RuntimeState; } as RuntimeState;
const { absolute } = getRuntimeOffset(state); const { absolute } = getRuntimeOffset(state);
@@ -783,7 +775,6 @@ describe('getRuntimeOffset()', () => {
id: '1', id: '1',
timeStart: 100, timeStart: 100,
timeEnd: 140, timeEnd: 140,
dayOffset: 0,
}, },
timer: { timer: {
startedAt: 100, // we started ontime startedAt: 100, // we started ontime
@@ -796,9 +787,7 @@ describe('getRuntimeOffset()', () => {
rundown: { rundown: {
actualStart: 100, actualStart: 100,
plannedStart: 100, plannedStart: 100,
currentDay: 0,
}, },
_startDayOffset: 0,
} as RuntimeState; } as RuntimeState;
const { absolute } = getRuntimeOffset(state); const { absolute } = getRuntimeOffset(state);
@@ -811,7 +800,6 @@ describe('getRuntimeOffset()', () => {
id: '1', id: '1',
timeStart: 100, timeStart: 100,
timeEnd: 150, timeEnd: 150,
dayOffset: 0,
}, },
clock: 150, clock: 150,
timer: { timer: {
@@ -825,9 +813,7 @@ describe('getRuntimeOffset()', () => {
rundown: { rundown: {
actualStart: 100, actualStart: 100,
plannedStart: 100, plannedStart: 100,
currentDay: 0,
}, },
_startDayOffset: 0,
} as RuntimeState; } as RuntimeState;
const { absolute } = getRuntimeOffset(state); const { absolute } = getRuntimeOffset(state);
@@ -844,7 +830,6 @@ describe('getRuntimeOffset()', () => {
duration: 3600000, duration: 3600000,
timeStrategy: 'lock-duration', timeStrategy: 'lock-duration',
linkStart: false, linkStart: false,
dayOffset: 0,
}, },
rundown: { rundown: {
selectedEventIndex: 0, selectedEventIndex: 0,
@@ -852,7 +837,6 @@ describe('getRuntimeOffset()', () => {
plannedStart: 77400000, plannedStart: 77400000,
plannedEnd: 84600000, plannedEnd: 84600000,
actualStart: null, actualStart: null,
currentDay: 0,
}, },
offset: { offset: {
absolute: -77400000, absolute: -77400000,
@@ -868,7 +852,6 @@ describe('getRuntimeOffset()', () => {
secondaryTimer: null, secondaryTimer: null,
startedAt: null, startedAt: null,
}, },
_startDayOffset: 0,
_timer: { pausedAt: null }, _timer: { pausedAt: null },
} as RuntimeState; } as RuntimeState;
@@ -891,7 +874,6 @@ describe('getRuntimeOffset()', () => {
endAction: EndAction.None, endAction: EndAction.None,
timerType: TimerType.CountDown, timerType: TimerType.CountDown,
countToEnd: true, countToEnd: true,
dayOffset: 0,
skip: false, skip: false,
note: '', note: '',
colour: '', colour: '',
@@ -908,7 +890,6 @@ describe('getRuntimeOffset()', () => {
plannedStart: 77400000, // 21:30:00 plannedStart: 77400000, // 21:30:00
plannedEnd: 81000000, // 22:30:00 plannedEnd: 81000000, // 22:30:00
actualStart: 78000000, // 21:40:00 actualStart: 78000000, // 21:40:00
currentDay: 0,
}, },
offset: { offset: {
absolute: 0, absolute: 0,
@@ -924,7 +905,6 @@ describe('getRuntimeOffset()', () => {
secondaryTimer: null, secondaryTimer: null,
startedAt: 78000000, startedAt: 78000000,
}, },
_startDayOffset: 0,
_timer: { pausedAt: null }, _timer: { pausedAt: null },
} as RuntimeState; } as RuntimeState;
@@ -942,7 +922,6 @@ describe('getRuntimeOffset()', () => {
timeStart: 77400000, // 21:30:00 timeStart: 77400000, // 21:30:00
timeEnd: 81000000, // 22:30:00 timeEnd: 81000000, // 22:30:00
duration: 3600000, // 01:00:00 duration: 3600000, // 01:00:00
dayOffset: 0,
timeStrategy: TimeStrategy.LockEnd, timeStrategy: TimeStrategy.LockEnd,
linkStart: false, linkStart: false,
endAction: EndAction.None, endAction: EndAction.None,
@@ -964,7 +943,6 @@ describe('getRuntimeOffset()', () => {
plannedStart: 77400000, // 21:30:00 plannedStart: 77400000, // 21:30:00
plannedEnd: 81000000, // 22:30:00 plannedEnd: 81000000, // 22:30:00
actualStart: 78000000, // 21:40:00 actualStart: 78000000, // 21:40:00
currentDay: 0,
}, },
offset: { offset: {
absolute: 0, absolute: 0,
@@ -980,7 +958,6 @@ describe('getRuntimeOffset()', () => {
secondaryTimer: null, secondaryTimer: null,
startedAt: 78000000, startedAt: 78000000,
}, },
_startDayOffset: 0,
_timer: { pausedAt: null }, _timer: { pausedAt: null },
} as RuntimeState; } as RuntimeState;
@@ -1001,7 +978,6 @@ describe('getRuntimeOffset()', () => {
endAction: EndAction.None, endAction: EndAction.None,
timerType: TimerType.CountDown, timerType: TimerType.CountDown,
countToEnd: true, countToEnd: true,
dayOffset: 0,
}, },
rundown: { rundown: {
selectedEventIndex: 0, selectedEventIndex: 0,
@@ -1009,7 +985,6 @@ describe('getRuntimeOffset()', () => {
plannedStart: 77400000, // 21:30:00 plannedStart: 77400000, // 21:30:00
plannedEnd: 81000000, // 22:30:00 plannedEnd: 81000000, // 22:30:00
actualStart: 82000000, // 22:46:40 <--- started now actualStart: 82000000, // 22:46:40 <--- started now
currentDay: 0,
}, },
offset: { offset: {
absolute: 0, absolute: 0,
@@ -1025,7 +1000,6 @@ describe('getRuntimeOffset()', () => {
secondaryTimer: null, secondaryTimer: null,
startedAt: 82000000, // <--- started now startedAt: 82000000, // <--- started now
}, },
_startDayOffset: 0,
_timer: { pausedAt: null }, _timer: { pausedAt: null },
} as RuntimeState; } as RuntimeState;
@@ -1043,7 +1017,6 @@ describe('getRuntimeOffset() relative', () => {
eventNow: { eventNow: {
id: '1', id: '1',
timeStart: 150, timeStart: 150,
dayOffset: 0,
}, },
timer: { timer: {
startedAt: 150, startedAt: 150,
@@ -1056,9 +1029,7 @@ describe('getRuntimeOffset() relative', () => {
rundown: { rundown: {
actualStart: 150, actualStart: 150,
plannedStart: 150, plannedStart: 150,
currentDay: 0,
}, },
_startDayOffset: 0,
} as RuntimeState; } as RuntimeState;
const { absolute, relative } = getRuntimeOffset(state); const { absolute, relative } = getRuntimeOffset(state);
@@ -1070,7 +1041,6 @@ describe('getRuntimeOffset() relative', () => {
eventNow: { eventNow: {
id: '1', id: '1',
timeStart: 100, timeStart: 100,
dayOffset: 0,
}, },
timer: { timer: {
startedAt: 150, startedAt: 150,
@@ -1083,9 +1053,7 @@ describe('getRuntimeOffset() relative', () => {
rundown: { rundown: {
actualStart: 150, actualStart: 150,
plannedStart: 100, plannedStart: 100,
currentDay: 0,
}, },
_startDayOffset: 0,
} as RuntimeState; } as RuntimeState;
const { absolute, relative } = getRuntimeOffset(state); const { absolute, relative } = getRuntimeOffset(state);
@@ -1097,7 +1065,6 @@ describe('getRuntimeOffset() relative', () => {
eventNow: { eventNow: {
id: '1', id: '1',
timeStart: 150, timeStart: 150,
dayOffset: 0,
}, },
timer: { timer: {
startedAt: 100, startedAt: 100,
@@ -1110,9 +1077,7 @@ describe('getRuntimeOffset() relative', () => {
rundown: { rundown: {
actualStart: 100, actualStart: 100,
plannedStart: 150, plannedStart: 150,
currentDay: 0,
}, },
_startDayOffset: 0,
} as RuntimeState; } as RuntimeState;
const { absolute, relative } = getRuntimeOffset(state); const { absolute, relative } = getRuntimeOffset(state);
@@ -1293,21 +1258,3 @@ describe('getTimerPhase()', () => {
expect(phase).toBe(TimerPhase.Pending); expect(phase).toBe(TimerPhase.Pending);
}); });
}); });
describe('findDay()', () => {
test('finds dayOffset', () => {
//both have 1 hour offset but the clock are on different days
expect(findDayOffset(0, 23 * MILLIS_PER_HOUR)).toBe(-1); // -> 23
expect(findDayOffset(0, 13 * MILLIS_PER_HOUR)).toBe(-1); // -> 13
expect(findDayOffset(0, 12 * MILLIS_PER_HOUR)).toBe(-1); // -> 12
expect(findDayOffset(0, 11 * MILLIS_PER_HOUR)).toBe(0); // -> 11
expect(findDayOffset(1 * MILLIS_PER_HOUR, 0)).toBe(0); // -> -1
//both have 1 hour offset but the clock are on different days
expect(findDayOffset(23 * MILLIS_PER_HOUR, 0)).toBe(1); // -> -23
expect(findDayOffset(13 * MILLIS_PER_HOUR, 0)).toBe(1); // -> -13
expect(findDayOffset(12 * MILLIS_PER_HOUR, 0)).toBe(0); // -> -12
expect(findDayOffset(11 * MILLIS_PER_HOUR, 0)).toBe(0); // -> -11
expect(findDayOffset(22 * MILLIS_PER_HOUR, 23 * MILLIS_PER_HOUR)).toBe(0); // -> 1
});
});
@@ -323,14 +323,11 @@ export async function patchCurrentProject(data: Partial<DatabaseModel>) {
/** /**
* The user may have multiple rundowns * The user may have multiple rundowns
* so attempt to get the one that was used last * We currently ignore all other rundowns
* otherwise just pick the first
*/ */
const last = await getLastLoaded(); const firstRundown = getFirstRundown(result);
const rundownToLoad =
last?.rundownId && last.rundownId in result ? result[last.rundownId] : getFirstRundown(result);
await initRundown(rundownToLoad, customFields, true); await initRundown(firstRundown, customFields);
} }
const updatedData = await getDataProvider().getData(); const updatedData = await getDataProvider().getData();
@@ -12,7 +12,6 @@ describe('isRestorePoint()', () => {
addedTime: 2, addedTime: 2,
pausedAt: 3, pausedAt: 3,
firstStart: 1, firstStart: 1,
startEpoch: 1,
}; };
expect(isRestorePoint(restorePoint)).toBe(true); expect(isRestorePoint(restorePoint)).toBe(true);
@@ -23,7 +22,6 @@ describe('isRestorePoint()', () => {
addedTime: 0, addedTime: 0,
pausedAt: null, pausedAt: null,
firstStart: 1, firstStart: 1,
startEpoch: 1,
}; };
expect(isRestorePoint(restorePoint)).toBe(true); expect(isRestorePoint(restorePoint)).toBe(true);
}); });
@@ -37,7 +35,6 @@ describe('isRestorePoint()', () => {
addedTime: 0, addedTime: 0,
pausedAt: null, pausedAt: null,
groupStartAt: 10, groupStartAt: 10,
startEpoch: 1,
}; };
expect(isRestorePoint(restorePoint)).toBe(false); expect(isRestorePoint(restorePoint)).toBe(false);
}); });
@@ -59,7 +56,6 @@ describe('isRestorePoint()', () => {
addedTime: 0, addedTime: 0,
pausedAt: null, pausedAt: null,
groupStartAt: 10, groupStartAt: 10,
startEpoch: 1,
}; };
expect(isRestorePoint(restorePoint)).toBe(false); expect(isRestorePoint(restorePoint)).toBe(false);
}); });
@@ -16,7 +16,6 @@ describe('restoreService', () => {
addedTime: 5678, addedTime: 5678,
pausedAt: 9087, pausedAt: 9087,
firstStart: 1234, firstStart: 1234,
startEpoch: 1234,
}; };
const mockRead = vi.fn().mockResolvedValue(expected); const mockRead = vi.fn().mockResolvedValue(expected);
@@ -34,7 +33,6 @@ describe('restoreService', () => {
addedTime: 0, addedTime: 0,
pausedAt: null, pausedAt: null,
firstStart: 1234, firstStart: 1234,
startEpoch: 1234,
}; };
const mockRead = vi.fn().mockResolvedValue(expected); const mockRead = vi.fn().mockResolvedValue(expected);
@@ -81,7 +79,6 @@ describe('restoreService', () => {
addedTime: 1234, addedTime: 1234,
pausedAt: 1234, pausedAt: 1234,
firstStart: 1234, firstStart: 1234,
startEpoch: 1234,
}; };
const mockWrite = vi.fn().mockResolvedValue(undefined); const mockWrite = vi.fn().mockResolvedValue(undefined);
@@ -98,7 +95,6 @@ describe('restoreService', () => {
addedTime: 5678, addedTime: 5678,
pausedAt: 5678, pausedAt: 5678,
firstStart: 5678, firstStart: 5678,
startEpoch: 5678,
}; };
const mockWrite = vi.fn().mockRejectedValue(new Error('Write failed')); const mockWrite = vi.fn().mockRejectedValue(new Error('Write failed'));
@@ -20,7 +20,6 @@ export function isRestorePoint(restorePoint: unknown): restorePoint is RestorePo
'addedTime', 'addedTime',
'pausedAt', 'pausedAt',
'firstStart', 'firstStart',
'startEpoch',
]) ])
) { ) {
return false; return false;
@@ -50,9 +49,5 @@ export function isRestorePoint(restorePoint: unknown): restorePoint is RestorePo
return false; return false;
} }
if (!is.number(restorePoint.startEpoch) && restorePoint.startEpoch !== null) {
return false;
}
return true; return true;
} }
@@ -7,5 +7,4 @@ export type RestorePoint = {
addedTime: number; addedTime: number;
pausedAt: MaybeNumber; pausedAt: MaybeNumber;
firstStart: MaybeNumber; firstStart: MaybeNumber;
startEpoch: MaybeNumber;
}; };
@@ -743,7 +743,6 @@ function broadcastResult(_target: any, _propertyKey: string, descriptor: Propert
addedTime: state.timer.addedTime, addedTime: state.timer.addedTime,
pausedAt: state._timer.pausedAt, pausedAt: state._timer.pausedAt,
firstStart: state.rundown.actualStart, firstStart: state.rundown.actualStart,
startEpoch: state._startEpoch,
}) })
.catch((_e) => { .catch((_e) => {
//we don't do anything with the error here //we don't do anything with the error here
@@ -412,7 +412,6 @@ export async function upload(sheetId: string, options: ImportMap) {
}, },
}); });
try {
// update the corresponding row with event data // update the corresponding row with event data
sheetOrder.forEach((entryId, index) => { sheetOrder.forEach((entryId, index) => {
const isGroupEnd = entryId.startsWith('group-end-'); const isGroupEnd = entryId.startsWith('group-end-');
@@ -422,9 +421,6 @@ export async function upload(sheetId: string, options: ImportMap) {
: structuredClone(rundown.entries[id]); : structuredClone(rundown.entries[id]);
updateRundown.push(cellRequestFromEvent(entry, index, worksheetId, sheetMetadata)); updateRundown.push(cellRequestFromEvent(entry, index, worksheetId, sheetMetadata));
}); });
} catch (e) {
throw new Error(`Sheet write failed to correctly parse rundown: ${e}`)
}
const writeResponse = await sheets({ version: 'v4', auth: currentAuthClient }).spreadsheets.batchUpdate({ const writeResponse = await sheets({ version: 'v4', auth: currentAuthClient }).spreadsheets.batchUpdate({
spreadsheetId: sheetId, spreadsheetId: sheetId,
@@ -154,6 +154,12 @@ function getCellData(key: OntimeEntryCommonKeys | 'blank', entry: OntimeEntry) {
return {}; return {};
} }
// we need to flatten the milestones
if (key.startsWith('custom')) {
const customKey = key.split(':')[1];
return { userEnteredValue: { stringValue: entry.custom[customKey] } };
}
// we need to remap the event type to timer type in the case of groups and milestones // we need to remap the event type to timer type in the case of groups and milestones
if (key === 'timerType') { if (key === 'timerType') {
if (isOntimeGroup(entry)) if (isOntimeGroup(entry))
@@ -162,17 +168,6 @@ function getCellData(key: OntimeEntryCommonKeys | 'blank', entry: OntimeEntry) {
return { userEnteredValue: { stringValue: entry.timerType } }; return { userEnteredValue: { stringValue: entry.timerType } };
} }
// all other data is not relevant for the group end entry
if (entry.id.startsWith('group-end')) {
return {};
}
// we need to flatten the milestones
if (key.startsWith('custom')) {
const customKey = key.split(':')[1];
return { userEnteredValue: { stringValue: entry.custom[customKey] } };
}
// typescript cannot guarantee that the key exists for every entry // typescript cannot guarantee that the key exists for every entry
// so we check for the key existence and assert the type // so we check for the key existence and assert the type
if (!(key in entry)) return {}; if (!(key in entry)) return {};
+8 -21
View File
@@ -1,6 +1,5 @@
import { MaybeNumber, TimerPhase } from 'ontime-types'; import { MaybeNumber, TimerPhase } from 'ontime-types';
import { dayInMs, isPlaybackActive, MILLIS_PER_HOUR } from 'ontime-utils'; import { dayInMs, isPlaybackActive } from 'ontime-utils';
import type { RuntimeState } from '../stores/runtimeState.js'; import type { RuntimeState } from '../stores/runtimeState.js';
/** /**
@@ -114,14 +113,14 @@ export function skippedOutOfEvent(state: RuntimeState, previousTime: number, ski
* Negative offset is under time / ahead of schedule * Negative offset is under time / ahead of schedule
*/ */
export function getRuntimeOffset(state: RuntimeState): { absolute: number; relative: number } { export function getRuntimeOffset(state: RuntimeState): { absolute: number; relative: number } {
const { eventNow, clock, _startDayOffset } = state; const { eventNow, clock } = state;
const { addedTime, current, startedAt } = state.timer; const { addedTime, current, startedAt } = state.timer;
// nothing to calculate if there are no loaded events or if we havent started // nothing to calculate if there are no loaded events or if we havent started
if (eventNow === null || startedAt === null || _startDayOffset === null) { if (eventNow === null || startedAt === null) {
return { absolute: 0, relative: 0 }; return { absolute: 0, relative: 0 };
} }
const { countToEnd, timeStart, dayOffset } = eventNow; const { countToEnd, timeStart } = eventNow;
const { plannedStart, actualStart } = state.rundown; const { plannedStart, actualStart } = state.rundown;
// eslint-disable-next-line no-unused-labels -- dev code path // eslint-disable-next-line no-unused-labels -- dev code path
@@ -132,8 +131,8 @@ export function getRuntimeOffset(state: RuntimeState): { absolute: number; relat
if (actualStart === null) throw new Error('timerUtils.getRuntimeOffset: state.rundown.plannedStart must be set'); if (actualStart === null) throw new Error('timerUtils.getRuntimeOffset: state.rundown.plannedStart must be set');
} }
// difference between planned event start and actual event start (will be positive if we started behind) // difference between planned event start and actual event start (will be positive if we stared behind )
const eventStartOffset = startedAt + _startDayOffset * dayInMs - (timeStart + dayOffset * dayInMs); const eventStartOffset = startedAt - timeStart;
// how long has the event been running over (is a negative number when in over timer so inverted before adding to offset) // how long has the event been running over (is a negative number when in over timer so inverted before adding to offset)
const overtime = Math.abs(Math.min(current, 0)); const overtime = Math.abs(Math.min(current, 0));
@@ -141,11 +140,10 @@ export function getRuntimeOffset(state: RuntimeState): { absolute: number; relat
// time the playback was paused, the different from now to when we paused is added to the offset TODO: brakes when crossing midnight // time the playback was paused, the different from now to when we paused is added to the offset TODO: brakes when crossing midnight
const pausedTime = state._timer.pausedAt === null ? 0 : clock - state._timer.pausedAt; const pausedTime = state._timer.pausedAt === null ? 0 : clock - state._timer.pausedAt;
// absolute offset is difference between schedule and playback time
const absolute = eventStartOffset + overtime + pausedTime + addedTime; const absolute = eventStartOffset + overtime + pausedTime + addedTime;
// the relative offset is the same as the absolute but adjusted relative to the actual start time // the relative offset i the same as the absolute offset but adjusted relative to the actual start time
const relative = absolute + plannedStart - actualStart - _startDayOffset * dayInMs; const relative = absolute + plannedStart - actualStart;
// in case of count to end, the absolute offset is just the overtime // in case of count to end, the absolute offset is just the overtime
return countToEnd ? { absolute: overtime, relative } : { absolute, relative }; return countToEnd ? { absolute: overtime, relative } : { absolute, relative };
@@ -182,14 +180,3 @@ export function getTimerPhase(state: RuntimeState): TimerPhase {
return TimerPhase.Default; return TimerPhase.Default;
} }
/**
* Finds the day offset relative to an event start
* used byt the runtimeState on first start to get correct offsets
*/
export function findDayOffset(plannedStart: number, clock: number): number {
const distance = clock - plannedStart;
if (distance >= 12 * MILLIS_PER_HOUR) return -1;
if (distance < -12 * MILLIS_PER_HOUR) return 1;
return 0;
}
@@ -14,8 +14,6 @@ const baseState: RuntimeState = {
plannedStart: 0, plannedStart: 0,
plannedEnd: 0, plannedEnd: 0,
actualStart: null, actualStart: null,
actualGroupStart: null,
currentDay: 0,
}, },
offset: { offset: {
absolute: 0, absolute: 0,
@@ -48,8 +46,6 @@ const baseState: RuntimeState = {
_group: null, _group: null,
_end: null, _end: null,
_flag: null, _flag: null,
_startDayOffset: null,
_startEpoch: null,
}; };
export function makeRuntimeStateData(patch?: Partial<RuntimeState>): RuntimeState { export function makeRuntimeStateData(patch?: Partial<RuntimeState>): RuntimeState {
@@ -393,7 +393,6 @@ describe('loadGroupFlagAndEnd()', () => {
const state = { const state = {
groupNow: null, groupNow: null,
eventNow: rundown.entries[11], eventNow: rundown.entries[11],
rundown: { actualGroupStart: null },
} as RuntimeState; } as RuntimeState;
const metadata = { playableEventOrder: ['0', '11', '3'], flags: ['1'] } as RundownMetadata; const metadata = { playableEventOrder: ['0', '11', '3'], flags: ['1'] } as RundownMetadata;
@@ -421,7 +420,6 @@ describe('loadGroupFlagAndEnd()', () => {
const state = { const state = {
groupNow: rundown.entries[1], groupNow: rundown.entries[1],
eventNow: rundown.entries[22], eventNow: rundown.entries[22],
rundown: { actualGroupStart: null },
} as RuntimeState; } as RuntimeState;
const metadata = { playableEventOrder: ['0', '11', '22'], flags: ['1'] } as RundownMetadata; const metadata = { playableEventOrder: ['0', '11', '22'], flags: ['1'] } as RundownMetadata;
@@ -449,7 +447,6 @@ describe('loadGroupFlagAndEnd()', () => {
const state = { const state = {
groupNow: rundown.entries[1], groupNow: rundown.entries[1],
eventNow: rundown.entries[0], eventNow: rundown.entries[0],
rundown: { actualGroupStart: null },
} as RuntimeState; } as RuntimeState;
const metadata = { playableEventOrder: ['0', '11', '22'], flags: ['1'] } as RundownMetadata; const metadata = { playableEventOrder: ['0', '11', '22'], flags: ['1'] } as RundownMetadata;
@@ -474,7 +471,6 @@ describe('loadGroupFlagAndEnd()', () => {
const state = { const state = {
groupNow: null, groupNow: null,
eventNow: rundown.entries[0], eventNow: rundown.entries[0],
rundown: { actualGroupStart: null },
} as RuntimeState; } as RuntimeState;
const metadata = { playableEventOrder: ['0', '1'], flags: ['1'] } as RundownMetadata; const metadata = { playableEventOrder: ['0', '1'], flags: ['1'] } as RundownMetadata;
+23 -93
View File
@@ -23,15 +23,9 @@ import {
isPlaybackActive, isPlaybackActive,
} from 'ontime-utils'; } from 'ontime-utils';
import { getTimeObject, timeNow } from '../utils/time.js'; import { timeNow } from '../utils/time.js';
import type { RestorePoint } from '../services/restore-service/restore.type.js'; import type { RestorePoint } from '../services/restore-service/restore.type.js';
import { import { getCurrent, getExpectedFinish, getRuntimeOffset, getTimerPhase } from '../services/timerUtils.js';
findDayOffset,
getCurrent,
getExpectedFinish,
getRuntimeOffset,
getTimerPhase,
} from '../services/timerUtils.js';
import { loadRoll, normaliseRollStart } from '../services/rollUtils.js'; import { loadRoll, normaliseRollStart } from '../services/rollUtils.js';
import { timerConfig } from '../setup/config.js'; import { timerConfig } from '../setup/config.js';
import { RundownMetadata } from '../api-data/rundown/rundown.types.js'; import { RundownMetadata } from '../api-data/rundown/rundown.types.js';
@@ -61,8 +55,6 @@ export type RuntimeState = {
_group: ExpectedMetadata; _group: ExpectedMetadata;
_flag: ExpectedMetadata; _flag: ExpectedMetadata;
_end: ExpectedMetadata; _end: ExpectedMetadata;
_startEpoch: MaybeNumber;
_startDayOffset: MaybeNumber;
}; };
const runtimeState: RuntimeState = { const runtimeState: RuntimeState = {
@@ -86,8 +78,6 @@ const runtimeState: RuntimeState = {
_group: null, _group: null,
_flag: null, _flag: null,
_end: null, _end: null,
_startEpoch: null,
_startDayOffset: null,
}; };
export function getState(): Readonly<RuntimeState> { export function getState(): Readonly<RuntimeState> {
@@ -162,10 +152,6 @@ export function clearState() {
runtimeState._timer.pausedAt = null; runtimeState._timer.pausedAt = null;
runtimeState._timer.secondaryTarget = null; runtimeState._timer.secondaryTarget = null;
runtimeState._timer.hasFinished = false; runtimeState._timer.hasFinished = false;
runtimeState._startEpoch = null;
runtimeState._startDayOffset = null;
runtimeState.rundown.currentDay = null;
} }
/** /**
@@ -204,8 +190,7 @@ export function updateRundownData(rundownData: {
runtimeState.rundown.plannedStart = rundownData.firstStart; runtimeState.rundown.plannedStart = rundownData.firstStart;
runtimeState.rundown.plannedEnd = runtimeState.rundown.plannedEnd =
rundownData.firstStart === null ? null : rundownData.firstStart + rundownData.totalDuration; rundownData.firstStart === null ? null : rundownData.firstStart + rundownData.totalDuration;
getExpectedTimes();
if (isPlaybackActive(runtimeState.timer.playback)) getExpectedTimes();
} }
/** /**
@@ -244,14 +229,9 @@ export function load(
// patch with potential provided data // patch with potential provided data
if (initialData) { if (initialData) {
patchTimer(initialData); patchTimer(initialData);
const startEpoch = initialData?.startEpoch;
const firstStart = initialData?.firstStart; const firstStart = initialData?.firstStart;
if ( if (firstStart === null || typeof firstStart === 'number') {
(firstStart === null || typeof firstStart === 'number') &&
(startEpoch === null || typeof startEpoch === 'number')
) {
runtimeState.rundown.actualStart = firstStart; runtimeState.rundown.actualStart = firstStart;
runtimeState._startEpoch = startEpoch;
const { absolute, relative } = getRuntimeOffset(runtimeState); const { absolute, relative } = getRuntimeOffset(runtimeState);
runtimeState.offset.absolute = absolute; runtimeState.offset.absolute = absolute;
runtimeState.offset.relative = relative; runtimeState.offset.relative = relative;
@@ -387,8 +367,7 @@ export function start(state: RuntimeState = runtimeState): boolean {
return false; return false;
} }
const [epoch, now] = getTimeObject(); state.clock = timeNow();
state.clock = now;
state.timer.secondaryTimer = null; state.timer.secondaryTimer = null;
// add paused time if it exists // add paused time if it exists
@@ -407,16 +386,9 @@ export function start(state: RuntimeState = runtimeState): boolean {
state.timer.elapsed = 0; state.timer.elapsed = 0;
if (state.rundown.actualStart === null) { if (state.rundown.actualStart === null) {
state._startDayOffset = findDayOffset(state.eventNow.timeStart, state.clock);
state.rundown.currentDay = state._startDayOffset;
state._startEpoch = epoch;
state.rundown.actualStart = state.clock; state.rundown.actualStart = state.clock;
} }
if (state.groupNow !== null && state.rundown.actualGroupStart === null) {
state.rundown.actualGroupStart = state.clock;
}
// update timer phase // update timer phase
runtimeState.timer.phase = getTimerPhase(runtimeState); runtimeState.timer.phase = getTimerPhase(runtimeState);
@@ -508,20 +480,13 @@ export type UpdateResult = {
export function update(): UpdateResult { export function update(): UpdateResult {
// 0. there are some things we always do // 0. there are some things we always do
const previousClock = runtimeState.clock; const previousClock = runtimeState.clock;
const [epoch, now] = getTimeObject(); runtimeState.clock = timeNow(); // we update the clock on every update call
runtimeState.clock = now; // we update the clock on every update call
// 1. is playback idle? // 1. is playback idle?
if (!isPlaybackActive(runtimeState.timer.playback)) { if (!isPlaybackActive(runtimeState.timer.playback)) {
return updateIfIdle(); return updateIfIdle();
} }
// if we are playing and playback changes. we tick the current runtime day
if (runtimeState._startDayOffset !== null && runtimeState._startEpoch) {
runtimeState.rundown.currentDay =
runtimeState._startDayOffset + Math.floor((epoch - runtimeState._startEpoch) / dayInMs);
}
// 2. are we waiting to roll? // 2. are we waiting to roll?
if (runtimeState.timer.playback === Playback.Roll && runtimeState.timer.secondaryTimer !== null) { if (runtimeState.timer.playback === Playback.Roll && runtimeState.timer.secondaryTimer !== null) {
const hasCrossedMidnight = previousClock > runtimeState.clock; const hasCrossedMidnight = previousClock > runtimeState.clock;
@@ -603,10 +568,6 @@ export function roll(
return { eventId: runtimeState.eventNow?.id ?? null, didStart: false }; return { eventId: runtimeState.eventNow?.id ?? null, didStart: false };
} }
// we will need to do some calculations, update the time first
const [epoch, now] = getTimeObject();
runtimeState.clock = now;
// 2. if there is an event armed, we use it // 2. if there is an event armed, we use it
if (runtimeState.timer.playback === Playback.Armed || runtimeState.timer.phase === TimerPhase.Pending) { if (runtimeState.timer.playback === Playback.Armed || runtimeState.timer.phase === TimerPhase.Pending) {
// eslint-disable-next-line no-unused-labels -- dev code path // eslint-disable-next-line no-unused-labels -- dev code path
@@ -637,24 +598,12 @@ export function roll(
// check if the event is ready to start or if needs to be pending // check if the event is ready to start or if needs to be pending
const isNow = checkIsNow(runtimeState.eventNow.timeStart, runtimeState.eventNow.timeEnd, offsetClock); const isNow = checkIsNow(runtimeState.eventNow.timeStart, runtimeState.eventNow.timeEnd, offsetClock);
if (isNow) { if (isNow) {
/** runtimeState.timer.startedAt = runtimeState.clock;
* If we are starting an event in roll mode
* we backtrace all the start times to the supposed start time of the event
*/
const plannedStart = runtimeState.eventNow.timeStart;
runtimeState.timer.startedAt = plannedStart;
// reset the secondary timer to cancel any countdowns
runtimeState.timer.secondaryTimer = null;
if (runtimeState.groupNow !== null && runtimeState.rundown.actualGroupStart === null) {
runtimeState.rundown.actualGroupStart = plannedStart;
}
if (runtimeState.rundown.actualStart === null) { if (runtimeState.rundown.actualStart === null) {
runtimeState.rundown.actualStart = plannedStart; runtimeState.rundown.actualStart = runtimeState.clock;
runtimeState._startDayOffset = 0;
runtimeState._startEpoch = epoch;
} }
runtimeState.timer.secondaryTimer = null;
} else { } else {
runtimeState._timer.secondaryTarget = normaliseRollStart(runtimeState.eventNow.timeStart, offsetClock); runtimeState._timer.secondaryTarget = normaliseRollStart(runtimeState.eventNow.timeStart, offsetClock);
runtimeState.timer.secondaryTimer = runtimeState._timer.secondaryTarget - offsetClock; runtimeState.timer.secondaryTimer = runtimeState._timer.secondaryTarget - offsetClock;
@@ -709,37 +658,26 @@ export function roll(
return { eventId: runtimeState.eventNow.id, didStart: false }; return { eventId: runtimeState.eventNow.id, didStart: false };
} }
/** // there is something to run, load event
* At this point we know that there is something to run and the event is loaded // event will finish on time
* - ensure the event will finish ontime // account for event that finishes the day after
* - account for events that finish the day after
*
* when we start in roll mode
* we need to backtrace all times to the supposed start time of the event
*/
const plannedStart = runtimeState.eventNow.timeStart;
const endTime = const endTime =
runtimeState.eventNow.timeEnd < runtimeState.eventNow.timeStart runtimeState.eventNow.timeEnd < runtimeState.eventNow.timeStart
? runtimeState.eventNow.timeEnd + dayInMs ? runtimeState.eventNow.timeEnd + dayInMs
: runtimeState.eventNow.timeEnd; : runtimeState.eventNow.timeEnd;
runtimeState.timer.startedAt = plannedStart; runtimeState.timer.startedAt = runtimeState.clock;
runtimeState.timer.expectedFinish = endTime; runtimeState.timer.expectedFinish = endTime;
// we add time to allow timer to catch up
runtimeState.timer.addedTime = -(runtimeState.clock - runtimeState.eventNow.timeStart);
// state catch up // state catch up
runtimeState.timer.duration = calculateDuration(runtimeState.eventNow.timeStart, endTime); runtimeState.timer.duration = calculateDuration(runtimeState.eventNow.timeStart, endTime);
runtimeState.timer.current = getCurrent(runtimeState); runtimeState.timer.current = getCurrent(runtimeState);
runtimeState.timer.elapsed = 0; runtimeState.timer.elapsed = 0;
// update runtime // update runtime
runtimeState.rundown.actualStart = plannedStart; runtimeState.rundown.actualStart = runtimeState.clock;
if (runtimeState.groupNow !== null && runtimeState.rundown.actualGroupStart === null) {
runtimeState.rundown.actualGroupStart = plannedStart;
}
// update metadata
runtimeState._startDayOffset = 0;
runtimeState._startEpoch = epoch;
return { eventId: runtimeState.eventNow.id, didStart: true }; return { eventId: runtimeState.eventNow.id, didStart: true };
} }
@@ -766,7 +704,7 @@ function getExpectedTimes(state = runtimeState) {
if (_group !== null) { if (_group !== null) {
const { event: lastEvent, accumulatedGap, isLinkedToLoaded } = _group; const { event: lastEvent, accumulatedGap, isLinkedToLoaded } = _group;
const lastEventExpectedStart = getExpectedStart(lastEvent, { const lastEventExpectedStart = getExpectedStart(lastEvent, {
currentDay: state.rundown.currentDay!, currentDay: eventNow.dayOffset,
totalGap: accumulatedGap, totalGap: accumulatedGap,
isLinkedToLoaded, isLinkedToLoaded,
mode: offset.mode, mode: offset.mode,
@@ -783,7 +721,7 @@ function getExpectedTimes(state = runtimeState) {
if (_flag) { if (_flag) {
const { event, accumulatedGap, isLinkedToLoaded } = _flag; const { event, accumulatedGap, isLinkedToLoaded } = _flag;
const expectedStart = getExpectedStart(event, { const expectedStart = getExpectedStart(event, {
currentDay: state.rundown.currentDay!, currentDay: eventNow.dayOffset,
totalGap: accumulatedGap, totalGap: accumulatedGap,
isLinkedToLoaded, isLinkedToLoaded,
mode: offset.mode, mode: offset.mode,
@@ -798,7 +736,7 @@ function getExpectedTimes(state = runtimeState) {
if (state._end) { if (state._end) {
const { event, accumulatedGap, isLinkedToLoaded } = state._end; const { event, accumulatedGap, isLinkedToLoaded } = state._end;
const expectedStart = getExpectedStart(event, { const expectedStart = getExpectedStart(event, {
currentDay: state.rundown.currentDay!, currentDay: eventNow.dayOffset,
totalGap: accumulatedGap, totalGap: accumulatedGap,
isLinkedToLoaded, isLinkedToLoaded,
mode: offset.mode, mode: offset.mode,
@@ -814,19 +752,16 @@ export function loadGroupFlagAndEnd(
rundown: Rundown, rundown: Rundown,
metadata: RundownMetadata, metadata: RundownMetadata,
currentIndex: MaybeNumber, currentIndex: MaybeNumber,
state = runtimeState, // used for testing state = runtimeState,
) { ) {
const previousGroup = state.groupNow?.id;
state.groupNow = null; state.groupNow = null;
state._group = null; state._group = null;
state.eventFlag = null; state.eventFlag = null;
state._flag = null; state._flag = null;
state._end = null; state._end = null;
if (currentIndex === null || state.eventNow === null) { if (currentIndex == null) return;
state.rundown.actualGroupStart = null; if (state.eventNow === null) return;
return;
}
const currentGroupId = state.eventNow.parent; const currentGroupId = state.eventNow.parent;
const flagsPresent = metadata.flags.length !== 0; const flagsPresent = metadata.flags.length !== 0;
@@ -838,10 +773,6 @@ export function loadGroupFlagAndEnd(
state.groupNow = currentGroupId ? (entries[currentGroupId] as OntimeGroup) : null; state.groupNow = currentGroupId ? (entries[currentGroupId] as OntimeGroup) : null;
const lastEventInGroup = orderInGroup ? getLastEventNormal(rundown.entries, orderInGroup).lastEvent : null; const lastEventInGroup = orderInGroup ? getLastEventNormal(rundown.entries, orderInGroup).lastEvent : null;
if (previousGroup !== currentGroupId) {
state.rundown.actualGroupStart = null;
}
// if we don't have a any flags in the rundown then no need to look for it // if we don't have a any flags in the rundown then no need to look for it
let foundFlag = !flagsPresent; let foundFlag = !flagsPresent;
// if we don't have a last event for the group there is no need to find its end time // if we don't have a last event for the group there is no need to find its end time
@@ -883,5 +814,4 @@ export function loadGroupFlagAndEnd(
export function setOffsetMode(mode: OffsetMode) { export function setOffsetMode(mode: OffsetMode) {
runtimeState.offset.mode = mode; runtimeState.offset.mode = mode;
if (isPlaybackActive(runtimeState.timer.playback)) getExpectedTimes();
} }
@@ -145,25 +145,16 @@ describe('coerce unknown value to a boolean', () => {
expect(coerceBoolean('')).toStrictEqual(false); expect(coerceBoolean('')).toStrictEqual(false);
}); });
test('invalid strings', () => {
expect(() => coerceBoolean('bla')).toThrowError('Invalid value received');
expect(() => coerceBoolean(' ')).toThrowError('Invalid value received');
});
test('true numbers', () => { test('true numbers', () => {
expect(coerceBoolean(1)).toStrictEqual(true); expect(coerceBoolean(1)).toStrictEqual(true);
expect(coerceBoolean(2)).toStrictEqual(true);
expect(coerceBoolean(100000)).toStrictEqual(true);
}); });
test('false numbers', () => { test.todo('false numbers', () => {
expect(coerceBoolean(0)).toStrictEqual(false); expect(coerceBoolean(0)).toStrictEqual(false);
}); expect(coerceBoolean(-1)).toStrictEqual(false);
expect(coerceBoolean(-10000)).toStrictEqual(false);
test('invalid numbers', () => {
expect(() => coerceBoolean(0.5)).toThrowError('Invalid value received');
expect(() => coerceBoolean(-1)).toThrowError('Invalid value received');
expect(() => coerceBoolean(2)).toThrowError('Invalid value received');
expect(() => coerceBoolean(NaN)).toThrowError('Invalid value received');
expect(() => coerceBoolean(Infinity)).toThrowError('Invalid value received');
}); });
test('booleans', () => { test('booleans', () => {
+3 -7
View File
@@ -33,8 +33,8 @@ export function coerceString(value: unknown): string {
* @throws {Error} Throws an error if the value is null or undefined. * @throws {Error} Throws an error if the value is null or undefined.
*/ */
export function coerceBoolean(value: unknown): boolean { export function coerceBoolean(value: unknown): boolean {
if (typeof value === 'boolean') { if (value === undefined || typeof value === 'object') {
return value; throw new Error('Invalid value received');
} }
if (typeof value === 'string') { if (typeof value === 'string') {
const lowerCaseValue = value.toLocaleLowerCase(); const lowerCaseValue = value.toLocaleLowerCase();
@@ -52,11 +52,7 @@ export function coerceBoolean(value: unknown): boolean {
throw new Error('Invalid value received'); throw new Error('Invalid value received');
} }
} }
if (typeof value === 'number') { return Boolean(value);
if (value === 0) return false;
if (value === 1) return true;
}
throw new Error('Invalid value received');
} }
/** /**
-15
View File
@@ -74,18 +74,3 @@ export function timeNow() {
elapsed += now.getMilliseconds(); elapsed += now.getMilliseconds();
return elapsed; return elapsed;
} }
/**
* Get current time from system
* @returns [number, number] - [epoch time, milliseconds since midnight]
*/
export function getTimeObject(): [number, number] {
const now = new Date();
// extract milliseconds since midnight
let elapsed = now.getHours() * 3600000;
elapsed += now.getMinutes() * 60000;
elapsed += now.getSeconds() * 1000;
elapsed += now.getMilliseconds();
return [now.getTime(), elapsed];
}
+6 -5
View File
@@ -1,12 +1,13 @@
{ {
"extends": "../../tsconfig.common.json",
"compilerOptions": { "compilerOptions": {
"target": "esnext", "strict": true,
"module": "node16", "target": "ESNext",
"moduleResolution": "node16", "module": "Node16",
"noImplicitReturns": false, //TODO: fix this "moduleResolution": "Node16",
"allowSyntheticDefaultImports": true,
"allowJs": true, "allowJs": true,
"esModuleInterop": true, "esModuleInterop": true,
"skipLibCheck": true,
"types": ["vitest/globals"], "types": ["vitest/globals"],
"outDir": "dist", "outDir": "dist",
"experimentalDecorators": true, "experimentalDecorators": true,
@@ -72,6 +72,4 @@ test('time until relative', async ({ page }) => {
await expect(page.getByTestId('entry-2').getByTestId('rundown-event')).toContainText('30s'); await expect(page.getByTestId('entry-2').getByTestId('rundown-event')).toContainText('30s');
await expect(page.getByTestId('entry-3').getByTestId('rundown-event')).toContainText('10m'); await expect(page.getByTestId('entry-3').getByTestId('rundown-event')).toContainText('10m');
await expect(page.getByTestId('entry-4').getByTestId('rundown-event')).toContainText('20m'); await expect(page.getByTestId('entry-4').getByTestId('rundown-event')).toContainText('20m');
await page.getByRole('button', { name: 'Absolute' }).click();
}); });
+3 -4
View File
@@ -1,6 +1,6 @@
{ {
"name": "ontime", "name": "ontime",
"version": "4.0.0-beta.4", "version": "4.0.0-beta.3",
"description": "Time keeping for live events", "description": "Time keeping for live events",
"keywords": [ "keywords": [
"ontime", "ontime",
@@ -33,7 +33,7 @@
"e2e": "pnpm clear-temp && cross-env DEBUG=pw:webserver npx playwright test -c playwright.config.ts", "e2e": "pnpm clear-temp && cross-env DEBUG=pw:webserver npx playwright test -c playwright.config.ts",
"e2e:ui": "cross-env DEBUG=pw:webserver npx playwright test --ui -c playwright.config.ts", "e2e:ui": "cross-env DEBUG=pw:webserver npx playwright test --ui -c playwright.config.ts",
"e2e:i": "npx playwright codegen", "e2e:i": "npx playwright codegen",
"cleanup": "pnpm rimraf node_modules && rimraf **/node_modules && rimraf **/**/node_modules", "cleanup": "rm -rf node_modules && rm -rf **/node_modules && rm -rf **/**/node_modules",
"clear-temp": "rm -rf e2e/tests/fixtures/tmp" "clear-temp": "rm -rf e2e/tests/fixtures/tmp"
}, },
"devDependencies": { "devDependencies": {
@@ -47,8 +47,7 @@
"eslint-plugin-playwright": "^1.5.2", "eslint-plugin-playwright": "^1.5.2",
"prettier": "catalog:", "prettier": "catalog:",
"turbo": "^2.3.3", "turbo": "^2.3.3",
"typescript": "catalog:", "typescript": "catalog:"
"rimraf": "catalog:"
}, },
"packageManager": "pnpm@10.11.0+sha512.6540583f41cc5f628eb3d9773ecee802f4f9ef9923cc45b69890fb47991d4b092964694ec3a4f738a420c918a333062c8b925d312f42e4f0c263eb603551f977" "packageManager": "pnpm@10.11.0+sha512.6540583f41cc5f628eb3d9773ecee802f4f9ef9923cc45b69890fb47991d4b092964694ec3a4f738a420c918a333062c8b925d312f42e4f0c263eb603551f977"
} }
+2 -5
View File
@@ -1,5 +1,4 @@
{ {
"version": "4.0.0-beta.4",
"name": "ontime-types", "name": "ontime-types",
"type": "module", "type": "module",
"main": "./src/index.ts", "main": "./src/index.ts",
@@ -8,8 +7,7 @@
"description": "shared typings for ontime", "description": "shared typings for ontime",
"scripts": { "scripts": {
"cleanup": "rm -rf .turbo && rm -rf node_modules", "cleanup": "rm -rf .turbo && rm -rf node_modules",
"lint": "eslint . --quiet", "lint": "eslint . --quiet"
"build": "tsc"
}, },
"keywords": [], "keywords": [],
"author": "", "author": "",
@@ -18,7 +16,6 @@
"@typescript-eslint/eslint-plugin": "catalog:", "@typescript-eslint/eslint-plugin": "catalog:",
"@typescript-eslint/parser": "catalog:", "@typescript-eslint/parser": "catalog:",
"eslint": "catalog:", "eslint": "catalog:",
"typescript": "catalog:", "typescript": "catalog:"
"ts-essentials": "catalog:"
} }
} }
+14 -176
View File
@@ -1,177 +1,15 @@
import type { DeepPartial } from 'ts-essentials';
import type { OntimeEvent } from '../../definitions/core/OntimeEntry.js';
import type { SimpleDirection, SimplePlayback } from '../../definitions/runtime/AuxTimer.type.js';
import type { MessageState } from '../../definitions/runtime/MessageControl.type.js';
import type { OffsetMode } from '../../definitions/runtime/Offset.type.js';
import type { RuntimeStore } from '../../definitions/runtime/RuntimeStore.type.js';
export type VersionAction = {
tag: 'version';
payload: undefined;
};
export type VersionResponse = {
tag: 'version';
payload: string;
};
export type PollAction = {
tag: 'poll';
payload: undefined;
};
export type PollResponse = {
tag: 'poll';
payload: RuntimeStore;
};
export type ChangeAction = {
tag: 'change';
payload: { [x: string]: Partial<OntimeEvent> };
};
export type ChangeResponse = {
tag: 'change';
payload: 'success' | 'throttled';
};
export type MessageAction = {
tag: 'message';
payload: DeepPartial<MessageState>;
};
export type MessageResponse = {
tag: 'message';
payload: MessageState;
};
export type StartAction = {
tag: 'start';
payload: undefined | { index: number } | { id: string } | { cue: string } | 'next' | 'previous';
};
export type StartResponse = {
tag: 'start';
payload: 'success';
};
export type PauseAction = {
tag: 'pause';
payload: undefined;
};
export type PauseResponse = {
tag: 'pause';
payload: 'success';
};
export type StopAction = {
tag: 'stop';
payload: undefined;
};
export type StopResponse = {
tag: 'stop';
payload: 'success';
};
export type ReloadAction = {
tag: 'reload';
payload: undefined;
};
export type ReloadResponse = {
tag: 'reload';
payload: 'success';
};
export type RollAction = {
tag: 'roll';
payload: undefined;
};
export type RollResponse = {
tag: 'roll';
payload: 'success';
};
export type LoadAction = {
tag: 'load';
payload: { index: number } | { id: string } | { cue: string } | 'next' | 'previous';
};
export type LoadResponse = {
tag: 'load';
payload: 'success';
};
export type AddtimeAction = {
tag: 'addtime';
payload: { add: number } | { remove: number } | number;
};
export type AddtimeResponse = {
tag: 'addtime';
payload: 'success';
};
export type AuxtimerAction = {
tag: 'auxtimer';
payload:
| {
['1']?: SimplePlayback | { duration?: number; addtime?: number; direction?: SimpleDirection };
}
| {
['2']?: SimplePlayback | { duration?: number; addtime?: number; direction?: SimpleDirection };
}
| {
['3']?: SimplePlayback | { duration?: number; addtime?: number; direction?: SimpleDirection };
};
};
export type AuxtimerResponse = {
tag: 'auxtimer';
payload: 'success';
};
export type ClientAction = {
tag: 'client';
payload: { target: string } & ({ rename: string } | { redirect: string } | { identify: string });
};
export type ClientResponse = {
tag: 'client';
payload: 'success';
};
export type OffsetmodeAction = {
tag: 'offsetmode';
payload: OffsetMode;
};
export type OffsetmodeResponse = {
tag: 'offsetmode';
payload: 'success';
};
export type ApiAction = export type ApiAction =
| VersionAction | 'version'
| PollAction | 'poll'
| ChangeAction | 'change'
| MessageAction | 'message'
| StartAction | 'start'
| PauseAction | 'pause'
| StopAction | 'stop'
| ReloadAction | 'reload'
| RollAction | 'roll'
| LoadAction | 'load'
| AddtimeAction | 'addtime'
| AuxtimerAction | 'auxtimer'
| ClientAction | 'client'
| OffsetmodeAction; | 'offsetmode';
export type ApiResponse =
| VersionResponse
| PollResponse
| ChangeResponse
| MessageResponse
| StartResponse
| PauseResponse
| StopResponse
| ReloadResponse
| RollResponse
| LoadResponse
| AddtimeResponse
| AuxtimerResponse
| ClientResponse
| OffsetmodeResponse;
export type ApiActionTag = ApiAction['tag'];
@@ -1,11 +1,12 @@
import type {
import type { AutomationSettings } from './core/Automation.type.js'; AutomationSettings,
import type { CustomFields } from './core/CustomFields.type.js'; CustomFields,
import type { ProjectData } from './core/ProjectData.type.js'; ProjectData,
import type { ProjectRundowns } from './core/Rundown.type.js'; ProjectRundowns,
import type { Settings } from './core/Settings.type.js'; Settings,
import type { URLPreset } from './core/UrlPreset.type.js'; URLPreset,
import type { ViewSettings } from './core/Views.type.js'; ViewSettings,
} from '../index.js';
export type DatabaseModel = { export type DatabaseModel = {
rundowns: ProjectRundowns; rundowns: ProjectRundowns;
@@ -1,9 +1,4 @@
import type { MaybeNumber } from '../../utils/utils.type.js'; import type { EndAction, EntryCustomFields, MaybeNumber, TimerType, TimeStrategy, Trigger } from '../../index.js';
import type { EndAction } from '../EndAction.type.js';
import type { TimerType } from '../TimerType.type.js';
import type { TimeStrategy } from '../TimeStrategy.type.js';
import type { Trigger } from './Automation.type.js';
import type { EntryCustomFields } from './CustomFields.type.js';
export type EntryId = string; export type EntryId = string;
@@ -4,8 +4,6 @@ export type RundownState = {
selectedEventIndex: MaybeNumber; selectedEventIndex: MaybeNumber;
numEvents: number; numEvents: number;
plannedStart: MaybeNumber; plannedStart: MaybeNumber;
plannedEnd: MaybeNumber;
actualStart: MaybeNumber; actualStart: MaybeNumber;
currentDay: MaybeNumber; plannedEnd: MaybeNumber;
actualGroupStart: MaybeNumber;
}; };
@@ -33,8 +33,6 @@ export const runtimeStorePlaceholder: Readonly<RuntimeStore> = {
plannedStart: 0, // only changes if event changes plannedStart: 0, // only changes if event changes
plannedEnd: 0, // only changes if event changes, overflows over dayInMs plannedEnd: 0, // only changes if event changes, overflows over dayInMs
actualStart: null, // set once we start the timer actualStart: null, // set once we start the timer
actualGroupStart: null, // maybe set once we start the timer
currentDay: null,
}, },
offset: { offset: {
absolute: 0, // changes at runtime absolute: 0, // changes at runtime
@@ -1,4 +1,4 @@
import type { MaybeNumber } from '../../utils/utils.type.js'; import type { MaybeNumber } from '../../index.js';
import type { Playback } from './Playback.type.js'; import type { Playback } from './Playback.type.js';
export enum TimerPhase { export enum TimerPhase {
+1 -1
View File
@@ -90,7 +90,7 @@ export type { LinkOptions } from './api/session-controller/BackendResponse.type.
export { MessageTag } from './api/websocket/data.type.js'; export { MessageTag } from './api/websocket/data.type.js';
export type { WsPacketToServer, WsPacketToClient } from './api/websocket/data.type.js'; export type { WsPacketToServer, WsPacketToClient } from './api/websocket/data.type.js';
export { RefetchKey } from './api/websocket/refetch.type.js'; export { RefetchKey } from './api/websocket/refetch.type.js';
export type { ApiAction, ApiActionTag, ApiResponse } from './api/websocket/api.type.js'; export type { ApiAction } from './api/websocket/api.type.js';
// SERVER RUNTIME // SERVER RUNTIME
export { type Log, LogLevel, type LogMessage, LogOrigin } from './definitions/runtime/Logger.type.js'; export { type Log, LogLevel, type LogMessage, LogOrigin } from './definitions/runtime/Logger.type.js';
export { Playback } from './definitions/runtime/Playback.type.js'; export { Playback } from './definitions/runtime/Playback.type.js';
-20
View File
@@ -1,20 +0,0 @@
{
"extends": "../../tsconfig.common.json",
"compilerOptions": {
"target": "esnext",
"module": "esnext",
"moduleResolution": "node10",
"allowUnusedLabels": false,
"removeComments": true,
"preserveConstEnums": true,
"allowJs": true,
"declaration": true,
"outDir": "dist",
},
"include": [
"src/index.ts",
],
"exclude": [
"node_modules", "dist"
]
}
@@ -1,6 +1,6 @@
import { OffsetMode } from 'ontime-types'; import { OffsetMode } from 'ontime-types';
import { dayInMs, MILLIS_PER_HOUR } from './conversionUtils'; import { dayInMs } from './conversionUtils';
import { getExpectedStart } from './getExpectedStart'; import { getExpectedStart } from './getExpectedStart';
describe('getExpectedStart()', () => { describe('getExpectedStart()', () => {
@@ -277,41 +277,4 @@ describe('getExpectedStart()', () => {
// the overlap will be pushed out to the expected available time // the overlap will be pushed out to the expected available time
expect(getExpectedStart(testEvent, { ...testState, totalGap: -5 })).toBe(110); expect(getExpectedStart(testEvent, { ...testState, totalGap: -5 })).toBe(110);
}); });
test('we started on the day before', () => {
const testEvent = {
timeStart: 5,
dayOffset: 0,
delay: 0,
};
const testState = {
currentDay: -1,
actualStart: 23 * MILLIS_PER_HOUR,
plannedStart: 0,
offset: -1 * MILLIS_PER_HOUR,
mode: OffsetMode.Absolute,
isLinkedToLoaded: true,
totalGap: 0,
};
expect(getExpectedStart(testEvent, { ...testState })).toBe(23 * MILLIS_PER_HOUR + 5);
});
test('next day in multi-day rundown', () => {
const testEvent = {
timeStart: 5,
dayOffset: 1,
delay: 0,
};
const testState = {
currentDay: -1,
actualStart: 23 * MILLIS_PER_HOUR,
plannedStart: 0,
offset: -1 * MILLIS_PER_HOUR,
mode: OffsetMode.Absolute,
isLinkedToLoaded: true,
totalGap: 0,
};
expect(getExpectedStart(testEvent, { ...testState })).toBe(23 * MILLIS_PER_HOUR + 5 + dayInMs);
expect(getExpectedStart(testEvent, { ...testState, currentDay: 0 })).toBe(23 * MILLIS_PER_HOUR + 5);
});
}); });
@@ -15,7 +15,7 @@ import { dayInMs } from './conversionUtils.js';
export function getExpectedStart( export function getExpectedStart(
event: Pick<OntimeEvent, 'timeStart' | 'dayOffset' | 'delay'>, event: Pick<OntimeEvent, 'timeStart' | 'dayOffset' | 'delay'>,
state: { state: {
currentDay: number; // the current day from the rundown currentDay: number;
totalGap: number; totalGap: number;
isLinkedToLoaded: boolean; isLinkedToLoaded: boolean;
offset: number; offset: number;
@@ -38,7 +38,7 @@ export function getExpectedStart(
let relativeStartOffset = 0; let relativeStartOffset = 0;
if (mode === OffsetMode.Relative) { if (mode === OffsetMode.Relative) {
relativeStartOffset = (actualStart ?? 0) + currentDay * dayInMs - (plannedStart ?? 0); relativeStartOffset = (actualStart ?? 0) - (plannedStart ?? 0);
} }
const scheduledStartTime = normalisedTimeStart + relativeStartOffset; const scheduledStartTime = normalisedTimeStart + relativeStartOffset;
+15 -2
View File
@@ -1,10 +1,23 @@
{ {
"extends": "../../tsconfig.common.json",
"compilerOptions": { "compilerOptions": {
"target": "esnext", "target": "ESNext",
"module": "esnext", "module": "esnext",
"moduleResolution": "node", "moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"skipLibCheck": true,
"allowSyntheticDefaultImports": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"noImplicitAny": true,
"noImplicitReturns": true,
"allowUnreachableCode": false,
"allowUnusedLabels": false, "allowUnusedLabels": false,
"noImplicitThis": true,
"strictNullChecks": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"removeComments": true, "removeComments": true,
"preserveConstEnums": true, "preserveConstEnums": true,
"allowJs": true, "allowJs": true,
+16 -350
View File
@@ -27,12 +27,6 @@ catalogs:
prettier: prettier:
specifier: 3.3.1 specifier: 3.3.1
version: 3.3.1 version: 3.3.1
rimraf:
specifier: 6.0.1
version: 6.0.1
ts-essentials:
specifier: 10.1.1
version: 10.1.1
typescript: typescript:
specifier: 5.5.3 specifier: 5.5.3
version: 5.5.3 version: 5.5.3
@@ -71,9 +65,6 @@ importers:
prettier: prettier:
specifier: 'catalog:' specifier: 'catalog:'
version: 3.3.1 version: 3.3.1
rimraf:
specifier: 'catalog:'
version: 6.0.1
turbo: turbo:
specifier: ^2.3.3 specifier: ^2.3.3
version: 2.5.6 version: 2.5.6
@@ -280,30 +271,6 @@ importers:
specifier: ^7.2.0 specifier: ^7.2.0
version: 7.2.0 version: 7.2.0
apps/resolver:
devDependencies:
'@sprout2000/esbuild-copy-plugin':
specifier: ^1.1.19
version: 1.1.19
'@typescript-eslint/parser':
specifier: 'catalog:'
version: 7.16.1(eslint@8.56.0)(typescript@5.5.3)
eslint:
specifier: 'catalog:'
version: 8.56.0
ontime-types:
specifier: workspace:^4.0.0
version: link:../../packages/types
rimraf:
specifier: 'catalog:'
version: 6.0.1
tsup:
specifier: ^8.5.0
version: 8.5.0(jiti@2.4.2)(postcss@8.5.4)(tsx@4.20.5)(typescript@5.5.3)
typescript:
specifier: 'catalog:'
version: 5.5.3
apps/server: apps/server:
dependencies: dependencies:
'@googleapis/sheets': '@googleapis/sheets':
@@ -351,6 +318,9 @@ importers:
sanitize-filename: sanitize-filename:
specifier: ^1.6.3 specifier: ^1.6.3
version: 1.6.3 version: 1.6.3
steno:
specifier: ^4.0.2
version: 4.0.2
ws: ws:
specifier: ^8.18.0 specifier: ^8.18.0
version: 8.18.3 version: 8.18.3
@@ -407,7 +377,7 @@ importers:
specifier: ^0.3.4 specifier: ^0.3.4
version: 0.3.4 version: 0.3.4
ts-essentials: ts-essentials:
specifier: 'catalog:' specifier: ^10.0.3
version: 10.1.1(typescript@5.5.3) version: 10.1.1(typescript@5.5.3)
tsx: tsx:
specifier: ^4.19.2 specifier: ^4.19.2
@@ -430,9 +400,6 @@ importers:
eslint: eslint:
specifier: 'catalog:' specifier: 'catalog:'
version: 8.56.0 version: 8.56.0
ts-essentials:
specifier: 'catalog:'
version: 10.1.1(typescript@5.5.3)
typescript: typescript:
specifier: 'catalog:' specifier: 'catalog:'
version: 5.5.3 version: 5.5.3
@@ -1608,10 +1575,6 @@ packages:
resolution: {integrity: sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==} resolution: {integrity: sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==}
engines: {node: '>=10'} engines: {node: '>=10'}
'@sprout2000/esbuild-copy-plugin@1.1.19':
resolution: {integrity: sha512-KXI1nDBWKLubg5EJy+jkxhqvtg9wlshF+ycVMoad6j2pr/APQiDMwAv6JUzkgU6INxg40E4/7xfgefi/Gn+blA==}
engines: {node: '>=16.7'}
'@svgr/babel-plugin-add-jsx-attribute@8.0.0': '@svgr/babel-plugin-add-jsx-attribute@8.0.0':
resolution: {integrity: sha512-b9MIk7yhdS1pMCZM8VeNfUlSKVRhsHZNMl5O9SfaX0l0t5wjdgu4IDzGB8bpnGBBOjGST3rRFVsaaEtI4W6f7g==} resolution: {integrity: sha512-b9MIk7yhdS1pMCZM8VeNfUlSKVRhsHZNMl5O9SfaX0l0t5wjdgu4IDzGB8bpnGBBOjGST3rRFVsaaEtI4W6f7g==}
engines: {node: '>=14'} engines: {node: '>=14'}
@@ -2067,9 +2030,6 @@ packages:
resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==} resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==}
engines: {node: '>=12'} engines: {node: '>=12'}
any-promise@1.3.0:
resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==}
anymatch@3.1.3: anymatch@3.1.3:
resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==}
engines: {node: '>= 8'} engines: {node: '>= 8'}
@@ -2273,12 +2233,6 @@ packages:
builder-util@26.0.17: builder-util@26.0.17:
resolution: {integrity: sha512-fym+vg0kegrHBSCmkYYql2EbsLvnlUhIUKRQJ7EHjyftwMz8mibpvTRll3pzK1rtWm/VRdjl7AB397jdtg/Jmw==} resolution: {integrity: sha512-fym+vg0kegrHBSCmkYYql2EbsLvnlUhIUKRQJ7EHjyftwMz8mibpvTRll3pzK1rtWm/VRdjl7AB397jdtg/Jmw==}
bundle-require@5.1.0:
resolution: {integrity: sha512-3WrrOuZiyaaZPWiEt4G3+IffISVC9HYlWueJEBWED4ZH4aIAC2PnkdnuRrR94M+w6yGWn4AglWtJtBI8YqvgoA==}
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
peerDependencies:
esbuild: '>=0.18'
busboy@1.6.0: busboy@1.6.0:
resolution: {integrity: sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==} resolution: {integrity: sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==}
engines: {node: '>=10.16.0'} engines: {node: '>=10.16.0'}
@@ -2423,10 +2377,6 @@ packages:
resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==}
engines: {node: '>= 0.8'} engines: {node: '>= 0.8'}
commander@4.1.1:
resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==}
engines: {node: '>= 6'}
commander@5.1.0: commander@5.1.0:
resolution: {integrity: sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==} resolution: {integrity: sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==}
engines: {node: '>= 6'} engines: {node: '>= 6'}
@@ -2446,16 +2396,9 @@ packages:
resolution: {integrity: sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==} resolution: {integrity: sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==}
engines: {'0': node >= 6.0} engines: {'0': node >= 6.0}
confbox@0.1.8:
resolution: {integrity: sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==}
config-file-ts@0.2.6: config-file-ts@0.2.6:
resolution: {integrity: sha512-6boGVaglwblBgJqGyxm4+xCmEGcWgnWHSWHY5jad58awQhB6gftq0G8HbzU39YqCIYHMLAiL1yjwiZ36m/CL8w==} resolution: {integrity: sha512-6boGVaglwblBgJqGyxm4+xCmEGcWgnWHSWHY5jad58awQhB6gftq0G8HbzU39YqCIYHMLAiL1yjwiZ36m/CL8w==}
consola@3.4.2:
resolution: {integrity: sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==}
engines: {node: ^14.18.0 || >=16.10.0}
content-disposition@1.0.0: content-disposition@1.0.0:
resolution: {integrity: sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==} resolution: {integrity: sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==}
engines: {node: '>= 0.6'} engines: {node: '>= 0.6'}
@@ -3042,9 +2985,6 @@ packages:
resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==}
engines: {node: '>=10'} engines: {node: '>=10'}
fix-dts-default-cjs-exports@1.0.1:
resolution: {integrity: sha512-pVIECanWFC61Hzl2+oOCtoJ3F17kglZC/6N94eRWycFgBH35hHx0Li604ZIzhseh97mf2p0cv7vVrOZGoqhlEg==}
flat-cache@3.1.1: flat-cache@3.1.1:
resolution: {integrity: sha512-/qM2b3LUIaIgviBQovTLvijfyOQXPtSRnRK26ksj2J7rzPIecePUIpJsZ4T02Qg+xiAEKIs5K8dsHEd+VaKa/Q==} resolution: {integrity: sha512-/qM2b3LUIaIgviBQovTLvijfyOQXPtSRnRK26ksj2J7rzPIecePUIpJsZ4T02Qg+xiAEKIs5K8dsHEd+VaKa/Q==}
engines: {node: '>=12.0.0'} engines: {node: '>=12.0.0'}
@@ -3186,11 +3126,6 @@ packages:
resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==} resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==}
hasBin: true hasBin: true
glob@11.0.3:
resolution: {integrity: sha512-2Nim7dha1KVkaiF4q6Dj+ngPPMdfvLJEOpZk/jKiUAkqKebpGAWQXAq9z1xu9HKu5lWfqw/FASuccEjyznjPaA==}
engines: {node: 20 || >=22}
hasBin: true
glob@7.2.3: glob@7.2.3:
resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==}
deprecated: Glob versions prior to v9 are no longer supported deprecated: Glob versions prior to v9 are no longer supported
@@ -3571,10 +3506,6 @@ packages:
jackspeak@3.4.3: jackspeak@3.4.3:
resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==}
jackspeak@4.1.1:
resolution: {integrity: sha512-zptv57P3GpL+O0I7VdMJNBZCu+BPHVQUk55Ft8/QCJjTVxrnJHuVuX/0Bl2A6/+2oyR/ZMEuFKwmzqqZ/U5nPQ==}
engines: {node: 20 || >=22}
jake@10.9.2: jake@10.9.2:
resolution: {integrity: sha512-2P4SQ0HrLQ+fw6llpLnOaGAvN2Zu6778SJMrCUwns4fOoG9ayrTiZk3VV8sCPkVZF8ab0zksVpS8FDY5pRCNBA==} resolution: {integrity: sha512-2P4SQ0HrLQ+fw6llpLnOaGAvN2Zu6778SJMrCUwns4fOoG9ayrTiZk3VV8sCPkVZF8ab0zksVpS8FDY5pRCNBA==}
engines: {node: '>=10'} engines: {node: '>=10'}
@@ -3587,10 +3518,6 @@ packages:
joi@17.13.3: joi@17.13.3:
resolution: {integrity: sha512-otDA4ldcIx+ZXsKHWmp0YizCweVRZG96J10b0FevjfuncLO1oX59THoAmHkNubYJ+9gWsYsp5k8v4ib6oDv1fA==} resolution: {integrity: sha512-otDA4ldcIx+ZXsKHWmp0YizCweVRZG96J10b0FevjfuncLO1oX59THoAmHkNubYJ+9gWsYsp5k8v4ib6oDv1fA==}
joycon@3.1.1:
resolution: {integrity: sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==}
engines: {node: '>=10'}
js-tokens@4.0.0: js-tokens@4.0.0:
resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
@@ -3670,17 +3597,9 @@ packages:
resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==}
engines: {node: '>= 0.8.0'} engines: {node: '>= 0.8.0'}
lilconfig@3.1.3:
resolution: {integrity: sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==}
engines: {node: '>=14'}
lines-and-columns@1.2.4: lines-and-columns@1.2.4:
resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==}
load-tsconfig@0.2.5:
resolution: {integrity: sha512-IXO6OCs9yg8tMKzfPZ1YmheJbZCiEsnBdcB03l0OcfK9prKnJb96siuHCr5Fl37/yo9DnKU+TLpxzTUspw9shg==}
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
locate-path@6.0.0: locate-path@6.0.0:
resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==}
engines: {node: '>=10'} engines: {node: '>=10'}
@@ -3700,9 +3619,6 @@ packages:
lodash.merge@4.6.2: lodash.merge@4.6.2:
resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==}
lodash.sortby@4.7.0:
resolution: {integrity: sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA==}
lodash.union@4.6.0: lodash.union@4.6.0:
resolution: {integrity: sha512-c4pB2CdGrGdjMKYLA+XiRDO7Y0PRQbm/Gzg8qMj+QH+pFVAoTp5sBpO0odL3FjoPCGjK96p6qsP+yQoiLoOBcw==} resolution: {integrity: sha512-c4pB2CdGrGdjMKYLA+XiRDO7Y0PRQbm/Gzg8qMj+QH+pFVAoTp5sBpO0odL3FjoPCGjK96p6qsP+yQoiLoOBcw==}
@@ -3734,10 +3650,6 @@ packages:
lru-cache@10.4.3: lru-cache@10.4.3:
resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==}
lru-cache@11.2.1:
resolution: {integrity: sha512-r8LA6i4LP4EeWOhqBaZZjDWwehd1xUJPCJd9Sv300H0ZmcUER4+JPh7bqqZeqs1o5pgtgvXm+d9UGrB5zZGDiQ==}
engines: {node: 20 || >=22}
lru-cache@5.1.1: lru-cache@5.1.1:
resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==}
@@ -3896,9 +3808,6 @@ packages:
engines: {node: '>=10'} engines: {node: '>=10'}
hasBin: true hasBin: true
mlly@1.8.0:
resolution: {integrity: sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g==}
ms@2.1.3: ms@2.1.3:
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
@@ -3906,9 +3815,6 @@ packages:
resolution: {integrity: sha512-Ug8bXeTIUlxurg8xLTEskKShvcKDZALo1THEX5E41pYCD2sCVub5/kIRIGqWNoqV6szyLyQKV6mD4QUrWE5GCQ==} resolution: {integrity: sha512-Ug8bXeTIUlxurg8xLTEskKShvcKDZALo1THEX5E41pYCD2sCVub5/kIRIGqWNoqV6szyLyQKV6mD4QUrWE5GCQ==}
engines: {node: '>= 10.16.0'} engines: {node: '>= 10.16.0'}
mz@2.7.0:
resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==}
nanoid@3.3.11: nanoid@3.3.11:
resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==}
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
@@ -4093,10 +3999,6 @@ packages:
resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==}
engines: {node: '>=16 || 14 >=14.18'} engines: {node: '>=16 || 14 >=14.18'}
path-scurry@2.0.0:
resolution: {integrity: sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg==}
engines: {node: 20 || >=22}
path-to-regexp@8.2.0: path-to-regexp@8.2.0:
resolution: {integrity: sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ==} resolution: {integrity: sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ==}
engines: {node: '>=16'} engines: {node: '>=16'}
@@ -4119,6 +4021,9 @@ packages:
pend@1.2.0: pend@1.2.0:
resolution: {integrity: sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==} resolution: {integrity: sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==}
picocolors@1.0.1:
resolution: {integrity: sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==}
picocolors@1.1.1: picocolors@1.1.1:
resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
@@ -4130,13 +4035,6 @@ packages:
resolution: {integrity: sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==} resolution: {integrity: sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==}
engines: {node: '>=12'} engines: {node: '>=12'}
pirates@4.0.7:
resolution: {integrity: sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==}
engines: {node: '>= 6'}
pkg-types@1.3.1:
resolution: {integrity: sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==}
playwright-core@1.55.0: playwright-core@1.55.0:
resolution: {integrity: sha512-GvZs4vU3U5ro2nZpeiwyb0zuFaqb9sUiAJuyrWpcGouD8y9/HLgGbNRjIph7zU9D3hnPaisMl9zG9CgFi/biIg==} resolution: {integrity: sha512-GvZs4vU3U5ro2nZpeiwyb0zuFaqb9sUiAJuyrWpcGouD8y9/HLgGbNRjIph7zU9D3hnPaisMl9zG9CgFi/biIg==}
engines: {node: '>=18'} engines: {node: '>=18'}
@@ -4155,24 +4053,6 @@ packages:
resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==} resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
postcss-load-config@6.0.1:
resolution: {integrity: sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==}
engines: {node: '>= 18'}
peerDependencies:
jiti: '>=1.21.0'
postcss: '>=8.0.9'
tsx: ^4.8.1
yaml: ^2.4.2
peerDependenciesMeta:
jiti:
optional: true
postcss:
optional: true
tsx:
optional: true
yaml:
optional: true
postcss@8.5.4: postcss@8.5.4:
resolution: {integrity: sha512-QSa9EBe+uwlGTFmHsPKokv3B/oEMQZxfqW0QqNCyhpa6mB1afzulwn8hihglqAb2pOw+BJgNlmXQ8la2VeHB7w==} resolution: {integrity: sha512-QSa9EBe+uwlGTFmHsPKokv3B/oEMQZxfqW0QqNCyhpa6mB1afzulwn8hihglqAb2pOw+BJgNlmXQ8la2VeHB7w==}
engines: {node: ^10 || ^12 || >=14} engines: {node: ^10 || ^12 || >=14}
@@ -4388,10 +4268,6 @@ packages:
resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==}
engines: {node: '>=4'} engines: {node: '>=4'}
resolve-from@5.0.0:
resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==}
engines: {node: '>=8'}
resolve-pkg-maps@1.0.0: resolve-pkg-maps@1.0.0:
resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==}
@@ -4424,11 +4300,6 @@ packages:
deprecated: Rimraf versions prior to v4 are no longer supported deprecated: Rimraf versions prior to v4 are no longer supported
hasBin: true hasBin: true
rimraf@6.0.1:
resolution: {integrity: sha512-9dkvaxAsk/xNXSJzMgFqqMCuFgt2+KsOFek3TMLfo8NCPfWpBmqwyNn5Y+NX56QUYfCtsyhF3ayiboEoUmJk/A==}
engines: {node: 20 || >=22}
hasBin: true
roarr@2.15.4: roarr@2.15.4:
resolution: {integrity: sha512-CHhPh+UNHD2GTXNYhPWLnU8ONHdI+5DI+4EYIAOaiD63rHeYlZvyh8P+in5999TTSFgUYuKUAjzRI4mdh/p+2A==} resolution: {integrity: sha512-CHhPh+UNHD2GTXNYhPWLnU8ONHdI+5DI+4EYIAOaiD63rHeYlZvyh8P+in5999TTSFgUYuKUAjzRI4mdh/p+2A==}
engines: {node: '>=8.0'} engines: {node: '>=8.0'}
@@ -4627,11 +4498,6 @@ packages:
resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==}
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
source-map@0.8.0-beta.0:
resolution: {integrity: sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA==}
engines: {node: '>= 8'}
deprecated: The work that was done in this beta branch won't be included in future versions
sprintf-js@1.1.3: sprintf-js@1.1.3:
resolution: {integrity: sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==} resolution: {integrity: sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==}
@@ -4714,11 +4580,6 @@ packages:
resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==}
engines: {node: '>=8'} engines: {node: '>=8'}
sucrase@3.35.0:
resolution: {integrity: sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==}
engines: {node: '>=16 || 14 >=14.17'}
hasBin: true
sumchecker@3.0.1: sumchecker@3.0.1:
resolution: {integrity: sha512-MvjXzkz/BOfyVDkG0oFOtBxHX2u3gKbMHIF/dXblZsgD3BWOFLmHovIpZY7BykJdAjcqRCBi1WYBNdEC9yI7vg==} resolution: {integrity: sha512-MvjXzkz/BOfyVDkG0oFOtBxHX2u3gKbMHIF/dXblZsgD3BWOFLmHovIpZY7BykJdAjcqRCBi1WYBNdEC9yI7vg==}
engines: {node: '>= 8.0'} engines: {node: '>= 8.0'}
@@ -4765,13 +4626,6 @@ packages:
text-table@0.2.0: text-table@0.2.0:
resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==}
thenify-all@1.6.0:
resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==}
engines: {node: '>=0.8'}
thenify@3.3.1:
resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==}
tiny-async-pool@1.3.0: tiny-async-pool@1.3.0:
resolution: {integrity: sha512-01EAw5EDrcVrdgyCLgoSPvqznC0sVxDSVeiOz09FUpjh71G79VCqneOr+xvt7T1r76CF6ZZfPjHorN2+d+3mqA==} resolution: {integrity: sha512-01EAw5EDrcVrdgyCLgoSPvqznC0sVxDSVeiOz09FUpjh71G79VCqneOr+xvt7T1r76CF6ZZfPjHorN2+d+3mqA==}
@@ -4823,17 +4677,10 @@ packages:
tr46@0.0.3: tr46@0.0.3:
resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==}
tr46@1.0.1:
resolution: {integrity: sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA==}
tr46@3.0.0: tr46@3.0.0:
resolution: {integrity: sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==} resolution: {integrity: sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==}
engines: {node: '>=12'} engines: {node: '>=12'}
tree-kill@1.2.2:
resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==}
hasBin: true
truncate-utf8-bytes@1.0.2: truncate-utf8-bytes@1.0.2:
resolution: {integrity: sha512-95Pu1QXQvruGEhv62XCMO3Mm90GscOCClvrIUwCM0PYOXK3kaF3l3sIHxx71ThJfcbM2O5Au6SO3AWCSEfW4mQ==} resolution: {integrity: sha512-95Pu1QXQvruGEhv62XCMO3Mm90GscOCClvrIUwCM0PYOXK3kaF3l3sIHxx71ThJfcbM2O5Au6SO3AWCSEfW4mQ==}
@@ -4857,9 +4704,6 @@ packages:
typescript: typescript:
optional: true optional: true
ts-interface-checker@0.1.13:
resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==}
tsconfck@3.1.6: tsconfck@3.1.6:
resolution: {integrity: sha512-ks6Vjr/jEw0P1gmOVwutM3B7fWxoWBL2KRDb1JfqGVawBmO5UsvmWOQFGHBPl5yxYz4eERr19E6L7NMv+Fej4w==} resolution: {integrity: sha512-ks6Vjr/jEw0P1gmOVwutM3B7fWxoWBL2KRDb1JfqGVawBmO5UsvmWOQFGHBPl5yxYz4eERr19E6L7NMv+Fej4w==}
engines: {node: ^18 || >=20} engines: {node: ^18 || >=20}
@@ -4873,25 +4717,6 @@ packages:
tslib@2.6.2: tslib@2.6.2:
resolution: {integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==} resolution: {integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==}
tsup@8.5.0:
resolution: {integrity: sha512-VmBp77lWNQq6PfuMqCHD3xWl22vEoWsKajkF8t+yMBawlUS8JzEI+vOVMeuNZIuMML8qXRizFKi9oD5glKQVcQ==}
engines: {node: '>=18'}
hasBin: true
peerDependencies:
'@microsoft/api-extractor': ^7.36.0
'@swc/core': ^1
postcss: ^8.4.12
typescript: '>=4.5.0'
peerDependenciesMeta:
'@microsoft/api-extractor':
optional: true
'@swc/core':
optional: true
postcss:
optional: true
typescript:
optional: true
tsx@4.20.5: tsx@4.20.5:
resolution: {integrity: sha512-+wKjMNU9w/EaQayHXb7WA7ZaHY6hN8WgfvHNQ3t1PnU91/7O8TcTnIhCDYTZwnt8JsO9IBqZ30Ln1r7pPF52Aw==} resolution: {integrity: sha512-+wKjMNU9w/EaQayHXb7WA7ZaHY6hN8WgfvHNQ3t1PnU91/7O8TcTnIhCDYTZwnt8JsO9IBqZ30Ln1r7pPF52Aw==}
engines: {node: '>=18.0.0'} engines: {node: '>=18.0.0'}
@@ -4980,9 +4805,6 @@ packages:
engines: {node: '>=14.17'} engines: {node: '>=14.17'}
hasBin: true hasBin: true
ufo@1.6.1:
resolution: {integrity: sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA==}
unbox-primitive@1.1.0: unbox-primitive@1.1.0:
resolution: {integrity: sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==} resolution: {integrity: sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
@@ -5172,9 +4994,6 @@ packages:
webidl-conversions@3.0.1: webidl-conversions@3.0.1:
resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==}
webidl-conversions@4.0.2:
resolution: {integrity: sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==}
webidl-conversions@7.0.0: webidl-conversions@7.0.0:
resolution: {integrity: sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==} resolution: {integrity: sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==}
engines: {node: '>=12'} engines: {node: '>=12'}
@@ -5201,9 +5020,6 @@ packages:
whatwg-url@5.0.0: whatwg-url@5.0.0:
resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==}
whatwg-url@7.1.0:
resolution: {integrity: sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==}
which-boxed-primitive@1.1.1: which-boxed-primitive@1.1.1:
resolution: {integrity: sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==} resolution: {integrity: sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
@@ -5361,7 +5177,7 @@ snapshots:
'@babel/code-frame@7.24.2': '@babel/code-frame@7.24.2':
dependencies: dependencies:
'@babel/highlight': 7.24.5 '@babel/highlight': 7.24.5
picocolors: 1.1.1 picocolors: 1.0.1
'@babel/code-frame@7.27.1': '@babel/code-frame@7.27.1':
dependencies: dependencies:
@@ -5436,8 +5252,8 @@ snapshots:
'@babel/generator@7.23.6': '@babel/generator@7.23.6':
dependencies: dependencies:
'@babel/types': 7.28.2 '@babel/types': 7.28.2
'@jridgewell/gen-mapping': 0.3.13 '@jridgewell/gen-mapping': 0.3.3
'@jridgewell/trace-mapping': 0.3.30 '@jridgewell/trace-mapping': 0.3.20
jsesc: 2.5.2 jsesc: 2.5.2
'@babel/generator@7.27.5': '@babel/generator@7.27.5':
@@ -5610,7 +5426,7 @@ snapshots:
'@babel/helper-validator-identifier': 7.27.1 '@babel/helper-validator-identifier': 7.27.1
chalk: 2.4.2 chalk: 2.4.2
js-tokens: 4.0.0 js-tokens: 4.0.0
picocolors: 1.1.1 picocolors: 1.0.1
'@babel/parser@7.23.6': '@babel/parser@7.23.6':
dependencies: dependencies:
@@ -6486,8 +6302,6 @@ snapshots:
'@sindresorhus/is@4.6.0': {} '@sindresorhus/is@4.6.0': {}
'@sprout2000/esbuild-copy-plugin@1.1.19': {}
'@svgr/babel-plugin-add-jsx-attribute@8.0.0(@babel/core@7.23.6)': '@svgr/babel-plugin-add-jsx-attribute@8.0.0(@babel/core@7.23.6)':
dependencies: dependencies:
'@babel/core': 7.23.6 '@babel/core': 7.23.6
@@ -6966,7 +6780,8 @@ snapshots:
acorn@8.14.0: {} acorn@8.14.0: {}
acorn@8.15.0: {} acorn@8.15.0:
optional: true
adler-32@1.3.1: {} adler-32@1.3.1: {}
@@ -7016,8 +6831,6 @@ snapshots:
ansi-styles@6.2.1: {} ansi-styles@6.2.1: {}
any-promise@1.3.0: {}
anymatch@3.1.3: anymatch@3.1.3:
dependencies: dependencies:
normalize-path: 3.0.0 normalize-path: 3.0.0
@@ -7381,11 +7194,6 @@ snapshots:
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
bundle-require@5.1.0(esbuild@0.25.5):
dependencies:
esbuild: 0.25.5
load-tsconfig: 0.2.5
busboy@1.6.0: busboy@1.6.0:
dependencies: dependencies:
streamsearch: 1.1.0 streamsearch: 1.1.0
@@ -7553,8 +7361,6 @@ snapshots:
dependencies: dependencies:
delayed-stream: 1.0.0 delayed-stream: 1.0.0
commander@4.1.1: {}
commander@5.1.0: {} commander@5.1.0: {}
compare-version@0.1.2: {} compare-version@0.1.2: {}
@@ -7575,15 +7381,11 @@ snapshots:
readable-stream: 3.6.2 readable-stream: 3.6.2
typedarray: 0.0.6 typedarray: 0.0.6
confbox@0.1.8: {}
config-file-ts@0.2.6: config-file-ts@0.2.6:
dependencies: dependencies:
glob: 10.4.5 glob: 10.4.5
typescript: 5.9.2 typescript: 5.9.2
consola@3.4.2: {}
content-disposition@1.0.0: content-disposition@1.0.0:
dependencies: dependencies:
safe-buffer: 5.2.1 safe-buffer: 5.2.1
@@ -8389,12 +8191,6 @@ snapshots:
locate-path: 6.0.0 locate-path: 6.0.0
path-exists: 4.0.0 path-exists: 4.0.0
fix-dts-default-cjs-exports@1.0.1:
dependencies:
magic-string: 0.30.17
mlly: 1.8.0
rollup: 4.41.1
flat-cache@3.1.1: flat-cache@3.1.1:
dependencies: dependencies:
flatted: 3.2.9 flatted: 3.2.9
@@ -8566,15 +8362,6 @@ snapshots:
package-json-from-dist: 1.0.1 package-json-from-dist: 1.0.1
path-scurry: 1.11.1 path-scurry: 1.11.1
glob@11.0.3:
dependencies:
foreground-child: 3.3.1
jackspeak: 4.1.1
minimatch: 10.0.3
minipass: 7.1.2
package-json-from-dist: 1.0.1
path-scurry: 2.0.0
glob@7.2.3: glob@7.2.3:
dependencies: dependencies:
fs.realpath: 1.0.0 fs.realpath: 1.0.0
@@ -8993,10 +8780,6 @@ snapshots:
optionalDependencies: optionalDependencies:
'@pkgjs/parseargs': 0.11.0 '@pkgjs/parseargs': 0.11.0
jackspeak@4.1.1:
dependencies:
'@isaacs/cliui': 8.0.2
jake@10.9.2: jake@10.9.2:
dependencies: dependencies:
async: 3.2.6 async: 3.2.6
@@ -9014,8 +8797,6 @@ snapshots:
'@sideway/formula': 3.0.1 '@sideway/formula': 3.0.1
'@sideway/pinpoint': 2.0.0 '@sideway/pinpoint': 2.0.0
joycon@3.1.1: {}
js-tokens@4.0.0: {} js-tokens@4.0.0: {}
js-yaml@4.1.0: js-yaml@4.1.0:
@@ -9118,12 +8899,8 @@ snapshots:
prelude-ls: 1.2.1 prelude-ls: 1.2.1
type-check: 0.4.0 type-check: 0.4.0
lilconfig@3.1.3: {}
lines-and-columns@1.2.4: {} lines-and-columns@1.2.4: {}
load-tsconfig@0.2.5: {}
locate-path@6.0.0: locate-path@6.0.0:
dependencies: dependencies:
p-locate: 5.0.0 p-locate: 5.0.0
@@ -9138,8 +8915,6 @@ snapshots:
lodash.merge@4.6.2: {} lodash.merge@4.6.2: {}
lodash.sortby@4.7.0: {}
lodash.union@4.6.0: {} lodash.union@4.6.0: {}
lodash@4.17.21: {} lodash@4.17.21: {}
@@ -9167,8 +8942,6 @@ snapshots:
lru-cache@10.4.3: {} lru-cache@10.4.3: {}
lru-cache@11.2.1: {}
lru-cache@5.1.1: lru-cache@5.1.1:
dependencies: dependencies:
yallist: 3.1.1 yallist: 3.1.1
@@ -9320,13 +9093,6 @@ snapshots:
mkdirp@1.0.4: {} mkdirp@1.0.4: {}
mlly@1.8.0:
dependencies:
acorn: 8.15.0
pathe: 2.0.3
pkg-types: 1.3.1
ufo: 1.6.1
ms@2.1.3: {} ms@2.1.3: {}
multer@2.0.1: multer@2.0.1:
@@ -9339,12 +9105,6 @@ snapshots:
type-is: 1.6.18 type-is: 1.6.18
xtend: 4.0.2 xtend: 4.0.2
mz@2.7.0:
dependencies:
any-promise: 1.3.0
object-assign: 4.1.1
thenify-all: 1.6.0
nanoid@3.3.11: {} nanoid@3.3.11: {}
nanoid@5.1.5: {} nanoid@5.1.5: {}
@@ -9528,11 +9288,6 @@ snapshots:
lru-cache: 10.4.3 lru-cache: 10.4.3
minipass: 7.1.2 minipass: 7.1.2
path-scurry@2.0.0:
dependencies:
lru-cache: 11.2.1
minipass: 7.1.2
path-to-regexp@8.2.0: {} path-to-regexp@8.2.0: {}
path-type@4.0.0: {} path-type@4.0.0: {}
@@ -9545,20 +9300,14 @@ snapshots:
pend@1.2.0: {} pend@1.2.0: {}
picocolors@1.0.1: {}
picocolors@1.1.1: {} picocolors@1.1.1: {}
picomatch@2.3.1: {} picomatch@2.3.1: {}
picomatch@4.0.2: {} picomatch@4.0.2: {}
pirates@4.0.7: {}
pkg-types@1.3.1:
dependencies:
confbox: 0.1.8
mlly: 1.8.0
pathe: 2.0.3
playwright-core@1.55.0: {} playwright-core@1.55.0: {}
playwright@1.55.0: playwright@1.55.0:
@@ -9575,14 +9324,6 @@ snapshots:
possible-typed-array-names@1.1.0: {} possible-typed-array-names@1.1.0: {}
postcss-load-config@6.0.1(jiti@2.4.2)(postcss@8.5.4)(tsx@4.20.5):
dependencies:
lilconfig: 3.1.3
optionalDependencies:
jiti: 2.4.2
postcss: 8.5.4
tsx: 4.20.5
postcss@8.5.4: postcss@8.5.4:
dependencies: dependencies:
nanoid: 3.3.11 nanoid: 3.3.11
@@ -9793,8 +9534,6 @@ snapshots:
resolve-from@4.0.0: {} resolve-from@4.0.0: {}
resolve-from@5.0.0: {}
resolve-pkg-maps@1.0.0: {} resolve-pkg-maps@1.0.0: {}
resolve@1.22.10: resolve@1.22.10:
@@ -9826,11 +9565,6 @@ snapshots:
dependencies: dependencies:
glob: 7.2.3 glob: 7.2.3
rimraf@6.0.1:
dependencies:
glob: 11.0.3
package-json-from-dist: 1.0.1
roarr@2.15.4: roarr@2.15.4:
dependencies: dependencies:
boolean: 3.2.0 boolean: 3.2.0
@@ -10098,10 +9832,6 @@ snapshots:
source-map@0.6.1: {} source-map@0.6.1: {}
source-map@0.8.0-beta.0:
dependencies:
whatwg-url: 7.1.0
sprintf-js@1.1.3: sprintf-js@1.1.3:
optional: true optional: true
@@ -10204,16 +9934,6 @@ snapshots:
strip-json-comments@3.1.1: {} strip-json-comments@3.1.1: {}
sucrase@3.35.0:
dependencies:
'@jridgewell/gen-mapping': 0.3.13
commander: 4.1.1
glob: 10.4.5
lines-and-columns: 1.2.4
mz: 2.7.0
pirates: 4.0.7
ts-interface-checker: 0.1.13
sumchecker@3.0.1: sumchecker@3.0.1:
dependencies: dependencies:
debug: 4.4.1 debug: 4.4.1
@@ -10268,14 +9988,6 @@ snapshots:
text-table@0.2.0: {} text-table@0.2.0: {}
thenify-all@1.6.0:
dependencies:
thenify: 3.3.1
thenify@3.3.1:
dependencies:
any-promise: 1.3.0
tiny-async-pool@1.3.0: tiny-async-pool@1.3.0:
dependencies: dependencies:
semver: 5.7.2 semver: 5.7.2
@@ -10321,17 +10033,11 @@ snapshots:
tr46@0.0.3: {} tr46@0.0.3: {}
tr46@1.0.1:
dependencies:
punycode: 2.3.1
tr46@3.0.0: tr46@3.0.0:
dependencies: dependencies:
punycode: 2.3.1 punycode: 2.3.1
optional: true optional: true
tree-kill@1.2.2: {}
truncate-utf8-bytes@1.0.2: truncate-utf8-bytes@1.0.2:
dependencies: dependencies:
utf8-byte-length: 1.0.4 utf8-byte-length: 1.0.4
@@ -10348,42 +10054,12 @@ snapshots:
optionalDependencies: optionalDependencies:
typescript: 5.5.3 typescript: 5.5.3
ts-interface-checker@0.1.13: {}
tsconfck@3.1.6(typescript@5.5.3): tsconfck@3.1.6(typescript@5.5.3):
optionalDependencies: optionalDependencies:
typescript: 5.5.3 typescript: 5.5.3
tslib@2.6.2: {} tslib@2.6.2: {}
tsup@8.5.0(jiti@2.4.2)(postcss@8.5.4)(tsx@4.20.5)(typescript@5.5.3):
dependencies:
bundle-require: 5.1.0(esbuild@0.25.5)
cac: 6.7.14
chokidar: 4.0.3
consola: 3.4.2
debug: 4.4.1
esbuild: 0.25.5
fix-dts-default-cjs-exports: 1.0.1
joycon: 3.1.1
picocolors: 1.1.1
postcss-load-config: 6.0.1(jiti@2.4.2)(postcss@8.5.4)(tsx@4.20.5)
resolve-from: 5.0.0
rollup: 4.41.1
source-map: 0.8.0-beta.0
sucrase: 3.35.0
tinyexec: 0.3.2
tinyglobby: 0.2.14
tree-kill: 1.2.2
optionalDependencies:
postcss: 8.5.4
typescript: 5.5.3
transitivePeerDependencies:
- jiti
- supports-color
- tsx
- yaml
tsx@4.20.5: tsx@4.20.5:
dependencies: dependencies:
esbuild: 0.25.5 esbuild: 0.25.5
@@ -10477,8 +10153,6 @@ snapshots:
typescript@5.9.2: {} typescript@5.9.2: {}
ufo@1.6.1: {}
unbox-primitive@1.1.0: unbox-primitive@1.1.0:
dependencies: dependencies:
call-bound: 1.0.4 call-bound: 1.0.4
@@ -10516,7 +10190,7 @@ snapshots:
dependencies: dependencies:
browserslist: 4.22.2 browserslist: 4.22.2
escalade: 3.1.1 escalade: 3.1.1
picocolors: 1.1.1 picocolors: 1.0.1
update-browserslist-db@1.1.3(browserslist@4.25.0): update-browserslist-db@1.1.3(browserslist@4.25.0):
dependencies: dependencies:
@@ -10689,8 +10363,6 @@ snapshots:
webidl-conversions@3.0.1: {} webidl-conversions@3.0.1: {}
webidl-conversions@4.0.2: {}
webidl-conversions@7.0.0: webidl-conversions@7.0.0:
optional: true optional: true
@@ -10717,12 +10389,6 @@ snapshots:
tr46: 0.0.3 tr46: 0.0.3
webidl-conversions: 3.0.1 webidl-conversions: 3.0.1
whatwg-url@7.1.0:
dependencies:
lodash.sortby: 4.7.0
tr46: 1.0.1
webidl-conversions: 4.0.2
which-boxed-primitive@1.1.1: which-boxed-primitive@1.1.1:
dependencies: dependencies:
is-bigint: 1.1.0 is-bigint: 1.1.0
-2
View File
@@ -11,8 +11,6 @@ catalog:
eslint-config-prettier: 9.1.0 eslint-config-prettier: 9.1.0
eslint-plugin-prettier: 5.1.3 eslint-plugin-prettier: 5.1.3
vitest: 3.2.1 vitest: 3.2.1
rimraf: 6.0.1
ts-essentials: 10.1.1
onlyBuiltDependencies: onlyBuiltDependencies:
- electron - electron
- esbuild - esbuild
-16
View File
@@ -1,16 +0,0 @@
{
"$schema": "https://json.schemastore.org/tsconfig",
"compilerOptions": {
"strict": true,
"skipLibCheck": true,
"allowSyntheticDefaultImports": true,
"resolveJsonModule": true,
"forceConsistentCasingInFileNames": true,
"isolatedModules": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"allowUnreachableCode": false
}
}
-3
View File
@@ -26,9 +26,6 @@
"cache": false "cache": false
}, },
"build": { "build": {
"dependsOn": [
"^build"
],
"env": ["SENTRY_AUTH_TOKEN"] "env": ["SENTRY_AUTH_TOKEN"]
}, },
"build:local": {}, "build:local": {},