mirror of
https://github.com/cpvalente/ontime.git
synced 2026-09-10 08:39:34 +00:00
feat: edit groups
This commit is contained in:
committed by
Carlos Valente
parent
4c08482258
commit
473f50b493
@@ -0,0 +1,71 @@
|
||||
import { CSSProperties, Fragment } from 'react';
|
||||
import { CustomFields, OntimeBlock, OntimeEvent } from 'ontime-types';
|
||||
|
||||
import { getAccessibleColour } from '../../../../common/utils/styleUtils';
|
||||
import { EventEditorUpdateFields } from '../EventEditor';
|
||||
|
||||
import EventEditorImage from './EventEditorImage';
|
||||
import EventTextArea from './EventTextArea';
|
||||
import EntryEditorTextInput from './EventTextInput';
|
||||
|
||||
import style from '../EntryEditor.module.scss';
|
||||
|
||||
interface EntryEditorCustomFieldsProps {
|
||||
fields: CustomFields;
|
||||
event: OntimeEvent | OntimeBlock;
|
||||
handleSubmit: (field: EventEditorUpdateFields, value: string) => void;
|
||||
}
|
||||
|
||||
export default function EntryEditorCustomFields({
|
||||
fields: customFields,
|
||||
handleSubmit,
|
||||
event,
|
||||
}: EntryEditorCustomFieldsProps) {
|
||||
return (
|
||||
<Fragment>
|
||||
{Object.keys(customFields).map((fieldKey) => {
|
||||
const key = `${event.id}-${fieldKey}`;
|
||||
const fieldName = `custom-${fieldKey}`;
|
||||
const initialValue = event.custom[fieldKey] ?? '';
|
||||
const { backgroundColor, color } = getAccessibleColour(customFields[fieldKey].colour);
|
||||
const labelText = customFields[fieldKey].label;
|
||||
|
||||
if (customFields[fieldKey].type === 'string') {
|
||||
return (
|
||||
<EventTextArea
|
||||
key={key}
|
||||
field={fieldName}
|
||||
label={labelText}
|
||||
initialValue={initialValue}
|
||||
submitHandler={handleSubmit}
|
||||
className={style.decorated}
|
||||
style={{ '--decorator-bg': backgroundColor, '--decorator-color': color } as CSSProperties}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (customFields[fieldKey].type === 'image') {
|
||||
return (
|
||||
<div key={key} className={style.customImage}>
|
||||
<EntryEditorTextInput
|
||||
key={key}
|
||||
field={fieldName}
|
||||
label={labelText}
|
||||
initialValue={initialValue}
|
||||
placeholder='Paste image URL'
|
||||
submitHandler={handleSubmit}
|
||||
className={style.decorated}
|
||||
maxLength={255}
|
||||
style={{ '--decorator-bg': backgroundColor, '--decorator-color': color } as CSSProperties}
|
||||
/>
|
||||
<EventEditorImage src={initialValue} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// we should have exhausted all types by now
|
||||
return null;
|
||||
})}
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
.footer {
|
||||
border-top: 1px solid $white-10;
|
||||
padding-top: 1rem;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
import { memo } from 'react';
|
||||
|
||||
import CopyTag from '../../../../common/components/copy-tag/CopyTag';
|
||||
|
||||
import style from './EventEditorFooter.module.scss';
|
||||
|
||||
interface EventEditorFooterProps {
|
||||
id: string;
|
||||
cue: string;
|
||||
}
|
||||
|
||||
export default memo(EventEditorFooter);
|
||||
function EventEditorFooter({ id, cue }: EventEditorFooterProps) {
|
||||
const loadById = `/ontime/load/id "${id}"`;
|
||||
const loadByCue = `/ontime/load/cue "${cue}"`;
|
||||
|
||||
return (
|
||||
<div className={style.footer}>
|
||||
<CopyTag copyValue={loadById} label='OSC trigger by ID'>
|
||||
{loadById}
|
||||
</CopyTag>
|
||||
<CopyTag copyValue={loadByCue} label='OSC trigger by cue'>
|
||||
{loadByCue}
|
||||
</CopyTag>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
.imageContainer {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: $gray-1250;
|
||||
display: grid;
|
||||
place-content: center;
|
||||
}
|
||||
|
||||
.imageOverlay {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
import style from './EventEditorImage.module.scss';
|
||||
|
||||
interface EventEditorImageProps {
|
||||
src: string;
|
||||
}
|
||||
|
||||
export default function EventEditorImage({ src }: EventEditorImageProps) {
|
||||
return (
|
||||
<div className={style.imageContainer}>
|
||||
<img loading='lazy' src={src} />
|
||||
<div className={style.imageOverlay} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,177 @@
|
||||
import { memo } from 'react';
|
||||
import { IoInformationCircle } from 'react-icons/io5';
|
||||
import { Tooltip } from '@chakra-ui/react';
|
||||
import { EndAction, TimerType, TimeStrategy } from 'ontime-types';
|
||||
import { millisToString, parseUserTime } from 'ontime-utils';
|
||||
|
||||
import * as Editor from '../../../../common/components/editor-utils/EditorUtils';
|
||||
import TimeInput from '../../../../common/components/input/time-input/TimeInput';
|
||||
import Select from '../../../../common/components/select/Select';
|
||||
import Switch from '../../../../common/components/switch/Switch';
|
||||
import { useEntryActions } from '../../../../common/hooks/useEntryAction';
|
||||
import { millisToDelayString } from '../../../../common/utils/dateConfig';
|
||||
import TimeInputFlow from '../../time-input-flow/TimeInputFlow';
|
||||
|
||||
import style from '../EntryEditor.module.scss';
|
||||
|
||||
interface EventEditorTimesProps {
|
||||
eventId: string;
|
||||
timeStart: number;
|
||||
timeEnd: number;
|
||||
duration: number;
|
||||
timeStrategy: TimeStrategy;
|
||||
linkStart: boolean;
|
||||
countToEnd: boolean;
|
||||
delay: number;
|
||||
endAction: EndAction;
|
||||
timerType: TimerType;
|
||||
timeWarning: number;
|
||||
timeDanger: number;
|
||||
}
|
||||
|
||||
type HandledActions = 'countToEnd' | 'timerType' | 'endAction' | 'timeWarning' | 'timeDanger';
|
||||
|
||||
export default memo(EventEditorTimes);
|
||||
function EventEditorTimes({
|
||||
eventId,
|
||||
timeStart,
|
||||
timeEnd,
|
||||
duration,
|
||||
timeStrategy,
|
||||
linkStart,
|
||||
countToEnd,
|
||||
delay,
|
||||
endAction,
|
||||
timerType,
|
||||
timeWarning,
|
||||
timeDanger,
|
||||
}: EventEditorTimesProps) {
|
||||
const { updateEntry } = useEntryActions();
|
||||
|
||||
const handleSubmit = (field: HandledActions, value: string | boolean) => {
|
||||
if (field === 'countToEnd') {
|
||||
updateEntry({ id: eventId, countToEnd: value as boolean });
|
||||
return;
|
||||
}
|
||||
|
||||
if (field === 'timeWarning' || field === 'timeDanger') {
|
||||
const newTime = parseUserTime(value as string);
|
||||
updateEntry({ id: eventId, [field]: newTime });
|
||||
return;
|
||||
}
|
||||
|
||||
if (field === 'timerType' || field === 'endAction') {
|
||||
updateEntry({ id: eventId, [field]: value });
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
const hasDelay = delay !== 0;
|
||||
const delayLabel = hasDelay
|
||||
? `Event is ${millisToDelayString(delay, 'expanded')}. New schedule ${millisToString(
|
||||
timeStart + delay,
|
||||
)} → ${millisToString(timeEnd + delay)}`
|
||||
: '';
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className={style.column}>
|
||||
<Editor.Title>Event schedule</Editor.Title>
|
||||
<div>
|
||||
<div className={style.inline}>
|
||||
<TimeInputFlow
|
||||
eventId={eventId}
|
||||
timeStart={timeStart}
|
||||
timeEnd={timeEnd}
|
||||
duration={duration}
|
||||
timeStrategy={timeStrategy}
|
||||
linkStart={linkStart}
|
||||
delay={delay}
|
||||
countToEnd={countToEnd}
|
||||
showLabels
|
||||
/>
|
||||
</div>
|
||||
<div className={style.delayLabel}>{delayLabel}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={style.column}>
|
||||
<Editor.Title>Event Behaviour</Editor.Title>
|
||||
<div className={style.splitTwo}>
|
||||
<div>
|
||||
<Editor.Label htmlFor='endAction'>End Action</Editor.Label>
|
||||
<Select
|
||||
value={endAction}
|
||||
onChange={(value) => handleSubmit('endAction', value)}
|
||||
options={[
|
||||
{ value: EndAction.None, label: 'None' },
|
||||
{ value: EndAction.LoadNext, label: 'Load next event' },
|
||||
{ value: EndAction.PlayNext, label: 'Play next event' },
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<Editor.Label htmlFor='countToEnd'>Count to End</Editor.Label>
|
||||
<Editor.Label className={style.switchLabel}>
|
||||
<Switch
|
||||
id='countToEnd'
|
||||
checked={countToEnd}
|
||||
onCheckedChange={(value) => handleSubmit('countToEnd', value)}
|
||||
/>
|
||||
{countToEnd ? 'On' : 'Off'}
|
||||
</Editor.Label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={style.column}>
|
||||
<Editor.Title>
|
||||
<Tooltip label='Changes how the timer is displayed in different views. It is not reflected in the rundown'>
|
||||
<span>
|
||||
Display Options
|
||||
<IoInformationCircle className={style.tooltipIcon} />
|
||||
</span>
|
||||
</Tooltip>
|
||||
</Editor.Title>
|
||||
<div className={style.splitTwo}>
|
||||
<div>
|
||||
<Editor.Label htmlFor='timerType'>Timer Type</Editor.Label>
|
||||
<Select
|
||||
value={timerType}
|
||||
onChange={(value) => handleSubmit('timerType', value)}
|
||||
options={[
|
||||
{ value: TimerType.CountDown, label: 'Count down' },
|
||||
{ value: TimerType.CountUp, label: 'Count up' },
|
||||
{ value: TimerType.Clock, label: 'Clock' },
|
||||
{ value: TimerType.None, label: 'None' },
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className={style.inline}>
|
||||
<div>
|
||||
<Editor.Label htmlFor='timeWarning'>Warning Time</Editor.Label>
|
||||
<TimeInput
|
||||
id='timeWarning'
|
||||
name='timeWarning'
|
||||
submitHandler={handleSubmit}
|
||||
time={timeWarning}
|
||||
placeholder='Duration'
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<Editor.Label htmlFor='timeDanger'>Danger Time</Editor.Label>
|
||||
<TimeInput
|
||||
id='timeDanger'
|
||||
name='timeDanger'
|
||||
submitHandler={handleSubmit}
|
||||
time={timeDanger}
|
||||
placeholder='Duration'
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
import { memo } from 'react';
|
||||
import { sanitiseCue } from 'ontime-utils';
|
||||
|
||||
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 { type EventEditorUpdateFields } from '../EventEditor';
|
||||
|
||||
import EventTextArea from './EventTextArea';
|
||||
import EntryEditorTextInput from './EventTextInput';
|
||||
|
||||
import style from '../EntryEditor.module.scss';
|
||||
|
||||
interface EventEditorTitlesProps {
|
||||
eventId: string;
|
||||
cue: string;
|
||||
title: string;
|
||||
note: string;
|
||||
colour: string;
|
||||
handleSubmit: (field: EventEditorUpdateFields, value: string) => void;
|
||||
}
|
||||
|
||||
export default memo(EventEditorTitles);
|
||||
function EventEditorTitles({ eventId, cue, title, note, colour, handleSubmit }: EventEditorTitlesProps) {
|
||||
const cueSubmitHandler = (_field: string, newValue: string) => {
|
||||
handleSubmit('cue', sanitiseCue(newValue));
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={style.column}>
|
||||
<Editor.Title>Event Data</Editor.Title>
|
||||
<div className={style.splitTwo}>
|
||||
<div>
|
||||
<Editor.Label htmlFor='eventId'>Event ID (read only)</Editor.Label>
|
||||
<Input id='eventId' data-testid='input-textfield' value={eventId} readOnly fluid />
|
||||
</div>
|
||||
<EntryEditorTextInput
|
||||
field='cue'
|
||||
label='Cue'
|
||||
initialValue={cue}
|
||||
submitHandler={cueSubmitHandler}
|
||||
maxLength={10}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<Editor.Label>Colour</Editor.Label>
|
||||
<SwatchSelect name='colour' value={colour} handleChange={handleSubmit} />
|
||||
</div>
|
||||
<EntryEditorTextInput field='title' label='Title' initialValue={title} submitHandler={handleSubmit} />
|
||||
<EventTextArea field='note' label='Note' initialValue={note} submitHandler={handleSubmit} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
.triggerForm {
|
||||
padding-block: 0.5rem;
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr auto auto;
|
||||
gap: 0.5rem;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.trigger {
|
||||
padding: 0.25rem 0.5rem;
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr auto;
|
||||
align-items: center;
|
||||
|
||||
&:nth-child(even) {
|
||||
background-color: $white-1;
|
||||
}
|
||||
|
||||
& > span {
|
||||
width: fit-content;
|
||||
}
|
||||
}
|
||||
|
||||
.errorLabel {
|
||||
color: $red-500;
|
||||
}
|
||||
|
||||
.success {
|
||||
color: $green-500;
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
import { Fragment, useCallback, useState } from 'react';
|
||||
import { IoAlertCircle, IoCheckmarkCircle, IoTrash } from 'react-icons/io5';
|
||||
import { Tooltip } from '@chakra-ui/react';
|
||||
import { TimerLifeCycle, timerLifecycleValues, Trigger } from 'ontime-types';
|
||||
import { generateId } from 'ontime-utils';
|
||||
|
||||
import Button from '../../../../common/components/buttons/Button';
|
||||
import IconButton from '../../../../common/components/buttons/IconButton';
|
||||
import Select from '../../../../common/components/select/Select';
|
||||
import Tag from '../../../../common/components/tag/Tag';
|
||||
import { useEntryActions } from '../../../../common/hooks/useEntryAction';
|
||||
import useAutomationSettings from '../../../../common/hooks-query/useAutomationSettings';
|
||||
|
||||
import { eventTriggerOptions } from './eventTrigger.constants';
|
||||
|
||||
import style from './EventEditorTriggers.module.scss';
|
||||
|
||||
interface EventEditorTriggersProps {
|
||||
eventId: string;
|
||||
triggers: Trigger[];
|
||||
}
|
||||
|
||||
export default function EventEditorTriggers({ triggers, eventId }: EventEditorTriggersProps) {
|
||||
const showTriggers = triggers.length > 0;
|
||||
|
||||
return (
|
||||
<>
|
||||
{showTriggers && <ExistingEventTriggers triggers={triggers} eventId={eventId} />}
|
||||
<EventTriggerForm triggers={triggers} eventId={eventId} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
interface EventTriggerFormProps {
|
||||
eventId: string;
|
||||
triggers?: Trigger[];
|
||||
}
|
||||
|
||||
function EventTriggerForm({ eventId, triggers }: EventTriggerFormProps) {
|
||||
const { data: automationSettings } = useAutomationSettings();
|
||||
const { updateEntry } = useEntryActions();
|
||||
const [automationId, setAutomationId] = useState<string | undefined>(undefined);
|
||||
const [cycleValue, setCycleValue] = useState(TimerLifeCycle.onStart);
|
||||
|
||||
const handleSubmit = (triggerLifeCycle: TimerLifeCycle, automationId: string) => {
|
||||
const newTriggers = triggers ?? new Array<Trigger>();
|
||||
const id = generateId();
|
||||
newTriggers.push({ id, title: '', trigger: triggerLifeCycle, automationId });
|
||||
updateEntry({ id: eventId, triggers: newTriggers });
|
||||
};
|
||||
|
||||
const getValidationError = (cycle: TimerLifeCycle, automationId?: string): string | undefined => {
|
||||
if (automationId === undefined) {
|
||||
return 'Select an automation';
|
||||
}
|
||||
if (!Object.keys(automationSettings.automations).includes(automationId)) {
|
||||
return 'This automation does not exist';
|
||||
}
|
||||
if (triggers === undefined) {
|
||||
return;
|
||||
}
|
||||
return Object.values(triggers).some((t) => t.automationId === automationId && t.trigger === cycle)
|
||||
? 'Automation can only be used once'
|
||||
: undefined;
|
||||
};
|
||||
|
||||
const validationError = getValidationError(cycleValue, automationId);
|
||||
|
||||
return (
|
||||
<div className={style.triggerForm}>
|
||||
<Select
|
||||
value={cycleValue}
|
||||
placeholder='Choose a trigger'
|
||||
onChange={(value) => setCycleValue(value)}
|
||||
options={eventTriggerOptions.map((cycle) => ({ value: cycle, label: cycle }))}
|
||||
/>
|
||||
|
||||
<Select
|
||||
value={automationId}
|
||||
placeholder='Choose an automation'
|
||||
onChange={(value) => setAutomationId(value)}
|
||||
options={Object.values(automationSettings.automations).map(({ id, title }) => ({ value: id, label: title }))}
|
||||
/>
|
||||
|
||||
<Button
|
||||
disabled={validationError !== undefined}
|
||||
onClick={() => automationId && handleSubmit(cycleValue, automationId)}
|
||||
>
|
||||
Add
|
||||
</Button>
|
||||
{validationError !== undefined ? (
|
||||
<Tooltip label={validationError} shouldWrapChildren>
|
||||
<IoAlertCircle className={style.errorLabel} />
|
||||
</Tooltip>
|
||||
) : (
|
||||
<IoCheckmarkCircle className={style.success} />
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
interface ExistingEventTriggersProps {
|
||||
eventId: string;
|
||||
triggers: Trigger[];
|
||||
}
|
||||
|
||||
function ExistingEventTriggers({ eventId, triggers }: ExistingEventTriggersProps) {
|
||||
const { updateEntry } = useEntryActions();
|
||||
const { data: automationSettings } = useAutomationSettings();
|
||||
|
||||
const handleDelete = useCallback(
|
||||
(triggerId: string) => {
|
||||
const newTriggers = triggers.filter((trigger) => trigger.id !== triggerId);
|
||||
updateEntry({ id: eventId, triggers: newTriggers });
|
||||
},
|
||||
[eventId, triggers, updateEntry],
|
||||
);
|
||||
|
||||
const filteredTriggers: Record<string, Trigger[]> = {};
|
||||
|
||||
// sort triggers out into groups by the Lifecycle they are on
|
||||
timerLifecycleValues.forEach((triggerType) => {
|
||||
const thisTriggerType = triggers.filter((trigger) => trigger.trigger === triggerType);
|
||||
if (thisTriggerType.length) {
|
||||
Object.assign(filteredTriggers, { [triggerType]: thisTriggerType });
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<div>
|
||||
{Object.entries(filteredTriggers).map(([triggerLifeCycle, triggerGroup]) => (
|
||||
<Fragment key={triggerLifeCycle}>
|
||||
{triggerGroup.map((trigger) => {
|
||||
const { id, automationId } = trigger;
|
||||
const automationTitle = automationSettings.automations[automationId]?.title ?? '<MISSING AUTOMATION>';
|
||||
return (
|
||||
<div key={id} className={style.trigger}>
|
||||
<Tag>{triggerLifeCycle}</Tag>
|
||||
<Tag>{automationTitle}</Tag>
|
||||
<IconButton variant='subtle-destructive' onClick={() => handleDelete(id)}>
|
||||
<IoTrash />
|
||||
</IconButton>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</Fragment>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
import { type CSSProperties, useCallback, useRef } from 'react';
|
||||
|
||||
import * as Editor from '../../../../common/components/editor-utils/EditorUtils';
|
||||
import { AutoTextArea } from '../../../../common/components/input/auto-text-area/AutoTextArea';
|
||||
import useReactiveTextInput from '../../../../common/components/input/text-input/useReactiveTextInput';
|
||||
import { EventEditorUpdateFields } from '../EventEditor';
|
||||
|
||||
interface CountedTextAreaProps {
|
||||
className?: string;
|
||||
field: EventEditorUpdateFields;
|
||||
label: string;
|
||||
initialValue: string;
|
||||
style?: CSSProperties;
|
||||
submitHandler: (field: EventEditorUpdateFields, value: string) => void;
|
||||
}
|
||||
|
||||
export default function EventTextArea({
|
||||
className,
|
||||
field,
|
||||
label,
|
||||
initialValue,
|
||||
style: givenStyles,
|
||||
submitHandler,
|
||||
}: CountedTextAreaProps) {
|
||||
const ref = useRef<HTMLInputElement | null>(null);
|
||||
const submitCallback = useCallback((newValue: string) => submitHandler(field, newValue), [field, submitHandler]);
|
||||
|
||||
const { value, onChange, onBlur, onKeyDown } = useReactiveTextInput(initialValue, submitCallback, ref, {
|
||||
submitOnCtrlEnter: true,
|
||||
});
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Editor.Label className={className} htmlFor={field} style={givenStyles}>
|
||||
{label}
|
||||
</Editor.Label>
|
||||
<AutoTextArea
|
||||
id={field}
|
||||
inputref={ref}
|
||||
rows={1}
|
||||
size='sm'
|
||||
resize='none'
|
||||
variant='ontime-filled'
|
||||
data-testid='input-textarea'
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
onBlur={onBlur}
|
||||
onKeyDown={onKeyDown}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
import { useCallback, useRef } from 'react';
|
||||
|
||||
import * as Editor from '../../../../common/components/editor-utils/EditorUtils';
|
||||
import Input, { type InputProps } from '../../../../common/components/input/input/Input';
|
||||
import useReactiveTextInput from '../../../../common/components/input/text-input/useReactiveTextInput';
|
||||
import { BlockEditorUpdateTextFields } from '../BlockEditor';
|
||||
import { EventEditorUpdateFields } from '../EventEditor';
|
||||
|
||||
interface EntryEditorTextInputProps extends InputProps {
|
||||
field: EventEditorUpdateFields | BlockEditorUpdateTextFields;
|
||||
label: string;
|
||||
initialValue: string;
|
||||
placeholder?: string;
|
||||
submitHandler: (field: EventEditorUpdateFields, value: string) => void;
|
||||
}
|
||||
|
||||
export default function EntryEditorTextInput({
|
||||
className,
|
||||
field,
|
||||
label,
|
||||
initialValue,
|
||||
style: givenStyles,
|
||||
submitHandler,
|
||||
maxLength,
|
||||
placeholder,
|
||||
}: EntryEditorTextInputProps) {
|
||||
const ref = useRef<HTMLInputElement | null>(null);
|
||||
const submitCallback = useCallback((newValue: string) => submitHandler(field, newValue), [field, submitHandler]);
|
||||
|
||||
const { value, onChange, onBlur, onKeyDown } = useReactiveTextInput(initialValue, submitCallback, ref, {
|
||||
submitOnEnter: true,
|
||||
});
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Editor.Label className={className} htmlFor={field} style={givenStyles}>
|
||||
{label}
|
||||
</Editor.Label>
|
||||
<Input
|
||||
id={field}
|
||||
ref={ref}
|
||||
maxLength={maxLength}
|
||||
fluid
|
||||
data-testid='input-textfield'
|
||||
value={value}
|
||||
placeholder={placeholder}
|
||||
onChange={onChange}
|
||||
onBlur={onBlur}
|
||||
onKeyDown={onKeyDown}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
import { TimerLifeCycle } from 'ontime-types';
|
||||
|
||||
export const eventTriggerOptions: TimerLifeCycle[] = [
|
||||
TimerLifeCycle.onLoad,
|
||||
TimerLifeCycle.onStart,
|
||||
TimerLifeCycle.onPause,
|
||||
TimerLifeCycle.onFinish,
|
||||
TimerLifeCycle.onWarning,
|
||||
TimerLifeCycle.onDanger,
|
||||
];
|
||||
Reference in New Issue
Block a user