refactor: style tweaks and bug fixes

- italic and bold for rundown metadata entries
- allow deleting groups
- make groups in order
- correct style for milestones
- open playing groups
- style tweaks
This commit is contained in:
Carlos Valente
2025-07-11 14:02:07 +02:00
parent 50b603f668
commit 4d419c0877
22 changed files with 118 additions and 39 deletions
@@ -107,7 +107,9 @@ export default function AutomationSettingsForm(props: AutomationSettingsProps) {
<Switch
size='large'
checked={watch('enabledAutomations')}
onCheckedChange={(value: boolean) => setValue('enabledAutomations', value, { shouldDirty: true })}
onCheckedChange={(value: boolean) =>
setValue('enabledAutomations', value, { shouldDirty: true, shouldValidate: true })
}
/>
</Panel.ListItem>
</Panel.ListGroup>
@@ -125,7 +127,9 @@ export default function AutomationSettingsForm(props: AutomationSettingsProps) {
<Switch
size='large'
checked={watch('enabledOscIn')}
onCheckedChange={(value: boolean) => setValue('enabledOscIn', value, { shouldDirty: true })}
onCheckedChange={(value: boolean) =>
setValue('enabledOscIn', value, { shouldDirty: true, shouldValidate: true })
}
/>
</Panel.ListItem>
<Panel.ListItem>
@@ -22,8 +22,15 @@ interface TriggerFormProps {
postSubmit: () => void;
}
export default function TriggerForm(props: TriggerFormProps) {
const { automations, initialId, initialTitle, initialAutomationId, initialTrigger, onCancel, postSubmit } = props;
export default function TriggerForm({
automations,
initialId,
initialTitle,
initialAutomationId,
initialTrigger,
onCancel,
postSubmit,
}: TriggerFormProps) {
const {
handleSubmit,
register,
@@ -35,8 +42,8 @@ export default function TriggerForm(props: TriggerFormProps) {
} = useForm<TriggerDTO>({
defaultValues: {
title: initialTitle,
trigger: initialTrigger,
automationId: initialAutomationId,
trigger: initialTrigger ?? (cycles[0].value as TimerLifeCycle | undefined),
automationId: initialAutomationId ?? automations?.[Object.keys(automations)[0]]?.id,
},
resetOptions: {
keepDirtyValues: true,
@@ -36,9 +36,9 @@ const staticSelectProperties = [
];
const staticNextSelectProperties = [
{ value: 'eventNow.id', label: 'Next ID' },
{ value: 'eventNow.title', label: 'Next Title' },
{ value: 'eventNow.cue', label: 'Next Cue' },
{ value: 'eventNext.id', label: 'Next ID' },
{ value: 'eventNext.title', label: 'Next Title' },
{ value: 'eventNext.cue', label: 'Next Cue' },
];
type SelectableField = {
@@ -28,7 +28,7 @@ export default function PreviewRundown(props: PreviewRundownProps) {
const fieldLabels = fieldKeys.map((key) => customFields[key].label);
return (
<Panel.Table>
<Panel.Table className={style.nowrap}>
<thead>
<tr>
<th>#</th>
@@ -1,5 +1,5 @@
import { useMemo } from 'react';
import { IoPause, IoPlay, IoPlaySkipBack, IoPlaySkipForward, IoReload, IoStop, IoTime } from 'react-icons/io5';
import { IoPause, IoPlay, IoPlaySkipBack, IoPlaySkipForward, IoReload, IoStop } from 'react-icons/io5';
import { Playback, TimerPhase } from 'ontime-types';
import { validatePlayback } from 'ontime-utils';
@@ -73,7 +73,7 @@ export default function PlaybackButtons(props: PlaybackButtonsProps) {
</div>
<div className={style.extra}>
<TapButton onClick={setPlayback.roll} disabled={disableRoll} theme={Playback.Roll} active={isRolling}>
<IoTime />
Roll
</TapButton>
<TapButton onClick={setPlayback.reload} disabled={disableReload}>
<IoReload className={style.invertX} />
+8 -1
View File
@@ -298,8 +298,15 @@ export default function Rundown({ data }: RundownProps) {
return;
}
const index = order.findIndex((id) => id === featureData.selectedEventId);
// @ts-expect-error -- but we safely check if the parent property exists
const maybeParent = entries[featureData.selectedEventId]?.parent;
if (maybeParent) {
// open the group
setCollapsedGroups((prev) => [...prev].filter((id) => id !== maybeParent));
}
setSelectedEvents({ id: featureData.selectedEventId, selectMode: 'click', index });
}, [editorMode, featureData.selectedEventId, order, setSelectedEvents]);
}, [editorMode, entries, featureData.selectedEventId, order, setCollapsedGroups, setSelectedEvents]);
/**
* On drag end, we reorder the events
@@ -1,6 +1,6 @@
import { EntryId, OntimeBlock, OntimeDelay, OntimeEvent, RundownEntries, SupportedEntry } from 'ontime-types';
import { makeRundownMetadata, makeSortableList, moveDown, moveUp } from '../rundown.utils';
import { makeRundownMetadata, makeSortableList, moveDown, moveUp, orderEntries } from '../rundown.utils';
describe('makeRundownMetadata()', () => {
it('processes nested rundown data', () => {
@@ -339,7 +339,6 @@ describe('makeSortableList()', () => {
});
});
describe('moveUp()', () => {
const rundown = {
entries: {
@@ -535,3 +534,40 @@ describe('moveDown()', () => {
});
});
});
describe('orderEntries()', () => {
it('should return an empty array when both inputs are empty', () => {
const unorderedArray: string[] = [];
const flatOrder: string[] = [];
const result = orderEntries(unorderedArray, flatOrder);
expect(result).toEqual([]);
});
it('should return an ordered array based on flatOrder', () => {
const unorderedArray = ['b', 'a', 'c'];
const flatOrder = ['a', 'b', 'c'];
const result = orderEntries(unorderedArray, flatOrder);
expect(result).toEqual(['a', 'b', 'c']);
});
it('should ignore elements in unorderedArray not present in flatOrder', () => {
const unorderedArray = ['b', 'a', 'c', 'd'];
const flatOrder = ['a', 'b', 'c'];
const result = orderEntries(unorderedArray, flatOrder);
expect(result).toEqual(['a', 'b', 'c']);
});
it('should handle cases where flatOrder has elements not in unorderedArray', () => {
const unorderedArray = ['b', 'a'];
const flatOrder = ['a', 'b', 'c'];
const result = orderEntries(unorderedArray, flatOrder);
expect(result).toEqual(['a', 'b']);
});
it('should return an empty array if unorderedArray has no matching elements in flatOrder', () => {
const unorderedArray = ['x', 'y', 'z'];
const flatOrder = ['a', 'b', 'c'];
const result = orderEntries(unorderedArray, flatOrder);
expect(result).toEqual([]);
});
});
@@ -43,7 +43,6 @@ export default function RundownBlock({ data, hasCursor, collapsed, onCollapse }:
},
{
type: 'item',
label: 'Ungroup',
icon: IoFolderOpenOutline,
onClick: () => ungroup(data.id),
@@ -55,7 +54,6 @@ export default function RundownBlock({ data, hasCursor, collapsed, onCollapse }:
label: 'Delete Group',
icon: IoTrash,
onClick: () => deleteEntry([data.id]),
disabled: true,
},
]);
@@ -94,7 +94,7 @@ $skip-opacity: 0.2;
cursor: pointer;
background-color: $gray-1050; // to override inline
color: $section-white;
color: $section-white; // to override inline
font-size: 1rem;
border-radius: 3px 0 0 3px;
@@ -6,7 +6,8 @@
margin-left: calc(2rem + 1px); // binder + border
margin-block: 0.125rem;
padding-right: 0.25rem;
background-color: color-mix(in srgb, var(--user-bg, $block-bg) 15%, transparent 85%);
background-color: $gray-1050; // to override inline
color: $section-white; // to override inline
display: grid;
grid-template-columns: 2rem 8rem 1fr auto;
@@ -23,7 +24,8 @@
height: 100%;
display: grid;
place-content: center;
background-color: var(--user-bg, $block-bg);
background-color: $gray-1050; // to override inline
color: $section-white; // to override inline
}
.drag {
@@ -76,17 +76,11 @@ export default function RundownMilestone({ colour, cue, entryId, hasCursor, titl
className={cx([style.milestone, hasCursor ? style.hasCursor : null])}
ref={setNodeRef}
onClick={handleFocusClick}
style={{ ...dragStyle, '--user-bg': colour }}
style={dragStyle}
data-testid='rundown-milestone'
>
<div className={style.binder}>
<span
className={style.drag}
style={{ ...binderColours }}
ref={handleRef}
{...dragAttributes}
{...dragListeners}
>
<div className={style.binder} style={{ ...binderColours }}>
<span className={style.drag} ref={handleRef} {...dragAttributes} {...dragListeners}>
<IoReorderTwo />
</span>
</div>
@@ -306,3 +306,17 @@ export function moveDown(
// default - swap positions with next entry
return { destinationId: nextEntryId, order: 'after' };
}
/**
* Reorders unorderedArray to match the flatOrder entries
* Useful for operations that convert selections (out of order) to rundown
*/
export function orderEntries(unorderedArray: EntryId[], flatOrder: EntryId[]): EntryId[] {
const orderedArray: EntryId[] = [];
for (const id of flatOrder) {
if (unorderedArray.includes(id)) {
orderedArray.push(id);
}
}
return orderedArray;
}