mirror of
https://github.com/cpvalente/ontime.git
synced 2026-08-19 14:14:17 +00:00
feat: milestones
This commit is contained in:
committed by
Carlos Valente
parent
a7ae8598db
commit
f2913971db
@@ -13,6 +13,7 @@ import { enDash, timerPlaceholder } from '../../../common/utils/styleUtils';
|
||||
import TextLikeInput from '../../../views/cuesheet/cuesheet-table/cuesheet-table-elements/TextLikeInput';
|
||||
|
||||
import EntryEditorCustomFields from './composite/EventEditorCustomFields';
|
||||
import EventTextArea from './composite/EventTextArea';
|
||||
import EntryEditorTextInput from './composite/EventTextInput';
|
||||
|
||||
import style from './EntryEditor.module.scss';
|
||||
@@ -132,6 +133,7 @@ export default function BlockEditor({ block }: BlockEditorProps) {
|
||||
<SwatchSelect name='colour' value={block.colour} handleChange={handleSubmit} />
|
||||
</div>
|
||||
<EntryEditorTextInput field='title' label='Title' initialValue={block.title} submitHandler={handleSubmit} />
|
||||
<EventTextArea field='note' label='Note' initialValue={block.note} submitHandler={handleSubmit} />
|
||||
</div>
|
||||
|
||||
<div className={style.column}>
|
||||
@@ -139,7 +141,7 @@ export default function BlockEditor({ block }: BlockEditorProps) {
|
||||
Custom Fields
|
||||
{isEditor && <AppLink search='settings=feature_settings__custom'>Manage Custom Fields</AppLink>}
|
||||
</Editor.Title>
|
||||
<EntryEditorCustomFields fields={customFields} handleSubmit={handleSubmit} event={block} />
|
||||
<EntryEditorCustomFields fields={customFields} handleSubmit={handleSubmit} entry={block} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -69,7 +69,7 @@ export default function EventEditor({ event }: EventEditorProps) {
|
||||
Custom Fields
|
||||
{isEditor && <AppLink search='settings=feature_settings__custom'>Manage Custom Fields</AppLink>}
|
||||
</Editor.Title>
|
||||
<EntryEditorCustomFields fields={customFields} handleSubmit={handleSubmit} event={event} />
|
||||
<EntryEditorCustomFields fields={customFields} handleSubmit={handleSubmit} entry={event} />
|
||||
</div>
|
||||
<div className={style.column}>
|
||||
<Editor.Title>
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
import { useCallback } from 'react';
|
||||
import { OntimeMilestone } from 'ontime-types';
|
||||
|
||||
import * as Editor from '../../../common/components/editor-utils/EditorUtils';
|
||||
import SwatchSelect from '../../../common/components/input/colour-input/SwatchSelect';
|
||||
import Input from '../../../common/components/input/input/Input';
|
||||
import AppLink from '../../../common/components/link/app-link/AppLink';
|
||||
import { useEntryActions } from '../../../common/hooks/useEntryAction';
|
||||
import useCustomFields from '../../../common/hooks-query/useCustomFields';
|
||||
|
||||
import EntryEditorCustomFields from './composite/EventEditorCustomFields';
|
||||
import EventTextArea from './composite/EventTextArea';
|
||||
import EntryEditorTextInput from './composite/EventTextInput';
|
||||
|
||||
import style from './EntryEditor.module.scss';
|
||||
|
||||
// cue + title + colour + custom field labels
|
||||
export type MilestoneEditorUpdateTextFields = 'cue' | 'title' | 'colour' | string;
|
||||
|
||||
interface MilestoneEditorProps {
|
||||
milestone: OntimeMilestone;
|
||||
}
|
||||
export default function MilestoneEditor({ milestone }: MilestoneEditorProps) {
|
||||
const { data: customFields } = useCustomFields();
|
||||
const { updateEntry } = useEntryActions();
|
||||
|
||||
const handleSubmit = useCallback(
|
||||
(field: MilestoneEditorUpdateTextFields, value: string) => {
|
||||
// Handle custom fields
|
||||
if (typeof field === 'string' && field.startsWith('custom-')) {
|
||||
const fieldLabel = field.split('custom-')[1];
|
||||
updateEntry({ id: milestone.id, custom: { [fieldLabel]: value } });
|
||||
return;
|
||||
}
|
||||
// all other strings are text fields
|
||||
return updateEntry({ id: milestone.id, [field]: value });
|
||||
},
|
||||
[milestone.id, updateEntry],
|
||||
);
|
||||
|
||||
const isEditor = window.location.pathname.includes('editor');
|
||||
|
||||
return (
|
||||
<div className={style.content}>
|
||||
<div className={style.column}>
|
||||
<Editor.Title>Milestone data</Editor.Title>
|
||||
<div className={style.splitTwo}>
|
||||
<div>
|
||||
<Editor.Label htmlFor='entryId'>Milestone ID (read only)</Editor.Label>
|
||||
<Input id='entryId' data-testid='input-textfield' value={milestone.id} readOnly fluid />
|
||||
</div>
|
||||
<EntryEditorTextInput
|
||||
field='cue'
|
||||
label='Cue'
|
||||
initialValue={milestone.cue}
|
||||
submitHandler={handleSubmit}
|
||||
maxLength={10}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<Editor.Label>Colour</Editor.Label>
|
||||
<SwatchSelect name='colour' value={milestone.colour} handleChange={handleSubmit} />
|
||||
</div>
|
||||
<EntryEditorTextInput field='title' label='Title' initialValue={milestone.title} submitHandler={handleSubmit} />
|
||||
<EventTextArea field='note' label='Note' initialValue={milestone.note} submitHandler={handleSubmit} />
|
||||
</div>
|
||||
|
||||
<div className={style.column}>
|
||||
<Editor.Title>
|
||||
Custom Fields
|
||||
{isEditor && <AppLink search='settings=feature_settings__custom'>Manage Custom Fields</AppLink>}
|
||||
</Editor.Title>
|
||||
<EntryEditorCustomFields fields={customFields} handleSubmit={handleSubmit} entry={milestone} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,5 +1,13 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { isOntimeBlock, isOntimeDelay, OntimeBlock, OntimeEvent } from 'ontime-types';
|
||||
import {
|
||||
isOntimeBlock,
|
||||
isOntimeDelay,
|
||||
isOntimeEvent,
|
||||
isOntimeMilestone,
|
||||
OntimeBlock,
|
||||
OntimeEvent,
|
||||
OntimeMilestone,
|
||||
} from 'ontime-types';
|
||||
|
||||
import useRundown from '../../../common/hooks-query/useRundown';
|
||||
import { useEventSelection } from '../useEventSelection';
|
||||
@@ -8,6 +16,7 @@ import EventEditorFooter from './composite/EventEditorFooter';
|
||||
import BlockEditor from './BlockEditor';
|
||||
import EventEditor from './EventEditor';
|
||||
import EventEditorEmpty from './EventEditorEmpty';
|
||||
import MilestoneEditor from './MilestoneEditor';
|
||||
|
||||
import style from './EntryEditor.module.scss';
|
||||
|
||||
@@ -15,44 +24,56 @@ export default function RundownEntryEditor() {
|
||||
const selectedEvents = useEventSelection((state) => state.selectedEvents);
|
||||
const { data } = useRundown();
|
||||
|
||||
const [event, setEvent] = useState<OntimeEvent | OntimeBlock | null>(null);
|
||||
const [entry, setEntry] = useState<OntimeEvent | OntimeBlock | OntimeMilestone | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (data.order.length === 0) {
|
||||
setEvent(null);
|
||||
setEntry(null);
|
||||
return;
|
||||
}
|
||||
|
||||
const selectedEventId = Array.from(selectedEvents).at(0);
|
||||
if (!selectedEventId) {
|
||||
setEvent(null);
|
||||
setEntry(null);
|
||||
return;
|
||||
}
|
||||
const event = data.entries[selectedEventId];
|
||||
|
||||
if (event && !isOntimeDelay(event)) {
|
||||
setEvent(event);
|
||||
setEntry(event);
|
||||
} else {
|
||||
setEvent(null);
|
||||
setEntry(null);
|
||||
}
|
||||
}, [data.order, data.entries, selectedEvents]);
|
||||
|
||||
if (!event) {
|
||||
if (!entry) {
|
||||
return <EventEditorEmpty />;
|
||||
}
|
||||
|
||||
if (isOntimeBlock(event)) {
|
||||
if (isOntimeEvent(entry)) {
|
||||
return (
|
||||
<div className={style.entryEditor} data-testid='editor-container'>
|
||||
<BlockEditor block={event} />
|
||||
<EventEditor event={entry} />
|
||||
<EventEditorFooter id={entry.id} cue={entry.cue} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={style.entryEditor} data-testid='editor-container'>
|
||||
<EventEditor event={event} />
|
||||
<EventEditorFooter id={event.id} cue={event.cue} />
|
||||
</div>
|
||||
);
|
||||
if (isOntimeMilestone(entry)) {
|
||||
return (
|
||||
<div className={style.entryEditor} data-testid='editor-container'>
|
||||
<MilestoneEditor milestone={entry} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (isOntimeBlock(entry)) {
|
||||
return (
|
||||
<div className={style.entryEditor} data-testid='editor-container'>
|
||||
<BlockEditor block={entry} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { CSSProperties, Fragment } from 'react';
|
||||
import { CustomFields, OntimeBlock, OntimeEvent } from 'ontime-types';
|
||||
import { CustomFields, OntimeBlock, OntimeEvent, OntimeMilestone } from 'ontime-types';
|
||||
|
||||
import { getAccessibleColour } from '../../../../common/utils/styleUtils';
|
||||
import { EventEditorUpdateFields } from '../EventEditor';
|
||||
@@ -12,21 +12,21 @@ import style from '../EntryEditor.module.scss';
|
||||
|
||||
interface EntryEditorCustomFieldsProps {
|
||||
fields: CustomFields;
|
||||
event: OntimeEvent | OntimeBlock;
|
||||
entry: OntimeEvent | OntimeBlock | OntimeMilestone;
|
||||
handleSubmit: (field: EventEditorUpdateFields, value: string) => void;
|
||||
}
|
||||
|
||||
export default function EntryEditorCustomFields({
|
||||
fields: customFields,
|
||||
handleSubmit,
|
||||
event,
|
||||
entry,
|
||||
}: EntryEditorCustomFieldsProps) {
|
||||
return (
|
||||
<Fragment>
|
||||
{Object.keys(customFields).map((fieldKey) => {
|
||||
const key = `${event.id}-${fieldKey}`;
|
||||
const key = `${entry.id}-${fieldKey}`;
|
||||
const fieldName = `custom-${fieldKey}`;
|
||||
const initialValue = event.custom[fieldKey] ?? '';
|
||||
const initialValue = entry.custom[fieldKey] ?? '';
|
||||
const { backgroundColor, color } = getAccessibleColour(customFields[fieldKey].colour);
|
||||
const labelText = customFields[fieldKey].label;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user