* refactor: align header columns

* refactor: improve scrollbar visibility

* refactor: center align table elements

* refactor: make param elements stateful

* fix: issue with collapsed elements not loosing value

* fix: prevent search params containing multiple alias references

* fix: the issue where a file disappears if it is both migrated and recovered in the same load operation (#1744)

* refactor: disable group action for elements in groups

* refactor: move context menu items into the event element (#1747)

* feat: sheet import new features for v4 (#1730)

* import milestone

* fixup! import milestone

* test: milestone import

* add entries to group

stop on new group or on group-end type

* fixup! add entries to group

* cleanup

* add event target duration

* link start if undefined

* add skip import type

* extract some to the excel paresing functions

* tweaks to presentation

* move file

---------

Co-authored-by: Carlos Valente <carlosvalente@pm.me>

* fix: notify runtimeStore of events bieng groupd

* fix: improve authentication and stage detection in demo

* chore: ship logo with project

* fix: client is referenced by name

* fix: prevent reflow in event editor

* fix: stale render on selected event due to ref mismatch

* Create/Load/Delete multiple rundowns (#1696)

* refactor: restore last loaded rundown

* refactor: initialise rundown in ProjectService

* feat: allow switching rundowns

ensure on coordination between the db object and the working object

server provide list of rundowns

implement switch in the UI

implement delete

implement new rundown button

* fix: render order for floating button

* refactor: appropriate names to service

* refactor: rundown endpoints

* refactor: save last loaded rundown ID

* refactor: rundown management UI

* refactor: emit refetch all on project load

---------

Co-authored-by: Carlos Valente <carlosvalente@pm.me>

* feat: recover single event subscription

* fixup! feat: sheet import new features for v4 (#1730)

* fix: prevent dropping a group inside another

* fixup! refactor: move context menu items into the event element (#1747)

* fix: prevent stale references to custom fields

* fix: propagate updates to all rundowns

* refactor: client rundown metadata (#1728)

* generate metadata in the hook

* move test

* ensure there is always a last element

* use for-loop

* update metadata in useEfect

* fully extract metadata generation

* use direct assignment

* cleanup

---------

Co-authored-by: Carlos Valente <carlosvalente@pm.me>

* refactor: small imporvement and tests for coerce functions (#1752)

* refactor: small imporvement and tests for coerce functions

add test `coerceString`

add test `coerceBoolean`

add test `coerceColour`

* remove old todo

* fix: consistent quick add behaviour

* refactor: create flat rundown with metadata

* fix: show add buttons on top

* feat: allow editing milestones

* refactor: style tweaks to rundown elements

refactor: milestones are full width

refactor: cuesheet header alignment

fix: editor styling in cuesheet

* refactor: virtualise table

* refactor: improve overscan (#1758)

* bump version to 4.0.0-alpha.5

---------

Co-authored-by: Alex Christoffer Rasmussen <ac@omnivox.dk>
This commit is contained in:
Carlos Valente
2025-09-03 15:50:20 +02:00
committed by GitHub
parent 3c41b40c5f
commit ae15f3cdc5
102 changed files with 2950 additions and 2104 deletions
@@ -1,10 +1,11 @@
import { useEffect, useState } from 'react';
import { isOntimeEvent, isOntimeGroup, OntimeEntry } from 'ontime-types';
import { isOntimeEvent, isOntimeGroup, isOntimeMilestone, OntimeEntry } from 'ontime-types';
import useRundown from '../../../common/hooks-query/useRundown';
import EventEditor from './EventEditor';
import GroupEditor from './GroupEditor';
import MilestoneEditor from './MilestoneEditor';
import style from './EntryEditor.module.scss';
@@ -38,6 +39,14 @@ export default function CuesheetEntryEditor({ entryId }: CuesheetEntryEditorProp
);
}
if (isOntimeMilestone(entry)) {
return (
<div className={style.inModal} data-testid='editor-container'>
<MilestoneEditor milestone={entry} />
</div>
);
}
if (isOntimeGroup(entry)) {
return (
<div className={style.inModal} data-testid='editor-container'>
@@ -1,10 +1,21 @@
.entryEditor {
.cuesheetEditor,
.rundownEditor {
max-height: 100%;
display: flex;
flex-direction: column;
overflow-x: auto;
}
.rundownEditor {
// width is locked to swatch picker elements
width: calc(15 * 2rem + 13 * 0.5rem);
// we dont want a scrollbar when in the modal
.content {
overflow-y: scroll;
}
}
.content {
padding-inline: 0.5rem 1.5rem;
padding-bottom: 4rem;
@@ -13,7 +24,6 @@
display: flex;
flex-direction: column;
gap: 1.5rem;
overflow-y: scroll;
}
.timeSettings {
@@ -52,7 +52,7 @@ export default function RundownEntryEditor() {
if (isOntimeEvent(entry)) {
return (
<div className={style.entryEditor} data-testid='editor-container'>
<div className={style.rundownEditor} data-testid='editor-container'>
<EventEditor event={entry} />
<EventEditorFooter id={entry.id} cue={entry.cue} />
</div>
@@ -61,7 +61,7 @@ export default function RundownEntryEditor() {
if (isOntimeMilestone(entry)) {
return (
<div className={style.entryEditor} data-testid='editor-container'>
<div className={style.rundownEditor} data-testid='editor-container'>
<MilestoneEditor milestone={entry} />
</div>
);
@@ -69,7 +69,7 @@ export default function RundownEntryEditor() {
if (isOntimeGroup(entry)) {
return (
<div className={style.entryEditor} data-testid='editor-container'>
<div className={style.rundownEditor} data-testid='editor-container'>
<GroupEditor group={entry} />
</div>
);
@@ -1,7 +1,7 @@
.triggerForm {
padding-block: 0.5rem;
display: grid;
grid-template-columns: 1fr 1fr auto auto;
grid-template-columns: 8rem 1fr auto 2rem;
gap: 0.5rem;
align-items: center;
}
@@ -9,7 +9,7 @@
.trigger {
padding: 0.25rem 0.5rem;
display: grid;
grid-template-columns: 1fr 1fr auto;
grid-template-columns: 8rem 1fr 2rem;
align-items: center;
&:nth-child(even) {
@@ -155,7 +155,7 @@ function ExistingEventTriggers({ eventId, triggers }: ExistingEventTriggersProps
<div key={id} className={style.trigger}>
<Tag>{triggerLifeCycle}</Tag>
<Tag>{automationTitle}</Tag>
<IconButton variant='subtle-destructive' onClick={() => handleDelete(id)}>
<IconButton variant='ghosted-destructive' onClick={() => handleDelete(id)}>
<IoTrash />
</IconButton>
</div>
@@ -6,6 +6,7 @@
height: 1px;
background: $blue-500;
z-index: $zindex-floating;
}
.addButton {
@@ -9,68 +9,53 @@ import { useEntryActions } from '../../../../common/hooks/useEntryAction';
import style from './QuickAddInline.module.scss';
interface QuickAddInlineProps {
previousEventId: MaybeString;
referenceEntryId: MaybeString;
parentGroup: MaybeString;
placement: 'before' | 'after';
}
export default memo(QuickAddInline);
function QuickAddInline({ previousEventId, parentGroup }: QuickAddInlineProps) {
function QuickAddInline({ referenceEntryId, parentGroup, placement }: QuickAddInlineProps) {
const { addEntry } = useEntryActions();
const addEvent = () => {
addEntry(
{
type: SupportedEntry.Event,
parent: parentGroup,
},
{
after: previousEventId,
lastEventId: previousEventId,
},
);
};
const addDelay = () => {
addEntry(
{ type: SupportedEntry.Delay, parent: parentGroup },
{
lastEventId: previousEventId,
after: previousEventId,
},
);
};
const addMilestone = () => {
addEntry(
{ type: SupportedEntry.Milestone, parent: parentGroup },
{
lastEventId: previousEventId,
after: previousEventId,
},
);
};
const addGroup = () => {
if (parentGroup !== null) {
return;
const handleAddEntry = (type: SupportedEntry) => {
if (placement === 'before') {
addEntry(
{ type, parent: type !== SupportedEntry.Group ? parentGroup : null },
{
before: referenceEntryId,
},
);
} else {
addEntry(
{ type, parent: type !== SupportedEntry.Group ? parentGroup : null },
{
lastEventId: referenceEntryId,
after: referenceEntryId,
},
);
}
addEntry(
{ type: SupportedEntry.Group },
{
lastEventId: previousEventId,
after: previousEventId,
},
);
};
return (
<div className={style.quickAdd} data-testid='quick-add-inline'>
<DropdownMenu
items={[
{ type: 'item', icon: IoAdd, label: 'Add Event', onClick: addEvent },
{ type: 'item', icon: IoAdd, label: 'Add Delay', onClick: addDelay },
{ type: 'item', icon: IoAdd, label: 'Add Milestone', onClick: addMilestone },
{ type: 'item', icon: IoAdd, label: 'Add Group', onClick: addGroup, disabled: parentGroup !== null },
{ type: 'item', icon: IoAdd, label: 'Add Event', onClick: () => handleAddEntry(SupportedEntry.Event) },
{ type: 'item', icon: IoAdd, label: 'Add Delay', onClick: () => handleAddEntry(SupportedEntry.Delay) },
{
type: 'item',
icon: IoAdd,
label: 'Add Milestone',
onClick: () => handleAddEntry(SupportedEntry.Milestone),
},
{
type: 'item',
icon: IoAdd,
label: 'Add Group',
onClick: () => handleAddEntry(SupportedEntry.Group),
disabled: parentGroup !== null,
},
]}
render={<IconButton size='small' variant='primary' className={style.addButton} />}
>