Skip fixes (#805)

* refactor: resolve current in time-to-end

* refactor: recalculate on skip

* Alpha tweaks (#806)

* style: small tweaks to interface

* refactor: simplify quick add
This commit is contained in:
Carlos Valente
2024-03-08 19:54:18 +01:00
committed by GitHub
parent 6dc2bebeee
commit 10d7273ec0
29 changed files with 221 additions and 165 deletions
@@ -7,4 +7,5 @@
gap: 0.25rem;
overflow: hidden;
background-color: black;
}
@@ -5,6 +5,7 @@ import { useKeyDown } from '../../common/hooks/useKeyDown';
import AboutPanel from './panel/about-panel/AboutPanel';
import GeneralPanel from './panel/general-panel/GeneralPanel';
import IntegrationsPanel from './panel/integrations-panel/IntegrationsPanel';
import InterfacePanel from './panel/interface-panel/InterfacePanel';
import LogPanel from './panel/log-panel/LogPanel';
import ProjectPanel from './panel/project-panel/ProjectPanel';
import ProjectSettingsPanel from './panel/project-settings-panel/ProjectSettingsPanel';
@@ -32,6 +33,7 @@ export default function AppSettings() {
{selectedPanel === 'project' && <ProjectPanel />}
{selectedPanel === 'general' && <GeneralPanel />}
{selectedPanel === 'sources' && <SourcesPanel />}
{selectedPanel === 'interface' && <InterfacePanel />}
{selectedPanel === 'integrations' && <IntegrationsPanel />}
{selectedPanel === 'project_settings' && <ProjectSettingsPanel />}
{selectedPanel === 'about' && <AboutPanel />}
@@ -1,5 +1,5 @@
import { PropsWithChildren } from 'react';
import { IconButton } from '@chakra-ui/react';
import { Button } from '@chakra-ui/react';
import { IoClose } from '@react-icons/all-files/io5/IoClose';
import style from './PanelContent.module.scss';
@@ -14,7 +14,9 @@ export default function PanelContent(props: PropsWithChildren<PanelContentProps>
return (
<div className={style.contentWrapper}>
<div className={style.corner}>
<IconButton onClick={onClose} aria-label='close' icon={<IoClose />} variant='ontime-ghosted-white' />
<Button onClick={onClose} aria-label='close' rightIcon={<IoClose />} variant='ontime-subtle'>
Close settings
</Button>
</div>
<div className={style.content}>{children}</div>
</div>
@@ -55,7 +55,7 @@ $inner-padding: 1rem;
}
.pad {
padding: 0 1rem;
margin: 0 2rem;
max-height: 550px;
overflow-y: scroll;
}
@@ -58,6 +58,7 @@ export default function CheckUpdatesButton(props: CheckUpdatesButtonProps) {
isLoading={isFetching}
isDisabled={disableButton}
size='sm'
maxWidth='max-content'
>
Check for updates
</Button>
@@ -1,6 +1,5 @@
import * as Panel from '../PanelUtils';
import EditorSettingsForm from './EditorSettingsForm';
import GeneralPanelForm from './GeneralPanelForm';
import ViewSettingsForm from './ViewSettingsForm';
@@ -9,7 +8,6 @@ export default function GeneralPanel() {
<>
<Panel.Header>Settings</Panel.Header>
<GeneralPanelForm />
<EditorSettingsForm />
<ViewSettingsForm />
</>
);
@@ -91,7 +91,7 @@ export default function HttpIntegrations() {
</Button>
</div>
</Panel.SubHeader>
<Panel.Divider />
<Panel.Section as='form' id='http-form' onSubmit={handleSubmit(onSubmit)} onKeyDown={preventEscape}>
{errors?.root && <Panel.Error>{errors.root.message}</Panel.Error>}
<Panel.ListGroup>
@@ -0,0 +1,27 @@
import { Alert, AlertDescription, AlertIcon } from '@chakra-ui/react';
import * as Panel from '../PanelUtils';
import EditorSettingsForm from './EditorSettingsForm';
export default function InterfacePanel() {
return (
<>
<Panel.Header>Interface</Panel.Header>
<Panel.Section>
<Alert status='info' variant='ontime-on-dark-info'>
<AlertIcon />
<AlertDescription>
Interface settings
<br />
<br />
These concern settings that are applied to this user in this browser.
<br />
It will not affect other users or other browsers.
</AlertDescription>
</Alert>
</Panel.Section>
<EditorSettingsForm />
</>
);
}
@@ -81,7 +81,7 @@ export default function ManageProjects() {
isDisabled={Boolean(loading) || isCreatingProject}
rightIcon={<IoAdd />}
>
Add
New
</Button>
</div>
</Panel.SubHeader>
@@ -77,7 +77,7 @@ export default function ProjectCreateForm(props: ProjectCreateFromProps) {
</div>
</Panel.Title>
{error && <Panel.Error>{error}</Panel.Error>}
<div className={style.createFormInputField}>
<div className={style.innerColumn}>
<label>
Project title
<Input
@@ -89,8 +89,6 @@ export default function ProjectCreateForm(props: ProjectCreateFromProps) {
{...register('title')}
/>
</label>
</div>
<div className={style.createFormInputField}>
<label>
Project description
<Input
@@ -102,8 +100,6 @@ export default function ProjectCreateForm(props: ProjectCreateFromProps) {
{...register('description')}
/>
</label>
</div>
<div className={style.createFormInputField}>
<label>
Public info
<Textarea
@@ -112,11 +108,10 @@ export default function ProjectCreateForm(props: ProjectCreateFromProps) {
maxLength={150}
placeholder='Shows always start ontime'
autoComplete='off'
resize='none'
{...register('publicInfo')}
/>
</label>
</div>
<div className={style.createFormInputField}>
<label>
Public QR code Url
<Input
@@ -127,8 +122,6 @@ export default function ProjectCreateForm(props: ProjectCreateFromProps) {
{...register('publicUrl')}
/>
</label>
</div>
<div className={style.createFormInputField}>
<label>
Backstage info
<Textarea
@@ -137,11 +130,10 @@ export default function ProjectCreateForm(props: ProjectCreateFromProps) {
maxLength={150}
placeholder='Wi-Fi password: 1234'
autoComplete='off'
resize='none'
{...register('backstageInfo')}
/>
</label>
</div>
<div className={style.createFormInputField}>
<label>
Backstage QR code Url
<Input
@@ -70,84 +70,72 @@ export default function ProjectData() {
</Panel.SubHeader>
<Panel.Divider />
<Panel.Section>
<div className={style.createFormInputField}>
<label>
Project title
<Input
variant='ontime-filled'
size='sm'
maxLength={50}
placeholder='Your project name'
autoComplete='off'
{...register('title')}
/>
</label>
</div>
<div className={style.createFormInputField}>
<label>
Project description
<Input
variant='ontime-filled'
size='sm'
maxLength={100}
placeholder='Euro Love, Malmö 2024'
autoComplete='off'
{...register('description')}
/>
</label>
</div>
<div className={style.createFormInputField}>
<label>
Public info
<Textarea
variant='ontime-filled'
size='sm'
maxLength={150}
placeholder='Shows always start ontime'
autoComplete='off'
resize='none'
{...register('publicInfo')}
/>
</label>
</div>
<div className={style.createFormInputField}>
<label>
Public QR code URL
<Input
variant='ontime-filled'
size='sm'
placeholder='www.getontime.no'
autoComplete='off'
{...register('publicUrl')}
/>
</label>
</div>
<div className={style.createFormInputField}>
<label>
Backstage info
<Textarea
variant='ontime-filled'
size='sm'
maxLength={150}
placeholder='Wi-Fi password: 1234'
autoComplete='off'
resize='none'
{...register('backstageInfo')}
/>
</label>
</div>
<div className={style.createFormInputField}>
<label>
Backstage QR code URL
<Input
variant='ontime-filled'
size='sm'
placeholder='http://docs.getontime.no'
autoComplete='off'
{...register('backstageUrl')}
/>
</label>
</div>
<label>
Project title
<Input
variant='ontime-filled'
size='sm'
maxLength={50}
placeholder='Your project name'
autoComplete='off'
{...register('title')}
/>
</label>
<label>
Project description
<Input
variant='ontime-filled'
size='sm'
maxLength={100}
placeholder='Euro Love, Malmö 2024'
autoComplete='off'
{...register('description')}
/>
</label>
<label>
Public info
<Textarea
variant='ontime-filled'
size='sm'
maxLength={150}
placeholder='Shows always start ontime'
autoComplete='off'
resize='none'
{...register('publicInfo')}
/>
</label>
<label>
Public QR code URL
<Input
variant='ontime-filled'
size='sm'
placeholder='www.getontime.no'
autoComplete='off'
{...register('publicUrl')}
/>
</label>
<label>
Backstage info
<Textarea
variant='ontime-filled'
size='sm'
maxLength={150}
placeholder='Wi-Fi password: 1234'
autoComplete='off'
resize='none'
{...register('backstageInfo')}
/>
</label>
<label>
Backstage QR code URL
<Input
variant='ontime-filled'
size='sm'
placeholder='http://docs.getontime.no'
autoComplete='off'
{...register('backstageUrl')}
/>
</label>
</Panel.Section>
</Panel.Card>
</Panel.Section>
@@ -18,10 +18,6 @@
flex: 2;
}
.createFormInputField {
padding-bottom: 0.5rem;
}
.headerButtons,
.actionButtons,
.createActionButtons {
@@ -39,10 +35,6 @@
justify-content: flex-end;
}
.createContainer {
padding: 0.5rem 0;
}
.saveButton {
text-transform: capitalize;
}
@@ -50,3 +42,11 @@
.containCell {
max-width: 400px;
}
.innerColumn {
margin: 0 2rem;
margin-bottom: 2rem;
display: flex;
flex-direction: column;
gap: 1em;
}
@@ -56,6 +56,7 @@ export default function ProjectSettingsPanel() {
New
</Button>
</Panel.SubHeader>
<Panel.Divider />
<Panel.Section>
<Alert status='info' variant='ontime-on-dark-info'>
<AlertIcon />
@@ -13,14 +13,21 @@ export const settingPanels: Readonly<SettingsOption[]> = [
label: 'Project',
secondary: [{ id: 'project__manage', label: 'Manage project files' }],
},
{ id: 'general', label: 'General', secondary: [{ id: 'general__manage', label: 'Manage app settings' }] },
{
id: 'general',
label: 'General',
secondary: [
{ id: 'general__manage', label: 'Manage Ontime settings' },
{ id: 'general__view', label: 'View settings' },
{ id: 'general__urlPresets', label: 'URL presets' },
],
},
{
id: 'project_settings',
label: 'Project Settings',
secondary: [{ id: 'project_settings__custom', label: 'Custom fields' }],
},
{ id: 'interface', label: 'Interface' },
{ id: 'views', label: 'Views' },
{ id: 'interface', label: 'Interface', secondary: [{ id: 'general__editor', label: 'Editor settings' }] },
{
id: 'sources',
label: 'Data Sources',
+21 -11
View File
@@ -1,7 +1,15 @@
import { Fragment, lazy, useCallback, useEffect, useRef, useState } from 'react';
import { closestCenter, DndContext, DragEndEvent, PointerSensor, useSensor, useSensors } from '@dnd-kit/core';
import { arrayMove, SortableContext, verticalListSortingStrategy } from '@dnd-kit/sortable';
import { isOntimeBlock, isOntimeDelay, isOntimeEvent, Playback, RundownCached, SupportedEvent } from 'ontime-types';
import {
isOntimeBlock,
isOntimeDelay,
isOntimeEvent,
MaybeNumber,
Playback,
RundownCached,
SupportedEvent,
} from 'ontime-types';
import { getFirstNormal, getNextNormal, getPreviousNormal } from 'ontime-utils';
import { useEventAction } from '../../common/hooks/useEventAction';
@@ -201,10 +209,13 @@ export default function Rundown({ data }: RundownProps) {
return <RundownEmpty handleAddNew={() => insertAtCursor(SupportedEvent.Event, null)} />;
}
let previousEnd: null | number = null;
let thisEnd = 0;
let previousEnd: MaybeNumber = null;
let previousEventId: string | undefined;
let thisEnd: MaybeNumber = previousEnd;
let thisId = previousEventId;
let eventIndex = 0;
// all events before the current selected are in the past
let isPast = Boolean(featureData?.selectedEventId);
return (
@@ -223,16 +234,16 @@ export default function Rundown({ data }: RundownProps) {
if (index === 0) {
eventIndex = 0;
}
let isFirstEvent = false;
if (isOntimeEvent(event)) {
isFirstEvent = eventIndex === 0;
// event indexes are 1 based in frontend
eventIndex++;
if (!isFirstEvent) {
previousEnd = thisEnd;
previousEnd = thisEnd;
previousEventId = thisId;
if (!event.skip) {
thisEnd = event.timeEnd;
thisId = eventId;
}
thisEnd = event.timeEnd;
previousEventId = event.id;
}
const isLast = index === order.length - 1;
const isLoaded = featureData?.selectedEventId === event.id;
@@ -265,8 +276,7 @@ export default function Rundown({ data }: RundownProps) {
{((showQuickEntry && hasCursor) || isLast) && (
<QuickAddBlock
showKbd={hasCursor}
eventId={event.id}
previousEventId={previousEventId}
previousEventId={event.id}
disableAddDelay={isOntimeDelay(event)}
disableAddBlock={isOntimeBlock(event)}
/>
@@ -1,6 +1,6 @@
@use '../blockMixins' as *;
$skip-opacity: 0.1;
$skip-opacity: 0.2;
.eventBlock {
@include block-styling;
@@ -173,7 +173,7 @@ function TimerIcon(props: { type: TimerType; className: string }) {
return <IoTime className={className} />;
}
if (type === TimerType.TimeToEnd) {
return <IoFlag className={className} />
return <IoFlag className={className} />;
}
return <IoArrowDown className={className} />;
}
@@ -10,14 +10,14 @@ $gap-left: calc(2rem + 0.25rem + 3rem);
display: flex;
gap: 0.5rem;
font-weight: 700;
font-weight: 600;
}
@mixin indicator($bg-colour) {
padding: 0 0.5rem;
border-radius: 2px;
background-color: $bg-colour;
color: $ui-black;
color: $ui-white;
}
.delay {
@@ -94,8 +94,8 @@ const EventBlockPlayback = (props: EventBlockPlaybackProps) => {
aria-label='Skip event'
tooltip='Skip event'
icon={skip ? <IoRemoveCircle /> : <IoRemoveCircleOutline />}
backgroundColor={skip ? '#FA5656' : undefined}
_hover={{ backgroundColor: skip ? '#FF7878' : '#404040' }}
backgroundColor={skip ? '#B20000' : undefined}
_hover={{ backgroundColor: '#FF7878' }}
{...tooltipProps}
{...blockBtnStyle}
clickHandler={toggleSkip}
@@ -7,7 +7,6 @@
display: flex;
flex-direction: column;
overflow-x: auto;
}
.content {
@@ -49,6 +48,14 @@
text-transform: capitalize;
}
.decorated {
color: var(--decorator-color, $ui-white);
background-color: var(--decorator-bg, $gray-1100);
width: fit-content;
padding: 0 0.5rem;
border-radius: 2px;
}
.delayLabel {
font-size: calc(1rem - 3px);
color: $ontime-delay-text;
@@ -81,17 +88,3 @@
column-gap: 1.5rem;
row-gap: 1rem;
}
.decorated {
&::before{
content: '';
display: inline-block;
vertical-align: baseline;
margin-right: 0.25rem;
width: 1em;
height: 1em;
aspect-ratio: 1;
border-radius: 99px;
background-color: var(--decorator-color, $gray-1100);
}
}
@@ -6,6 +6,7 @@ import CopyTag from '../../../common/components/copy-tag/CopyTag';
import { useEventAction } from '../../../common/hooks/useEventAction';
import useCustomFields from '../../../common/hooks-query/useCustomFields';
import useRundown from '../../../common/hooks-query/useRundown';
import { getAccessibleColour } from '../../../common/utils/styleUtils';
import { useEventSelection } from '../useEventSelection';
import EventEditorTimes from './composite/EventEditorTimes';
@@ -105,6 +106,7 @@ export default function EventEditor() {
const key = `${event.id}-${label}`;
const fieldName = `custom-${label}`;
const initialValue = event.custom[label]?.value ?? '';
const { backgroundColor, color } = getAccessibleColour(customFields[label].colour);
return (
<EventTextArea
@@ -114,7 +116,7 @@ export default function EventEditor() {
initialValue={initialValue}
submitHandler={handleSubmit}
className={style.decorated}
style={{ '--decorator-color': customFields[label].colour } as CSSProperties}
style={{ '--decorator-bg': backgroundColor, '--decorator-color': color } as CSSProperties}
/>
);
})}
@@ -13,14 +13,13 @@ import style from './QuickAddBlock.module.scss';
interface QuickAddBlockProps {
showKbd: boolean;
eventId: string;
previousEventId?: string;
previousEventId: string;
disableAddDelay?: boolean;
disableAddBlock: boolean;
}
const QuickAddBlock = (props: QuickAddBlockProps) => {
const { showKbd, eventId, previousEventId, disableAddDelay = true, disableAddBlock } = props;
const { showKbd, previousEventId, disableAddDelay = true, disableAddBlock } = props;
const { addEvent } = useEventAction();
const { emitError } = useEmitLog();
@@ -43,7 +42,7 @@ const QuickAddBlock = (props: QuickAddBlockProps) => {
defaultPublic: isPublicOption,
startTimeIsLastEnd: startTimeIsLastEndOption,
lastEventId: previousEventId,
after: eventId,
after: previousEventId,
};
addEvent(newEvent, options);
break;
@@ -51,7 +50,7 @@ const QuickAddBlock = (props: QuickAddBlockProps) => {
case 'delay': {
const options = {
lastEventId: previousEventId,
after: eventId,
after: previousEventId,
};
addEvent({ type: SupportedEvent.Delay }, options);
break;
@@ -59,7 +58,7 @@ const QuickAddBlock = (props: QuickAddBlockProps) => {
case 'block': {
const options = {
lastEventId: previousEventId,
after: eventId,
after: previousEventId,
};
addEvent({ type: SupportedEvent.Block }, options);
break;
@@ -70,7 +69,7 @@ const QuickAddBlock = (props: QuickAddBlockProps) => {
}
}
},
[previousEventId, eventId, addEvent, emitError],
[previousEventId, addEvent, emitError],
);
return (
+1 -1
View File
@@ -277,7 +277,7 @@ export async function previewSpreadsheet(req: Request, res: Response) {
}
const options = JSON.parse(req.body.options);
const data = handleMaybeExcel(filePath, options);
const { data } = handleMaybeExcel(filePath, options);
res.status(200).send(data);
} catch (error) {
res.status(500).send({ message: String(error) });
@@ -68,7 +68,7 @@ export async function addEvent(
notifyChanges({ timer: [newEvent.id], external: true });
// notify runtime that rundown size has changed
// notify runtime that rundown has changed
updateRuntimeOnChange();
return newEvent;
@@ -84,7 +84,7 @@ export async function deleteEvent(eventId: string) {
notifyChanges({ timer: [eventId], external: true });
// notify event loader that rundown size has changed
// notify event loader that rundown has changed
updateRuntimeOnChange();
}
@@ -114,6 +114,9 @@ export async function editEvent(patch: Partial<OntimeEvent> | Partial<OntimeBloc
notifyChanges({ timer: [patch.id], external: true });
// notify event loader that rundown has changed
updateRuntimeOnChange();
return newEvent;
}
@@ -127,6 +130,9 @@ export async function batchEditEvents(ids: string[], data: Partial<OntimeEvent>)
await scopedMutation({ patch: data, eventIds: ids });
notifyChanges({ timer: ids, external: true });
// notify event loader that rundown has changed
updateRuntimeOnChange();
}
/**
@@ -141,6 +147,9 @@ export async function reorderEvent(eventId: string, from: number, to: number) {
notifyChanges({ timer: true, external: true });
// notify event loader that rundown has changed
updateRuntimeOnChange();
return reorderedItem;
}
@@ -162,6 +171,9 @@ export async function swapEvents(from: string, to: string) {
await scopedMutation({ fromId: from, toId: to });
notifyChanges({ timer: true, external: true });
// notify event loader that rundown has changed
updateRuntimeOnChange();
}
/**
@@ -169,7 +181,8 @@ export async function swapEvents(from: string, to: string) {
* Called when we make changes to the rundown object
*/
function updateRuntimeOnChange() {
updateRundownData(getPlayableEvents());
// schedule an update for the end of the event loop
setImmediate(() => updateRundownData(getPlayableEvents()));
}
/**
@@ -180,10 +193,8 @@ export function notifyChanges(options: { timer?: boolean | string[]; external?:
const playableEvents = getPlayableEvents();
// notify timer service of changed events
// timer can be true or an array of changed IDs
if (Array.isArray(options.timer)) {
runtimeService.maybeUpdate(playableEvents, options.timer);
}
runtimeService.maybeUpdate(playableEvents);
const affected = Array.isArray(options.timer) ? options.timer : undefined;
runtimeService.maybeUpdate(playableEvents, affected);
}
if (options.external) {
@@ -8,7 +8,7 @@ export function getLink(currentIndex: number, rundown: OntimeRundown): OntimeEve
// currently the link is the previous event
for (let i = currentIndex - 1; i >= 0; i--) {
const event = rundown[i];
if (isOntimeEvent(event)) {
if (isOntimeEvent(event) && !event.skip) {
return event;
}
}
@@ -147,13 +147,13 @@ class RuntimeService {
*/
maybeUpdate(playableEvents: OntimeEvent[], affectedIds?: string[]) {
const state = runtimeState.getState();
const hasLoadedElements = state.eventNow || state.eventNext;
const hasLoadedElements = state.eventNow !== null || state.eventNext !== null;
if (!hasLoadedElements) {
return;
}
// we need to reload in a few scenarios:
// 1. we are not confident that changes do not affect running event
// 1. we are not confident that changes do not affect running event (eg. all events where changed)
const safeOption = typeof affectedIds === 'undefined';
// 2. the edited event is in memory (now or next) running
const eventInMemory = safeOption ? false : this.affectsLoaded(affectedIds);
@@ -166,15 +166,18 @@ class RuntimeService {
}
// load stuff again, but keep running if our events still exist
const eventNow = getEventWithId(state.eventNow.id);
if (eventNow) {
const onlyChangedNow = affectedIds?.length === 1 && affectedIds.at(0) === eventNow.id;
if (onlyChangedNow) {
runtimeState.reload(eventNow);
} else {
runtimeState.reloadAll(eventNow, playableEvents);
}
return;
}
// Maybe the event will become the next
isNext = this.isNewNext();
if (isNext) {
// TODO: do i need to load here?
runtimeState.loadNext(playableEvents);
}
}
+20 -1
View File
@@ -120,6 +120,11 @@ export function updateRundownData(playableRundown: OntimeEvent[]) {
runtimeState.runtime.plannedStart = firstEvent?.timeStart ?? null;
runtimeState.runtime.plannedEnd = lastEvent?.timeEnd ?? null;
if (runtimeState.runtime.plannedEnd === null) {
runtimeState.runtime.expectedEnd = null;
} else {
runtimeState.runtime.expectedEnd = (runtimeState.runtime.plannedEnd + runtimeState.runtime.offset) % dayInMs;
}
}
/**
@@ -255,6 +260,18 @@ export function reload(event?: OntimeEvent) {
runtimeState.timer.expectedFinish = getExpectedFinish(runtimeState);
}
/**
* Used in situations when we want to reload all events
* without interrupting timer
* @param eventNow
* @param playableEvents
*/
export function reloadAll(eventNow: OntimeEvent, playableEvents: OntimeEvent[]) {
loadNow(eventNow, playableEvents);
loadNext(playableEvents);
reload(eventNow);
}
export function start(state: RuntimeState = runtimeState): boolean {
if (state.eventNow === null) {
return false;
@@ -307,8 +324,10 @@ export function stop(state: RuntimeState = runtimeState): boolean {
if (state.timer.playback === Playback.Stop) {
return false;
}
runtimeState.runtime.actualStart = null;
clear();
runtimeState.runtime.actualStart = null;
runtimeState.runtime.expectedEnd = null;
return true;
}
@@ -31,7 +31,7 @@ export function getFirstEvent(rundown: OntimeRundownEntry[]): {
} {
for (let i = 0; i < rundown.length; i++) {
const firstEvent = rundown[i];
if (isOntimeEvent(firstEvent)) {
if (isOntimeEvent(firstEvent) && !firstEvent.skip) {
return { firstEvent, firstIndex: i };
}
}
@@ -54,7 +54,7 @@ export function getFirstEventNormal(
for (let i = 0; i < order.length; i++) {
const firstId = order[i];
const firstEvent = rundown[firstId];
if (isOntimeEvent(firstEvent)) {
if (isOntimeEvent(firstEvent) && !firstEvent.skip) {
return { firstEvent, firstIndex: i };
}
}
@@ -76,7 +76,7 @@ export function getLastEvent(rundown: OntimeRundown): {
for (let i = rundown.length - 1; i >= 0; i--) {
const lastEvent = rundown.at(i);
if (isOntimeEvent(lastEvent)) {
if (isOntimeEvent(lastEvent) && !lastEvent.skip) {
return { lastEvent, lastIndex: i };
}
}
@@ -103,7 +103,7 @@ export function getLastEventNormal(
for (let i = order.length - 1; i >= 0; i--) {
const lastId = order[i];
const lastEvent = rundown[lastId];
if (isOntimeEvent(lastEvent)) {
if (isOntimeEvent(lastEvent) && !lastEvent.skip) {
return { lastEvent, lastIndex: i };
}
}