mirror of
https://github.com/cpvalente/ontime.git
synced 2026-08-22 15:39:11 +00:00
chore: rename blocks to groups
This commit is contained in:
committed by
Carlos Valente
parent
486d89ecf4
commit
e5d2457717
@@ -95,14 +95,14 @@ export async function postCloneEntry(entryId: EntryId): Promise<AxiosResponse<Ru
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* HTTP request for dissolving of a block
|
* HTTP request for dissolving of a group
|
||||||
*/
|
*/
|
||||||
export async function requestUngroup(blockId: EntryId): Promise<AxiosResponse<Rundown>> {
|
export async function requestUngroup(groupId: EntryId): Promise<AxiosResponse<Rundown>> {
|
||||||
return axios.post(`${rundownPath}/ungroup/${blockId}`);
|
return axios.post(`${rundownPath}/ungroup/${groupId}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* HTTP request for grouping a list of entries into a block
|
* HTTP request for grouping a list of entries into a group
|
||||||
*/
|
*/
|
||||||
export async function requestGroupEntries(entryIds: EntryId[]): Promise<AxiosResponse<Rundown>> {
|
export async function requestGroupEntries(entryIds: EntryId[]): Promise<AxiosResponse<Rundown>> {
|
||||||
return axios.post(`${rundownPath}/group`, { ids: entryIds });
|
return axios.post(`${rundownPath}/group`, { ids: entryIds });
|
||||||
|
|||||||
@@ -2,13 +2,14 @@ import { useCallback } from 'react';
|
|||||||
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
||||||
import {
|
import {
|
||||||
EntryId,
|
EntryId,
|
||||||
isOntimeBlock,
|
|
||||||
isOntimeEvent,
|
isOntimeEvent,
|
||||||
|
isOntimeGroup,
|
||||||
MaybeString,
|
MaybeString,
|
||||||
OntimeBlock,
|
|
||||||
OntimeEntry,
|
OntimeEntry,
|
||||||
OntimeEvent,
|
OntimeEvent,
|
||||||
|
OntimeGroup,
|
||||||
Rundown,
|
Rundown,
|
||||||
|
SupportedEntry,
|
||||||
TimeField,
|
TimeField,
|
||||||
TimeStrategy,
|
TimeStrategy,
|
||||||
TransientEventPayload,
|
TransientEventPayload,
|
||||||
@@ -36,7 +37,7 @@ import { logAxiosError } from '../api/utils';
|
|||||||
import { useEditorSettings } from '../stores/editorSettings';
|
import { useEditorSettings } from '../stores/editorSettings';
|
||||||
|
|
||||||
export type EventOptions = Partial<{
|
export type EventOptions = Partial<{
|
||||||
// options of any new entries (event / delay / block)
|
// options of any new entries (event / delay / group)
|
||||||
after: MaybeString;
|
after: MaybeString;
|
||||||
before: MaybeString;
|
before: MaybeString;
|
||||||
// options of entries of type OntimeEvent
|
// options of entries of type OntimeEvent
|
||||||
@@ -552,7 +553,7 @@ export const useEntryActions = () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Calls mutation to dissolve a block
|
* Calls mutation to dissolve a group
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
const { mutateAsync: ungroupMutation } = useMutation({
|
const { mutateAsync: ungroupMutation } = useMutation({
|
||||||
@@ -574,12 +575,12 @@ export const useEntryActions = () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Deletes a block and moves its events to the top level
|
* Deletes a group and moves its events to the top level
|
||||||
*/
|
*/
|
||||||
const ungroup = useCallback(
|
const ungroup = useCallback(
|
||||||
async (blockId: EntryId) => {
|
async (groupId: EntryId) => {
|
||||||
try {
|
try {
|
||||||
await ungroupMutation(blockId);
|
await ungroupMutation(groupId);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logAxiosError('Error dissolving group', error);
|
logAxiosError('Error dissolving group', error);
|
||||||
}
|
}
|
||||||
@@ -588,7 +589,7 @@ export const useEntryActions = () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Calls mutation to create a block with a selection
|
* Calls mutation to create a group with a selection
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
const { mutateAsync: groupEntriesMutation } = useMutation({
|
const { mutateAsync: groupEntriesMutation } = useMutation({
|
||||||
@@ -610,7 +611,7 @@ export const useEntryActions = () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a block with a selection
|
* Create a group with a selection
|
||||||
*/
|
*/
|
||||||
const groupEntries = useCallback(
|
const groupEntries = useCallback(
|
||||||
async (entryIds: EntryId[]) => {
|
async (entryIds: EntryId[]) => {
|
||||||
@@ -674,8 +675,8 @@ export const useEntryActions = () => {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
logAxiosError('Error re-ordering event', error);
|
logAxiosError('Error re-ordering event', error);
|
||||||
}
|
}
|
||||||
// the rundown needs to know whether we moved into a block
|
// the rundown needs to know whether we moved into a group
|
||||||
return rundown.entries[destinationId]?.type === 'block' ? destinationId : undefined;
|
return rundown.entries[destinationId]?.type === SupportedEntry.Group ? destinationId : undefined;
|
||||||
},
|
},
|
||||||
[queryClient, reorderEntryMutation],
|
[queryClient, reorderEntryMutation],
|
||||||
);
|
);
|
||||||
@@ -798,12 +799,12 @@ function optimisticDeleteEntries(entryIds: EntryId[], rundown: Rundown) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function deleteEntry(entry: OntimeEntry) {
|
function deleteEntry(entry: OntimeEntry) {
|
||||||
if (isOntimeBlock(entry) || !entry.parent) {
|
if (isOntimeGroup(entry) || !entry.parent) {
|
||||||
order = order.filter((id) => id !== entry.id);
|
order = order.filter((id) => id !== entry.id);
|
||||||
} else {
|
} else {
|
||||||
const parent = entries[entry.parent];
|
const parent = entries[entry.parent];
|
||||||
if ('parent' in entries) {
|
if ('parent' in entries) {
|
||||||
(parent as OntimeBlock).entries = (parent as OntimeBlock).entries.filter(
|
(parent as OntimeGroup).entries = (parent as OntimeGroup).entries.filter(
|
||||||
(parentEntry) => parentEntry !== entry.id,
|
(parentEntry) => parentEntry !== entry.id,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ export const setClientRemote = {
|
|||||||
export const useRundownEditor = createSelector((state: RuntimeStore) => ({
|
export const useRundownEditor = createSelector((state: RuntimeStore) => ({
|
||||||
playback: state.timer.playback,
|
playback: state.timer.playback,
|
||||||
selectedEventId: state.eventNow?.id ?? null,
|
selectedEventId: state.eventNow?.id ?? null,
|
||||||
selectedBlockId: state.blockNow?.id ?? null,
|
|
||||||
nextEventId: state.eventNext?.id ?? null,
|
nextEventId: state.eventNext?.id ?? null,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
@@ -131,8 +130,8 @@ export const useSelectedEventId = createSelector((state: RuntimeStore) => ({
|
|||||||
selectedEventId: state.eventNow?.id ?? null,
|
selectedEventId: state.eventNow?.id ?? null,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
export const useCurrentBlockId = createSelector((state: RuntimeStore) => ({
|
export const useCurrentGroupId = createSelector((state: RuntimeStore) => ({
|
||||||
currentBlockId: state.blockNow?.id ?? null,
|
currentGroupId: state.groupNow?.id ?? null,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
export const setEventPlayback = {
|
export const setEventPlayback = {
|
||||||
@@ -178,7 +177,7 @@ export const useRuntimePlaybackOverview = createSelector((state: RuntimeStore) =
|
|||||||
selectedEventIndex: state.runtime.selectedEventIndex,
|
selectedEventIndex: state.runtime.selectedEventIndex,
|
||||||
offset: state.runtime.offsetMode === OffsetMode.Absolute ? state.runtime.offsetAbs : state.runtime.offsetRel,
|
offset: state.runtime.offsetMode === OffsetMode.Absolute ? state.runtime.offsetAbs : state.runtime.offsetRel,
|
||||||
|
|
||||||
blockExpectedEnd: state.blockNow?.expectedEnd ?? null,
|
groupExpectedEnd: state.groupNow?.expectedEnd ?? null,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
export const useTimelineStatus = createSelector((state: RuntimeStore) => ({
|
export const useTimelineStatus = createSelector((state: RuntimeStore) => ({
|
||||||
|
|||||||
+2
-2
@@ -25,8 +25,8 @@ const staticAutocompleteOptions = [
|
|||||||
'{{runtime.plannedEnd}}',
|
'{{runtime.plannedEnd}}',
|
||||||
'{{runtime.actualStart}}',
|
'{{runtime.actualStart}}',
|
||||||
'{{runtime.expectedEnd}}',
|
'{{runtime.expectedEnd}}',
|
||||||
'{{currentBlock.block}}',
|
'{{currentGroup.id}}',
|
||||||
'{{currentBlock.startedAt}}',
|
'{{currentGroup.startedAt}}',
|
||||||
];
|
];
|
||||||
|
|
||||||
const eventStaticPropertiesNow = [
|
const eventStaticPropertiesNow = [
|
||||||
|
|||||||
+2
-2
@@ -1,6 +1,6 @@
|
|||||||
import { Fragment } from 'react';
|
import { Fragment } from 'react';
|
||||||
import { IoLink } from 'react-icons/io5';
|
import { IoLink } from 'react-icons/io5';
|
||||||
import { CustomFields, isOntimeBlock, isOntimeEvent, Rundown } from 'ontime-types';
|
import { CustomFields, isOntimeEvent, isOntimeGroup, Rundown } from 'ontime-types';
|
||||||
import { millisToString } from 'ontime-utils';
|
import { millisToString } from 'ontime-utils';
|
||||||
|
|
||||||
import Tag from '../../../../../../common/components/tag/Tag';
|
import Tag from '../../../../../../common/components/tag/Tag';
|
||||||
@@ -55,7 +55,7 @@ export default function PreviewRundown(props: PreviewRundownProps) {
|
|||||||
<tbody>
|
<tbody>
|
||||||
{rundown.order.map((entryId) => {
|
{rundown.order.map((entryId) => {
|
||||||
const entry = rundown.entries[entryId];
|
const entry = rundown.entries[entryId];
|
||||||
if (isOntimeBlock(entry)) {
|
if (isOntimeGroup(entry)) {
|
||||||
return (
|
return (
|
||||||
<tr key={entry.id}>
|
<tr key={entry.id}>
|
||||||
<td className={style.center}>
|
<td className={style.center}>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Fragment, useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
import { Fragment, useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||||
import { isOntimeBlock, isOntimeEvent, OntimeView } from 'ontime-types';
|
import { isOntimeEvent, isOntimeGroup, OntimeView } from 'ontime-types';
|
||||||
|
|
||||||
import EmptyPage from '../../common/components/state/EmptyPage';
|
import EmptyPage from '../../common/components/state/EmptyPage';
|
||||||
import ViewParamsEditor from '../../common/components/view-params-editor/ViewParamsEditor';
|
import ViewParamsEditor from '../../common/components/view-params-editor/ViewParamsEditor';
|
||||||
@@ -16,8 +16,8 @@ import { getDefaultFormat } from '../../common/utils/time';
|
|||||||
|
|
||||||
import EditModal from './edit-modal/EditModal';
|
import EditModal from './edit-modal/EditModal';
|
||||||
import FollowButton from './follow-button/FollowButton';
|
import FollowButton from './follow-button/FollowButton';
|
||||||
import OperatorBlock from './operator-block/OperatorBlock';
|
|
||||||
import OperatorEvent from './operator-event/OperatorEvent';
|
import OperatorEvent from './operator-event/OperatorEvent';
|
||||||
|
import OperatorGroup from './operator-group/OperatorGroup';
|
||||||
import StatusBar from './status-bar/StatusBar';
|
import StatusBar from './status-bar/StatusBar';
|
||||||
import { getOperatorOptions, useOperatorOptions } from './operator.options';
|
import { getOperatorOptions, useOperatorOptions } from './operator.options';
|
||||||
import type { EditEvent } from './operator.types';
|
import type { EditEvent } from './operator.types';
|
||||||
@@ -168,10 +168,10 @@ export default function Operator() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isOntimeBlock(entry)) {
|
if (isOntimeGroup(entry)) {
|
||||||
return (
|
return (
|
||||||
<Fragment key={entry.id}>
|
<Fragment key={entry.id}>
|
||||||
<OperatorBlock key={entry.id} title={entry.title} />
|
<OperatorGroup key={entry.id} title={entry.title} />
|
||||||
{entry.entries.map((nestedEntryId) => {
|
{entry.entries.map((nestedEntryId) => {
|
||||||
const nestedEntry = data.entries[nestedEntryId];
|
const nestedEntry = data.entries[nestedEntryId];
|
||||||
if (!isOntimeEvent(nestedEntry)) {
|
if (!isOntimeEvent(nestedEntry)) {
|
||||||
|
|||||||
@@ -1,13 +0,0 @@
|
|||||||
import { memo } from 'react';
|
|
||||||
|
|
||||||
import style from './OperatorBlock.module.scss';
|
|
||||||
|
|
||||||
interface OperatorBlockProps {
|
|
||||||
title: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
function OperatorBlock({ title }: OperatorBlockProps) {
|
|
||||||
return <div className={style.block}>{title}</div>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default memo(OperatorBlock);
|
|
||||||
+2
-2
@@ -1,4 +1,4 @@
|
|||||||
.block {
|
.group {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding: 0.25rem 0.5rem;
|
padding: 0.25rem 0.5rem;
|
||||||
background-color: $gray-1350;
|
background-color: $gray-1350;
|
||||||
@@ -7,7 +7,7 @@
|
|||||||
|
|
||||||
// tablet
|
// tablet
|
||||||
@media (min-width: $min-tablet) {
|
@media (min-width: $min-tablet) {
|
||||||
.block {
|
.group {
|
||||||
padding: 0.25rem 1rem;
|
padding: 0.25rem 1rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
import { memo } from 'react';
|
||||||
|
|
||||||
|
import style from './OperatorGroup.module.scss';
|
||||||
|
|
||||||
|
interface OperatorGroup {
|
||||||
|
title: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default memo(OperatorGroup);
|
||||||
|
function OperatorGroup({ title }: OperatorGroup) {
|
||||||
|
return <div className={style.group}>{title}</div>;
|
||||||
|
}
|
||||||
@@ -8,13 +8,13 @@ import {
|
|||||||
TbFolderPin,
|
TbFolderPin,
|
||||||
TbFolderStar,
|
TbFolderStar,
|
||||||
} from 'react-icons/tb';
|
} from 'react-icons/tb';
|
||||||
import { OntimeBlock, OntimeEvent, TimerPhase, TimerType } from 'ontime-types';
|
import { OntimeEvent, OntimeGroup, TimerPhase, TimerType } from 'ontime-types';
|
||||||
import { isPlaybackActive, millisToString } from 'ontime-utils';
|
import { isPlaybackActive, millisToString } from 'ontime-utils';
|
||||||
|
|
||||||
import Tooltip from '../../../common/components/tooltip/Tooltip';
|
import Tooltip from '../../../common/components/tooltip/Tooltip';
|
||||||
import {
|
import {
|
||||||
useClock,
|
useClock,
|
||||||
useCurrentBlockId,
|
useCurrentGroupId,
|
||||||
useNextFlag,
|
useNextFlag,
|
||||||
useRuntimeOverview,
|
useRuntimeOverview,
|
||||||
useRuntimePlaybackOverview,
|
useRuntimePlaybackOverview,
|
||||||
@@ -98,15 +98,15 @@ 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
|
//TODO: there a some things here we still need to think about, mainly what to do whit the planed group duration in relation to the events
|
||||||
function GroupTimes() {
|
function GroupTimes() {
|
||||||
const { clock, blockExpectedEnd } = useRuntimePlaybackOverview();
|
const { clock, groupExpectedEnd } = useRuntimePlaybackOverview();
|
||||||
const { currentBlockId } = useCurrentBlockId();
|
const { currentGroupId } = useCurrentGroupId();
|
||||||
const group = useEntry(currentBlockId) as OntimeBlock | null;
|
const group = useEntry(currentGroupId) as OntimeGroup | null;
|
||||||
|
|
||||||
// the group end time dose not encode any day offsets
|
// the group end time dose not encode any day offsets
|
||||||
const plannedGroupEnd = group && group.timeStart !== null ? group.timeStart + group.duration - clock : null;
|
const plannedGroupEnd = group && group.timeStart !== null ? group.timeStart + group.duration - clock : null;
|
||||||
const plannedTimeUntilGroupEnd = formattedTime(plannedGroupEnd, 3, TimerType.CountDown);
|
const plannedTimeUntilGroupEnd = formattedTime(plannedGroupEnd, 3, TimerType.CountDown);
|
||||||
|
|
||||||
const expectedGroupEnd = blockExpectedEnd !== null ? blockExpectedEnd - clock : null;
|
const expectedGroupEnd = groupExpectedEnd !== null ? groupExpectedEnd - clock : null;
|
||||||
const expectedTimeUntilGroupEnd = formattedTime(expectedGroupEnd, 3, TimerType.CountDown);
|
const expectedTimeUntilGroupEnd = formattedTime(expectedGroupEnd, 3, TimerType.CountDown);
|
||||||
|
|
||||||
const groupTitle = group?.title ?? null;
|
const groupTitle = group?.title ?? null;
|
||||||
@@ -120,7 +120,7 @@ function GroupTimes() {
|
|||||||
</div>
|
</div>
|
||||||
<div className={style.labelledElement}>
|
<div className={style.labelledElement}>
|
||||||
<Tooltip text='Time to expected group end' render={<TbFolderStar className={style.icon} />} />
|
<Tooltip text='Time to expected group end' render={<TbFolderStar className={style.icon} />} />
|
||||||
<span className={cx([style.time, blockExpectedEnd === null && style.muted])}>{expectedTimeUntilGroupEnd}</span>
|
<span className={cx([style.time, groupExpectedEnd === null && style.muted])}>{expectedTimeUntilGroupEnd}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -16,8 +16,8 @@ import {
|
|||||||
type EntryId,
|
type EntryId,
|
||||||
type MaybeString,
|
type MaybeString,
|
||||||
type Rundown,
|
type Rundown,
|
||||||
isOntimeBlock,
|
|
||||||
isOntimeEvent,
|
isOntimeEvent,
|
||||||
|
isOntimeGroup,
|
||||||
OntimeEntry,
|
OntimeEntry,
|
||||||
Playback,
|
Playback,
|
||||||
SupportedEntry,
|
SupportedEntry,
|
||||||
@@ -25,9 +25,9 @@ import {
|
|||||||
import {
|
import {
|
||||||
getFirstNormal,
|
getFirstNormal,
|
||||||
getLastNormal,
|
getLastNormal,
|
||||||
getNextBlockNormal,
|
getNextGroupNormal,
|
||||||
getNextNormal,
|
getNextNormal,
|
||||||
getPreviousBlockNormal,
|
getPreviousGroupNormal,
|
||||||
getPreviousNormal,
|
getPreviousNormal,
|
||||||
reorderArray,
|
reorderArray,
|
||||||
} from 'ontime-utils';
|
} from 'ontime-utils';
|
||||||
@@ -41,8 +41,8 @@ import { AppMode, sessionKeys } from '../../ontimeConfig';
|
|||||||
|
|
||||||
import QuickAddButtons from './entry-editor/quick-add-buttons/QuickAddButtons';
|
import QuickAddButtons from './entry-editor/quick-add-buttons/QuickAddButtons';
|
||||||
import QuickAddInline from './entry-editor/quick-add-cursor/QuickAddInline';
|
import QuickAddInline from './entry-editor/quick-add-cursor/QuickAddInline';
|
||||||
import RundownBlock from './rundown-block/RundownBlock';
|
import RundownGroup from './rundown-group/RundownGroup';
|
||||||
import RundownBlockEnd from './rundown-block/RundownBlockEnd';
|
import RundownGroupEnd from './rundown-group/RundownGroupEnd';
|
||||||
import { canDrop, makeRundownMetadata, makeSortableList } from './rundown.utils';
|
import { canDrop, makeRundownMetadata, makeSortableList } from './rundown.utils';
|
||||||
import RundownEmpty from './RundownEmpty';
|
import RundownEmpty from './RundownEmpty';
|
||||||
import { useEventSelection } from './useEventSelection';
|
import { useEventSelection } from './useEventSelection';
|
||||||
@@ -127,7 +127,7 @@ export default function Rundown({ data }: RundownProps) {
|
|||||||
[addEntry],
|
[addEntry],
|
||||||
);
|
);
|
||||||
|
|
||||||
const selectBlock = useCallback(
|
const selectGroup = useCallback(
|
||||||
(cursor: string | null, direction: 'up' | 'down') => {
|
(cursor: string | null, direction: 'up' | 'down') => {
|
||||||
if (order.length < 1) {
|
if (order.length < 1) {
|
||||||
return;
|
return;
|
||||||
@@ -137,7 +137,7 @@ export default function Rundown({ data }: RundownProps) {
|
|||||||
// there is no cursor, we select the first or last depending on direction
|
// there is no cursor, we select the first or last depending on direction
|
||||||
const selected = direction === 'up' ? getLastNormal(entries, order) : getFirstNormal(entries, order);
|
const selected = direction === 'up' ? getLastNormal(entries, order) : getFirstNormal(entries, order);
|
||||||
|
|
||||||
if (isOntimeBlock(selected)) {
|
if (isOntimeGroup(selected)) {
|
||||||
setSelectedEvents({ id: selected.id, selectMode: 'click', index: direction === 'up' ? order.length : 0 });
|
setSelectedEvents({ id: selected.id, selectMode: 'click', index: direction === 'up' ? order.length : 0 });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -151,8 +151,8 @@ export default function Rundown({ data }: RundownProps) {
|
|||||||
// otherwise we select the next or previous
|
// otherwise we select the next or previous
|
||||||
const selected =
|
const selected =
|
||||||
direction === 'up'
|
direction === 'up'
|
||||||
? getPreviousBlockNormal(entries, order, newCursor)
|
? getPreviousGroupNormal(entries, order, newCursor)
|
||||||
: getNextBlockNormal(entries, order, newCursor);
|
: getNextGroupNormal(entries, order, newCursor);
|
||||||
|
|
||||||
if (selected.entry !== null && selected.index !== null) {
|
if (selected.entry !== null && selected.index !== null) {
|
||||||
setSelectedEvents({ id: selected.entry.id, selectMode: 'click', index: selected.index });
|
setSelectedEvents({ id: selected.entry.id, selectMode: 'click', index: selected.index });
|
||||||
@@ -188,11 +188,11 @@ export default function Rundown({ data }: RundownProps) {
|
|||||||
);
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks whether a block is collapsed
|
* Checks whether a group is collapsed
|
||||||
*/
|
*/
|
||||||
const getIsCollapsed = useCallback(
|
const getIsCollapsed = useCallback(
|
||||||
(blockId: EntryId): boolean => {
|
(groupId: EntryId): boolean => {
|
||||||
return Boolean(collapsedGroups.find((id) => id === blockId));
|
return Boolean(collapsedGroups.find((id) => id === groupId));
|
||||||
},
|
},
|
||||||
[collapsedGroups],
|
[collapsedGroups],
|
||||||
);
|
);
|
||||||
@@ -223,10 +223,10 @@ export default function Rundown({ data }: RundownProps) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const movedIntoBlockId = await move(cursor, direction);
|
const movedIntoGroupId = await move(cursor, direction);
|
||||||
// if we are moving into a block, we need to make sure it is expanded
|
// if we are moving into a group, we need to make sure it is expanded
|
||||||
if (movedIntoBlockId) {
|
if (movedIntoGroupId) {
|
||||||
handleCollapseGroup(false, movedIntoBlockId);
|
handleCollapseGroup(false, movedIntoGroupId);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[handleCollapseGroup, move],
|
[handleCollapseGroup, move],
|
||||||
@@ -237,8 +237,8 @@ export default function Rundown({ data }: RundownProps) {
|
|||||||
['alt + ArrowDown', () => selectEntry(cursor, 'down'), { preventDefault: true, usePhysicalKeys: true }],
|
['alt + ArrowDown', () => selectEntry(cursor, 'down'), { preventDefault: true, usePhysicalKeys: true }],
|
||||||
['alt + ArrowUp', () => selectEntry(cursor, 'up'), { preventDefault: true, usePhysicalKeys: true }],
|
['alt + ArrowUp', () => selectEntry(cursor, 'up'), { preventDefault: true, usePhysicalKeys: true }],
|
||||||
|
|
||||||
['alt + shift + ArrowDown', () => selectBlock(cursor, 'down'), { preventDefault: true, usePhysicalKeys: true }],
|
['alt + shift + ArrowDown', () => selectGroup(cursor, 'down'), { preventDefault: true, usePhysicalKeys: true }],
|
||||||
['alt + shift + ArrowUp', () => selectBlock(cursor, 'up'), { preventDefault: true, usePhysicalKeys: true }],
|
['alt + shift + ArrowUp', () => selectGroup(cursor, 'up'), { preventDefault: true, usePhysicalKeys: true }],
|
||||||
|
|
||||||
['alt + mod + ArrowDown', () => moveEntry(cursor, 'down'), { preventDefault: true, usePhysicalKeys: true }],
|
['alt + mod + ArrowDown', () => moveEntry(cursor, 'down'), { preventDefault: true, usePhysicalKeys: true }],
|
||||||
['alt + mod + ArrowUp', () => moveEntry(cursor, 'up'), { preventDefault: true, usePhysicalKeys: true }],
|
['alt + mod + ArrowUp', () => moveEntry(cursor, 'up'), { preventDefault: true, usePhysicalKeys: true }],
|
||||||
@@ -260,12 +260,12 @@ export default function Rundown({ data }: RundownProps) {
|
|||||||
|
|
||||||
[
|
[
|
||||||
'alt + G',
|
'alt + G',
|
||||||
() => insertAtId({ type: SupportedEntry.Block }, cursor),
|
() => insertAtId({ type: SupportedEntry.Group }, cursor),
|
||||||
{ preventDefault: true, usePhysicalKeys: true },
|
{ preventDefault: true, usePhysicalKeys: true },
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'alt + shift + G',
|
'alt + shift + G',
|
||||||
() => insertAtId({ type: SupportedEntry.Block }, cursor, true),
|
() => insertAtId({ type: SupportedEntry.Group }, cursor, true),
|
||||||
{ preventDefault: true, usePhysicalKeys: true },
|
{ preventDefault: true, usePhysicalKeys: true },
|
||||||
],
|
],
|
||||||
|
|
||||||
@@ -335,7 +335,10 @@ export default function Rundown({ data }: RundownProps) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// prevent dropping a group inside another
|
// prevent dropping a group inside another
|
||||||
if (active.data.current?.type === 'block' && !canDrop(over.data.current?.type, over.data.current?.parent)) {
|
if (
|
||||||
|
active.data.current?.type === SupportedEntry.Group &&
|
||||||
|
!canDrop(over.data.current?.type, over.data.current?.parent)
|
||||||
|
) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -346,10 +349,10 @@ export default function Rundown({ data }: RundownProps) {
|
|||||||
let order: 'before' | 'after' | 'insert' = fromIndex < toIndex ? 'after' : 'before';
|
let order: 'before' | 'after' | 'insert' = fromIndex < toIndex ? 'after' : 'before';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* We need to specially handle the end blocks
|
* We need to specially handle the end-group
|
||||||
* Dragging before and end block will add the entry to the end of the block
|
* Dragging before a end-group will add the entry to the end of the group
|
||||||
* Dragging after an end block will add the event after the block itself
|
* Dragging after a end-group will add the event after the group itself
|
||||||
* Dragging to the top of a block either place before first entry or if no entries do insert
|
* Dragging to the top of a group either place before first entry or if no entries do insert
|
||||||
*/
|
*/
|
||||||
if (destinationId.startsWith('end-')) {
|
if (destinationId.startsWith('end-')) {
|
||||||
destinationId = destinationId.replace('end-', '');
|
destinationId = destinationId.replace('end-', '');
|
||||||
@@ -358,11 +361,11 @@ export default function Rundown({ data }: RundownProps) {
|
|||||||
order = 'insert';
|
order = 'insert';
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const block = data.entries[destinationId];
|
const group = data.entries[destinationId];
|
||||||
if (isOntimeBlock(block) && order === 'after') {
|
if (isOntimeGroup(group) && order === 'after') {
|
||||||
if (block.entries.length === 0) order = 'insert';
|
if (group.entries.length === 0) order = 'insert';
|
||||||
else {
|
else {
|
||||||
destinationId = block.entries[0];
|
destinationId = group.entries[0];
|
||||||
order = 'before';
|
order = 'before';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -380,31 +383,31 @@ export default function Rundown({ data }: RundownProps) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* When we drag a block, we force collapse it
|
* When we drag a group, we force collapse it
|
||||||
* This avoids strange scenarios like dropping a block inside itself
|
* This avoids strange scenarios like dropping a group inside itself
|
||||||
*/
|
*/
|
||||||
const collapseDraggedBlocks = (event: DragStartEvent) => {
|
const collapseDraggedGroups = (event: DragStartEvent) => {
|
||||||
const isBlock = event.active.data.current?.type === 'block';
|
const isGroup = event.active.data.current?.type === SupportedEntry.Group;
|
||||||
if (isBlock) {
|
if (isGroup) {
|
||||||
handleCollapseGroup(true, event.active.id as EntryId);
|
handleCollapseGroup(true, event.active.id as EntryId);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* When we drag over a block, we expand it if it is collapsed
|
* When we drag over a group, we expand it if it is collapsed
|
||||||
*/
|
*/
|
||||||
const expandOverBlock = (event: DragOverEvent) => {
|
const expandOverGroup = (event: DragOverEvent) => {
|
||||||
// if we are dragging a block, the drop operation is invalid so we dont expand
|
// if we are dragging a group, the drop operation is invalid so we dont expand
|
||||||
if (event.active.data.current?.type === 'block') {
|
if (event.active.data.current?.type === 'group') {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (event.over?.data.current?.type !== 'block') {
|
if (event.over?.data.current?.type !== 'group') {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const blockId = event.over?.id as EntryId;
|
const groupId = event.over?.id as EntryId;
|
||||||
const isCollapsed = getIsCollapsed(blockId);
|
const isCollapsed = getIsCollapsed(groupId);
|
||||||
if (isCollapsed) {
|
if (isCollapsed) {
|
||||||
handleCollapseGroup(false, blockId);
|
handleCollapseGroup(false, groupId);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -424,37 +427,37 @@ export default function Rundown({ data }: RundownProps) {
|
|||||||
<div className={style.rundownContainer} ref={scrollRef} data-testid='rundown'>
|
<div className={style.rundownContainer} ref={scrollRef} data-testid='rundown'>
|
||||||
<DndContext
|
<DndContext
|
||||||
onDragEnd={handleOnDragEnd}
|
onDragEnd={handleOnDragEnd}
|
||||||
onDragStart={collapseDraggedBlocks}
|
onDragStart={collapseDraggedGroups}
|
||||||
onDragOver={expandOverBlock}
|
onDragOver={expandOverGroup}
|
||||||
sensors={sensors}
|
sensors={sensors}
|
||||||
collisionDetection={closestCenter}
|
collisionDetection={closestCenter}
|
||||||
>
|
>
|
||||||
<SortableContext items={sortableData} strategy={verticalListSortingStrategy}>
|
<SortableContext items={sortableData} strategy={verticalListSortingStrategy}>
|
||||||
<div className={style.list}>
|
<div className={style.list}>
|
||||||
{isEditMode && <QuickAddButtons previousEventId={null} parentBlock={null} />}
|
{isEditMode && <QuickAddButtons previousEventId={null} parentGroup={null} />}
|
||||||
{sortableData.map((entryId, index) => {
|
{sortableData.map((entryId, index) => {
|
||||||
// the entry might be a pseudo block-end which does not generate metadata and should not be processed
|
// the entry might be a pseudo end-group which does not generate metadata and should not be processed
|
||||||
if (entryId.startsWith('end-')) {
|
if (entryId.startsWith('end-')) {
|
||||||
const parentId = entryId.split('end-')[1];
|
const parentId = entryId.split('end-')[1];
|
||||||
const isBlockCollapsed = getIsCollapsed(parentId);
|
const isGroupCollapsed = getIsCollapsed(parentId);
|
||||||
|
|
||||||
if (isBlockCollapsed) {
|
if (isGroupCollapsed) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// if the previous element is selected, it will have its own QuickAddInline
|
// if the previous element is selected, it will have its own QuickAddInline
|
||||||
// we use thisId instead of previousEntryId because the block end does not process
|
// we use thisId instead of previousEntryId because the end-group does not process
|
||||||
// and it does not cause the reassignment of the iteration id to the previous entry
|
// and it does not cause the reassignment of the iteration id to the previous entry
|
||||||
return (
|
return (
|
||||||
<Fragment key={entryId}>
|
<Fragment key={entryId}>
|
||||||
{isEditMode && rundownMetadata.groupEntries === 0 && (
|
{isEditMode && rundownMetadata.groupEntries === 0 && (
|
||||||
<QuickAddButtons
|
<QuickAddButtons
|
||||||
previousEventId={null}
|
previousEventId={null}
|
||||||
parentBlock={parentId}
|
parentGroup={parentId}
|
||||||
backgroundColor={rundownMetadata.groupColour}
|
backgroundColor={rundownMetadata.groupColour}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<RundownBlockEnd key={entryId} id={entryId} colour={rundownMetadata.groupColour} />
|
<RundownGroupEnd key={entryId} id={entryId} colour={rundownMetadata.groupColour} />
|
||||||
</Fragment>
|
</Fragment>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -469,7 +472,7 @@ export default function Rundown({ data }: RundownProps) {
|
|||||||
|
|
||||||
// if the entry has a parent, and it is collapsed, render nothing
|
// if the entry has a parent, and it is collapsed, render nothing
|
||||||
if (
|
if (
|
||||||
entry.type !== SupportedEntry.Block &&
|
entry.type !== SupportedEntry.Group &&
|
||||||
rundownMetadata.groupId !== null &&
|
rundownMetadata.groupId !== null &&
|
||||||
getIsCollapsed(rundownMetadata.groupId)
|
getIsCollapsed(rundownMetadata.groupId)
|
||||||
) {
|
) {
|
||||||
@@ -480,12 +483,12 @@ export default function Rundown({ data }: RundownProps) {
|
|||||||
const hasCursor = entry.id === cursor;
|
const hasCursor = entry.id === cursor;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Outside a block, the value will be undefined
|
* Outside a group, the value will be undefined
|
||||||
* If the colour is empty string ''
|
* If the colour is empty string ''
|
||||||
* ie: we are inside a block, but there is no defined colour
|
* ie: we are inside a group, but there is no defined colour
|
||||||
* we default to $gray-500 #9d9d9d
|
* we default to $gray-500 #9d9d9d
|
||||||
*/
|
*/
|
||||||
const blockColour = rundownMetadata.groupColour === '' ? '#9d9d9d' : rundownMetadata.groupColour;
|
const groupColour = rundownMetadata.groupColour === '' ? '#9d9d9d' : rundownMetadata.groupColour;
|
||||||
|
|
||||||
const isFirst = index === 0;
|
const isFirst = index === 0;
|
||||||
const isLast = entryId === order.at(-1);
|
const isLast = entryId === order.at(-1);
|
||||||
@@ -510,10 +513,10 @@ export default function Rundown({ data }: RundownProps) {
|
|||||||
* - if it is not the first entry (the buttons would be there)
|
* - if it is not the first entry (the buttons would be there)
|
||||||
*/}
|
*/}
|
||||||
{isEditMode && hasCursor && !isFirst && (
|
{isEditMode && hasCursor && !isFirst && (
|
||||||
<QuickAddInline previousEventId={rundownMetadata.previousEntryId} parentBlock={parentIdForBefore} />
|
<QuickAddInline previousEventId={rundownMetadata.previousEntryId} parentGroup={parentIdForBefore} />
|
||||||
)}
|
)}
|
||||||
{isOntimeBlock(entry) ? (
|
{isOntimeGroup(entry) ? (
|
||||||
<RundownBlock
|
<RundownGroup
|
||||||
data={entry}
|
data={entry}
|
||||||
hasCursor={hasCursor}
|
hasCursor={hasCursor}
|
||||||
collapsed={getIsCollapsed(entry.id)}
|
collapsed={getIsCollapsed(entry.id)}
|
||||||
@@ -523,7 +526,7 @@ export default function Rundown({ data }: RundownProps) {
|
|||||||
<div
|
<div
|
||||||
className={style.entryWrapper}
|
className={style.entryWrapper}
|
||||||
data-testid={`entry-${rundownMetadata.eventIndex}`}
|
data-testid={`entry-${rundownMetadata.eventIndex}`}
|
||||||
style={blockColour ? { '--user-bg': blockColour } : {}}
|
style={groupColour ? { '--user-bg': groupColour } : {}}
|
||||||
>
|
>
|
||||||
{isOntimeEvent(entry) && (
|
{isOntimeEvent(entry) && (
|
||||||
<div className={style.entryIndex}>
|
<div className={style.entryIndex}>
|
||||||
@@ -556,16 +559,16 @@ export default function Rundown({ data }: RundownProps) {
|
|||||||
* - edit mode only
|
* - edit mode only
|
||||||
* - if there is a cursor
|
* - if there is a cursor
|
||||||
* - if it is not the last entry (the buttons would be there)
|
* - if it is not the last entry (the buttons would be there)
|
||||||
* - if the entry is not the block header
|
* - if the entry is not the group header
|
||||||
*/}
|
*/}
|
||||||
{isEditMode && hasCursor && !isLast && (
|
{isEditMode && hasCursor && !isLast && (
|
||||||
<QuickAddInline previousEventId={entry.id} parentBlock={parentIdForAfter} />
|
<QuickAddInline previousEventId={entry.id} parentGroup={parentIdForAfter} />
|
||||||
)}
|
)}
|
||||||
</Fragment>
|
</Fragment>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
{isEditMode && (
|
{isEditMode && (
|
||||||
<QuickAddButtons previousEventId={rundownMetadata.groupId ?? rundownMetadata.thisId} parentBlock={null} />
|
<QuickAddButtons previousEventId={rundownMetadata.groupId ?? rundownMetadata.thisId} parentGroup={null} />
|
||||||
)}
|
)}
|
||||||
<div className={style.spacer} />
|
<div className={style.spacer} />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ export default function RundownEmpty(props: RundownEmptyProps) {
|
|||||||
|
|
||||||
<Editor.Separator />
|
<Editor.Separator />
|
||||||
|
|
||||||
<Button onClick={() => handleAddNew(SupportedEntry.Block)} variant='primary' size='large'>
|
<Button onClick={() => handleAddNew(SupportedEntry.Group)} variant='primary' size='large'>
|
||||||
<IoAdd /> Create Group
|
<IoAdd /> Create Group
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -25,12 +25,12 @@ export type EventItemActions =
|
|||||||
| 'event-before'
|
| 'event-before'
|
||||||
| 'delay'
|
| 'delay'
|
||||||
| 'delay-before'
|
| 'delay-before'
|
||||||
| 'block'
|
| 'group'
|
||||||
| 'block-before'
|
| 'group-before'
|
||||||
| 'swap'
|
| 'swap'
|
||||||
| 'delete'
|
| 'delete'
|
||||||
| 'clone'
|
| 'clone'
|
||||||
| 'group'
|
| 'make-group'
|
||||||
| 'update';
|
| 'update';
|
||||||
|
|
||||||
interface RundownEntryProps {
|
interface RundownEntryProps {
|
||||||
@@ -106,11 +106,11 @@ export default function RundownEntry({
|
|||||||
case 'delay-before': {
|
case 'delay-before': {
|
||||||
return addEntry({ type: SupportedEntry.Delay }, { after: previousEntryId });
|
return addEntry({ type: SupportedEntry.Delay }, { after: previousEntryId });
|
||||||
}
|
}
|
||||||
case 'block': {
|
case 'group': {
|
||||||
return addEntry({ type: SupportedEntry.Block }, { after: data.id });
|
return addEntry({ type: SupportedEntry.Group }, { after: data.id });
|
||||||
}
|
}
|
||||||
case 'block-before': {
|
case 'group-before': {
|
||||||
return addEntry({ type: SupportedEntry.Block }, { after: previousEntryId });
|
return addEntry({ type: SupportedEntry.Group }, { after: previousEntryId });
|
||||||
}
|
}
|
||||||
case 'swap': {
|
case 'swap': {
|
||||||
const { value } = payload as FieldValue;
|
const { value } = payload as FieldValue;
|
||||||
@@ -129,7 +129,7 @@ export default function RundownEntry({
|
|||||||
addEntry(newEvent, { after: data.id });
|
addEntry(newEvent, { after: data.id });
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'group': {
|
case 'make-group': {
|
||||||
if (selectedEvents.size > 1) {
|
if (selectedEvents.size > 1) {
|
||||||
clearMultiSelection();
|
clearMultiSelection();
|
||||||
return groupEntries(Array.from(selectedEvents));
|
return groupEntries(Array.from(selectedEvents));
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { EntryId, OntimeBlock, OntimeDelay, OntimeEvent, RundownEntries, SupportedEntry } from 'ontime-types';
|
import { EntryId, OntimeDelay, OntimeEvent, OntimeGroup, RundownEntries, SupportedEntry } from 'ontime-types';
|
||||||
|
|
||||||
import { makeRundownMetadata, makeSortableList, moveDown, moveUp, orderEntries } from '../rundown.utils';
|
import { makeRundownMetadata, makeSortableList, moveDown, moveUp, orderEntries } from '../rundown.utils';
|
||||||
|
|
||||||
@@ -18,16 +18,16 @@ describe('makeRundownMetadata()', () => {
|
|||||||
skip: false,
|
skip: false,
|
||||||
linkStart: false,
|
linkStart: false,
|
||||||
} as OntimeEvent,
|
} as OntimeEvent,
|
||||||
block: {
|
group: {
|
||||||
id: 'block',
|
id: 'group',
|
||||||
type: SupportedEntry.Block,
|
type: SupportedEntry.Group,
|
||||||
entries: ['11', 'delay', '12', '13'],
|
entries: ['11', 'delay', '12', '13'],
|
||||||
colour: 'red',
|
colour: 'red',
|
||||||
} as OntimeBlock,
|
} as OntimeGroup,
|
||||||
'11': {
|
'11': {
|
||||||
id: '11',
|
id: '11',
|
||||||
type: SupportedEntry.Event,
|
type: SupportedEntry.Event,
|
||||||
parent: 'block',
|
parent: 'group',
|
||||||
timeStart: 10,
|
timeStart: 10,
|
||||||
timeEnd: 11,
|
timeEnd: 11,
|
||||||
duration: 1,
|
duration: 1,
|
||||||
@@ -39,13 +39,13 @@ describe('makeRundownMetadata()', () => {
|
|||||||
delay: {
|
delay: {
|
||||||
id: 'delay',
|
id: 'delay',
|
||||||
type: SupportedEntry.Delay,
|
type: SupportedEntry.Delay,
|
||||||
parent: 'block',
|
parent: 'group',
|
||||||
duration: 0,
|
duration: 0,
|
||||||
} as OntimeDelay,
|
} as OntimeDelay,
|
||||||
'12': {
|
'12': {
|
||||||
id: '12',
|
id: '12',
|
||||||
type: SupportedEntry.Event,
|
type: SupportedEntry.Event,
|
||||||
parent: 'block',
|
parent: 'group',
|
||||||
timeStart: 11,
|
timeStart: 11,
|
||||||
timeEnd: 12,
|
timeEnd: 12,
|
||||||
duration: 1,
|
duration: 1,
|
||||||
@@ -57,7 +57,7 @@ describe('makeRundownMetadata()', () => {
|
|||||||
'13': {
|
'13': {
|
||||||
id: '13',
|
id: '13',
|
||||||
type: SupportedEntry.Event,
|
type: SupportedEntry.Event,
|
||||||
parent: 'block',
|
parent: 'group',
|
||||||
timeStart: 12,
|
timeStart: 12,
|
||||||
timeEnd: 13,
|
timeEnd: 13,
|
||||||
duration: 1,
|
duration: 1,
|
||||||
@@ -114,25 +114,25 @@ describe('makeRundownMetadata()', () => {
|
|||||||
groupEntries: undefined,
|
groupEntries: undefined,
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(process(demoEvents['block'])).toMatchObject({
|
expect(process(demoEvents['group'])).toMatchObject({
|
||||||
previousEvent: demoEvents['1'],
|
previousEvent: demoEvents['1'],
|
||||||
latestEvent: demoEvents['1'],
|
latestEvent: demoEvents['1'],
|
||||||
previousEntryId: demoEvents['1'].id,
|
previousEntryId: demoEvents['1'].id,
|
||||||
thisId: demoEvents['block'].id,
|
thisId: demoEvents['group'].id,
|
||||||
eventIndex: 1,
|
eventIndex: 1,
|
||||||
isPast: true,
|
isPast: true,
|
||||||
isNextDay: false,
|
isNextDay: false,
|
||||||
totalGap: 0,
|
totalGap: 0,
|
||||||
isLinkedToLoaded: false,
|
isLinkedToLoaded: false,
|
||||||
isLoaded: false,
|
isLoaded: false,
|
||||||
groupId: 'block',
|
groupId: 'group',
|
||||||
groupColour: 'red',
|
groupColour: 'red',
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(process(demoEvents['11'])).toMatchObject({
|
expect(process(demoEvents['11'])).toMatchObject({
|
||||||
previousEvent: demoEvents['1'],
|
previousEvent: demoEvents['1'],
|
||||||
latestEvent: demoEvents['11'],
|
latestEvent: demoEvents['11'],
|
||||||
previousEntryId: demoEvents['block'].id,
|
previousEntryId: demoEvents['group'].id,
|
||||||
thisId: demoEvents['11'].id,
|
thisId: demoEvents['11'].id,
|
||||||
eventIndex: 2,
|
eventIndex: 2,
|
||||||
isPast: true,
|
isPast: true,
|
||||||
@@ -140,7 +140,7 @@ describe('makeRundownMetadata()', () => {
|
|||||||
totalGap: 10,
|
totalGap: 10,
|
||||||
isLinkedToLoaded: false,
|
isLinkedToLoaded: false,
|
||||||
isLoaded: false,
|
isLoaded: false,
|
||||||
groupId: 'block',
|
groupId: 'group',
|
||||||
groupColour: 'red',
|
groupColour: 'red',
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -155,7 +155,7 @@ describe('makeRundownMetadata()', () => {
|
|||||||
totalGap: 10,
|
totalGap: 10,
|
||||||
isLinkedToLoaded: false,
|
isLinkedToLoaded: false,
|
||||||
isLoaded: false,
|
isLoaded: false,
|
||||||
groupId: 'block',
|
groupId: 'group',
|
||||||
groupColour: 'red',
|
groupColour: 'red',
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -170,7 +170,7 @@ describe('makeRundownMetadata()', () => {
|
|||||||
totalGap: 10,
|
totalGap: 10,
|
||||||
isLinkedToLoaded: false,
|
isLinkedToLoaded: false,
|
||||||
isLoaded: true,
|
isLoaded: true,
|
||||||
groupId: 'block',
|
groupId: 'group',
|
||||||
groupColour: 'red',
|
groupColour: 'red',
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -185,7 +185,7 @@ describe('makeRundownMetadata()', () => {
|
|||||||
totalGap: 10,
|
totalGap: 10,
|
||||||
isLinkedToLoaded: true,
|
isLinkedToLoaded: true,
|
||||||
isLoaded: false,
|
isLoaded: false,
|
||||||
groupId: 'block',
|
groupId: 'group',
|
||||||
groupColour: 'red',
|
groupColour: 'red',
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -205,18 +205,18 @@ describe('makeRundownMetadata()', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('populates previousEntries in blocks', () => {
|
it('populates previousEntries in groups', () => {
|
||||||
const rundownStartsWithBlock = {
|
const rundownStartsWithGroup = {
|
||||||
block: {
|
group: {
|
||||||
id: 'block',
|
id: 'group',
|
||||||
type: SupportedEntry.Block,
|
type: SupportedEntry.Group,
|
||||||
colour: 'red',
|
colour: 'red',
|
||||||
entries: ['1', '2'],
|
entries: ['1', '2'],
|
||||||
} as OntimeBlock,
|
} as OntimeGroup,
|
||||||
'1': {
|
'1': {
|
||||||
id: '1',
|
id: '1',
|
||||||
type: SupportedEntry.Event,
|
type: SupportedEntry.Event,
|
||||||
parent: 'block',
|
parent: 'group',
|
||||||
timeStart: 1,
|
timeStart: 1,
|
||||||
timeEnd: 2,
|
timeEnd: 2,
|
||||||
duration: 1,
|
duration: 1,
|
||||||
@@ -228,7 +228,7 @@ describe('makeRundownMetadata()', () => {
|
|||||||
'2': {
|
'2': {
|
||||||
id: '2',
|
id: '2',
|
||||||
type: SupportedEntry.Event,
|
type: SupportedEntry.Event,
|
||||||
parent: 'block',
|
parent: 'group',
|
||||||
timeStart: 2,
|
timeStart: 2,
|
||||||
timeEnd: 3,
|
timeEnd: 3,
|
||||||
duration: 1,
|
duration: 1,
|
||||||
@@ -240,49 +240,49 @@ describe('makeRundownMetadata()', () => {
|
|||||||
};
|
};
|
||||||
const { process } = makeRundownMetadata(null);
|
const { process } = makeRundownMetadata(null);
|
||||||
|
|
||||||
expect(process(rundownStartsWithBlock.block)).toStrictEqual({
|
expect(process(rundownStartsWithGroup.group)).toStrictEqual({
|
||||||
previousEvent: null,
|
previousEvent: null,
|
||||||
latestEvent: null,
|
latestEvent: null,
|
||||||
previousEntryId: null,
|
previousEntryId: null,
|
||||||
thisId: rundownStartsWithBlock.block.id,
|
thisId: rundownStartsWithGroup.group.id,
|
||||||
eventIndex: 0,
|
eventIndex: 0,
|
||||||
isPast: false,
|
isPast: false,
|
||||||
isNextDay: false,
|
isNextDay: false,
|
||||||
totalGap: 0,
|
totalGap: 0,
|
||||||
isLinkedToLoaded: false,
|
isLinkedToLoaded: false,
|
||||||
isLoaded: false,
|
isLoaded: false,
|
||||||
groupId: rundownStartsWithBlock.block.id,
|
groupId: rundownStartsWithGroup.group.id,
|
||||||
groupColour: 'red',
|
groupColour: 'red',
|
||||||
groupEntries: 2,
|
groupEntries: 2,
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(process(rundownStartsWithBlock['1'])).toStrictEqual({
|
expect(process(rundownStartsWithGroup['1'])).toStrictEqual({
|
||||||
previousEvent: null,
|
previousEvent: null,
|
||||||
latestEvent: rundownStartsWithBlock['1'],
|
latestEvent: rundownStartsWithGroup['1'],
|
||||||
previousEntryId: rundownStartsWithBlock.block.id,
|
previousEntryId: rundownStartsWithGroup.group.id,
|
||||||
thisId: rundownStartsWithBlock['1'].id,
|
thisId: rundownStartsWithGroup['1'].id,
|
||||||
eventIndex: 1,
|
eventIndex: 1,
|
||||||
isPast: false,
|
isPast: false,
|
||||||
isNextDay: false,
|
isNextDay: false,
|
||||||
totalGap: 0,
|
totalGap: 0,
|
||||||
isLinkedToLoaded: false,
|
isLinkedToLoaded: false,
|
||||||
isLoaded: false,
|
isLoaded: false,
|
||||||
groupId: rundownStartsWithBlock.block.id,
|
groupId: rundownStartsWithGroup.group.id,
|
||||||
groupColour: 'red',
|
groupColour: 'red',
|
||||||
groupEntries: 2,
|
groupEntries: 2,
|
||||||
});
|
});
|
||||||
expect(process(rundownStartsWithBlock['2'])).toStrictEqual({
|
expect(process(rundownStartsWithGroup['2'])).toStrictEqual({
|
||||||
previousEvent: rundownStartsWithBlock['1'],
|
previousEvent: rundownStartsWithGroup['1'],
|
||||||
latestEvent: rundownStartsWithBlock['2'],
|
latestEvent: rundownStartsWithGroup['2'],
|
||||||
previousEntryId: rundownStartsWithBlock['1'].id,
|
previousEntryId: rundownStartsWithGroup['1'].id,
|
||||||
thisId: rundownStartsWithBlock['2'].id,
|
thisId: rundownStartsWithGroup['2'].id,
|
||||||
eventIndex: 2,
|
eventIndex: 2,
|
||||||
isPast: false,
|
isPast: false,
|
||||||
isNextDay: false,
|
isNextDay: false,
|
||||||
totalGap: 0,
|
totalGap: 0,
|
||||||
isLinkedToLoaded: false,
|
isLinkedToLoaded: false,
|
||||||
isLoaded: false,
|
isLoaded: false,
|
||||||
groupId: rundownStartsWithBlock.block.id,
|
groupId: rundownStartsWithGroup.group.id,
|
||||||
groupColour: 'red',
|
groupColour: 'red',
|
||||||
groupEntries: 2,
|
groupEntries: 2,
|
||||||
});
|
});
|
||||||
@@ -290,52 +290,52 @@ describe('makeRundownMetadata()', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('makeSortableList()', () => {
|
describe('makeSortableList()', () => {
|
||||||
it('generates a list with block ends', () => {
|
it('generates a list with group ends', () => {
|
||||||
const order = ['block-1', '2', 'block-3', 'block-4'];
|
const order = ['group-1', '2', 'group-3', 'group-4'];
|
||||||
const entries: RundownEntries = {
|
const entries: RundownEntries = {
|
||||||
'block-1': { type: SupportedEntry.Block, id: 'block-1', entries: ['11'] } as OntimeBlock,
|
'group-1': { type: SupportedEntry.Group, id: 'group-1', entries: ['11'] } as OntimeGroup,
|
||||||
'11': { type: SupportedEntry.Event, id: '11', parent: 'block-1' } as OntimeEvent,
|
'11': { type: SupportedEntry.Event, id: '11', parent: 'group-1' } as OntimeEvent,
|
||||||
'2': { type: SupportedEntry.Event, id: '2', parent: null } as OntimeEvent,
|
'2': { type: SupportedEntry.Event, id: '2', parent: null } as OntimeEvent,
|
||||||
'block-3': { type: SupportedEntry.Block, id: 'block-3', entries: ['31'] } as OntimeBlock,
|
'group-3': { type: SupportedEntry.Group, id: 'group-3', entries: ['31'] } as OntimeGroup,
|
||||||
'31': { type: SupportedEntry.Event, id: '31', parent: 'block-3' } as OntimeEvent,
|
'31': { type: SupportedEntry.Event, id: '31', parent: 'group-3' } as OntimeEvent,
|
||||||
'block-4': { type: SupportedEntry.Block, id: 'block-4', entries: [] as string[] } as OntimeBlock,
|
'group-4': { type: SupportedEntry.Group, id: 'group-4', entries: [] as string[] } as OntimeGroup,
|
||||||
};
|
};
|
||||||
|
|
||||||
const sortableList = makeSortableList(order, entries);
|
const sortableList = makeSortableList(order, entries);
|
||||||
expect(sortableList).toStrictEqual([
|
expect(sortableList).toStrictEqual([
|
||||||
'block-1',
|
'group-1',
|
||||||
'11',
|
'11',
|
||||||
'end-block-1',
|
'end-group-1',
|
||||||
'2',
|
'2',
|
||||||
'block-3',
|
'group-3',
|
||||||
'31',
|
'31',
|
||||||
'end-block-3',
|
'end-group-3',
|
||||||
'block-4',
|
'group-4',
|
||||||
'end-block-4',
|
'end-group-4',
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('closes dangling blocks', () => {
|
it('closes dangling group', () => {
|
||||||
const order = ['block'];
|
const order = ['group'];
|
||||||
const entries: RundownEntries = {
|
const entries: RundownEntries = {
|
||||||
block: { type: SupportedEntry.Block, id: 'block-1', entries: ['11', '12'] } as OntimeBlock,
|
group: { type: SupportedEntry.Group, id: 'group-1', entries: ['11', '12'] } as OntimeGroup,
|
||||||
'11': { type: SupportedEntry.Event, id: '11', parent: 'block-1' } as OntimeEvent,
|
'11': { type: SupportedEntry.Event, id: '11', parent: 'group-1' } as OntimeEvent,
|
||||||
'12': { type: SupportedEntry.Event, id: '12', parent: 'block-1' } as OntimeEvent,
|
'12': { type: SupportedEntry.Event, id: '12', parent: 'group-1' } as OntimeEvent,
|
||||||
};
|
};
|
||||||
|
|
||||||
const sortableList = makeSortableList(order, entries);
|
const sortableList = makeSortableList(order, entries);
|
||||||
expect(sortableList).toStrictEqual(['block-1', '11', '12', 'end-block-1']);
|
expect(sortableList).toStrictEqual(['group-1', '11', '12', 'end-group-1']);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('handles a list with a with just blocks', () => {
|
it('handles a list with a with just groups', () => {
|
||||||
const order = ['block-1', 'block-2'];
|
const order = ['group-1', 'group-2'];
|
||||||
const entries: RundownEntries = {
|
const entries: RundownEntries = {
|
||||||
'block-1': { type: SupportedEntry.Block, id: 'block-1', entries: [] as string[] } as OntimeBlock,
|
'group-1': { type: SupportedEntry.Group, id: 'group-1', entries: [] as string[] } as OntimeGroup,
|
||||||
'block-2': { type: SupportedEntry.Block, id: 'block-2', entries: [] as string[] } as OntimeBlock,
|
'group-2': { type: SupportedEntry.Group, id: 'group-2', entries: [] as string[] } as OntimeGroup,
|
||||||
};
|
};
|
||||||
|
|
||||||
const sortableList = makeSortableList(order, entries);
|
const sortableList = makeSortableList(order, entries);
|
||||||
expect(sortableList).toStrictEqual(['block-1', 'end-block-1', 'block-2', 'end-block-2']);
|
expect(sortableList).toStrictEqual(['group-1', 'end-group-1', 'group-2', 'end-group-2']);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -345,15 +345,15 @@ describe('moveUp()', () => {
|
|||||||
'1': { id: '1', type: 'event', parent: null } as OntimeEvent,
|
'1': { id: '1', type: 'event', parent: null } as OntimeEvent,
|
||||||
'2': { id: '2', type: 'event', parent: null } as OntimeEvent,
|
'2': { id: '2', type: 'event', parent: null } as OntimeEvent,
|
||||||
'3': { id: '3', type: 'event', parent: null } as OntimeEvent,
|
'3': { id: '3', type: 'event', parent: null } as OntimeEvent,
|
||||||
block: { id: 'block', type: 'block', entries: ['11', '12'] } as OntimeBlock,
|
group: { id: 'group', type: 'group', entries: ['11', '12'] } as OntimeGroup,
|
||||||
'11': { id: '11', type: 'event', parent: 'block' } as OntimeEvent,
|
'11': { id: '11', type: 'event', parent: 'group' } as OntimeEvent,
|
||||||
'12': { id: '12', type: 'event', parent: 'block' } as OntimeEvent,
|
'12': { id: '12', type: 'event', parent: 'group' } as OntimeEvent,
|
||||||
'4': { id: '4', type: 'event', parent: null } as OntimeEvent,
|
'4': { id: '4', type: 'event', parent: null } as OntimeEvent,
|
||||||
block2: { id: 'block2', type: 'block', entries: [] as EntryId[] } as OntimeBlock,
|
group2: { id: 'group2', type: 'group', entries: [] as EntryId[] } as OntimeGroup,
|
||||||
'5': { id: '5', type: 'event', parent: null } as OntimeEvent,
|
'5': { id: '5', type: 'event', parent: null } as OntimeEvent,
|
||||||
},
|
},
|
||||||
order: ['1', '2', '3', 'block', '4', 'block2', '5'],
|
order: ['1', '2', '3', 'group', '4', 'group2', '5'],
|
||||||
flatOrder: ['1', '2', '3', 'block', '11', '12', '4', 'block2', '5'],
|
flatOrder: ['1', '2', '3', 'group', '11', '12', '4', 'group2', '5'],
|
||||||
};
|
};
|
||||||
|
|
||||||
it('moving the first event is a noop', () => {
|
it('moving the first event is a noop', () => {
|
||||||
@@ -370,7 +370,7 @@ describe('moveUp()', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('moves an entry up inside a block', () => {
|
it('moves an entry up inside a group', () => {
|
||||||
expect(moveUp('12', rundown.flatOrder, rundown.entries)).toStrictEqual({
|
expect(moveUp('12', rundown.flatOrder, rundown.entries)).toStrictEqual({
|
||||||
destinationId: '11',
|
destinationId: '11',
|
||||||
order: 'before',
|
order: 'before',
|
||||||
@@ -379,7 +379,7 @@ describe('moveUp()', () => {
|
|||||||
|
|
||||||
it('moves an entry up into an empty group', () => {
|
it('moves an entry up into an empty group', () => {
|
||||||
expect(moveUp('5', rundown.flatOrder, rundown.entries)).toStrictEqual({
|
expect(moveUp('5', rundown.flatOrder, rundown.entries)).toStrictEqual({
|
||||||
destinationId: 'block2',
|
destinationId: 'group2',
|
||||||
order: 'insert',
|
order: 'insert',
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -393,45 +393,45 @@ describe('moveUp()', () => {
|
|||||||
|
|
||||||
it('moves an entry up out of a group', () => {
|
it('moves an entry up out of a group', () => {
|
||||||
expect(moveUp('11', rundown.flatOrder, rundown.entries)).toStrictEqual({
|
expect(moveUp('11', rundown.flatOrder, rundown.entries)).toStrictEqual({
|
||||||
destinationId: 'block',
|
destinationId: 'group',
|
||||||
order: 'before',
|
order: 'before',
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('moves a block in the rundown', () => {
|
it('moves a group in the rundown', () => {
|
||||||
expect(moveUp('block', rundown.flatOrder, rundown.entries)).toStrictEqual({
|
expect(moveUp('group', rundown.flatOrder, rundown.entries)).toStrictEqual({
|
||||||
destinationId: '3',
|
destinationId: '3',
|
||||||
order: 'before',
|
order: 'before',
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('swaps two blocks', () => {
|
it('swaps two groups', () => {
|
||||||
const rundown = {
|
const rundown = {
|
||||||
entries: {
|
entries: {
|
||||||
block: { id: 'block', type: 'block', entries: ['11'] } as OntimeBlock,
|
group: { id: 'group', type: 'group', entries: ['11'] } as OntimeGroup,
|
||||||
'11': { id: '11', type: 'event', parent: 'block' } as OntimeEvent,
|
'11': { id: '11', type: 'event', parent: 'group' } as OntimeEvent,
|
||||||
block2: { id: 'block2', type: 'block', entries: [] as EntryId[] } as OntimeBlock,
|
group2: { id: 'group2', type: 'group', entries: [] as EntryId[] } as OntimeGroup,
|
||||||
},
|
},
|
||||||
order: ['block', 'block2'],
|
order: ['group', 'group2'],
|
||||||
flatOrder: ['block', '11', 'block2'],
|
flatOrder: ['group', '11', 'group2'],
|
||||||
};
|
};
|
||||||
expect(moveUp('block2', rundown.flatOrder, rundown.entries)).toStrictEqual({
|
expect(moveUp('group2', rundown.flatOrder, rundown.entries)).toStrictEqual({
|
||||||
destinationId: 'block',
|
destinationId: 'group',
|
||||||
order: 'before',
|
order: 'before',
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('moves before a block', () => {
|
it('moves before a group', () => {
|
||||||
const rundown = {
|
const rundown = {
|
||||||
entries: {
|
entries: {
|
||||||
block: { id: 'block', type: 'block', entries: ['11'] } as OntimeBlock,
|
group: { id: 'group', type: 'group', entries: ['11'] } as OntimeGroup,
|
||||||
'11': { id: '11', type: 'event', parent: 'block' } as OntimeEvent,
|
'11': { id: '11', type: 'event', parent: 'group' } as OntimeEvent,
|
||||||
},
|
},
|
||||||
order: ['block'],
|
order: ['group'],
|
||||||
flatOrder: ['block', '11'],
|
flatOrder: ['group', '11'],
|
||||||
};
|
};
|
||||||
expect(moveUp('11', rundown.flatOrder, rundown.entries)).toStrictEqual({
|
expect(moveUp('11', rundown.flatOrder, rundown.entries)).toStrictEqual({
|
||||||
destinationId: 'block',
|
destinationId: 'group',
|
||||||
order: 'before',
|
order: 'before',
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -443,15 +443,15 @@ describe('moveDown()', () => {
|
|||||||
'1': { id: '1', type: 'event', parent: null } as OntimeEvent,
|
'1': { id: '1', type: 'event', parent: null } as OntimeEvent,
|
||||||
'2': { id: '2', type: 'event', parent: null } as OntimeEvent,
|
'2': { id: '2', type: 'event', parent: null } as OntimeEvent,
|
||||||
'3': { id: '3', type: 'event', parent: null } as OntimeEvent,
|
'3': { id: '3', type: 'event', parent: null } as OntimeEvent,
|
||||||
block: { id: 'block', type: 'block', entries: ['11', '12'] } as OntimeBlock,
|
group: { id: 'group', type: 'group', entries: ['11', '12'] } as OntimeGroup,
|
||||||
'11': { id: '11', type: 'event', parent: 'block' } as OntimeEvent,
|
'11': { id: '11', type: 'event', parent: 'group' } as OntimeEvent,
|
||||||
'12': { id: '12', type: 'event', parent: 'block' } as OntimeEvent,
|
'12': { id: '12', type: 'event', parent: 'group' } as OntimeEvent,
|
||||||
'4': { id: '4', type: 'event', parent: null } as OntimeEvent,
|
'4': { id: '4', type: 'event', parent: null } as OntimeEvent,
|
||||||
block2: { id: 'block2', type: 'block', entries: [] as EntryId[] } as OntimeBlock,
|
group2: { id: 'group2', type: 'group', entries: [] as EntryId[] } as OntimeGroup,
|
||||||
'5': { id: '5', type: 'event', parent: null } as OntimeEvent,
|
'5': { id: '5', type: 'event', parent: null } as OntimeEvent,
|
||||||
},
|
},
|
||||||
order: ['1', '2', '3', 'block', '4', 'block2', '5'],
|
order: ['1', '2', '3', 'group', '4', 'group2', '5'],
|
||||||
flatOrder: ['1', '2', '3', 'block', '11', '12', '4', 'block2', '5'],
|
flatOrder: ['1', '2', '3', 'group', '11', '12', '4', 'group2', '5'],
|
||||||
};
|
};
|
||||||
|
|
||||||
it('moving the last event is a noop', () => {
|
it('moving the last event is a noop', () => {
|
||||||
@@ -468,7 +468,7 @@ describe('moveDown()', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('moves an entry down inside a block', () => {
|
it('moves an entry down inside a group', () => {
|
||||||
expect(moveDown('11', rundown.flatOrder, rundown.entries)).toStrictEqual({
|
expect(moveDown('11', rundown.flatOrder, rundown.entries)).toStrictEqual({
|
||||||
destinationId: '12',
|
destinationId: '12',
|
||||||
order: 'after',
|
order: 'after',
|
||||||
@@ -477,14 +477,14 @@ describe('moveDown()', () => {
|
|||||||
|
|
||||||
it('moves an entry down into an empty group', () => {
|
it('moves an entry down into an empty group', () => {
|
||||||
expect(moveDown('4', rundown.flatOrder, rundown.entries)).toStrictEqual({
|
expect(moveDown('4', rundown.flatOrder, rundown.entries)).toStrictEqual({
|
||||||
destinationId: 'block2',
|
destinationId: 'group2',
|
||||||
order: 'insert',
|
order: 'insert',
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('moves an entry down out of a group', () => {
|
it('moves an entry down out of a group', () => {
|
||||||
expect(moveDown('12', rundown.flatOrder, rundown.entries)).toStrictEqual({
|
expect(moveDown('12', rundown.flatOrder, rundown.entries)).toStrictEqual({
|
||||||
destinationId: 'block',
|
destinationId: 'group',
|
||||||
order: 'after',
|
order: 'after',
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -496,40 +496,40 @@ describe('moveDown()', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('moves a block in the rundown', () => {
|
it('moves a group in the rundown', () => {
|
||||||
expect(moveDown('block', rundown.flatOrder, rundown.entries)).toStrictEqual({
|
expect(moveDown('group', rundown.flatOrder, rundown.entries)).toStrictEqual({
|
||||||
destinationId: '4',
|
destinationId: '4',
|
||||||
order: 'after',
|
order: 'after',
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('swaps two blocks', () => {
|
it('swaps two groups', () => {
|
||||||
const rundown = {
|
const rundown = {
|
||||||
entries: {
|
entries: {
|
||||||
block: { id: 'block', type: 'block', entries: ['11'] } as OntimeBlock,
|
group: { id: 'group', type: 'group', entries: ['11'] } as OntimeGroup,
|
||||||
'11': { id: '11', type: 'event', parent: 'block' } as OntimeEvent,
|
'11': { id: '11', type: 'event', parent: 'group' } as OntimeEvent,
|
||||||
block2: { id: 'block2', type: 'block', entries: [] as EntryId[] } as OntimeBlock,
|
group2: { id: 'group2', type: 'group', entries: [] as EntryId[] } as OntimeGroup,
|
||||||
},
|
},
|
||||||
order: ['block', 'block2'],
|
order: ['group', 'group2'],
|
||||||
flatOrder: ['block', '11', 'block2'],
|
flatOrder: ['group', '11', 'group2'],
|
||||||
};
|
};
|
||||||
expect(moveDown('block', rundown.flatOrder, rundown.entries)).toStrictEqual({
|
expect(moveDown('group', rundown.flatOrder, rundown.entries)).toStrictEqual({
|
||||||
destinationId: 'block2',
|
destinationId: 'group2',
|
||||||
order: 'after',
|
order: 'after',
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('moves after a block', () => {
|
it('moves after a group', () => {
|
||||||
const rundown = {
|
const rundown = {
|
||||||
entries: {
|
entries: {
|
||||||
block: { id: 'block', type: 'block', entries: ['11'] } as OntimeBlock,
|
group: { id: 'group', type: 'group', entries: ['11'] } as OntimeGroup,
|
||||||
'11': { id: '11', type: 'event', parent: 'block' } as OntimeEvent,
|
'11': { id: '11', type: 'event', parent: 'group' } as OntimeEvent,
|
||||||
},
|
},
|
||||||
order: ['block'],
|
order: ['group'],
|
||||||
flatOrder: ['block', '11'],
|
flatOrder: ['group', '11'],
|
||||||
};
|
};
|
||||||
expect(moveDown('11', rundown.flatOrder, rundown.entries)).toStrictEqual({
|
expect(moveDown('11', rundown.flatOrder, rundown.entries)).toStrictEqual({
|
||||||
destinationId: 'block',
|
destinationId: 'group',
|
||||||
order: 'after',
|
order: 'after',
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
+5
-6
@@ -9,13 +9,12 @@ import style from './TitleEditor.module.scss';
|
|||||||
|
|
||||||
interface TitleEditorProps {
|
interface TitleEditorProps {
|
||||||
title: string;
|
title: string;
|
||||||
eventId: string;
|
entryId: string;
|
||||||
placeholder: string;
|
placeholder: string;
|
||||||
className?: string;
|
className?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function EditableBlockTitle(props: TitleEditorProps) {
|
export default function TitleEditor({ title, entryId, placeholder, className }: TitleEditorProps) {
|
||||||
const { title, eventId, placeholder, className } = props;
|
|
||||||
const { updateEntry } = useEntryActions();
|
const { updateEntry } = useEntryActions();
|
||||||
const ref = useRef<HTMLInputElement | null>(null);
|
const ref = useRef<HTMLInputElement | null>(null);
|
||||||
const submitCallback = useCallback(
|
const submitCallback = useCallback(
|
||||||
@@ -25,9 +24,9 @@ export default function EditableBlockTitle(props: TitleEditorProps) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const cleanVal = text.trim();
|
const cleanVal = text.trim();
|
||||||
updateEntry({ id: eventId, title: cleanVal });
|
updateEntry({ id: entryId, title: cleanVal });
|
||||||
},
|
},
|
||||||
[title, updateEntry, eventId],
|
[title, updateEntry, entryId],
|
||||||
);
|
);
|
||||||
|
|
||||||
const { value, onChange, onBlur, onKeyDown } = useReactiveTextInput(title, submitCallback, ref, {
|
const { value, onChange, onBlur, onKeyDown } = useReactiveTextInput(title, submitCallback, ref, {
|
||||||
@@ -38,7 +37,7 @@ export default function EditableBlockTitle(props: TitleEditorProps) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Input
|
<Input
|
||||||
data-testid='block__title'
|
data-testid='entry__title'
|
||||||
variant='ghosted'
|
variant='ghosted'
|
||||||
fluid
|
fluid
|
||||||
ref={ref}
|
ref={ref}
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { isOntimeBlock, isOntimeEvent, OntimeEntry } from 'ontime-types';
|
import { isOntimeEvent, isOntimeGroup, OntimeEntry } from 'ontime-types';
|
||||||
|
|
||||||
import useRundown from '../../../common/hooks-query/useRundown';
|
import useRundown from '../../../common/hooks-query/useRundown';
|
||||||
|
|
||||||
import BlockEditor from './BlockEditor';
|
|
||||||
import EventEditor from './EventEditor';
|
import EventEditor from './EventEditor';
|
||||||
|
import GroupEditor from './GroupEditor';
|
||||||
|
|
||||||
import style from './EntryEditor.module.scss';
|
import style from './EntryEditor.module.scss';
|
||||||
|
|
||||||
@@ -38,10 +38,10 @@ export default function CuesheetEntryEditor({ entryId }: CuesheetEntryEditorProp
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isOntimeBlock(entry)) {
|
if (isOntimeGroup(entry)) {
|
||||||
return (
|
return (
|
||||||
<div className={style.inModal} data-testid='editor-container'>
|
<div className={style.inModal} data-testid='editor-container'>
|
||||||
<BlockEditor block={entry} />
|
<GroupEditor group={entry} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
+22
-22
@@ -1,5 +1,5 @@
|
|||||||
import { useCallback } from 'react';
|
import { useCallback } from 'react';
|
||||||
import { MaybeNumber, OntimeBlock } from 'ontime-types';
|
import { MaybeNumber, OntimeGroup } from 'ontime-types';
|
||||||
import { millisToString } from 'ontime-utils';
|
import { millisToString } from 'ontime-utils';
|
||||||
|
|
||||||
import * as Editor from '../../../common/components/editor-utils/EditorUtils';
|
import * as Editor from '../../../common/components/editor-utils/EditorUtils';
|
||||||
@@ -19,38 +19,38 @@ import TargetDurationInput from './composite/TargetDurationInput';
|
|||||||
import style from './EntryEditor.module.scss';
|
import style from './EntryEditor.module.scss';
|
||||||
|
|
||||||
// title + colour + custom field labels
|
// title + colour + custom field labels
|
||||||
export type BlockEditorUpdateTextFields = 'title' | 'colour' | string;
|
export type GroupEditorUpdateTextFields = 'title' | 'colour' | string;
|
||||||
export type BlockEditorUpdateMaybeNumberFields = 'targetDuration';
|
export type GroupEditorUpdateMaybeNumberFields = 'targetDuration';
|
||||||
|
|
||||||
interface BlockEditorProps {
|
interface GroupEditorProps {
|
||||||
block: OntimeBlock;
|
group: OntimeGroup;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function BlockEditor({ block }: BlockEditorProps) {
|
export default function GroupEditor({ group }: GroupEditorProps) {
|
||||||
const { data: customFields } = useCustomFields();
|
const { data: customFields } = useCustomFields();
|
||||||
const { updateEntry } = useEntryActions();
|
const { updateEntry } = useEntryActions();
|
||||||
|
|
||||||
const handleSubmit = useCallback(
|
const handleSubmit = useCallback(
|
||||||
(field: BlockEditorUpdateTextFields | BlockEditorUpdateMaybeNumberFields, value: string | MaybeNumber) => {
|
(field: GroupEditorUpdateTextFields | GroupEditorUpdateMaybeNumberFields, value: string | MaybeNumber) => {
|
||||||
// Handle custom fields
|
// Handle custom fields
|
||||||
if (typeof field === 'string' && field.startsWith('custom-')) {
|
if (typeof field === 'string' && field.startsWith('custom-')) {
|
||||||
const fieldLabel = field.split('custom-')[1];
|
const fieldLabel = field.split('custom-')[1];
|
||||||
updateEntry({ id: block.id, custom: { [fieldLabel]: value as string } });
|
updateEntry({ id: group.id, custom: { [fieldLabel]: value as string } });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (field === 'targetDuration') {
|
if (field === 'targetDuration') {
|
||||||
return updateEntry({ id: block.id, targetDuration: value as MaybeNumber });
|
return updateEntry({ id: group.id, targetDuration: value as MaybeNumber });
|
||||||
}
|
}
|
||||||
|
|
||||||
// all other strings are text fields
|
// all other strings are text fields
|
||||||
return updateEntry({ id: block.id, [field]: value as string });
|
return updateEntry({ id: group.id, [field]: value as string });
|
||||||
},
|
},
|
||||||
[block.id, updateEntry],
|
[group.id, updateEntry],
|
||||||
);
|
);
|
||||||
|
|
||||||
const isEditor = window.location.pathname.includes('editor');
|
const isEditor = window.location.pathname.includes('editor');
|
||||||
const planOffset = typeof block.targetDuration !== 'number' ? null : block.duration - block.targetDuration;
|
const planOffset = typeof group.targetDuration !== 'number' ? null : group.duration - group.targetDuration;
|
||||||
const planOffsetLabel = planOffset !== null ? getOffsetState(planOffset * -1) : null;
|
const planOffsetLabel = planOffset !== null ? getOffsetState(planOffset * -1) : null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -64,19 +64,19 @@ export default function BlockEditor({ block }: BlockEditorProps) {
|
|||||||
}
|
}
|
||||||
<Editor.Label>First event start</Editor.Label>
|
<Editor.Label>First event start</Editor.Label>
|
||||||
<TextLikeInput className={style.textLikeInput}>
|
<TextLikeInput className={style.textLikeInput}>
|
||||||
{millisToString(block.timeStart, { fallback: timerPlaceholder })}
|
{millisToString(group.timeStart, { fallback: timerPlaceholder })}
|
||||||
</TextLikeInput>
|
</TextLikeInput>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<Editor.Label>Last event end</Editor.Label>
|
<Editor.Label>Last event end</Editor.Label>
|
||||||
<TextLikeInput className={style.textLikeInput}>
|
<TextLikeInput className={style.textLikeInput}>
|
||||||
{millisToString(block.timeEnd, { fallback: timerPlaceholder })}
|
{millisToString(group.timeEnd, { fallback: timerPlaceholder })}
|
||||||
</TextLikeInput>
|
</TextLikeInput>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<Editor.Label htmlFor='duration'>Scheduled duration</Editor.Label>
|
<Editor.Label htmlFor='duration'>Scheduled duration</Editor.Label>
|
||||||
<TextLikeInput className={style.textLikeInput}>
|
<TextLikeInput className={style.textLikeInput}>
|
||||||
{millisToString(block.duration, { fallback: enDash })}
|
{millisToString(group.duration, { fallback: enDash })}
|
||||||
</TextLikeInput>
|
</TextLikeInput>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -93,21 +93,21 @@ export default function BlockEditor({ block }: BlockEditorProps) {
|
|||||||
</TextLikeInput>
|
</TextLikeInput>
|
||||||
</div>
|
</div>
|
||||||
<TargetDurationInput
|
<TargetDurationInput
|
||||||
duration={block.duration}
|
duration={group.duration}
|
||||||
targetDuration={block.targetDuration}
|
targetDuration={group.targetDuration}
|
||||||
submitHandler={handleSubmit}
|
submitHandler={handleSubmit}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className={style.column}>
|
<div className={style.column}>
|
||||||
<Editor.Title>Block data</Editor.Title>
|
<Editor.Title>Group data</Editor.Title>
|
||||||
<div>
|
<div>
|
||||||
<Editor.Label>Colour</Editor.Label>
|
<Editor.Label>Colour</Editor.Label>
|
||||||
<SwatchSelect name='colour' value={block.colour} handleChange={handleSubmit} />
|
<SwatchSelect name='colour' value={group.colour} handleChange={handleSubmit} />
|
||||||
</div>
|
</div>
|
||||||
<EntryEditorTextInput field='title' label='Title' initialValue={block.title} submitHandler={handleSubmit} />
|
<EntryEditorTextInput field='title' label='Title' initialValue={group.title} submitHandler={handleSubmit} />
|
||||||
<EventTextArea field='note' label='Note' initialValue={block.note} submitHandler={handleSubmit} />
|
<EventTextArea field='note' label='Note' initialValue={group.note} submitHandler={handleSubmit} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className={style.column}>
|
<div className={style.column}>
|
||||||
@@ -115,7 +115,7 @@ export default function BlockEditor({ block }: BlockEditorProps) {
|
|||||||
Custom Fields
|
Custom Fields
|
||||||
{isEditor && <AppLink search='settings=manage__custom'>Manage Custom Fields</AppLink>}
|
{isEditor && <AppLink search='settings=manage__custom'>Manage Custom Fields</AppLink>}
|
||||||
</Editor.Title>
|
</Editor.Title>
|
||||||
<EntryEditorCustomFields fields={customFields} handleSubmit={handleSubmit} entry={block} />
|
<EntryEditorCustomFields fields={customFields} handleSubmit={handleSubmit} entry={group} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@@ -1,11 +1,11 @@
|
|||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import {
|
import {
|
||||||
isOntimeBlock,
|
|
||||||
isOntimeDelay,
|
isOntimeDelay,
|
||||||
isOntimeEvent,
|
isOntimeEvent,
|
||||||
|
isOntimeGroup,
|
||||||
isOntimeMilestone,
|
isOntimeMilestone,
|
||||||
OntimeBlock,
|
|
||||||
OntimeEvent,
|
OntimeEvent,
|
||||||
|
OntimeGroup,
|
||||||
OntimeMilestone,
|
OntimeMilestone,
|
||||||
} from 'ontime-types';
|
} from 'ontime-types';
|
||||||
|
|
||||||
@@ -13,9 +13,9 @@ import useRundown from '../../../common/hooks-query/useRundown';
|
|||||||
import { useEventSelection } from '../useEventSelection';
|
import { useEventSelection } from '../useEventSelection';
|
||||||
|
|
||||||
import EventEditorFooter from './composite/EventEditorFooter';
|
import EventEditorFooter from './composite/EventEditorFooter';
|
||||||
import BlockEditor from './BlockEditor';
|
|
||||||
import EventEditor from './EventEditor';
|
import EventEditor from './EventEditor';
|
||||||
import EventEditorEmpty from './EventEditorEmpty';
|
import EventEditorEmpty from './EventEditorEmpty';
|
||||||
|
import GroupEditor from './GroupEditor';
|
||||||
import MilestoneEditor from './MilestoneEditor';
|
import MilestoneEditor from './MilestoneEditor';
|
||||||
|
|
||||||
import style from './EntryEditor.module.scss';
|
import style from './EntryEditor.module.scss';
|
||||||
@@ -24,7 +24,7 @@ export default function RundownEntryEditor() {
|
|||||||
const selectedEvents = useEventSelection((state) => state.selectedEvents);
|
const selectedEvents = useEventSelection((state) => state.selectedEvents);
|
||||||
const { data } = useRundown();
|
const { data } = useRundown();
|
||||||
|
|
||||||
const [entry, setEntry] = useState<OntimeEvent | OntimeBlock | OntimeMilestone | null>(null);
|
const [entry, setEntry] = useState<OntimeEvent | OntimeGroup | OntimeMilestone | null>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (data.order.length === 0) {
|
if (data.order.length === 0) {
|
||||||
@@ -67,10 +67,10 @@ export default function RundownEntryEditor() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isOntimeBlock(entry)) {
|
if (isOntimeGroup(entry)) {
|
||||||
return (
|
return (
|
||||||
<div className={style.entryEditor} data-testid='editor-container'>
|
<div className={style.entryEditor} data-testid='editor-container'>
|
||||||
<BlockEditor block={entry} />
|
<GroupEditor group={entry} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { CSSProperties, Fragment } from 'react';
|
import { CSSProperties, Fragment } from 'react';
|
||||||
import { CustomFields, OntimeBlock, OntimeEvent, OntimeMilestone } from 'ontime-types';
|
import { CustomFields, OntimeEvent, OntimeGroup, OntimeMilestone } from 'ontime-types';
|
||||||
|
|
||||||
import { getAccessibleColour } from '../../../../common/utils/styleUtils';
|
import { getAccessibleColour } from '../../../../common/utils/styleUtils';
|
||||||
import { EventEditorUpdateFields } from '../EventEditor';
|
import { EventEditorUpdateFields } from '../EventEditor';
|
||||||
@@ -12,7 +12,7 @@ import style from '../EntryEditor.module.scss';
|
|||||||
|
|
||||||
interface EntryEditorCustomFieldsProps {
|
interface EntryEditorCustomFieldsProps {
|
||||||
fields: CustomFields;
|
fields: CustomFields;
|
||||||
entry: OntimeEvent | OntimeBlock | OntimeMilestone;
|
entry: OntimeEvent | OntimeGroup | OntimeMilestone;
|
||||||
handleSubmit: (field: EventEditorUpdateFields, value: string) => void;
|
handleSubmit: (field: EventEditorUpdateFields, value: string) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,11 +3,11 @@ import { useCallback, useRef } from 'react';
|
|||||||
import * as Editor from '../../../../common/components/editor-utils/EditorUtils';
|
import * as Editor from '../../../../common/components/editor-utils/EditorUtils';
|
||||||
import Input, { type InputProps } from '../../../../common/components/input/input/Input';
|
import Input, { type InputProps } from '../../../../common/components/input/input/Input';
|
||||||
import useReactiveTextInput from '../../../../common/components/input/text-input/useReactiveTextInput';
|
import useReactiveTextInput from '../../../../common/components/input/text-input/useReactiveTextInput';
|
||||||
import { BlockEditorUpdateTextFields } from '../BlockEditor';
|
|
||||||
import { EventEditorUpdateFields } from '../EventEditor';
|
import { EventEditorUpdateFields } from '../EventEditor';
|
||||||
|
import { GroupEditorUpdateTextFields } from '../GroupEditor';
|
||||||
|
|
||||||
interface EntryEditorTextInputProps extends InputProps {
|
interface EntryEditorTextInputProps extends InputProps {
|
||||||
field: EventEditorUpdateFields | BlockEditorUpdateTextFields;
|
field: EventEditorUpdateFields | GroupEditorUpdateTextFields;
|
||||||
label: string;
|
label: string;
|
||||||
initialValue: string;
|
initialValue: string;
|
||||||
placeholder?: string;
|
placeholder?: string;
|
||||||
|
|||||||
@@ -17,27 +17,27 @@ interface TargetDurationInputProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default function TargetDurationInput({ duration, targetDuration, submitHandler }: TargetDurationInputProps) {
|
export default function TargetDurationInput({ duration, targetDuration, submitHandler }: TargetDurationInputProps) {
|
||||||
const isBlocked = targetDuration !== null;
|
const isLocked = targetDuration !== null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Editor.Label htmlFor='targetDuration'>Target duration</Editor.Label>
|
<Editor.Label htmlFor='targetDuration'>Target duration</Editor.Label>
|
||||||
<TimeInputGroup hasDelay={isBlocked && targetDuration !== duration}>
|
<TimeInputGroup hasDelay={isLocked && targetDuration !== duration}>
|
||||||
<NullableTimeInput
|
<NullableTimeInput
|
||||||
name='targetDuration'
|
name='targetDuration'
|
||||||
time={targetDuration}
|
time={targetDuration}
|
||||||
submitHandler={submitHandler}
|
submitHandler={submitHandler}
|
||||||
emptyDisplay={enDash}
|
emptyDisplay={enDash}
|
||||||
className={isBlocked ? '' : style.inactive}
|
className={isLocked ? '' : style.inactive}
|
||||||
/>
|
/>
|
||||||
<Tooltip
|
<Tooltip
|
||||||
text='Lock to target duration'
|
text='Lock to target duration'
|
||||||
className={cx([style.timeAction, isBlocked && style.active])}
|
className={cx([style.timeAction, isLocked && style.active])}
|
||||||
onClick={() => submitHandler('targetDuration', isBlocked ? null : duration)}
|
onClick={() => submitHandler('targetDuration', isLocked ? null : duration)}
|
||||||
data-testid='lock__duration'
|
data-testid='lock__duration'
|
||||||
render={<IconButton variant='subtle-white' className={isBlocked ? style.active : style.inactive} />}
|
render={<IconButton variant='subtle-white' className={isLocked ? style.active : style.inactive} />}
|
||||||
>
|
>
|
||||||
{isBlocked ? <IoLockClosed /> : <IoLockOpenOutline />}
|
{isLocked ? <IoLockClosed /> : <IoLockOpenOutline />}
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</TimeInputGroup>
|
</TimeInputGroup>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
+14
-14
@@ -11,19 +11,19 @@ import style from './QuickAddButtons.module.scss';
|
|||||||
|
|
||||||
interface QuickAddButtonsProps {
|
interface QuickAddButtonsProps {
|
||||||
previousEventId: MaybeString;
|
previousEventId: MaybeString;
|
||||||
parentBlock: MaybeString;
|
parentGroup: MaybeString;
|
||||||
backgroundColor?: string;
|
backgroundColor?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default memo(QuickAddButtons);
|
export default memo(QuickAddButtons);
|
||||||
function QuickAddButtons({ previousEventId, parentBlock, backgroundColor }: QuickAddButtonsProps) {
|
function QuickAddButtons({ previousEventId, parentGroup, backgroundColor }: QuickAddButtonsProps) {
|
||||||
const { addEntry } = useEntryActions();
|
const { addEntry } = useEntryActions();
|
||||||
|
|
||||||
const addEvent = () => {
|
const addEvent = () => {
|
||||||
addEntry(
|
addEntry(
|
||||||
{
|
{
|
||||||
type: SupportedEntry.Event,
|
type: SupportedEntry.Event,
|
||||||
parent: parentBlock,
|
parent: parentGroup,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
after: previousEventId,
|
after: previousEventId,
|
||||||
@@ -34,7 +34,7 @@ function QuickAddButtons({ previousEventId, parentBlock, backgroundColor }: Quic
|
|||||||
|
|
||||||
const addDelay = () => {
|
const addDelay = () => {
|
||||||
addEntry(
|
addEntry(
|
||||||
{ type: SupportedEntry.Delay, parent: parentBlock },
|
{ type: SupportedEntry.Delay, parent: parentGroup },
|
||||||
{
|
{
|
||||||
lastEventId: previousEventId,
|
lastEventId: previousEventId,
|
||||||
after: previousEventId,
|
after: previousEventId,
|
||||||
@@ -44,7 +44,7 @@ function QuickAddButtons({ previousEventId, parentBlock, backgroundColor }: Quic
|
|||||||
|
|
||||||
const addMilestone = () => {
|
const addMilestone = () => {
|
||||||
addEntry(
|
addEntry(
|
||||||
{ type: SupportedEntry.Milestone, parent: parentBlock },
|
{ type: SupportedEntry.Milestone, parent: parentGroup },
|
||||||
{
|
{
|
||||||
lastEventId: previousEventId,
|
lastEventId: previousEventId,
|
||||||
after: previousEventId,
|
after: previousEventId,
|
||||||
@@ -52,12 +52,12 @@ function QuickAddButtons({ previousEventId, parentBlock, backgroundColor }: Quic
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const addBlock = () => {
|
const addGroup = () => {
|
||||||
if (parentBlock !== null) {
|
if (parentGroup !== null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
addEntry(
|
addEntry(
|
||||||
{ type: SupportedEntry.Block },
|
{ type: SupportedEntry.Group },
|
||||||
{
|
{
|
||||||
lastEventId: previousEventId,
|
lastEventId: previousEventId,
|
||||||
after: previousEventId,
|
after: previousEventId,
|
||||||
@@ -67,15 +67,15 @@ function QuickAddButtons({ previousEventId, parentBlock, backgroundColor }: Quic
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* If the colour is empty string ''
|
* If the colour is empty string ''
|
||||||
* ie: we are inside a block, but there is no defined colour
|
* ie: we are inside a group, but there is no defined colour
|
||||||
* we default to $gray-500 #9d9d9d
|
* we default to $gray-500 #9d9d9d
|
||||||
*/
|
*/
|
||||||
const blockColour = backgroundColor === '' ? '#9d9d9d' : backgroundColor;
|
const groupColour = backgroundColor === '' ? '#9d9d9d' : backgroundColor;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Toolbar.Root
|
<Toolbar.Root
|
||||||
className={cx([style.quickAdd, Boolean(parentBlock) && style.indent])}
|
className={cx([style.quickAdd, Boolean(parentGroup) && style.indent])}
|
||||||
style={blockColour ? { '--user-bg': blockColour } : {}}
|
style={groupColour ? { '--user-bg': groupColour } : {}}
|
||||||
data-testid='quick-add-buttons'
|
data-testid='quick-add-buttons'
|
||||||
>
|
>
|
||||||
<Toolbar.Button render={<Button size='small' />} onClick={addEvent}>
|
<Toolbar.Button render={<Button size='small' />} onClick={addEvent}>
|
||||||
@@ -93,8 +93,8 @@ function QuickAddButtons({ previousEventId, parentBlock, backgroundColor }: Quic
|
|||||||
Milestone
|
Milestone
|
||||||
</Toolbar.Button>
|
</Toolbar.Button>
|
||||||
|
|
||||||
{parentBlock === null && (
|
{parentGroup === null && (
|
||||||
<Toolbar.Button render={<Button size='small' />} onClick={addBlock}>
|
<Toolbar.Button render={<Button size='small' />} onClick={addGroup}>
|
||||||
<IoAdd />
|
<IoAdd />
|
||||||
Group
|
Group
|
||||||
</Toolbar.Button>
|
</Toolbar.Button>
|
||||||
|
|||||||
@@ -10,18 +10,18 @@ import style from './QuickAddInline.module.scss';
|
|||||||
|
|
||||||
interface QuickAddInlineProps {
|
interface QuickAddInlineProps {
|
||||||
previousEventId: MaybeString;
|
previousEventId: MaybeString;
|
||||||
parentBlock: MaybeString;
|
parentGroup: MaybeString;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default memo(QuickAddInline);
|
export default memo(QuickAddInline);
|
||||||
function QuickAddInline({ previousEventId, parentBlock }: QuickAddInlineProps) {
|
function QuickAddInline({ previousEventId, parentGroup }: QuickAddInlineProps) {
|
||||||
const { addEntry } = useEntryActions();
|
const { addEntry } = useEntryActions();
|
||||||
|
|
||||||
const addEvent = () => {
|
const addEvent = () => {
|
||||||
addEntry(
|
addEntry(
|
||||||
{
|
{
|
||||||
type: SupportedEntry.Event,
|
type: SupportedEntry.Event,
|
||||||
parent: parentBlock,
|
parent: parentGroup,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
after: previousEventId,
|
after: previousEventId,
|
||||||
@@ -32,7 +32,7 @@ function QuickAddInline({ previousEventId, parentBlock }: QuickAddInlineProps) {
|
|||||||
|
|
||||||
const addDelay = () => {
|
const addDelay = () => {
|
||||||
addEntry(
|
addEntry(
|
||||||
{ type: SupportedEntry.Delay, parent: parentBlock },
|
{ type: SupportedEntry.Delay, parent: parentGroup },
|
||||||
{
|
{
|
||||||
lastEventId: previousEventId,
|
lastEventId: previousEventId,
|
||||||
after: previousEventId,
|
after: previousEventId,
|
||||||
@@ -42,7 +42,7 @@ function QuickAddInline({ previousEventId, parentBlock }: QuickAddInlineProps) {
|
|||||||
|
|
||||||
const addMilestone = () => {
|
const addMilestone = () => {
|
||||||
addEntry(
|
addEntry(
|
||||||
{ type: SupportedEntry.Milestone, parent: parentBlock },
|
{ type: SupportedEntry.Milestone, parent: parentGroup },
|
||||||
{
|
{
|
||||||
lastEventId: previousEventId,
|
lastEventId: previousEventId,
|
||||||
after: previousEventId,
|
after: previousEventId,
|
||||||
@@ -50,12 +50,12 @@ function QuickAddInline({ previousEventId, parentBlock }: QuickAddInlineProps) {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const addBlock = () => {
|
const addGroup = () => {
|
||||||
if (parentBlock !== null) {
|
if (parentGroup !== null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
addEntry(
|
addEntry(
|
||||||
{ type: SupportedEntry.Block },
|
{ type: SupportedEntry.Group },
|
||||||
{
|
{
|
||||||
lastEventId: previousEventId,
|
lastEventId: previousEventId,
|
||||||
after: previousEventId,
|
after: previousEventId,
|
||||||
@@ -70,7 +70,7 @@ function QuickAddInline({ previousEventId, parentBlock }: QuickAddInlineProps) {
|
|||||||
{ type: 'item', icon: IoAdd, label: 'Add Event', onClick: addEvent },
|
{ type: 'item', icon: IoAdd, label: 'Add Event', onClick: addEvent },
|
||||||
{ type: 'item', icon: IoAdd, label: 'Add Delay', onClick: addDelay },
|
{ type: 'item', icon: IoAdd, label: 'Add Delay', onClick: addDelay },
|
||||||
{ type: 'item', icon: IoAdd, label: 'Add Milestone', onClick: addMilestone },
|
{ type: 'item', icon: IoAdd, label: 'Add Milestone', onClick: addMilestone },
|
||||||
{ type: 'item', icon: IoAdd, label: 'Add Group', onClick: addBlock, disabled: parentBlock !== null },
|
{ type: 'item', icon: IoAdd, label: 'Add Group', onClick: addGroup, disabled: parentGroup !== null },
|
||||||
]}
|
]}
|
||||||
render={<IconButton size='small' variant='primary' className={style.addButton} />}
|
render={<IconButton size='small' variant='primary' className={style.addButton} />}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -130,7 +130,7 @@ export default function RundownEvent({
|
|||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
{ type: 'divider' },
|
{ type: 'divider' },
|
||||||
{ type: 'item', label: 'Group', icon: IoFolder, onClick: () => actionHandler('group') },
|
{ type: 'item', label: 'Group', icon: IoFolder, onClick: () => actionHandler('make-group') },
|
||||||
{ type: 'divider' },
|
{ type: 'divider' },
|
||||||
{ type: 'item', label: 'Delete', icon: IoTrash, onClick: () => actionHandler('delete') },
|
{ type: 'item', label: 'Delete', icon: IoTrash, onClick: () => actionHandler('delete') },
|
||||||
]
|
]
|
||||||
@@ -205,8 +205,8 @@ export default function RundownEvent({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const elementInFocus = document.activeElement;
|
const elementInFocus = document.activeElement;
|
||||||
// we know the block is the grandparent of our binder
|
// we know the group is the grandparent of our binder
|
||||||
const blockElement = handleRef.current.closest('#event-block');
|
const blockElement = handleRef.current.closest('#event-group');
|
||||||
|
|
||||||
// we only move focus if the block doesnt already contain focus
|
// we only move focus if the block doesnt already contain focus
|
||||||
if (blockElement && !blockElement.contains(elementInFocus)) {
|
if (blockElement && !blockElement.contains(elementInFocus)) {
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import { EndAction, Playback, TimerType, TimeStrategy } from 'ontime-types';
|
|||||||
|
|
||||||
import Tooltip from '../../../common/components/tooltip/Tooltip';
|
import Tooltip from '../../../common/components/tooltip/Tooltip';
|
||||||
import { cx } from '../../../common/utils/styleUtils';
|
import { cx } from '../../../common/utils/styleUtils';
|
||||||
import EditableBlockTitle from '../common/EditableBlockTitle';
|
import TitleEditor from '../common/TitleEditor';
|
||||||
import TimeInputFlow from '../time-input-flow/TimeInputFlow';
|
import TimeInputFlow from '../time-input-flow/TimeInputFlow';
|
||||||
|
|
||||||
import RundownEventChip from './composite/RundownEventChip';
|
import RundownEventChip from './composite/RundownEventChip';
|
||||||
@@ -105,7 +105,7 @@ function RundownEventInner({
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className={style.titleSection}>
|
<div className={style.titleSection}>
|
||||||
<EditableBlockTitle title={title} eventId={eventId} placeholder='Event title' className={style.eventTitle} />
|
<TitleEditor title={title} entryId={eventId} placeholder='Event title' className={style.eventTitle} />
|
||||||
{isNext && <span className={style.nextTag}>UP NEXT</span>}
|
{isNext && <span className={style.nextTag}>UP NEXT</span>}
|
||||||
</div>
|
</div>
|
||||||
<EventBlockPlayback
|
<EventBlockPlayback
|
||||||
@@ -130,7 +130,7 @@ function RundownEventInner({
|
|||||||
duration={duration}
|
duration={duration}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<div className={style.statusElements} id='block-status' data-timertype={timerType}>
|
<div className={style.statusElements} id='entry-status' data-timertype={timerType}>
|
||||||
<span className={style.eventNote}>{note}</span>
|
<span className={style.eventNote}>{note}</span>
|
||||||
<div className={loaded ? style.progressBg : `${style.progressBg} ${style.hidden}`}>
|
<div className={loaded ? style.progressBg : `${style.progressBg} ${style.hidden}`}>
|
||||||
{loaded && <EventBlockProgressBar />}
|
{loaded && <EventBlockProgressBar />}
|
||||||
|
|||||||
+2
-2
@@ -1,6 +1,6 @@
|
|||||||
@use '../blockMixins' as *;
|
@use '../blockMixins' as *;
|
||||||
|
|
||||||
.block {
|
.group {
|
||||||
@include block-styling;
|
@include block-styling;
|
||||||
|
|
||||||
margin-block: 0.5rem;
|
margin-block: 0.5rem;
|
||||||
@@ -22,7 +22,7 @@
|
|||||||
.binder {
|
.binder {
|
||||||
grid-area: binder;
|
grid-area: binder;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
background-color: var(--block-color, $gray-1050);
|
background-color: var(--user-bg, $gray-1050);
|
||||||
color: $section-white;
|
color: $section-white;
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
display: grid;
|
display: grid;
|
||||||
+13
-14
@@ -9,7 +9,7 @@ import {
|
|||||||
} from 'react-icons/io5';
|
} from 'react-icons/io5';
|
||||||
import { useSortable } from '@dnd-kit/sortable';
|
import { useSortable } from '@dnd-kit/sortable';
|
||||||
import { CSS } from '@dnd-kit/utilities';
|
import { CSS } from '@dnd-kit/utilities';
|
||||||
import { EntryId, OntimeBlock } from 'ontime-types';
|
import { EntryId, OntimeGroup } from 'ontime-types';
|
||||||
|
|
||||||
import IconButton from '../../../common/components/buttons/IconButton';
|
import IconButton from '../../../common/components/buttons/IconButton';
|
||||||
import { useContextMenu } from '../../../common/hooks/useContextMenu';
|
import { useContextMenu } from '../../../common/hooks/useContextMenu';
|
||||||
@@ -17,24 +17,24 @@ import { useEntryActions } from '../../../common/hooks/useEntryAction';
|
|||||||
import { getOffsetState } from '../../../common/utils/offset';
|
import { getOffsetState } from '../../../common/utils/offset';
|
||||||
import { cx, getAccessibleColour } from '../../../common/utils/styleUtils';
|
import { cx, getAccessibleColour } from '../../../common/utils/styleUtils';
|
||||||
import { formatDuration, formatTime } from '../../../common/utils/time';
|
import { formatDuration, formatTime } from '../../../common/utils/time';
|
||||||
import EditableBlockTitle from '../common/EditableBlockTitle';
|
import TitleEditor from '../common/TitleEditor';
|
||||||
import { canDrop } from '../rundown.utils';
|
import { canDrop } from '../rundown.utils';
|
||||||
import { useEventSelection } from '../useEventSelection';
|
import { useEventSelection } from '../useEventSelection';
|
||||||
|
|
||||||
import style from './RundownBlock.module.scss';
|
import style from './RundownGroup.module.scss';
|
||||||
|
|
||||||
interface RundownBlockProps {
|
interface RundownGroupProps {
|
||||||
data: OntimeBlock;
|
data: OntimeGroup;
|
||||||
hasCursor: boolean;
|
hasCursor: boolean;
|
||||||
collapsed: boolean;
|
collapsed: boolean;
|
||||||
onCollapse: (collapsed: boolean, groupId: EntryId) => void;
|
onCollapse: (collapsed: boolean, groupId: EntryId) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
//TODO: the block should maybe include a multiple day indicator
|
//TODO: the group should maybe include a multiple day indicator
|
||||||
export default function RundownBlock({ data, hasCursor, collapsed, onCollapse }: RundownBlockProps) {
|
export default function RundownGroup({ data, hasCursor, collapsed, onCollapse }: RundownGroupProps) {
|
||||||
const handleRef = useRef<null | HTMLSpanElement>(null);
|
const handleRef = useRef<null | HTMLSpanElement>(null);
|
||||||
const { clone, ungroup, deleteEntry } = useEntryActions();
|
const { clone, ungroup, deleteEntry } = useEntryActions();
|
||||||
const { selectedEvents, setSelectedBlock } = useEventSelection();
|
const { selectedEvents, setSingleEntrySelection } = useEventSelection();
|
||||||
|
|
||||||
const [onContextMenu] = useContextMenu<HTMLDivElement>([
|
const [onContextMenu] = useContextMenu<HTMLDivElement>([
|
||||||
{
|
{
|
||||||
@@ -71,7 +71,7 @@ export default function RundownBlock({ data, hasCursor, collapsed, onCollapse }:
|
|||||||
} = useSortable({
|
} = useSortable({
|
||||||
id: data.id,
|
id: data.id,
|
||||||
data: {
|
data: {
|
||||||
type: 'block',
|
type: 'group',
|
||||||
},
|
},
|
||||||
animateLayoutChanges: () => false,
|
animateLayoutChanges: () => false,
|
||||||
});
|
});
|
||||||
@@ -87,7 +87,7 @@ export default function RundownBlock({ data, hasCursor, collapsed, onCollapse }:
|
|||||||
}
|
}
|
||||||
|
|
||||||
// UI indexes are 1 based
|
// UI indexes are 1 based
|
||||||
setSelectedBlock({ id: data.id });
|
setSingleEntrySelection({ id: data.id });
|
||||||
};
|
};
|
||||||
|
|
||||||
const binderColours = data.colour && getAccessibleColour(data.colour);
|
const binderColours = data.colour && getAccessibleColour(data.colour);
|
||||||
@@ -115,16 +115,15 @@ export default function RundownBlock({ data, hasCursor, collapsed, onCollapse }:
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={cx([style.block, hasCursor && style.hasCursor, !collapsed && style.expanded])}
|
className={cx([style.group, hasCursor && style.hasCursor, !collapsed && style.expanded])}
|
||||||
ref={setNodeRef}
|
ref={setNodeRef}
|
||||||
onClick={handleFocusClick}
|
onClick={handleFocusClick}
|
||||||
onContextMenu={onContextMenu}
|
onContextMenu={onContextMenu}
|
||||||
style={{
|
style={{
|
||||||
// ...(binderColours ? { '--user-bg': binderColours.backgroundColor } : {}),
|
|
||||||
...dragStyle,
|
...dragStyle,
|
||||||
'--user-bg': data.colour || '#929292',
|
'--user-bg': data.colour || '#929292',
|
||||||
}}
|
}}
|
||||||
data-testid='rundown-block'
|
data-testid='rundown-group'
|
||||||
>
|
>
|
||||||
<div className={style.binder} style={{ ...binderColours }} tabIndex={-1}>
|
<div className={style.binder} style={{ ...binderColours }} tabIndex={-1}>
|
||||||
<span
|
<span
|
||||||
@@ -138,7 +137,7 @@ export default function RundownBlock({ data, hasCursor, collapsed, onCollapse }:
|
|||||||
</div>
|
</div>
|
||||||
<div className={style.header}>
|
<div className={style.header}>
|
||||||
<div className={style.titleRow}>
|
<div className={style.titleRow}>
|
||||||
<EditableBlockTitle title={data.title} eventId={data.id} placeholder='Group title' />
|
<TitleEditor title={data.title} entryId={data.id} placeholder='Group title' />
|
||||||
<IconButton aria-label='Collapse' variant='subtle-white' onClick={() => onCollapse(!collapsed, data.id)}>
|
<IconButton aria-label='Collapse' variant='subtle-white' onClick={() => onCollapse(!collapsed, data.id)}>
|
||||||
{collapsed ? <IoChevronUp /> : <IoChevronDown />}
|
{collapsed ? <IoChevronUp /> : <IoChevronDown />}
|
||||||
</IconButton>
|
</IconButton>
|
||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
@use '../blockMixins' as *;
|
@use '../blockMixins' as *;
|
||||||
|
|
||||||
.blockEnd {
|
.groupEnd {
|
||||||
cursor: default;
|
cursor: default;
|
||||||
height: 1rem;
|
height: 1rem;
|
||||||
background-color: var(--user-bg, $gray-1050);
|
background-color: var(--user-bg, $gray-1050);
|
||||||
+6
-6
@@ -1,14 +1,14 @@
|
|||||||
import { useSortable } from '@dnd-kit/sortable';
|
import { useSortable } from '@dnd-kit/sortable';
|
||||||
import { CSS } from '@dnd-kit/utilities';
|
import { CSS } from '@dnd-kit/utilities';
|
||||||
|
|
||||||
import style from './RundownBlockEnd.module.scss';
|
import style from './RundownGroupEnd.module.scss';
|
||||||
|
|
||||||
interface BlockEndProps {
|
interface RundownGroupEndProps {
|
||||||
id: string;
|
id: string;
|
||||||
colour?: string;
|
colour?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function RundownBlockEnd({ id, colour }: BlockEndProps) {
|
export default function RundownGroupEnd({ id, colour }: RundownGroupEndProps) {
|
||||||
const {
|
const {
|
||||||
attributes: dragAttributes,
|
attributes: dragAttributes,
|
||||||
listeners: dragListeners,
|
listeners: dragListeners,
|
||||||
@@ -18,10 +18,10 @@ export default function RundownBlockEnd({ id, colour }: BlockEndProps) {
|
|||||||
} = useSortable({
|
} = useSortable({
|
||||||
id,
|
id,
|
||||||
data: {
|
data: {
|
||||||
type: 'end-block',
|
type: 'end-group',
|
||||||
},
|
},
|
||||||
animateLayoutChanges: () => false,
|
animateLayoutChanges: () => false,
|
||||||
disabled: true, // we do not want to drag end blocks
|
disabled: true, // we do not want to drag end groups
|
||||||
});
|
});
|
||||||
|
|
||||||
const dragStyle = {
|
const dragStyle = {
|
||||||
@@ -31,7 +31,7 @@ export default function RundownBlockEnd({ id, colour }: BlockEndProps) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={style.blockEnd}
|
className={style.groupEnd}
|
||||||
ref={setNodeRef}
|
ref={setNodeRef}
|
||||||
{...dragAttributes}
|
{...dragAttributes}
|
||||||
{...dragListeners}
|
{...dragListeners}
|
||||||
@@ -24,7 +24,7 @@ interface RundownMilestoneProps {
|
|||||||
export default function RundownMilestone({ colour, cue, entryId, hasCursor, title }: RundownMilestoneProps) {
|
export default function RundownMilestone({ colour, cue, entryId, hasCursor, title }: RundownMilestoneProps) {
|
||||||
const handleRef = useRef<null | HTMLSpanElement>(null);
|
const handleRef = useRef<null | HTMLSpanElement>(null);
|
||||||
const { updateEntry, deleteEntry } = useEntryActions();
|
const { updateEntry, deleteEntry } = useEntryActions();
|
||||||
const { selectedEvents, setSelectedBlock } = useEventSelection();
|
const { selectedEvents, setSingleEntrySelection } = useEventSelection();
|
||||||
|
|
||||||
const [onContextMenu] = useContextMenu<HTMLDivElement>([
|
const [onContextMenu] = useContextMenu<HTMLDivElement>([
|
||||||
{
|
{
|
||||||
@@ -61,7 +61,7 @@ export default function RundownMilestone({ colour, cue, entryId, hasCursor, titl
|
|||||||
}
|
}
|
||||||
|
|
||||||
// UI indexes are 1 based
|
// UI indexes are 1 based
|
||||||
setSelectedBlock({ id: entryId });
|
setSingleEntrySelection({ id: entryId });
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleUpdate = (field: 'cue' | 'title', value: string) => {
|
const handleUpdate = (field: 'cue' | 'title', value: string) => {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import {
|
import {
|
||||||
EntryId,
|
EntryId,
|
||||||
isOntimeBlock,
|
|
||||||
isOntimeEvent,
|
isOntimeEvent,
|
||||||
|
isOntimeGroup,
|
||||||
isPlayableEvent,
|
isPlayableEvent,
|
||||||
MaybeString,
|
MaybeString,
|
||||||
OntimeDelay,
|
OntimeDelay,
|
||||||
@@ -81,12 +81,12 @@ function processEntry(
|
|||||||
processedData.isPast = false;
|
processedData.isPast = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isOntimeBlock(entry)) {
|
if (isOntimeGroup(entry)) {
|
||||||
processedData.groupId = entry.id;
|
processedData.groupId = entry.id;
|
||||||
processedData.groupColour = entry.colour;
|
processedData.groupColour = entry.colour;
|
||||||
processedData.groupEntries = entry.entries.length;
|
processedData.groupEntries = entry.entries.length;
|
||||||
} else {
|
} else {
|
||||||
// for delays and blocks, we insert the group metadata
|
// for delays and groups, we insert the group metadata
|
||||||
if ((entry as OntimeEvent | OntimeDelay | OntimeMilestone).parent !== processedData.groupId) {
|
if ((entry as OntimeEvent | OntimeDelay | OntimeMilestone).parent !== processedData.groupId) {
|
||||||
// if the parent is not the current group, we need to update the groupId
|
// if the parent is not the current group, we need to update the groupId
|
||||||
processedData.groupId = (entry as OntimeEvent | OntimeDelay | OntimeMilestone).parent;
|
processedData.groupId = (entry as OntimeEvent | OntimeDelay | OntimeMilestone).parent;
|
||||||
@@ -129,7 +129,7 @@ function processEntry(
|
|||||||
* Creates a sortable list of entries
|
* Creates a sortable list of entries
|
||||||
* ------------------------------------
|
* ------------------------------------
|
||||||
* Due to limitations in dnd-kit we need to flatten the list of entries
|
* Due to limitations in dnd-kit we need to flatten the list of entries
|
||||||
* This list should also be aware of any elements that are sortable (ie: block ends)
|
* This list should also be aware of any elements that are sortable (ie: group ends)
|
||||||
*/
|
*/
|
||||||
export function makeSortableList(order: EntryId[], entries: RundownEntries): EntryId[] {
|
export function makeSortableList(order: EntryId[], entries: RundownEntries): EntryId[] {
|
||||||
const flatIds: EntryId[] = [];
|
const flatIds: EntryId[] = [];
|
||||||
@@ -141,13 +141,13 @@ export function makeSortableList(order: EntryId[], entries: RundownEntries): Ent
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isOntimeBlock(entry)) {
|
if (isOntimeGroup(entry)) {
|
||||||
// inside a block there are delays and events
|
// inside a group there are delays and events
|
||||||
// there is no need for special handling
|
// there is no need for special handling
|
||||||
flatIds.push(entry.id);
|
flatIds.push(entry.id);
|
||||||
flatIds.push(...entry.entries);
|
flatIds.push(...entry.entries);
|
||||||
|
|
||||||
// close the block
|
// close the group
|
||||||
flatIds.push(`end-${entry.id}`);
|
flatIds.push(`end-${entry.id}`);
|
||||||
} else {
|
} else {
|
||||||
flatIds.push(entry.id);
|
flatIds.push(entry.id);
|
||||||
@@ -160,14 +160,14 @@ export function makeSortableList(order: EntryId[], entries: RundownEntries): Ent
|
|||||||
* Checks whether a drop operation is valid
|
* Checks whether a drop operation is valid
|
||||||
* Currently only used for validating dropping groups
|
* Currently only used for validating dropping groups
|
||||||
*/
|
*/
|
||||||
export function canDrop(targetType?: SupportedEntry & 'end-block', targetParent?: EntryId | null): boolean {
|
export function canDrop(targetType?: SupportedEntry & 'end-group', targetParent?: EntryId | null): boolean {
|
||||||
// this would mean inserting a group inside another
|
// this would mean inserting a group inside another
|
||||||
if (targetType === 'end-block') {
|
if (targetType === 'end-group') {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// this means swapping places with another group
|
// this means swapping places with another group
|
||||||
if (targetType === 'block') {
|
if (targetType === 'group') {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -182,7 +182,7 @@ export function canDrop(targetType?: SupportedEntry & 'end-block', targetParent?
|
|||||||
* - order: How to position relative to the destination:
|
* - order: How to position relative to the destination:
|
||||||
* - 'before': Place before the destination
|
* - 'before': Place before the destination
|
||||||
* - 'after': Place after the destination
|
* - 'after': Place after the destination
|
||||||
* - 'insert': Insert into the destination (for blocks)
|
* - 'insert': Insert into the destination (for groups)
|
||||||
*/
|
*/
|
||||||
export function moveUp(
|
export function moveUp(
|
||||||
entryId: EntryId,
|
entryId: EntryId,
|
||||||
@@ -195,7 +195,7 @@ export function moveUp(
|
|||||||
|
|
||||||
// 1. moving at the top of the list
|
// 1. moving at the top of the list
|
||||||
if (!previousEntryId) {
|
if (!previousEntryId) {
|
||||||
// 1a. we are in a block and need to move outside of it
|
// 1a. we are in a group and need to move outside of it
|
||||||
if ('parent' in currentEntry && currentEntry.parent !== null) {
|
if ('parent' in currentEntry && currentEntry.parent !== null) {
|
||||||
return { destinationId: currentEntry.parent, order: 'before' };
|
return { destinationId: currentEntry.parent, order: 'before' };
|
||||||
}
|
}
|
||||||
@@ -203,9 +203,9 @@ export function moveUp(
|
|||||||
return { destinationId: null, order: 'before' };
|
return { destinationId: null, order: 'before' };
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. moving a block (always moves at top level)
|
// 2. moving a group (always moves at top level)
|
||||||
if (isOntimeBlock(currentEntry)) {
|
if (isOntimeGroup(currentEntry)) {
|
||||||
// 21. if previous entry is inside a block, swap with parent
|
// 21. if previous entry is inside a group, swap with parent
|
||||||
const previousEntry = entries[previousEntryId];
|
const previousEntry = entries[previousEntryId];
|
||||||
if ('parent' in previousEntry && previousEntry.parent !== null) {
|
if ('parent' in previousEntry && previousEntry.parent !== null) {
|
||||||
return { destinationId: previousEntry.parent, order: 'before' };
|
return { destinationId: previousEntry.parent, order: 'before' };
|
||||||
@@ -218,17 +218,17 @@ export function moveUp(
|
|||||||
const previousEntry = entries[previousEntryId];
|
const previousEntry = entries[previousEntryId];
|
||||||
const currentEntryParent = currentEntry.parent;
|
const currentEntryParent = currentEntry.parent;
|
||||||
|
|
||||||
// 3. moving in and out of a block
|
// 3. moving in and out of a group
|
||||||
if (isOntimeBlock(previousEntry)) {
|
if (isOntimeGroup(previousEntry)) {
|
||||||
// 3a. if we're not already in the block, move into it
|
// 3a. if we're not already in the group, move into it
|
||||||
if (currentEntryParent === null) {
|
if (currentEntryParent === null) {
|
||||||
return { destinationId: previousEntryId, order: 'insert' };
|
return { destinationId: previousEntryId, order: 'insert' };
|
||||||
}
|
}
|
||||||
// 3b. otherwise, move before the block
|
// 3b. otherwise, move before the group
|
||||||
return { destinationId: previousEntryId, order: 'before' };
|
return { destinationId: previousEntryId, order: 'before' };
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4. moving into the same block as previous entry
|
// 4. moving into the same group as previous entry
|
||||||
if (isOntimeEvent(previousEntry) && previousEntry.parent !== null && currentEntryParent === null) {
|
if (isOntimeEvent(previousEntry) && previousEntry.parent !== null && currentEntryParent === null) {
|
||||||
return { destinationId: previousEntryId, order: 'after' };
|
return { destinationId: previousEntryId, order: 'after' };
|
||||||
}
|
}
|
||||||
@@ -244,7 +244,7 @@ export function moveUp(
|
|||||||
* - order: How to position relative to the destination:
|
* - order: How to position relative to the destination:
|
||||||
* - 'before': Place before the destination
|
* - 'before': Place before the destination
|
||||||
* - 'after': Place after the destination
|
* - 'after': Place after the destination
|
||||||
* - 'insert': Insert into the destination (for blocks)
|
* - 'insert': Insert into the destination (for groups)
|
||||||
*/
|
*/
|
||||||
export function moveDown(
|
export function moveDown(
|
||||||
entryId: EntryId,
|
entryId: EntryId,
|
||||||
@@ -255,10 +255,10 @@ export function moveDown(
|
|||||||
const currentIndex = flatOrder.indexOf(entryId);
|
const currentIndex = flatOrder.indexOf(entryId);
|
||||||
const nextEntryId = flatOrder[currentIndex + 1];
|
const nextEntryId = flatOrder[currentIndex + 1];
|
||||||
|
|
||||||
// 1. check if we're the last entry in a block
|
// 1. check if we're the last entry in a group
|
||||||
if ('parent' in currentEntry && currentEntry.parent !== null) {
|
if ('parent' in currentEntry && currentEntry.parent !== null) {
|
||||||
const parentBlock = entries[currentEntry.parent];
|
const parentGroup = entries[currentEntry.parent];
|
||||||
if (isOntimeBlock(parentBlock) && parentBlock.entries[parentBlock.entries.length - 1] === entryId) {
|
if (isOntimeGroup(parentGroup) && parentGroup.entries[parentGroup.entries.length - 1] === entryId) {
|
||||||
return { destinationId: currentEntry.parent, order: 'after' };
|
return { destinationId: currentEntry.parent, order: 'after' };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -268,42 +268,42 @@ export function moveDown(
|
|||||||
return { destinationId: null, order: 'after' };
|
return { destinationId: null, order: 'after' };
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. moving a block (always moves at top level)
|
// 3. moving a group (always moves at top level)
|
||||||
if (isOntimeBlock(currentEntry)) {
|
if (isOntimeGroup(currentEntry)) {
|
||||||
// if next entry is inside this block, skip past all children
|
// if next entry is inside this group, skip past all children
|
||||||
if (currentEntry.entries.includes(nextEntryId)) {
|
if (currentEntry.entries.includes(nextEntryId)) {
|
||||||
const afterBlockIndex = currentIndex + currentEntry.entries.length + 1;
|
const afterGroupIndex = currentIndex + currentEntry.entries.length + 1;
|
||||||
const afterBlockId = flatOrder[afterBlockIndex];
|
const afterGroupId = flatOrder[afterGroupIndex];
|
||||||
|
|
||||||
// 2a. block is the last top level entry
|
// 2a. group is the last top level entry
|
||||||
if (!afterBlockId) {
|
if (!afterGroupId) {
|
||||||
return { destinationId: null, order: 'after' };
|
return { destinationId: null, order: 'after' };
|
||||||
}
|
}
|
||||||
// 2b. move after the next top level event
|
// 2b. move after the next top level event
|
||||||
return { destinationId: afterBlockId, order: 'after' };
|
return { destinationId: afterGroupId, order: 'after' };
|
||||||
}
|
}
|
||||||
// 2c. empty block move after the next entry
|
// 2c. empty group move after the next entry
|
||||||
return { destinationId: nextEntryId, order: 'after' };
|
return { destinationId: nextEntryId, order: 'after' };
|
||||||
}
|
}
|
||||||
|
|
||||||
const nextEntry = entries[nextEntryId];
|
const nextEntry = entries[nextEntryId];
|
||||||
const currentEntryParent = currentEntry.parent;
|
const currentEntryParent = currentEntry.parent;
|
||||||
|
|
||||||
// 4. handle moving relative to blocks
|
// 4. handle moving relative to groups
|
||||||
if (isOntimeBlock(nextEntry)) {
|
if (isOntimeGroup(nextEntry)) {
|
||||||
if (currentEntryParent === null) {
|
if (currentEntryParent === null) {
|
||||||
// we are entering a block
|
// we are entering a group
|
||||||
if (nextEntry.entries.length === 0) {
|
if (nextEntry.entries.length === 0) {
|
||||||
// 3a. if the block is empty, insert into it
|
// 3a. if the group is empty, insert into it
|
||||||
return { destinationId: nextEntryId, order: 'insert' };
|
return { destinationId: nextEntryId, order: 'insert' };
|
||||||
}
|
}
|
||||||
// 3b. otherwise, add before the first entry in the block
|
// 3b. otherwise, add before the first entry in the group
|
||||||
const firstBlockEntryId = nextEntry.entries[0];
|
const firstGroupEntryId = nextEntry.entries[0];
|
||||||
return { destinationId: firstBlockEntryId, order: 'before' };
|
return { destinationId: firstGroupEntryId, order: 'before' };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 5. handle moving between block and top level
|
// 5. handle moving between group and top level
|
||||||
const nextEntryParent = isOntimeEvent(nextEntry) ? nextEntry.parent : null;
|
const nextEntryParent = isOntimeEvent(nextEntry) ? nextEntry.parent : null;
|
||||||
if (nextEntryParent !== null && currentEntryParent === null) {
|
if (nextEntryParent !== null && currentEntryParent === null) {
|
||||||
return { destinationId: nextEntryId, order: 'after' };
|
return { destinationId: nextEntryId, order: 'after' };
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import TimeInputGroup from './TimeInputGroup';
|
|||||||
|
|
||||||
import style from './TimeInputFlow.module.scss';
|
import style from './TimeInputFlow.module.scss';
|
||||||
|
|
||||||
interface EventBlockTimerProps {
|
interface TimeInputFlowProps {
|
||||||
eventId: string;
|
eventId: string;
|
||||||
countToEnd: boolean;
|
countToEnd: boolean;
|
||||||
timeStart: number;
|
timeStart: number;
|
||||||
@@ -36,7 +36,7 @@ function TimeInputFlow({
|
|||||||
linkStart,
|
linkStart,
|
||||||
delay,
|
delay,
|
||||||
showLabels,
|
showLabels,
|
||||||
}: EventBlockTimerProps) {
|
}: TimeInputFlowProps) {
|
||||||
const { updateEntry, updateTimer } = useEntryActions();
|
const { updateEntry, updateTimer } = useEntryActions();
|
||||||
|
|
||||||
// In sync with EventEditorTimes
|
// In sync with EventEditorTimes
|
||||||
|
|||||||
@@ -12,8 +12,8 @@ interface EventSelectionStore {
|
|||||||
selectedEvents: Set<EntryId>;
|
selectedEvents: Set<EntryId>;
|
||||||
anchoredIndex: MaybeNumber;
|
anchoredIndex: MaybeNumber;
|
||||||
cursor: MaybeString;
|
cursor: MaybeString;
|
||||||
entryMode: 'event' | 'block' | null;
|
entryMode: 'event' | 'single' | null;
|
||||||
setSelectedBlock: (selectionArgs: { id: EntryId }) => void;
|
setSingleEntrySelection: (selectionArgs: { id: EntryId }) => void;
|
||||||
setSelectedEvents: (selectionArgs: { id: EntryId; index: number; selectMode: SelectionMode }) => void;
|
setSelectedEvents: (selectionArgs: { id: EntryId; index: number; selectMode: SelectionMode }) => void;
|
||||||
clearSelectedEvents: () => void;
|
clearSelectedEvents: () => void;
|
||||||
clearMultiSelect: () => void;
|
clearMultiSelect: () => void;
|
||||||
@@ -25,14 +25,14 @@ export const useEventSelection = create<EventSelectionStore>()((set, get) => ({
|
|||||||
anchoredIndex: null,
|
anchoredIndex: null,
|
||||||
cursor: null,
|
cursor: null,
|
||||||
entryMode: null,
|
entryMode: null,
|
||||||
setSelectedBlock: ({ id }) => {
|
setSingleEntrySelection: ({ id }) => {
|
||||||
set({ selectedEvents: new Set([id]), anchoredIndex: null, cursor: id, entryMode: 'block' });
|
set({ selectedEvents: new Set([id]), anchoredIndex: null, cursor: id, entryMode: 'single' });
|
||||||
},
|
},
|
||||||
setSelectedEvents: ({ id, index, selectMode }) => {
|
setSelectedEvents: ({ id, index, selectMode }) => {
|
||||||
const { selectedEvents, anchoredIndex, entryMode } = get();
|
const { selectedEvents, anchoredIndex, entryMode } = get();
|
||||||
|
|
||||||
// if we are in block mode, we replace the selection and change the mode
|
// if we are in single mode, we replace the selection and change the mode
|
||||||
if (entryMode === 'block') {
|
if (entryMode === 'single') {
|
||||||
return set({ selectedEvents: new Set([id]), anchoredIndex: index, cursor: id, entryMode: 'event' });
|
return set({ selectedEvents: new Set([id]), anchoredIndex: index, cursor: id, entryMode: 'event' });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+16
-16
@@ -2,12 +2,12 @@ import { RefObject, useEffect } from 'react';
|
|||||||
import { useQueryClient } from '@tanstack/react-query';
|
import { useQueryClient } from '@tanstack/react-query';
|
||||||
import { RowModel, Table } from '@tanstack/react-table';
|
import { RowModel, Table } from '@tanstack/react-table';
|
||||||
import {
|
import {
|
||||||
isOntimeBlock,
|
|
||||||
isOntimeDelay,
|
isOntimeDelay,
|
||||||
isOntimeEvent,
|
isOntimeEvent,
|
||||||
|
isOntimeGroup,
|
||||||
isOntimeMilestone,
|
isOntimeMilestone,
|
||||||
OntimeBlock,
|
|
||||||
OntimeEntry,
|
OntimeEntry,
|
||||||
|
OntimeGroup,
|
||||||
Rundown,
|
Rundown,
|
||||||
} from 'ontime-types';
|
} from 'ontime-types';
|
||||||
import { colourToHex, cssOrHexToColour } from 'ontime-utils';
|
import { colourToHex, cssOrHexToColour } from 'ontime-utils';
|
||||||
@@ -18,9 +18,9 @@ import { useSelectedEventId } from '../../../../common/hooks/useSocket';
|
|||||||
import { getAccessibleColour } from '../../../../common/utils/styleUtils';
|
import { getAccessibleColour } from '../../../../common/utils/styleUtils';
|
||||||
import { usePersistedCuesheetOptions } from '../../cuesheet.options';
|
import { usePersistedCuesheetOptions } from '../../cuesheet.options';
|
||||||
|
|
||||||
import BlockRow from './BlockRow';
|
|
||||||
import DelayRow from './DelayRow';
|
import DelayRow from './DelayRow';
|
||||||
import EventRow from './EventRow';
|
import EventRow from './EventRow';
|
||||||
|
import GroupRow from './GroupRow';
|
||||||
import MilestoneRow from './MilestoneRow';
|
import MilestoneRow from './MilestoneRow';
|
||||||
import { cleanup } from './rowObserver';
|
import { cleanup } from './rowObserver';
|
||||||
|
|
||||||
@@ -39,7 +39,7 @@ export default function CuesheetBody({ rowModel, selectedRef, table }: CuesheetB
|
|||||||
let eventIndex = 0;
|
let eventIndex = 0;
|
||||||
// for the first event, it will be past if there is something selected
|
// for the first event, it will be past if there is something selected
|
||||||
let isPast = Boolean(selectedEventId);
|
let isPast = Boolean(selectedEventId);
|
||||||
let hadBlock = false;
|
let hadGroup = false;
|
||||||
|
|
||||||
// remove the observer when the table unmounts
|
// remove the observer when the table unmounts
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -62,11 +62,11 @@ export default function CuesheetBody({ rowModel, selectedRef, table }: CuesheetB
|
|||||||
isPast = false;
|
isPast = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isOntimeBlock(entry)) {
|
if (isOntimeGroup(entry)) {
|
||||||
return (
|
return (
|
||||||
<BlockRow
|
<GroupRow
|
||||||
key={key}
|
key={key}
|
||||||
blockId={entry.id}
|
groupId={entry.id}
|
||||||
colour={entry.colour}
|
colour={entry.colour}
|
||||||
hidePast={isPast && hidePast}
|
hidePast={isPast && hidePast}
|
||||||
rowId={row.id}
|
rowId={row.id}
|
||||||
@@ -88,7 +88,7 @@ export default function CuesheetBody({ rowModel, selectedRef, table }: CuesheetB
|
|||||||
if (entry.parent) {
|
if (entry.parent) {
|
||||||
const rundown = queryClient.getQueryData<Rundown>(RUNDOWN);
|
const rundown = queryClient.getQueryData<Rundown>(RUNDOWN);
|
||||||
const parentEntry = rundown?.entries[entry.parent];
|
const parentEntry = rundown?.entries[entry.parent];
|
||||||
parentBgColour = (parentEntry as OntimeBlock).colour ?? null;
|
parentBgColour = (parentEntry as OntimeGroup).colour ?? null;
|
||||||
}
|
}
|
||||||
return <DelayRow key={key} duration={delayVal} parentBgColour={parentBgColour} />;
|
return <DelayRow key={key} duration={delayVal} parentBgColour={parentBgColour} />;
|
||||||
}
|
}
|
||||||
@@ -113,7 +113,7 @@ export default function CuesheetBody({ rowModel, selectedRef, table }: CuesheetB
|
|||||||
if (entry.parent) {
|
if (entry.parent) {
|
||||||
const rundown = queryClient.getQueryData<Rundown>(RUNDOWN);
|
const rundown = queryClient.getQueryData<Rundown>(RUNDOWN);
|
||||||
const parentEntry = rundown?.entries[entry.parent];
|
const parentEntry = rundown?.entries[entry.parent];
|
||||||
parentBgColour = (parentEntry as OntimeBlock | undefined)?.colour ?? null;
|
parentBgColour = (parentEntry as OntimeGroup | undefined)?.colour ?? null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -153,15 +153,15 @@ export default function CuesheetBody({ rowModel, selectedRef, table }: CuesheetB
|
|||||||
}
|
}
|
||||||
|
|
||||||
let parentBgColour: string | undefined;
|
let parentBgColour: string | undefined;
|
||||||
let firstAfterBlock = false;
|
let firstAfterGroup = false;
|
||||||
if (entry.parent) {
|
if (entry.parent) {
|
||||||
const rundown = queryClient.getQueryData<Rundown>(RUNDOWN);
|
const rundown = queryClient.getQueryData<Rundown>(RUNDOWN);
|
||||||
const parentEntry = rundown?.entries[entry.parent] as OntimeBlock | undefined;
|
const parentEntry = rundown?.entries[entry.parent] as OntimeGroup | undefined;
|
||||||
parentBgColour = parentEntry?.colour;
|
parentBgColour = parentEntry?.colour;
|
||||||
hadBlock = true;
|
hadGroup = true;
|
||||||
} else if (hadBlock) {
|
} else if (hadGroup) {
|
||||||
firstAfterBlock = true;
|
firstAfterGroup = true;
|
||||||
hadBlock = false;
|
hadGroup = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -176,7 +176,7 @@ export default function CuesheetBody({ rowModel, selectedRef, table }: CuesheetB
|
|||||||
rowBgColour={rowBgColour}
|
rowBgColour={rowBgColour}
|
||||||
parentBgColour={parentBgColour}
|
parentBgColour={parentBgColour}
|
||||||
table={table}
|
table={table}
|
||||||
firstAfterBlock={firstAfterBlock}
|
firstAfterGroup={firstAfterGroup}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -11,7 +11,7 @@
|
|||||||
background: color-mix(in srgb, transparent 80%, var(--user-bg, $gray-500) 20%);
|
background: color-mix(in srgb, transparent 80%, var(--user-bg, $gray-500) 20%);
|
||||||
}
|
}
|
||||||
|
|
||||||
&.firstAfterBlock {
|
&.firstAfterGroup {
|
||||||
margin-top: 1rem;
|
margin-top: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ interface EventRowProps {
|
|||||||
rowBgColour?: string;
|
rowBgColour?: string;
|
||||||
parentBgColour?: string;
|
parentBgColour?: string;
|
||||||
table: Table<OntimeEntry>;
|
table: Table<OntimeEntry>;
|
||||||
firstAfterBlock: boolean;
|
firstAfterGroup: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function EventRow({
|
export default function EventRow({
|
||||||
@@ -39,7 +39,7 @@ export default function EventRow({
|
|||||||
rowBgColour,
|
rowBgColour,
|
||||||
parentBgColour,
|
parentBgColour,
|
||||||
table,
|
table,
|
||||||
firstAfterBlock,
|
firstAfterGroup,
|
||||||
}: EventRowProps) {
|
}: EventRowProps) {
|
||||||
const { cuesheetMode, hideIndexColumn } = table.options.meta?.options ?? {
|
const { cuesheetMode, hideIndexColumn } = table.options.meta?.options ?? {
|
||||||
cuesheetMode: AppMode.Edit,
|
cuesheetMode: AppMode.Edit,
|
||||||
@@ -75,7 +75,7 @@ export default function EventRow({
|
|||||||
className={cx([
|
className={cx([
|
||||||
style.eventRow,
|
style.eventRow,
|
||||||
event.skip && style.skip,
|
event.skip && style.skip,
|
||||||
firstAfterBlock && style.firstAfterBlock,
|
firstAfterGroup && style.firstAfterGroup,
|
||||||
Boolean(parentBgColour) && style.hasParent,
|
Boolean(parentBgColour) && style.hasParent,
|
||||||
])}
|
])}
|
||||||
style={{
|
style={{
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
@import '../CuesheetTable.module.scss';
|
@import '../CuesheetTable.module.scss';
|
||||||
|
|
||||||
.blockRow {
|
.groupRow {
|
||||||
margin-top: 1rem;
|
margin-top: 1rem;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
display: flex;
|
display: flex;
|
||||||
+9
-9
@@ -3,14 +3,14 @@ import { flexRender, Table } from '@tanstack/react-table';
|
|||||||
import { EntryId, OntimeEntry, SupportedEntry } from 'ontime-types';
|
import { EntryId, OntimeEntry, SupportedEntry } from 'ontime-types';
|
||||||
|
|
||||||
import IconButton from '../../../../common/components/buttons/IconButton';
|
import IconButton from '../../../../common/components/buttons/IconButton';
|
||||||
import { useCurrentBlockId } from '../../../../common/hooks/useSocket';
|
import { useCurrentGroupId } from '../../../../common/hooks/useSocket';
|
||||||
import { AppMode } from '../../../../ontimeConfig';
|
import { AppMode } from '../../../../ontimeConfig';
|
||||||
import { useCuesheetTableMenu } from '../cuesheet-table-menu/useCuesheetTableMenu';
|
import { useCuesheetTableMenu } from '../cuesheet-table-menu/useCuesheetTableMenu';
|
||||||
|
|
||||||
import style from './BlockRow.module.scss';
|
import style from './GroupRow.module.scss';
|
||||||
|
|
||||||
interface BlockRowProps {
|
interface GroupRowProps {
|
||||||
blockId: EntryId;
|
groupId: EntryId;
|
||||||
colour: string;
|
colour: string;
|
||||||
hidePast: boolean;
|
hidePast: boolean;
|
||||||
rowId: string;
|
rowId: string;
|
||||||
@@ -18,8 +18,8 @@ interface BlockRowProps {
|
|||||||
table: Table<OntimeEntry>;
|
table: Table<OntimeEntry>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function BlockRow({ blockId, colour, hidePast, rowId, rowIndex, table }: BlockRowProps) {
|
export default function GroupRow({ groupId, colour, hidePast, rowId, rowIndex, table }: GroupRowProps) {
|
||||||
const { currentBlockId } = useCurrentBlockId();
|
const { currentGroupId } = useCurrentGroupId();
|
||||||
|
|
||||||
const { cuesheetMode, hideIndexColumn } = table.options.meta?.options ?? {
|
const { cuesheetMode, hideIndexColumn } = table.options.meta?.options ?? {
|
||||||
cuesheetMode: AppMode.Edit,
|
cuesheetMode: AppMode.Edit,
|
||||||
@@ -28,12 +28,12 @@ export default function BlockRow({ blockId, colour, hidePast, rowId, rowIndex, t
|
|||||||
|
|
||||||
const openMenu = useCuesheetTableMenu((store) => store.openMenu);
|
const openMenu = useCuesheetTableMenu((store) => store.openMenu);
|
||||||
|
|
||||||
if (hidePast && !currentBlockId) {
|
if (hidePast && !currentGroupId) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<tr className={style.blockRow} style={{ '--user-bg': colour }} data-testid='cuesheet-block'>
|
<tr className={style.groupRow} style={{ '--user-bg': colour }} data-testid='cuesheet-group'>
|
||||||
{cuesheetMode === AppMode.Edit && (
|
{cuesheetMode === AppMode.Edit && (
|
||||||
<td className={style.actionColumn} tabIndex={-1} role='cell'>
|
<td className={style.actionColumn} tabIndex={-1} role='cell'>
|
||||||
<IconButton
|
<IconButton
|
||||||
@@ -43,7 +43,7 @@ export default function BlockRow({ blockId, colour, hidePast, rowId, rowIndex, t
|
|||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
const rect = e.currentTarget.getBoundingClientRect();
|
const rect = e.currentTarget.getBoundingClientRect();
|
||||||
const yPos = 8 + rect.y + rect.height / 2;
|
const yPos = 8 + rect.y + rect.height / 2;
|
||||||
openMenu({ x: rect.x, y: yPos }, blockId, SupportedEntry.Block, rowIndex, null, null);
|
openMenu({ x: rect.x, y: yPos }, groupId, SupportedEntry.Group, rowIndex, null, null);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<IoEllipsisHorizontal />
|
<IoEllipsisHorizontal />
|
||||||
+2
-2
@@ -134,7 +134,7 @@ function MakeMultiLineField({ row, column, table }: CellContext<OntimeEntry, unk
|
|||||||
[column.id, row.index, table.options.meta],
|
[column.id, row.index, table.options.meta],
|
||||||
);
|
);
|
||||||
|
|
||||||
// not all entries have all properties (eg blocks)
|
// not all entries have all properties (eg groups)
|
||||||
const initialValue = row.original[column.id as keyof OntimeEntry];
|
const initialValue = row.original[column.id as keyof OntimeEntry];
|
||||||
if (initialValue === undefined) {
|
if (initialValue === undefined) {
|
||||||
return null;
|
return null;
|
||||||
@@ -174,7 +174,7 @@ function MakeSingleLineField({ row, column, table }: CellContext<OntimeEntry, un
|
|||||||
[column.id, row.index, table.options.meta],
|
[column.id, row.index, table.options.meta],
|
||||||
);
|
);
|
||||||
|
|
||||||
// not all entries have all properties (eg blocks)
|
// not all entries have all properties (eg groups)
|
||||||
const initialValue = row.original[column.id as keyof OntimeEntry];
|
const initialValue = row.original[column.id as keyof OntimeEntry];
|
||||||
if (initialValue === undefined) {
|
if (initialValue === undefined) {
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
import { ChangeEvent, useCallback, useEffect, useRef, useState } from 'react';
|
import { ChangeEvent, useCallback, useEffect, useRef, useState } from 'react';
|
||||||
import { useSessionStorage } from '@mantine/hooks';
|
import { useSessionStorage } from '@mantine/hooks';
|
||||||
import { EntryId, isOntimeBlock, isOntimeEvent, MaybeString, SupportedEntry } from 'ontime-types';
|
import { EntryId, isOntimeEvent, isOntimeGroup, MaybeString, SupportedEntry } from 'ontime-types';
|
||||||
|
|
||||||
import { useFlatRundown } from '../../../common/hooks-query/useRundown';
|
import { useFlatRundown } from '../../../common/hooks-query/useRundown';
|
||||||
import { useEventSelection } from '../../../features/rundown/useEventSelection';
|
import { useEventSelection } from '../../../features/rundown/useEventSelection';
|
||||||
|
|
||||||
const maxResults = 12;
|
const maxResults = 12;
|
||||||
|
|
||||||
type FilterableBlock = {
|
type FilterableGroup = {
|
||||||
type: SupportedEntry.Block;
|
type: SupportedEntry.Group;
|
||||||
id: string;
|
id: string;
|
||||||
index: number;
|
index: number;
|
||||||
title: string;
|
title: string;
|
||||||
@@ -25,7 +25,7 @@ type FilterableEvent = {
|
|||||||
parent: MaybeString;
|
parent: MaybeString;
|
||||||
};
|
};
|
||||||
|
|
||||||
type FilterableEntry = FilterableBlock | FilterableEvent;
|
type FilterableEntry = FilterableGroup | FilterableEvent;
|
||||||
|
|
||||||
export default function useFinder() {
|
export default function useFinder() {
|
||||||
const { data, rundownId } = useFlatRundown();
|
const { data, rundownId } = useFlatRundown();
|
||||||
@@ -179,15 +179,15 @@ export default function useFinder() {
|
|||||||
}
|
}
|
||||||
eventIndex++;
|
eventIndex++;
|
||||||
}
|
}
|
||||||
if (isOntimeBlock(event)) {
|
if (isOntimeGroup(event)) {
|
||||||
if (event.title.toLowerCase().includes(searchString)) {
|
if (event.title.toLowerCase().includes(searchString)) {
|
||||||
remaining--;
|
remaining--;
|
||||||
results.push({
|
results.push({
|
||||||
type: SupportedEntry.Block,
|
type: SupportedEntry.Group,
|
||||||
id: event.id,
|
id: event.id,
|
||||||
index: i,
|
index: i,
|
||||||
title: event.title,
|
title: event.title,
|
||||||
} satisfies FilterableBlock);
|
} satisfies FilterableGroup);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ describe('test parseDatabaseModel() with demo project (valid)', () => {
|
|||||||
// remove time-related fields from the comparison
|
// remove time-related fields from the comparison
|
||||||
// these are not calculated in the parser
|
// these are not calculated in the parser
|
||||||
Object.values(filteredDemoProject.rundowns.default.entries).forEach((entry: any) => {
|
Object.values(filteredDemoProject.rundowns.default.entries).forEach((entry: any) => {
|
||||||
if (entry.type === SupportedEntry.Block) {
|
if (entry.type === SupportedEntry.Group) {
|
||||||
delete entry.timeStart;
|
delete entry.timeStart;
|
||||||
delete entry.timeEnd;
|
delete entry.timeEnd;
|
||||||
delete entry.duration;
|
delete entry.duration;
|
||||||
@@ -54,7 +54,7 @@ describe('test parseDatabaseModel() with demo project (valid)', () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
Object.values(data.rundowns.default.entries).forEach((entry: any) => {
|
Object.values(data.rundowns.default.entries).forEach((entry: any) => {
|
||||||
if (entry.type === SupportedEntry.Block) {
|
if (entry.type === SupportedEntry.Group) {
|
||||||
delete entry.timeStart;
|
delete entry.timeStart;
|
||||||
delete entry.timeEnd;
|
delete entry.timeEnd;
|
||||||
delete entry.duration;
|
delete entry.duration;
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import {
|
|||||||
EndAction,
|
EndAction,
|
||||||
EntryCustomFields,
|
EntryCustomFields,
|
||||||
NormalisedAutomation,
|
NormalisedAutomation,
|
||||||
OntimeBlock,
|
OntimeGroup,
|
||||||
OntimeEntry,
|
OntimeEntry,
|
||||||
ProjectData,
|
ProjectData,
|
||||||
ProjectRundowns,
|
ProjectRundowns,
|
||||||
@@ -317,7 +317,8 @@ export function migrateAutomations(jsonData: object): AutomationSettings | undef
|
|||||||
* - add parent
|
* - add parent
|
||||||
*
|
*
|
||||||
* - block:
|
* - block:
|
||||||
* - add all the new blocks of the block that is now a group
|
* - rename to group
|
||||||
|
* - create group data
|
||||||
*/
|
*/
|
||||||
export function migrateRundown(
|
export function migrateRundown(
|
||||||
jsonData: object,
|
jsonData: object,
|
||||||
@@ -392,13 +393,13 @@ export function migrateRundown(
|
|||||||
});
|
});
|
||||||
} else if (entry.type === 'block') {
|
} else if (entry.type === 'block') {
|
||||||
if (parent) {
|
if (parent) {
|
||||||
(newRundown.entries[parent] as OntimeBlock).entries = [...children];
|
(newRundown.entries[parent] as OntimeGroup).entries = [...children];
|
||||||
children = [];
|
children = [];
|
||||||
}
|
}
|
||||||
parent = entry.id;
|
parent = entry.id;
|
||||||
append({
|
append({
|
||||||
id: entry.id,
|
id: entry.id,
|
||||||
type: SupportedEntry.Block,
|
type: SupportedEntry.Group,
|
||||||
title: entry.title,
|
title: entry.title,
|
||||||
note: '', // leave blank
|
note: '', // leave blank
|
||||||
entries: [], // leave empty
|
entries: [], // leave empty
|
||||||
@@ -418,7 +419,7 @@ export function migrateRundown(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (parent) {
|
if (parent) {
|
||||||
(newRundown.entries[parent] as OntimeBlock).entries = [...children];
|
(newRundown.entries[parent] as OntimeGroup).entries = [...children];
|
||||||
children = [];
|
children = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ describe('v3 to v4', () => {
|
|||||||
dayOffset: 0,
|
dayOffset: 0,
|
||||||
gap: 0,
|
gap: 0,
|
||||||
},
|
},
|
||||||
{ id: 'block0', type: 'block', title: 'BLOCK 0' },
|
{ id: 'group0', type: 'block', title: 'GROUP 0' },
|
||||||
{
|
{
|
||||||
id: 'event2',
|
id: 'event2',
|
||||||
type: SupportedEntry.Event,
|
type: SupportedEntry.Event,
|
||||||
@@ -107,7 +107,7 @@ describe('v3 to v4', () => {
|
|||||||
dayOffset: 0,
|
dayOffset: 0,
|
||||||
gap: 0,
|
gap: 0,
|
||||||
},
|
},
|
||||||
{ id: 'block1', type: 'block', title: 'BLOCK 1' },
|
{ id: 'group1', type: 'block', title: 'GROUP 1' },
|
||||||
{ id: 'delay', type: 'delay', duration: 1000 },
|
{ id: 'delay', type: 'delay', duration: 1000 },
|
||||||
],
|
],
|
||||||
project: {
|
project: {
|
||||||
@@ -268,8 +268,8 @@ describe('v3 to v4', () => {
|
|||||||
const expectedRundown: Rundown = {
|
const expectedRundown: Rundown = {
|
||||||
id: 'default',
|
id: 'default',
|
||||||
title: 'Default',
|
title: 'Default',
|
||||||
order: ['event1', 'block0', 'block1'],
|
order: ['event1', 'group0', 'group1'],
|
||||||
flatOrder: ['event1', 'block0', 'event2', 'event3', 'block1', 'delay'],
|
flatOrder: ['event1', 'group0', 'event2', 'event3', 'group1', 'delay'],
|
||||||
entries: {
|
entries: {
|
||||||
event1: {
|
event1: {
|
||||||
id: 'event1',
|
id: 'event1',
|
||||||
@@ -298,10 +298,10 @@ describe('v3 to v4', () => {
|
|||||||
dayOffset: 0,
|
dayOffset: 0,
|
||||||
gap: 0,
|
gap: 0,
|
||||||
},
|
},
|
||||||
block0: {
|
group0: {
|
||||||
id: 'block0',
|
id: 'group0',
|
||||||
type: SupportedEntry.Block,
|
type: SupportedEntry.Group,
|
||||||
title: 'BLOCK 0',
|
title: 'GROUP 0',
|
||||||
colour: '',
|
colour: '',
|
||||||
custom: {},
|
custom: {},
|
||||||
duration: 0,
|
duration: 0,
|
||||||
@@ -337,7 +337,7 @@ describe('v3 to v4', () => {
|
|||||||
},
|
},
|
||||||
triggers: [{ id: 'testTrig', title: 'Test trigger', trigger: TimerLifeCycle.onStart, automationId: '1' }],
|
triggers: [{ id: 'testTrig', title: 'Test trigger', trigger: TimerLifeCycle.onStart, automationId: '1' }],
|
||||||
flag: false,
|
flag: false,
|
||||||
parent: 'block0',
|
parent: 'group0',
|
||||||
revision: -1,
|
revision: -1,
|
||||||
delay: 0,
|
delay: 0,
|
||||||
dayOffset: 0,
|
dayOffset: 0,
|
||||||
@@ -368,16 +368,16 @@ describe('v3 to v4', () => {
|
|||||||
},
|
},
|
||||||
triggers: [{ id: 'testTrig', title: 'Test trigger', trigger: TimerLifeCycle.onStart, automationId: '1' }],
|
triggers: [{ id: 'testTrig', title: 'Test trigger', trigger: TimerLifeCycle.onStart, automationId: '1' }],
|
||||||
flag: false,
|
flag: false,
|
||||||
parent: 'block0',
|
parent: 'group0',
|
||||||
revision: -1,
|
revision: -1,
|
||||||
delay: 0,
|
delay: 0,
|
||||||
dayOffset: 0,
|
dayOffset: 0,
|
||||||
gap: 0,
|
gap: 0,
|
||||||
},
|
},
|
||||||
block1: {
|
group1: {
|
||||||
id: 'block1',
|
id: 'group1',
|
||||||
type: SupportedEntry.Block,
|
type: SupportedEntry.Group,
|
||||||
title: 'BLOCK 1',
|
title: 'GROUP 1',
|
||||||
colour: '',
|
colour: '',
|
||||||
custom: {},
|
custom: {},
|
||||||
duration: 0,
|
duration: 0,
|
||||||
@@ -393,7 +393,7 @@ describe('v3 to v4', () => {
|
|||||||
type: SupportedEntry.Delay,
|
type: SupportedEntry.Delay,
|
||||||
id: 'delay',
|
id: 'delay',
|
||||||
duration: 1000,
|
duration: 1000,
|
||||||
parent: 'block1',
|
parent: 'group1',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -156,10 +156,10 @@ describe('parseExcel()', () => {
|
|||||||
expect((firstEvent as OntimeEvent).title).toBe('A song from the hearth');
|
expect((firstEvent as OntimeEvent).title).toBe('A song from the hearth');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('imports blocks', () => {
|
it('imports groups', () => {
|
||||||
const testdata = [
|
const testdata = [
|
||||||
['Title', 'Timer type'],
|
['Title', 'Timer type'],
|
||||||
['a block', 'block'],
|
['a group', 'group'],
|
||||||
['an event', 'clock'],
|
['an event', 'clock'],
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -171,7 +171,7 @@ describe('parseExcel()', () => {
|
|||||||
const firstEvent = result.rundown.entries[result.rundown.order[0]];
|
const firstEvent = result.rundown.entries[result.rundown.order[0]];
|
||||||
|
|
||||||
expect(result.rundown.order.length).toBe(2);
|
expect(result.rundown.order.length).toBe(2);
|
||||||
expect((firstEvent as OntimeEvent).type).toBe(SupportedEntry.Block);
|
expect((firstEvent as OntimeEvent).type).toBe(SupportedEntry.Group);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('imports as events if there is no timer type column', () => {
|
it('imports as events if there is no timer type column', () => {
|
||||||
@@ -301,8 +301,8 @@ describe('parseExcel()', () => {
|
|||||||
['9:45:00', '10:56:00', 'B', 'x', 'count-down'],
|
['9:45:00', '10:56:00', 'B', 'x', 'count-down'],
|
||||||
['10:00:00', '16:36:00', 'C', 'x', 'count-down'],
|
['10:00:00', '16:36:00', 'C', 'x', 'count-down'],
|
||||||
['21:45:00', '22:56:00', 'D', '', 'count-down'],
|
['21:45:00', '22:56:00', 'D', '', 'count-down'],
|
||||||
['', '', 'BLOCK', 'x', 'block'], // <-- block with link
|
['', '', 'GROUP', 'x', 'group'], // <-- group with link
|
||||||
['00:0:00', '23:56:00', 'E', 'x', 'count-down'], // <-- link past blocks
|
['00:0:00', '23:56:00', 'E', 'x', 'count-down'], // <-- must link past previous group
|
||||||
];
|
];
|
||||||
|
|
||||||
const importMap = {
|
const importMap = {
|
||||||
@@ -315,7 +315,7 @@ describe('parseExcel()', () => {
|
|||||||
|
|
||||||
const result = parseExcel(testData, {}, 'testSheet', importMap);
|
const result = parseExcel(testData, {}, 'testSheet', importMap);
|
||||||
expect(result.rundown.order.length).toBe(6);
|
expect(result.rundown.order.length).toBe(6);
|
||||||
expect(result.rundown.order).toMatchObject(['A', 'B', 'C', 'D', 'BLOCK', 'E']);
|
expect(result.rundown.order).toMatchObject(['A', 'B', 'C', 'D', 'GROUP', 'E']);
|
||||||
|
|
||||||
expect(result.rundown.entries).toMatchObject({
|
expect(result.rundown.entries).toMatchObject({
|
||||||
A: {
|
A: {
|
||||||
@@ -330,8 +330,8 @@ describe('parseExcel()', () => {
|
|||||||
D: {
|
D: {
|
||||||
linkStart: false,
|
linkStart: false,
|
||||||
},
|
},
|
||||||
BLOCK: {
|
GROUP: {
|
||||||
type: SupportedEntry.Block,
|
type: SupportedEntry.Group,
|
||||||
},
|
},
|
||||||
E: {
|
E: {
|
||||||
linkStart: true,
|
linkStart: true,
|
||||||
|
|||||||
@@ -2,10 +2,10 @@ import {
|
|||||||
CustomFields,
|
CustomFields,
|
||||||
Rundown,
|
Rundown,
|
||||||
OntimeEvent,
|
OntimeEvent,
|
||||||
OntimeBlock,
|
OntimeGroup,
|
||||||
EntryCustomFields,
|
EntryCustomFields,
|
||||||
SupportedEntry,
|
SupportedEntry,
|
||||||
isOntimeBlock,
|
isOntimeGroup,
|
||||||
TimerType,
|
TimerType,
|
||||||
CustomFieldKey,
|
CustomFieldKey,
|
||||||
} from 'ontime-types';
|
} from 'ontime-types';
|
||||||
@@ -31,6 +31,7 @@ import { parseExcelDate } from '../../utils/time.js';
|
|||||||
* @param {array} excelData - array with excel sheet
|
* @param {array} excelData - array with excel sheet
|
||||||
* @param {ImportOptions} options - an object that contains the import map
|
* @param {ImportOptions} options - an object that contains the import map
|
||||||
* @returns {object} - parsed object
|
* @returns {object} - parsed object
|
||||||
|
* TODO: import milestones
|
||||||
*/
|
*/
|
||||||
export const parseExcel = (
|
export const parseExcel = (
|
||||||
excelData: unknown[][],
|
excelData: unknown[][],
|
||||||
@@ -170,7 +171,7 @@ export const parseExcel = (
|
|||||||
},
|
},
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
const entry: Partial<Merge<OntimeEvent, OntimeBlock>> = {};
|
const entry: Partial<Merge<OntimeEvent, OntimeGroup>> = {};
|
||||||
const entryCustomFields: EntryCustomFields = {};
|
const entryCustomFields: EntryCustomFields = {};
|
||||||
|
|
||||||
for (let j = 0; j < row.length; j++) {
|
for (let j = 0; j < row.length; j++) {
|
||||||
@@ -178,16 +179,16 @@ export const parseExcel = (
|
|||||||
// 1. we check if we have set a flag for a known field
|
// 1. we check if we have set a flag for a known field
|
||||||
if (j === timerTypeIndex) {
|
if (j === timerTypeIndex) {
|
||||||
const maybeTimeType = makeString(column, '');
|
const maybeTimeType = makeString(column, '');
|
||||||
if (maybeTimeType === 'block') {
|
if (maybeTimeType === 'group') {
|
||||||
// we leave this as a clue for the object filtering later on
|
// we leave this as a clue for the object filtering later on
|
||||||
entry.type = SupportedEntry.Block;
|
entry.type = SupportedEntry.Group;
|
||||||
entry.entries = [];
|
entry.entries = [];
|
||||||
} else if (maybeTimeType === '' || maybeTimeType === 'event' || isKnownTimerType(maybeTimeType)) {
|
} else if (maybeTimeType === '' || maybeTimeType === 'event' || isKnownTimerType(maybeTimeType)) {
|
||||||
// @ts-expect-error -- we leave this as a clue for the object filtering later on
|
// @ts-expect-error -- we leave this as a clue for the object filtering later on
|
||||||
entry.type = SupportedEntry.Event;
|
entry.type = SupportedEntry.Event;
|
||||||
entry.timerType = validateTimerType(maybeTimeType);
|
entry.timerType = validateTimerType(maybeTimeType);
|
||||||
} else {
|
} else {
|
||||||
// if it is not a block or a known type, we dont import it
|
// if it is not a group or a known type, we dont import it
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
} else if (j === titleIndex) {
|
} else if (j === titleIndex) {
|
||||||
@@ -258,11 +259,11 @@ export const parseExcel = (
|
|||||||
}
|
}
|
||||||
|
|
||||||
const id = entry.id || generateId();
|
const id = entry.id || generateId();
|
||||||
// from excel, we can only get blocks and events
|
// from excel, we can only get groups, milestones and events
|
||||||
if (isOntimeBlock(entry)) {
|
if (isOntimeGroup(entry)) {
|
||||||
const block: OntimeBlock = { ...entry, custom: { ...entryCustomFields } };
|
const group: OntimeGroup = { ...entry, custom: { ...entryCustomFields } };
|
||||||
rundown.order.push(id);
|
rundown.order.push(id);
|
||||||
rundown.entries[id] = block;
|
rundown.entries[id] = group;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import {
|
|||||||
SupportedEntry,
|
SupportedEntry,
|
||||||
OntimeEvent,
|
OntimeEvent,
|
||||||
OntimeDelay,
|
OntimeDelay,
|
||||||
OntimeBlock,
|
OntimeGroup,
|
||||||
Rundown,
|
Rundown,
|
||||||
CustomField,
|
CustomField,
|
||||||
OntimeMilestone,
|
OntimeMilestone,
|
||||||
@@ -16,8 +16,8 @@ const baseEvent = {
|
|||||||
revision: 1,
|
revision: 1,
|
||||||
};
|
};
|
||||||
|
|
||||||
const baseBlock = {
|
const baseGroup = {
|
||||||
type: SupportedEntry.Block,
|
type: SupportedEntry.Group,
|
||||||
entries: [],
|
entries: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -28,7 +28,7 @@ const baseMilestone = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Utility to create a Ontime event
|
* Utility to create an Ontime event
|
||||||
*/
|
*/
|
||||||
export function makeOntimeEvent(patch: Partial<OntimeEvent>): OntimeEvent {
|
export function makeOntimeEvent(patch: Partial<OntimeEvent>): OntimeEvent {
|
||||||
return {
|
return {
|
||||||
@@ -38,21 +38,21 @@ export function makeOntimeEvent(patch: Partial<OntimeEvent>): OntimeEvent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Utility to create a delay event
|
* Utility to create a delay entry
|
||||||
*/
|
*/
|
||||||
export function makeOntimeDelay(patch: Partial<OntimeDelay>): OntimeDelay {
|
export function makeOntimeDelay(patch: Partial<OntimeDelay>): OntimeDelay {
|
||||||
return { id: 'delay', type: SupportedEntry.Delay, duration: 0, ...patch } as OntimeDelay;
|
return { id: 'delay', type: SupportedEntry.Delay, duration: 0, ...patch } as OntimeDelay;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Utility to create a block event
|
* Utility to create a group entry
|
||||||
*/
|
*/
|
||||||
export function makeOntimeBlock(patch: Partial<OntimeBlock>): OntimeBlock {
|
export function makeOntimeGroup(patch: Partial<OntimeGroup>): OntimeGroup {
|
||||||
return { id: 'block', ...baseBlock, ...patch } as OntimeBlock;
|
return { id: 'group', ...baseGroup, ...patch } as OntimeGroup;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Utility to create a block event
|
* Utility to create a milestone entry
|
||||||
*/
|
*/
|
||||||
export function makeOntimeMilestone(patch: Partial<OntimeMilestone>): OntimeMilestone {
|
export function makeOntimeMilestone(patch: Partial<OntimeMilestone>): OntimeMilestone {
|
||||||
return { id: 'milestone', ...baseMilestone, ...patch } as OntimeMilestone;
|
return { id: 'milestone', ...baseMilestone, ...patch } as OntimeMilestone;
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import { CustomFields, OntimeBlock, OntimeDelay, OntimeEvent, SupportedEntry, TimeStrategy } from 'ontime-types';
|
import { CustomFields, OntimeGroup, OntimeDelay, OntimeEvent, SupportedEntry, TimeStrategy } from 'ontime-types';
|
||||||
import { dayInMs, MILLIS_PER_HOUR, MILLIS_PER_MINUTE } from 'ontime-utils';
|
import { dayInMs, MILLIS_PER_HOUR, MILLIS_PER_MINUTE } from 'ontime-utils';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
makeOntimeEvent,
|
makeOntimeEvent,
|
||||||
makeRundown,
|
makeRundown,
|
||||||
makeOntimeBlock,
|
makeOntimeGroup,
|
||||||
makeOntimeDelay,
|
makeOntimeDelay,
|
||||||
makeCustomField,
|
makeCustomField,
|
||||||
} from '../__mocks__/rundown.mocks.js';
|
} from '../__mocks__/rundown.mocks.js';
|
||||||
@@ -112,7 +112,7 @@ describe('processRundown()', () => {
|
|||||||
order: ['1', '2', '3'],
|
order: ['1', '2', '3'],
|
||||||
entries: {
|
entries: {
|
||||||
'1': makeOntimeEvent({ id: '1' }),
|
'1': makeOntimeEvent({ id: '1' }),
|
||||||
'2': makeOntimeBlock({ id: '2' }),
|
'2': makeOntimeGroup({ id: '2' }),
|
||||||
'3': makeOntimeDelay({ id: '3' }),
|
'3': makeOntimeDelay({ id: '3' }),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -121,7 +121,7 @@ describe('processRundown()', () => {
|
|||||||
expect(initResult.order.length).toBe(3);
|
expect(initResult.order.length).toBe(3);
|
||||||
expect(initResult.order).toStrictEqual(['1', '2', '3']);
|
expect(initResult.order).toStrictEqual(['1', '2', '3']);
|
||||||
expect(initResult.entries['1'].type).toBe(SupportedEntry.Event);
|
expect(initResult.entries['1'].type).toBe(SupportedEntry.Event);
|
||||||
expect(initResult.entries['2'].type).toBe(SupportedEntry.Block);
|
expect(initResult.entries['2'].type).toBe(SupportedEntry.Group);
|
||||||
expect(initResult.entries['3'].type).toBe(SupportedEntry.Delay);
|
expect(initResult.entries['3'].type).toBe(SupportedEntry.Delay);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -142,14 +142,14 @@ describe('processRundown()', () => {
|
|||||||
|
|
||||||
it('accounts for gaps in rundown when calculating delays', () => {
|
it('accounts for gaps in rundown when calculating delays', () => {
|
||||||
const rundown = makeRundown({
|
const rundown = makeRundown({
|
||||||
order: ['1', 'delay', '2', 'block', '3', 'another-block', '4'],
|
order: ['1', 'delay', '2', 'group', '3', 'another-group', '4'],
|
||||||
entries: {
|
entries: {
|
||||||
'1': makeOntimeEvent({ id: '1', timeStart: 100, timeEnd: 200, duration: 100 }),
|
'1': makeOntimeEvent({ id: '1', timeStart: 100, timeEnd: 200, duration: 100 }),
|
||||||
delay: makeOntimeDelay({ id: 'delay', duration: 200 }),
|
delay: makeOntimeDelay({ id: 'delay', duration: 200 }),
|
||||||
'2': makeOntimeEvent({ id: '2', timeStart: 200, timeEnd: 300, duration: 100 }),
|
'2': makeOntimeEvent({ id: '2', timeStart: 200, timeEnd: 300, duration: 100 }),
|
||||||
block: makeOntimeBlock({ id: 'block', title: 'break' }),
|
group: makeOntimeGroup({ id: 'group', title: 'break' }),
|
||||||
'3': makeOntimeEvent({ id: '3', timeStart: 400, timeEnd: 500, duration: 100 }),
|
'3': makeOntimeEvent({ id: '3', timeStart: 400, timeEnd: 500, duration: 100 }),
|
||||||
'another-block': makeOntimeBlock({ id: 'another-block', title: 'another-break' }),
|
'another-group': makeOntimeGroup({ id: 'another-group', title: 'another-break' }),
|
||||||
'4': makeOntimeEvent({ id: '4', timeStart: 600, timeEnd: 700, duration: 100 }),
|
'4': makeOntimeEvent({ id: '4', timeStart: 600, timeEnd: 700, duration: 100 }),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -297,14 +297,14 @@ describe('processRundown()', () => {
|
|||||||
|
|
||||||
it('handles negative delays', () => {
|
it('handles negative delays', () => {
|
||||||
const rundown = makeRundown({
|
const rundown = makeRundown({
|
||||||
order: ['1', 'delay', '2', 'block', '3', 'another-block', '4'],
|
order: ['1', 'delay', '2', 'group', '3', 'another-group', '4'],
|
||||||
entries: {
|
entries: {
|
||||||
'1': makeOntimeEvent({ id: '1', timeStart: 100, timeEnd: 200, duration: 100 }),
|
'1': makeOntimeEvent({ id: '1', timeStart: 100, timeEnd: 200, duration: 100 }),
|
||||||
delay: makeOntimeDelay({ id: 'delay', duration: -200 }),
|
delay: makeOntimeDelay({ id: 'delay', duration: -200 }),
|
||||||
'2': makeOntimeEvent({ id: '2', timeStart: 200, timeEnd: 300, duration: 100 }),
|
'2': makeOntimeEvent({ id: '2', timeStart: 200, timeEnd: 300, duration: 100 }),
|
||||||
block: makeOntimeBlock({ id: 'block', title: 'break' }),
|
group: makeOntimeGroup({ id: 'group', title: 'break' }),
|
||||||
'3': makeOntimeEvent({ id: '3', timeStart: 400, timeEnd: 500, duration: 100 }),
|
'3': makeOntimeEvent({ id: '3', timeStart: 400, timeEnd: 500, duration: 100 }),
|
||||||
'another-block': makeOntimeBlock({ id: 'another-block', title: 'another-break' }),
|
'another-group': makeOntimeGroup({ id: 'another-group', title: 'another-break' }),
|
||||||
'4': makeOntimeEvent({ id: '4', timeStart: 600, timeEnd: 700, duration: 100 }),
|
'4': makeOntimeEvent({ id: '4', timeStart: 600, timeEnd: 700, duration: 100 }),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -321,7 +321,7 @@ describe('processRundown()', () => {
|
|||||||
|
|
||||||
it('links times across events', () => {
|
it('links times across events', () => {
|
||||||
const rundown = makeRundown({
|
const rundown = makeRundown({
|
||||||
order: ['1', '2', 'block', 'delay', '3'],
|
order: ['1', '2', 'group', 'delay', '3'],
|
||||||
entries: {
|
entries: {
|
||||||
'1': makeOntimeEvent({
|
'1': makeOntimeEvent({
|
||||||
id: '1',
|
id: '1',
|
||||||
@@ -338,7 +338,7 @@ describe('processRundown()', () => {
|
|||||||
linkStart: true,
|
linkStart: true,
|
||||||
timeStrategy: TimeStrategy.LockEnd,
|
timeStrategy: TimeStrategy.LockEnd,
|
||||||
}),
|
}),
|
||||||
block: makeOntimeBlock({ id: 'block' }),
|
group: makeOntimeGroup({ id: 'group' }),
|
||||||
delay: makeOntimeDelay({ id: 'delay' }),
|
delay: makeOntimeDelay({ id: 'delay' }),
|
||||||
'3': makeOntimeEvent({
|
'3': makeOntimeEvent({
|
||||||
id: '3',
|
id: '3',
|
||||||
@@ -570,7 +570,7 @@ describe('processRundown()', () => {
|
|||||||
const rundown = makeRundown({
|
const rundown = makeRundown({
|
||||||
order: ['1'],
|
order: ['1'],
|
||||||
entries: {
|
entries: {
|
||||||
'1': makeOntimeBlock({ id: '1', entries: ['100', '200', '300'] }),
|
'1': makeOntimeGroup({ id: '1', entries: ['100', '200', '300'] }),
|
||||||
'100': makeOntimeEvent({ id: '100', timeStart: 100, timeEnd: 200, duration: 100, linkStart: false }),
|
'100': makeOntimeEvent({ id: '100', timeStart: 100, timeEnd: 200, duration: 100, linkStart: false }),
|
||||||
'200': makeOntimeEvent({ id: '200', timeStart: 200, timeEnd: 300, duration: 100 }),
|
'200': makeOntimeEvent({ id: '200', timeStart: 200, timeEnd: 300, duration: 100 }),
|
||||||
'300': makeOntimeEvent({ id: '300', timeStart: 300, timeEnd: 400, duration: 100 }),
|
'300': makeOntimeEvent({ id: '300', timeStart: 300, timeEnd: 400, duration: 100 }),
|
||||||
@@ -583,7 +583,7 @@ describe('processRundown()', () => {
|
|||||||
expect(generatedRundown.totalDelay).toBe(0);
|
expect(generatedRundown.totalDelay).toBe(0);
|
||||||
expect(generatedRundown.entries).toMatchObject({
|
expect(generatedRundown.entries).toMatchObject({
|
||||||
'1': {
|
'1': {
|
||||||
type: SupportedEntry.Block,
|
type: SupportedEntry.Group,
|
||||||
entries: ['100', '200', '300'],
|
entries: ['100', '200', '300'],
|
||||||
timeStart: 100,
|
timeStart: 100,
|
||||||
timeEnd: 400,
|
timeEnd: 400,
|
||||||
@@ -601,15 +601,15 @@ describe('processRundown()', () => {
|
|||||||
order: ['0', '1', '2', '3'],
|
order: ['0', '1', '2', '3'],
|
||||||
entries: {
|
entries: {
|
||||||
'0': makeOntimeEvent({ id: '0', timeStart: 0, timeEnd: 10, duration: 10, linkStart: false }),
|
'0': makeOntimeEvent({ id: '0', timeStart: 0, timeEnd: 10, duration: 10, linkStart: false }),
|
||||||
'1': makeOntimeBlock({ id: '1', entries: ['101', '102', '103'] }),
|
'1': makeOntimeGroup({ id: '1', entries: ['101', '102', '103'] }),
|
||||||
'101': makeOntimeEvent({ id: '101', timeStart: 100, timeEnd: 200, duration: 100, linkStart: false }),
|
'101': makeOntimeEvent({ id: '101', timeStart: 100, timeEnd: 200, duration: 100, linkStart: false }),
|
||||||
'102': makeOntimeEvent({ id: '102', timeStart: 200, timeEnd: 300, duration: 100, linkStart: true }),
|
'102': makeOntimeEvent({ id: '102', timeStart: 200, timeEnd: 300, duration: 100, linkStart: true }),
|
||||||
'103': makeOntimeEvent({ id: '103', timeStart: 300, timeEnd: 400, duration: 100, linkStart: true }),
|
'103': makeOntimeEvent({ id: '103', timeStart: 300, timeEnd: 400, duration: 100, linkStart: true }),
|
||||||
'2': makeOntimeBlock({ id: '2', entries: ['201', '202', '203'] }),
|
'2': makeOntimeGroup({ id: '2', entries: ['201', '202', '203'] }),
|
||||||
'201': makeOntimeEvent({ id: '201', timeStart: 500, timeEnd: 600, duration: 100, linkStart: false }),
|
'201': makeOntimeEvent({ id: '201', timeStart: 500, timeEnd: 600, duration: 100, linkStart: false }),
|
||||||
'202': makeOntimeEvent({ id: '202', timeStart: 600, timeEnd: 700, duration: 100, linkStart: true }),
|
'202': makeOntimeEvent({ id: '202', timeStart: 600, timeEnd: 700, duration: 100, linkStart: true }),
|
||||||
'203': makeOntimeEvent({ id: '203', timeStart: 700, timeEnd: 800, duration: 100, linkStart: true }),
|
'203': makeOntimeEvent({ id: '203', timeStart: 700, timeEnd: 800, duration: 100, linkStart: true }),
|
||||||
'3': makeOntimeBlock({ id: '3', entries: ['301', '302', '303'] }),
|
'3': makeOntimeGroup({ id: '3', entries: ['301', '302', '303'] }),
|
||||||
'301': makeOntimeEvent({ id: '301', timeStart: 900, timeEnd: 1000, duration: 100, linkStart: false }),
|
'301': makeOntimeEvent({ id: '301', timeStart: 900, timeEnd: 1000, duration: 100, linkStart: false }),
|
||||||
'302': makeOntimeEvent({ id: '302', timeStart: 1000, timeEnd: 1100, duration: 100, linkStart: true }),
|
'302': makeOntimeEvent({ id: '302', timeStart: 1000, timeEnd: 1100, duration: 100, linkStart: true }),
|
||||||
'303': makeOntimeEvent({ id: '303', timeStart: 1100, timeEnd: 1200, duration: 100, linkStart: true }),
|
'303': makeOntimeEvent({ id: '303', timeStart: 1100, timeEnd: 1200, duration: 100, linkStart: true }),
|
||||||
@@ -623,7 +623,7 @@ describe('processRundown()', () => {
|
|||||||
expect(generatedRundown.entries).toMatchObject({
|
expect(generatedRundown.entries).toMatchObject({
|
||||||
'0': { type: SupportedEntry.Event, parent: null },
|
'0': { type: SupportedEntry.Event, parent: null },
|
||||||
'1': {
|
'1': {
|
||||||
type: SupportedEntry.Block,
|
type: SupportedEntry.Group,
|
||||||
entries: ['101', '102', '103'],
|
entries: ['101', '102', '103'],
|
||||||
timeStart: 100,
|
timeStart: 100,
|
||||||
timeEnd: 400,
|
timeEnd: 400,
|
||||||
@@ -634,7 +634,7 @@ describe('processRundown()', () => {
|
|||||||
'102': { parent: '1' },
|
'102': { parent: '1' },
|
||||||
'103': { parent: '1' },
|
'103': { parent: '1' },
|
||||||
'2': {
|
'2': {
|
||||||
type: SupportedEntry.Block,
|
type: SupportedEntry.Group,
|
||||||
entries: ['201', '202', '203'],
|
entries: ['201', '202', '203'],
|
||||||
timeStart: 500,
|
timeStart: 500,
|
||||||
timeEnd: 800,
|
timeEnd: 800,
|
||||||
@@ -645,7 +645,7 @@ describe('processRundown()', () => {
|
|||||||
'202': { id: '202', timeStart: 600, timeEnd: 700, duration: 100 },
|
'202': { id: '202', timeStart: 600, timeEnd: 700, duration: 100 },
|
||||||
'203': { id: '203', timeStart: 700, timeEnd: 800, duration: 100 },
|
'203': { id: '203', timeStart: 700, timeEnd: 800, duration: 100 },
|
||||||
'3': {
|
'3': {
|
||||||
type: SupportedEntry.Block,
|
type: SupportedEntry.Group,
|
||||||
entries: ['301', '302', '303'],
|
entries: ['301', '302', '303'],
|
||||||
timeStart: 900,
|
timeStart: 900,
|
||||||
timeEnd: 1200,
|
timeEnd: 1200,
|
||||||
@@ -688,36 +688,36 @@ describe('rundownMutation.add()', () => {
|
|||||||
expect(rundown.entries['mock']).toMatchObject(mockEvent);
|
expect(rundown.entries['mock']).toMatchObject(mockEvent);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('adds an event at the top of the block if no after is given', () => {
|
test('adds an event at the top of the group if no after is given', () => {
|
||||||
const mockEvent = makeOntimeEvent({ id: 'mock', cue: 'mock' });
|
const mockEvent = makeOntimeEvent({ id: 'mock', cue: 'mock' });
|
||||||
const rundown = makeRundown({
|
const rundown = makeRundown({
|
||||||
flatOrder: ['1', '1a'],
|
flatOrder: ['1', '1a'],
|
||||||
order: ['1'],
|
order: ['1'],
|
||||||
entries: {
|
entries: {
|
||||||
'1': makeOntimeBlock({ id: '1' }),
|
'1': makeOntimeGroup({ id: '1' }),
|
||||||
'1a': makeOntimeEvent({ id: '1a', parent: '1' }),
|
'1a': makeOntimeEvent({ id: '1a', parent: '1' }),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
rundownMutation.add(rundown, mockEvent, null, rundown.entries['1'] as OntimeBlock);
|
rundownMutation.add(rundown, mockEvent, null, rundown.entries['1'] as OntimeGroup);
|
||||||
|
|
||||||
expect(rundown.order).toStrictEqual(['1']);
|
expect(rundown.order).toStrictEqual(['1']);
|
||||||
expect(rundown.flatOrder).toStrictEqual(['1', 'mock', '1a']);
|
expect(rundown.flatOrder).toStrictEqual(['1', 'mock', '1a']);
|
||||||
expect(rundown.entries['mock']).toMatchObject(mockEvent);
|
expect(rundown.entries['mock']).toMatchObject(mockEvent);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('adds an event at the a given location inside a block', () => {
|
test('adds an event at the a given location inside a group', () => {
|
||||||
const mockEvent = makeOntimeEvent({ id: 'mock', cue: 'mock' });
|
const mockEvent = makeOntimeEvent({ id: 'mock', cue: 'mock' });
|
||||||
const rundown = makeRundown({
|
const rundown = makeRundown({
|
||||||
flatOrder: ['1', '1a'],
|
flatOrder: ['1', '1a'],
|
||||||
order: ['1'],
|
order: ['1'],
|
||||||
entries: {
|
entries: {
|
||||||
'1': makeOntimeBlock({ id: '1' }),
|
'1': makeOntimeGroup({ id: '1' }),
|
||||||
'1a': makeOntimeEvent({ id: '1a', parent: '1' }),
|
'1a': makeOntimeEvent({ id: '1a', parent: '1' }),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
rundownMutation.add(rundown, mockEvent, '1a', rundown.entries['1'] as OntimeBlock);
|
rundownMutation.add(rundown, mockEvent, '1a', rundown.entries['1'] as OntimeGroup);
|
||||||
|
|
||||||
expect(rundown.order).toStrictEqual(['1']);
|
expect(rundown.order).toStrictEqual(['1']);
|
||||||
expect(rundown.flatOrder).toStrictEqual(['1', '1a', 'mock']);
|
expect(rundown.flatOrder).toStrictEqual(['1', '1a', 'mock']);
|
||||||
@@ -785,11 +785,11 @@ describe('rundownMutation.remove()', () => {
|
|||||||
expect(rundown.entries['3']).not.toBeUndefined();
|
expect(rundown.entries['3']).not.toBeUndefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('deletes a block and its children', () => {
|
it('deletes a group and its children', () => {
|
||||||
const rundown = makeRundown({
|
const rundown = makeRundown({
|
||||||
order: ['1', '4'],
|
order: ['1', '4'],
|
||||||
entries: {
|
entries: {
|
||||||
'1': makeOntimeBlock({ id: '1', entries: ['2', '3'] }),
|
'1': makeOntimeGroup({ id: '1', entries: ['2', '3'] }),
|
||||||
'2': makeOntimeEvent({ id: '2', parent: '1' }),
|
'2': makeOntimeEvent({ id: '2', parent: '1' }),
|
||||||
'3': makeOntimeDelay({ id: '3', parent: '1' }),
|
'3': makeOntimeDelay({ id: '3', parent: '1' }),
|
||||||
'4': makeOntimeEvent({ id: '4', parent: null }),
|
'4': makeOntimeEvent({ id: '4', parent: null }),
|
||||||
@@ -811,7 +811,7 @@ describe('rundownMutation.remove()', () => {
|
|||||||
const rundown = makeRundown({
|
const rundown = makeRundown({
|
||||||
order: ['1', '4'],
|
order: ['1', '4'],
|
||||||
entries: {
|
entries: {
|
||||||
'1': makeOntimeBlock({ id: '1', entries: ['2', '3'] }),
|
'1': makeOntimeGroup({ id: '1', entries: ['2', '3'] }),
|
||||||
'2': makeOntimeEvent({ id: '2', parent: '1' }),
|
'2': makeOntimeEvent({ id: '2', parent: '1' }),
|
||||||
'3': makeOntimeDelay({ id: '3', parent: '1' }),
|
'3': makeOntimeDelay({ id: '3', parent: '1' }),
|
||||||
'4': makeOntimeEvent({ id: '4', parent: null }),
|
'4': makeOntimeEvent({ id: '4', parent: null }),
|
||||||
@@ -849,11 +849,11 @@ describe('rundownMutation.removeAll()', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('rundownMutation.reorder()', () => {
|
describe('rundownMutation.reorder()', () => {
|
||||||
it('moves an event into a block', () => {
|
it('moves an event into a group', () => {
|
||||||
const rundown = makeRundown({
|
const rundown = makeRundown({
|
||||||
order: ['1', '2', '3'],
|
order: ['1', '2', '3'],
|
||||||
entries: {
|
entries: {
|
||||||
'1': makeOntimeBlock({ id: '1', entries: [] }),
|
'1': makeOntimeGroup({ id: '1', entries: [] }),
|
||||||
'2': makeOntimeEvent({ id: '2', parent: null }),
|
'2': makeOntimeEvent({ id: '2', parent: null }),
|
||||||
'3': makeOntimeEvent({ id: '3', parent: null }),
|
'3': makeOntimeEvent({ id: '3', parent: null }),
|
||||||
},
|
},
|
||||||
@@ -870,12 +870,12 @@ describe('rundownMutation.reorder()', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('adds an event into a block', () => {
|
it('adds an event into a group', () => {
|
||||||
const rundown = makeRundown({
|
const rundown = makeRundown({
|
||||||
order: ['1', '2'],
|
order: ['1', '2'],
|
||||||
flatOrder: ['1', '11', '2'],
|
flatOrder: ['1', '11', '2'],
|
||||||
entries: {
|
entries: {
|
||||||
'1': makeOntimeBlock({ id: '1', entries: ['11'] }),
|
'1': makeOntimeGroup({ id: '1', entries: ['11'] }),
|
||||||
'11': makeOntimeEvent({ id: '11', parent: '1' }),
|
'11': makeOntimeEvent({ id: '11', parent: '1' }),
|
||||||
'2': makeOntimeEvent({ id: '2', parent: null }),
|
'2': makeOntimeEvent({ id: '2', parent: null }),
|
||||||
},
|
},
|
||||||
@@ -926,12 +926,12 @@ describe('rundownMutation.reorder()', () => {
|
|||||||
expect(rundown.order).toStrictEqual(['3', '1', '2']);
|
expect(rundown.order).toStrictEqual(['3', '1', '2']);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('moves an event out and before a block', () => {
|
it('moves an event out and before a group', () => {
|
||||||
const rundown = makeRundown({
|
const rundown = makeRundown({
|
||||||
order: ['1', '2'],
|
order: ['1', '2'],
|
||||||
flatOrder: ['1', '11', '2'],
|
flatOrder: ['1', '11', '2'],
|
||||||
entries: {
|
entries: {
|
||||||
'1': makeOntimeBlock({ id: '1', entries: ['11'] }),
|
'1': makeOntimeGroup({ id: '1', entries: ['11'] }),
|
||||||
'11': makeOntimeEvent({ id: '11', parent: '1' }),
|
'11': makeOntimeEvent({ id: '11', parent: '1' }),
|
||||||
'2': makeOntimeEvent({ id: '2', parent: null }),
|
'2': makeOntimeEvent({ id: '2', parent: null }),
|
||||||
},
|
},
|
||||||
@@ -951,22 +951,22 @@ describe('rundownMutation.reorder()', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('moves an event out and after a block', () => {
|
it('moves an event out and after a group', () => {
|
||||||
const rundown = makeRundown({
|
const rundown = makeRundown({
|
||||||
order: ['1', 'block', '2'],
|
order: ['1', 'group', '2'],
|
||||||
flatOrder: ['1', 'block', '11', '2'],
|
flatOrder: ['1', 'group', '11', '2'],
|
||||||
entries: {
|
entries: {
|
||||||
'1': makeOntimeEvent({ id: '1', parent: null }),
|
'1': makeOntimeEvent({ id: '1', parent: null }),
|
||||||
block: makeOntimeBlock({ id: 'block', entries: ['11'] }),
|
group: makeOntimeGroup({ id: 'group', entries: ['11'] }),
|
||||||
'11': makeOntimeEvent({ id: '11', parent: 'block' }),
|
'11': makeOntimeEvent({ id: '11', parent: 'group' }),
|
||||||
'2': makeOntimeEvent({ id: '2', parent: null }),
|
'2': makeOntimeEvent({ id: '2', parent: null }),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
rundownMutation.reorder(rundown, rundown.entries['11'], rundown.entries['block'], 'after');
|
rundownMutation.reorder(rundown, rundown.entries['11'], rundown.entries['group'], 'after');
|
||||||
|
|
||||||
expect(rundown.order).toStrictEqual(['1', 'block', '11', '2']);
|
expect(rundown.order).toStrictEqual(['1', 'group', '11', '2']);
|
||||||
expect(rundown.entries['block']).toMatchObject({
|
expect(rundown.entries['group']).toMatchObject({
|
||||||
entries: [],
|
entries: [],
|
||||||
});
|
});
|
||||||
expect(rundown.entries['11']).toMatchObject({
|
expect(rundown.entries['11']).toMatchObject({
|
||||||
@@ -977,14 +977,14 @@ describe('rundownMutation.reorder()', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('moves an event between blocks', () => {
|
it('moves an event between groups', () => {
|
||||||
const rundown = makeRundown({
|
const rundown = makeRundown({
|
||||||
order: ['1', '2'],
|
order: ['1', '2'],
|
||||||
flatOrder: ['1', '11', '2', '22'],
|
flatOrder: ['1', '11', '2', '22'],
|
||||||
entries: {
|
entries: {
|
||||||
'1': makeOntimeBlock({ id: '1', entries: ['11'] }),
|
'1': makeOntimeGroup({ id: '1', entries: ['11'] }),
|
||||||
'11': makeOntimeEvent({ id: '11', parent: '1' }),
|
'11': makeOntimeEvent({ id: '11', parent: '1' }),
|
||||||
'2': makeOntimeBlock({ id: '2', entries: ['22'] }),
|
'2': makeOntimeGroup({ id: '2', entries: ['22'] }),
|
||||||
'22': makeOntimeEvent({ id: '22', parent: '2' }),
|
'22': makeOntimeEvent({ id: '22', parent: '2' }),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -1003,13 +1003,13 @@ describe('rundownMutation.reorder()', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('moves an event into an empty block', () => {
|
it('moves an event into an empty group', () => {
|
||||||
const rundown = makeRundown({
|
const rundown = makeRundown({
|
||||||
order: ['1', '2'],
|
order: ['1', '2'],
|
||||||
flatOrder: ['1', '2', '22'],
|
flatOrder: ['1', '2', '22'],
|
||||||
entries: {
|
entries: {
|
||||||
'1': makeOntimeBlock({ id: '1', entries: [] }),
|
'1': makeOntimeGroup({ id: '1', entries: [] }),
|
||||||
'2': makeOntimeBlock({ id: '2', entries: ['22'] }),
|
'2': makeOntimeGroup({ id: '2', entries: ['22'] }),
|
||||||
'22': makeOntimeEvent({ id: '22', parent: '2' }),
|
'22': makeOntimeEvent({ id: '22', parent: '2' }),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -1028,14 +1028,14 @@ describe('rundownMutation.reorder()', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('moves an event out of a block (up)', () => {
|
it('moves an event out of a group (up)', () => {
|
||||||
const rundown = makeRundown({
|
const rundown = makeRundown({
|
||||||
order: ['1', '2'],
|
order: ['1', '2'],
|
||||||
flatOrder: ['1', '11', '2', '22'],
|
flatOrder: ['1', '11', '2', '22'],
|
||||||
entries: {
|
entries: {
|
||||||
'1': makeOntimeBlock({ id: '1', entries: ['11'] }),
|
'1': makeOntimeGroup({ id: '1', entries: ['11'] }),
|
||||||
'11': makeOntimeEvent({ id: '11', parent: '1' }),
|
'11': makeOntimeEvent({ id: '11', parent: '1' }),
|
||||||
'2': makeOntimeBlock({ id: '2', entries: ['22'] }),
|
'2': makeOntimeGroup({ id: '2', entries: ['22'] }),
|
||||||
'22': makeOntimeEvent({ id: '22', parent: '2' }),
|
'22': makeOntimeEvent({ id: '22', parent: '2' }),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -1059,14 +1059,14 @@ describe('rundownMutation.reorder()', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('moves an event out of a block (down)', () => {
|
it('moves an event out of a group (down)', () => {
|
||||||
const rundown = makeRundown({
|
const rundown = makeRundown({
|
||||||
order: ['1', '2'],
|
order: ['1', '2'],
|
||||||
flatOrder: ['1', '11', '2', '22'],
|
flatOrder: ['1', '11', '2', '22'],
|
||||||
entries: {
|
entries: {
|
||||||
'1': makeOntimeBlock({ id: '1', entries: ['11'] }),
|
'1': makeOntimeGroup({ id: '1', entries: ['11'] }),
|
||||||
'11': makeOntimeEvent({ id: '11', parent: '1' }),
|
'11': makeOntimeEvent({ id: '11', parent: '1' }),
|
||||||
'2': makeOntimeBlock({ id: '2', entries: ['22'] }),
|
'2': makeOntimeGroup({ id: '2', entries: ['22'] }),
|
||||||
'22': makeOntimeEvent({ id: '22', parent: '2' }),
|
'22': makeOntimeEvent({ id: '22', parent: '2' }),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -1088,14 +1088,14 @@ describe('rundownMutation.reorder()', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('moves a block (up)', () => {
|
it('moves a group (up)', () => {
|
||||||
const rundown = makeRundown({
|
const rundown = makeRundown({
|
||||||
order: ['1', '2'],
|
order: ['1', '2'],
|
||||||
flatOrder: ['1', '11', '2', '22'],
|
flatOrder: ['1', '11', '2', '22'],
|
||||||
entries: {
|
entries: {
|
||||||
'1': makeOntimeBlock({ id: '1', entries: ['11'] }),
|
'1': makeOntimeGroup({ id: '1', entries: ['11'] }),
|
||||||
'11': makeOntimeEvent({ id: '11', parent: '1' }),
|
'11': makeOntimeEvent({ id: '11', parent: '1' }),
|
||||||
'2': makeOntimeBlock({ id: '2', entries: ['22'] }),
|
'2': makeOntimeGroup({ id: '2', entries: ['22'] }),
|
||||||
'22': makeOntimeEvent({ id: '22', parent: '2' }),
|
'22': makeOntimeEvent({ id: '22', parent: '2' }),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -1105,14 +1105,14 @@ describe('rundownMutation.reorder()', () => {
|
|||||||
expect(rundown.order).toStrictEqual(['2', '1']);
|
expect(rundown.order).toStrictEqual(['2', '1']);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('moves a block (down)', () => {
|
it('moves a group (down)', () => {
|
||||||
const rundown = makeRundown({
|
const rundown = makeRundown({
|
||||||
order: ['1', '2'],
|
order: ['1', '2'],
|
||||||
flatOrder: ['1', '11', '2', '22'],
|
flatOrder: ['1', '11', '2', '22'],
|
||||||
entries: {
|
entries: {
|
||||||
'1': makeOntimeBlock({ id: '1', entries: ['11'] }),
|
'1': makeOntimeGroup({ id: '1', entries: ['11'] }),
|
||||||
'11': makeOntimeEvent({ id: '11', parent: '1' }),
|
'11': makeOntimeEvent({ id: '11', parent: '1' }),
|
||||||
'2': makeOntimeBlock({ id: '2', entries: ['22'] }),
|
'2': makeOntimeGroup({ id: '2', entries: ['22'] }),
|
||||||
'22': makeOntimeEvent({ id: '22', parent: '2' }),
|
'22': makeOntimeEvent({ id: '22', parent: '2' }),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -1132,7 +1132,7 @@ describe('rundownMutation.applyDelay()', () => {
|
|||||||
delay: makeOntimeDelay({ id: 'delay', duration: 10 }),
|
delay: makeOntimeDelay({ id: 'delay', duration: 10 }),
|
||||||
'1': makeOntimeEvent({ id: '1', timeStart: 0, timeEnd: 10, duration: 10 }),
|
'1': makeOntimeEvent({ id: '1', timeStart: 0, timeEnd: 10, duration: 10 }),
|
||||||
'2': makeOntimeEvent({ id: '2', timeStart: 10, timeEnd: 20, duration: 10, linkStart: true }),
|
'2': makeOntimeEvent({ id: '2', timeStart: 10, timeEnd: 20, duration: 10, linkStart: true }),
|
||||||
'3': makeOntimeBlock({ id: '3' }),
|
'3': makeOntimeGroup({ id: '3' }),
|
||||||
'4': makeOntimeEvent({ id: '4', timeStart: 20, timeEnd: 30, duration: 10, linkStart: false }),
|
'4': makeOntimeEvent({ id: '4', timeStart: 20, timeEnd: 30, duration: 10, linkStart: false }),
|
||||||
'5': makeOntimeEvent({ id: '5', timeStart: 30, timeEnd: 40, duration: 10, linkStart: true }),
|
'5': makeOntimeEvent({ id: '5', timeStart: 30, timeEnd: 40, duration: 10, linkStart: true }),
|
||||||
},
|
},
|
||||||
@@ -1158,7 +1158,7 @@ describe('rundownMutation.applyDelay()', () => {
|
|||||||
delay: makeOntimeDelay({ id: 'delay', duration: -10 }),
|
delay: makeOntimeDelay({ id: 'delay', duration: -10 }),
|
||||||
'1': makeOntimeEvent({ id: '1', timeStart: 0, timeEnd: 10, duration: 10 }),
|
'1': makeOntimeEvent({ id: '1', timeStart: 0, timeEnd: 10, duration: 10 }),
|
||||||
'2': makeOntimeEvent({ id: '2', timeStart: 10, timeEnd: 20, duration: 10, linkStart: true }),
|
'2': makeOntimeEvent({ id: '2', timeStart: 10, timeEnd: 20, duration: 10, linkStart: true }),
|
||||||
'3': makeOntimeBlock({ id: '3' }),
|
'3': makeOntimeGroup({ id: '3' }),
|
||||||
'4': makeOntimeEvent({ id: '4', timeStart: 20, timeEnd: 30, duration: 10, linkStart: false }),
|
'4': makeOntimeEvent({ id: '4', timeStart: 20, timeEnd: 30, duration: 10, linkStart: false }),
|
||||||
'5': makeOntimeEvent({ id: '5', timeStart: 30, timeEnd: 40, duration: 10, linkStart: true }),
|
'5': makeOntimeEvent({ id: '5', timeStart: 30, timeEnd: 40, duration: 10, linkStart: true }),
|
||||||
},
|
},
|
||||||
@@ -1398,13 +1398,13 @@ describe('rundownMutation.applyDelay()', () => {
|
|||||||
expect(testRundown.entries).toMatchObject({ '1': { id: '1', timeStart: 0, timeEnd: 100, duration: 100 } });
|
expect(testRundown.entries).toMatchObject({ '1': { id: '1', timeStart: 0, timeEnd: 100, duration: 100 } });
|
||||||
});
|
});
|
||||||
|
|
||||||
it('unlinks events to across blocks is it is the first event after the delay', () => {
|
it('unlinks events to across groups is it is the first event after the delay', () => {
|
||||||
const testRundown = makeRundown({
|
const testRundown = makeRundown({
|
||||||
order: ['1', 'delay', 'block', '2'],
|
order: ['1', 'delay', 'group', '2'],
|
||||||
entries: {
|
entries: {
|
||||||
'1': makeOntimeEvent({ id: '1', timeStart: 0, timeEnd: 100, duration: 100, revision: 1 }),
|
'1': makeOntimeEvent({ id: '1', timeStart: 0, timeEnd: 100, duration: 100, revision: 1 }),
|
||||||
delay: makeOntimeDelay({ id: 'delay', duration: 50 }),
|
delay: makeOntimeDelay({ id: 'delay', duration: 50 }),
|
||||||
block: makeOntimeBlock({ id: 'block' }),
|
group: makeOntimeGroup({ id: 'group' }),
|
||||||
'2': makeOntimeEvent({ id: '2', timeStart: 100, timeEnd: 150, duration: 50, revision: 1, linkStart: true }),
|
'2': makeOntimeEvent({ id: '2', timeStart: 100, timeEnd: 150, duration: 50, revision: 1, linkStart: true }),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -1420,7 +1420,7 @@ describe('rundownMutation.applyDelay()', () => {
|
|||||||
duration: 100,
|
duration: 100,
|
||||||
revision: 1,
|
revision: 1,
|
||||||
},
|
},
|
||||||
block: { id: 'block' },
|
group: { id: 'group' },
|
||||||
'2': {
|
'2': {
|
||||||
id: '2',
|
id: '2',
|
||||||
timeStart: 150,
|
timeStart: 150,
|
||||||
@@ -1432,13 +1432,13 @@ describe('rundownMutation.applyDelay()', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('applies a delay from inside a block', () => {
|
it('applies a delay from inside a group', () => {
|
||||||
const testRundown = makeRundown({
|
const testRundown = makeRundown({
|
||||||
order: ['1', 'block', '2', '3'],
|
order: ['1', 'group', '2', '3'],
|
||||||
entries: {
|
entries: {
|
||||||
'1': makeOntimeEvent({ id: '1', timeStart: 0, timeEnd: 100, duration: 100 }),
|
'1': makeOntimeEvent({ id: '1', timeStart: 0, timeEnd: 100, duration: 100 }),
|
||||||
block: makeOntimeBlock({ id: 'block', entries: ['delay'] }),
|
group: makeOntimeGroup({ id: 'group', entries: ['delay'] }),
|
||||||
delay: makeOntimeDelay({ id: 'delay', duration: 100, parent: 'block' }),
|
delay: makeOntimeDelay({ id: 'delay', duration: 100, parent: 'group' }),
|
||||||
'2': makeOntimeEvent({ id: '2', timeStart: 100, timeEnd: 200, duration: 100, linkStart: true }),
|
'2': makeOntimeEvent({ id: '2', timeStart: 100, timeEnd: 200, duration: 100, linkStart: true }),
|
||||||
'3': makeOntimeEvent({ id: '3', timeStart: 200, timeEnd: 300, duration: 100, linkStart: true }),
|
'3': makeOntimeEvent({ id: '3', timeStart: 200, timeEnd: 300, duration: 100, linkStart: true }),
|
||||||
},
|
},
|
||||||
@@ -1476,18 +1476,18 @@ describe('rundownMutation.applyDelay()', () => {
|
|||||||
|
|
||||||
it('applies a delay from across nested orders', () => {
|
it('applies a delay from across nested orders', () => {
|
||||||
const testRundown = makeRundown({
|
const testRundown = makeRundown({
|
||||||
order: ['1', 'delay', 'block', '2', '3'],
|
order: ['1', 'delay', 'group', '2', '3'],
|
||||||
entries: {
|
entries: {
|
||||||
'1': makeOntimeEvent({ id: '1', timeStart: 0, timeEnd: 100, duration: 100 }),
|
'1': makeOntimeEvent({ id: '1', timeStart: 0, timeEnd: 100, duration: 100 }),
|
||||||
delay: makeOntimeDelay({ id: 'delay', duration: 100 }),
|
delay: makeOntimeDelay({ id: 'delay', duration: 100 }),
|
||||||
block: makeOntimeBlock({ id: 'block', entries: ['block-1'] }),
|
group: makeOntimeGroup({ id: 'group', entries: ['group-1'] }),
|
||||||
'block-1': makeOntimeEvent({
|
'group-1': makeOntimeEvent({
|
||||||
id: 'block-1',
|
id: 'group-1',
|
||||||
timeStart: 100,
|
timeStart: 100,
|
||||||
timeEnd: 200,
|
timeEnd: 200,
|
||||||
duration: 100,
|
duration: 100,
|
||||||
linkStart: true,
|
linkStart: true,
|
||||||
parent: 'block',
|
parent: 'group',
|
||||||
}),
|
}),
|
||||||
'2': makeOntimeEvent({ id: '2', timeStart: 200, timeEnd: 300, duration: 100, linkStart: true }),
|
'2': makeOntimeEvent({ id: '2', timeStart: 200, timeEnd: 300, duration: 100, linkStart: true }),
|
||||||
'3': makeOntimeEvent({ id: '3', timeStart: 300, timeEnd: 400, duration: 100, linkStart: true }),
|
'3': makeOntimeEvent({ id: '3', timeStart: 300, timeEnd: 400, duration: 100, linkStart: true }),
|
||||||
@@ -1504,7 +1504,7 @@ describe('rundownMutation.applyDelay()', () => {
|
|||||||
duration: 100,
|
duration: 100,
|
||||||
revision: 1,
|
revision: 1,
|
||||||
},
|
},
|
||||||
'block-1': {
|
'group-1': {
|
||||||
timeStart: 200,
|
timeStart: 200,
|
||||||
timeEnd: 300,
|
timeEnd: 300,
|
||||||
duration: 100,
|
duration: 100,
|
||||||
@@ -1579,11 +1579,11 @@ describe('rundownMutation.clone()', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('clones an event inside a block and adds it to the rundown', () => {
|
it('clones an event inside a group and adds it to the rundown', () => {
|
||||||
const testRundown = makeRundown({
|
const testRundown = makeRundown({
|
||||||
order: ['1'],
|
order: ['1'],
|
||||||
entries: {
|
entries: {
|
||||||
'1': makeOntimeBlock({ id: '1', entries: ['1a'] }),
|
'1': makeOntimeGroup({ id: '1', entries: ['1a'] }),
|
||||||
'1a': makeOntimeEvent({ id: '1a', cue: 'nested', parent: '1' }),
|
'1a': makeOntimeEvent({ id: '1a', cue: 'nested', parent: '1' }),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -1599,11 +1599,11 @@ describe('rundownMutation.clone()', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('clones a block and its nested elements', () => {
|
it('clones a group and its nested elements', () => {
|
||||||
const testRundown = makeRundown({
|
const testRundown = makeRundown({
|
||||||
order: ['1'],
|
order: ['1'],
|
||||||
entries: {
|
entries: {
|
||||||
'1': makeOntimeBlock({ id: '1', title: 'top', entries: ['1a'] }),
|
'1': makeOntimeGroup({ id: '1', title: 'top', entries: ['1a'] }),
|
||||||
'1a': makeOntimeEvent({ id: '1a', cue: 'nested', parent: '1' }),
|
'1a': makeOntimeEvent({ id: '1a', cue: 'nested', parent: '1' }),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -1612,10 +1612,10 @@ describe('rundownMutation.clone()', () => {
|
|||||||
|
|
||||||
expect(testRundown.order).toStrictEqual(['1', newEntry.id]);
|
expect(testRundown.order).toStrictEqual(['1', newEntry.id]);
|
||||||
expect(testRundown.entries[newEntry.id]).toMatchObject({
|
expect(testRundown.entries[newEntry.id]).toMatchObject({
|
||||||
type: SupportedEntry.Block,
|
type: SupportedEntry.Group,
|
||||||
entries: [expect.any(String)],
|
entries: [expect.any(String)],
|
||||||
});
|
});
|
||||||
expect((testRundown.entries[newEntry.id] as OntimeBlock).entries[0]).not.toBe('1a');
|
expect((testRundown.entries[newEntry.id] as OntimeGroup).entries[0]).not.toBe('1a');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1632,34 +1632,34 @@ describe('rundownMutation.group()', () => {
|
|||||||
|
|
||||||
rundownMutation.group(rundown, ['1', '2']);
|
rundownMutation.group(rundown, ['1', '2']);
|
||||||
|
|
||||||
const blockId = rundown.order[0];
|
const groupId = rundown.order[0];
|
||||||
expect(blockId).toStrictEqual(expect.any(String));
|
expect(groupId).toStrictEqual(expect.any(String));
|
||||||
expect(rundown.order).toStrictEqual([expect.any(String), '3']);
|
expect(rundown.order).toStrictEqual([expect.any(String), '3']);
|
||||||
expect(rundown.entries).toMatchObject({
|
expect(rundown.entries).toMatchObject({
|
||||||
[blockId]: {
|
[groupId]: {
|
||||||
type: SupportedEntry.Block,
|
type: SupportedEntry.Group,
|
||||||
entries: ['1', '2'],
|
entries: ['1', '2'],
|
||||||
},
|
},
|
||||||
'1': { id: '1', type: SupportedEntry.Event, parent: blockId },
|
'1': { id: '1', type: SupportedEntry.Event, parent: groupId },
|
||||||
'2': { id: '2', type: SupportedEntry.Event, parent: blockId },
|
'2': { id: '2', type: SupportedEntry.Event, parent: groupId },
|
||||||
'3': { id: '3', type: SupportedEntry.Event, parent: null },
|
'3': { id: '3', type: SupportedEntry.Event, parent: null },
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('rundownMutation.ungroup()', () => {
|
describe('rundownMutation.ungroup()', () => {
|
||||||
it('should correctly dissolve a block into its events', () => {
|
it('should correctly dissolve a group into its events', () => {
|
||||||
const testRundown = makeRundown({
|
const testRundown = makeRundown({
|
||||||
order: ['1', '2'],
|
order: ['1', '2'],
|
||||||
entries: {
|
entries: {
|
||||||
'1': makeOntimeEvent({ id: '1', cue: 'data1', parent: null }),
|
'1': makeOntimeEvent({ id: '1', cue: 'data1', parent: null }),
|
||||||
'2': makeOntimeBlock({ id: '2', entries: ['21', '22'] }),
|
'2': makeOntimeGroup({ id: '2', entries: ['21', '22'] }),
|
||||||
'21': makeOntimeEvent({ id: '21', cue: 'data21', parent: '2' }),
|
'21': makeOntimeEvent({ id: '21', cue: 'data21', parent: '2' }),
|
||||||
'22': makeOntimeEvent({ id: '22', cue: 'data22', parent: '2' }),
|
'22': makeOntimeEvent({ id: '22', cue: 'data22', parent: '2' }),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
rundownMutation.ungroup(testRundown, testRundown.entries['2'] as OntimeBlock);
|
rundownMutation.ungroup(testRundown, testRundown.entries['2'] as OntimeGroup);
|
||||||
|
|
||||||
expect(testRundown.order).toStrictEqual(['1', '21', '22']);
|
expect(testRundown.order).toStrictEqual(['1', '21', '22']);
|
||||||
expect(testRundown.entries['2']).toBeUndefined();
|
expect(testRundown.entries['2']).toBeUndefined();
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { SupportedEntry, OntimeEvent, OntimeBlock, Rundown, CustomFields } from 'ontime-types';
|
import { SupportedEntry, OntimeEvent, OntimeGroup, Rundown, CustomFields } from 'ontime-types';
|
||||||
|
|
||||||
import { defaultRundown } from '../../../models/dataModel.js';
|
import { defaultRundown } from '../../../models/dataModel.js';
|
||||||
import { makeOntimeBlock, makeOntimeEvent, makeOntimeMilestone } from '../__mocks__/rundown.mocks.js';
|
import { makeOntimeGroup, makeOntimeEvent, makeOntimeMilestone } from '../__mocks__/rundown.mocks.js';
|
||||||
|
|
||||||
import { parseRundowns, parseRundown, handleCustomField, addToCustomAssignment } from '../rundown.parser.js';
|
import { parseRundowns, parseRundown, handleCustomField, addToCustomAssignment } from '../rundown.parser.js';
|
||||||
|
|
||||||
@@ -47,7 +47,7 @@ describe('parseRundown()', () => {
|
|||||||
flatOrder: ['1', '2', '3', '4'],
|
flatOrder: ['1', '2', '3', '4'],
|
||||||
entries: {
|
entries: {
|
||||||
'1': { id: '1', type: SupportedEntry.Event, title: 'test', skip: false } as OntimeEvent, // OK
|
'1': { id: '1', type: SupportedEntry.Event, title: 'test', skip: false } as OntimeEvent, // OK
|
||||||
'2': { id: '1', type: SupportedEntry.Block, title: 'test 2' } as OntimeBlock, // duplicate ID
|
'2': { id: '1', type: SupportedEntry.Group, title: 'test 2' } as OntimeGroup, // duplicate ID
|
||||||
'3': {} as OntimeEvent, // no data
|
'3': {} as OntimeEvent, // no data
|
||||||
'4': { id: '4', title: 'test 2', skip: false } as OntimeEvent, // no type
|
'4': { id: '4', title: 'test 2', skip: false } as OntimeEvent, // no type
|
||||||
},
|
},
|
||||||
@@ -221,7 +221,7 @@ describe('parseRundown()', () => {
|
|||||||
flatOrder: ['1', '2', '21'],
|
flatOrder: ['1', '2', '21'],
|
||||||
entries: {
|
entries: {
|
||||||
'1': makeOntimeEvent({ id: '1', custom: { lighting: 'yes' } }),
|
'1': makeOntimeEvent({ id: '1', custom: { lighting: 'yes' } }),
|
||||||
'2': makeOntimeBlock({ id: '2', entries: ['21'], custom: { lighting: '' } }),
|
'2': makeOntimeGroup({ id: '2', entries: ['21'], custom: { lighting: '' } }),
|
||||||
'21': makeOntimeEvent({ id: '21', custom: { lighting: '' } }),
|
'21': makeOntimeEvent({ id: '21', custom: { lighting: '' } }),
|
||||||
},
|
},
|
||||||
revision: 1,
|
revision: 1,
|
||||||
@@ -237,7 +237,7 @@ describe('parseRundown()', () => {
|
|||||||
|
|
||||||
const parsedRundown = parseRundown(rundown, customFields);
|
const parsedRundown = parseRundown(rundown, customFields);
|
||||||
expect((parsedRundown.entries['1'] as OntimeEvent).custom).toStrictEqual({ lighting: 'yes' });
|
expect((parsedRundown.entries['1'] as OntimeEvent).custom).toStrictEqual({ lighting: 'yes' });
|
||||||
expect((parsedRundown.entries['2'] as OntimeBlock).custom).not.toHaveProperty('lighting');
|
expect((parsedRundown.entries['2'] as OntimeGroup).custom).not.toHaveProperty('lighting');
|
||||||
expect((parsedRundown.entries['21'] as OntimeEvent).custom).not.toHaveProperty('lighting');
|
expect((parsedRundown.entries['21'] as OntimeEvent).custom).not.toHaveProperty('lighting');
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -245,41 +245,41 @@ describe('parseRundown()', () => {
|
|||||||
const rundown = {
|
const rundown = {
|
||||||
id: 'test',
|
id: 'test',
|
||||||
title: '',
|
title: '',
|
||||||
order: ['block'],
|
order: ['group'],
|
||||||
flatOrder: ['block'],
|
flatOrder: ['group'],
|
||||||
isNextDay: false,
|
isNextDay: false,
|
||||||
entries: {
|
entries: {
|
||||||
block: makeOntimeBlock({
|
group: makeOntimeGroup({
|
||||||
id: 'block',
|
id: 'group',
|
||||||
title: 'block-title',
|
title: 'group-title',
|
||||||
note: 'block-note',
|
note: 'group-note',
|
||||||
colour: 'red',
|
colour: 'red',
|
||||||
entries: ['1', '2', '3'],
|
entries: ['1', '2', '3'],
|
||||||
}),
|
}),
|
||||||
'1': makeOntimeEvent({ id: '1', parent: 'block' }),
|
'1': makeOntimeEvent({ id: '1', parent: 'group' }),
|
||||||
'2': makeOntimeMilestone({ id: '2', parent: 'block' }),
|
'2': makeOntimeMilestone({ id: '2', parent: 'group' }),
|
||||||
},
|
},
|
||||||
revision: 1,
|
revision: 1,
|
||||||
} as Rundown;
|
} as Rundown;
|
||||||
|
|
||||||
const parsedRundown = parseRundown(rundown, {});
|
const parsedRundown = parseRundown(rundown, {});
|
||||||
expect(parsedRundown.order).toStrictEqual(['block']);
|
expect(parsedRundown.order).toStrictEqual(['group']);
|
||||||
expect(parsedRundown.flatOrder).toStrictEqual(['block', '1', '2']);
|
expect(parsedRundown.flatOrder).toStrictEqual(['group', '1', '2']);
|
||||||
expect(parsedRundown.entries).toMatchObject({
|
expect(parsedRundown.entries).toMatchObject({
|
||||||
block: { id: 'block', type: SupportedEntry.Block, entries: ['1', '2'] },
|
group: { id: 'group', type: SupportedEntry.Group, entries: ['1', '2'] },
|
||||||
'1': { id: '1', type: SupportedEntry.Event },
|
'1': { id: '1', type: SupportedEntry.Event },
|
||||||
'2': { id: '2', type: SupportedEntry.Milestone },
|
'2': { id: '2', type: SupportedEntry.Milestone },
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('parses events nested in blocks', () => {
|
it('parses events nested in groups', () => {
|
||||||
const rundown = {
|
const rundown = {
|
||||||
id: 'test',
|
id: 'test',
|
||||||
title: '',
|
title: '',
|
||||||
order: ['block'],
|
order: ['group'],
|
||||||
flatOrder: ['block'],
|
flatOrder: ['group'],
|
||||||
entries: {
|
entries: {
|
||||||
block: makeOntimeBlock({ id: 'block', entries: ['1', '2'] }),
|
group: makeOntimeGroup({ id: 'group', entries: ['1', '2'] }),
|
||||||
'1': makeOntimeEvent({ id: '1' }),
|
'1': makeOntimeEvent({ id: '1' }),
|
||||||
'2': makeOntimeEvent({ id: '2' }),
|
'2': makeOntimeEvent({ id: '2' }),
|
||||||
},
|
},
|
||||||
@@ -288,7 +288,7 @@ describe('parseRundown()', () => {
|
|||||||
|
|
||||||
const parsedRundown = parseRundown(rundown, {});
|
const parsedRundown = parseRundown(rundown, {});
|
||||||
expect(parsedRundown.order.length).toEqual(1);
|
expect(parsedRundown.order.length).toEqual(1);
|
||||||
expect(parsedRundown.entries.block).toMatchObject({ entries: ['1', '2'] });
|
expect(parsedRundown.entries.group).toMatchObject({ entries: ['1', '2'] });
|
||||||
expect(Object.keys(parsedRundown.entries).length).toEqual(3);
|
expect(Object.keys(parsedRundown.entries).length).toEqual(3);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { TimeStrategy, EndAction, TimerType, OntimeEvent, OntimeBlock } from 'ontime-types';
|
import { TimeStrategy, EndAction, TimerType, OntimeEvent, OntimeGroup } from 'ontime-types';
|
||||||
import { MILLIS_PER_HOUR } from 'ontime-utils';
|
import { MILLIS_PER_HOUR } from 'ontime-utils';
|
||||||
|
|
||||||
import { assertType } from 'vitest';
|
import { assertType } from 'vitest';
|
||||||
@@ -11,7 +11,7 @@ import {
|
|||||||
getInsertAfterId,
|
getInsertAfterId,
|
||||||
hasChanges,
|
hasChanges,
|
||||||
} from '../rundown.utils.js';
|
} from '../rundown.utils.js';
|
||||||
import { makeOntimeBlock, makeOntimeEvent, makeRundown } from '../__mocks__/rundown.mocks.js';
|
import { makeOntimeGroup, makeOntimeEvent, makeRundown } from '../__mocks__/rundown.mocks.js';
|
||||||
|
|
||||||
describe('test event validator', () => {
|
describe('test event validator', () => {
|
||||||
it('validates a good object', () => {
|
it('validates a good object', () => {
|
||||||
@@ -227,13 +227,13 @@ describe('getInsertAfterId()', () => {
|
|||||||
entries: {
|
entries: {
|
||||||
'1': makeOntimeEvent({ id: '1', parent: null }),
|
'1': makeOntimeEvent({ id: '1', parent: null }),
|
||||||
'2': makeOntimeEvent({ id: '2', parent: null }),
|
'2': makeOntimeEvent({ id: '2', parent: null }),
|
||||||
block: makeOntimeBlock({ id: 'block', entries: ['31', '32'] }),
|
group: makeOntimeGroup({ id: 'group', entries: ['31', '32'] }),
|
||||||
'31': makeOntimeEvent({ id: '31', parent: 'block' }),
|
'31': makeOntimeEvent({ id: '31', parent: 'group' }),
|
||||||
'32': makeOntimeEvent({ id: '32', parent: 'block' }),
|
'32': makeOntimeEvent({ id: '32', parent: 'group' }),
|
||||||
'4': makeOntimeEvent({ id: '31', parent: null }),
|
'4': makeOntimeEvent({ id: '4', parent: null }),
|
||||||
},
|
},
|
||||||
order: ['1', '2', 'block', '4'],
|
order: ['1', '2', 'group', '4'],
|
||||||
flatOrder: ['1', '2', 'block', '31', '32', '4'],
|
flatOrder: ['1', '2', 'group', '31', '32', '4'],
|
||||||
});
|
});
|
||||||
|
|
||||||
it('returns afterId if provided', () => {
|
it('returns afterId if provided', () => {
|
||||||
@@ -251,12 +251,12 @@ describe('getInsertAfterId()', () => {
|
|||||||
|
|
||||||
it('returns the previous id of an entry in the rundown', () => {
|
it('returns the previous id of an entry in the rundown', () => {
|
||||||
expect(getInsertAfterId(rundown, null, undefined, '2')).toBe('1');
|
expect(getInsertAfterId(rundown, null, undefined, '2')).toBe('1');
|
||||||
expect(getInsertAfterId(rundown, null, undefined, '4')).toBe('block');
|
expect(getInsertAfterId(rundown, null, undefined, '4')).toBe('group');
|
||||||
expect(getInsertAfterId(rundown, null, undefined, 'block')).toBe('2');
|
expect(getInsertAfterId(rundown, null, undefined, 'group')).toBe('2');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('returns the previous id of an event in a block', () => {
|
it('returns the previous id of an event in a group', () => {
|
||||||
expect(getInsertAfterId(rundown, rundown.entries.block as OntimeBlock, undefined, '31')).toBeNull();
|
expect(getInsertAfterId(rundown, rundown.entries.group as OntimeGroup, undefined, '31')).toBeNull();
|
||||||
expect(getInsertAfterId(rundown, rundown.entries.block as OntimeBlock, undefined, '32')).toBe('31');
|
expect(getInsertAfterId(rundown, rundown.entries.group as OntimeGroup, undefined, '32')).toBe('31');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -15,10 +15,10 @@ import {
|
|||||||
CustomFieldKey,
|
CustomFieldKey,
|
||||||
CustomFields,
|
CustomFields,
|
||||||
EntryId,
|
EntryId,
|
||||||
isOntimeBlock,
|
isOntimeGroup,
|
||||||
isOntimeEvent,
|
isOntimeEvent,
|
||||||
isPlayableEvent,
|
isPlayableEvent,
|
||||||
OntimeBlock,
|
OntimeGroup,
|
||||||
OntimeDelay,
|
OntimeDelay,
|
||||||
OntimeEntry,
|
OntimeEntry,
|
||||||
OntimeEvent,
|
OntimeEvent,
|
||||||
@@ -32,9 +32,9 @@ import { getDataProvider } from '../../classes/data-provider/DataProvider.js';
|
|||||||
import type { AssignedMap, CustomFieldsMetadata, RundownMetadata } from './rundown.types.js';
|
import type { AssignedMap, CustomFieldsMetadata, RundownMetadata } from './rundown.types.js';
|
||||||
import {
|
import {
|
||||||
applyPatchToEntry,
|
applyPatchToEntry,
|
||||||
cloneBlock,
|
cloneGroup,
|
||||||
cloneEntry,
|
cloneEntry,
|
||||||
createBlock,
|
createGroup,
|
||||||
deleteById,
|
deleteById,
|
||||||
doesInvalidateMetadata,
|
doesInvalidateMetadata,
|
||||||
getUniqueId,
|
getUniqueId,
|
||||||
@@ -176,14 +176,14 @@ export function createTransaction(options: TransactionOptions): Transaction {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Add entry to rundown, handles the following cases:
|
* Add entry to rundown, handles the following cases:
|
||||||
* - 1a. add entry in block, after a given entry
|
* - 1a. add entry in group, after a given entry
|
||||||
* - 1b. add entry in block, at the beginning
|
* - 1b. add entry in group, at the beginning
|
||||||
* - 2a. add entry to the rundown, after a given entry
|
* - 2a. add entry to the rundown, after a given entry
|
||||||
* - 2b. add entry to the rundown, at the beginning
|
* - 2b. add entry to the rundown, at the beginning
|
||||||
*/
|
*/
|
||||||
function add(rundown: Rundown, entry: OntimeEntry, afterId: EntryId | null, parent: OntimeBlock | null): OntimeEntry {
|
function add(rundown: Rundown, entry: OntimeEntry, afterId: EntryId | null, parent: OntimeGroup | null): OntimeEntry {
|
||||||
if (parent) {
|
if (parent) {
|
||||||
// 1. inserting an entry inside a block
|
// 1. inserting an entry inside a group
|
||||||
if (afterId) {
|
if (afterId) {
|
||||||
const atEventsIndex = parent.entries.indexOf(afterId) + 1;
|
const atEventsIndex = parent.entries.indexOf(afterId) + 1;
|
||||||
const atFlatIndex = rundown.flatOrder.indexOf(afterId) + 1;
|
const atFlatIndex = rundown.flatOrder.indexOf(afterId) + 1;
|
||||||
@@ -232,31 +232,31 @@ function edit(rundown: Rundown, patch: PatchWithId): { entry: OntimeEntry; didIn
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Deletes an entry from the rundown
|
* Deletes an entry from the rundown
|
||||||
* - if the entry is an ontime block, we delete it along with its children
|
* - if the entry is an ontime group, we delete it along with its children
|
||||||
* - if the entry is inside a block, we delete it and remove the reference from the parent block
|
* - if the entry is inside a group, we delete it and remove the reference from the parent group
|
||||||
*/
|
*/
|
||||||
function remove(rundown: Rundown, entry: OntimeEntry) {
|
function remove(rundown: Rundown, entry: OntimeEntry) {
|
||||||
if (isOntimeBlock(entry)) {
|
if (isOntimeGroup(entry)) {
|
||||||
// for ontime blocks, we need to iterate through the children and delete them
|
// for ontime groups, we need to iterate through the children and delete them
|
||||||
for (let i = 0; i < entry.entries.length; i++) {
|
for (let i = 0; i < entry.entries.length; i++) {
|
||||||
const nestedEntryId = entry.entries[i];
|
const nestedEntryId = entry.entries[i];
|
||||||
deleteEntry(nestedEntryId);
|
deleteEntry(nestedEntryId);
|
||||||
}
|
}
|
||||||
} else if (entry.parent) {
|
} else if (entry.parent) {
|
||||||
// at this point, we are handling entries inside a block, so we need to remove the reference
|
// at this point, we are handling entries inside a group, so we need to remove the reference
|
||||||
const parentBlock = rundown.entries[entry.parent];
|
const parentGroup = rundown.entries[entry.parent];
|
||||||
|
|
||||||
// eslint-disable-next-line no-unused-labels -- dev code path
|
// eslint-disable-next-line no-unused-labels -- dev code path
|
||||||
DEV: {
|
DEV: {
|
||||||
if (parentBlock && !isOntimeBlock(parentBlock)) {
|
if (parentGroup && !isOntimeGroup(parentGroup)) {
|
||||||
consoleError(`Parent block with ID ${entry.parent} is not a valid OntimeBlock`);
|
consoleError(`Parent group with ID ${entry.parent} is not a valid Group`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (parentBlock && isOntimeBlock(parentBlock)) {
|
if (parentGroup && isOntimeGroup(parentGroup)) {
|
||||||
// we call a mutation to the parent event to remove the entry from the events
|
// we call a mutation to the parent event to remove the entry from the events
|
||||||
const filteredEvents = deleteById(parentBlock.entries, entry.id);
|
const filteredEvents = deleteById(parentGroup.entries, entry.id);
|
||||||
edit(rundown, { id: parentBlock.id, entries: filteredEvents });
|
edit(rundown, { id: parentGroup.id, entries: filteredEvents });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
deleteEntry(entry.id);
|
deleteEntry(entry.id);
|
||||||
@@ -281,22 +281,22 @@ function removeAll(rundown: Rundown): Rundown {
|
|||||||
/**
|
/**
|
||||||
* Reorders an entry in the rundown
|
* Reorders an entry in the rundown
|
||||||
* Handle moving across order lists
|
* Handle moving across order lists
|
||||||
* @param order - 'before' | 'after' | 'insert' - where to add the entry, insert serves to add the entry into an empty block
|
* @param order - 'before' | 'after' | 'insert' - where to add the entry, insert serves to add the entry into an empty group
|
||||||
* @throws if we insert a block inside another
|
* @throws if we insert a group inside another
|
||||||
*/
|
*/
|
||||||
function reorder(rundown: Rundown, eventFrom: OntimeEntry, eventTo: OntimeEntry, order: 'before' | 'after' | 'insert') {
|
function reorder(rundown: Rundown, eventFrom: OntimeEntry, eventTo: OntimeEntry, order: 'before' | 'after' | 'insert') {
|
||||||
// handle moving across parents
|
// handle moving across parents
|
||||||
const fromParent: EntryId | null = (eventFrom as { parent?: EntryId })?.parent ?? null;
|
const fromParent: EntryId | null = (eventFrom as { parent?: EntryId })?.parent ?? null;
|
||||||
const toParent = (() => {
|
const toParent = (() => {
|
||||||
if (isOntimeBlock(eventTo)) {
|
if (isOntimeGroup(eventTo)) {
|
||||||
// Special case: if we're moving relative to our own parent block, remove from block
|
// Special case: if we're moving relative to our own parent group, remove from group
|
||||||
if ('parent' in eventFrom && eventFrom.parent === eventTo.id) {
|
if ('parent' in eventFrom && eventFrom.parent === eventTo.id) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
if (order === 'insert') {
|
if (order === 'insert') {
|
||||||
// prevent blocks from being inserted into other blocks
|
// prevent groups from being inserted into other groups
|
||||||
if (isOntimeBlock(eventFrom)) {
|
if (isOntimeGroup(eventFrom)) {
|
||||||
throw new Error('Cannot insert a block into another block');
|
throw new Error('Cannot insert a group into another group');
|
||||||
}
|
}
|
||||||
return eventTo.id;
|
return eventTo.id;
|
||||||
}
|
}
|
||||||
@@ -310,8 +310,8 @@ function reorder(rundown: Rundown, eventFrom: OntimeEntry, eventTo: OntimeEntry,
|
|||||||
eventFrom.parent = toParent;
|
eventFrom.parent = toParent;
|
||||||
}
|
}
|
||||||
|
|
||||||
const sourceArray = fromParent === null ? rundown.order : (rundown.entries[fromParent] as OntimeBlock).entries;
|
const sourceArray = fromParent === null ? rundown.order : (rundown.entries[fromParent] as OntimeGroup).entries;
|
||||||
const destinationArray = toParent === null ? rundown.order : (rundown.entries[toParent] as OntimeBlock).entries;
|
const destinationArray = toParent === null ? rundown.order : (rundown.entries[toParent] as OntimeGroup).entries;
|
||||||
|
|
||||||
const fromIndex = sourceArray.indexOf(eventFrom.id);
|
const fromIndex = sourceArray.indexOf(eventFrom.id);
|
||||||
const toIndex = (() => {
|
const toIndex = (() => {
|
||||||
@@ -450,11 +450,11 @@ function swap(rundown: Rundown, eventFrom: OntimeEvent, eventTo: OntimeEvent) {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Inserts a clone of the given entry into the rundown
|
* Inserts a clone of the given entry into the rundown
|
||||||
* Handles cloning children if the entry is a block
|
* Handles cloning children if the entry is a group
|
||||||
*/
|
*/
|
||||||
function clone(rundown: Rundown, entry: OntimeEntry): OntimeEntry {
|
function clone(rundown: Rundown, entry: OntimeEntry): OntimeEntry {
|
||||||
if (isOntimeBlock(entry)) {
|
if (isOntimeGroup(entry)) {
|
||||||
const newBlock = cloneBlock(entry, getUniqueId(rundown));
|
const newGroup = cloneGroup(entry, getUniqueId(rundown));
|
||||||
const nestedIds: EntryId[] = [];
|
const nestedIds: EntryId[] = [];
|
||||||
|
|
||||||
for (let i = 0; i < entry.entries.length; i++) {
|
for (let i = 0; i < entry.entries.length; i++) {
|
||||||
@@ -464,83 +464,83 @@ function clone(rundown: Rundown, entry: OntimeEntry): OntimeEntry {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// clone the event and assign it to the new block
|
// clone the event and assign it to the new group
|
||||||
const newNestedEntry = cloneEntry(nestedEntry, getUniqueId(rundown));
|
const newNestedEntry = cloneEntry(nestedEntry, getUniqueId(rundown));
|
||||||
(newNestedEntry as OntimeEvent | OntimeDelay).parent = newBlock.id;
|
(newNestedEntry as OntimeEvent | OntimeDelay).parent = newGroup.id;
|
||||||
|
|
||||||
nestedIds.push(newNestedEntry.id);
|
nestedIds.push(newNestedEntry.id);
|
||||||
// we immediately insert the nested entries into the rundown
|
// we immediately insert the nested entries into the rundown
|
||||||
rundown.entries[newNestedEntry.id] = newNestedEntry;
|
rundown.entries[newNestedEntry.id] = newNestedEntry;
|
||||||
}
|
}
|
||||||
|
|
||||||
// indexes + 1 since we are inserting after the cloned block
|
// indexes + 1 since we are inserting after the cloned group
|
||||||
const atIndex = rundown.order.indexOf(entry.id) + 1;
|
const atIndex = rundown.order.indexOf(entry.id) + 1;
|
||||||
|
|
||||||
newBlock.entries = nestedIds;
|
newGroup.entries = nestedIds;
|
||||||
newBlock.title = `${entry.title || 'Untitled'} (copy)`;
|
newGroup.title = `${entry.title || 'Untitled'} (copy)`;
|
||||||
|
|
||||||
rundown.entries[newBlock.id] = newBlock;
|
rundown.entries[newGroup.id] = newGroup;
|
||||||
rundown.order = insertAtIndex(atIndex, newBlock.id, rundown.order);
|
rundown.order = insertAtIndex(atIndex, newGroup.id, rundown.order);
|
||||||
|
|
||||||
return newBlock;
|
return newGroup;
|
||||||
} else {
|
} else {
|
||||||
const parent: OntimeBlock | null = entry.parent ? (rundown.entries[entry.parent] as OntimeBlock) : null;
|
const parent: OntimeGroup | null = entry.parent ? (rundown.entries[entry.parent] as OntimeGroup) : null;
|
||||||
return add(rundown, cloneEntry(entry, getUniqueId(rundown)), entry.id, parent);
|
return add(rundown, cloneEntry(entry, getUniqueId(rundown)), entry.id, parent);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Groups a list of entries
|
* Groups a list of entries
|
||||||
* It ensures that the entries get reassigned parent and the block gets a list of events
|
* It ensures that the entries get reassigned parent and the group gets a list of events
|
||||||
* The group will be created at the index of the first event in the order, not at the lowest index
|
* The group will be created at the index of the first event in the order, not at the lowest index
|
||||||
* Mutates the given rundown
|
* Mutates the given rundown
|
||||||
*/
|
*/
|
||||||
function group(rundown: Rundown, entryIds: EntryId[]): OntimeBlock {
|
function group(rundown: Rundown, entryIds: EntryId[]): OntimeGroup {
|
||||||
const newBlock = createBlock({ id: getUniqueId(rundown) });
|
const newGroup = createGroup({ id: getUniqueId(rundown) });
|
||||||
|
|
||||||
const nestedEvents: EntryId[] = [];
|
const nestedEvents: EntryId[] = [];
|
||||||
let firstIndex = -1;
|
let firstIndex = -1;
|
||||||
for (let i = 0; i < entryIds.length; i++) {
|
for (let i = 0; i < entryIds.length; i++) {
|
||||||
const entryId = entryIds[i];
|
const entryId = entryIds[i];
|
||||||
const entry = rundown.entries[entryId];
|
const entry = rundown.entries[entryId];
|
||||||
if (!entry || isOntimeBlock(entry)) {
|
if (!entry || isOntimeGroup(entry)) {
|
||||||
// invalid operation, we skip this entry
|
// invalid operation, we skip this entry
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// the block will be created at the first selected event position
|
// the group will be created at the first selected event position
|
||||||
// note that this is not the lowest index
|
// note that this is not the lowest index
|
||||||
if (firstIndex === -1) {
|
if (firstIndex === -1) {
|
||||||
firstIndex = rundown.flatOrder.indexOf(entryId);
|
firstIndex = rundown.flatOrder.indexOf(entryId);
|
||||||
}
|
}
|
||||||
|
|
||||||
nestedEvents.push(entryId);
|
nestedEvents.push(entryId);
|
||||||
entry.parent = newBlock.id;
|
entry.parent = newGroup.id;
|
||||||
rundown.flatOrder = rundown.flatOrder.filter((id) => id !== entryId);
|
rundown.flatOrder = rundown.flatOrder.filter((id) => id !== entryId);
|
||||||
rundown.order = rundown.order.filter((id) => id !== entryId);
|
rundown.order = rundown.order.filter((id) => id !== entryId);
|
||||||
}
|
}
|
||||||
|
|
||||||
newBlock.entries = nestedEvents;
|
newGroup.entries = nestedEvents;
|
||||||
const insertIndex = Math.max(0, firstIndex);
|
const insertIndex = Math.max(0, firstIndex);
|
||||||
// we have filtered the items from the order
|
// we have filtered the items from the order
|
||||||
// we will insert them now, with only the block at top level ...
|
// we will insert them now, with only the group at top level ...
|
||||||
rundown.order = insertAtIndex(insertIndex, newBlock.id, rundown.order);
|
rundown.order = insertAtIndex(insertIndex, newGroup.id, rundown.order);
|
||||||
rundown.entries[newBlock.id] = newBlock;
|
rundown.entries[newGroup.id] = newGroup;
|
||||||
|
|
||||||
return newBlock;
|
return newGroup;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Deletes a block and moves all its children to the top level order
|
* Deletes a group and moves all its children to the top level order
|
||||||
*/
|
*/
|
||||||
function ungroup(rundown: Rundown, block: OntimeBlock) {
|
function ungroup(rundown: Rundown, group: OntimeGroup) {
|
||||||
// get the events from the block and merge them into the order where the block was
|
// get the events from the group and merge them into the order where the group was
|
||||||
const nestedEvents = block.entries;
|
const nestedEvents = group.entries;
|
||||||
const blockIndex = rundown.order.indexOf(block.id);
|
const groupIndex = rundown.order.indexOf(group.id);
|
||||||
rundown.order.splice(blockIndex, 1, ...nestedEvents);
|
rundown.order.splice(groupIndex, 1, ...nestedEvents);
|
||||||
|
|
||||||
// delete block from entries and remove its reference from the child events
|
// delete the group from entries and remove its reference from the child events
|
||||||
delete rundown.entries[block.id];
|
delete rundown.entries[group.id];
|
||||||
for (let i = 0; i < nestedEvents.length; i++) {
|
for (let i = 0; i < nestedEvents.length; i++) {
|
||||||
const eventId = nestedEvents[i];
|
const eventId = nestedEvents[i];
|
||||||
const entry = rundown.entries[eventId];
|
const entry = rundown.entries[eventId];
|
||||||
@@ -720,16 +720,16 @@ export function processRundown(
|
|||||||
}
|
}
|
||||||
const { processedEntry } = process(currentEntry, null);
|
const { processedEntry } = process(currentEntry, null);
|
||||||
|
|
||||||
// if the event is a block, we process the nested entries
|
// if the event is a group, we process the nested entries
|
||||||
// the code here is a copy of the processing of top level events
|
// the code here is a copy of the processing of top level events
|
||||||
if (isOntimeBlock(processedEntry)) {
|
if (isOntimeGroup(processedEntry)) {
|
||||||
let blockStartTime = null;
|
let groupStartTime = null;
|
||||||
let blockEndTime = null;
|
let groupEndTime = null;
|
||||||
let isFirstLinked = false;
|
let isFirstLinked = false;
|
||||||
const blockEvents: EntryId[] = [];
|
const groupEvents: EntryId[] = [];
|
||||||
processedEntry.duration = 0;
|
processedEntry.duration = 0;
|
||||||
|
|
||||||
// check if the block contains nested entries
|
// check if the group contains nested entries
|
||||||
for (let j = 0; j < processedEntry.entries.length; j++) {
|
for (let j = 0; j < processedEntry.entries.length; j++) {
|
||||||
const nestedEntryId = processedEntry.entries[j];
|
const nestedEntryId = processedEntry.entries[j];
|
||||||
const nestedEntry = initialRundown.entries[nestedEntryId];
|
const nestedEntry = initialRundown.entries[nestedEntryId];
|
||||||
@@ -738,7 +738,7 @@ export function processRundown(
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
blockEvents.push(nestedEntry.id);
|
groupEvents.push(nestedEntry.id);
|
||||||
const { processedEntry: processedNestedEntry } = process(nestedEntry, processedEntry.id);
|
const { processedEntry: processedNestedEntry } = process(nestedEntry, processedEntry.id);
|
||||||
|
|
||||||
// we dont extract metadata of skipped events,
|
// we dont extract metadata of skipped events,
|
||||||
@@ -748,24 +748,24 @@ export function processRundown(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// first start is always the first event
|
// first start is always the first event
|
||||||
if (blockStartTime === null) {
|
if (groupStartTime === null) {
|
||||||
blockStartTime = processedNestedEntry.timeStart;
|
groupStartTime = processedNestedEntry.timeStart;
|
||||||
isFirstLinked = Boolean(processedNestedEntry.linkStart);
|
isFirstLinked = Boolean(processedNestedEntry.linkStart);
|
||||||
}
|
}
|
||||||
|
|
||||||
// lastEntry is the event with the latest end time
|
// lastEntry is the event with the latest end time
|
||||||
blockEndTime = processedNestedEntry.timeEnd;
|
groupEndTime = processedNestedEntry.timeEnd;
|
||||||
if (j > 0) {
|
if (j > 0) {
|
||||||
processedEntry.duration += processedNestedEntry.gap;
|
processedEntry.duration += processedNestedEntry.gap;
|
||||||
}
|
}
|
||||||
processedEntry.duration = processedEntry.duration + processedNestedEntry.duration;
|
processedEntry.duration = processedEntry.duration + processedNestedEntry.duration;
|
||||||
}
|
}
|
||||||
|
|
||||||
// update block metadata
|
// update group metadata
|
||||||
processedEntry.timeStart = blockStartTime;
|
processedEntry.timeStart = groupStartTime;
|
||||||
processedEntry.timeEnd = blockEndTime;
|
processedEntry.timeEnd = groupEndTime;
|
||||||
processedEntry.isFirstLinked = isFirstLinked;
|
processedEntry.isFirstLinked = isFirstLinked;
|
||||||
processedEntry.entries = blockEvents;
|
processedEntry.entries = groupEvents;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import {
|
|||||||
OntimeEvent,
|
OntimeEvent,
|
||||||
isOntimeEvent,
|
isOntimeEvent,
|
||||||
isOntimeDelay,
|
isOntimeDelay,
|
||||||
isOntimeBlock,
|
isOntimeGroup,
|
||||||
CustomFieldKey,
|
CustomFieldKey,
|
||||||
EntryId,
|
EntryId,
|
||||||
OntimeEntry,
|
OntimeEntry,
|
||||||
@@ -21,7 +21,7 @@ import { defaultRundown } from '../../models/dataModel.js';
|
|||||||
import { delay as delayDef } from '../../models/eventsDefinition.js';
|
import { delay as delayDef } from '../../models/eventsDefinition.js';
|
||||||
import type { ErrorEmitter } from '../../utils/parserUtils.js';
|
import type { ErrorEmitter } from '../../utils/parserUtils.js';
|
||||||
|
|
||||||
import { calculateDayOffset, cleanupCustomFields, createBlock, createEvent, createMilestone } from './rundown.utils.js';
|
import { calculateDayOffset, cleanupCustomFields, createGroup, createEvent, createMilestone } from './rundown.utils.js';
|
||||||
import { RundownMetadata } from './rundown.types.js';
|
import { RundownMetadata } from './rundown.types.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -110,7 +110,11 @@ export function parseRundown(
|
|||||||
} else if (isOntimeMilestone(event)) {
|
} else if (isOntimeMilestone(event)) {
|
||||||
newEvent = createMilestone({ ...event, id });
|
newEvent = createMilestone({ ...event, id });
|
||||||
cleanupCustomFields(newEvent.custom, parsedCustomFields);
|
cleanupCustomFields(newEvent.custom, parsedCustomFields);
|
||||||
} else if (isOntimeBlock(event)) {
|
/**
|
||||||
|
* We leave here an entry point for blocks for the alpha testers, should remove this after a while
|
||||||
|
*/
|
||||||
|
// @ts-expect-error -- we are checking a legacy type
|
||||||
|
} else if (event.type === 'block' || isOntimeGroup(event)) {
|
||||||
for (let i = 0; i < event.entries.length; i++) {
|
for (let i = 0; i < event.entries.length; i++) {
|
||||||
const nestedEventId = event.entries[i];
|
const nestedEventId = event.entries[i];
|
||||||
const nestedEvent = rundown.entries[nestedEventId];
|
const nestedEvent = rundown.entries[nestedEventId];
|
||||||
@@ -143,7 +147,7 @@ export function parseRundown(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
newEvent = createBlock({ ...structuredClone(event), id });
|
newEvent = createGroup({ ...structuredClone(event), id });
|
||||||
// ensure entries exist
|
// ensure entries exist
|
||||||
if (event.entries?.length > 0) {
|
if (event.entries?.length > 0) {
|
||||||
newEvent.entries = event.entries.filter((eventId) => Object.hasOwn(rundown.entries, eventId));
|
newEvent.entries = event.entries.filter((eventId) => Object.hasOwn(rundown.entries, eventId));
|
||||||
@@ -240,9 +244,9 @@ export function makeRundownMetadata(customFields: CustomFields) {
|
|||||||
|
|
||||||
function process<T extends OntimeEntry>(
|
function process<T extends OntimeEntry>(
|
||||||
entry: T,
|
entry: T,
|
||||||
childOfBlock: EntryId | null,
|
childOfGroup: EntryId | null,
|
||||||
): { processedData: ProcessedRundownMetadata; processedEntry: T } {
|
): { processedData: ProcessedRundownMetadata; processedEntry: T } {
|
||||||
const data = processEntry(rundownMeta, customFields, entry, childOfBlock);
|
const data = processEntry(rundownMeta, customFields, entry, childOfGroup);
|
||||||
rundownMeta = data.processedData;
|
rundownMeta = data.processedData;
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
@@ -261,7 +265,7 @@ function processEntry<T extends OntimeEntry>(
|
|||||||
rundownMetadata: ProcessedRundownMetadata,
|
rundownMetadata: ProcessedRundownMetadata,
|
||||||
customFields: CustomFields,
|
customFields: CustomFields,
|
||||||
entry: T,
|
entry: T,
|
||||||
childOfBlock: EntryId | null,
|
childOfGroup: EntryId | null,
|
||||||
): { processedData: ProcessedRundownMetadata; processedEntry: T } {
|
): { processedData: ProcessedRundownMetadata; processedEntry: T } {
|
||||||
const processedData = { ...rundownMetadata };
|
const processedData = { ...rundownMetadata };
|
||||||
const currentEntry = structuredClone(entry);
|
const currentEntry = structuredClone(entry);
|
||||||
@@ -294,7 +298,7 @@ function processEntry<T extends OntimeEntry>(
|
|||||||
currentEntry.dayOffset = processedData.totalDays;
|
currentEntry.dayOffset = processedData.totalDays;
|
||||||
currentEntry.delay = 0; // this means we dont calculate delays or gaps for skipped events
|
currentEntry.delay = 0; // this means we dont calculate delays or gaps for skipped events
|
||||||
currentEntry.gap = 0; // this means we dont calculate delays or gaps for skipped events
|
currentEntry.gap = 0; // this means we dont calculate delays or gaps for skipped events
|
||||||
currentEntry.parent = childOfBlock;
|
currentEntry.parent = childOfGroup;
|
||||||
|
|
||||||
// update rundown metadata, it only concerns playable events
|
// update rundown metadata, it only concerns playable events
|
||||||
if (isPlayableEvent(currentEntry)) {
|
if (isPlayableEvent(currentEntry)) {
|
||||||
@@ -353,10 +357,10 @@ function processEntry<T extends OntimeEntry>(
|
|||||||
} else if (isOntimeDelay(currentEntry)) {
|
} else if (isOntimeDelay(currentEntry)) {
|
||||||
// !!! this must happen after handling the links
|
// !!! this must happen after handling the links
|
||||||
processedData.totalDelay += currentEntry.duration;
|
processedData.totalDelay += currentEntry.duration;
|
||||||
currentEntry.parent = childOfBlock;
|
currentEntry.parent = childOfGroup;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!childOfBlock) {
|
if (!childOfGroup) {
|
||||||
processedData.order.push(currentEntry.id);
|
processedData.order.push(currentEntry.id);
|
||||||
}
|
}
|
||||||
processedData.entries[currentEntry.id] = currentEntry;
|
processedData.entries[currentEntry.id] = currentEntry;
|
||||||
|
|||||||
@@ -4,10 +4,10 @@ import {
|
|||||||
CustomFields,
|
CustomFields,
|
||||||
EntryId,
|
EntryId,
|
||||||
EventPostPayload,
|
EventPostPayload,
|
||||||
isOntimeBlock,
|
isOntimeGroup,
|
||||||
isOntimeDelay,
|
isOntimeDelay,
|
||||||
isOntimeEvent,
|
isOntimeEvent,
|
||||||
OntimeBlock,
|
OntimeGroup,
|
||||||
OntimeEntry,
|
OntimeEntry,
|
||||||
PatchWithId,
|
PatchWithId,
|
||||||
RefetchKey,
|
RefetchKey,
|
||||||
@@ -35,12 +35,12 @@ export async function addEntry(eventData: EventPostPayload): Promise<OntimeEntry
|
|||||||
}
|
}
|
||||||
|
|
||||||
// the parent can be provided or inferred from position
|
// the parent can be provided or inferred from position
|
||||||
let parent: OntimeBlock | null = null;
|
let parent: OntimeGroup | null = null;
|
||||||
|
|
||||||
if ('parent' in eventData && eventData.parent != null) {
|
if ('parent' in eventData && eventData.parent != null) {
|
||||||
// if the user provides a parent (inside a group), we make sure it exists and it is a group
|
// if the user provides a parent (inside a group), we make sure it exists and it is a group
|
||||||
const maybeParent = rundown.entries[eventData.parent];
|
const maybeParent = rundown.entries[eventData.parent];
|
||||||
if (!maybeParent || !isOntimeBlock(maybeParent)) {
|
if (!maybeParent || !isOntimeGroup(maybeParent)) {
|
||||||
throw new Error(`Invalid parent event with ID ${eventData.parent}`);
|
throw new Error(`Invalid parent event with ID ${eventData.parent}`);
|
||||||
}
|
}
|
||||||
parent = maybeParent;
|
parent = maybeParent;
|
||||||
@@ -51,7 +51,7 @@ export async function addEntry(eventData: EventPostPayload): Promise<OntimeEntry
|
|||||||
const maybeSibling = rundown.entries[referenceId];
|
const maybeSibling = rundown.entries[referenceId];
|
||||||
if (maybeSibling && 'parent' in maybeSibling && maybeSibling.parent) {
|
if (maybeSibling && 'parent' in maybeSibling && maybeSibling.parent) {
|
||||||
const maybeParent = rundown.entries[maybeSibling.parent];
|
const maybeParent = rundown.entries[maybeSibling.parent];
|
||||||
if (maybeParent && isOntimeBlock(maybeParent)) {
|
if (maybeParent && isOntimeGroup(maybeParent)) {
|
||||||
parent = maybeParent;
|
parent = maybeParent;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -241,7 +241,7 @@ export async function deleteAllEntries(): Promise<Rundown> {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Moves an event to a new position in the rundown
|
* Moves an event to a new position in the rundown
|
||||||
* Handles moving across root orders (a block order and top level order)
|
* Handles moving across root orders (a group order and top level order)
|
||||||
* @throws if entryId or destinationId not found
|
* @throws if entryId or destinationId not found
|
||||||
*/
|
*/
|
||||||
export function reorderEntry(entryId: EntryId, destinationId: EntryId, order: 'before' | 'after' | 'insert') {
|
export function reorderEntry(entryId: EntryId, destinationId: EntryId, order: 'before' | 'after' | 'insert') {
|
||||||
@@ -356,7 +356,7 @@ export async function cloneEntry(entryId: EntryId): Promise<Rundown> {
|
|||||||
updateRuntimeOnChange(rundownMetadata);
|
updateRuntimeOnChange(rundownMetadata);
|
||||||
|
|
||||||
// notify timer and external services of change
|
// notify timer and external services of change
|
||||||
if (isOntimeBlock(newEntry)) {
|
if (isOntimeGroup(newEntry)) {
|
||||||
notifyChanges(rundownMetadata, revision, { timer: newEntry.entries, external: true });
|
notifyChanges(rundownMetadata, revision, { timer: newEntry.entries, external: true });
|
||||||
} else if (isOntimeEvent(newEntry)) {
|
} else if (isOntimeEvent(newEntry)) {
|
||||||
notifyChanges(rundownMetadata, revision, { timer: [newEntry.id], external: true });
|
notifyChanges(rundownMetadata, revision, { timer: [newEntry.id], external: true });
|
||||||
@@ -370,7 +370,7 @@ export async function cloneEntry(entryId: EntryId): Promise<Rundown> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Groups a list of entries into a new block
|
* Groups a list of entries into a new group
|
||||||
*/
|
*/
|
||||||
export async function groupEntries(entryIds: EntryId[]): Promise<Rundown> {
|
export async function groupEntries(entryIds: EntryId[]): Promise<Rundown> {
|
||||||
const { rundown, commit } = createTransaction({ mutableRundown: true, mutableCustomFields: false });
|
const { rundown, commit } = createTransaction({ mutableRundown: true, mutableCustomFields: false });
|
||||||
@@ -391,17 +391,17 @@ export async function groupEntries(entryIds: EntryId[]): Promise<Rundown> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Deletes a block and moves all its children to the top level
|
* Deletes a group and moves all its children to the top level
|
||||||
*/
|
*/
|
||||||
export async function ungroupEntries(blockId: EntryId): Promise<Rundown> {
|
export async function ungroupEntries(groupId: EntryId): Promise<Rundown> {
|
||||||
const { rundown, commit } = createTransaction({ mutableRundown: true, mutableCustomFields: false });
|
const { rundown, commit } = createTransaction({ mutableRundown: true, mutableCustomFields: false });
|
||||||
|
|
||||||
const block = rundown.entries[blockId];
|
const group = rundown.entries[groupId];
|
||||||
if (!block || !isOntimeBlock(block)) {
|
if (!group || !isOntimeGroup(group)) {
|
||||||
throw new Error(`Block with ID ${blockId} not found or is not a block`);
|
throw new Error(`Group with ID ${groupId} not found or is not a group`);
|
||||||
}
|
}
|
||||||
|
|
||||||
rundownMutation.ungroup(rundown, block);
|
rundownMutation.ungroup(rundown, group);
|
||||||
const { rundown: rundownResult, rundownMetadata, revision } = commit();
|
const { rundown: rundownResult, rundownMetadata, revision } = commit();
|
||||||
|
|
||||||
// schedule the side effects
|
// schedule the side effects
|
||||||
|
|||||||
@@ -2,12 +2,12 @@ import {
|
|||||||
CustomFields,
|
CustomFields,
|
||||||
EntryCustomFields,
|
EntryCustomFields,
|
||||||
EntryId,
|
EntryId,
|
||||||
isOntimeBlock,
|
isOntimeGroup,
|
||||||
isOntimeDelay,
|
isOntimeDelay,
|
||||||
isOntimeEvent,
|
isOntimeEvent,
|
||||||
isOntimeMilestone,
|
isOntimeMilestone,
|
||||||
OntimeBaseEvent,
|
OntimeBaseEvent,
|
||||||
OntimeBlock,
|
OntimeGroup,
|
||||||
OntimeDelay,
|
OntimeDelay,
|
||||||
OntimeEntry,
|
OntimeEntry,
|
||||||
OntimeEvent,
|
OntimeEvent,
|
||||||
@@ -27,7 +27,7 @@ import {
|
|||||||
|
|
||||||
import {
|
import {
|
||||||
event as eventDef,
|
event as eventDef,
|
||||||
block as blockDef,
|
group as groupDef,
|
||||||
delay as delayDef,
|
delay as delayDef,
|
||||||
milestone as milestoneDef,
|
milestone as milestoneDef,
|
||||||
} from '../../models/eventsDefinition.js';
|
} from '../../models/eventsDefinition.js';
|
||||||
@@ -39,8 +39,8 @@ type CompleteEntry<T> =
|
|||||||
? OntimeEvent
|
? OntimeEvent
|
||||||
: T extends Partial<OntimeDelay>
|
: T extends Partial<OntimeDelay>
|
||||||
? OntimeDelay
|
? OntimeDelay
|
||||||
: T extends Partial<OntimeBlock>
|
: T extends Partial<OntimeGroup>
|
||||||
? OntimeBlock
|
? OntimeGroup
|
||||||
: T extends Partial<OntimeMilestone>
|
: T extends Partial<OntimeMilestone>
|
||||||
? OntimeMilestone
|
? OntimeMilestone
|
||||||
: never;
|
: never;
|
||||||
@@ -49,7 +49,7 @@ type CompleteEntry<T> =
|
|||||||
* Generates a fully formed RundownEntry of the patch type
|
* Generates a fully formed RundownEntry of the patch type
|
||||||
*/
|
*/
|
||||||
export function generateEvent<
|
export function generateEvent<
|
||||||
T extends Partial<OntimeEvent> | Partial<OntimeDelay> | Partial<OntimeBlock> | Partial<OntimeMilestone>,
|
T extends Partial<OntimeEvent> | Partial<OntimeDelay> | Partial<OntimeGroup> | Partial<OntimeMilestone>,
|
||||||
>(rundown: Rundown, eventData: T, afterId: EntryId | null): CompleteEntry<T> {
|
>(rundown: Rundown, eventData: T, afterId: EntryId | null): CompleteEntry<T> {
|
||||||
if (isOntimeEvent(eventData)) {
|
if (isOntimeEvent(eventData)) {
|
||||||
return createEvent(eventData, getCueCandidate(rundown.entries, rundown.order, afterId)) as CompleteEntry<T>;
|
return createEvent(eventData, getCueCandidate(rundown.entries, rundown.order, afterId)) as CompleteEntry<T>;
|
||||||
@@ -61,9 +61,9 @@ export function generateEvent<
|
|||||||
return { ...delayDef, duration: eventData.duration ?? 0, id } as CompleteEntry<T>;
|
return { ...delayDef, duration: eventData.duration ?? 0, id } as CompleteEntry<T>;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO(v4): allow user to provide a larger patch of the block entry
|
// TODO(v4): allow user to provide a larger patch of the group entry
|
||||||
if (isOntimeBlock(eventData)) {
|
if (isOntimeGroup(eventData)) {
|
||||||
return createBlock({ id, title: eventData.title ?? '' }) as CompleteEntry<T>;
|
return createGroup({ id, title: eventData.title ?? '' }) as CompleteEntry<T>;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isOntimeMilestone(eventData)) {
|
if (isOntimeMilestone(eventData)) {
|
||||||
@@ -115,35 +115,35 @@ export function createEventPatch(originalEvent: OntimeEvent, patchEvent: Partial
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createBlockPatch(originalBlock: OntimeBlock, patchBlock: Partial<OntimeBlock>): OntimeBlock {
|
export function createGroupPatch(originalGroup: OntimeGroup, patchGroup: Partial<OntimeGroup>): OntimeGroup {
|
||||||
if (Object.keys(patchBlock).length === 0) {
|
if (Object.keys(patchGroup).length === 0) {
|
||||||
return originalBlock;
|
return originalGroup;
|
||||||
}
|
}
|
||||||
|
|
||||||
const maybeTargetDuration = () => {
|
const maybeTargetDuration = () => {
|
||||||
if (typeof patchBlock.targetDuration === 'number') {
|
if (typeof patchGroup.targetDuration === 'number') {
|
||||||
return patchBlock.targetDuration;
|
return patchGroup.targetDuration;
|
||||||
}
|
}
|
||||||
if (patchBlock.targetDuration === null || patchBlock.targetDuration === '') {
|
if (patchGroup.targetDuration === null || patchGroup.targetDuration === '') {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return originalBlock.targetDuration;
|
return originalGroup.targetDuration;
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: originalBlock.id,
|
id: originalGroup.id,
|
||||||
type: SupportedEntry.Block,
|
type: SupportedEntry.Group,
|
||||||
title: makeString(patchBlock.title, originalBlock.title),
|
title: makeString(patchGroup.title, originalGroup.title),
|
||||||
note: makeString(patchBlock.note, originalBlock.note),
|
note: makeString(patchGroup.note, originalGroup.note),
|
||||||
entries: patchBlock.entries ?? originalBlock.entries,
|
entries: patchGroup.entries ?? originalGroup.entries,
|
||||||
targetDuration: maybeTargetDuration(),
|
targetDuration: maybeTargetDuration(),
|
||||||
colour: makeString(patchBlock.colour, originalBlock.colour),
|
colour: makeString(patchGroup.colour, originalGroup.colour),
|
||||||
revision: originalBlock.revision,
|
revision: originalGroup.revision,
|
||||||
timeStart: originalBlock.timeStart,
|
timeStart: originalGroup.timeStart,
|
||||||
timeEnd: originalBlock.timeEnd,
|
timeEnd: originalGroup.timeEnd,
|
||||||
duration: originalBlock.duration,
|
duration: originalGroup.duration,
|
||||||
isFirstLinked: originalBlock.isFirstLinked,
|
isFirstLinked: originalGroup.isFirstLinked,
|
||||||
custom: { ...originalBlock.custom, ...patchBlock.custom },
|
custom: { ...originalGroup.custom, ...patchGroup.custom },
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -179,10 +179,10 @@ export function applyPatchToEntry(eventFromRundown: OntimeEntry, patch: Partial<
|
|||||||
return newEvent;
|
return newEvent;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isOntimeBlock(eventFromRundown)) {
|
if (isOntimeGroup(eventFromRundown)) {
|
||||||
const newBlock = createBlockPatch(eventFromRundown as OntimeBlock, patch as Partial<OntimeBlock>);
|
const newGroup = createGroupPatch(eventFromRundown as OntimeGroup, patch as Partial<OntimeGroup>);
|
||||||
newBlock.revision++;
|
newGroup.revision++;
|
||||||
return newBlock;
|
return newGroup;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isOntimeMilestone(eventFromRundown)) {
|
if (isOntimeMilestone(eventFromRundown)) {
|
||||||
@@ -218,16 +218,16 @@ export const createEvent = (eventArgs: Partial<OntimeEvent>, eventIndex: number
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new block from an optional patch
|
* Creates a new group from an optional patch
|
||||||
*/
|
*/
|
||||||
export function createBlock(patch?: Partial<OntimeBlock>): OntimeBlock {
|
export function createGroup(patch?: Partial<OntimeGroup>): OntimeGroup {
|
||||||
if (!patch) {
|
if (!patch) {
|
||||||
return { ...blockDef, id: generateId() };
|
return { ...groupDef, id: generateId() };
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: patch.id ?? generateId(),
|
id: patch.id ?? generateId(),
|
||||||
type: SupportedEntry.Block,
|
type: SupportedEntry.Group,
|
||||||
title: patch.title ?? '',
|
title: patch.title ?? '',
|
||||||
note: patch.note ?? '',
|
note: patch.note ?? '',
|
||||||
entries: patch.entries ?? [],
|
entries: patch.entries ?? [],
|
||||||
@@ -385,13 +385,13 @@ export function cloneMilestone(entry: OntimeMilestone, newId: EntryId): OntimeMi
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gathers business logic for how to clone an OntimeBlock
|
* Gathers business logic for how to clone an OntimeGroup
|
||||||
*/
|
*/
|
||||||
export function cloneBlock(entry: OntimeBlock, newId: EntryId): OntimeBlock {
|
export function cloneGroup(entry: OntimeGroup, newId: EntryId): OntimeGroup {
|
||||||
const newEntry = structuredClone(entry);
|
const newEntry = structuredClone(entry);
|
||||||
newEntry.id = newId;
|
newEntry.id = newId;
|
||||||
|
|
||||||
// in blocks, we need to remove the events references
|
// in groups, we need to remove the events references
|
||||||
newEntry.entries = [];
|
newEntry.entries = [];
|
||||||
newEntry.revision = 0;
|
newEntry.revision = 0;
|
||||||
return newEntry;
|
return newEntry;
|
||||||
@@ -405,8 +405,8 @@ export function cloneEntry(entry: OntimeEntry, newId: EntryId): OntimeEntry {
|
|||||||
return cloneEvent(entry, newId);
|
return cloneEvent(entry, newId);
|
||||||
} else if (isOntimeDelay(entry)) {
|
} else if (isOntimeDelay(entry)) {
|
||||||
return cloneDelay(entry, newId);
|
return cloneDelay(entry, newId);
|
||||||
} else if (isOntimeBlock(entry)) {
|
} else if (isOntimeGroup(entry)) {
|
||||||
return cloneBlock(entry, newId);
|
return cloneGroup(entry, newId);
|
||||||
} else if (isOntimeMilestone(entry)) {
|
} else if (isOntimeMilestone(entry)) {
|
||||||
return cloneMilestone(entry, newId);
|
return cloneMilestone(entry, newId);
|
||||||
}
|
}
|
||||||
@@ -452,7 +452,7 @@ export function calculateDayOffset(
|
|||||||
*/
|
*/
|
||||||
export function getInsertAfterId(
|
export function getInsertAfterId(
|
||||||
rundown: Rundown,
|
rundown: Rundown,
|
||||||
parent: OntimeBlock | null,
|
parent: OntimeGroup | null,
|
||||||
afterId?: EntryId,
|
afterId?: EntryId,
|
||||||
beforeId?: EntryId,
|
beforeId?: EntryId,
|
||||||
): EntryId | null {
|
): EntryId | null {
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { body, param } from 'express-validator';
|
|||||||
import { requestValidationFunction } from '../validation-utils/validationFunction.js';
|
import { requestValidationFunction } from '../validation-utils/validationFunction.js';
|
||||||
|
|
||||||
export const rundownPostValidator = [
|
export const rundownPostValidator = [
|
||||||
body('type').isString().isIn(['event', 'delay', 'block', 'milestone']),
|
body('type').isString().isIn(['event', 'delay', 'group', 'milestone']),
|
||||||
body('after').optional().isString(),
|
body('after').optional().isString(),
|
||||||
body('before').optional().isString(),
|
body('before').optional().isString(),
|
||||||
|
|
||||||
|
|||||||
@@ -187,8 +187,8 @@ export const startServer = async (): Promise<{ message: string; serverPort: numb
|
|||||||
runtime: state.runtime,
|
runtime: state.runtime,
|
||||||
eventNow: state.eventNow,
|
eventNow: state.eventNow,
|
||||||
eventNext: state.eventNext,
|
eventNext: state.eventNext,
|
||||||
blockNow: null,
|
groupNow: null,
|
||||||
blockNext: null,
|
groupNext: null,
|
||||||
nextFlag: null,
|
nextFlag: null,
|
||||||
auxtimer1: {
|
auxtimer1: {
|
||||||
duration: timerConfig.auxTimerDefault,
|
duration: timerConfig.auxTimerDefault,
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ export const demoDb: DatabaseModel = {
|
|||||||
},
|
},
|
||||||
'7eaf99': {
|
'7eaf99': {
|
||||||
id: '7eaf99',
|
id: '7eaf99',
|
||||||
type: SupportedEntry.Block,
|
type: SupportedEntry.Group,
|
||||||
title: 'Morning Sessions',
|
title: 'Morning Sessions',
|
||||||
note: '',
|
note: '',
|
||||||
entries: ['9bf60f', 'bf71a2', 'c2697f', 'fa593e', 'a8b0b3'],
|
entries: ['9bf60f', 'bf71a2', 'c2697f', 'fa593e', 'a8b0b3'],
|
||||||
@@ -160,7 +160,7 @@ export const demoDb: DatabaseModel = {
|
|||||||
},
|
},
|
||||||
f60403: {
|
f60403: {
|
||||||
id: 'f60403',
|
id: 'f60403',
|
||||||
type: SupportedEntry.Block,
|
type: SupportedEntry.Group,
|
||||||
title: 'Lunch',
|
title: 'Lunch',
|
||||||
note: '',
|
note: '',
|
||||||
entries: ['0aaa7d'],
|
entries: ['0aaa7d'],
|
||||||
@@ -202,7 +202,7 @@ export const demoDb: DatabaseModel = {
|
|||||||
},
|
},
|
||||||
'6b0edb': {
|
'6b0edb': {
|
||||||
id: '6b0edb',
|
id: '6b0edb',
|
||||||
type: SupportedEntry.Block,
|
type: SupportedEntry.Group,
|
||||||
title: 'Afternoon Sessions',
|
title: 'Afternoon Sessions',
|
||||||
note: '',
|
note: '',
|
||||||
entries: ['02afca', '75ce86', 'e10ed9', '07df89'],
|
entries: ['02afca', '75ce86', 'e10ed9', '07df89'],
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import {
|
import {
|
||||||
EndAction,
|
EndAction,
|
||||||
OntimeBlock,
|
OntimeGroup,
|
||||||
OntimeDelay,
|
OntimeDelay,
|
||||||
OntimeEvent,
|
OntimeEvent,
|
||||||
OntimeMilestone,
|
OntimeMilestone,
|
||||||
@@ -54,8 +54,8 @@ export const milestone: Omit<OntimeMilestone, 'id'> = {
|
|||||||
revision: 0, // calculated at runtime
|
revision: 0, // calculated at runtime
|
||||||
};
|
};
|
||||||
|
|
||||||
export const block: Omit<OntimeBlock, 'id'> = {
|
export const group: Omit<OntimeGroup, 'id'> = {
|
||||||
type: SupportedEntry.Block,
|
type: SupportedEntry.Group,
|
||||||
title: '',
|
title: '',
|
||||||
note: '',
|
note: '',
|
||||||
entries: [],
|
entries: [],
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ export type RestorePoint = {
|
|||||||
addedTime: number;
|
addedTime: number;
|
||||||
pausedAt: MaybeNumber;
|
pausedAt: MaybeNumber;
|
||||||
firstStart: MaybeNumber;
|
firstStart: MaybeNumber;
|
||||||
blockStartAt: MaybeNumber;
|
groupStartAt: MaybeNumber;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -51,7 +51,7 @@ export function isRestorePoint(obj: unknown): obj is RestorePoint {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof restorePoint.blockStartAt !== 'number' && restorePoint.blockStartAt !== null) {
|
if (typeof restorePoint.groupStartAt !== 'number' && restorePoint.groupStartAt !== null) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ describe('isRestorePoint()', () => {
|
|||||||
addedTime: 2,
|
addedTime: 2,
|
||||||
pausedAt: 3,
|
pausedAt: 3,
|
||||||
firstStart: 1,
|
firstStart: 1,
|
||||||
blockStartAt: 10,
|
groupStartAt: 10,
|
||||||
};
|
};
|
||||||
expect(isRestorePoint(restorePoint)).toBe(true);
|
expect(isRestorePoint(restorePoint)).toBe(true);
|
||||||
|
|
||||||
@@ -25,7 +25,7 @@ describe('isRestorePoint()', () => {
|
|||||||
addedTime: 0,
|
addedTime: 0,
|
||||||
pausedAt: null,
|
pausedAt: null,
|
||||||
firstStart: 1,
|
firstStart: 1,
|
||||||
blockStartAt: null,
|
groupStartAt: null,
|
||||||
};
|
};
|
||||||
expect(isRestorePoint(restorePoint)).toBe(true);
|
expect(isRestorePoint(restorePoint)).toBe(true);
|
||||||
});
|
});
|
||||||
@@ -38,7 +38,7 @@ describe('isRestorePoint()', () => {
|
|||||||
startedAt: null,
|
startedAt: null,
|
||||||
addedTime: 0,
|
addedTime: 0,
|
||||||
pausedAt: null,
|
pausedAt: null,
|
||||||
blockStartAt: 10,
|
groupStartAt: 10,
|
||||||
};
|
};
|
||||||
expect(isRestorePoint(restorePoint)).toBe(false);
|
expect(isRestorePoint(restorePoint)).toBe(false);
|
||||||
});
|
});
|
||||||
@@ -48,7 +48,7 @@ describe('isRestorePoint()', () => {
|
|||||||
startedAt: null,
|
startedAt: null,
|
||||||
addedTime: 0,
|
addedTime: 0,
|
||||||
pausedAt: null,
|
pausedAt: null,
|
||||||
blockStartAt: 10,
|
groupStartAt: 10,
|
||||||
};
|
};
|
||||||
expect(isRestorePoint(restorePoint)).toBe(false);
|
expect(isRestorePoint(restorePoint)).toBe(false);
|
||||||
});
|
});
|
||||||
@@ -59,7 +59,7 @@ describe('isRestorePoint()', () => {
|
|||||||
startedAt: 'testing',
|
startedAt: 'testing',
|
||||||
addedTime: 0,
|
addedTime: 0,
|
||||||
pausedAt: null,
|
pausedAt: null,
|
||||||
blockStartAt: 10,
|
groupStartAt: 10,
|
||||||
};
|
};
|
||||||
expect(isRestorePoint(restorePoint)).toBe(false);
|
expect(isRestorePoint(restorePoint)).toBe(false);
|
||||||
});
|
});
|
||||||
@@ -76,7 +76,7 @@ describe('RestoreService()', () => {
|
|||||||
addedTime: 5678,
|
addedTime: 5678,
|
||||||
pausedAt: 9087,
|
pausedAt: 9087,
|
||||||
firstStart: 1234,
|
firstStart: 1234,
|
||||||
blockStartAt: 1652,
|
groupStartAt: 1652,
|
||||||
};
|
};
|
||||||
|
|
||||||
const restoreService = new RestoreService('/path/to/restore/file');
|
const restoreService = new RestoreService('/path/to/restore/file');
|
||||||
@@ -94,7 +94,7 @@ describe('RestoreService()', () => {
|
|||||||
addedTime: 0,
|
addedTime: 0,
|
||||||
pausedAt: null,
|
pausedAt: null,
|
||||||
firstStart: 1234,
|
firstStart: 1234,
|
||||||
blockStartAt: null,
|
groupStartAt: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
const restoreService = new RestoreService('/path/to/restore/file');
|
const restoreService = new RestoreService('/path/to/restore/file');
|
||||||
@@ -112,7 +112,7 @@ describe('RestoreService()', () => {
|
|||||||
addedTime: 1234,
|
addedTime: 1234,
|
||||||
pausedAt: 1234,
|
pausedAt: 1234,
|
||||||
firstStart: 1234,
|
firstStart: 1234,
|
||||||
blockStartAt: 10,
|
groupStartAt: 10,
|
||||||
};
|
};
|
||||||
|
|
||||||
const restoreService = new RestoreService('/path/to/restore/file');
|
const restoreService = new RestoreService('/path/to/restore/file');
|
||||||
@@ -132,7 +132,7 @@ describe('RestoreService()', () => {
|
|||||||
addedTime: 1234,
|
addedTime: 1234,
|
||||||
pausedAt: 1234,
|
pausedAt: 1234,
|
||||||
firstStart: 1234,
|
firstStart: 1234,
|
||||||
blockStartAt: null,
|
groupStartAt: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
const restoreService = new RestoreService('/path/to/restore/file');
|
const restoreService = new RestoreService('/path/to/restore/file');
|
||||||
|
|||||||
@@ -716,9 +716,9 @@ function broadcastResult(_target: any, _propertyKey: string, descriptor: Propert
|
|||||||
!deepEqual(RuntimeService.previousState?.runtime, state.runtime);
|
!deepEqual(RuntimeService.previousState?.runtime, state.runtime);
|
||||||
|
|
||||||
// TODO: the value shows up one tick to late
|
// TODO: the value shows up one tick to late
|
||||||
const shouldBlockUpdate =
|
const shouldGroupUpdate =
|
||||||
!deepEqual(RuntimeService?.previousState.blockNow, state.blockNow) ||
|
!deepEqual(RuntimeService?.previousState.groupNow, state.groupNow) ||
|
||||||
RuntimeService?.previousState.blockNext !== state.blockNext;
|
RuntimeService?.previousState.groupNext !== state.groupNext;
|
||||||
|
|
||||||
// TODO: the value shows up one tick to late
|
// TODO: the value shows up one tick to late
|
||||||
const shouldNextFlagUpdate = !deepEqual(RuntimeService?.previousState?.nextFlag, state.nextFlag);
|
const shouldNextFlagUpdate = !deepEqual(RuntimeService?.previousState?.nextFlag, state.nextFlag);
|
||||||
@@ -728,7 +728,7 @@ function broadcastResult(_target: any, _propertyKey: string, descriptor: Propert
|
|||||||
* so if any of them are updated we also need to send the clock
|
* so if any of them are updated we also need to send the clock
|
||||||
* in case nothing else is updating the clock will be updated at the notification rate
|
* in case nothing else is updating the clock will be updated at the notification rate
|
||||||
*/
|
*/
|
||||||
const shouldUpdateClock = shouldRuntimeUpdate || shouldBlockUpdate || normalClockUpdate;
|
const shouldUpdateClock = shouldRuntimeUpdate || shouldGroupUpdate || normalClockUpdate;
|
||||||
|
|
||||||
// Now we set all the updates on the eventstore and update the previous value
|
// Now we set all the updates on the eventstore and update the previous value
|
||||||
if (shouldUpdateTimer) {
|
if (shouldUpdateTimer) {
|
||||||
@@ -744,11 +744,11 @@ function broadcastResult(_target: any, _propertyKey: string, descriptor: Propert
|
|||||||
RuntimeService.previousState.runtime = structuredClone(state.runtime);
|
RuntimeService.previousState.runtime = structuredClone(state.runtime);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (shouldBlockUpdate) {
|
if (shouldGroupUpdate) {
|
||||||
batch.add('blockNow', state.blockNow);
|
batch.add('groupNow', state.groupNow);
|
||||||
batch.add('blockNext', state.blockNext);
|
batch.add('groupNext', state.groupNext);
|
||||||
RuntimeService.previousState.blockNow = structuredClone(state.blockNow);
|
RuntimeService.previousState.groupNow = structuredClone(state.groupNow);
|
||||||
RuntimeService.previousState.blockNext = state.blockNext;
|
RuntimeService.previousState.groupNext = structuredClone(state.groupNext);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (shouldNextFlagUpdate) {
|
if (shouldNextFlagUpdate) {
|
||||||
@@ -808,7 +808,7 @@ function broadcastResult(_target: any, _propertyKey: string, descriptor: Propert
|
|||||||
addedTime: state.timer.addedTime,
|
addedTime: state.timer.addedTime,
|
||||||
pausedAt: state._timer.pausedAt,
|
pausedAt: state._timer.pausedAt,
|
||||||
firstStart: state.runtime.actualStart,
|
firstStart: state.runtime.actualStart,
|
||||||
blockStartAt: state.blockNow?.startedAt ?? null,
|
groupStartAt: state.groupNow?.startedAt ?? null,
|
||||||
})
|
})
|
||||||
.catch((_e) => {
|
.catch((_e) => {
|
||||||
//we don't do anything with the error here
|
//we don't do anything with the error here
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { isOntimeBlock, isOntimeEvent, OntimeEvent, OntimeEntry, RGBColour } from 'ontime-types';
|
import { isOntimeGroup, isOntimeEvent, OntimeEvent, OntimeEntry, RGBColour } from 'ontime-types';
|
||||||
import { cssOrHexToColour, isLightColour, millisToString, mixColours } from 'ontime-utils';
|
import { cssOrHexToColour, isLightColour, millisToString, mixColours } from 'ontime-utils';
|
||||||
|
|
||||||
import type { sheets_v4 } from '@googleapis/sheets';
|
import type { sheets_v4 } from '@googleapis/sheets';
|
||||||
@@ -100,7 +100,7 @@ export function cellRequestFromEvent(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const colors = isOntimeEvent(event) || isOntimeBlock(event) ? getAccessibleColour(event.colour) : undefined;
|
const colors = isOntimeEvent(event) || isOntimeGroup(event) ? getAccessibleColour(event.colour) : undefined;
|
||||||
const cellColor: sheets_v4.Schema$CellData = !colors
|
const cellColor: sheets_v4.Schema$CellData = !colors
|
||||||
? {}
|
? {}
|
||||||
: {
|
: {
|
||||||
@@ -163,12 +163,12 @@ function getCellData(key: keyof OntimeEvent | 'blank', event: OntimeEntry) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isOntimeBlock(event)) {
|
if (isOntimeGroup(event)) {
|
||||||
if (key === 'title') {
|
if (key === 'title') {
|
||||||
return { userEnteredValue: { stringValue: event[key] } };
|
return { userEnteredValue: { stringValue: event[key] } };
|
||||||
}
|
}
|
||||||
if (key === 'timerType') {
|
if (key === 'timerType') {
|
||||||
return { userEnteredValue: { stringValue: 'block' } };
|
return { userEnteredValue: { stringValue: 'group' } };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,8 +6,8 @@ const baseState: RuntimeState = {
|
|||||||
clock: 0,
|
clock: 0,
|
||||||
eventNow: null,
|
eventNow: null,
|
||||||
eventNext: null,
|
eventNext: null,
|
||||||
blockNow: null,
|
groupNow: null,
|
||||||
blockNext: null,
|
groupNext: null,
|
||||||
nextFlag: null,
|
nextFlag: null,
|
||||||
runtime: {
|
runtime: {
|
||||||
selectedEventIndex: null,
|
selectedEventIndex: null,
|
||||||
@@ -40,7 +40,7 @@ const baseState: RuntimeState = {
|
|||||||
_rundown: {
|
_rundown: {
|
||||||
totalDelay: 0,
|
totalDelay: 0,
|
||||||
},
|
},
|
||||||
_block: null,
|
_group: null,
|
||||||
_end: null,
|
_end: null,
|
||||||
_flag: null,
|
_flag: null,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { PlayableEvent, Playback, TimerPhase } from 'ontime-types';
|
import { PlayableEvent, Playback, TimerPhase } from 'ontime-types';
|
||||||
|
|
||||||
import { makeOntimeBlock, makeOntimeEvent, makeRundown } from '../../api-data/rundown/__mocks__/rundown.mocks.js';
|
import { makeOntimeGroup, makeOntimeEvent, makeRundown } from '../../api-data/rundown/__mocks__/rundown.mocks.js';
|
||||||
import { initRundown } from '../../api-data/rundown/rundown.service.js';
|
import { initRundown } from '../../api-data/rundown/rundown.service.js';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@@ -9,7 +9,7 @@ import {
|
|||||||
clearState,
|
clearState,
|
||||||
getState,
|
getState,
|
||||||
load,
|
load,
|
||||||
loadBlockFlagAndEnd,
|
loadGroupFlagAndEnd,
|
||||||
pause,
|
pause,
|
||||||
roll,
|
roll,
|
||||||
start,
|
start,
|
||||||
@@ -105,7 +105,7 @@ describe('mutation on runtimeState', () => {
|
|||||||
expect(newState.eventNext?.id).toBe('event2');
|
expect(newState.eventNext?.id).toBe('event2');
|
||||||
expect(newState.timer.playback).toBe(Playback.Armed);
|
expect(newState.timer.playback).toBe(Playback.Armed);
|
||||||
expect(newState.clock).not.toBe(666);
|
expect(newState.clock).not.toBe(666);
|
||||||
expect(newState.blockNow).toBeNull();
|
expect(newState.groupNow).toBeNull();
|
||||||
|
|
||||||
// 2. Start event
|
// 2. Start event
|
||||||
let success = start();
|
let success = start();
|
||||||
@@ -185,7 +185,7 @@ describe('mutation on runtimeState', () => {
|
|||||||
expect(newState.runtime.actualStart).toBeNull();
|
expect(newState.runtime.actualStart).toBeNull();
|
||||||
expect(newState.runtime.plannedStart).toBe(0);
|
expect(newState.runtime.plannedStart).toBe(0);
|
||||||
expect(newState.runtime.plannedEnd).toBe(1500);
|
expect(newState.runtime.plannedEnd).toBe(1500);
|
||||||
expect(newState.blockNow).toBeNull();
|
expect(newState.groupNow).toBeNull();
|
||||||
expect(newState.runtime.offsetAbs).toBe(0);
|
expect(newState.runtime.offsetAbs).toBe(0);
|
||||||
|
|
||||||
// 2. Start event
|
// 2. Start event
|
||||||
@@ -218,7 +218,7 @@ describe('mutation on runtimeState', () => {
|
|||||||
expect(newState.runtime.offsetAbs).toBe(delayBefore);
|
expect(newState.runtime.offsetAbs).toBe(delayBefore);
|
||||||
// finish is the difference between the runtime and the schedule
|
// finish is the difference between the runtime and the schedule
|
||||||
expect(newState.runtime.expectedEnd).toBe(entries.event2.timeEnd - newState.runtime.offsetAbs);
|
expect(newState.runtime.expectedEnd).toBe(entries.event2.timeEnd - newState.runtime.offsetAbs);
|
||||||
expect(newState.blockNow).toBeNull();
|
expect(newState.groupNow).toBeNull();
|
||||||
|
|
||||||
// 4. Add time
|
// 4. Add time
|
||||||
addTime(10);
|
addTime(10);
|
||||||
@@ -371,75 +371,75 @@ describe('roll mode', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('loadBlock', () => {
|
describe('loadGroup', () => {
|
||||||
test('from no-block to a block will clear startedAt', () => {
|
test('from no-group to a group will clear startedAt', () => {
|
||||||
const rundown = makeRundown({
|
const rundown = makeRundown({
|
||||||
entries: {
|
entries: {
|
||||||
0: makeOntimeEvent({ id: '0', parent: null }),
|
0: makeOntimeEvent({ id: '0', parent: null }),
|
||||||
1: makeOntimeBlock({ id: '1', entries: ['11'] }),
|
1: makeOntimeGroup({ id: '1', entries: ['11'] }),
|
||||||
11: makeOntimeEvent({ id: '11', parent: '1' }),
|
11: makeOntimeEvent({ id: '11', parent: '1' }),
|
||||||
2: makeOntimeBlock({ id: '2', entries: [] }),
|
2: makeOntimeGroup({ id: '2', entries: [] }),
|
||||||
3: makeOntimeEvent({ id: '3', parent: null }),
|
3: makeOntimeEvent({ id: '3', parent: null }),
|
||||||
},
|
},
|
||||||
order: ['0', '1', '2', '3'],
|
order: ['0', '1', '2', '3'],
|
||||||
});
|
});
|
||||||
|
|
||||||
const state = {
|
const state = {
|
||||||
blockNow: null,
|
groupNow: null,
|
||||||
eventNow: rundown.entries[11],
|
eventNow: rundown.entries[11],
|
||||||
} as unknown as RuntimeState;
|
} as RuntimeState;
|
||||||
|
|
||||||
const metadata = { playableEventOrder: ['0', '11', '3'], flags: ['1'] } as RundownMetadata;
|
const metadata = { playableEventOrder: ['0', '11', '3'], flags: ['1'] } as RundownMetadata;
|
||||||
|
|
||||||
loadBlockFlagAndEnd(rundown, metadata, 2, state);
|
loadGroupFlagAndEnd(rundown, metadata, 2, state);
|
||||||
|
|
||||||
expect(state).toMatchObject({
|
expect(state).toMatchObject({
|
||||||
blockNow: { id: rundown.entries[1].id, startedAt: null },
|
groupNow: { id: rundown.entries[1].id, startedAt: null },
|
||||||
eventNow: rundown.entries[11],
|
eventNow: rundown.entries[11],
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test('from block to a different block will clear startedAt', () => {
|
test('from a group to a different group will clear startedAt', () => {
|
||||||
const rundown = makeRundown({
|
const rundown = makeRundown({
|
||||||
entries: {
|
entries: {
|
||||||
0: makeOntimeEvent({ id: '0', parent: null }),
|
0: makeOntimeEvent({ id: '0', parent: null }),
|
||||||
1: makeOntimeBlock({ id: '1', entries: ['11'] }),
|
1: makeOntimeGroup({ id: '1', entries: ['11'] }),
|
||||||
11: makeOntimeEvent({ id: '11', parent: '1' }),
|
11: makeOntimeEvent({ id: '11', parent: '1' }),
|
||||||
2: makeOntimeBlock({ id: '2', entries: ['22'] }),
|
2: makeOntimeGroup({ id: '2', entries: ['22'] }),
|
||||||
22: makeOntimeEvent({ id: '22', parent: '2' }),
|
22: makeOntimeEvent({ id: '22', parent: '2' }),
|
||||||
},
|
},
|
||||||
order: ['0', '1', '2'],
|
order: ['0', '1', '2'],
|
||||||
});
|
});
|
||||||
|
|
||||||
const state = {
|
const state = {
|
||||||
blockNow: { id: rundown.entries[1].id, startedAt: 123 },
|
groupNow: { id: rundown.entries[1].id, startedAt: 123 },
|
||||||
eventNow: rundown.entries[22],
|
eventNow: rundown.entries[22],
|
||||||
} as RuntimeState;
|
} as RuntimeState;
|
||||||
|
|
||||||
const metadata = { playableEventOrder: ['0', '11', '22'], flags: ['1'] } as RundownMetadata;
|
const metadata = { playableEventOrder: ['0', '11', '22'], flags: ['1'] } as RundownMetadata;
|
||||||
|
|
||||||
loadBlockFlagAndEnd(rundown, metadata, 1, state);
|
loadGroupFlagAndEnd(rundown, metadata, 1, state);
|
||||||
|
|
||||||
expect(state).toMatchObject({
|
expect(state).toMatchObject({
|
||||||
blockNow: { id: rundown.entries[2].id, startedAt: null },
|
groupNow: { id: rundown.entries[2].id, startedAt: null },
|
||||||
eventNow: rundown.entries[22],
|
eventNow: rundown.entries[22],
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test('from block to a no-block will clear startedAt', () => {
|
test('from group to a no-group will clear startedAt', () => {
|
||||||
const rundown = makeRundown({
|
const rundown = makeRundown({
|
||||||
entries: {
|
entries: {
|
||||||
0: makeOntimeEvent({ id: '0', parent: null }),
|
0: makeOntimeEvent({ id: '0', parent: null }),
|
||||||
1: makeOntimeBlock({ id: '1', entries: ['11'] }),
|
1: makeOntimeGroup({ id: '1', entries: ['11'] }),
|
||||||
11: makeOntimeEvent({ id: '11', parent: '1' }),
|
11: makeOntimeEvent({ id: '11', parent: '1' }),
|
||||||
2: makeOntimeBlock({ id: '2', entries: ['22'] }),
|
2: makeOntimeGroup({ id: '2', entries: ['22'] }),
|
||||||
22: makeOntimeEvent({ id: '22', parent: '2' }),
|
22: makeOntimeEvent({ id: '22', parent: '2' }),
|
||||||
},
|
},
|
||||||
order: ['0', '1', '2'],
|
order: ['0', '1', '2'],
|
||||||
});
|
});
|
||||||
|
|
||||||
const state = {
|
const state = {
|
||||||
blockNow: {
|
groupNow: {
|
||||||
id: rundown.entries[1].id,
|
id: rundown.entries[1].id,
|
||||||
startedAt: 123,
|
startedAt: 123,
|
||||||
},
|
},
|
||||||
@@ -448,18 +448,18 @@ describe('loadBlock', () => {
|
|||||||
|
|
||||||
const metadata = { playableEventOrder: ['0', '11', '22'], flags: ['1'] } as RundownMetadata;
|
const metadata = { playableEventOrder: ['0', '11', '22'], flags: ['1'] } as RundownMetadata;
|
||||||
|
|
||||||
loadBlockFlagAndEnd(rundown, metadata, 1, state);
|
loadGroupFlagAndEnd(rundown, metadata, 1, state);
|
||||||
|
|
||||||
expect(state).toMatchObject({
|
expect(state).toMatchObject({
|
||||||
blockNow: null,
|
groupNow: null,
|
||||||
eventNow: rundown.entries[0],
|
eventNow: rundown.entries[0],
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test('from block to same block will keep startedAt', () => {
|
test('from a group to same group will keep startedAt', () => {
|
||||||
const rundown = makeRundown({
|
const rundown = makeRundown({
|
||||||
entries: {
|
entries: {
|
||||||
0: makeOntimeBlock({ id: '0', entries: ['1', '2'] }),
|
0: makeOntimeGroup({ id: '0', entries: ['1', '2'] }),
|
||||||
1: makeOntimeEvent({ id: '1', parent: '0' }),
|
1: makeOntimeEvent({ id: '1', parent: '0' }),
|
||||||
2: makeOntimeEvent({ id: '2', parent: '0' }),
|
2: makeOntimeEvent({ id: '2', parent: '0' }),
|
||||||
},
|
},
|
||||||
@@ -467,21 +467,21 @@ describe('loadBlock', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const state = {
|
const state = {
|
||||||
blockNow: { id: rundown.entries[0].id, startedAt: 123 },
|
groupNow: { id: rundown.entries[0].id, startedAt: 123 },
|
||||||
eventNow: rundown.entries[2],
|
eventNow: rundown.entries[2],
|
||||||
} as RuntimeState;
|
} as RuntimeState;
|
||||||
|
|
||||||
const metadata = { playableEventOrder: ['1', '2'], flags: ['1'] } as RundownMetadata;
|
const metadata = { playableEventOrder: ['1', '2'], flags: ['1'] } as RundownMetadata;
|
||||||
|
|
||||||
loadBlockFlagAndEnd(rundown, metadata, 0, state);
|
loadGroupFlagAndEnd(rundown, metadata, 0, state);
|
||||||
|
|
||||||
expect(state).toMatchObject({
|
expect(state).toMatchObject({
|
||||||
blockNow: { id: rundown.entries[0].id, startedAt: 123 },
|
groupNow: { id: rundown.entries[0].id, startedAt: 123 },
|
||||||
eventNow: rundown.entries[2],
|
eventNow: rundown.entries[2],
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test('from no-block to no-block will keep startedAt', () => {
|
test('from no-group to no-group will keep startedAt', () => {
|
||||||
const rundown = makeRundown({
|
const rundown = makeRundown({
|
||||||
entries: {
|
entries: {
|
||||||
0: makeOntimeEvent({ id: '0', parent: null }),
|
0: makeOntimeEvent({ id: '0', parent: null }),
|
||||||
@@ -491,16 +491,16 @@ describe('loadBlock', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const state = {
|
const state = {
|
||||||
blockNow: null,
|
groupNow: null,
|
||||||
eventNow: rundown.entries[0],
|
eventNow: rundown.entries[0],
|
||||||
} as RuntimeState;
|
} as RuntimeState;
|
||||||
|
|
||||||
const metadata = { playableEventOrder: ['0', '1'], flags: ['1'] } as RundownMetadata;
|
const metadata = { playableEventOrder: ['0', '1'], flags: ['1'] } as RundownMetadata;
|
||||||
|
|
||||||
loadBlockFlagAndEnd(rundown, metadata, 0, state);
|
loadGroupFlagAndEnd(rundown, metadata, 0, state);
|
||||||
|
|
||||||
expect(state).toMatchObject({
|
expect(state).toMatchObject({
|
||||||
blockNow: null,
|
groupNow: null,
|
||||||
eventNow: rundown.entries[0],
|
eventNow: rundown.entries[0],
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
import {
|
import {
|
||||||
CurrentBlockState,
|
CurrentGroupState,
|
||||||
EntryMetaData,
|
EntryMetaData,
|
||||||
isOntimeEvent,
|
isOntimeEvent,
|
||||||
MaybeNumber,
|
MaybeNumber,
|
||||||
MaybeString,
|
MaybeString,
|
||||||
OffsetMode,
|
OffsetMode,
|
||||||
OntimeBlock,
|
OntimeGroup,
|
||||||
OntimeEvent,
|
OntimeEvent,
|
||||||
PlayableEvent,
|
PlayableEvent,
|
||||||
Playback,
|
Playback,
|
||||||
@@ -36,8 +36,8 @@ type ExpectedMetadata = { event: OntimeEvent; accumulatedGap: number; isLinkedTo
|
|||||||
|
|
||||||
export type RuntimeState = {
|
export type RuntimeState = {
|
||||||
clock: number; // realtime clock
|
clock: number; // realtime clock
|
||||||
blockNow: CurrentBlockState | null;
|
groupNow: CurrentGroupState | null;
|
||||||
blockNext: MaybeString;
|
groupNext: MaybeString;
|
||||||
nextFlag: EntryMetaData | null;
|
nextFlag: EntryMetaData | null;
|
||||||
eventNow: PlayableEvent | null;
|
eventNow: PlayableEvent | null;
|
||||||
eventNext: PlayableEvent | null;
|
eventNext: PlayableEvent | null;
|
||||||
@@ -53,15 +53,15 @@ export type RuntimeState = {
|
|||||||
_rundown: {
|
_rundown: {
|
||||||
totalDelay: number; // this value comes from rundown service
|
totalDelay: number; // this value comes from rundown service
|
||||||
};
|
};
|
||||||
_block: ExpectedMetadata;
|
_group: ExpectedMetadata;
|
||||||
_flag: ExpectedMetadata;
|
_flag: ExpectedMetadata;
|
||||||
_end: ExpectedMetadata;
|
_end: ExpectedMetadata;
|
||||||
};
|
};
|
||||||
|
|
||||||
const runtimeState: RuntimeState = {
|
const runtimeState: RuntimeState = {
|
||||||
clock: timeNow(),
|
clock: timeNow(),
|
||||||
blockNow: null,
|
groupNow: null,
|
||||||
blockNext: null,
|
groupNext: null,
|
||||||
nextFlag: null,
|
nextFlag: null,
|
||||||
eventNow: null,
|
eventNow: null,
|
||||||
eventNext: null,
|
eventNext: null,
|
||||||
@@ -76,7 +76,7 @@ const runtimeState: RuntimeState = {
|
|||||||
_rundown: {
|
_rundown: {
|
||||||
totalDelay: 0,
|
totalDelay: 0,
|
||||||
},
|
},
|
||||||
_block: null,
|
_group: null,
|
||||||
_flag: null,
|
_flag: null,
|
||||||
_end: null,
|
_end: null,
|
||||||
};
|
};
|
||||||
@@ -107,7 +107,7 @@ export function clearEventData() {
|
|||||||
runtimeState.runtime.selectedEventIndex = null;
|
runtimeState.runtime.selectedEventIndex = null;
|
||||||
|
|
||||||
//TODO: is there any ExpectedMetadata stuff we need to clear here
|
//TODO: is there any ExpectedMetadata stuff we need to clear here
|
||||||
if (runtimeState.blockNow) runtimeState.blockNow.expectedEnd = null;
|
if (runtimeState.groupNow) runtimeState.groupNow.expectedEnd = null;
|
||||||
|
|
||||||
runtimeState.timer.playback = Playback.Stop;
|
runtimeState.timer.playback = Playback.Stop;
|
||||||
runtimeState.clock = timeNow();
|
runtimeState.clock = timeNow();
|
||||||
@@ -125,9 +125,9 @@ export function clearState() {
|
|||||||
runtimeState.eventNow = null;
|
runtimeState.eventNow = null;
|
||||||
runtimeState.eventNext = null;
|
runtimeState.eventNext = null;
|
||||||
|
|
||||||
runtimeState.blockNow = null;
|
runtimeState.groupNow = null;
|
||||||
runtimeState.blockNext = null;
|
runtimeState.groupNext = null;
|
||||||
runtimeState._block = null;
|
runtimeState._group = null;
|
||||||
|
|
||||||
runtimeState.nextFlag = null;
|
runtimeState.nextFlag = null;
|
||||||
runtimeState._flag = null;
|
runtimeState._flag = null;
|
||||||
@@ -216,7 +216,7 @@ export function load(
|
|||||||
// load events in memory along with their data
|
// load events in memory along with their data
|
||||||
loadNow(rundown, metadata, eventIndex);
|
loadNow(rundown, metadata, eventIndex);
|
||||||
loadNext(rundown, metadata, eventIndex);
|
loadNext(rundown, metadata, eventIndex);
|
||||||
loadBlockFlagAndEnd(rundown, metadata, eventIndex);
|
loadGroupFlagAndEnd(rundown, metadata, eventIndex);
|
||||||
|
|
||||||
// update state
|
// update state
|
||||||
runtimeState.timer.playback = Playback.Armed;
|
runtimeState.timer.playback = Playback.Armed;
|
||||||
@@ -235,8 +235,8 @@ export function load(
|
|||||||
runtimeState.runtime.offsetRel = offsetRel;
|
runtimeState.runtime.offsetRel = offsetRel;
|
||||||
getExpectedTimes();
|
getExpectedTimes();
|
||||||
}
|
}
|
||||||
if (typeof initialData.blockStartAt === 'number' && runtimeState.blockNow) {
|
if (typeof initialData.groupStartAt === 'number' && runtimeState.groupNow) {
|
||||||
runtimeState.blockNow.startedAt = initialData.blockStartAt;
|
runtimeState.groupNow.startedAt = initialData.groupStartAt;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return event.id === runtimeState.eventNow?.id;
|
return event.id === runtimeState.eventNow?.id;
|
||||||
@@ -357,7 +357,7 @@ export function updateAll(rundown: Rundown, metadata: RundownMetadata) {
|
|||||||
loadNow(rundown, metadata, eventNowIndex >= 0 ? eventNowIndex : undefined);
|
loadNow(rundown, metadata, eventNowIndex >= 0 ? eventNowIndex : undefined);
|
||||||
loadNext(rundown, metadata, eventNowIndex >= 0 ? eventNowIndex : undefined);
|
loadNext(rundown, metadata, eventNowIndex >= 0 ? eventNowIndex : undefined);
|
||||||
updateLoaded(runtimeState.eventNow ?? undefined);
|
updateLoaded(runtimeState.eventNow ?? undefined);
|
||||||
loadBlockFlagAndEnd(rundown, metadata, eventNowIndex);
|
loadGroupFlagAndEnd(rundown, metadata, eventNowIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function start(state: RuntimeState = runtimeState): boolean {
|
export function start(state: RuntimeState = runtimeState): boolean {
|
||||||
@@ -382,9 +382,9 @@ export function start(state: RuntimeState = runtimeState): boolean {
|
|||||||
state.timer.startedAt = state.clock;
|
state.timer.startedAt = state.clock;
|
||||||
}
|
}
|
||||||
|
|
||||||
// update block start time
|
// update group start time
|
||||||
if (state.blockNow && state.blockNow.startedAt === null) {
|
if (state.groupNow && state.groupNow.startedAt === null) {
|
||||||
state.blockNow.startedAt = state.clock;
|
state.groupNow.startedAt = state.clock;
|
||||||
}
|
}
|
||||||
|
|
||||||
state.timer.playback = Playback.Play;
|
state.timer.playback = Playback.Play;
|
||||||
@@ -609,8 +609,8 @@ export function roll(
|
|||||||
runtimeState.timer.startedAt = runtimeState.clock;
|
runtimeState.timer.startedAt = runtimeState.clock;
|
||||||
|
|
||||||
// update runtime
|
// update runtime
|
||||||
if (runtimeState.blockNow && runtimeState.blockNow.startedAt === null) {
|
if (runtimeState.groupNow && runtimeState.groupNow.startedAt === null) {
|
||||||
runtimeState.blockNow.startedAt = runtimeState.clock;
|
runtimeState.groupNow.startedAt = runtimeState.clock;
|
||||||
}
|
}
|
||||||
if (!runtimeState.runtime.actualStart) {
|
if (!runtimeState.runtime.actualStart) {
|
||||||
runtimeState.runtime.actualStart = runtimeState.clock;
|
runtimeState.runtime.actualStart = runtimeState.clock;
|
||||||
@@ -630,7 +630,7 @@ export function roll(
|
|||||||
throw new Error('No playable events found');
|
throw new Error('No playable events found');
|
||||||
}
|
}
|
||||||
|
|
||||||
// we need to persist the current block state across loads
|
// we need to persist the current group state across loads
|
||||||
clearEventData();
|
clearEventData();
|
||||||
|
|
||||||
//account for offset but we only keep it if passed to us
|
//account for offset but we only keep it if passed to us
|
||||||
@@ -642,7 +642,7 @@ export function roll(
|
|||||||
// load events in memory along with their data
|
// load events in memory along with their data
|
||||||
loadNow(rundown, metadata, index);
|
loadNow(rundown, metadata, index);
|
||||||
loadNext(rundown, metadata, index);
|
loadNext(rundown, metadata, index);
|
||||||
loadBlockFlagAndEnd(rundown, metadata, index);
|
loadGroupFlagAndEnd(rundown, metadata, index);
|
||||||
|
|
||||||
// update roll state
|
// update roll state
|
||||||
runtimeState.timer.playback = Playback.Roll;
|
runtimeState.timer.playback = Playback.Roll;
|
||||||
@@ -673,8 +673,8 @@ export function roll(
|
|||||||
// there is something to run, load event
|
// there is something to run, load event
|
||||||
|
|
||||||
// update runtime
|
// update runtime
|
||||||
if (runtimeState.blockNow && runtimeState.blockNow.startedAt === null) {
|
if (runtimeState.groupNow && runtimeState.groupNow.startedAt === null) {
|
||||||
runtimeState.blockNow.startedAt = runtimeState.clock;
|
runtimeState.groupNow.startedAt = runtimeState.clock;
|
||||||
}
|
}
|
||||||
|
|
||||||
// event will finish on time
|
// event will finish on time
|
||||||
@@ -702,7 +702,7 @@ export function roll(
|
|||||||
/**
|
/**
|
||||||
* calculates and sets values directly in state
|
* calculates and sets values directly in state
|
||||||
* - runtime.expectedEnd
|
* - runtime.expectedEnd
|
||||||
* - blockNow.expectedEnd
|
* - groupNow.expectedEnd
|
||||||
* - nextFlag.expectedStart
|
* - nextFlag.expectedStart
|
||||||
*/
|
*/
|
||||||
function getExpectedTimes(state = runtimeState) {
|
function getExpectedTimes(state = runtimeState) {
|
||||||
@@ -712,11 +712,11 @@ function getExpectedTimes(state = runtimeState) {
|
|||||||
if (!eventNow) return;
|
if (!eventNow) return;
|
||||||
|
|
||||||
state.runtime.expectedEnd = null;
|
state.runtime.expectedEnd = null;
|
||||||
if (state.blockNow) {
|
if (state.groupNow) {
|
||||||
state.blockNow.expectedEnd = null;
|
state.groupNow.expectedEnd = null;
|
||||||
const { _block } = state;
|
const { _group } = state;
|
||||||
if (state.blockNow.startedAt !== null && _block !== null) {
|
if (state.groupNow.startedAt !== null && _group !== null) {
|
||||||
const { event, accumulatedGap, isLinkedToLoaded } = _block;
|
const { event, accumulatedGap, isLinkedToLoaded } = _group;
|
||||||
const expectedStart = getExpectedStart(event, {
|
const expectedStart = getExpectedStart(event, {
|
||||||
currentDay: eventNow.dayOffset,
|
currentDay: eventNow.dayOffset,
|
||||||
totalGap: accumulatedGap,
|
totalGap: accumulatedGap,
|
||||||
@@ -726,7 +726,7 @@ function getExpectedTimes(state = runtimeState) {
|
|||||||
plannedStart,
|
plannedStart,
|
||||||
actualStart,
|
actualStart,
|
||||||
});
|
});
|
||||||
state.blockNow.expectedEnd = expectedStart + event.duration;
|
state.groupNow.expectedEnd = expectedStart + event.duration;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -765,7 +765,7 @@ function getExpectedTimes(state = runtimeState) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function loadBlockFlagAndEnd(
|
export function loadGroupFlagAndEnd(
|
||||||
rundown: Rundown,
|
rundown: Rundown,
|
||||||
metadata: RundownMetadata,
|
metadata: RundownMetadata,
|
||||||
currentIndex: MaybeNumber,
|
currentIndex: MaybeNumber,
|
||||||
@@ -774,14 +774,14 @@ export function loadBlockFlagAndEnd(
|
|||||||
if (currentIndex === null) return resetMetaData();
|
if (currentIndex === null) return resetMetaData();
|
||||||
if (state.eventNow === null) return resetMetaData();
|
if (state.eventNow === null) return resetMetaData();
|
||||||
|
|
||||||
const currentBlockId = state.eventNow.parent;
|
const currentGroupId = state.eventNow.parent;
|
||||||
const flagsPresent = metadata.flags.length !== 0;
|
const flagsPresent = metadata.flags.length !== 0;
|
||||||
|
|
||||||
const { playableEventOrder } = metadata;
|
const { playableEventOrder } = metadata;
|
||||||
const { entries } = rundown;
|
const { entries } = rundown;
|
||||||
|
|
||||||
const orderInBlock = currentBlockId ? (entries[currentBlockId] as OntimeBlock).entries : null;
|
const orderInGroup = currentGroupId ? (entries[currentGroupId] as OntimeGroup).entries : null;
|
||||||
const lastEventInGroup = orderInBlock ? getLastEventNormal(rundown.entries, orderInBlock).lastEvent : null;
|
const lastEventInGroup = orderInGroup ? getLastEventNormal(rundown.entries, orderInGroup).lastEvent : null;
|
||||||
|
|
||||||
// if we don't have a any flags in the rundown then no need to look for it
|
// if we don't have a any flags in the rundown then no need to look for it
|
||||||
let foundFlag = !flagsPresent;
|
let foundFlag = !flagsPresent;
|
||||||
@@ -811,12 +811,12 @@ export function loadBlockFlagAndEnd(
|
|||||||
|
|
||||||
if (!foundGroupEnd && entry.id === lastEventInGroup?.id) {
|
if (!foundGroupEnd && entry.id === lastEventInGroup?.id) {
|
||||||
foundGroupEnd = true;
|
foundGroupEnd = true;
|
||||||
state._block = { event: lastEventInGroup, isLinkedToLoaded, accumulatedGap };
|
state._group = { event: lastEventInGroup, isLinkedToLoaded, accumulatedGap };
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!foundNextGroup && entry.parent !== currentBlockId) {
|
if (!foundNextGroup && entry.parent !== currentGroupId) {
|
||||||
foundNextGroup = true;
|
foundNextGroup = true;
|
||||||
state.blockNext = entry.parent;
|
state.groupNext = entry.parent;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -829,19 +829,19 @@ export function loadBlockFlagAndEnd(
|
|||||||
|
|
||||||
if (!foundFlag) state.nextFlag = null;
|
if (!foundFlag) state.nextFlag = null;
|
||||||
|
|
||||||
if (currentBlockId === null) {
|
if (currentGroupId === null) {
|
||||||
state.blockNow = null;
|
state.groupNow = null;
|
||||||
} else if ((state.blockNow != null && state.blockNow.id != currentBlockId) || state.blockNow == null) {
|
} else if ((state.groupNow != null && state.groupNow.id != currentGroupId) || state.groupNow == null) {
|
||||||
// we went into a new block - and it is different from the one we might have come from
|
// we went into a new group - and it is different from the one we might have come from
|
||||||
// the id is set here, the start time is set when starting events
|
// the id is set here, the start time is set when starting events
|
||||||
state.blockNow = { id: currentBlockId, startedAt: null, expectedEnd: null };
|
state.groupNow = { id: currentGroupId, startedAt: null, expectedEnd: null };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const resetMetaData = (state = runtimeState) => {
|
const resetMetaData = (state = runtimeState) => {
|
||||||
state.blockNow = null;
|
state.groupNow = null;
|
||||||
state.blockNext = null;
|
state.groupNext = null;
|
||||||
state._block = null;
|
state._group = null;
|
||||||
state.nextFlag = null;
|
state.nextFlag = null;
|
||||||
state._flag = null;
|
state._flag = null;
|
||||||
state._end = null;
|
state._end = null;
|
||||||
|
|||||||
@@ -163,7 +163,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"01e85": {
|
"01e85": {
|
||||||
"type": "block",
|
"type": "group",
|
||||||
"id": "01e85",
|
"id": "01e85",
|
||||||
"title": "Lunch break",
|
"title": "Lunch break",
|
||||||
"note": "",
|
"note": "",
|
||||||
@@ -318,7 +318,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"cb90b": {
|
"cb90b": {
|
||||||
"type": "block",
|
"type": "group",
|
||||||
"id": "cb90b",
|
"id": "cb90b",
|
||||||
"title": "Afternoon break",
|
"title": "Afternoon break",
|
||||||
"note": "",
|
"note": "",
|
||||||
|
|||||||
@@ -34,13 +34,13 @@ test('project file upload', async ({ page }) => {
|
|||||||
await page.getByRole('button', { name: 'close' }).click();
|
await page.getByRole('button', { name: 'close' }).click();
|
||||||
|
|
||||||
// asset test events
|
// asset test events
|
||||||
const firstTitle = page.getByTestId('entry-1').getByTestId('block__title');
|
const firstTitle = page.getByTestId('entry-1').getByTestId('entry__title');
|
||||||
await expect(firstTitle).toHaveValue('Albania');
|
await expect(firstTitle).toHaveValue('Albania');
|
||||||
|
|
||||||
const secondTitle = page.getByTestId('entry-2').getByTestId('block__title');
|
const secondTitle = page.getByTestId('entry-2').getByTestId('entry__title');
|
||||||
await expect(secondTitle).toHaveValue('Latvia');
|
await expect(secondTitle).toHaveValue('Latvia');
|
||||||
|
|
||||||
const thirdTitle = page.getByTestId('entry-3').getByTestId('block__title');
|
const thirdTitle = page.getByTestId('entry-3').getByTestId('entry__title');
|
||||||
await expect(thirdTitle).toHaveValue('Lithuania');
|
await expect(thirdTitle).toHaveValue('Lithuania');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -10,5 +10,5 @@ test('cuesheet displays events', async ({ page }) => {
|
|||||||
|
|
||||||
// there should be 16 rows in the table (same as the amount of events in the rundown)
|
// there should be 16 rows in the table (same as the amount of events in the rundown)
|
||||||
await expect(page.getByTestId('cuesheet-event')).toHaveCount(14);
|
await expect(page.getByTestId('cuesheet-event')).toHaveCount(14);
|
||||||
await expect(page.getByTestId('cuesheet-block')).toHaveCount(2);
|
await expect(page.getByTestId('cuesheet-group')).toHaveCount(2);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { expect, test } from '@playwright/test';
|
import { expect, test } from '@playwright/test';
|
||||||
|
|
||||||
test('delay blocks add time to events', async ({ page }) => {
|
test('delays add time to events', async ({ page }) => {
|
||||||
await page.goto('http://localhost:4001/editor');
|
await page.goto('http://localhost:4001/editor');
|
||||||
|
|
||||||
// delete all events and add a new one
|
// delete all events and add a new one
|
||||||
@@ -17,7 +17,7 @@ test('delay blocks add time to events', async ({ page }) => {
|
|||||||
await page.getByTestId('rundown').getByPlaceholder('Duration').fill('20m');
|
await page.getByTestId('rundown').getByPlaceholder('Duration').fill('20m');
|
||||||
await page.getByTestId('rundown').getByPlaceholder('Duration').press('Enter');
|
await page.getByTestId('rundown').getByPlaceholder('Duration').press('Enter');
|
||||||
|
|
||||||
// add delay block
|
// add delay
|
||||||
await page.getByRole('button', { name: 'Delay' }).nth(0).click();
|
await page.getByRole('button', { name: 'Delay' }).nth(0).click();
|
||||||
|
|
||||||
// fill positive delay
|
// fill positive delay
|
||||||
@@ -64,10 +64,10 @@ test('delays are show correctly', async ({ page }) => {
|
|||||||
await page.getByTestId('rundown').getByTestId('time-input-duration').click();
|
await page.getByTestId('rundown').getByTestId('time-input-duration').click();
|
||||||
await page.getByTestId('rundown').getByTestId('time-input-duration').fill('10');
|
await page.getByTestId('rundown').getByTestId('time-input-duration').fill('10');
|
||||||
await page.getByTestId('rundown').getByTestId('time-input-duration').press('Enter');
|
await page.getByTestId('rundown').getByTestId('time-input-duration').press('Enter');
|
||||||
await page.getByTestId('block__title').click();
|
await page.getByTestId('entry__title').click();
|
||||||
await page.getByTestId('block__title').fill('test');
|
await page.getByTestId('entry__title').fill('test');
|
||||||
await page.getByTestId('block__title').press('Enter');
|
await page.getByTestId('entry__title').press('Enter');
|
||||||
await expect(page.getByTestId('entry-1').locator('#block-status')).toHaveAttribute('data-timerType', 'count-down');
|
await expect(page.getByTestId('entry-1').locator('#entry-status')).toHaveAttribute('data-timerType', 'count-down');
|
||||||
|
|
||||||
// add a delay
|
// add a delay
|
||||||
await page.getByRole('button', { name: 'Delay' }).nth(0).click();
|
await page.getByRole('button', { name: 'Delay' }).nth(0).click();
|
||||||
|
|||||||
@@ -8,13 +8,13 @@ test('CRUD operations on the rundown', async ({ page }) => {
|
|||||||
await page.getByRole('button', { name: 'Delete all' }).click();
|
await page.getByRole('button', { name: 'Delete all' }).click();
|
||||||
await expect(page.getByTestId('rundown-event')).toHaveCount(0);
|
await expect(page.getByTestId('rundown-event')).toHaveCount(0);
|
||||||
await expect(page.getByTestId('rundown-delay')).toHaveCount(0);
|
await expect(page.getByTestId('rundown-delay')).toHaveCount(0);
|
||||||
await expect(page.getByTestId('rundown-block')).toHaveCount(0);
|
await expect(page.getByTestId('rundown-group')).toHaveCount(0);
|
||||||
|
|
||||||
// create event from the rundown empty button
|
// create event from the rundown empty button
|
||||||
await page.getByRole('button', { name: 'Create Event' }).click();
|
await page.getByRole('button', { name: 'Create Event' }).click();
|
||||||
await expect(page.getByTestId('rundown-event')).toHaveCount(1);
|
await expect(page.getByTestId('rundown-event')).toHaveCount(1);
|
||||||
await expect(page.getByTestId('rundown-delay')).toHaveCount(0);
|
await expect(page.getByTestId('rundown-delay')).toHaveCount(0);
|
||||||
await expect(page.getByTestId('rundown-block')).toHaveCount(0);
|
await expect(page.getByTestId('rundown-group')).toHaveCount(0);
|
||||||
|
|
||||||
// create groups using the quick add buttons
|
// create groups using the quick add buttons
|
||||||
await page.getByTestId('rundown').getByRole('button', { name: 'Group' }).nth(1).click();
|
await page.getByTestId('rundown').getByRole('button', { name: 'Group' }).nth(1).click();
|
||||||
@@ -22,7 +22,7 @@ test('CRUD operations on the rundown', async ({ page }) => {
|
|||||||
await page.getByRole('button', { name: 'Event', exact: true }).nth(1).click();
|
await page.getByRole('button', { name: 'Event', exact: true }).nth(1).click();
|
||||||
await expect(page.getByTestId('rundown-event')).toHaveCount(2);
|
await expect(page.getByTestId('rundown-event')).toHaveCount(2);
|
||||||
await expect(page.getByTestId('rundown-delay')).toHaveCount(1);
|
await expect(page.getByTestId('rundown-delay')).toHaveCount(1);
|
||||||
await expect(page.getByTestId('rundown-block')).toHaveCount(1);
|
await expect(page.getByTestId('rundown-group')).toHaveCount(1);
|
||||||
|
|
||||||
// test quick add options - star2+5-t is last end
|
// test quick add options - star2+5-t is last end
|
||||||
await page.getByTestId('entry-2').getByTestId('time-input-duration').fill('20m');
|
await page.getByTestId('entry-2').getByTestId('time-input-duration').fill('20m');
|
||||||
@@ -30,13 +30,13 @@ test('CRUD operations on the rundown', async ({ page }) => {
|
|||||||
expect(await page.getByTestId('entry-3').getByTestId('time-input-timeStart').inputValue()).toContain('00:30:00');
|
expect(await page.getByTestId('entry-3').getByTestId('time-input-timeStart').inputValue()).toContain('00:30:00');
|
||||||
await expect(page.getByTestId('rundown-event')).toHaveCount(3);
|
await expect(page.getByTestId('rundown-event')).toHaveCount(3);
|
||||||
await expect(page.getByTestId('rundown-delay')).toHaveCount(1);
|
await expect(page.getByTestId('rundown-delay')).toHaveCount(1);
|
||||||
await expect(page.getByTestId('rundown-block')).toHaveCount(1);
|
await expect(page.getByTestId('rundown-group')).toHaveCount(1);
|
||||||
|
|
||||||
// test quick add options
|
// test quick add options
|
||||||
await page.getByRole('button', { name: 'Event', exact: true }).nth(1).click();
|
await page.getByRole('button', { name: 'Event', exact: true }).nth(1).click();
|
||||||
await expect(page.getByTestId('rundown-event')).toHaveCount(4);
|
await expect(page.getByTestId('rundown-event')).toHaveCount(4);
|
||||||
await expect(page.getByTestId('rundown-delay')).toHaveCount(1);
|
await expect(page.getByTestId('rundown-delay')).toHaveCount(1);
|
||||||
await expect(page.getByTestId('rundown-block')).toHaveCount(1);
|
await expect(page.getByTestId('rundown-group')).toHaveCount(1);
|
||||||
|
|
||||||
await expect(page.getByTestId('entry-4').locator('#block-status')).toHaveAttribute('data-timerType', 'count-down');
|
await expect(page.getByTestId('entry-4').locator('#entry-status')).toHaveAttribute('data-timerType', 'count-down');
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -30,23 +30,23 @@ test('smoke test operator', async ({ page }) => {
|
|||||||
await page.getByTestId('entry-1').click();
|
await page.getByTestId('entry-1').click();
|
||||||
await page.getByRole('button', { name: 'Event', exact: true }).nth(1).click();
|
await page.getByRole('button', { name: 'Event', exact: true }).nth(1).click();
|
||||||
|
|
||||||
await page.getByTestId('entry-1').getByTestId('block__title').click();
|
await page.getByTestId('entry-1').getByTestId('entry__title').click();
|
||||||
await page.getByTestId('entry-1').getByTestId('block__title').fill('title 1');
|
await page.getByTestId('entry-1').getByTestId('entry__title').fill('title 1');
|
||||||
await page.getByTestId('entry-1').getByTestId('block__title').press('Enter');
|
await page.getByTestId('entry-1').getByTestId('entry__title').press('Enter');
|
||||||
|
|
||||||
await page.getByTestId('entry-2').click();
|
await page.getByTestId('entry-2').click();
|
||||||
await page.getByRole('button', { name: 'Event', exact: true }).nth(1).click();
|
await page.getByRole('button', { name: 'Event', exact: true }).nth(1).click();
|
||||||
|
|
||||||
await page.getByTestId('entry-2').getByTestId('block__title').click();
|
await page.getByTestId('entry-2').getByTestId('entry__title').click();
|
||||||
await page.getByTestId('entry-2').getByTestId('block__title').fill('title 2');
|
await page.getByTestId('entry-2').getByTestId('entry__title').fill('title 2');
|
||||||
await page.getByTestId('entry-2').getByTestId('block__title').press('Enter');
|
await page.getByTestId('entry-2').getByTestId('entry__title').press('Enter');
|
||||||
|
|
||||||
await page.getByTestId('entry-3').click();
|
await page.getByTestId('entry-3').click();
|
||||||
await page.getByRole('button', { name: 'Event', exact: true }).nth(1).click();
|
await page.getByRole('button', { name: 'Event', exact: true }).nth(1).click();
|
||||||
|
|
||||||
await page.getByTestId('entry-3').getByTestId('block__title').click();
|
await page.getByTestId('entry-3').getByTestId('entry__title').click();
|
||||||
await page.getByTestId('entry-3').getByTestId('block__title').fill('title 3');
|
await page.getByTestId('entry-3').getByTestId('entry__title').fill('title 3');
|
||||||
await page.getByTestId('entry-3').getByTestId('block__title').press('Enter');
|
await page.getByTestId('entry-3').getByTestId('entry__title').press('Enter');
|
||||||
|
|
||||||
// start an event
|
// start an event
|
||||||
await page.getByTestId('panel-timer-control').getByRole('button', { name: 'Start' }).click();
|
await page.getByTestId('panel-timer-control').getByRole('button', { name: 'Start' }).click();
|
||||||
|
|||||||
@@ -123,9 +123,9 @@ test.describe('Sharing from cuesheet', () => {
|
|||||||
await page.getByRole('button', { name: 'Clear all' }).click();
|
await page.getByRole('button', { name: 'Clear all' }).click();
|
||||||
await page.getByRole('button', { name: 'Delete all' }).click();
|
await page.getByRole('button', { name: 'Delete all' }).click();
|
||||||
await page.getByRole('button', { name: 'Create Event' }).click();
|
await page.getByRole('button', { name: 'Create Event' }).click();
|
||||||
await page.getByTestId('entry-1').getByTestId('block__title').click();
|
await page.getByTestId('entry-1').getByTestId('entry__title').click();
|
||||||
await page.getByTestId('entry-1').getByTestId('block__title').fill('title 1');
|
await page.getByTestId('entry-1').getByTestId('entry__title').fill('title 1');
|
||||||
await page.getByTestId('entry-1').getByTestId('block__title').press('Enter');
|
await page.getByTestId('entry-1').getByTestId('entry__title').press('Enter');
|
||||||
|
|
||||||
await page.close();
|
await page.close();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -14,9 +14,9 @@ test('Copy-paste', async ({ page }) => {
|
|||||||
await page.getByLabel('Cue', { exact: true }).fill('4');
|
await page.getByLabel('Cue', { exact: true }).fill('4');
|
||||||
await page.getByLabel('Cue', { exact: true }).press('Enter');
|
await page.getByLabel('Cue', { exact: true }).press('Enter');
|
||||||
await page.getByTestId('entry-1').click();
|
await page.getByTestId('entry-1').click();
|
||||||
await page.getByTestId('block__title').click();
|
await page.getByTestId('entry__title').click();
|
||||||
await page.getByTestId('block__title').fill('test');
|
await page.getByTestId('entry__title').fill('test');
|
||||||
await page.getByTestId('block__title').press('Enter');
|
await page.getByTestId('entry__title').press('Enter');
|
||||||
|
|
||||||
// copy paste below
|
// copy paste below
|
||||||
await page.getByTestId('rundown-event').locator('div').filter({ hasText: '4' }).click();
|
await page.getByTestId('rundown-event').locator('div').filter({ hasText: '4' }).click();
|
||||||
@@ -25,7 +25,7 @@ test('Copy-paste', async ({ page }) => {
|
|||||||
|
|
||||||
// assert
|
// assert
|
||||||
await expect(page.getByTestId('entry-2')).toBeVisible();
|
await expect(page.getByTestId('entry-2')).toBeVisible();
|
||||||
await expect(page.getByTestId('entry-2').getByTestId('block__title')).toHaveValue('test');
|
await expect(page.getByTestId('entry-2').getByTestId('entry__title')).toHaveValue('test');
|
||||||
await expect(page.getByTestId('entry-2').getByTestId('rundown-event')).toContainText('5');
|
await expect(page.getByTestId('entry-2').getByTestId('rundown-event')).toContainText('5');
|
||||||
|
|
||||||
// copy paste above
|
// copy paste above
|
||||||
@@ -35,7 +35,7 @@ test('Copy-paste', async ({ page }) => {
|
|||||||
|
|
||||||
// assert
|
// assert
|
||||||
await expect(page.getByTestId('entry-2')).toBeVisible();
|
await expect(page.getByTestId('entry-2')).toBeVisible();
|
||||||
await expect(page.getByTestId('entry-2').getByTestId('block__title')).toHaveValue('test');
|
await expect(page.getByTestId('entry-2').getByTestId('entry__title')).toHaveValue('test');
|
||||||
await expect(page.getByTestId('entry-2').getByTestId('rundown-event')).toContainText('4.1');
|
await expect(page.getByTestId('entry-2').getByTestId('rundown-event')).toContainText('4.1');
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -67,37 +67,37 @@ test('Move', async ({ page }) => {
|
|||||||
await expect(page.getByTestId('entry-1').getByTestId('rundown-event')).toContainText('3');
|
await expect(page.getByTestId('entry-1').getByTestId('rundown-event')).toContainText('3');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Add block', async ({ page }) => {
|
test('Add group', async ({ page }) => {
|
||||||
await page.goto('http://localhost:4001/rundown');
|
await page.goto('http://localhost:4001/rundown');
|
||||||
|
|
||||||
// clear rundown
|
// clear rundown
|
||||||
await page.getByRole('button', { name: 'Clear all' }).click();
|
await page.getByRole('button', { name: 'Clear all' }).click();
|
||||||
await page.getByRole('button', { name: 'Delete all' }).click();
|
await page.getByRole('button', { name: 'Delete all' }).click();
|
||||||
await expect(page.getByTestId('rundown-event')).toHaveCount(0);
|
await expect(page.getByTestId('rundown-event')).toHaveCount(0);
|
||||||
await expect(page.getByTestId('rundown-block')).toHaveCount(0);
|
await expect(page.getByTestId('rundown-group')).toHaveCount(0);
|
||||||
|
|
||||||
// create events
|
// create events
|
||||||
await page.getByRole('button', { name: 'Create Event' }).click();
|
await page.getByRole('button', { name: 'Create Event' }).click();
|
||||||
await expect(page.getByTestId('rundown-event')).toHaveCount(1);
|
await expect(page.getByTestId('rundown-event')).toHaveCount(1);
|
||||||
await expect(page.getByTestId('rundown-block')).toHaveCount(0);
|
await expect(page.getByTestId('rundown-group')).toHaveCount(0);
|
||||||
await page.getByPlaceholder(/event title/i).fill('test');
|
await page.getByPlaceholder(/event title/i).fill('test');
|
||||||
await page.getByTestId('entry-1').click();
|
await page.getByTestId('entry-1').click();
|
||||||
|
|
||||||
// add block below
|
// add group below
|
||||||
await page.getByTestId('rundown-event').locator('div').filter({ hasText: '1' }).press('Alt+G');
|
await page.getByTestId('rundown-event').locator('div').filter({ hasText: '1' }).press('Alt+G');
|
||||||
await expect(page.getByTestId('rundown-event')).toHaveCount(1);
|
await expect(page.getByTestId('rundown-event')).toHaveCount(1);
|
||||||
await expect(page.getByTestId('rundown-block')).toHaveCount(1);
|
await expect(page.getByTestId('rundown-group')).toHaveCount(1);
|
||||||
await page.getByTestId('rundown-block').getByTestId('block__title').fill('block below');
|
await page.getByTestId('rundown-group').getByTestId('entry__title').fill('group below');
|
||||||
|
|
||||||
// add block above
|
// add group above
|
||||||
await page.getByTestId('rundown-event').locator('div').filter({ hasText: '1' }).press('Alt+Shift+G');
|
await page.getByTestId('rundown-event').locator('div').filter({ hasText: '1' }).press('Alt+Shift+G');
|
||||||
await expect(page.getByTestId('rundown-event')).toHaveCount(1);
|
await expect(page.getByTestId('rundown-event')).toHaveCount(1);
|
||||||
await expect(page.getByTestId('rundown-block')).toHaveCount(2);
|
await expect(page.getByTestId('rundown-group')).toHaveCount(2);
|
||||||
await page.getByTestId('block__title').first().fill('block above');
|
await page.getByTestId('entry__title').first().fill('group above');
|
||||||
|
|
||||||
await expect(page.getByTestId(/block__title/i).first()).toHaveValue('block above');
|
await expect(page.getByTestId(/entry__title/i).first()).toHaveValue('group above');
|
||||||
await expect(page.getByTestId(/block__title/i).nth(2)).toHaveValue('block below');
|
await expect(page.getByTestId(/entry__title/i).nth(2)).toHaveValue('group below');
|
||||||
await expect(page.getByTestId('entry-1').getByTestId(/block__title/)).toHaveValue('test');
|
await expect(page.getByTestId('entry-1').getByTestId(/entry__title/)).toHaveValue('test');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Add delay', async ({ page }) => {
|
test('Add delay', async ({ page }) => {
|
||||||
@@ -114,7 +114,7 @@ test('Add delay', async ({ page }) => {
|
|||||||
await expect(page.getByTestId('rundown-event')).toHaveCount(1);
|
await expect(page.getByTestId('rundown-event')).toHaveCount(1);
|
||||||
await expect(page.getByTestId('rundown-delay')).toHaveCount(0);
|
await expect(page.getByTestId('rundown-delay')).toHaveCount(0);
|
||||||
await page.getByTestId('entry-1').click();
|
await page.getByTestId('entry-1').click();
|
||||||
await page.getByTestId('block__title').press('Escape');
|
await page.getByTestId('entry__title').press('Escape');
|
||||||
|
|
||||||
//add delay below
|
//add delay below
|
||||||
await page.getByTestId('rundown-event').locator('div').filter({ hasText: '1' }).press('Alt+D');
|
await page.getByTestId('rundown-event').locator('div').filter({ hasText: '1' }).press('Alt+D');
|
||||||
@@ -141,7 +141,7 @@ test('Add event', async ({ page }) => {
|
|||||||
await page.getByRole('button', { name: 'Create Event' }).click();
|
await page.getByRole('button', { name: 'Create Event' }).click();
|
||||||
await expect(page.getByTestId('rundown-event')).toHaveCount(1);
|
await expect(page.getByTestId('rundown-event')).toHaveCount(1);
|
||||||
await page.getByTestId('entry-1').click();
|
await page.getByTestId('entry-1').click();
|
||||||
await page.getByTestId('block__title').press('Escape');
|
await page.getByTestId('entry__title').press('Escape');
|
||||||
|
|
||||||
//add event below
|
//add event below
|
||||||
await page.getByTestId('rundown-event').locator('div').filter({ hasText: '1' }).press('Alt+E');
|
await page.getByTestId('rundown-event').locator('div').filter({ hasText: '1' }).press('Alt+E');
|
||||||
|
|||||||
@@ -29,12 +29,12 @@ test('sheet file upload', async ({ page }) => {
|
|||||||
await page.getByRole('button', { name: 'Close settings' }).click();
|
await page.getByRole('button', { name: 'Close settings' }).click();
|
||||||
|
|
||||||
// asset test events
|
// asset test events
|
||||||
const firstTitle = page.getByTestId('entry-1').getByTestId('block__title');
|
const firstTitle = page.getByTestId('entry-1').getByTestId('entry__title');
|
||||||
await expect(firstTitle).toHaveValue('Attempt light check');
|
await expect(firstTitle).toHaveValue('Attempt light check');
|
||||||
|
|
||||||
const secondTitle = page.getByTestId('entry-2').getByTestId('block__title');
|
const secondTitle = page.getByTestId('entry-2').getByTestId('entry__title');
|
||||||
await expect(secondTitle).toHaveValue('Preset');
|
await expect(secondTitle).toHaveValue('Preset');
|
||||||
|
|
||||||
const thirdTitle = page.getByTestId('entry-3').getByTestId('block__title');
|
const thirdTitle = page.getByTestId('entry-3').getByTestId('entry__title');
|
||||||
await expect(thirdTitle).toHaveValue('Albania');
|
await expect(thirdTitle).toHaveValue('Albania');
|
||||||
});
|
});
|
||||||
|
|||||||
Vendored
+2
-2
@@ -181,7 +181,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"01e85": {
|
"01e85": {
|
||||||
"type": "block",
|
"type": "group",
|
||||||
"id": "01e85",
|
"id": "01e85",
|
||||||
"title": "Lunch break",
|
"title": "Lunch break",
|
||||||
"note": "",
|
"note": "",
|
||||||
@@ -336,7 +336,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"cb90b": {
|
"cb90b": {
|
||||||
"type": "block",
|
"type": "group",
|
||||||
"id": "cb90b",
|
"id": "cb90b",
|
||||||
"title": "Afternoon break",
|
"title": "Afternoon break",
|
||||||
"note": "",
|
"note": "",
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ export type EntryId = string;
|
|||||||
export enum SupportedEntry {
|
export enum SupportedEntry {
|
||||||
Event = 'event',
|
Event = 'event',
|
||||||
Delay = 'delay',
|
Delay = 'delay',
|
||||||
Block = 'block',
|
Group = 'group',
|
||||||
Milestone = 'milestone',
|
Milestone = 'milestone',
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -32,8 +32,8 @@ export type OntimeMilestone = OntimeBaseEvent & {
|
|||||||
revision: number;
|
revision: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type OntimeBlock = OntimeBaseEvent & {
|
export type OntimeGroup = OntimeBaseEvent & {
|
||||||
type: SupportedEntry.Block;
|
type: SupportedEntry.Group;
|
||||||
title: string;
|
title: string;
|
||||||
note: string;
|
note: string;
|
||||||
entries: EntryId[];
|
entries: EntryId[];
|
||||||
@@ -78,7 +78,7 @@ export type OntimeEvent = OntimeBaseEvent & {
|
|||||||
|
|
||||||
export type PlayableEvent = OntimeEvent & { skip: false };
|
export type PlayableEvent = OntimeEvent & { skip: false };
|
||||||
export type TimeField = 'timeStart' | 'timeEnd' | 'duration';
|
export type TimeField = 'timeStart' | 'timeEnd' | 'duration';
|
||||||
export type OntimeEntry = OntimeDelay | OntimeBlock | OntimeEvent | OntimeMilestone;
|
export type OntimeEntry = OntimeDelay | OntimeGroup | OntimeEvent | OntimeMilestone;
|
||||||
|
|
||||||
// we need to create a manual union type since keys cannot be used in type unions
|
// we need to create a manual union type since keys cannot be used in type unions
|
||||||
export type OntimeEntryCommonKeys = keyof OntimeEvent | keyof OntimeDelay | keyof OntimeBlock | keyof OntimeMilestone;
|
export type OntimeEntryCommonKeys = keyof OntimeEvent | keyof OntimeDelay | keyof OntimeGroup | keyof OntimeMilestone;
|
||||||
|
|||||||
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
import type { MaybeNumber } from '../../utils/utils.type.js';
|
import type { MaybeNumber } from '../../utils/utils.type.js';
|
||||||
import type { EntryId } from '../core/OntimeEntry.js';
|
import type { EntryId } from '../core/OntimeEntry.js';
|
||||||
|
|
||||||
export type CurrentBlockState = {
|
export type CurrentGroupState = {
|
||||||
id: EntryId;
|
id: EntryId;
|
||||||
startedAt: MaybeNumber;
|
startedAt: MaybeNumber;
|
||||||
expectedEnd: MaybeNumber;
|
expectedEnd: MaybeNumber;
|
||||||
@@ -38,8 +38,8 @@ export const runtimeStorePlaceholder: Readonly<RuntimeStore> = {
|
|||||||
expectedEnd: null, // changes with runtime, based on offset, overflows over dayInMs
|
expectedEnd: null, // changes with runtime, based on offset, overflows over dayInMs
|
||||||
offsetMode: OffsetMode.Absolute,
|
offsetMode: OffsetMode.Absolute,
|
||||||
},
|
},
|
||||||
blockNow: null,
|
groupNow: null,
|
||||||
blockNext: null,
|
groupNext: null,
|
||||||
nextFlag: null,
|
nextFlag: null,
|
||||||
eventNow: null,
|
eventNow: null,
|
||||||
eventNext: null,
|
eventNext: null,
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import type { MaybeString } from '../../utils/utils.type.js';
|
import type { MaybeString } from '../../utils/utils.type.js';
|
||||||
import type { OntimeEvent } from '../core/OntimeEntry.js';
|
import type { OntimeEvent } from '../core/OntimeEntry.js';
|
||||||
import type { SimpleTimerState } from './AuxTimer.type.js';
|
import type { SimpleTimerState } from './AuxTimer.type.js';
|
||||||
import type { CurrentBlockState, EntryMetaData } from './CurrentBlockState.type.js';
|
import type { CurrentGroupState, EntryMetaData } from './CurrentGroupState.type.js';
|
||||||
import type { MessageState } from './MessageControl.type.js';
|
import type { MessageState } from './MessageControl.type.js';
|
||||||
import type { Runtime } from './Runtime.type.js';
|
import type { Runtime } from './Runtime.type.js';
|
||||||
import type { TimerState } from './TimerState.type.js';
|
import type { TimerState } from './TimerState.type.js';
|
||||||
@@ -19,8 +19,8 @@ export type RuntimeStore = {
|
|||||||
eventNow: OntimeEvent | null;
|
eventNow: OntimeEvent | null;
|
||||||
eventNext: OntimeEvent | null;
|
eventNext: OntimeEvent | null;
|
||||||
|
|
||||||
blockNow: CurrentBlockState | null;
|
groupNow: CurrentGroupState | null;
|
||||||
blockNext: MaybeString;
|
groupNext: MaybeString;
|
||||||
nextFlag: EntryMetaData | null;
|
nextFlag: EntryMetaData | null;
|
||||||
|
|
||||||
// extra timers
|
// extra timers
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ export {
|
|||||||
type EntryId,
|
type EntryId,
|
||||||
type OntimeBaseEvent,
|
type OntimeBaseEvent,
|
||||||
type OntimeDelay,
|
type OntimeDelay,
|
||||||
type OntimeBlock,
|
type OntimeGroup,
|
||||||
type OntimeEntryCommonKeys,
|
type OntimeEntryCommonKeys,
|
||||||
type OntimeEntry,
|
type OntimeEntry,
|
||||||
type OntimeMilestone,
|
type OntimeMilestone,
|
||||||
@@ -101,7 +101,7 @@ export { OffsetMode } from './definitions/runtime/Runtime.type.js';
|
|||||||
export type { RuntimeStore } from './definitions/runtime/RuntimeStore.type.js';
|
export type { RuntimeStore } from './definitions/runtime/RuntimeStore.type.js';
|
||||||
export { runtimeStorePlaceholder } from './definitions/runtime/RuntimeStore.js';
|
export { runtimeStorePlaceholder } from './definitions/runtime/RuntimeStore.js';
|
||||||
export { type TimerState, TimerPhase } from './definitions/runtime/TimerState.type.js';
|
export { type TimerState, TimerPhase } from './definitions/runtime/TimerState.type.js';
|
||||||
export type { CurrentBlockState, UpcomingEntry, EntryMetaData } from './definitions/runtime/CurrentBlockState.type.js';
|
export type { CurrentGroupState, UpcomingEntry, EntryMetaData } from './definitions/runtime/CurrentGroupState.type.js';
|
||||||
|
|
||||||
// ---> Extra Timer
|
// ---> Extra Timer
|
||||||
export { type SimpleTimerState, SimplePlayback, SimpleDirection } from './definitions/runtime/AuxTimer.type.js';
|
export { type SimpleTimerState, SimplePlayback, SimpleDirection } from './definitions/runtime/AuxTimer.type.js';
|
||||||
@@ -111,7 +111,7 @@ export type { Client, ClientList, ClientType } from './definitions/Clients.type.
|
|||||||
|
|
||||||
// TYPE UTILITIES
|
// TYPE UTILITIES
|
||||||
export {
|
export {
|
||||||
isOntimeBlock,
|
isOntimeGroup,
|
||||||
isOntimeDelay,
|
isOntimeDelay,
|
||||||
isOntimeEvent,
|
isOntimeEvent,
|
||||||
isOntimeMilestone,
|
isOntimeMilestone,
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import type { AutomationOutput, HTTPOutput, OntimeAction, OSCOutput } from '../definitions/core/Automation.type.js';
|
import type { AutomationOutput, HTTPOutput, OntimeAction, OSCOutput } from '../definitions/core/Automation.type.js';
|
||||||
import type {
|
import type {
|
||||||
OntimeBlock,
|
|
||||||
OntimeDelay,
|
OntimeDelay,
|
||||||
OntimeEntry,
|
OntimeEntry,
|
||||||
OntimeEvent,
|
OntimeEvent,
|
||||||
|
OntimeGroup,
|
||||||
OntimeMilestone,
|
OntimeMilestone,
|
||||||
PlayableEvent,
|
PlayableEvent,
|
||||||
} from '../definitions/core/OntimeEntry.js';
|
} from '../definitions/core/OntimeEntry.js';
|
||||||
@@ -24,8 +24,8 @@ export function isOntimeDelay(event: MaybeEvent): event is OntimeDelay {
|
|||||||
return event?.type === SupportedEntry.Delay;
|
return event?.type === SupportedEntry.Delay;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isOntimeBlock(event: MaybeEvent): event is OntimeBlock {
|
export function isOntimeGroup(event: MaybeEvent): event is OntimeGroup {
|
||||||
return event?.type === SupportedEntry.Block;
|
return event?.type === SupportedEntry.Group;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isOntimeMilestone(event: MaybeEvent): event is OntimeMilestone {
|
export function isOntimeMilestone(event: MaybeEvent): event is OntimeMilestone {
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ export {
|
|||||||
getLastEventNormal,
|
getLastEventNormal,
|
||||||
getLastNormal,
|
getLastNormal,
|
||||||
getNext,
|
getNext,
|
||||||
getNextBlockNormal,
|
getNextGroupNormal,
|
||||||
getNextEvent,
|
getNextEvent,
|
||||||
getNextEventNormal,
|
getNextEventNormal,
|
||||||
getNextNormal,
|
getNextNormal,
|
||||||
@@ -24,8 +24,8 @@ export {
|
|||||||
getPreviousEvent,
|
getPreviousEvent,
|
||||||
getPreviousEventNormal,
|
getPreviousEventNormal,
|
||||||
getPreviousNormal,
|
getPreviousNormal,
|
||||||
getPreviousBlock,
|
getPreviousGroup,
|
||||||
getPreviousBlockNormal,
|
getPreviousGroupNormal,
|
||||||
swapEventData,
|
swapEventData,
|
||||||
} from './src/rundown-utils/rundownUtils.js';
|
} from './src/rundown-utils/rundownUtils.js';
|
||||||
export { getFirstRundown } from './src/rundown/rundown.utils.js';
|
export { getFirstRundown } from './src/rundown/rundown.utils.js';
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import type { OntimeBlock, OntimeDelay, OntimeEntry, OntimeEvent } from 'ontime-types';
|
import type { OntimeDelay, OntimeEntry, OntimeEvent, OntimeGroup } from 'ontime-types';
|
||||||
import { SupportedEntry } from 'ontime-types';
|
import { SupportedEntry } from 'ontime-types';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@@ -7,8 +7,8 @@ import {
|
|||||||
getNext,
|
getNext,
|
||||||
getNextEvent,
|
getNextEvent,
|
||||||
getPrevious,
|
getPrevious,
|
||||||
getPreviousBlock,
|
|
||||||
getPreviousEvent,
|
getPreviousEvent,
|
||||||
|
getPreviousGroup,
|
||||||
swapEventData,
|
swapEventData,
|
||||||
} from './rundownUtils';
|
} from './rundownUtils';
|
||||||
|
|
||||||
@@ -33,7 +33,7 @@ describe('getNext()', () => {
|
|||||||
entries: {
|
entries: {
|
||||||
'1': { id: '1', type: SupportedEntry.Event } as OntimeEvent,
|
'1': { id: '1', type: SupportedEntry.Event } as OntimeEvent,
|
||||||
'2': { id: '2', type: SupportedEntry.Delay } as OntimeDelay,
|
'2': { id: '2', type: SupportedEntry.Delay } as OntimeDelay,
|
||||||
'3': { id: '3', type: SupportedEntry.Block } as OntimeBlock,
|
'3': { id: '3', type: SupportedEntry.Group } as OntimeGroup,
|
||||||
'4': { id: '4', type: SupportedEntry.Event } as OntimeEvent,
|
'4': { id: '4', type: SupportedEntry.Event } as OntimeEvent,
|
||||||
},
|
},
|
||||||
order: ['1', '2', '3', '4'],
|
order: ['1', '2', '3', '4'],
|
||||||
@@ -76,7 +76,7 @@ describe('getNextEvent()', () => {
|
|||||||
const testRundown = [
|
const testRundown = [
|
||||||
{ id: '1', type: SupportedEntry.Event } as OntimeEvent,
|
{ id: '1', type: SupportedEntry.Event } as OntimeEvent,
|
||||||
{ id: '2', type: SupportedEntry.Delay } as OntimeDelay,
|
{ id: '2', type: SupportedEntry.Delay } as OntimeDelay,
|
||||||
{ id: '3', type: SupportedEntry.Block } as OntimeBlock,
|
{ id: '3', type: SupportedEntry.Group } as OntimeGroup,
|
||||||
{ id: '4', type: SupportedEntry.Event } as OntimeEvent,
|
{ id: '4', type: SupportedEntry.Event } as OntimeEvent,
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -89,7 +89,7 @@ describe('getNextEvent()', () => {
|
|||||||
const testRundown = [
|
const testRundown = [
|
||||||
{ id: '1', type: SupportedEntry.Event } as OntimeEvent,
|
{ id: '1', type: SupportedEntry.Event } as OntimeEvent,
|
||||||
{ id: '2', type: SupportedEntry.Delay } as OntimeDelay,
|
{ id: '2', type: SupportedEntry.Delay } as OntimeDelay,
|
||||||
{ id: '3', type: SupportedEntry.Block } as OntimeBlock,
|
{ id: '3', type: SupportedEntry.Group } as OntimeGroup,
|
||||||
];
|
];
|
||||||
|
|
||||||
const { nextEvent, nextIndex } = getNextEvent(testRundown, '1');
|
const { nextEvent, nextIndex } = getNextEvent(testRundown, '1');
|
||||||
@@ -119,7 +119,7 @@ describe('getPrevious()', () => {
|
|||||||
entries: {
|
entries: {
|
||||||
'1': { id: '1', type: SupportedEntry.Event } as OntimeEvent,
|
'1': { id: '1', type: SupportedEntry.Event } as OntimeEvent,
|
||||||
'2': { id: '2', type: SupportedEntry.Delay } as OntimeDelay,
|
'2': { id: '2', type: SupportedEntry.Delay } as OntimeDelay,
|
||||||
'3': { id: '3', type: SupportedEntry.Block } as OntimeBlock,
|
'3': { id: '3', type: SupportedEntry.Group } as OntimeGroup,
|
||||||
'4': { id: '4', type: SupportedEntry.Event } as OntimeEvent,
|
'4': { id: '4', type: SupportedEntry.Event } as OntimeEvent,
|
||||||
},
|
},
|
||||||
order: ['1', '2', '3', '4'],
|
order: ['1', '2', '3', '4'],
|
||||||
@@ -166,7 +166,7 @@ describe('getPreviousEvent()', () => {
|
|||||||
entries: {
|
entries: {
|
||||||
'1': { id: '1', type: SupportedEntry.Event } as OntimeEvent,
|
'1': { id: '1', type: SupportedEntry.Event } as OntimeEvent,
|
||||||
'2': { id: '2', type: SupportedEntry.Delay } as OntimeDelay,
|
'2': { id: '2', type: SupportedEntry.Delay } as OntimeDelay,
|
||||||
'3': { id: '3', type: SupportedEntry.Block } as OntimeBlock,
|
'3': { id: '3', type: SupportedEntry.Group } as OntimeGroup,
|
||||||
'4': { id: '4', type: SupportedEntry.Event } as OntimeEvent,
|
'4': { id: '4', type: SupportedEntry.Event } as OntimeEvent,
|
||||||
},
|
},
|
||||||
order: ['1', '2', '3', '4'],
|
order: ['1', '2', '3', '4'],
|
||||||
@@ -181,7 +181,7 @@ describe('getPreviousEvent()', () => {
|
|||||||
const testRundown = {
|
const testRundown = {
|
||||||
entries: {
|
entries: {
|
||||||
'2': { id: '2', type: SupportedEntry.Delay } as OntimeDelay,
|
'2': { id: '2', type: SupportedEntry.Delay } as OntimeDelay,
|
||||||
'3': { id: '3', type: SupportedEntry.Block } as OntimeBlock,
|
'3': { id: '3', type: SupportedEntry.Group } as OntimeGroup,
|
||||||
'4': { id: '4', type: SupportedEntry.Event } as OntimeEvent,
|
'4': { id: '4', type: SupportedEntry.Event } as OntimeEvent,
|
||||||
},
|
},
|
||||||
order: ['2', '3', '4'],
|
order: ['2', '3', '4'],
|
||||||
@@ -247,7 +247,7 @@ describe('getLastEvent', () => {
|
|||||||
{ id: '1', type: SupportedEntry.Event } as OntimeEvent,
|
{ id: '1', type: SupportedEntry.Event } as OntimeEvent,
|
||||||
{ id: '2', type: SupportedEntry.Delay } as OntimeDelay,
|
{ id: '2', type: SupportedEntry.Delay } as OntimeDelay,
|
||||||
{ id: '3', type: SupportedEntry.Event } as OntimeEvent,
|
{ id: '3', type: SupportedEntry.Event } as OntimeEvent,
|
||||||
{ id: '4', type: SupportedEntry.Block } as OntimeBlock,
|
{ id: '4', type: SupportedEntry.Group } as OntimeGroup,
|
||||||
];
|
];
|
||||||
|
|
||||||
const { lastEvent } = getLastEvent(testRundown);
|
const { lastEvent } = getLastEvent(testRundown);
|
||||||
@@ -263,7 +263,7 @@ describe('getLastEvent', () => {
|
|||||||
describe('getLastNormal', () => {
|
describe('getLastNormal', () => {
|
||||||
it('returns the last entry', () => {
|
it('returns the last entry', () => {
|
||||||
const entries = {
|
const entries = {
|
||||||
4: { id: '4', type: SupportedEntry.Block } as OntimeBlock,
|
4: { id: '4', type: SupportedEntry.Group } as OntimeGroup,
|
||||||
1: { id: '1', type: SupportedEntry.Event } as OntimeEvent,
|
1: { id: '1', type: SupportedEntry.Event } as OntimeEvent,
|
||||||
3: { id: '3', type: SupportedEntry.Event } as OntimeEvent,
|
3: { id: '3', type: SupportedEntry.Event } as OntimeEvent,
|
||||||
2: { id: '2', type: SupportedEntry.Delay } as OntimeDelay,
|
2: { id: '2', type: SupportedEntry.Delay } as OntimeDelay,
|
||||||
@@ -300,16 +300,16 @@ describe('getLastEvent', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('getPreviousBlock()', () => {
|
describe('getPreviousGroup()', () => {
|
||||||
const testRundown = {
|
const testRundown = {
|
||||||
entries: {
|
entries: {
|
||||||
a: { id: 'a', type: SupportedEntry.Event } as OntimeEvent,
|
a: { id: 'a', type: SupportedEntry.Event } as OntimeEvent,
|
||||||
b: { id: 'b', type: SupportedEntry.Event } as OntimeEvent,
|
b: { id: 'b', type: SupportedEntry.Event } as OntimeEvent,
|
||||||
c: { id: 'c', type: SupportedEntry.Event } as OntimeEvent,
|
c: { id: 'c', type: SupportedEntry.Event } as OntimeEvent,
|
||||||
d: { id: 'd', type: SupportedEntry.Delay } as OntimeDelay,
|
d: { id: 'd', type: SupportedEntry.Delay } as OntimeDelay,
|
||||||
e: { id: 'e', type: SupportedEntry.Block } as OntimeBlock,
|
e: { id: 'e', type: SupportedEntry.Group } as OntimeGroup,
|
||||||
f: { id: 'f', type: SupportedEntry.Event } as OntimeEvent,
|
f: { id: 'f', type: SupportedEntry.Event } as OntimeEvent,
|
||||||
g: { id: 'g', type: SupportedEntry.Block } as OntimeBlock,
|
g: { id: 'g', type: SupportedEntry.Group } as OntimeGroup,
|
||||||
h: { id: 'h', type: SupportedEntry.Event } as OntimeEvent,
|
h: { id: 'h', type: SupportedEntry.Event } as OntimeEvent,
|
||||||
},
|
},
|
||||||
order: ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'],
|
order: ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'],
|
||||||
@@ -318,37 +318,37 @@ describe('getLastEvent', () => {
|
|||||||
test.each([
|
test.each([
|
||||||
['h', 'g'],
|
['h', 'g'],
|
||||||
['f', 'e'],
|
['f', 'e'],
|
||||||
])('returns the relevant block', (id, expected) => {
|
])('returns the relevant group', (id, expected) => {
|
||||||
const block = getPreviousBlock(testRundown, id);
|
const group = getPreviousGroup(testRundown, id);
|
||||||
expect(block?.id).toBe(expected);
|
expect(group?.id).toBe(expected);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('returns null if there is no parent block relevant block', () => {
|
it('returns null if there is no parent group relevant group', () => {
|
||||||
const block = getPreviousBlock(testRundown, 'a');
|
const group = getPreviousGroup(testRundown, 'a');
|
||||||
expect(block).toBe(null);
|
expect(group).toBe(null);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('also works on index 0', () => {
|
it('also works on index 0', () => {
|
||||||
testRundown.order.unshift('0');
|
testRundown.order.unshift('0');
|
||||||
// @ts-expect-error -- we are adding an event to the rundown
|
// @ts-expect-error -- we are adding an event to the rundown
|
||||||
testRundown.entries['0'] = { id: '0', type: SupportedEntry.Block } as OntimeBlock;
|
testRundown.entries['0'] = { id: '0', type: SupportedEntry.Group } as OntimeGroup;
|
||||||
const block = getPreviousBlock(testRundown, 'a');
|
const group = getPreviousGroup(testRundown, 'a');
|
||||||
expect(block?.id).toBe('0');
|
expect(group?.id).toBe('0');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('returns the parent block if nested event', () => {
|
it('returns the parent group if nested event', () => {
|
||||||
const testRundown = {
|
const testRundown = {
|
||||||
entries: {
|
entries: {
|
||||||
1: { id: '1', type: SupportedEntry.Event } as OntimeEvent,
|
1: { id: '1', type: SupportedEntry.Event } as OntimeEvent,
|
||||||
block: { id: 'block', type: SupportedEntry.Block, entries: ['21', '22', '23'] } as OntimeBlock,
|
group: { id: 'group', type: SupportedEntry.Group, entries: ['21', '22', '23'] } as OntimeGroup,
|
||||||
21: { id: '21', type: SupportedEntry.Event, parent: 'block' } as OntimeEvent,
|
21: { id: '21', type: SupportedEntry.Event, parent: 'group' } as OntimeEvent,
|
||||||
22: { id: '22', type: SupportedEntry.Event, parent: 'block' } as OntimeEvent,
|
22: { id: '22', type: SupportedEntry.Event, parent: 'group' } as OntimeEvent,
|
||||||
23: { id: '23', type: SupportedEntry.Event, parent: 'block' } as OntimeEvent,
|
23: { id: '23', type: SupportedEntry.Event, parent: 'group' } as OntimeEvent,
|
||||||
},
|
},
|
||||||
order: ['1', 'block'],
|
order: ['1', 'group'],
|
||||||
};
|
};
|
||||||
const block = getPreviousBlock(testRundown, '21');
|
const group = getPreviousGroup(testRundown, '21');
|
||||||
expect(block?.id).toBe('block');
|
expect(group?.id).toBe('group');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
import type {
|
import type {
|
||||||
EntryId,
|
EntryId,
|
||||||
OntimeBlock,
|
|
||||||
OntimeEntry,
|
OntimeEntry,
|
||||||
OntimeEvent,
|
OntimeEvent,
|
||||||
|
OntimeGroup,
|
||||||
PlayableEvent,
|
PlayableEvent,
|
||||||
Rundown,
|
Rundown,
|
||||||
RundownEntries,
|
RundownEntries,
|
||||||
} from 'ontime-types';
|
} from 'ontime-types';
|
||||||
import { isOntimeBlock, isOntimeEvent, isPlayableEvent } from 'ontime-types';
|
import { isOntimeEvent, isOntimeGroup, isPlayableEvent } from 'ontime-types';
|
||||||
|
|
||||||
type IndexAndEntry = { entry: OntimeEntry | null; index: number | null };
|
type IndexAndEntry = { entry: OntimeEntry | null; index: number | null };
|
||||||
|
|
||||||
@@ -313,9 +313,9 @@ export function getEventWithId(rundown: OntimeEntry[], id: string): OntimeEntry
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets relevant block element for a given ID
|
* Gets relevant group element for a given ID
|
||||||
*/
|
*/
|
||||||
export function getPreviousBlockNormal(rundown: RundownEntries, order: string[], currentId: string): IndexAndEntry {
|
export function getPreviousGroupNormal(rundown: RundownEntries, order: string[], currentId: string): IndexAndEntry {
|
||||||
let foundCurrentEvent = false;
|
let foundCurrentEvent = false;
|
||||||
// Iterate backwards through the rundown to find the current event
|
// Iterate backwards through the rundown to find the current event
|
||||||
for (let index = order.length - 1; index >= 0; index--) {
|
for (let index = order.length - 1; index >= 0; index--) {
|
||||||
@@ -325,20 +325,20 @@ export function getPreviousBlockNormal(rundown: RundownEntries, order: string[],
|
|||||||
foundCurrentEvent = true;
|
foundCurrentEvent = true;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
// the first block before the current event is the relevant one
|
// the first group before the current event is the relevant one
|
||||||
const entry = rundown[id];
|
const entry = rundown[id];
|
||||||
if (foundCurrentEvent && isOntimeBlock(entry)) {
|
if (foundCurrentEvent && isOntimeGroup(entry)) {
|
||||||
return { entry, index };
|
return { entry, index };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// no blocks exist before current event
|
// no groups exist before current event
|
||||||
return { entry: null, index: null };
|
return { entry: null, index: null };
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets next block element for a given ID
|
* Gets next group element for a given ID
|
||||||
*/
|
*/
|
||||||
export function getNextBlockNormal(rundown: RundownEntries, order: string[], currentId: string): IndexAndEntry {
|
export function getNextGroupNormal(rundown: RundownEntries, order: string[], currentId: string): IndexAndEntry {
|
||||||
let foundCurrentEvent = false;
|
let foundCurrentEvent = false;
|
||||||
// Iterate backwards through the rundown to find the current event
|
// Iterate backwards through the rundown to find the current event
|
||||||
for (let index = 0; index < order.length; index++) {
|
for (let index = 0; index < order.length; index++) {
|
||||||
@@ -348,25 +348,25 @@ export function getNextBlockNormal(rundown: RundownEntries, order: string[], cur
|
|||||||
foundCurrentEvent = true;
|
foundCurrentEvent = true;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
// the first block before the current event is the relevant one
|
// the first group before the current event is the relevant one
|
||||||
const entry = rundown[id];
|
const entry = rundown[id];
|
||||||
if (foundCurrentEvent && isOntimeBlock(entry)) {
|
if (foundCurrentEvent && isOntimeGroup(entry)) {
|
||||||
return { entry, index };
|
return { entry, index };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// no blocks exist before current event
|
// no groups exist before current event
|
||||||
return { entry: null, index: null };
|
return { entry: null, index: null };
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets relevant block element for a given ID
|
* Gets relevant group element for a given ID
|
||||||
*/
|
*/
|
||||||
export function getPreviousBlock(rundown: Pick<Rundown, 'entries' | 'order'>, currentId: EntryId): OntimeBlock | null {
|
export function getPreviousGroup(rundown: Pick<Rundown, 'entries' | 'order'>, currentId: EntryId): OntimeGroup | null {
|
||||||
const currentEvent = rundown.entries[currentId];
|
const currentEvent = rundown.entries[currentId];
|
||||||
|
|
||||||
// check if event is inside a block
|
// check if event is inside a group
|
||||||
if (isOntimeEvent(currentEvent) && currentEvent.parent) {
|
if (isOntimeEvent(currentEvent) && currentEvent.parent) {
|
||||||
return rundown.entries[currentEvent.parent] as OntimeBlock;
|
return rundown.entries[currentEvent.parent] as OntimeGroup;
|
||||||
}
|
}
|
||||||
|
|
||||||
let foundCurrentEvent = false;
|
let foundCurrentEvent = false;
|
||||||
@@ -379,11 +379,11 @@ export function getPreviousBlock(rundown: Pick<Rundown, 'entries' | 'order'>, cu
|
|||||||
foundCurrentEvent = true;
|
foundCurrentEvent = true;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
// the first block before the current event is the relevant one
|
// the first group before the current event is the relevant one
|
||||||
if (foundCurrentEvent && isOntimeBlock(entry)) {
|
if (foundCurrentEvent && isOntimeGroup(entry)) {
|
||||||
return entry;
|
return entry;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// no blocks exist before null event
|
// no groups exist before null event
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user