mirror of
https://github.com/cpvalente/ontime.git
synced 2026-08-04 06:58:02 +00:00
Compare commits
25 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 00aa8a09fa | |||
| d04f958c11 | |||
| 5716fc31cf | |||
| a5d628af0c | |||
| 111a1f95ba | |||
| 4bf74d86b3 | |||
| d2638d64e4 | |||
| 02cddeec4d | |||
| a8fe8b5f1e | |||
| 8d2db7ffcd | |||
| 313590905f | |||
| 7bbdab7dd9 | |||
| 304832ecde | |||
| 0ab4abfcfe | |||
| 1847822f31 | |||
| 4499495212 | |||
| 0035070db8 | |||
| 890a97d071 | |||
| 5441e5b07f | |||
| ab4b94f2d8 | |||
| fd7ccb4a5b | |||
| 2e6bf234d5 | |||
| c934b0ecdd | |||
| cd6d6f24eb | |||
| e632a08557 |
+2
-1
@@ -16,7 +16,8 @@
|
||||
|
||||
# Ignore build folders
|
||||
node_modules
|
||||
dist
|
||||
**/node_modules
|
||||
**/dist
|
||||
|
||||
# Ignore default volumes created by running docker compose up
|
||||
ontime-db
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
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
|
||||
@@ -99,3 +99,16 @@ Other useful commands
|
||||
|
||||
- __List running processes__ by running `docker ps`
|
||||
- __Kill running process__ by running `docker kill <process-id>`
|
||||
|
||||
## CONTRIBUTION GUIDELINES
|
||||
|
||||
If you want to propose changes to the codebase, please reach out before opening a Pull Request.
|
||||
|
||||
For new PRs, please follow the following checklist:
|
||||
* [ ] You have updated and ran unit locally and they are passing. Unit tests are generally created for all utility functions and business logic
|
||||
* [ ] You have ran code formatting and linting in all your changes
|
||||
* [ ] The branch is clean and the commits are meaningfully separated and contain descriptive messages
|
||||
* [ ] The PR body contains description and motivation for the changes
|
||||
|
||||
After this checklist is complete, you can request a review from one of the maintainers to get feedback and approval on the changes. \
|
||||
We will review as soon as possible
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@getontime/cli",
|
||||
"version": "4.0.0-beta.2",
|
||||
"version": "4.0.0-beta.3",
|
||||
"author": "Carlos Valente",
|
||||
"description": "Time keeping for live events",
|
||||
"repository": "https://github.com/cpvalente/ontime",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "ontime-ui",
|
||||
"version": "4.0.0-beta.2",
|
||||
"version": "4.0.0-beta.3",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
|
||||
@@ -11,9 +11,11 @@
|
||||
}
|
||||
|
||||
.pin {
|
||||
margin-top: 0.5rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 4px;
|
||||
|
||||
input {
|
||||
font-size: 4rem;
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
bottom: 0;
|
||||
|
||||
width: 40rem;
|
||||
max-width: 100vw;
|
||||
height: 100vh;
|
||||
|
||||
display: flex;
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { RefObject, useCallback, useEffect, useRef } from 'react';
|
||||
|
||||
import { useSelectedEventId } from './useSocket';
|
||||
import { RefObject, useCallback, useEffect } from 'react';
|
||||
import { MaybeString } from 'ontime-types';
|
||||
|
||||
function scrollToComponent<ComponentRef extends HTMLElement, ScrollRef extends HTMLElement>(
|
||||
componentRef: RefObject<ComponentRef>,
|
||||
@@ -18,37 +17,26 @@ function scrollToComponent<ComponentRef extends HTMLElement, ScrollRef extends H
|
||||
scrollRef.current.scrollTo({ top, behavior: 'smooth' });
|
||||
}
|
||||
|
||||
function snapToComponent<ComponentRef extends HTMLElement, ScrollRef extends HTMLElement>(
|
||||
componentRef: RefObject<ComponentRef>,
|
||||
scrollRef: RefObject<ScrollRef>,
|
||||
topOffset: number,
|
||||
) {
|
||||
if (!componentRef.current || !scrollRef.current) {
|
||||
return;
|
||||
}
|
||||
|
||||
const componentRect = componentRef.current.getBoundingClientRect();
|
||||
const scrollRect = scrollRef.current.getBoundingClientRect();
|
||||
const top = componentRect.top - scrollRect.top + scrollRef.current.scrollTop - topOffset;
|
||||
|
||||
// maintain current x scroll position
|
||||
scrollRef.current.scrollTo(scrollRef.current.scrollLeft, top);
|
||||
}
|
||||
|
||||
interface UseFollowComponentProps {
|
||||
followRef: RefObject<HTMLElement | null>;
|
||||
scrollRef: RefObject<HTMLElement | null>;
|
||||
doFollow: boolean;
|
||||
topOffset?: number;
|
||||
setScrollFlag?: (newValue: boolean) => void;
|
||||
followTrigger?: MaybeString; // this would be an entry id or null
|
||||
}
|
||||
|
||||
export default function useFollowComponent(props: UseFollowComponentProps) {
|
||||
const { followRef, scrollRef, doFollow, topOffset = 100, setScrollFlag } = props;
|
||||
|
||||
// when cursor moves, view should follow
|
||||
export default function useFollowComponent({
|
||||
followRef,
|
||||
scrollRef,
|
||||
doFollow,
|
||||
topOffset = 100,
|
||||
setScrollFlag,
|
||||
followTrigger,
|
||||
}: UseFollowComponentProps) {
|
||||
// when trigger moves, view should follow
|
||||
useEffect(() => {
|
||||
if (!doFollow) {
|
||||
if (!doFollow || !followTrigger) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -60,16 +48,14 @@ export default function useFollowComponent(props: UseFollowComponentProps) {
|
||||
setScrollFlag?.(false);
|
||||
});
|
||||
}
|
||||
|
||||
// eslint-disable-next-line -- the prompt seems incorrect
|
||||
}, [followRef?.current, scrollRef?.current]);
|
||||
}, [followTrigger, doFollow, followRef, scrollRef, setScrollFlag, topOffset]);
|
||||
|
||||
const scrollToRefComponent = useCallback(
|
||||
(componentRef = followRef, containerRef = scrollRef, offset = topOffset) => {
|
||||
if (componentRef.current && containerRef.current) {
|
||||
if (componentRef && containerRef) {
|
||||
// @ts-expect-error -- we know this are not null
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
scrollToComponent(componentRef!, scrollRef!, offset);
|
||||
scrollToComponent(componentRef!, containerRef!, offset);
|
||||
}
|
||||
},
|
||||
[followRef, scrollRef, topOffset],
|
||||
@@ -77,32 +63,3 @@ export default function useFollowComponent(props: UseFollowComponentProps) {
|
||||
|
||||
return scrollToRefComponent;
|
||||
}
|
||||
|
||||
export function useFollowSelected(doFollow: boolean, topOffset = 100) {
|
||||
const selectedEvenId = useSelectedEventId();
|
||||
|
||||
const selectedRef = useRef<HTMLTableRowElement>(null);
|
||||
const scrollRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (!doFollow) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (selectedEvenId && selectedRef.current && scrollRef.current) {
|
||||
// Use requestAnimationFrame to ensure the component is fully loaded
|
||||
window.requestAnimationFrame(() => {
|
||||
snapToComponent(
|
||||
{ current: selectedRef.current } as RefObject<HTMLElement>,
|
||||
{ current: scrollRef.current } as RefObject<HTMLElement>,
|
||||
topOffset,
|
||||
);
|
||||
});
|
||||
}
|
||||
}, [doFollow, selectedEvenId, topOffset]);
|
||||
|
||||
return {
|
||||
selectedRef,
|
||||
scrollRef,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -11,7 +11,6 @@ const createSelector =
|
||||
export const setClientRemote = {
|
||||
setIdentify: (payload: { target: string; identify: boolean }) => sendSocket('client', payload),
|
||||
setRedirect: (payload: { target: string; redirect: string }) => {
|
||||
console.log('--- got', payload);
|
||||
sendSocket('client', payload);
|
||||
},
|
||||
setClientName: (payload: { target: string; rename: string }) => sendSocket('client', payload),
|
||||
@@ -165,24 +164,6 @@ export const useProgressData = createSelector((state: RuntimeStore) => ({
|
||||
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) => ({
|
||||
clock: state.clock,
|
||||
offset: state.offset.absolute,
|
||||
@@ -191,7 +172,7 @@ export const useTimelineStatus = createSelector((state: RuntimeStore) => ({
|
||||
export const useExpectedStartData = createSelector((state: RuntimeStore) => ({
|
||||
offset: state.offset.mode === OffsetMode.Absolute ? state.offset.absolute : state.offset.relative,
|
||||
mode: state.offset.mode,
|
||||
currentDay: state.eventNow?.dayOffset ?? 0,
|
||||
currentDay: state.rundown.currentDay ?? 0,
|
||||
actualStart: state.rundown.actualStart,
|
||||
plannedStart: state.rundown.plannedStart,
|
||||
clock: state.clock,
|
||||
@@ -228,6 +209,45 @@ export const usePlayback = () => {
|
||||
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 ======================= */
|
||||
|
||||
export const useTimerSocket = createSelector((state: RuntimeStore) => ({
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
export type OverridableOptions = {
|
||||
keyColour?: string;
|
||||
textColour?: string;
|
||||
textBackground?: string;
|
||||
font?: string;
|
||||
size?: number;
|
||||
justifyContent?: 'start' | 'center' | 'end';
|
||||
alignItems?: 'start' | 'center' | 'end';
|
||||
left?: string;
|
||||
top?: string;
|
||||
hideNav?: boolean;
|
||||
hideOvertime?: boolean;
|
||||
hideMessagesOverlay?: boolean;
|
||||
hideEndMessage?: boolean;
|
||||
language?: string;
|
||||
showProgressBar?: boolean;
|
||||
hideTimerSeconds?: boolean;
|
||||
removeLeadingZeros?: boolean;
|
||||
};
|
||||
@@ -1,5 +1,5 @@
|
||||
import {
|
||||
ApiAction,
|
||||
ApiActionTag,
|
||||
Log,
|
||||
MessageTag,
|
||||
RefetchKey,
|
||||
@@ -199,7 +199,7 @@ export const connectSocket = () => {
|
||||
};
|
||||
};
|
||||
|
||||
export function sendSocket<T extends MessageTag | ApiAction>(
|
||||
export function sendSocket<T extends MessageTag | ApiActionTag>(
|
||||
tag: T,
|
||||
payload: T extends MessageTag ? Pick<WsPacketToServer & { tag: T }, 'payload'>['payload'] : unknown,
|
||||
): void {
|
||||
|
||||
@@ -49,6 +49,7 @@ export default function Operator() {
|
||||
scrollRef,
|
||||
doFollow: !lockAutoScroll,
|
||||
topOffset: selectedOffset,
|
||||
followTrigger: selectedEventId,
|
||||
});
|
||||
|
||||
useWindowTitle('Operator');
|
||||
@@ -181,7 +182,7 @@ export default function Operator() {
|
||||
const { isPast, isSelected, isLinkedToLoaded, totalGap } = process(nestedEntry);
|
||||
|
||||
// hide past events (if setting) and skipped events
|
||||
if (hidePast && isPast) {
|
||||
if ((hidePast && isPast) || nestedEntry.skip) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@@ -47,9 +47,10 @@
|
||||
|
||||
.daySpan {
|
||||
&::after {
|
||||
content: '*';
|
||||
content: "+"attr(data-day-offset);
|
||||
vertical-align: super;
|
||||
font-size: 0.75em;
|
||||
font-size: 0.6em;
|
||||
letter-spacing: 0;
|
||||
color: $info-blue;
|
||||
}
|
||||
}
|
||||
@@ -67,3 +68,10 @@
|
||||
font-size: calc(1rem - 2px);
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.dueTime {
|
||||
text-transform: capitalize;
|
||||
font-size: 1rem;
|
||||
letter-spacing: 0;
|
||||
color: $playback-over;
|
||||
}
|
||||
|
||||
@@ -8,23 +8,26 @@ import {
|
||||
TbFolderPin,
|
||||
TbFolderStar,
|
||||
} from 'react-icons/tb';
|
||||
import { OntimeEvent, OntimeGroup, TimerPhase, TimerType } from 'ontime-types';
|
||||
import { isPlaybackActive, millisToString } from 'ontime-utils';
|
||||
import { OffsetMode, OntimeEvent, OntimeGroup, TimerPhase, TimerType } from 'ontime-types';
|
||||
import { dayInMs, isPlaybackActive, millisToString } from 'ontime-utils';
|
||||
|
||||
import Tooltip from '../../../common/components/tooltip/Tooltip';
|
||||
import {
|
||||
useClock,
|
||||
useCurrentGroupId,
|
||||
useFlagTimerOverView,
|
||||
useGroupTimerOverView,
|
||||
useNextFlag,
|
||||
useOffsetOverview,
|
||||
useProgressOverview,
|
||||
useRundownOverview,
|
||||
useRuntimePlaybackOverview,
|
||||
useTimer,
|
||||
} from '../../../common/hooks/useSocket';
|
||||
import { useEntry } from '../../../common/hooks-query/useRundown';
|
||||
import { getOffsetState, getOffsetText } from '../../../common/utils/offset';
|
||||
import { cx, enDash, timerPlaceholder } from '../../../common/utils/styleUtils';
|
||||
import { formatTime } from '../../../common/utils/time';
|
||||
import { calculateEndAndDaySpan, formattedTime } from '../overview.utils';
|
||||
import { calculateEndAndDaySpan, formatDueTime, formattedTime } from '../overview.utils';
|
||||
|
||||
import { OverUnder, TimeColumn } from './TimeLayout';
|
||||
|
||||
@@ -58,8 +61,8 @@ export function StartTimes() {
|
||||
<Tooltip text='Planned end time' render={<TbCalendarPin className={style.icon} />} />
|
||||
{maybePlannedDaySpan > 0 ? (
|
||||
<Tooltip
|
||||
text={`Event spans over ${maybePlannedDaySpan + 1} days`}
|
||||
render={<span className={cx([style.time, style.daySpan])} />}
|
||||
text={`Rundown spans over ${maybePlannedDaySpan + 1} days`}
|
||||
render={<span className={cx([style.time, style.daySpan])} data-day-offset={maybePlannedDaySpan} />}
|
||||
>
|
||||
{plannedEndText}
|
||||
</Tooltip>
|
||||
@@ -71,8 +74,8 @@ export function StartTimes() {
|
||||
<Tooltip text='Expected end time' render={<TbCalendarStar className={style.icon} />} />
|
||||
{maybeExpectedEnd !== null && maybeExpectedDaySpan > 0 ? (
|
||||
<Tooltip
|
||||
text={`Event spans over ${maybeExpectedDaySpan + 1} days`}
|
||||
render={<span className={cx([style.time, style.daySpan])} />}
|
||||
text={`Rundown spans over ${maybeExpectedDaySpan + 1} days`}
|
||||
render={<span className={cx([style.time, style.daySpan])} data-day-offset={maybeExpectedDaySpan} />}
|
||||
>
|
||||
{formattedTime(maybeExpectedEnd)}
|
||||
</Tooltip>
|
||||
@@ -96,61 +99,110 @@ 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() {
|
||||
const { clock, groupExpectedEnd } = useRuntimePlaybackOverview();
|
||||
const { clock, mode, groupExpectedEnd, actualGroupStart, currentDay, playback } = useGroupTimerOverView();
|
||||
const { currentGroupId } = useCurrentGroupId();
|
||||
const group = useEntry(currentGroupId) as OntimeGroup | null;
|
||||
|
||||
// the group end time dose not encode any day offsets
|
||||
const plannedGroupEnd = group && group.timeStart !== null ? group.timeStart + group.duration - clock : null;
|
||||
const plannedTimeUntilGroupEnd = formattedTime(plannedGroupEnd, 3, TimerType.CountDown);
|
||||
const active = isPlaybackActive(playback);
|
||||
|
||||
// the group end time dose not encode any day offsets so it is calculated with group start time and duration
|
||||
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 expectedTimeUntilGroupEnd = formattedTime(expectedGroupEnd, 3, TimerType.CountDown);
|
||||
|
||||
const groupTitle = group?.title ?? null;
|
||||
const expectedTimeUntilGroupEnd = formatDueTime(expectedGroupEnd, 3, TimerType.CountDown);
|
||||
|
||||
return (
|
||||
<div className={style.metadataRow}>
|
||||
<span className={groupTitle ? style.labelTitle : style.label}>{`${groupTitle ? groupTitle : 'Group'} `}</span>
|
||||
<span className={group?.title ? style.labelTitle : style.label}>{`${group?.title || 'Group'} `}</span>
|
||||
<div className={style.labelledElement}>
|
||||
<Tooltip text='Time to planned group end' render={<TbFolderPin className={style.icon} />} />
|
||||
<span className={cx([style.time, !group && style.muted])}>{plannedTimeUntilGroupEnd}</span>
|
||||
<span
|
||||
className={cx([
|
||||
style.time,
|
||||
(!group || !active) && style.muted,
|
||||
plannedTimeUntilGroupEnd === 'due' && style.dueTime,
|
||||
])}
|
||||
>
|
||||
{plannedTimeUntilGroupEnd}
|
||||
</span>
|
||||
</div>
|
||||
<div className={style.labelledElement}>
|
||||
<Tooltip text='Time to expected group end' render={<TbFolderStar className={style.icon} />} />
|
||||
<span className={cx([style.time, groupExpectedEnd === null && style.muted])}>{expectedTimeUntilGroupEnd}</span>
|
||||
<span
|
||||
className={cx([
|
||||
style.time,
|
||||
!groupExpectedEnd && style.muted,
|
||||
expectedTimeUntilGroupEnd === 'due' && style.dueTime,
|
||||
])}
|
||||
>
|
||||
{expectedTimeUntilGroupEnd}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function FlagTimes() {
|
||||
const { clock } = useClock();
|
||||
const { clock, mode, actualStart, plannedStart, playback, currentDay } = useFlagTimerOverView();
|
||||
const { id, expectedStart } = useNextFlag();
|
||||
const entry = useEntry(id) as OntimeEvent | null;
|
||||
|
||||
const plannedFlagStart = entry ? entry.timeStart - clock : null;
|
||||
const plannedTimeUntilDisplay = formattedTime(plannedFlagStart, 3, TimerType.CountDown);
|
||||
const active = isPlaybackActive(playback);
|
||||
|
||||
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 expectedTimeUntilDisplay = formattedTime(expectedTimeUntil, 3, TimerType.CountDown);
|
||||
const expectedTimeUntilDisplay = formatDueTime(expectedTimeUntil, 3, TimerType.CountDown);
|
||||
|
||||
const title = entry?.title ?? null;
|
||||
|
||||
return (
|
||||
<div className={style.metadataRow}>
|
||||
<span className={title ? style.labelTitle : style.label}>{`${title ? title : 'Flag'} `}</span>
|
||||
<span className={title ? style.labelTitle : style.label}>{`${title || 'Flag'} `}</span>
|
||||
<div className={style.labelledElement}>
|
||||
<Tooltip text='Time to next flag planned start' render={<TbFlagPin className={style.icon} />} />
|
||||
<span data-testid='flag-plannedStart' className={cx([style.time, !entry && style.muted])}>
|
||||
<span
|
||||
data-testid='flag-plannedStart'
|
||||
className={cx([
|
||||
style.time,
|
||||
(!entry || !active) && style.muted,
|
||||
plannedTimeUntilDisplay === 'due' && style.dueTime,
|
||||
])}
|
||||
>
|
||||
{plannedTimeUntilDisplay}
|
||||
</span>
|
||||
</div>
|
||||
<div className={style.labelledElement}>
|
||||
<Tooltip text='Time to next flag expected start' render={<TbFlagStar className={style.icon} />} />
|
||||
<span data-testid='flag-expectedStart' className={cx([style.time, expectedTimeUntil === null && style.muted])}>
|
||||
<span
|
||||
data-testid='flag-expectedStart'
|
||||
className={cx([
|
||||
style.time,
|
||||
expectedTimeUntil === null && style.muted,
|
||||
expectedTimeUntilDisplay === 'due' && style.dueTime,
|
||||
])}
|
||||
>
|
||||
{expectedTimeUntilDisplay}
|
||||
</span>
|
||||
</div>
|
||||
@@ -159,7 +211,7 @@ function FlagTimes() {
|
||||
}
|
||||
|
||||
export function ProgressOverview() {
|
||||
const { numEvents, selectedEventIndex } = useRuntimePlaybackOverview();
|
||||
const { numEvents, selectedEventIndex } = useProgressOverview();
|
||||
|
||||
const current = selectedEventIndex !== null ? selectedEventIndex + 1 : enDash;
|
||||
const progressText = numEvents ? `${current} of ${numEvents || enDash}` : enDash;
|
||||
@@ -168,7 +220,7 @@ export function ProgressOverview() {
|
||||
}
|
||||
|
||||
export function OffsetOverview() {
|
||||
const { offset, playback } = useRuntimePlaybackOverview();
|
||||
const { offset, playback } = useOffsetOverview();
|
||||
|
||||
const isPlaying = isPlaybackActive(playback);
|
||||
const offsetState = getOffsetState(isPlaying ? offset : null);
|
||||
|
||||
@@ -3,6 +3,23 @@ import { dayInMs, millisToString } from 'ontime-utils';
|
||||
|
||||
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
|
||||
*/
|
||||
|
||||
@@ -82,7 +82,12 @@ export default function Rundown({ data, rundownMetadata }: RundownProps) {
|
||||
|
||||
const cursorRef = useRef<HTMLDivElement | null>(null);
|
||||
const scrollRef = useRef<HTMLDivElement | null>(null);
|
||||
useFollowComponent({ followRef: cursorRef, scrollRef, doFollow: editorMode === AppMode.Run });
|
||||
useFollowComponent({
|
||||
followRef: cursorRef,
|
||||
scrollRef,
|
||||
doFollow: true,
|
||||
followTrigger: editorMode === AppMode.Edit ? cursor : featureData?.selectedEventId,
|
||||
});
|
||||
|
||||
// DND KIT
|
||||
const sensors = useSensors(useSensor(PointerSensor, { activationConstraint: { distance: 10 } }));
|
||||
@@ -312,7 +317,7 @@ export default function Rundown({ data, rundownMetadata }: RundownProps) {
|
||||
setMetadata(rundownMetadata);
|
||||
}, [order, entries, rundownMetadata]);
|
||||
|
||||
// in run mode, we follow selection
|
||||
// in run mode, we follow the playback selection and open groups as needed
|
||||
useEffect(() => {
|
||||
if (editorMode !== AppMode.Run || !featureData?.selectedEventId) {
|
||||
return;
|
||||
|
||||
@@ -28,20 +28,22 @@
|
||||
padding-inline: 0;
|
||||
box-shadow: $box-shadow-right;
|
||||
|
||||
flex: 1 2 auto; /* flex-grow: 1, flex-shrink: 2, flex-basis: auto */
|
||||
flex: 1 1 0; /* flex-grow: 1, flex-shrink: 1, flex-basis: 0 */
|
||||
min-width: 38rem;
|
||||
max-width: 60rem;
|
||||
max-width: none;
|
||||
width: 0;
|
||||
}
|
||||
|
||||
.side {
|
||||
max-height: 100%;
|
||||
max-width: 45rem;
|
||||
margin: 0.5rem 0;
|
||||
padding: 1rem;
|
||||
padding-right: 0;
|
||||
background-color: $gray-1325;
|
||||
border-radius: 0 8px 8px 0;
|
||||
|
||||
flex: 1 1 auto; /* flex-grow: 1, flex-shrink: 1, flex-basis: auto */
|
||||
max-width: 45rem;
|
||||
flex: 1 1 0; /* flex-grow: 1, flex-shrink: 1, flex-basis: 0 */
|
||||
// width is locked to swatch picker elements
|
||||
min-width: calc(15 * 2rem + 13 * 0.5rem);
|
||||
width: 0;
|
||||
}
|
||||
|
||||
@@ -73,9 +73,7 @@ interface EventUntilProps {
|
||||
isLinkedToLoaded: boolean;
|
||||
}
|
||||
|
||||
function EventUntil(props: EventUntilProps) {
|
||||
const { timeStart, delay, dayOffset, totalGap, isLinkedToLoaded } = props;
|
||||
|
||||
function EventUntil({ timeStart, delay, dayOffset, totalGap, isLinkedToLoaded }: EventUntilProps) {
|
||||
const timeUntil = useTimeUntilExpectedStart({ timeStart, delay, dayOffset }, { totalGap, isLinkedToLoaded });
|
||||
const isDue = timeUntil < MILLIS_PER_SECOND;
|
||||
|
||||
|
||||
@@ -41,9 +41,9 @@ export function canDrop(
|
||||
order?: 'after' | 'before',
|
||||
isTargetCollapsed?: boolean,
|
||||
): boolean {
|
||||
// this would mean inserting a group inside another
|
||||
// inserting before would mean adding a group inside another
|
||||
if (targetType === 'end-group') {
|
||||
return false;
|
||||
return order === 'after';
|
||||
}
|
||||
|
||||
// this means swapping places with another group
|
||||
|
||||
@@ -17,7 +17,6 @@ $header-font-size: clamp(24px, 2.5vw, 48px);
|
||||
|
||||
// General styling
|
||||
$accent-color: $red-500; // --accent-color-override
|
||||
$delay-color: $ontime-delay-text;
|
||||
$viewer-label-color: rgba(white, 25%);
|
||||
|
||||
// Main Properties of a viewer
|
||||
|
||||
@@ -10,13 +10,14 @@ import {
|
||||
makeProjectDataOptions,
|
||||
} from '../../common/components/view-params-editor/viewParams.utils';
|
||||
import { PresetContext } from '../../common/context/PresetContext';
|
||||
import { scheduleOptions } from '../common/schedule/schedule.options';
|
||||
import { getScheduleOptions } from '../common/schedule/schedule.options';
|
||||
|
||||
export const getBackstageOptions = (
|
||||
timeFormat: string,
|
||||
customFields: CustomFields,
|
||||
projectData: ProjectData,
|
||||
): ViewOption[] => {
|
||||
const customFieldOptions = makeOptionsFromCustomFields(customFields, []);
|
||||
const secondaryOptions = makeOptionsFromCustomFields(customFields, [
|
||||
{ value: 'none', label: 'None' },
|
||||
{ value: 'note', label: 'Note' },
|
||||
@@ -39,7 +40,7 @@ export const getBackstageOptions = (
|
||||
},
|
||||
],
|
||||
},
|
||||
scheduleOptions,
|
||||
getScheduleOptions(customFieldOptions),
|
||||
{
|
||||
title: OptionTitle.ElementVisibility,
|
||||
collapsible: true,
|
||||
|
||||
@@ -1,14 +1,5 @@
|
||||
import {
|
||||
createContext,
|
||||
PropsWithChildren,
|
||||
RefObject,
|
||||
useContext,
|
||||
useEffect,
|
||||
useLayoutEffect,
|
||||
useRef,
|
||||
useState,
|
||||
} from 'react';
|
||||
import { isOntimeEvent, OntimeEntry, OntimeEvent } from 'ontime-types';
|
||||
import { createContext, PropsWithChildren, RefObject, use, useEffect, useLayoutEffect, useRef, useState } from 'react';
|
||||
import { EntryId, isOntimeEvent, OntimeEntry, OntimeEvent } from 'ontime-types';
|
||||
|
||||
import { usePartialRundown } from '../../../common/hooks-query/useRundown';
|
||||
|
||||
@@ -25,13 +16,18 @@ interface ScheduleContextState {
|
||||
const ScheduleContext = createContext<ScheduleContextState | undefined>(undefined);
|
||||
|
||||
interface ScheduleProviderProps {
|
||||
selectedEventId: string | null;
|
||||
selectedEventId: EntryId | null;
|
||||
}
|
||||
|
||||
export const ScheduleProvider = ({ children, selectedEventId }: PropsWithChildren<ScheduleProviderProps>) => {
|
||||
const { cycleInterval, stopCycle } = useScheduleOptions();
|
||||
const { data: events } = usePartialRundown((event: OntimeEntry) => {
|
||||
return isOntimeEvent(event);
|
||||
const { cycleInterval, stopCycle, filter } = useScheduleOptions();
|
||||
const { data: events } = usePartialRundown((entry: OntimeEntry) => {
|
||||
if (filter) {
|
||||
// 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);
|
||||
@@ -135,7 +131,7 @@ export const ScheduleProvider = ({ children, selectedEventId }: PropsWithChildre
|
||||
selectedEventIndex = 0;
|
||||
|
||||
return (
|
||||
<ScheduleContext.Provider
|
||||
<ScheduleContext
|
||||
value={{
|
||||
events: viewEvents as OntimeEvent[],
|
||||
selectedEventId,
|
||||
@@ -145,12 +141,12 @@ export const ScheduleProvider = ({ children, selectedEventId }: PropsWithChildre
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</ScheduleContext.Provider>
|
||||
</ScheduleContext>
|
||||
);
|
||||
};
|
||||
|
||||
export const useSchedule = () => {
|
||||
const context = useContext(ScheduleContext);
|
||||
const context = use(ScheduleContext);
|
||||
if (!context) {
|
||||
throw new Error('useSchedule() can only be used inside a ScheduleContext');
|
||||
}
|
||||
|
||||
@@ -22,8 +22,7 @@ interface ScheduleItemProps {
|
||||
delay: number;
|
||||
}
|
||||
|
||||
export default function ScheduleItem(props: ScheduleItemProps) {
|
||||
const { timeStart, timeEnd, title, colour, skip, delay } = props;
|
||||
export default function ScheduleItem({ timeStart, timeEnd, title, colour, skip, delay }: ScheduleItemProps) {
|
||||
const { showExpected } = useScheduleOptions();
|
||||
|
||||
if (showExpected) {
|
||||
@@ -67,9 +66,7 @@ export default function ScheduleItem(props: ScheduleItemProps) {
|
||||
);
|
||||
}
|
||||
|
||||
function DelayedScheduleItem(props: ScheduleItemProps) {
|
||||
const { timeStart, timeEnd, title, colour, skip, delay } = props;
|
||||
|
||||
function DelayedScheduleItem({ timeStart, timeEnd, title, colour, skip, delay }: ScheduleItemProps) {
|
||||
const start = formatTime(timeStart, formatOptions);
|
||||
const end = formatTime(timeEnd, formatOptions);
|
||||
const delayedStart = formatTime(timeStart + delay, formatOptions);
|
||||
@@ -95,9 +92,7 @@ function DelayedScheduleItem(props: ScheduleItemProps) {
|
||||
);
|
||||
}
|
||||
|
||||
function ExpectedScheduleItem(props: ScheduleItemProps) {
|
||||
const { timeStart, timeEnd, title, colour, skip, delay } = props;
|
||||
|
||||
function ExpectedScheduleItem({ timeStart, timeEnd, title, colour, skip, delay }: ScheduleItemProps) {
|
||||
return (
|
||||
<li className={cx(['entry', skip && 'entry--skip'])}>
|
||||
<div className='entry-times'>
|
||||
@@ -116,12 +111,11 @@ interface ExpectedTimeProps {
|
||||
delay: number;
|
||||
}
|
||||
|
||||
function ExpectedTime(props: ExpectedTimeProps) {
|
||||
const { time, delay } = props;
|
||||
function ExpectedTime({ time, delay }: ExpectedTimeProps) {
|
||||
const { offset } = useRuntimeOffset();
|
||||
|
||||
const expectedOffset = offset - delay;
|
||||
const expectedTime = formatTime(time - offset, formatOptions);
|
||||
const expectedTime = formatTime(time + offset, formatOptions);
|
||||
const expectedState = getOffsetState(expectedOffset);
|
||||
|
||||
return <SuperscriptTime className={`entry-times--${expectedState}`} time={expectedTime} />;
|
||||
|
||||
@@ -1,14 +1,23 @@
|
||||
import { useMemo } from 'react';
|
||||
import { useSearchParams } from 'react-router';
|
||||
|
||||
import { SelectOption } from '../../../common/components/select/Select';
|
||||
import { OptionTitle } from '../../../common/components/view-params-editor/constants';
|
||||
import { ViewOption } from '../../../common/components/view-params-editor/viewParams.types';
|
||||
import type { ViewOption } from '../../../common/components/view-params-editor/viewParams.types';
|
||||
import { isStringBoolean } from '../../../features/viewers/common/viewUtils';
|
||||
|
||||
export const scheduleOptions: ViewOption = {
|
||||
export const getScheduleOptions = (customFieldOptions: SelectOption[]): ViewOption => ({
|
||||
title: OptionTitle.Schedule,
|
||||
collapsible: true,
|
||||
options: [
|
||||
{
|
||||
id: 'filter',
|
||||
title: 'Filter',
|
||||
description: 'Hide events without data in the selected custom field',
|
||||
type: 'option',
|
||||
values: customFieldOptions,
|
||||
defaultValue: 'None',
|
||||
},
|
||||
{
|
||||
id: 'stopCycle',
|
||||
title: 'Stop cycling through event pages',
|
||||
@@ -31,9 +40,10 @@ export const scheduleOptions: ViewOption = {
|
||||
defaultValue: false,
|
||||
},
|
||||
],
|
||||
};
|
||||
});
|
||||
|
||||
type ScheduleOptions = {
|
||||
filter: string | null;
|
||||
cycleInterval: number;
|
||||
stopCycle: boolean;
|
||||
showExpected: boolean;
|
||||
@@ -41,6 +51,7 @@ type ScheduleOptions = {
|
||||
|
||||
function getScheduleOptionsFromParams(searchParams: URLSearchParams): ScheduleOptions {
|
||||
return {
|
||||
filter: searchParams.get('filter'),
|
||||
cycleInterval: Number(searchParams.get('cycleInterval')) || 10,
|
||||
stopCycle: isStringBoolean(searchParams.get('stopCycle')),
|
||||
showExpected: isStringBoolean(searchParams.get('showExpected')),
|
||||
|
||||
@@ -141,7 +141,7 @@ $item-height: 3.5rem;
|
||||
}
|
||||
|
||||
.sub__schedule--delayed {
|
||||
color: $delay-color;
|
||||
color: $ontime-delay-text;
|
||||
}
|
||||
|
||||
.sub__schedule--strike {
|
||||
|
||||
@@ -53,6 +53,7 @@ export default function CountdownSubscriptions({ subscribedEvents, goToEditMode
|
||||
scrollRef,
|
||||
doFollow: !lockAutoScroll,
|
||||
topOffset: 0,
|
||||
followTrigger: selectedEventId,
|
||||
});
|
||||
|
||||
// reset scroll if nothing is selected
|
||||
|
||||
@@ -11,14 +11,14 @@ import {
|
||||
import { ColumnDef } from '@tanstack/react-table';
|
||||
|
||||
import type { ExtendedEntry } from '../../../common/utils/rundownMetadata';
|
||||
import useColumnManager from '../cuesheet-table/useColumnManager';
|
||||
import { useColumnOrder } from '../cuesheet-table/useColumnManager';
|
||||
|
||||
interface CuesheetDndProps {
|
||||
columns: ColumnDef<ExtendedEntry>[];
|
||||
}
|
||||
|
||||
export default function CuesheetDnd({ columns, children }: PropsWithChildren<CuesheetDndProps>) {
|
||||
const { columnOrder, saveColumnOrder } = useColumnManager(columns);
|
||||
const { columnOrder, saveColumnOrder } = useColumnOrder(columns);
|
||||
|
||||
const sensors = useSensors(
|
||||
useSensor(PointerSensor, {
|
||||
|
||||
@@ -13,14 +13,14 @@ import type { ExtendedEntry } from '../../../common/utils/rundownMetadata';
|
||||
import { AppMode } from '../../../ontimeConfig';
|
||||
import { usePersistedCuesheetOptions } from '../cuesheet.options';
|
||||
|
||||
import CuesheetHeader from './cuesheet-table-elements/CuesheetHeader';
|
||||
import { CuesheetHeader, SortableCuesheetHeader } from './cuesheet-table-elements/CuesheetHeader';
|
||||
import DelayRow from './cuesheet-table-elements/DelayRow';
|
||||
import EventRow from './cuesheet-table-elements/EventRow';
|
||||
import GroupRow from './cuesheet-table-elements/GroupRow';
|
||||
import MilestoneRow from './cuesheet-table-elements/MilestoneRow';
|
||||
import CuesheetTableMenu from './cuesheet-table-menu/CuesheetTableMenu';
|
||||
import CuesheetTableSettings from './cuesheet-table-settings/CuesheetTableSettings';
|
||||
import useColumnManager from './useColumnManager';
|
||||
import { useColumnOrder, useColumnSizes, useColumnVisibility } from './useColumnManager';
|
||||
|
||||
import style from './CuesheetTable.module.scss';
|
||||
|
||||
@@ -79,8 +79,9 @@ export default function CuesheetTable({ columns, cuesheetMode }: CuesheetTablePr
|
||||
[cuesheetMode, data, hideIndexColumn, hideTableSeconds, showDelayedTimes, updateEntry, updateTimer],
|
||||
);
|
||||
|
||||
const { columnVisibility, columnOrder, columnSizing, resetColumnOrder, setColumnVisibility, setColumnSizing } =
|
||||
useColumnManager(columns);
|
||||
const { columnOrder, resetColumnOrder } = useColumnOrder(columns);
|
||||
const { columnSizing, setColumnSizing } = useColumnSizes();
|
||||
const { columnVisibility, setColumnVisibility } = useColumnVisibility();
|
||||
|
||||
const table = useReactTable({
|
||||
data,
|
||||
@@ -237,11 +238,14 @@ export default function CuesheetTable({ columns, cuesheetMode }: CuesheetTablePr
|
||||
TableHead: (virtuosoProps) => <thead className={style.tableHeader} {...virtuosoProps} />,
|
||||
}}
|
||||
fixedHeaderContent={() => {
|
||||
return table
|
||||
.getHeaderGroups()
|
||||
.map((headerGroup) => (
|
||||
<CuesheetHeader key={headerGroup.id} cuesheetMode={cuesheetMode} headerGroup={headerGroup} />
|
||||
));
|
||||
return table.getHeaderGroups().map((headerGroup) => {
|
||||
const HeaderComponent = table.getState().columnSizingInfo.isResizingColumn
|
||||
? CuesheetHeader
|
||||
: SortableCuesheetHeader;
|
||||
|
||||
// if the table is being resized, we render non-sortable headers to avoid performance issues
|
||||
return <HeaderComponent key={headerGroup.id} cuesheetMode={cuesheetMode} headerGroup={headerGroup} />;
|
||||
});
|
||||
}}
|
||||
/>
|
||||
|
||||
|
||||
+46
-3
@@ -7,7 +7,7 @@ import { getAccessibleColour } from '../../../../common/utils/styleUtils';
|
||||
import { AppMode } from '../../../../ontimeConfig';
|
||||
import { usePersistedCuesheetOptions } from '../../cuesheet.options';
|
||||
|
||||
import { SortableCell } from './SortableCell';
|
||||
import { Draggable, SortableCell, TableCell } from './SortableCell';
|
||||
|
||||
import style from '../CuesheetTable.module.scss';
|
||||
|
||||
@@ -16,8 +16,9 @@ interface CuesheetHeaderProps {
|
||||
cuesheetMode: AppMode;
|
||||
}
|
||||
|
||||
export default function CuesheetHeader({ headerGroup, cuesheetMode }: CuesheetHeaderProps) {
|
||||
export function SortableCuesheetHeader({ headerGroup, cuesheetMode }: CuesheetHeaderProps) {
|
||||
const hideIndexColumn = usePersistedCuesheetOptions((state) => state.hideIndexColumn);
|
||||
|
||||
return (
|
||||
<tr key={headerGroup.id}>
|
||||
{cuesheetMode === AppMode.Edit && <th className={style.actionColumn} tabIndex={-1} />}
|
||||
@@ -43,8 +44,10 @@ export default function CuesheetHeader({ headerGroup, cuesheetMode }: CuesheetHe
|
||||
return (
|
||||
<SortableCell
|
||||
key={header.column.columnDef.id}
|
||||
header={header}
|
||||
columnId={header.column.id}
|
||||
colSpan={header.colSpan}
|
||||
injectedStyles={{ width: `calc(var(--header-${header?.id}-size) * 1px)`, ...customStyles }}
|
||||
draggable={<Draggable header={header} />}
|
||||
>
|
||||
{header.isPlaceholder ? null : flexRender(header.column.columnDef.header, header.getContext())}
|
||||
</SortableCell>
|
||||
@@ -54,3 +57,43 @@ export default function CuesheetHeader({ headerGroup, cuesheetMode }: CuesheetHe
|
||||
</tr>
|
||||
);
|
||||
}
|
||||
|
||||
export function CuesheetHeader({ headerGroup, cuesheetMode }: CuesheetHeaderProps) {
|
||||
const hideIndexColumn = usePersistedCuesheetOptions((state) => state.hideIndexColumn);
|
||||
|
||||
return (
|
||||
<tr key={headerGroup.id}>
|
||||
{cuesheetMode === AppMode.Edit && <th className={style.actionColumn} tabIndex={-1} />}
|
||||
{!hideIndexColumn && (
|
||||
<th className={style.indexColumn} tabIndex={-1}>
|
||||
#
|
||||
</th>
|
||||
)}
|
||||
{headerGroup.headers.map((header) => {
|
||||
const customBackground = header.column.columnDef.meta?.colour;
|
||||
const canWrite = header.column.columnDef.meta?.canWrite;
|
||||
|
||||
const customStyles: CSSProperties = {
|
||||
opacity: canWrite ? 1 : 0.6,
|
||||
};
|
||||
if (customBackground) {
|
||||
const customColour = getAccessibleColour(customBackground);
|
||||
customStyles.backgroundColor = customColour.backgroundColor;
|
||||
customStyles.color = customColour.color;
|
||||
}
|
||||
|
||||
return (
|
||||
<TableCell
|
||||
key={header.column.columnDef.id}
|
||||
columnId={header.column.id}
|
||||
colSpan={header.colSpan}
|
||||
injectedStyles={{ width: `calc(var(--header-${header?.id}-size) * 1px)`, ...customStyles }}
|
||||
draggable={<Draggable header={header} />}
|
||||
>
|
||||
{header.isPlaceholder ? null : flexRender(header.column.columnDef.header, header.getContext())}
|
||||
</TableCell>
|
||||
);
|
||||
})}
|
||||
</tr>
|
||||
);
|
||||
}
|
||||
|
||||
+4
@@ -4,3 +4,7 @@
|
||||
padding-top: 0.25em;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.multiline {
|
||||
white-space: break-spaces;
|
||||
}
|
||||
|
||||
+6
-2
@@ -2,6 +2,10 @@ import { PropsWithChildren } from 'react';
|
||||
|
||||
import style from './GhostedText.module.scss';
|
||||
|
||||
export default function GhostedText({ children }: PropsWithChildren) {
|
||||
return <div className={style.ghostedText}>{children}</div>;
|
||||
interface GhostedTextProps {
|
||||
multiline?: boolean;
|
||||
}
|
||||
|
||||
export default function GhostedText({ children, multiline }: PropsWithChildren<GhostedTextProps>) {
|
||||
return <div className={`${style.ghostedText} ${multiline ? style.multiline : ''}`}>{children}</div>;
|
||||
}
|
||||
|
||||
+30
-11
@@ -8,16 +8,16 @@ import type { ExtendedEntry } from '../../../../common/utils/rundownMetadata';
|
||||
import style from '../CuesheetTable.module.scss';
|
||||
|
||||
interface SortableCellProps {
|
||||
header: Header<ExtendedEntry, unknown>;
|
||||
columnId: string;
|
||||
colSpan: number;
|
||||
injectedStyles: CSSProperties;
|
||||
children: ReactNode;
|
||||
draggable: ReactNode;
|
||||
}
|
||||
|
||||
export function SortableCell({ header, injectedStyles, children }: SortableCellProps) {
|
||||
const { column, colSpan } = header;
|
||||
|
||||
export function SortableCell({ columnId, colSpan, injectedStyles, children, draggable }: SortableCellProps) {
|
||||
const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({
|
||||
id: column.id,
|
||||
id: columnId,
|
||||
});
|
||||
|
||||
// build drag styles
|
||||
@@ -34,12 +34,31 @@ export function SortableCell({ header, injectedStyles, children }: SortableCellP
|
||||
<div {...attributes} {...listeners}>
|
||||
{children}
|
||||
</div>
|
||||
<div
|
||||
onDoubleClick={() => header.column.resetSize()}
|
||||
onMouseDown={header.getResizeHandler()}
|
||||
onTouchStart={header.getResizeHandler()}
|
||||
className={style.resizer}
|
||||
/>
|
||||
{draggable}
|
||||
</th>
|
||||
);
|
||||
}
|
||||
|
||||
export function TableCell({ colSpan, injectedStyles, children, draggable }: SortableCellProps) {
|
||||
return (
|
||||
<th style={injectedStyles} colSpan={colSpan} tabIndex={-1}>
|
||||
<div>{children}</div>
|
||||
{draggable}
|
||||
</th>
|
||||
);
|
||||
}
|
||||
|
||||
interface DraggableProps {
|
||||
header: Header<ExtendedEntry, unknown>;
|
||||
}
|
||||
|
||||
export function Draggable({ header }: DraggableProps) {
|
||||
return (
|
||||
<div
|
||||
onDoubleClick={() => header.column.resetSize()}
|
||||
onMouseDown={header.getResizeHandler()}
|
||||
onTouchStart={header.getResizeHandler()}
|
||||
className={style.resizer}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
+1
-1
@@ -143,7 +143,7 @@ function MakeMultiLineField({ row, column, table }: CellContext<ExtendedEntry, u
|
||||
|
||||
const canWrite = column.columnDef.meta?.canWrite;
|
||||
if (!canWrite) {
|
||||
return <GhostedText>{initialValue}</GhostedText>;
|
||||
return <GhostedText multiline>{initialValue}</GhostedText>;
|
||||
}
|
||||
|
||||
return <MultiLineCell initialValue={initialValue as string} handleUpdate={update} />;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { useLocalStorage } from '@mantine/hooks';
|
||||
import { ColumnDef } from '@tanstack/react-table';
|
||||
import { ColumnDef, ColumnSizingState, Updater } from '@tanstack/react-table';
|
||||
|
||||
import { debounce } from '../../../common/utils/debounce';
|
||||
import { makeStageKey } from '../../../common/utils/localStorage';
|
||||
@@ -14,17 +14,8 @@ const saveSizesToStorage = debounce((sizes: Record<string, number>) => {
|
||||
localStorage.setItem(tableSizesKey, JSON.stringify(sizes));
|
||||
}, 500);
|
||||
|
||||
export default function useColumnManager(columns: ColumnDef<ExtendedEntry>[]) {
|
||||
const [columnVisibility, setColumnVisibility] = useLocalStorage({
|
||||
key: tableHiddenKey,
|
||||
defaultValue: {},
|
||||
});
|
||||
const [columnOrder, saveColumnOrder] = useLocalStorage<string[]>({
|
||||
key: tableOrderKey,
|
||||
defaultValue: columns.map((col) => col.id as string),
|
||||
});
|
||||
|
||||
const [columnSizing, setColumnSizingState] = useState(() => {
|
||||
export function useColumnSizes() {
|
||||
const [columnSizing, setColumnSizingState] = useState<Record<string, number>>(() => {
|
||||
try {
|
||||
const stored = localStorage.getItem(tableSizesKey);
|
||||
return stored ? JSON.parse(stored) : {};
|
||||
@@ -38,10 +29,22 @@ export default function useColumnManager(columns: ColumnDef<ExtendedEntry>[]) {
|
||||
saveSizesToStorage(columnSizing);
|
||||
}, [columnSizing]);
|
||||
|
||||
const setColumnSizing = useCallback((sizes: typeof columnSizing) => {
|
||||
setColumnSizingState(sizes);
|
||||
const setColumnSizing = useCallback((sizesOrUpdater: Updater<ColumnSizingState>) => {
|
||||
setColumnSizingState(sizesOrUpdater);
|
||||
}, []);
|
||||
|
||||
return {
|
||||
columnSizing,
|
||||
setColumnSizing,
|
||||
};
|
||||
}
|
||||
|
||||
export function useColumnOrder(columns: ColumnDef<ExtendedEntry>[]) {
|
||||
const [columnOrder, saveColumnOrder] = useLocalStorage<string[]>({
|
||||
key: tableOrderKey,
|
||||
defaultValue: columns.map((col) => col.id as string),
|
||||
});
|
||||
|
||||
// update column order if columns change
|
||||
useEffect(() => {
|
||||
const newColumns = columns.map((col) => col.id as string);
|
||||
@@ -55,12 +58,20 @@ export default function useColumnManager(columns: ColumnDef<ExtendedEntry>[]) {
|
||||
}, [columns, saveColumnOrder]);
|
||||
|
||||
return {
|
||||
columnVisibility,
|
||||
columnOrder,
|
||||
columnSizing,
|
||||
resetColumnOrder,
|
||||
setColumnVisibility,
|
||||
saveColumnOrder,
|
||||
setColumnSizing,
|
||||
resetColumnOrder,
|
||||
};
|
||||
}
|
||||
|
||||
export function useColumnVisibility() {
|
||||
const [columnVisibility, setColumnVisibility] = useLocalStorage({
|
||||
key: tableHiddenKey,
|
||||
defaultValue: {},
|
||||
});
|
||||
|
||||
return {
|
||||
columnVisibility,
|
||||
setColumnVisibility,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -66,4 +66,5 @@
|
||||
.scrollContainer {
|
||||
max-height: 70vh;
|
||||
overflow: auto;
|
||||
padding-top: 1rem;
|
||||
}
|
||||
@@ -71,8 +71,7 @@ export default function Finder({ isOpen, onClose }: FinderProps) {
|
||||
results.map((entry, index) => {
|
||||
const isSelected = selected === index;
|
||||
const displayIndex = entry.type === SupportedEntry.Event ? entry.eventIndex : '-';
|
||||
const displayCue = entry.type === SupportedEntry.Event ? entry.cue : '';
|
||||
const colour = entry.type === SupportedEntry.Event ? entry.colour : '';
|
||||
const displayCue = 'cue' in entry ? entry.cue : '';
|
||||
|
||||
return (
|
||||
<li
|
||||
@@ -83,7 +82,7 @@ export default function Finder({ isOpen, onClose }: FinderProps) {
|
||||
onClick={submit}
|
||||
>
|
||||
<div className={style.data}>
|
||||
<div className={style.index} style={{ '--color': colour }}>
|
||||
<div className={style.index} style={{ '--color': entry.colour }}>
|
||||
{displayIndex}
|
||||
</div>
|
||||
<div className={style.cue}>{displayCue}</div>
|
||||
@@ -99,7 +98,7 @@ export default function Finder({ isOpen, onClose }: FinderProps) {
|
||||
footerElements={
|
||||
<div className={style.footer}>
|
||||
Use the keywords <span className={style.em}>cue</span>, <span className={style.em}>index</span> or
|
||||
<span className={style.em}>title</span> to filter search
|
||||
<span className={style.em}>title</span> to filter search.
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { ChangeEvent, useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { useSessionStorage } from '@mantine/hooks';
|
||||
import { EntryId, isOntimeEvent, isOntimeGroup, MaybeString, SupportedEntry } from 'ontime-types';
|
||||
import { EntryId, isOntimeEvent, isOntimeGroup, isOntimeMilestone, MaybeString, SupportedEntry } from 'ontime-types';
|
||||
|
||||
import { useFlatRundown } from '../../../common/hooks-query/useRundown';
|
||||
import { useEventSelection } from '../../../features/rundown/useEventSelection';
|
||||
@@ -9,14 +9,15 @@ const maxResults = 12;
|
||||
|
||||
type FilterableGroup = {
|
||||
type: SupportedEntry.Group;
|
||||
id: string;
|
||||
id: EntryId;
|
||||
index: number;
|
||||
title: string;
|
||||
colour: string;
|
||||
};
|
||||
|
||||
type FilterableEvent = {
|
||||
type: SupportedEntry.Event;
|
||||
id: string;
|
||||
id: EntryId;
|
||||
index: number;
|
||||
eventIndex: number;
|
||||
title: string;
|
||||
@@ -25,7 +26,17 @@ type FilterableEvent = {
|
||||
parent: MaybeString;
|
||||
};
|
||||
|
||||
type FilterableEntry = FilterableGroup | FilterableEvent;
|
||||
type FilterableMilestone = {
|
||||
type: SupportedEntry.Milestone;
|
||||
id: EntryId;
|
||||
index: number;
|
||||
title: string;
|
||||
cue: string;
|
||||
colour: string;
|
||||
parent: MaybeString;
|
||||
};
|
||||
|
||||
type FilterableEntry = FilterableGroup | FilterableEvent | FilterableMilestone;
|
||||
|
||||
export default function useFinder() {
|
||||
const { data, rundownId } = useFlatRundown();
|
||||
@@ -59,7 +70,7 @@ export default function useFinder() {
|
||||
lastSearchString.current = searchValue;
|
||||
|
||||
if (searchValue.startsWith('index ')) {
|
||||
const searchString = searchValue.replace('index ', '').trim();
|
||||
const searchString = searchValue.slice('index '.length).trim();
|
||||
const { results, error } = searchByIndex(searchString);
|
||||
setResults(results);
|
||||
setError(error);
|
||||
@@ -67,14 +78,14 @@ export default function useFinder() {
|
||||
}
|
||||
|
||||
if (searchValue.startsWith('cue ')) {
|
||||
const searchString = searchValue.replace('cue ', '').trim();
|
||||
const searchString = searchValue.slice('cue '.length).trim();
|
||||
const { results, error } = searchByCue(searchString);
|
||||
setResults(results);
|
||||
setError(error);
|
||||
return;
|
||||
}
|
||||
|
||||
const searchString = searchValue.replace('title ', '').trim();
|
||||
const searchString = searchValue.startsWith('title ') ? searchValue.slice('title '.length).trim() : searchValue;
|
||||
const { results, error } = searchByTitle(searchString);
|
||||
setResults(results);
|
||||
setError(error);
|
||||
@@ -162,33 +173,46 @@ export default function useFinder() {
|
||||
break;
|
||||
}
|
||||
|
||||
const event = data[i];
|
||||
if (isOntimeEvent(event)) {
|
||||
if (event.title.toLowerCase().includes(searchString)) {
|
||||
const entry = data[i];
|
||||
if (isOntimeEvent(entry)) {
|
||||
if (entry.title.toLowerCase().includes(searchString)) {
|
||||
remaining--;
|
||||
results.push({
|
||||
type: SupportedEntry.Event,
|
||||
id: event.id,
|
||||
id: entry.id,
|
||||
index: i,
|
||||
eventIndex,
|
||||
title: event.title,
|
||||
cue: event.cue,
|
||||
colour: event.colour,
|
||||
parent: event.parent,
|
||||
title: entry.title,
|
||||
cue: entry.cue,
|
||||
colour: entry.colour,
|
||||
parent: entry.parent,
|
||||
} satisfies FilterableEvent);
|
||||
}
|
||||
eventIndex++;
|
||||
}
|
||||
if (isOntimeGroup(event)) {
|
||||
if (event.title.toLowerCase().includes(searchString)) {
|
||||
} else if (isOntimeGroup(entry)) {
|
||||
if (entry.title.toLowerCase().includes(searchString)) {
|
||||
remaining--;
|
||||
results.push({
|
||||
type: SupportedEntry.Group,
|
||||
id: event.id,
|
||||
id: entry.id,
|
||||
index: i,
|
||||
title: event.title,
|
||||
title: entry.title,
|
||||
colour: entry.colour,
|
||||
} satisfies FilterableGroup);
|
||||
}
|
||||
} else if (isOntimeMilestone(entry)) {
|
||||
if (entry.title.toLowerCase().includes(searchString)) {
|
||||
remaining--;
|
||||
results.push({
|
||||
type: SupportedEntry.Milestone,
|
||||
id: entry.id,
|
||||
index: i,
|
||||
title: entry.title,
|
||||
cue: entry.cue,
|
||||
colour: entry.colour,
|
||||
parent: entry.parent,
|
||||
} satisfies FilterableMilestone);
|
||||
}
|
||||
}
|
||||
}
|
||||
return { results, error: null };
|
||||
@@ -200,7 +224,7 @@ export default function useFinder() {
|
||||
const select = useCallback(
|
||||
(selectedEvent: FilterableEntry) => {
|
||||
// First expand the parent group if this is an event inside a group
|
||||
if (selectedEvent.type === SupportedEntry.Event && selectedEvent.parent !== null) {
|
||||
if ('parent' in selectedEvent && selectedEvent.parent !== null) {
|
||||
// Try direct state update instead of using callback
|
||||
const currentGroups = [...new Set(collapsedGroups)];
|
||||
const newGroups = currentGroups.filter((id) => id !== selectedEvent.parent);
|
||||
|
||||
@@ -105,7 +105,7 @@ $timeline-color: color-mix(in srgb, transparent 60%, var(--background-color-over
|
||||
}
|
||||
|
||||
.delay {
|
||||
color: $delay-color;
|
||||
color: $ontime-delay-text;
|
||||
}
|
||||
|
||||
.timeOverview {
|
||||
@@ -117,7 +117,7 @@ $timeline-color: color-mix(in srgb, transparent 60%, var(--background-color-over
|
||||
.cross {
|
||||
text-decoration: line-through;
|
||||
text-decoration-thickness: 2px;
|
||||
text-decoration-color: $delay-color;
|
||||
text-decoration-color: $ontime-delay-text;
|
||||
}
|
||||
|
||||
.separeLeft {
|
||||
|
||||
@@ -183,7 +183,7 @@ export function getUpcomingEvents(events: PlayableEvent[], selectedId: MaybeStri
|
||||
* Utility function calculates time to start
|
||||
*/
|
||||
export function getTimeToStart(now: number, start: number, delay: number, offset: number): number {
|
||||
return start + delay - now - offset;
|
||||
return start + delay - now + offset;
|
||||
}
|
||||
|
||||
interface TimelineLayout {
|
||||
|
||||
@@ -68,7 +68,7 @@ function Timer({ customFields, projectData, isMirrored, settings, viewSettings }
|
||||
hidePhase,
|
||||
font,
|
||||
keyColour,
|
||||
textColour,
|
||||
timerColour,
|
||||
} = useTimerOptions();
|
||||
|
||||
const { getLocalizedString } = useTranslation();
|
||||
@@ -131,11 +131,11 @@ function Timer({ customFields, projectData, isMirrored, settings, viewSettings }
|
||||
);
|
||||
|
||||
// gather presentation styles
|
||||
const timerColour = getTimerColour(viewSettings, textColour, showWarning, showDanger);
|
||||
const resolvedTimerColour = getTimerColour(viewSettings, timerColour, showWarning, showDanger);
|
||||
const { timerFontSize, externalFontSize } = getEstimatedFontSize(display, secondaryContent);
|
||||
const userStyles = {
|
||||
...(keyColour && { '--timer-bg': keyColour }),
|
||||
...(textColour && { '--timer-colour': timerColour }),
|
||||
...(resolvedTimerColour && { '--timer-colour': resolvedTimerColour }),
|
||||
...(font && { '--timer-font': font }),
|
||||
};
|
||||
|
||||
|
||||
@@ -25,10 +25,12 @@ const timerDisplayOptions: SelectOption[] = [
|
||||
|
||||
export const getTimerOptions = (timeFormat: string, customFields: CustomFields): ViewOption[] => {
|
||||
const mainOptions = makeOptionsFromCustomFields(customFields, [
|
||||
{ value: 'none', label: 'None' },
|
||||
{ value: 'title', label: 'Title' },
|
||||
{ value: 'note', label: 'Note' },
|
||||
]);
|
||||
const secondaryOptions = makeOptionsFromCustomFields(customFields, [
|
||||
{ value: 'none', label: 'None' },
|
||||
{ value: 'title', label: 'Title' },
|
||||
{ value: 'note', label: 'Note' },
|
||||
]);
|
||||
@@ -84,7 +86,7 @@ export const getTimerOptions = (timeFormat: string, customFields: CustomFields):
|
||||
description: 'Select the data source for the main text',
|
||||
type: 'option',
|
||||
values: mainOptions,
|
||||
defaultValue: 'Title',
|
||||
defaultValue: 'title',
|
||||
},
|
||||
{
|
||||
id: 'secondary-src',
|
||||
@@ -92,7 +94,7 @@ export const getTimerOptions = (timeFormat: string, customFields: CustomFields):
|
||||
description: 'Select the data source for the secondary text',
|
||||
type: 'option',
|
||||
values: secondaryOptions,
|
||||
defaultValue: '',
|
||||
defaultValue: 'none',
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -163,9 +165,9 @@ export const getTimerOptions = (timeFormat: string, customFields: CustomFields):
|
||||
defaultValue: '101010',
|
||||
},
|
||||
{
|
||||
id: 'textColour',
|
||||
title: 'Text Colour',
|
||||
description: 'Text colour. Default: #f6f6f6',
|
||||
id: 'timerColour',
|
||||
title: 'Timer Colour',
|
||||
description: 'Timer colour. Default: #f6f6f6',
|
||||
type: 'colour',
|
||||
defaultValue: 'f6f6f6',
|
||||
},
|
||||
@@ -191,7 +193,7 @@ type TimerOptions = {
|
||||
hidePhase: boolean;
|
||||
font?: string;
|
||||
keyColour?: string;
|
||||
textColour?: string;
|
||||
timerColour?: string;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -226,7 +228,7 @@ function getOptionsFromParams(searchParams: URLSearchParams, defaultValues?: URL
|
||||
|
||||
font: getValue('font') ?? undefined,
|
||||
keyColour: makeColourString(getValue('keyColour')),
|
||||
textColour: makeColourString(getValue('textColour')),
|
||||
timerColour: makeColourString(getValue('timerColour')),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -32,7 +32,8 @@
|
||||
"preserveConstEnums": true,
|
||||
"allowJs": true,
|
||||
"baseUrl": "src",
|
||||
"jsx": "react-jsx"
|
||||
"jsx": "react-jsx",
|
||||
"outDir": "build"
|
||||
},
|
||||
"include": [
|
||||
"src"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "ontime-electron",
|
||||
"version": "4.0.0-beta.2",
|
||||
"version": "4.0.0-beta.3",
|
||||
"author": "Carlos Valente",
|
||||
"description": "Time keeping for live events",
|
||||
"repository": "https://github.com/cpvalente/ontime",
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
*.tgz
|
||||
@@ -0,0 +1,17 @@
|
||||
# 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://github.com/sponsors/cpvalente)
|
||||
[](https://www.buymeacoffee.com/cpvalente)
|
||||
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"name": "@getontime/resolver",
|
||||
"version": "4.0.0-beta.3",
|
||||
"type": "module",
|
||||
"repository": "https://github.com/cpvalente/ontime",
|
||||
"types": "./dist/main.d.ts",
|
||||
"main": "./dist/main.js",
|
||||
"private": true,
|
||||
"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"]
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
// 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';
|
||||
@@ -0,0 +1,18 @@
|
||||
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;
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "esnext",
|
||||
"module": "preserve",
|
||||
"moduleResolution": "bundler",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"skipLibCheck": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"strict": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"noImplicitAny": true,
|
||||
"noImplicitReturns": true,
|
||||
"allowUnreachableCode": false,
|
||||
"allowUnusedLabels": false,
|
||||
"noImplicitThis": true,
|
||||
"strictNullChecks": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"removeComments": true,
|
||||
"preserveConstEnums": true,
|
||||
"allowJs": true,
|
||||
"declaration": true,
|
||||
"outDir": "dist",
|
||||
"sourceMap": true,
|
||||
"baseUrl": "src",
|
||||
},
|
||||
"include": [
|
||||
"src",
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
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',
|
||||
}),
|
||||
],
|
||||
});
|
||||
@@ -2,7 +2,7 @@
|
||||
"name": "ontime-server",
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
"version": "4.0.0-beta.2",
|
||||
"version": "4.0.0-beta.3",
|
||||
"exports": "./src/index.js",
|
||||
"dependencies": {
|
||||
"@googleapis/sheets": "^5.0.5",
|
||||
@@ -41,7 +41,7 @@
|
||||
"prettier": "catalog:",
|
||||
"server-timing": "^3.3.3",
|
||||
"shx": "^0.3.4",
|
||||
"ts-essentials": "^10.0.3",
|
||||
"ts-essentials": "catalog:",
|
||||
"tsx": "^4.19.2",
|
||||
"typescript": "catalog:",
|
||||
"vitest": "catalog:"
|
||||
|
||||
@@ -10,7 +10,7 @@ export const dataFromExcelTemplate = [
|
||||
'Timer type',
|
||||
'Count to end',
|
||||
'Skip',
|
||||
'Notes',
|
||||
'Note',
|
||||
't0',
|
||||
'Test1',
|
||||
'test2',
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import {
|
||||
ApiAction,
|
||||
ApiActionTag,
|
||||
MessageState,
|
||||
OffsetMode,
|
||||
OntimeEvent,
|
||||
@@ -30,7 +30,7 @@ let lastRequest: Date | null = null;
|
||||
|
||||
export function dispatchFromAdapter(tag: string, payload: unknown, _source?: 'osc' | 'ws' | 'http') {
|
||||
const action = tag.toLowerCase();
|
||||
const handler = actionHandlers[action as ApiAction];
|
||||
const handler = actionHandlers[action as ApiActionTag];
|
||||
lastRequest = new Date();
|
||||
|
||||
if (handler) {
|
||||
@@ -46,7 +46,7 @@ export function getLastRequest() {
|
||||
|
||||
type ActionHandler = (payload: unknown) => { payload: unknown };
|
||||
|
||||
const actionHandlers: Record<ApiAction, ActionHandler> = {
|
||||
const actionHandlers: Record<ApiActionTag, ActionHandler> = {
|
||||
/* General */
|
||||
version: () => ({ payload: ONTIME_VERSION }),
|
||||
poll: () => ({
|
||||
|
||||
@@ -337,7 +337,7 @@ export const demoDb: DatabaseModel = {
|
||||
alias: 'minimal',
|
||||
target: OntimeView.Timer,
|
||||
search:
|
||||
'hideclock=true&hidecards=true&hideprogress=true&hidemessage=true&hidesecondary=true&hidelogo=true&font=arial+black&keycolour=00ff00&textcolour=ffffff',
|
||||
'hideclock=true&hidecards=true&hideprogress=true&hidemessage=true&hidesecondary=true&hidelogo=true&font=arial+black&keycolour=00ff00&timerColour=ffffff',
|
||||
},
|
||||
],
|
||||
customFields: {
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { dayInMs, millisToString } from 'ontime-utils';
|
||||
import { dayInMs, MILLIS_PER_HOUR, millisToString } from 'ontime-utils';
|
||||
import { EndAction, Playback, TimeStrategy, TimerPhase, TimerType } from 'ontime-types';
|
||||
|
||||
import {
|
||||
findDayOffset,
|
||||
getCurrent,
|
||||
getExpectedFinish,
|
||||
getRuntimeOffset,
|
||||
@@ -726,6 +727,7 @@ describe('getRuntimeOffset()', () => {
|
||||
eventNow: {
|
||||
id: '1',
|
||||
timeStart: 100,
|
||||
dayOffset: 0,
|
||||
},
|
||||
timer: {
|
||||
startedAt: 150,
|
||||
@@ -738,7 +740,10 @@ describe('getRuntimeOffset()', () => {
|
||||
rundown: {
|
||||
actualStart: 150,
|
||||
plannedStart: 100,
|
||||
currentDay: 0,
|
||||
},
|
||||
clock: 150,
|
||||
_startDayOffset: 0,
|
||||
} as RuntimeState;
|
||||
|
||||
const { absolute } = getRuntimeOffset(state);
|
||||
@@ -750,6 +755,7 @@ describe('getRuntimeOffset()', () => {
|
||||
eventNow: {
|
||||
id: '1',
|
||||
timeStart: 100,
|
||||
dayOffset: 0,
|
||||
},
|
||||
timer: {
|
||||
startedAt: 150, // we started 50ms delayed
|
||||
@@ -762,7 +768,9 @@ describe('getRuntimeOffset()', () => {
|
||||
rundown: {
|
||||
actualStart: 150,
|
||||
plannedStart: 100,
|
||||
currentDay: 0,
|
||||
},
|
||||
_startDayOffset: 0,
|
||||
} as RuntimeState;
|
||||
|
||||
const { absolute } = getRuntimeOffset(state);
|
||||
@@ -775,6 +783,7 @@ describe('getRuntimeOffset()', () => {
|
||||
id: '1',
|
||||
timeStart: 100,
|
||||
timeEnd: 140,
|
||||
dayOffset: 0,
|
||||
},
|
||||
timer: {
|
||||
startedAt: 100, // we started ontime
|
||||
@@ -787,7 +796,9 @@ describe('getRuntimeOffset()', () => {
|
||||
rundown: {
|
||||
actualStart: 100,
|
||||
plannedStart: 100,
|
||||
currentDay: 0,
|
||||
},
|
||||
_startDayOffset: 0,
|
||||
} as RuntimeState;
|
||||
|
||||
const { absolute } = getRuntimeOffset(state);
|
||||
@@ -800,6 +811,7 @@ describe('getRuntimeOffset()', () => {
|
||||
id: '1',
|
||||
timeStart: 100,
|
||||
timeEnd: 150,
|
||||
dayOffset: 0,
|
||||
},
|
||||
clock: 150,
|
||||
timer: {
|
||||
@@ -813,7 +825,9 @@ describe('getRuntimeOffset()', () => {
|
||||
rundown: {
|
||||
actualStart: 100,
|
||||
plannedStart: 100,
|
||||
currentDay: 0,
|
||||
},
|
||||
_startDayOffset: 0,
|
||||
} as RuntimeState;
|
||||
|
||||
const { absolute } = getRuntimeOffset(state);
|
||||
@@ -830,6 +844,7 @@ describe('getRuntimeOffset()', () => {
|
||||
duration: 3600000,
|
||||
timeStrategy: 'lock-duration',
|
||||
linkStart: false,
|
||||
dayOffset: 0,
|
||||
},
|
||||
rundown: {
|
||||
selectedEventIndex: 0,
|
||||
@@ -837,6 +852,7 @@ describe('getRuntimeOffset()', () => {
|
||||
plannedStart: 77400000,
|
||||
plannedEnd: 84600000,
|
||||
actualStart: null,
|
||||
currentDay: 0,
|
||||
},
|
||||
offset: {
|
||||
absolute: -77400000,
|
||||
@@ -852,6 +868,7 @@ describe('getRuntimeOffset()', () => {
|
||||
secondaryTimer: null,
|
||||
startedAt: null,
|
||||
},
|
||||
_startDayOffset: 0,
|
||||
_timer: { pausedAt: null },
|
||||
} as RuntimeState;
|
||||
|
||||
@@ -874,6 +891,7 @@ describe('getRuntimeOffset()', () => {
|
||||
endAction: EndAction.None,
|
||||
timerType: TimerType.CountDown,
|
||||
countToEnd: true,
|
||||
dayOffset: 0,
|
||||
skip: false,
|
||||
note: '',
|
||||
colour: '',
|
||||
@@ -890,6 +908,7 @@ describe('getRuntimeOffset()', () => {
|
||||
plannedStart: 77400000, // 21:30:00
|
||||
plannedEnd: 81000000, // 22:30:00
|
||||
actualStart: 78000000, // 21:40:00
|
||||
currentDay: 0,
|
||||
},
|
||||
offset: {
|
||||
absolute: 0,
|
||||
@@ -905,6 +924,7 @@ describe('getRuntimeOffset()', () => {
|
||||
secondaryTimer: null,
|
||||
startedAt: 78000000,
|
||||
},
|
||||
_startDayOffset: 0,
|
||||
_timer: { pausedAt: null },
|
||||
} as RuntimeState;
|
||||
|
||||
@@ -922,6 +942,7 @@ describe('getRuntimeOffset()', () => {
|
||||
timeStart: 77400000, // 21:30:00
|
||||
timeEnd: 81000000, // 22:30:00
|
||||
duration: 3600000, // 01:00:00
|
||||
dayOffset: 0,
|
||||
timeStrategy: TimeStrategy.LockEnd,
|
||||
linkStart: false,
|
||||
endAction: EndAction.None,
|
||||
@@ -943,6 +964,7 @@ describe('getRuntimeOffset()', () => {
|
||||
plannedStart: 77400000, // 21:30:00
|
||||
plannedEnd: 81000000, // 22:30:00
|
||||
actualStart: 78000000, // 21:40:00
|
||||
currentDay: 0,
|
||||
},
|
||||
offset: {
|
||||
absolute: 0,
|
||||
@@ -958,6 +980,7 @@ describe('getRuntimeOffset()', () => {
|
||||
secondaryTimer: null,
|
||||
startedAt: 78000000,
|
||||
},
|
||||
_startDayOffset: 0,
|
||||
_timer: { pausedAt: null },
|
||||
} as RuntimeState;
|
||||
|
||||
@@ -978,6 +1001,7 @@ describe('getRuntimeOffset()', () => {
|
||||
endAction: EndAction.None,
|
||||
timerType: TimerType.CountDown,
|
||||
countToEnd: true,
|
||||
dayOffset: 0,
|
||||
},
|
||||
rundown: {
|
||||
selectedEventIndex: 0,
|
||||
@@ -985,6 +1009,7 @@ describe('getRuntimeOffset()', () => {
|
||||
plannedStart: 77400000, // 21:30:00
|
||||
plannedEnd: 81000000, // 22:30:00
|
||||
actualStart: 82000000, // 22:46:40 <--- started now
|
||||
currentDay: 0,
|
||||
},
|
||||
offset: {
|
||||
absolute: 0,
|
||||
@@ -1000,6 +1025,7 @@ describe('getRuntimeOffset()', () => {
|
||||
secondaryTimer: null,
|
||||
startedAt: 82000000, // <--- started now
|
||||
},
|
||||
_startDayOffset: 0,
|
||||
_timer: { pausedAt: null },
|
||||
} as RuntimeState;
|
||||
|
||||
@@ -1017,6 +1043,7 @@ describe('getRuntimeOffset() relative', () => {
|
||||
eventNow: {
|
||||
id: '1',
|
||||
timeStart: 150,
|
||||
dayOffset: 0,
|
||||
},
|
||||
timer: {
|
||||
startedAt: 150,
|
||||
@@ -1029,7 +1056,9 @@ describe('getRuntimeOffset() relative', () => {
|
||||
rundown: {
|
||||
actualStart: 150,
|
||||
plannedStart: 150,
|
||||
currentDay: 0,
|
||||
},
|
||||
_startDayOffset: 0,
|
||||
} as RuntimeState;
|
||||
|
||||
const { absolute, relative } = getRuntimeOffset(state);
|
||||
@@ -1041,6 +1070,7 @@ describe('getRuntimeOffset() relative', () => {
|
||||
eventNow: {
|
||||
id: '1',
|
||||
timeStart: 100,
|
||||
dayOffset: 0,
|
||||
},
|
||||
timer: {
|
||||
startedAt: 150,
|
||||
@@ -1053,7 +1083,9 @@ describe('getRuntimeOffset() relative', () => {
|
||||
rundown: {
|
||||
actualStart: 150,
|
||||
plannedStart: 100,
|
||||
currentDay: 0,
|
||||
},
|
||||
_startDayOffset: 0,
|
||||
} as RuntimeState;
|
||||
|
||||
const { absolute, relative } = getRuntimeOffset(state);
|
||||
@@ -1065,6 +1097,7 @@ describe('getRuntimeOffset() relative', () => {
|
||||
eventNow: {
|
||||
id: '1',
|
||||
timeStart: 150,
|
||||
dayOffset: 0,
|
||||
},
|
||||
timer: {
|
||||
startedAt: 100,
|
||||
@@ -1077,7 +1110,9 @@ describe('getRuntimeOffset() relative', () => {
|
||||
rundown: {
|
||||
actualStart: 100,
|
||||
plannedStart: 150,
|
||||
currentDay: 0,
|
||||
},
|
||||
_startDayOffset: 0,
|
||||
} as RuntimeState;
|
||||
|
||||
const { absolute, relative } = getRuntimeOffset(state);
|
||||
@@ -1258,3 +1293,21 @@ describe('getTimerPhase()', () => {
|
||||
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
|
||||
});
|
||||
});
|
||||
|
||||
@@ -12,6 +12,7 @@ describe('isRestorePoint()', () => {
|
||||
addedTime: 2,
|
||||
pausedAt: 3,
|
||||
firstStart: 1,
|
||||
startEpoch: 1,
|
||||
};
|
||||
expect(isRestorePoint(restorePoint)).toBe(true);
|
||||
|
||||
@@ -22,6 +23,7 @@ describe('isRestorePoint()', () => {
|
||||
addedTime: 0,
|
||||
pausedAt: null,
|
||||
firstStart: 1,
|
||||
startEpoch: 1,
|
||||
};
|
||||
expect(isRestorePoint(restorePoint)).toBe(true);
|
||||
});
|
||||
@@ -35,6 +37,7 @@ describe('isRestorePoint()', () => {
|
||||
addedTime: 0,
|
||||
pausedAt: null,
|
||||
groupStartAt: 10,
|
||||
startEpoch: 1,
|
||||
};
|
||||
expect(isRestorePoint(restorePoint)).toBe(false);
|
||||
});
|
||||
@@ -56,6 +59,7 @@ describe('isRestorePoint()', () => {
|
||||
addedTime: 0,
|
||||
pausedAt: null,
|
||||
groupStartAt: 10,
|
||||
startEpoch: 1,
|
||||
};
|
||||
expect(isRestorePoint(restorePoint)).toBe(false);
|
||||
});
|
||||
|
||||
@@ -16,6 +16,7 @@ describe('restoreService', () => {
|
||||
addedTime: 5678,
|
||||
pausedAt: 9087,
|
||||
firstStart: 1234,
|
||||
startEpoch: 1234,
|
||||
};
|
||||
|
||||
const mockRead = vi.fn().mockResolvedValue(expected);
|
||||
@@ -33,6 +34,7 @@ describe('restoreService', () => {
|
||||
addedTime: 0,
|
||||
pausedAt: null,
|
||||
firstStart: 1234,
|
||||
startEpoch: 1234,
|
||||
};
|
||||
|
||||
const mockRead = vi.fn().mockResolvedValue(expected);
|
||||
@@ -79,6 +81,7 @@ describe('restoreService', () => {
|
||||
addedTime: 1234,
|
||||
pausedAt: 1234,
|
||||
firstStart: 1234,
|
||||
startEpoch: 1234,
|
||||
};
|
||||
|
||||
const mockWrite = vi.fn().mockResolvedValue(undefined);
|
||||
@@ -95,6 +98,7 @@ describe('restoreService', () => {
|
||||
addedTime: 5678,
|
||||
pausedAt: 5678,
|
||||
firstStart: 5678,
|
||||
startEpoch: 5678,
|
||||
};
|
||||
|
||||
const mockWrite = vi.fn().mockRejectedValue(new Error('Write failed'));
|
||||
|
||||
@@ -20,6 +20,7 @@ export function isRestorePoint(restorePoint: unknown): restorePoint is RestorePo
|
||||
'addedTime',
|
||||
'pausedAt',
|
||||
'firstStart',
|
||||
'startEpoch',
|
||||
])
|
||||
) {
|
||||
return false;
|
||||
@@ -49,5 +50,9 @@ export function isRestorePoint(restorePoint: unknown): restorePoint is RestorePo
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!is.number(restorePoint.startEpoch) && restorePoint.startEpoch !== null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -7,4 +7,5 @@ export type RestorePoint = {
|
||||
addedTime: number;
|
||||
pausedAt: MaybeNumber;
|
||||
firstStart: MaybeNumber;
|
||||
startEpoch: MaybeNumber;
|
||||
};
|
||||
|
||||
@@ -743,6 +743,7 @@ function broadcastResult(_target: any, _propertyKey: string, descriptor: Propert
|
||||
addedTime: state.timer.addedTime,
|
||||
pausedAt: state._timer.pausedAt,
|
||||
firstStart: state.rundown.actualStart,
|
||||
startEpoch: state._startEpoch,
|
||||
})
|
||||
.catch((_e) => {
|
||||
//we don't do anything with the error here
|
||||
|
||||
@@ -412,15 +412,19 @@ export async function upload(sheetId: string, options: ImportMap) {
|
||||
},
|
||||
});
|
||||
|
||||
// update the corresponding row with event data
|
||||
sheetOrder.forEach((entryId, index) => {
|
||||
const isGroupEnd = entryId.startsWith('group-end-');
|
||||
const id = isGroupEnd ? entryId.split('group-end-')[1] : entryId;
|
||||
const entry = isGroupEnd
|
||||
try {
|
||||
// update the corresponding row with event data
|
||||
sheetOrder.forEach((entryId, index) => {
|
||||
const isGroupEnd = entryId.startsWith('group-end-');
|
||||
const id = isGroupEnd ? entryId.split('group-end-')[1] : entryId;
|
||||
const entry = isGroupEnd
|
||||
? ({ id: entryId, type: SupportedEntry.Group } as OntimeGroup)
|
||||
: 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({
|
||||
spreadsheetId: sheetId,
|
||||
|
||||
@@ -154,12 +154,6 @@ function getCellData(key: OntimeEntryCommonKeys | 'blank', entry: OntimeEntry) {
|
||||
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
|
||||
if (key === 'timerType') {
|
||||
if (isOntimeGroup(entry))
|
||||
@@ -168,6 +162,17 @@ function getCellData(key: OntimeEntryCommonKeys | 'blank', entry: OntimeEntry) {
|
||||
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
|
||||
// so we check for the key existence and assert the type
|
||||
if (!(key in entry)) return {};
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { MaybeNumber, TimerPhase } from 'ontime-types';
|
||||
import { dayInMs, isPlaybackActive } from 'ontime-utils';
|
||||
import { dayInMs, isPlaybackActive, MILLIS_PER_HOUR } from 'ontime-utils';
|
||||
|
||||
import type { RuntimeState } from '../stores/runtimeState.js';
|
||||
|
||||
/**
|
||||
@@ -113,14 +114,14 @@ export function skippedOutOfEvent(state: RuntimeState, previousTime: number, ski
|
||||
* Negative offset is under time / ahead of schedule
|
||||
*/
|
||||
export function getRuntimeOffset(state: RuntimeState): { absolute: number; relative: number } {
|
||||
const { eventNow, clock } = state;
|
||||
const { eventNow, clock, _startDayOffset } = state;
|
||||
const { addedTime, current, startedAt } = state.timer;
|
||||
// nothing to calculate if there are no loaded events or if we havent started
|
||||
if (eventNow === null || startedAt === null) {
|
||||
if (eventNow === null || startedAt === null || _startDayOffset === null) {
|
||||
return { absolute: 0, relative: 0 };
|
||||
}
|
||||
|
||||
const { countToEnd, timeStart } = eventNow;
|
||||
const { countToEnd, timeStart, dayOffset } = eventNow;
|
||||
const { plannedStart, actualStart } = state.rundown;
|
||||
|
||||
// eslint-disable-next-line no-unused-labels -- dev code path
|
||||
@@ -131,8 +132,8 @@ export function getRuntimeOffset(state: RuntimeState): { absolute: number; relat
|
||||
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 stared behind )
|
||||
const eventStartOffset = startedAt - timeStart;
|
||||
// difference between planned event start and actual event start (will be positive if we started behind)
|
||||
const eventStartOffset = startedAt + _startDayOffset * dayInMs - (timeStart + dayOffset * dayInMs);
|
||||
|
||||
// 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));
|
||||
@@ -140,10 +141,11 @@ 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
|
||||
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;
|
||||
|
||||
// the relative offset i the same as the absolute offset but adjusted relative to the actual start time
|
||||
const relative = absolute + plannedStart - actualStart;
|
||||
// the relative offset is the same as the absolute but adjusted relative to the actual start time
|
||||
const relative = absolute + plannedStart - actualStart - _startDayOffset * dayInMs;
|
||||
|
||||
// in case of count to end, the absolute offset is just the overtime
|
||||
return countToEnd ? { absolute: overtime, relative } : { absolute, relative };
|
||||
@@ -180,3 +182,14 @@ export function getTimerPhase(state: RuntimeState): TimerPhase {
|
||||
|
||||
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,6 +14,8 @@ const baseState: RuntimeState = {
|
||||
plannedStart: 0,
|
||||
plannedEnd: 0,
|
||||
actualStart: null,
|
||||
actualGroupStart: null,
|
||||
currentDay: 0,
|
||||
},
|
||||
offset: {
|
||||
absolute: 0,
|
||||
@@ -46,6 +48,8 @@ const baseState: RuntimeState = {
|
||||
_group: null,
|
||||
_end: null,
|
||||
_flag: null,
|
||||
_startDayOffset: null,
|
||||
_startEpoch: null,
|
||||
};
|
||||
|
||||
export function makeRuntimeStateData(patch?: Partial<RuntimeState>): RuntimeState {
|
||||
|
||||
@@ -393,6 +393,7 @@ describe('loadGroupFlagAndEnd()', () => {
|
||||
const state = {
|
||||
groupNow: null,
|
||||
eventNow: rundown.entries[11],
|
||||
rundown: { actualGroupStart: null },
|
||||
} as RuntimeState;
|
||||
|
||||
const metadata = { playableEventOrder: ['0', '11', '3'], flags: ['1'] } as RundownMetadata;
|
||||
@@ -420,6 +421,7 @@ describe('loadGroupFlagAndEnd()', () => {
|
||||
const state = {
|
||||
groupNow: rundown.entries[1],
|
||||
eventNow: rundown.entries[22],
|
||||
rundown: { actualGroupStart: null },
|
||||
} as RuntimeState;
|
||||
|
||||
const metadata = { playableEventOrder: ['0', '11', '22'], flags: ['1'] } as RundownMetadata;
|
||||
@@ -447,6 +449,7 @@ describe('loadGroupFlagAndEnd()', () => {
|
||||
const state = {
|
||||
groupNow: rundown.entries[1],
|
||||
eventNow: rundown.entries[0],
|
||||
rundown: { actualGroupStart: null },
|
||||
} as RuntimeState;
|
||||
|
||||
const metadata = { playableEventOrder: ['0', '11', '22'], flags: ['1'] } as RundownMetadata;
|
||||
@@ -471,6 +474,7 @@ describe('loadGroupFlagAndEnd()', () => {
|
||||
const state = {
|
||||
groupNow: null,
|
||||
eventNow: rundown.entries[0],
|
||||
rundown: { actualGroupStart: null },
|
||||
} as RuntimeState;
|
||||
|
||||
const metadata = { playableEventOrder: ['0', '1'], flags: ['1'] } as RundownMetadata;
|
||||
|
||||
@@ -23,9 +23,15 @@ import {
|
||||
isPlaybackActive,
|
||||
} from 'ontime-utils';
|
||||
|
||||
import { timeNow } from '../utils/time.js';
|
||||
import { getTimeObject, timeNow } from '../utils/time.js';
|
||||
import type { RestorePoint } from '../services/restore-service/restore.type.js';
|
||||
import { getCurrent, getExpectedFinish, getRuntimeOffset, getTimerPhase } from '../services/timerUtils.js';
|
||||
import {
|
||||
findDayOffset,
|
||||
getCurrent,
|
||||
getExpectedFinish,
|
||||
getRuntimeOffset,
|
||||
getTimerPhase,
|
||||
} from '../services/timerUtils.js';
|
||||
import { loadRoll, normaliseRollStart } from '../services/rollUtils.js';
|
||||
import { timerConfig } from '../setup/config.js';
|
||||
import { RundownMetadata } from '../api-data/rundown/rundown.types.js';
|
||||
@@ -55,6 +61,8 @@ export type RuntimeState = {
|
||||
_group: ExpectedMetadata;
|
||||
_flag: ExpectedMetadata;
|
||||
_end: ExpectedMetadata;
|
||||
_startEpoch: MaybeNumber;
|
||||
_startDayOffset: MaybeNumber;
|
||||
};
|
||||
|
||||
const runtimeState: RuntimeState = {
|
||||
@@ -78,6 +86,8 @@ const runtimeState: RuntimeState = {
|
||||
_group: null,
|
||||
_flag: null,
|
||||
_end: null,
|
||||
_startEpoch: null,
|
||||
_startDayOffset: null,
|
||||
};
|
||||
|
||||
export function getState(): Readonly<RuntimeState> {
|
||||
@@ -152,6 +162,10 @@ export function clearState() {
|
||||
runtimeState._timer.pausedAt = null;
|
||||
runtimeState._timer.secondaryTarget = null;
|
||||
runtimeState._timer.hasFinished = false;
|
||||
|
||||
runtimeState._startEpoch = null;
|
||||
runtimeState._startDayOffset = null;
|
||||
runtimeState.rundown.currentDay = null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -190,7 +204,8 @@ export function updateRundownData(rundownData: {
|
||||
runtimeState.rundown.plannedStart = rundownData.firstStart;
|
||||
runtimeState.rundown.plannedEnd =
|
||||
rundownData.firstStart === null ? null : rundownData.firstStart + rundownData.totalDuration;
|
||||
getExpectedTimes();
|
||||
|
||||
if (isPlaybackActive(runtimeState.timer.playback)) getExpectedTimes();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -229,9 +244,14 @@ export function load(
|
||||
// patch with potential provided data
|
||||
if (initialData) {
|
||||
patchTimer(initialData);
|
||||
const startEpoch = initialData?.startEpoch;
|
||||
const firstStart = initialData?.firstStart;
|
||||
if (firstStart === null || typeof firstStart === 'number') {
|
||||
if (
|
||||
(firstStart === null || typeof firstStart === 'number') &&
|
||||
(startEpoch === null || typeof startEpoch === 'number')
|
||||
) {
|
||||
runtimeState.rundown.actualStart = firstStart;
|
||||
runtimeState._startEpoch = startEpoch;
|
||||
const { absolute, relative } = getRuntimeOffset(runtimeState);
|
||||
runtimeState.offset.absolute = absolute;
|
||||
runtimeState.offset.relative = relative;
|
||||
@@ -367,7 +387,8 @@ export function start(state: RuntimeState = runtimeState): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
state.clock = timeNow();
|
||||
const [epoch, now] = getTimeObject();
|
||||
state.clock = now;
|
||||
state.timer.secondaryTimer = null;
|
||||
|
||||
// add paused time if it exists
|
||||
@@ -386,9 +407,16 @@ export function start(state: RuntimeState = runtimeState): boolean {
|
||||
state.timer.elapsed = 0;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
if (state.groupNow !== null && state.rundown.actualGroupStart === null) {
|
||||
state.rundown.actualGroupStart = state.clock;
|
||||
}
|
||||
|
||||
// update timer phase
|
||||
runtimeState.timer.phase = getTimerPhase(runtimeState);
|
||||
|
||||
@@ -480,13 +508,20 @@ export type UpdateResult = {
|
||||
export function update(): UpdateResult {
|
||||
// 0. there are some things we always do
|
||||
const previousClock = runtimeState.clock;
|
||||
runtimeState.clock = timeNow(); // we update the clock on every update call
|
||||
const [epoch, now] = getTimeObject();
|
||||
runtimeState.clock = now; // we update the clock on every update call
|
||||
|
||||
// 1. is playback idle?
|
||||
if (!isPlaybackActive(runtimeState.timer.playback)) {
|
||||
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?
|
||||
if (runtimeState.timer.playback === Playback.Roll && runtimeState.timer.secondaryTimer !== null) {
|
||||
const hasCrossedMidnight = previousClock > runtimeState.clock;
|
||||
@@ -568,6 +603,10 @@ export function roll(
|
||||
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
|
||||
if (runtimeState.timer.playback === Playback.Armed || runtimeState.timer.phase === TimerPhase.Pending) {
|
||||
// eslint-disable-next-line no-unused-labels -- dev code path
|
||||
@@ -598,12 +637,24 @@ export function roll(
|
||||
// check if the event is ready to start or if needs to be pending
|
||||
const isNow = checkIsNow(runtimeState.eventNow.timeStart, runtimeState.eventNow.timeEnd, offsetClock);
|
||||
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) {
|
||||
runtimeState.rundown.actualStart = runtimeState.clock;
|
||||
runtimeState.rundown.actualStart = plannedStart;
|
||||
runtimeState._startDayOffset = 0;
|
||||
runtimeState._startEpoch = epoch;
|
||||
}
|
||||
runtimeState.timer.secondaryTimer = null;
|
||||
} else {
|
||||
runtimeState._timer.secondaryTarget = normaliseRollStart(runtimeState.eventNow.timeStart, offsetClock);
|
||||
runtimeState.timer.secondaryTimer = runtimeState._timer.secondaryTarget - offsetClock;
|
||||
@@ -658,26 +709,37 @@ export function roll(
|
||||
return { eventId: runtimeState.eventNow.id, didStart: false };
|
||||
}
|
||||
|
||||
// there is something to run, load event
|
||||
// event will finish on time
|
||||
// account for event that finishes the day after
|
||||
/**
|
||||
* At this point we know that there is something to run and the event is loaded
|
||||
* - ensure the event will finish ontime
|
||||
* - 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 =
|
||||
runtimeState.eventNow.timeEnd < runtimeState.eventNow.timeStart
|
||||
? runtimeState.eventNow.timeEnd + dayInMs
|
||||
: runtimeState.eventNow.timeEnd;
|
||||
runtimeState.timer.startedAt = runtimeState.clock;
|
||||
runtimeState.timer.startedAt = plannedStart;
|
||||
runtimeState.timer.expectedFinish = endTime;
|
||||
|
||||
// we add time to allow timer to catch up
|
||||
runtimeState.timer.addedTime = -(runtimeState.clock - runtimeState.eventNow.timeStart);
|
||||
|
||||
// state catch up
|
||||
runtimeState.timer.duration = calculateDuration(runtimeState.eventNow.timeStart, endTime);
|
||||
runtimeState.timer.current = getCurrent(runtimeState);
|
||||
runtimeState.timer.elapsed = 0;
|
||||
|
||||
// update runtime
|
||||
runtimeState.rundown.actualStart = runtimeState.clock;
|
||||
runtimeState.rundown.actualStart = plannedStart;
|
||||
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 };
|
||||
}
|
||||
|
||||
@@ -704,7 +766,7 @@ function getExpectedTimes(state = runtimeState) {
|
||||
if (_group !== null) {
|
||||
const { event: lastEvent, accumulatedGap, isLinkedToLoaded } = _group;
|
||||
const lastEventExpectedStart = getExpectedStart(lastEvent, {
|
||||
currentDay: eventNow.dayOffset,
|
||||
currentDay: state.rundown.currentDay!,
|
||||
totalGap: accumulatedGap,
|
||||
isLinkedToLoaded,
|
||||
mode: offset.mode,
|
||||
@@ -721,7 +783,7 @@ function getExpectedTimes(state = runtimeState) {
|
||||
if (_flag) {
|
||||
const { event, accumulatedGap, isLinkedToLoaded } = _flag;
|
||||
const expectedStart = getExpectedStart(event, {
|
||||
currentDay: eventNow.dayOffset,
|
||||
currentDay: state.rundown.currentDay!,
|
||||
totalGap: accumulatedGap,
|
||||
isLinkedToLoaded,
|
||||
mode: offset.mode,
|
||||
@@ -736,7 +798,7 @@ function getExpectedTimes(state = runtimeState) {
|
||||
if (state._end) {
|
||||
const { event, accumulatedGap, isLinkedToLoaded } = state._end;
|
||||
const expectedStart = getExpectedStart(event, {
|
||||
currentDay: eventNow.dayOffset,
|
||||
currentDay: state.rundown.currentDay!,
|
||||
totalGap: accumulatedGap,
|
||||
isLinkedToLoaded,
|
||||
mode: offset.mode,
|
||||
@@ -752,16 +814,19 @@ export function loadGroupFlagAndEnd(
|
||||
rundown: Rundown,
|
||||
metadata: RundownMetadata,
|
||||
currentIndex: MaybeNumber,
|
||||
state = runtimeState,
|
||||
state = runtimeState, // used for testing
|
||||
) {
|
||||
const previousGroup = state.groupNow?.id;
|
||||
state.groupNow = null;
|
||||
state._group = null;
|
||||
state.eventFlag = null;
|
||||
state._flag = null;
|
||||
state._end = null;
|
||||
|
||||
if (currentIndex == null) return;
|
||||
if (state.eventNow === null) return;
|
||||
if (currentIndex === null || state.eventNow === null) {
|
||||
state.rundown.actualGroupStart = null;
|
||||
return;
|
||||
}
|
||||
|
||||
const currentGroupId = state.eventNow.parent;
|
||||
const flagsPresent = metadata.flags.length !== 0;
|
||||
@@ -773,6 +838,10 @@ export function loadGroupFlagAndEnd(
|
||||
state.groupNow = currentGroupId ? (entries[currentGroupId] as OntimeGroup) : 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
|
||||
let foundFlag = !flagsPresent;
|
||||
// if we don't have a last event for the group there is no need to find its end time
|
||||
@@ -814,4 +883,5 @@ export function loadGroupFlagAndEnd(
|
||||
|
||||
export function setOffsetMode(mode: OffsetMode) {
|
||||
runtimeState.offset.mode = mode;
|
||||
if (isPlaybackActive(runtimeState.timer.playback)) getExpectedTimes();
|
||||
}
|
||||
|
||||
@@ -74,3 +74,18 @@ export function timeNow() {
|
||||
elapsed += now.getMilliseconds();
|
||||
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];
|
||||
}
|
||||
|
||||
@@ -72,4 +72,6 @@ test('time until relative', async ({ page }) => {
|
||||
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-4').getByTestId('rundown-event')).toContainText('20m');
|
||||
|
||||
await page.getByRole('button', { name: 'Absolute' }).click();
|
||||
});
|
||||
|
||||
+4
-3
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "ontime",
|
||||
"version": "4.0.0-beta.2",
|
||||
"version": "4.0.0-beta.3",
|
||||
"description": "Time keeping for live events",
|
||||
"keywords": [
|
||||
"ontime",
|
||||
@@ -33,7 +33,7 @@
|
||||
"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:i": "npx playwright codegen",
|
||||
"cleanup": "rm -rf node_modules && rm -rf **/node_modules && rm -rf **/**/node_modules",
|
||||
"cleanup": "pnpm rimraf node_modules && rimraf **/node_modules && rimraf **/**/node_modules",
|
||||
"clear-temp": "rm -rf e2e/tests/fixtures/tmp"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -47,7 +47,8 @@
|
||||
"eslint-plugin-playwright": "^1.5.2",
|
||||
"prettier": "catalog:",
|
||||
"turbo": "^2.3.3",
|
||||
"typescript": "catalog:"
|
||||
"typescript": "catalog:",
|
||||
"rimraf": "catalog:"
|
||||
},
|
||||
"packageManager": "pnpm@10.11.0+sha512.6540583f41cc5f628eb3d9773ecee802f4f9ef9923cc45b69890fb47991d4b092964694ec3a4f738a420c918a333062c8b925d312f42e4f0c263eb603551f977"
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"version": "4.0.0-beta.3",
|
||||
"name": "ontime-types",
|
||||
"type": "module",
|
||||
"main": "./src/index.ts",
|
||||
@@ -7,7 +8,8 @@
|
||||
"description": "shared typings for ontime",
|
||||
"scripts": {
|
||||
"cleanup": "rm -rf .turbo && rm -rf node_modules",
|
||||
"lint": "eslint . --quiet"
|
||||
"lint": "eslint . --quiet",
|
||||
"build": "tsc"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
@@ -16,6 +18,7 @@
|
||||
"@typescript-eslint/eslint-plugin": "catalog:",
|
||||
"@typescript-eslint/parser": "catalog:",
|
||||
"eslint": "catalog:",
|
||||
"typescript": "catalog:"
|
||||
"typescript": "catalog:",
|
||||
"ts-essentials": "catalog:"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,15 +1,174 @@
|
||||
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 }
|
||||
| { '1': { duration?: number; addtime?: number; direction?: SimpleDirection } }
|
||||
| { '2': SimplePlayback }
|
||||
| { '2': { duration?: number; addtime?: number; direction?: SimpleDirection } }
|
||||
| { '3': SimplePlayback }
|
||||
| { '3': { 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 =
|
||||
| 'version'
|
||||
| 'poll'
|
||||
| 'change'
|
||||
| 'message'
|
||||
| 'start'
|
||||
| 'pause'
|
||||
| 'stop'
|
||||
| 'reload'
|
||||
| 'roll'
|
||||
| 'load'
|
||||
| 'addtime'
|
||||
| 'auxtimer'
|
||||
| 'client'
|
||||
| 'offsetmode';
|
||||
| VersionAction
|
||||
| PollAction
|
||||
| ChangeAction
|
||||
| MessageAction
|
||||
| StartAction
|
||||
| PauseAction
|
||||
| StopAction
|
||||
| ReloadAction
|
||||
| RollAction
|
||||
| LoadAction
|
||||
| AddtimeAction
|
||||
| AuxtimerAction
|
||||
| ClientAction
|
||||
| OffsetmodeAction;
|
||||
|
||||
export type ApiResponse =
|
||||
| VersionResponse
|
||||
| PollResponse
|
||||
| ChangeResponse
|
||||
| MessageResponse
|
||||
| StartResponse
|
||||
| PauseResponse
|
||||
| StopResponse
|
||||
| ReloadResponse
|
||||
| RollResponse
|
||||
| LoadResponse
|
||||
| AddtimeResponse
|
||||
| AuxtimerResponse
|
||||
| ClientResponse
|
||||
| OffsetmodeResponse;
|
||||
|
||||
export type ApiActionTag = ApiAction['tag'];
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
import type {
|
||||
AutomationSettings,
|
||||
CustomFields,
|
||||
ProjectData,
|
||||
ProjectRundowns,
|
||||
Settings,
|
||||
URLPreset,
|
||||
ViewSettings,
|
||||
} from '../index.js';
|
||||
|
||||
import type { AutomationSettings } from './core/Automation.type.js';
|
||||
import type { CustomFields } from './core/CustomFields.type.js';
|
||||
import type { ProjectData } from './core/ProjectData.type.js';
|
||||
import type { ProjectRundowns } from './core/Rundown.type.js';
|
||||
import type { Settings } from './core/Settings.type.js';
|
||||
import type { URLPreset } from './core/UrlPreset.type.js';
|
||||
import type { ViewSettings } from './core/Views.type.js';
|
||||
|
||||
export type DatabaseModel = {
|
||||
rundowns: ProjectRundowns;
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
import type { EndAction, EntryCustomFields, MaybeNumber, TimerType, TimeStrategy, Trigger } from '../../index.js';
|
||||
import type { MaybeNumber } from '../../utils/utils.type.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;
|
||||
|
||||
|
||||
@@ -4,6 +4,8 @@ export type RundownState = {
|
||||
selectedEventIndex: MaybeNumber;
|
||||
numEvents: number;
|
||||
plannedStart: MaybeNumber;
|
||||
actualStart: MaybeNumber;
|
||||
plannedEnd: MaybeNumber;
|
||||
actualStart: MaybeNumber;
|
||||
currentDay: MaybeNumber;
|
||||
actualGroupStart: MaybeNumber;
|
||||
};
|
||||
|
||||
@@ -33,6 +33,8 @@ export const runtimeStorePlaceholder: Readonly<RuntimeStore> = {
|
||||
plannedStart: 0, // only changes if event changes
|
||||
plannedEnd: 0, // only changes if event changes, overflows over dayInMs
|
||||
actualStart: null, // set once we start the timer
|
||||
actualGroupStart: null, // maybe set once we start the timer
|
||||
currentDay: null,
|
||||
},
|
||||
offset: {
|
||||
absolute: 0, // changes at runtime
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { MaybeNumber } from '../../index.js';
|
||||
import type { MaybeNumber } from '../../utils/utils.type.js';
|
||||
import type { Playback } from './Playback.type.js';
|
||||
|
||||
export enum TimerPhase {
|
||||
|
||||
@@ -90,7 +90,7 @@ export type { LinkOptions } from './api/session-controller/BackendResponse.type.
|
||||
export { MessageTag } 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 type { ApiAction } from './api/websocket/api.type.js';
|
||||
export type { ApiAction, ApiActionTag, ApiResponse } from './api/websocket/api.type.js';
|
||||
// SERVER RUNTIME
|
||||
export { type Log, LogLevel, type LogMessage, LogOrigin } from './definitions/runtime/Logger.type.js';
|
||||
export { Playback } from './definitions/runtime/Playback.type.js';
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "esnext",
|
||||
"module": "esnext",
|
||||
"moduleResolution": "node10",
|
||||
"resolveJsonModule": false,
|
||||
"isolatedModules": false,
|
||||
"skipLibCheck": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"strict": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"noImplicitAny": true,
|
||||
"noImplicitReturns": true,
|
||||
"allowUnreachableCode": false,
|
||||
"allowUnusedLabels": false,
|
||||
"noImplicitThis": true,
|
||||
"strictNullChecks": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"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 { dayInMs } from './conversionUtils';
|
||||
import { dayInMs, MILLIS_PER_HOUR } from './conversionUtils';
|
||||
import { getExpectedStart } from './getExpectedStart';
|
||||
|
||||
describe('getExpectedStart()', () => {
|
||||
@@ -277,4 +277,41 @@ describe('getExpectedStart()', () => {
|
||||
// the overlap will be pushed out to the expected available time
|
||||
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(
|
||||
event: Pick<OntimeEvent, 'timeStart' | 'dayOffset' | 'delay'>,
|
||||
state: {
|
||||
currentDay: number;
|
||||
currentDay: number; // the current day from the rundown
|
||||
totalGap: number;
|
||||
isLinkedToLoaded: boolean;
|
||||
offset: number;
|
||||
@@ -38,7 +38,7 @@ export function getExpectedStart(
|
||||
let relativeStartOffset = 0;
|
||||
|
||||
if (mode === OffsetMode.Relative) {
|
||||
relativeStartOffset = (actualStart ?? 0) - (plannedStart ?? 0);
|
||||
relativeStartOffset = (actualStart ?? 0) + currentDay * dayInMs - (plannedStart ?? 0);
|
||||
}
|
||||
|
||||
const scheduledStartTime = normalisedTimeStart + relativeStartOffset;
|
||||
|
||||
@@ -14,7 +14,7 @@ export const defaultImportMap = {
|
||||
title: 'title',
|
||||
countToEnd: 'count to end',
|
||||
skip: 'skip',
|
||||
note: 'notes',
|
||||
note: 'note',
|
||||
colour: 'colour',
|
||||
endAction: 'end action',
|
||||
timerType: 'timer type',
|
||||
|
||||
Generated
+350
-13
@@ -27,6 +27,12 @@ catalogs:
|
||||
prettier:
|
||||
specifier: 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:
|
||||
specifier: 5.5.3
|
||||
version: 5.5.3
|
||||
@@ -65,6 +71,9 @@ importers:
|
||||
prettier:
|
||||
specifier: 'catalog:'
|
||||
version: 3.3.1
|
||||
rimraf:
|
||||
specifier: 'catalog:'
|
||||
version: 6.0.1
|
||||
turbo:
|
||||
specifier: ^2.3.3
|
||||
version: 2.5.6
|
||||
@@ -271,6 +280,30 @@ importers:
|
||||
specifier: ^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:
|
||||
dependencies:
|
||||
'@googleapis/sheets':
|
||||
@@ -377,7 +410,7 @@ importers:
|
||||
specifier: ^0.3.4
|
||||
version: 0.3.4
|
||||
ts-essentials:
|
||||
specifier: ^10.0.3
|
||||
specifier: 'catalog:'
|
||||
version: 10.1.1(typescript@5.5.3)
|
||||
tsx:
|
||||
specifier: ^4.19.2
|
||||
@@ -400,6 +433,9 @@ importers:
|
||||
eslint:
|
||||
specifier: 'catalog:'
|
||||
version: 8.56.0
|
||||
ts-essentials:
|
||||
specifier: 'catalog:'
|
||||
version: 10.1.1(typescript@5.5.3)
|
||||
typescript:
|
||||
specifier: 'catalog:'
|
||||
version: 5.5.3
|
||||
@@ -1575,6 +1611,10 @@ packages:
|
||||
resolution: {integrity: sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==}
|
||||
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':
|
||||
resolution: {integrity: sha512-b9MIk7yhdS1pMCZM8VeNfUlSKVRhsHZNMl5O9SfaX0l0t5wjdgu4IDzGB8bpnGBBOjGST3rRFVsaaEtI4W6f7g==}
|
||||
engines: {node: '>=14'}
|
||||
@@ -2030,6 +2070,9 @@ packages:
|
||||
resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
any-promise@1.3.0:
|
||||
resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==}
|
||||
|
||||
anymatch@3.1.3:
|
||||
resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==}
|
||||
engines: {node: '>= 8'}
|
||||
@@ -2233,6 +2276,12 @@ packages:
|
||||
builder-util@26.0.17:
|
||||
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:
|
||||
resolution: {integrity: sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==}
|
||||
engines: {node: '>=10.16.0'}
|
||||
@@ -2377,6 +2426,10 @@ packages:
|
||||
resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==}
|
||||
engines: {node: '>= 0.8'}
|
||||
|
||||
commander@4.1.1:
|
||||
resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==}
|
||||
engines: {node: '>= 6'}
|
||||
|
||||
commander@5.1.0:
|
||||
resolution: {integrity: sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==}
|
||||
engines: {node: '>= 6'}
|
||||
@@ -2396,9 +2449,16 @@ packages:
|
||||
resolution: {integrity: sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==}
|
||||
engines: {'0': node >= 6.0}
|
||||
|
||||
confbox@0.1.8:
|
||||
resolution: {integrity: sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==}
|
||||
|
||||
config-file-ts@0.2.6:
|
||||
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:
|
||||
resolution: {integrity: sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==}
|
||||
engines: {node: '>= 0.6'}
|
||||
@@ -2985,6 +3045,9 @@ packages:
|
||||
resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
fix-dts-default-cjs-exports@1.0.1:
|
||||
resolution: {integrity: sha512-pVIECanWFC61Hzl2+oOCtoJ3F17kglZC/6N94eRWycFgBH35hHx0Li604ZIzhseh97mf2p0cv7vVrOZGoqhlEg==}
|
||||
|
||||
flat-cache@3.1.1:
|
||||
resolution: {integrity: sha512-/qM2b3LUIaIgviBQovTLvijfyOQXPtSRnRK26ksj2J7rzPIecePUIpJsZ4T02Qg+xiAEKIs5K8dsHEd+VaKa/Q==}
|
||||
engines: {node: '>=12.0.0'}
|
||||
@@ -3126,6 +3189,11 @@ packages:
|
||||
resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==}
|
||||
hasBin: true
|
||||
|
||||
glob@11.0.3:
|
||||
resolution: {integrity: sha512-2Nim7dha1KVkaiF4q6Dj+ngPPMdfvLJEOpZk/jKiUAkqKebpGAWQXAq9z1xu9HKu5lWfqw/FASuccEjyznjPaA==}
|
||||
engines: {node: 20 || >=22}
|
||||
hasBin: true
|
||||
|
||||
glob@7.2.3:
|
||||
resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==}
|
||||
deprecated: Glob versions prior to v9 are no longer supported
|
||||
@@ -3506,6 +3574,10 @@ packages:
|
||||
jackspeak@3.4.3:
|
||||
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:
|
||||
resolution: {integrity: sha512-2P4SQ0HrLQ+fw6llpLnOaGAvN2Zu6778SJMrCUwns4fOoG9ayrTiZk3VV8sCPkVZF8ab0zksVpS8FDY5pRCNBA==}
|
||||
engines: {node: '>=10'}
|
||||
@@ -3518,6 +3590,10 @@ packages:
|
||||
joi@17.13.3:
|
||||
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:
|
||||
resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
|
||||
|
||||
@@ -3597,9 +3673,17 @@ packages:
|
||||
resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==}
|
||||
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:
|
||||
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:
|
||||
resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==}
|
||||
engines: {node: '>=10'}
|
||||
@@ -3619,6 +3703,9 @@ packages:
|
||||
lodash.merge@4.6.2:
|
||||
resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==}
|
||||
|
||||
lodash.sortby@4.7.0:
|
||||
resolution: {integrity: sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA==}
|
||||
|
||||
lodash.union@4.6.0:
|
||||
resolution: {integrity: sha512-c4pB2CdGrGdjMKYLA+XiRDO7Y0PRQbm/Gzg8qMj+QH+pFVAoTp5sBpO0odL3FjoPCGjK96p6qsP+yQoiLoOBcw==}
|
||||
|
||||
@@ -3650,6 +3737,10 @@ packages:
|
||||
lru-cache@10.4.3:
|
||||
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:
|
||||
resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==}
|
||||
|
||||
@@ -3808,6 +3899,9 @@ packages:
|
||||
engines: {node: '>=10'}
|
||||
hasBin: true
|
||||
|
||||
mlly@1.8.0:
|
||||
resolution: {integrity: sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g==}
|
||||
|
||||
ms@2.1.3:
|
||||
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
|
||||
|
||||
@@ -3815,6 +3909,9 @@ packages:
|
||||
resolution: {integrity: sha512-Ug8bXeTIUlxurg8xLTEskKShvcKDZALo1THEX5E41pYCD2sCVub5/kIRIGqWNoqV6szyLyQKV6mD4QUrWE5GCQ==}
|
||||
engines: {node: '>= 10.16.0'}
|
||||
|
||||
mz@2.7.0:
|
||||
resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==}
|
||||
|
||||
nanoid@3.3.11:
|
||||
resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==}
|
||||
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
|
||||
@@ -3999,6 +4096,10 @@ packages:
|
||||
resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==}
|
||||
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:
|
||||
resolution: {integrity: sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ==}
|
||||
engines: {node: '>=16'}
|
||||
@@ -4021,9 +4122,6 @@ packages:
|
||||
pend@1.2.0:
|
||||
resolution: {integrity: sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==}
|
||||
|
||||
picocolors@1.0.1:
|
||||
resolution: {integrity: sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==}
|
||||
|
||||
picocolors@1.1.1:
|
||||
resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
|
||||
|
||||
@@ -4035,6 +4133,13 @@ packages:
|
||||
resolution: {integrity: sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==}
|
||||
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:
|
||||
resolution: {integrity: sha512-GvZs4vU3U5ro2nZpeiwyb0zuFaqb9sUiAJuyrWpcGouD8y9/HLgGbNRjIph7zU9D3hnPaisMl9zG9CgFi/biIg==}
|
||||
engines: {node: '>=18'}
|
||||
@@ -4053,6 +4158,24 @@ packages:
|
||||
resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==}
|
||||
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:
|
||||
resolution: {integrity: sha512-QSa9EBe+uwlGTFmHsPKokv3B/oEMQZxfqW0QqNCyhpa6mB1afzulwn8hihglqAb2pOw+BJgNlmXQ8la2VeHB7w==}
|
||||
engines: {node: ^10 || ^12 || >=14}
|
||||
@@ -4268,6 +4391,10 @@ packages:
|
||||
resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==}
|
||||
engines: {node: '>=4'}
|
||||
|
||||
resolve-from@5.0.0:
|
||||
resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
resolve-pkg-maps@1.0.0:
|
||||
resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==}
|
||||
|
||||
@@ -4300,6 +4427,11 @@ packages:
|
||||
deprecated: Rimraf versions prior to v4 are no longer supported
|
||||
hasBin: true
|
||||
|
||||
rimraf@6.0.1:
|
||||
resolution: {integrity: sha512-9dkvaxAsk/xNXSJzMgFqqMCuFgt2+KsOFek3TMLfo8NCPfWpBmqwyNn5Y+NX56QUYfCtsyhF3ayiboEoUmJk/A==}
|
||||
engines: {node: 20 || >=22}
|
||||
hasBin: true
|
||||
|
||||
roarr@2.15.4:
|
||||
resolution: {integrity: sha512-CHhPh+UNHD2GTXNYhPWLnU8ONHdI+5DI+4EYIAOaiD63rHeYlZvyh8P+in5999TTSFgUYuKUAjzRI4mdh/p+2A==}
|
||||
engines: {node: '>=8.0'}
|
||||
@@ -4498,6 +4630,11 @@ packages:
|
||||
resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==}
|
||||
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:
|
||||
resolution: {integrity: sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==}
|
||||
|
||||
@@ -4580,6 +4717,11 @@ packages:
|
||||
resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==}
|
||||
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:
|
||||
resolution: {integrity: sha512-MvjXzkz/BOfyVDkG0oFOtBxHX2u3gKbMHIF/dXblZsgD3BWOFLmHovIpZY7BykJdAjcqRCBi1WYBNdEC9yI7vg==}
|
||||
engines: {node: '>= 8.0'}
|
||||
@@ -4626,6 +4768,13 @@ packages:
|
||||
text-table@0.2.0:
|
||||
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:
|
||||
resolution: {integrity: sha512-01EAw5EDrcVrdgyCLgoSPvqznC0sVxDSVeiOz09FUpjh71G79VCqneOr+xvt7T1r76CF6ZZfPjHorN2+d+3mqA==}
|
||||
|
||||
@@ -4677,10 +4826,17 @@ packages:
|
||||
tr46@0.0.3:
|
||||
resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==}
|
||||
|
||||
tr46@1.0.1:
|
||||
resolution: {integrity: sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA==}
|
||||
|
||||
tr46@3.0.0:
|
||||
resolution: {integrity: sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
tree-kill@1.2.2:
|
||||
resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==}
|
||||
hasBin: true
|
||||
|
||||
truncate-utf8-bytes@1.0.2:
|
||||
resolution: {integrity: sha512-95Pu1QXQvruGEhv62XCMO3Mm90GscOCClvrIUwCM0PYOXK3kaF3l3sIHxx71ThJfcbM2O5Au6SO3AWCSEfW4mQ==}
|
||||
|
||||
@@ -4704,6 +4860,9 @@ packages:
|
||||
typescript:
|
||||
optional: true
|
||||
|
||||
ts-interface-checker@0.1.13:
|
||||
resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==}
|
||||
|
||||
tsconfck@3.1.6:
|
||||
resolution: {integrity: sha512-ks6Vjr/jEw0P1gmOVwutM3B7fWxoWBL2KRDb1JfqGVawBmO5UsvmWOQFGHBPl5yxYz4eERr19E6L7NMv+Fej4w==}
|
||||
engines: {node: ^18 || >=20}
|
||||
@@ -4717,6 +4876,25 @@ packages:
|
||||
tslib@2.6.2:
|
||||
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:
|
||||
resolution: {integrity: sha512-+wKjMNU9w/EaQayHXb7WA7ZaHY6hN8WgfvHNQ3t1PnU91/7O8TcTnIhCDYTZwnt8JsO9IBqZ30Ln1r7pPF52Aw==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
@@ -4805,6 +4983,9 @@ packages:
|
||||
engines: {node: '>=14.17'}
|
||||
hasBin: true
|
||||
|
||||
ufo@1.6.1:
|
||||
resolution: {integrity: sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA==}
|
||||
|
||||
unbox-primitive@1.1.0:
|
||||
resolution: {integrity: sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==}
|
||||
engines: {node: '>= 0.4'}
|
||||
@@ -4994,6 +5175,9 @@ packages:
|
||||
webidl-conversions@3.0.1:
|
||||
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:
|
||||
resolution: {integrity: sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==}
|
||||
engines: {node: '>=12'}
|
||||
@@ -5020,6 +5204,9 @@ packages:
|
||||
whatwg-url@5.0.0:
|
||||
resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==}
|
||||
|
||||
whatwg-url@7.1.0:
|
||||
resolution: {integrity: sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==}
|
||||
|
||||
which-boxed-primitive@1.1.1:
|
||||
resolution: {integrity: sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==}
|
||||
engines: {node: '>= 0.4'}
|
||||
@@ -5177,7 +5364,7 @@ snapshots:
|
||||
'@babel/code-frame@7.24.2':
|
||||
dependencies:
|
||||
'@babel/highlight': 7.24.5
|
||||
picocolors: 1.0.1
|
||||
picocolors: 1.1.1
|
||||
|
||||
'@babel/code-frame@7.27.1':
|
||||
dependencies:
|
||||
@@ -5252,8 +5439,8 @@ snapshots:
|
||||
'@babel/generator@7.23.6':
|
||||
dependencies:
|
||||
'@babel/types': 7.28.2
|
||||
'@jridgewell/gen-mapping': 0.3.3
|
||||
'@jridgewell/trace-mapping': 0.3.20
|
||||
'@jridgewell/gen-mapping': 0.3.13
|
||||
'@jridgewell/trace-mapping': 0.3.30
|
||||
jsesc: 2.5.2
|
||||
|
||||
'@babel/generator@7.27.5':
|
||||
@@ -5426,7 +5613,7 @@ snapshots:
|
||||
'@babel/helper-validator-identifier': 7.27.1
|
||||
chalk: 2.4.2
|
||||
js-tokens: 4.0.0
|
||||
picocolors: 1.0.1
|
||||
picocolors: 1.1.1
|
||||
|
||||
'@babel/parser@7.23.6':
|
||||
dependencies:
|
||||
@@ -6302,6 +6489,8 @@ snapshots:
|
||||
|
||||
'@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)':
|
||||
dependencies:
|
||||
'@babel/core': 7.23.6
|
||||
@@ -6780,8 +6969,7 @@ snapshots:
|
||||
|
||||
acorn@8.14.0: {}
|
||||
|
||||
acorn@8.15.0:
|
||||
optional: true
|
||||
acorn@8.15.0: {}
|
||||
|
||||
adler-32@1.3.1: {}
|
||||
|
||||
@@ -6831,6 +7019,8 @@ snapshots:
|
||||
|
||||
ansi-styles@6.2.1: {}
|
||||
|
||||
any-promise@1.3.0: {}
|
||||
|
||||
anymatch@3.1.3:
|
||||
dependencies:
|
||||
normalize-path: 3.0.0
|
||||
@@ -7194,6 +7384,11 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- 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:
|
||||
dependencies:
|
||||
streamsearch: 1.1.0
|
||||
@@ -7361,6 +7556,8 @@ snapshots:
|
||||
dependencies:
|
||||
delayed-stream: 1.0.0
|
||||
|
||||
commander@4.1.1: {}
|
||||
|
||||
commander@5.1.0: {}
|
||||
|
||||
compare-version@0.1.2: {}
|
||||
@@ -7381,11 +7578,15 @@ snapshots:
|
||||
readable-stream: 3.6.2
|
||||
typedarray: 0.0.6
|
||||
|
||||
confbox@0.1.8: {}
|
||||
|
||||
config-file-ts@0.2.6:
|
||||
dependencies:
|
||||
glob: 10.4.5
|
||||
typescript: 5.9.2
|
||||
|
||||
consola@3.4.2: {}
|
||||
|
||||
content-disposition@1.0.0:
|
||||
dependencies:
|
||||
safe-buffer: 5.2.1
|
||||
@@ -8191,6 +8392,12 @@ snapshots:
|
||||
locate-path: 6.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:
|
||||
dependencies:
|
||||
flatted: 3.2.9
|
||||
@@ -8362,6 +8569,15 @@ snapshots:
|
||||
package-json-from-dist: 1.0.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:
|
||||
dependencies:
|
||||
fs.realpath: 1.0.0
|
||||
@@ -8780,6 +8996,10 @@ snapshots:
|
||||
optionalDependencies:
|
||||
'@pkgjs/parseargs': 0.11.0
|
||||
|
||||
jackspeak@4.1.1:
|
||||
dependencies:
|
||||
'@isaacs/cliui': 8.0.2
|
||||
|
||||
jake@10.9.2:
|
||||
dependencies:
|
||||
async: 3.2.6
|
||||
@@ -8797,6 +9017,8 @@ snapshots:
|
||||
'@sideway/formula': 3.0.1
|
||||
'@sideway/pinpoint': 2.0.0
|
||||
|
||||
joycon@3.1.1: {}
|
||||
|
||||
js-tokens@4.0.0: {}
|
||||
|
||||
js-yaml@4.1.0:
|
||||
@@ -8899,8 +9121,12 @@ snapshots:
|
||||
prelude-ls: 1.2.1
|
||||
type-check: 0.4.0
|
||||
|
||||
lilconfig@3.1.3: {}
|
||||
|
||||
lines-and-columns@1.2.4: {}
|
||||
|
||||
load-tsconfig@0.2.5: {}
|
||||
|
||||
locate-path@6.0.0:
|
||||
dependencies:
|
||||
p-locate: 5.0.0
|
||||
@@ -8915,6 +9141,8 @@ snapshots:
|
||||
|
||||
lodash.merge@4.6.2: {}
|
||||
|
||||
lodash.sortby@4.7.0: {}
|
||||
|
||||
lodash.union@4.6.0: {}
|
||||
|
||||
lodash@4.17.21: {}
|
||||
@@ -8942,6 +9170,8 @@ snapshots:
|
||||
|
||||
lru-cache@10.4.3: {}
|
||||
|
||||
lru-cache@11.2.1: {}
|
||||
|
||||
lru-cache@5.1.1:
|
||||
dependencies:
|
||||
yallist: 3.1.1
|
||||
@@ -9093,6 +9323,13 @@ snapshots:
|
||||
|
||||
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: {}
|
||||
|
||||
multer@2.0.1:
|
||||
@@ -9105,6 +9342,12 @@ snapshots:
|
||||
type-is: 1.6.18
|
||||
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@5.1.5: {}
|
||||
@@ -9288,6 +9531,11 @@ snapshots:
|
||||
lru-cache: 10.4.3
|
||||
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-type@4.0.0: {}
|
||||
@@ -9300,14 +9548,20 @@ snapshots:
|
||||
|
||||
pend@1.2.0: {}
|
||||
|
||||
picocolors@1.0.1: {}
|
||||
|
||||
picocolors@1.1.1: {}
|
||||
|
||||
picomatch@2.3.1: {}
|
||||
|
||||
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@1.55.0:
|
||||
@@ -9324,6 +9578,14 @@ snapshots:
|
||||
|
||||
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:
|
||||
dependencies:
|
||||
nanoid: 3.3.11
|
||||
@@ -9534,6 +9796,8 @@ snapshots:
|
||||
|
||||
resolve-from@4.0.0: {}
|
||||
|
||||
resolve-from@5.0.0: {}
|
||||
|
||||
resolve-pkg-maps@1.0.0: {}
|
||||
|
||||
resolve@1.22.10:
|
||||
@@ -9565,6 +9829,11 @@ snapshots:
|
||||
dependencies:
|
||||
glob: 7.2.3
|
||||
|
||||
rimraf@6.0.1:
|
||||
dependencies:
|
||||
glob: 11.0.3
|
||||
package-json-from-dist: 1.0.1
|
||||
|
||||
roarr@2.15.4:
|
||||
dependencies:
|
||||
boolean: 3.2.0
|
||||
@@ -9832,6 +10101,10 @@ snapshots:
|
||||
|
||||
source-map@0.6.1: {}
|
||||
|
||||
source-map@0.8.0-beta.0:
|
||||
dependencies:
|
||||
whatwg-url: 7.1.0
|
||||
|
||||
sprintf-js@1.1.3:
|
||||
optional: true
|
||||
|
||||
@@ -9934,6 +10207,16 @@ snapshots:
|
||||
|
||||
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:
|
||||
dependencies:
|
||||
debug: 4.4.1
|
||||
@@ -9988,6 +10271,14 @@ snapshots:
|
||||
|
||||
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:
|
||||
dependencies:
|
||||
semver: 5.7.2
|
||||
@@ -10033,11 +10324,17 @@ snapshots:
|
||||
|
||||
tr46@0.0.3: {}
|
||||
|
||||
tr46@1.0.1:
|
||||
dependencies:
|
||||
punycode: 2.3.1
|
||||
|
||||
tr46@3.0.0:
|
||||
dependencies:
|
||||
punycode: 2.3.1
|
||||
optional: true
|
||||
|
||||
tree-kill@1.2.2: {}
|
||||
|
||||
truncate-utf8-bytes@1.0.2:
|
||||
dependencies:
|
||||
utf8-byte-length: 1.0.4
|
||||
@@ -10054,12 +10351,42 @@ snapshots:
|
||||
optionalDependencies:
|
||||
typescript: 5.5.3
|
||||
|
||||
ts-interface-checker@0.1.13: {}
|
||||
|
||||
tsconfck@3.1.6(typescript@5.5.3):
|
||||
optionalDependencies:
|
||||
typescript: 5.5.3
|
||||
|
||||
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:
|
||||
dependencies:
|
||||
esbuild: 0.25.5
|
||||
@@ -10153,6 +10480,8 @@ snapshots:
|
||||
|
||||
typescript@5.9.2: {}
|
||||
|
||||
ufo@1.6.1: {}
|
||||
|
||||
unbox-primitive@1.1.0:
|
||||
dependencies:
|
||||
call-bound: 1.0.4
|
||||
@@ -10190,7 +10519,7 @@ snapshots:
|
||||
dependencies:
|
||||
browserslist: 4.22.2
|
||||
escalade: 3.1.1
|
||||
picocolors: 1.0.1
|
||||
picocolors: 1.1.1
|
||||
|
||||
update-browserslist-db@1.1.3(browserslist@4.25.0):
|
||||
dependencies:
|
||||
@@ -10363,6 +10692,8 @@ snapshots:
|
||||
|
||||
webidl-conversions@3.0.1: {}
|
||||
|
||||
webidl-conversions@4.0.2: {}
|
||||
|
||||
webidl-conversions@7.0.0:
|
||||
optional: true
|
||||
|
||||
@@ -10389,6 +10720,12 @@ snapshots:
|
||||
tr46: 0.0.3
|
||||
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:
|
||||
dependencies:
|
||||
is-bigint: 1.1.0
|
||||
|
||||
@@ -11,6 +11,8 @@ catalog:
|
||||
eslint-config-prettier: 9.1.0
|
||||
eslint-plugin-prettier: 5.1.3
|
||||
vitest: 3.2.1
|
||||
rimraf: 6.0.1
|
||||
ts-essentials: 10.1.1
|
||||
onlyBuiltDependencies:
|
||||
- electron
|
||||
- esbuild
|
||||
|
||||
@@ -26,6 +26,9 @@
|
||||
"cache": false
|
||||
},
|
||||
"build": {
|
||||
"dependsOn": [
|
||||
"^build"
|
||||
],
|
||||
"env": ["SENTRY_AUTH_TOKEN"]
|
||||
},
|
||||
"build:local": {},
|
||||
|
||||
Reference in New Issue
Block a user