mirror of
https://github.com/cpvalente/ontime.git
synced 2026-08-21 15:09:10 +00:00
Rundown modes (#864)
* refactor: simplify settings * refactor: quick add around selection * refactor: no action menu in delay block * refactor: no action menu in event block * style: context menu dark * refactor: context menu actions
This commit is contained in:
@@ -3,7 +3,6 @@ import { create } from 'zustand';
|
|||||||
import { booleanFromLocalStorage } from '../utils/localStorage';
|
import { booleanFromLocalStorage } from '../utils/localStorage';
|
||||||
|
|
||||||
type EditorSettings = {
|
type EditorSettings = {
|
||||||
showQuickEntry: boolean;
|
|
||||||
linkPrevious: boolean;
|
linkPrevious: boolean;
|
||||||
defaultPublic: boolean;
|
defaultPublic: boolean;
|
||||||
defaultDuration: string;
|
defaultDuration: string;
|
||||||
@@ -12,14 +11,12 @@ type EditorSettings = {
|
|||||||
type EditorSettingsStore = {
|
type EditorSettingsStore = {
|
||||||
eventSettings: EditorSettings;
|
eventSettings: EditorSettings;
|
||||||
setLocalEventSettings: (newState: EditorSettings) => void;
|
setLocalEventSettings: (newState: EditorSettings) => void;
|
||||||
setShowQuickEntry: (showQuickEntry: boolean) => void;
|
|
||||||
setLinkPrevious: (linkPrevious: boolean) => void;
|
setLinkPrevious: (linkPrevious: boolean) => void;
|
||||||
setDefaultPublic: (defaultPublic: boolean) => void;
|
setDefaultPublic: (defaultPublic: boolean) => void;
|
||||||
setDefaultDuration: (defaultDuration: string) => void;
|
setDefaultDuration: (defaultDuration: string) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
enum EditorSettingsKeys {
|
enum EditorSettingsKeys {
|
||||||
ShowQuickEntry = 'ontime-show-quick-entry',
|
|
||||||
LinkPrevious = 'ontime-link-previous',
|
LinkPrevious = 'ontime-link-previous',
|
||||||
DefaultPublic = 'ontime-default-public',
|
DefaultPublic = 'ontime-default-public',
|
||||||
DefaultDuration = 'ontime-default-duration',
|
DefaultDuration = 'ontime-default-duration',
|
||||||
@@ -27,7 +24,6 @@ enum EditorSettingsKeys {
|
|||||||
|
|
||||||
export const useEditorSettings = create<EditorSettingsStore>((set) => ({
|
export const useEditorSettings = create<EditorSettingsStore>((set) => ({
|
||||||
eventSettings: {
|
eventSettings: {
|
||||||
showQuickEntry: booleanFromLocalStorage(EditorSettingsKeys.ShowQuickEntry, false),
|
|
||||||
linkPrevious: booleanFromLocalStorage(EditorSettingsKeys.LinkPrevious, true),
|
linkPrevious: booleanFromLocalStorage(EditorSettingsKeys.LinkPrevious, true),
|
||||||
defaultPublic: booleanFromLocalStorage(EditorSettingsKeys.DefaultPublic, true),
|
defaultPublic: booleanFromLocalStorage(EditorSettingsKeys.DefaultPublic, true),
|
||||||
defaultDuration: localStorage.getItem(EditorSettingsKeys.DefaultDuration) ?? '00:10:00',
|
defaultDuration: localStorage.getItem(EditorSettingsKeys.DefaultDuration) ?? '00:10:00',
|
||||||
@@ -35,18 +31,11 @@ export const useEditorSettings = create<EditorSettingsStore>((set) => ({
|
|||||||
|
|
||||||
setLocalEventSettings: (value) =>
|
setLocalEventSettings: (value) =>
|
||||||
set(() => {
|
set(() => {
|
||||||
localStorage.setItem(EditorSettingsKeys.ShowQuickEntry, String(value.showQuickEntry));
|
|
||||||
localStorage.setItem(EditorSettingsKeys.LinkPrevious, String(value.linkPrevious));
|
localStorage.setItem(EditorSettingsKeys.LinkPrevious, String(value.linkPrevious));
|
||||||
localStorage.setItem(EditorSettingsKeys.DefaultPublic, String(value.defaultPublic));
|
localStorage.setItem(EditorSettingsKeys.DefaultPublic, String(value.defaultPublic));
|
||||||
return { eventSettings: value };
|
return { eventSettings: value };
|
||||||
}),
|
}),
|
||||||
|
|
||||||
setShowQuickEntry: (showQuickEntry) =>
|
|
||||||
set((state) => {
|
|
||||||
localStorage.setItem(EditorSettingsKeys.ShowQuickEntry, String(showQuickEntry));
|
|
||||||
return { eventSettings: { ...state.eventSettings, showQuickEntry } };
|
|
||||||
}),
|
|
||||||
|
|
||||||
setLinkPrevious: (linkPrevious) =>
|
setLinkPrevious: (linkPrevious) =>
|
||||||
set((state) => {
|
set((state) => {
|
||||||
localStorage.setItem(EditorSettingsKeys.LinkPrevious, String(linkPrevious));
|
localStorage.setItem(EditorSettingsKeys.LinkPrevious, String(linkPrevious));
|
||||||
|
|||||||
+76
-48
@@ -7,7 +7,6 @@ import * as Panel from '../PanelUtils';
|
|||||||
|
|
||||||
export default function EditorSettingsForm() {
|
export default function EditorSettingsForm() {
|
||||||
const eventSettings = useEditorSettings((state) => state.eventSettings);
|
const eventSettings = useEditorSettings((state) => state.eventSettings);
|
||||||
const setShowQuickEntry = useEditorSettings((state) => state.setShowQuickEntry);
|
|
||||||
const setLinkPrevious = useEditorSettings((state) => state.setLinkPrevious);
|
const setLinkPrevious = useEditorSettings((state) => state.setLinkPrevious);
|
||||||
const setDefaultPublic = useEditorSettings((state) => state.setDefaultPublic);
|
const setDefaultPublic = useEditorSettings((state) => state.setDefaultPublic);
|
||||||
const setDefaultDuration = useEditorSettings((state) => state.setDefaultDuration);
|
const setDefaultDuration = useEditorSettings((state) => state.setDefaultDuration);
|
||||||
@@ -19,53 +18,82 @@ export default function EditorSettingsForm() {
|
|||||||
<Panel.Card>
|
<Panel.Card>
|
||||||
<Panel.SubHeader>Editor settings</Panel.SubHeader>
|
<Panel.SubHeader>Editor settings</Panel.SubHeader>
|
||||||
<Panel.Divider />
|
<Panel.Divider />
|
||||||
<Panel.ListGroup>
|
<Panel.Section>
|
||||||
<Panel.ListItem>
|
<Panel.Title>Rundown options</Panel.Title>
|
||||||
<Panel.Field
|
<Panel.ListGroup>
|
||||||
title='Show quick entry'
|
<Panel.ListItem>
|
||||||
description='Whether the quick entry buttons show under selected event'
|
<Panel.Field
|
||||||
/>
|
title='Default duration'
|
||||||
<Switch
|
description='When creating a new event, what is the default duration'
|
||||||
variant='ontime'
|
/>
|
||||||
size='lg'
|
<TimeInput<'defaultDuration'>
|
||||||
defaultChecked={eventSettings.showQuickEntry}
|
name='defaultDuration'
|
||||||
onChange={(event) => setShowQuickEntry(event.target.checked)}
|
submitHandler={(_field, value) => setDefaultDuration(value)}
|
||||||
/>
|
time={durationInMs}
|
||||||
</Panel.ListItem>
|
placeholder='00:10:00'
|
||||||
<Panel.ListItem>
|
/>
|
||||||
<Panel.Field
|
</Panel.ListItem>
|
||||||
title='Link previous'
|
<Panel.ListItem>
|
||||||
description='New events start time will be linked to the previous event'
|
<Panel.Field
|
||||||
/>
|
title='Link previous'
|
||||||
<Switch
|
description='New events start time will be linked to the previous event'
|
||||||
variant='ontime'
|
/>
|
||||||
size='lg'
|
<Switch
|
||||||
defaultChecked={eventSettings.linkPrevious}
|
variant='ontime'
|
||||||
onChange={(event) => setLinkPrevious(event.target.checked)}
|
size='lg'
|
||||||
/>
|
defaultChecked={eventSettings.linkPrevious}
|
||||||
</Panel.ListItem>
|
onChange={(event) => setLinkPrevious(event.target.checked)}
|
||||||
<Panel.ListItem>
|
/>
|
||||||
<Panel.Field
|
</Panel.ListItem>
|
||||||
title='Default duration'
|
<Panel.ListItem>
|
||||||
description='When creating a new event, what is the default duration'
|
<Panel.Field title='Default public' description='New events will be public' />
|
||||||
/>
|
<Switch
|
||||||
<TimeInput<'defaultDuration'>
|
variant='ontime'
|
||||||
name='defaultDuration'
|
size='lg'
|
||||||
submitHandler={(_field, value) => setDefaultDuration(value)}
|
defaultChecked={eventSettings.defaultPublic}
|
||||||
time={durationInMs}
|
onChange={(event) => setDefaultPublic(event.target.checked)}
|
||||||
placeholder='00:10:00'
|
/>
|
||||||
/>
|
</Panel.ListItem>
|
||||||
</Panel.ListItem>
|
</Panel.ListGroup>
|
||||||
<Panel.ListItem>
|
</Panel.Section>
|
||||||
<Panel.Field title='Default public' description='New events will be public' />
|
<Panel.Section>
|
||||||
<Switch
|
<Panel.Title>Play mode</Panel.Title>
|
||||||
variant='ontime'
|
<Panel.ListGroup>
|
||||||
size='lg'
|
<Panel.ListItem>
|
||||||
defaultChecked={eventSettings.defaultPublic}
|
<Panel.Field
|
||||||
onChange={(event) => setDefaultPublic(event.target.checked)}
|
title='Show quick entry'
|
||||||
/>
|
description='Whether the quick entry buttons show above / under selected event'
|
||||||
</Panel.ListItem>
|
/>
|
||||||
</Panel.ListGroup>
|
<Switch variant='ontime' size='lg' defaultChecked={false} isDisabled />
|
||||||
|
</Panel.ListItem>
|
||||||
|
<Panel.ListItem>
|
||||||
|
<Panel.Field
|
||||||
|
title='Follow playback'
|
||||||
|
description='Whether view automatically follows the event being played'
|
||||||
|
/>
|
||||||
|
<Switch variant='ontime' size='lg' defaultChecked isDisabled />
|
||||||
|
</Panel.ListItem>
|
||||||
|
</Panel.ListGroup>
|
||||||
|
</Panel.Section>
|
||||||
|
<Panel.Section>
|
||||||
|
<Panel.Title>Edit mode</Panel.Title>
|
||||||
|
<Panel.ListGroup>
|
||||||
|
<Panel.ListItem>
|
||||||
|
<Panel.Field
|
||||||
|
title='Show quick entry'
|
||||||
|
description='Whether the quick entry buttons show above / under selected event'
|
||||||
|
/>
|
||||||
|
<Switch variant='ontime' size='lg' defaultChecked isDisabled />
|
||||||
|
</Panel.ListItem>
|
||||||
|
<Panel.ListItem>
|
||||||
|
<Panel.Field
|
||||||
|
title='Follow playback'
|
||||||
|
description='Whether view automatically follows the event being played'
|
||||||
|
/>
|
||||||
|
<Switch variant='ontime' size='lg' defaultChecked={false} isDisabled />
|
||||||
|
</Panel.ListItem>
|
||||||
|
</Panel.ListGroup>
|
||||||
|
</Panel.Section>
|
||||||
</Panel.Card>
|
</Panel.Card>
|
||||||
</Panel.Section>
|
</Panel.Section>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -39,7 +39,6 @@ export default function Rundown({ data }: RundownProps) {
|
|||||||
const eventSettings = useEditorSettings((state) => state.eventSettings);
|
const eventSettings = useEditorSettings((state) => state.eventSettings);
|
||||||
const defaultPublic = eventSettings.defaultPublic;
|
const defaultPublic = eventSettings.defaultPublic;
|
||||||
const linkPrevious = eventSettings.linkPrevious;
|
const linkPrevious = eventSettings.linkPrevious;
|
||||||
const showQuickEntry = eventSettings.showQuickEntry;
|
|
||||||
|
|
||||||
// cursor
|
// cursor
|
||||||
const { cursor, mode: appMode, setCursor } = useAppMode();
|
const { cursor, mode: appMode, setCursor } = useAppMode();
|
||||||
@@ -219,6 +218,8 @@ export default function Rundown({ data }: RundownProps) {
|
|||||||
// all events before the current selected are in the past
|
// all events before the current selected are in the past
|
||||||
let isPast = Boolean(featureData?.selectedEventId);
|
let isPast = Boolean(featureData?.selectedEventId);
|
||||||
|
|
||||||
|
const isEditMode = appMode === AppMode.Edit;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={style.eventContainer} ref={scrollRef} data-testid='rundown'>
|
<div className={style.eventContainer} ref={scrollRef} data-testid='rundown'>
|
||||||
<DndContext onDragEnd={handleOnDragEnd} sensors={sensors} collisionDetection={closestCenter}>
|
<DndContext onDragEnd={handleOnDragEnd} sensors={sensors} collisionDetection={closestCenter}>
|
||||||
@@ -248,6 +249,7 @@ export default function Rundown({ data }: RundownProps) {
|
|||||||
thisId = eventId;
|
thisId = eventId;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
const isFirst = index === 0;
|
||||||
const isLast = index === order.length - 1;
|
const isLast = index === order.length - 1;
|
||||||
const isLoaded = featureData?.selectedEventId === event.id;
|
const isLoaded = featureData?.selectedEventId === event.id;
|
||||||
const isNext = featureData?.nextEventId === event.id;
|
const isNext = featureData?.nextEventId === event.id;
|
||||||
@@ -258,6 +260,14 @@ export default function Rundown({ data }: RundownProps) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Fragment key={event.id}>
|
<Fragment key={event.id}>
|
||||||
|
{isEditMode && (hasCursor || isFirst) && (
|
||||||
|
<QuickAddBlock
|
||||||
|
showKbd={hasCursor ? 'above' : 'none'}
|
||||||
|
previousEventId={previousEventId}
|
||||||
|
disableAddDelay={isOntimeDelay(event)}
|
||||||
|
disableAddBlock={isOntimeBlock(event)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
<div className={style.entryWrapper} data-testid={`entry-${eventIndex}`}>
|
<div className={style.entryWrapper} data-testid={`entry-${eventIndex}`}>
|
||||||
{isOntimeEvent(event) && <div className={style.entryIndex}>{eventIndex}</div>}
|
{isOntimeEvent(event) && <div className={style.entryIndex}>{eventIndex}</div>}
|
||||||
<div className={style.entry} key={event.id} ref={hasCursor ? cursorRef : undefined}>
|
<div className={style.entry} key={event.id} ref={hasCursor ? cursorRef : undefined}>
|
||||||
@@ -277,9 +287,9 @@ export default function Rundown({ data }: RundownProps) {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{((showQuickEntry && hasCursor) || isLast) && (
|
{isEditMode && (hasCursor || isLast) && (
|
||||||
<QuickAddBlock
|
<QuickAddBlock
|
||||||
showKbd={hasCursor}
|
showKbd={hasCursor ? 'below' : 'none'}
|
||||||
previousEventId={event.id}
|
previousEventId={event.id}
|
||||||
disableAddDelay={isOntimeDelay(event)}
|
disableAddDelay={isOntimeDelay(event)}
|
||||||
disableAddBlock={isOntimeBlock(event)}
|
disableAddBlock={isOntimeBlock(event)}
|
||||||
|
|||||||
@@ -13,7 +13,18 @@ import DelayBlock from './delay-block/DelayBlock';
|
|||||||
import EventBlock from './event-block/EventBlock';
|
import EventBlock from './event-block/EventBlock';
|
||||||
import { useEventSelection } from './useEventSelection';
|
import { useEventSelection } from './useEventSelection';
|
||||||
|
|
||||||
export type EventItemActions = 'set-cursor' | 'event' | 'delay' | 'block' | 'delete' | 'clone' | 'update' | 'swap';
|
export type EventItemActions =
|
||||||
|
| 'set-cursor'
|
||||||
|
| 'event'
|
||||||
|
| 'event-before'
|
||||||
|
| 'delay'
|
||||||
|
| 'delay-before'
|
||||||
|
| 'block'
|
||||||
|
| 'block-before'
|
||||||
|
| 'delete'
|
||||||
|
| 'clone'
|
||||||
|
| 'update'
|
||||||
|
| 'swap';
|
||||||
|
|
||||||
interface RundownEntryProps {
|
interface RundownEntryProps {
|
||||||
type: SupportedEvent;
|
type: SupportedEvent;
|
||||||
@@ -83,12 +94,27 @@ export default function RundownEntry(props: RundownEntryProps) {
|
|||||||
};
|
};
|
||||||
return addEvent(newEvent, options);
|
return addEvent(newEvent, options);
|
||||||
}
|
}
|
||||||
|
case 'event-before': {
|
||||||
|
const newEvent = { type: SupportedEvent.Event };
|
||||||
|
const options = {
|
||||||
|
after: previousEventId,
|
||||||
|
defaultPublic,
|
||||||
|
linkPrevious,
|
||||||
|
};
|
||||||
|
return addEvent(newEvent, options);
|
||||||
|
}
|
||||||
case 'delay': {
|
case 'delay': {
|
||||||
return addEvent({ type: SupportedEvent.Delay }, { after: data.id });
|
return addEvent({ type: SupportedEvent.Delay }, { after: data.id });
|
||||||
}
|
}
|
||||||
|
case 'delay-before': {
|
||||||
|
return addEvent({ type: SupportedEvent.Delay }, { after: previousEventId });
|
||||||
|
}
|
||||||
case 'block': {
|
case 'block': {
|
||||||
return addEvent({ type: SupportedEvent.Block }, { after: data.id });
|
return addEvent({ type: SupportedEvent.Block }, { after: data.id });
|
||||||
}
|
}
|
||||||
|
case 'block-before': {
|
||||||
|
return addEvent({ type: SupportedEvent.Block }, { after: previousEventId });
|
||||||
|
}
|
||||||
case 'swap': {
|
case 'swap': {
|
||||||
const { value } = payload as FieldValue;
|
const { value } = payload as FieldValue;
|
||||||
return swapEvents({ from: value as string, to: data.id });
|
return swapEvents({ from: value as string, to: data.id });
|
||||||
@@ -163,9 +189,9 @@ export default function RundownEntry(props: RundownEntryProps) {
|
|||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
} else if (data.type === SupportedEvent.Block) {
|
} else if (data.type === SupportedEvent.Block) {
|
||||||
return <BlockBlock data={data} hasCursor={hasCursor} actionHandler={actionHandler} />;
|
return <BlockBlock data={data} hasCursor={hasCursor} onDelete={() => actionHandler('delete')} />;
|
||||||
} else if (data.type === SupportedEvent.Delay) {
|
} else if (data.type === SupportedEvent.Delay) {
|
||||||
return <DelayBlock data={data} hasCursor={hasCursor} actionHandler={actionHandler} />;
|
return <DelayBlock data={data} hasCursor={hasCursor} />;
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,32 +1,24 @@
|
|||||||
import { useEffect, useRef } from 'react';
|
import { useRef } from 'react';
|
||||||
|
import { IconButton } from '@chakra-ui/react';
|
||||||
import { useSortable } from '@dnd-kit/sortable';
|
import { useSortable } from '@dnd-kit/sortable';
|
||||||
import { CSS } from '@dnd-kit/utilities';
|
import { CSS } from '@dnd-kit/utilities';
|
||||||
import { IoReorderTwo } from '@react-icons/all-files/io5/IoReorderTwo';
|
import { IoReorderTwo } from '@react-icons/all-files/io5/IoReorderTwo';
|
||||||
import { OntimeBlock, OntimeEvent } from 'ontime-types';
|
import { IoTrash } from '@react-icons/all-files/io5/IoTrash';
|
||||||
|
import { OntimeBlock } from 'ontime-types';
|
||||||
|
|
||||||
import { cx } from '../../../common/utils/styleUtils';
|
import { cx } from '../../../common/utils/styleUtils';
|
||||||
import EditableBlockTitle from '../common/EditableBlockTitle';
|
import EditableBlockTitle from '../common/EditableBlockTitle';
|
||||||
import BlockActionMenu from '../event-block/composite/BlockActionMenu';
|
|
||||||
import type { EventItemActions } from '../RundownEntry';
|
|
||||||
|
|
||||||
import style from './BlockBlock.module.scss';
|
import style from './BlockBlock.module.scss';
|
||||||
|
|
||||||
interface BlockBlockProps {
|
interface BlockBlockProps {
|
||||||
data: OntimeBlock;
|
data: OntimeBlock;
|
||||||
hasCursor: boolean;
|
hasCursor: boolean;
|
||||||
actionHandler: (
|
onDelete: () => void;
|
||||||
action: EventItemActions,
|
|
||||||
payload?:
|
|
||||||
| number
|
|
||||||
| {
|
|
||||||
field: keyof Omit<OntimeEvent, 'duration'> | 'durationOverride';
|
|
||||||
value: unknown;
|
|
||||||
},
|
|
||||||
) => void;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function BlockBlock(props: BlockBlockProps) {
|
export default function BlockBlock(props: BlockBlockProps) {
|
||||||
const { data, hasCursor, actionHandler } = props;
|
const { data, hasCursor, onDelete } = props;
|
||||||
const handleRef = useRef<null | HTMLSpanElement>(null);
|
const handleRef = useRef<null | HTMLSpanElement>(null);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
@@ -45,12 +37,6 @@ export default function BlockBlock(props: BlockBlockProps) {
|
|||||||
transition,
|
transition,
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (hasCursor) {
|
|
||||||
handleRef?.current?.focus();
|
|
||||||
}
|
|
||||||
}, [hasCursor]);
|
|
||||||
|
|
||||||
const blockClasses = cx([style.block, hasCursor ? style.hasCursor : null]);
|
const blockClasses = cx([style.block, hasCursor ? style.hasCursor : null]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -59,7 +45,14 @@ export default function BlockBlock(props: BlockBlockProps) {
|
|||||||
<IoReorderTwo />
|
<IoReorderTwo />
|
||||||
</span>
|
</span>
|
||||||
<EditableBlockTitle title={data.title} eventId={data.id} placeholder='Block title' />
|
<EditableBlockTitle title={data.title} eventId={data.id} placeholder='Block title' />
|
||||||
<BlockActionMenu className={style.actionMenu} enableDelete actionHandler={actionHandler} />
|
<IconButton
|
||||||
|
aria-label='Delete'
|
||||||
|
size='sm'
|
||||||
|
icon={<IoTrash />}
|
||||||
|
variant='ontime-subtle'
|
||||||
|
color='#FA5656'
|
||||||
|
onClick={onDelete}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,32 +5,21 @@ import { CSS } from '@dnd-kit/utilities';
|
|||||||
import { IoCheckmark } from '@react-icons/all-files/io5/IoCheckmark';
|
import { IoCheckmark } from '@react-icons/all-files/io5/IoCheckmark';
|
||||||
import { IoClose } from '@react-icons/all-files/io5/IoClose';
|
import { IoClose } from '@react-icons/all-files/io5/IoClose';
|
||||||
import { IoReorderTwo } from '@react-icons/all-files/io5/IoReorderTwo';
|
import { IoReorderTwo } from '@react-icons/all-files/io5/IoReorderTwo';
|
||||||
import { OntimeDelay, OntimeEvent } from 'ontime-types';
|
import { OntimeDelay } from 'ontime-types';
|
||||||
|
|
||||||
import DelayInput from '../../../common/components/input/delay-input/DelayInput';
|
import DelayInput from '../../../common/components/input/delay-input/DelayInput';
|
||||||
import { useEventAction } from '../../../common/hooks/useEventAction';
|
import { useEventAction } from '../../../common/hooks/useEventAction';
|
||||||
import { cx } from '../../../common/utils/styleUtils';
|
import { cx } from '../../../common/utils/styleUtils';
|
||||||
import BlockActionMenu from '../event-block/composite/BlockActionMenu';
|
|
||||||
import type { EventItemActions } from '../RundownEntry';
|
|
||||||
|
|
||||||
import style from './DelayBlock.module.scss';
|
import style from './DelayBlock.module.scss';
|
||||||
|
|
||||||
interface DelayBlockProps {
|
interface DelayBlockProps {
|
||||||
data: OntimeDelay;
|
data: OntimeDelay;
|
||||||
hasCursor: boolean;
|
hasCursor: boolean;
|
||||||
actionHandler: (
|
|
||||||
action: EventItemActions,
|
|
||||||
payload?:
|
|
||||||
| number
|
|
||||||
| {
|
|
||||||
field: keyof Omit<OntimeEvent, 'duration'> | 'durationOverride';
|
|
||||||
value: unknown;
|
|
||||||
},
|
|
||||||
) => void;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function DelayBlock(props: DelayBlockProps) {
|
export default function DelayBlock(props: DelayBlockProps) {
|
||||||
const { data, hasCursor, actionHandler } = props;
|
const { data, hasCursor } = props;
|
||||||
const { applyDelay, deleteEvent } = useEventAction();
|
const { applyDelay, deleteEvent } = useEventAction();
|
||||||
const handleRef = useRef<null | HTMLSpanElement>(null);
|
const handleRef = useRef<null | HTMLSpanElement>(null);
|
||||||
|
|
||||||
@@ -79,7 +68,6 @@ export default function DelayBlock(props: DelayBlockProps) {
|
|||||||
<Button onClick={cancelDelayHandler} size='sm' leftIcon={<IoClose />} variant='ontime-subtle-white'>
|
<Button onClick={cancelDelayHandler} size='sm' leftIcon={<IoClose />} variant='ontime-subtle-white'>
|
||||||
Cancel
|
Cancel
|
||||||
</Button>
|
</Button>
|
||||||
<BlockActionMenu enableDelete actionHandler={actionHandler} />
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ $skip-opacity: 0.2;
|
|||||||
display: grid;
|
display: grid;
|
||||||
grid-template-areas:
|
grid-template-areas:
|
||||||
'binder ... ... ...'
|
'binder ... ... ...'
|
||||||
'binder pb-actions times actions'
|
'binder pb-actions times ...'
|
||||||
'binder pb-actions title title'
|
'binder pb-actions title title'
|
||||||
'binder pb-actions estatus estatus'
|
'binder pb-actions estatus estatus'
|
||||||
'binder ... ... ...';
|
'binder ... ... ...';
|
||||||
@@ -55,7 +55,6 @@ $skip-opacity: 0.2;
|
|||||||
outline: 1px solid $block-cursor-color;
|
outline: 1px solid $block-cursor-color;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* we stop the eventActions from having opacity to fix issue with dropdown drawing order */
|
|
||||||
&.past:not(.skip) {
|
&.past:not(.skip) {
|
||||||
.timerNote,
|
.timerNote,
|
||||||
.statusElements,
|
.statusElements,
|
||||||
@@ -153,12 +152,6 @@ $skip-opacity: 0.2;
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.eventActions {
|
|
||||||
grid-area: actions;
|
|
||||||
height: 100%;
|
|
||||||
text-align: end;
|
|
||||||
}
|
|
||||||
|
|
||||||
.progressBg {
|
.progressBg {
|
||||||
grid-area: progb;
|
grid-area: progb;
|
||||||
border-radius: 1px;
|
border-radius: 1px;
|
||||||
|
|||||||
@@ -2,16 +2,18 @@ import { MouseEvent, useEffect, useLayoutEffect, useRef, useState } from 'react'
|
|||||||
import { useSortable } from '@dnd-kit/sortable';
|
import { useSortable } from '@dnd-kit/sortable';
|
||||||
import { CSS } from '@dnd-kit/utilities';
|
import { CSS } from '@dnd-kit/utilities';
|
||||||
import { IoAdd } from '@react-icons/all-files/io5/IoAdd';
|
import { IoAdd } from '@react-icons/all-files/io5/IoAdd';
|
||||||
import { IoCopyOutline } from '@react-icons/all-files/io5/IoCopyOutline';
|
import { IoDuplicateOutline } from '@react-icons/all-files/io5/IoDuplicateOutline';
|
||||||
import { IoPeople } from '@react-icons/all-files/io5/IoPeople';
|
import { IoPeople } from '@react-icons/all-files/io5/IoPeople';
|
||||||
import { IoPeopleOutline } from '@react-icons/all-files/io5/IoPeopleOutline';
|
import { IoPeopleOutline } from '@react-icons/all-files/io5/IoPeopleOutline';
|
||||||
|
import { IoRemoveCircleOutline } from '@react-icons/all-files/io5/IoRemoveCircleOutline';
|
||||||
import { IoReorderTwo } from '@react-icons/all-files/io5/IoReorderTwo';
|
import { IoReorderTwo } from '@react-icons/all-files/io5/IoReorderTwo';
|
||||||
import { IoSwapVertical } from '@react-icons/all-files/io5/IoSwapVertical';
|
import { IoSwapVertical } from '@react-icons/all-files/io5/IoSwapVertical';
|
||||||
|
import { IoTimerOutline } from '@react-icons/all-files/io5/IoTimerOutline';
|
||||||
|
import { IoTrash } from '@react-icons/all-files/io5/IoTrash';
|
||||||
import { EndAction, MaybeNumber, MaybeString, OntimeEvent, Playback, TimerType, TimeStrategy } from 'ontime-types';
|
import { EndAction, MaybeNumber, MaybeString, OntimeEvent, Playback, TimerType, TimeStrategy } from 'ontime-types';
|
||||||
|
|
||||||
import { useContextMenu } from '../../../common/hooks/useContextMenu';
|
import { useContextMenu } from '../../../common/hooks/useContextMenu';
|
||||||
import { useAppMode } from '../../../common/stores/appModeStore';
|
import { useAppMode } from '../../../common/stores/appModeStore';
|
||||||
import copyToClipboard from '../../../common/utils/copyToClipboard';
|
|
||||||
import { cx, getAccessibleColour } from '../../../common/utils/styleUtils';
|
import { cx, getAccessibleColour } from '../../../common/utils/styleUtils';
|
||||||
import type { EventItemActions } from '../RundownEntry';
|
import type { EventItemActions } from '../RundownEntry';
|
||||||
import { useEventIdSwapping } from '../useEventIdSwapping';
|
import { useEventIdSwapping } from '../useEventIdSwapping';
|
||||||
@@ -96,31 +98,25 @@ export default function EventBlock(props: EventBlockProps) {
|
|||||||
selectedEvents.size > 1
|
selectedEvents.size > 1
|
||||||
? [
|
? [
|
||||||
{
|
{
|
||||||
label: 'Visiblity',
|
label: 'Make public',
|
||||||
group: [
|
icon: IoPeople,
|
||||||
{
|
onClick: () =>
|
||||||
label: 'Make public',
|
actionHandler('update', {
|
||||||
icon: IoPeople,
|
field: 'isPublic',
|
||||||
onClick: () =>
|
value: true,
|
||||||
actionHandler('update', {
|
}),
|
||||||
field: 'isPublic',
|
},
|
||||||
value: true,
|
{
|
||||||
}),
|
label: 'Make private',
|
||||||
},
|
icon: IoPeopleOutline,
|
||||||
{
|
onClick: () =>
|
||||||
label: 'Make private',
|
actionHandler('update', {
|
||||||
icon: IoPeopleOutline,
|
field: 'isPublic',
|
||||||
onClick: () =>
|
value: false,
|
||||||
actionHandler('update', {
|
}),
|
||||||
field: 'isPublic',
|
|
||||||
value: false,
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
: [
|
: [
|
||||||
{ label: `Copy ID: ${eventId}`, icon: IoCopyOutline, onClick: () => copyToClipboard(eventId) },
|
|
||||||
{
|
{
|
||||||
label: 'Toggle public',
|
label: 'Toggle public',
|
||||||
icon: IoPeopleOutline,
|
icon: IoPeopleOutline,
|
||||||
@@ -145,6 +141,14 @@ export default function EventBlock(props: EventBlockProps) {
|
|||||||
},
|
},
|
||||||
isDisabled: selectedEventId == null || selectedEventId === eventId,
|
isDisabled: selectedEventId == null || selectedEventId === eventId,
|
||||||
},
|
},
|
||||||
|
{ withDivider: true, label: 'Clone', icon: IoDuplicateOutline, onClick: () => actionHandler('clone') },
|
||||||
|
{ withDivider: true, label: 'Event before', icon: IoAdd, onClick: () => actionHandler('event-before') },
|
||||||
|
{ label: 'Event after', icon: IoAdd, onClick: () => actionHandler('event') },
|
||||||
|
{ label: 'Block before', icon: IoRemoveCircleOutline, onClick: () => actionHandler('block-before') },
|
||||||
|
{ label: 'Block after', icon: IoRemoveCircleOutline, onClick: () => actionHandler('block') },
|
||||||
|
{ label: 'Delay before', icon: IoTimerOutline, onClick: () => actionHandler('delay-before') },
|
||||||
|
{ label: 'Delay after', icon: IoTimerOutline, onClick: () => actionHandler('delay') },
|
||||||
|
{ withDivider: true, label: 'Delete', icon: IoTrash, onClick: () => actionHandler('delete') },
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -281,7 +285,6 @@ export default function EventBlock(props: EventBlockProps) {
|
|||||||
loaded={loaded}
|
loaded={loaded}
|
||||||
playback={playback}
|
playback={playback}
|
||||||
isRolling={isRolling}
|
isRolling={isRolling}
|
||||||
actionHandler={actionHandler}
|
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -14,10 +14,8 @@ import { EndAction, MaybeString, Playback, TimerType, TimeStrategy } from 'ontim
|
|||||||
import { cx } from '../../../common/utils/styleUtils';
|
import { cx } from '../../../common/utils/styleUtils';
|
||||||
import { tooltipDelayMid } from '../../../ontimeConfig';
|
import { tooltipDelayMid } from '../../../ontimeConfig';
|
||||||
import EditableBlockTitle from '../common/EditableBlockTitle';
|
import EditableBlockTitle from '../common/EditableBlockTitle';
|
||||||
import { EventItemActions } from '../RundownEntry';
|
|
||||||
import TimeInputFlow from '../time-input-flow/TimeInputFlow';
|
import TimeInputFlow from '../time-input-flow/TimeInputFlow';
|
||||||
|
|
||||||
import BlockActionMenu from './composite/BlockActionMenu';
|
|
||||||
import EventBlockPlayback from './composite/EventBlockPlayback';
|
import EventBlockPlayback from './composite/EventBlockPlayback';
|
||||||
import EventBlockProgressBar from './composite/EventBlockProgressBar';
|
import EventBlockProgressBar from './composite/EventBlockProgressBar';
|
||||||
|
|
||||||
@@ -46,7 +44,6 @@ interface EventBlockInnerProps {
|
|||||||
loaded: boolean;
|
loaded: boolean;
|
||||||
playback?: Playback;
|
playback?: Playback;
|
||||||
isRolling: boolean;
|
isRolling: boolean;
|
||||||
actionHandler: (action: EventItemActions, payload?: any) => void;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const EventBlockInner = (props: EventBlockInnerProps) => {
|
const EventBlockInner = (props: EventBlockInnerProps) => {
|
||||||
@@ -68,7 +65,6 @@ const EventBlockInner = (props: EventBlockInnerProps) => {
|
|||||||
loaded,
|
loaded,
|
||||||
playback,
|
playback,
|
||||||
isRolling,
|
isRolling,
|
||||||
actionHandler,
|
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
const [renderInner, setRenderInner] = useState(false);
|
const [renderInner, setRenderInner] = useState(false);
|
||||||
@@ -139,9 +135,6 @@ const EventBlockInner = (props: EventBlockInnerProps) => {
|
|||||||
</Tooltip>
|
</Tooltip>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className={style.eventActions}>
|
|
||||||
<BlockActionMenu showClone enableDelete={!loaded} actionHandler={actionHandler} />
|
|
||||||
</div>
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,64 +0,0 @@
|
|||||||
import { useCallback } from 'react';
|
|
||||||
import { IconButton, Menu, MenuButton, MenuDivider, MenuItem, MenuList, Tooltip } from '@chakra-ui/react';
|
|
||||||
import { IoAdd } from '@react-icons/all-files/io5/IoAdd';
|
|
||||||
import { IoDuplicateOutline } from '@react-icons/all-files/io5/IoDuplicateOutline';
|
|
||||||
import { IoEllipsisHorizontal } from '@react-icons/all-files/io5/IoEllipsisHorizontal';
|
|
||||||
import { IoRemoveCircleOutline } from '@react-icons/all-files/io5/IoRemoveCircleOutline';
|
|
||||||
import { IoTimerOutline } from '@react-icons/all-files/io5/IoTimerOutline';
|
|
||||||
import { IoTrashBinSharp } from '@react-icons/all-files/io5/IoTrashBinSharp';
|
|
||||||
|
|
||||||
import { tooltipDelayMid } from '../../../../ontimeConfig';
|
|
||||||
import { EventItemActions } from '../../RundownEntry';
|
|
||||||
|
|
||||||
interface BlockActionMenuProps {
|
|
||||||
enableDelete?: boolean;
|
|
||||||
showClone?: boolean;
|
|
||||||
actionHandler: (action: EventItemActions, payload?: any) => void;
|
|
||||||
className?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function BlockActionMenu(props: BlockActionMenuProps) {
|
|
||||||
const { enableDelete, showClone, actionHandler, className } = props;
|
|
||||||
|
|
||||||
const handleAddEvent = useCallback(() => actionHandler('event'), [actionHandler]);
|
|
||||||
const handleAddDelay = useCallback(() => actionHandler('delay'), [actionHandler]);
|
|
||||||
const handleAddBlock = useCallback(() => actionHandler('block'), [actionHandler]);
|
|
||||||
const handleClone = useCallback(() => actionHandler('clone'), [actionHandler]);
|
|
||||||
const handleDelete = useCallback(() => actionHandler('delete'), [actionHandler]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Menu isLazy lazyBehavior='unmount' variant='ontime-on-dark'>
|
|
||||||
<Tooltip label='Add ...' openDelay={tooltipDelayMid}>
|
|
||||||
<MenuButton
|
|
||||||
as={IconButton}
|
|
||||||
aria-label='Event options'
|
|
||||||
icon={<IoEllipsisHorizontal />}
|
|
||||||
tabIndex={-1}
|
|
||||||
variant='ontime-ghosted-white'
|
|
||||||
size='sm'
|
|
||||||
className={className}
|
|
||||||
/>
|
|
||||||
</Tooltip>
|
|
||||||
<MenuList>
|
|
||||||
<MenuItem icon={<IoAdd />} onClick={handleAddEvent}>
|
|
||||||
Add Event after
|
|
||||||
</MenuItem>
|
|
||||||
<MenuItem icon={<IoTimerOutline />} onClick={handleAddDelay}>
|
|
||||||
Add Delay after
|
|
||||||
</MenuItem>
|
|
||||||
<MenuItem icon={<IoRemoveCircleOutline />} onClick={handleAddBlock}>
|
|
||||||
Add Block after
|
|
||||||
</MenuItem>
|
|
||||||
{showClone && (
|
|
||||||
<MenuItem icon={<IoDuplicateOutline />} onClick={handleClone}>
|
|
||||||
Clone event
|
|
||||||
</MenuItem>
|
|
||||||
)}
|
|
||||||
<MenuDivider />
|
|
||||||
<MenuItem icon={<IoTrashBinSharp />} onClick={handleDelete} isDisabled={!enableDelete} color='#D20300'>
|
|
||||||
Delete
|
|
||||||
</MenuItem>
|
|
||||||
</MenuList>
|
|
||||||
</Menu>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -23,7 +23,7 @@
|
|||||||
padding: 0 0.25rem;
|
padding: 0 0.25rem;
|
||||||
color: $label-gray;
|
color: $label-gray;
|
||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
background-color: $black-10
|
background-color: $black-10;
|
||||||
}
|
}
|
||||||
|
|
||||||
.options {
|
.options {
|
||||||
|
|||||||
@@ -12,14 +12,14 @@ import { tooltipDelayMid } from '../../../ontimeConfig';
|
|||||||
import style from './QuickAddBlock.module.scss';
|
import style from './QuickAddBlock.module.scss';
|
||||||
|
|
||||||
interface QuickAddBlockProps {
|
interface QuickAddBlockProps {
|
||||||
showKbd: boolean;
|
showKbd: 'above' | 'below' | 'none';
|
||||||
previousEventId: string;
|
previousEventId?: string;
|
||||||
disableAddDelay?: boolean;
|
disableAddDelay?: boolean;
|
||||||
disableAddBlock: boolean;
|
disableAddBlock: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const QuickAddBlock = (props: QuickAddBlockProps) => {
|
const QuickAddBlock = (props: QuickAddBlockProps) => {
|
||||||
const { showKbd, previousEventId, disableAddDelay = true, disableAddBlock } = props;
|
const { showKbd = 'none', previousEventId, disableAddDelay = true, disableAddBlock } = props;
|
||||||
const { addEvent } = useEventAction();
|
const { addEvent } = useEventAction();
|
||||||
const { emitError } = useEmitLog();
|
const { emitError } = useEmitLog();
|
||||||
|
|
||||||
@@ -28,6 +28,8 @@ const QuickAddBlock = (props: QuickAddBlockProps) => {
|
|||||||
|
|
||||||
const { defaultPublic, linkPrevious } = useEditorSettings((state) => state.eventSettings);
|
const { defaultPublic, linkPrevious } = useEditorSettings((state) => state.eventSettings);
|
||||||
|
|
||||||
|
const shortcutBase = showKbd === 'none' ? '' : `${deviceAlt} ${showKbd === 'above' ? '⇧' : ''}`;
|
||||||
|
|
||||||
const handleCreateEvent = useCallback(
|
const handleCreateEvent = useCallback(
|
||||||
(eventType: SupportedEvent) => {
|
(eventType: SupportedEvent) => {
|
||||||
switch (eventType) {
|
switch (eventType) {
|
||||||
@@ -70,6 +72,9 @@ const QuickAddBlock = (props: QuickAddBlockProps) => {
|
|||||||
[previousEventId, addEvent, emitError],
|
[previousEventId, addEvent, emitError],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const canLinkPrevious = Boolean(previousEventId);
|
||||||
|
const shouldLinkPrevious = Boolean(linkPrevious) && canLinkPrevious;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={style.quickAdd}>
|
<div className={style.quickAdd}>
|
||||||
<div className={style.btnRow}>
|
<div className={style.btnRow}>
|
||||||
@@ -79,10 +84,9 @@ const QuickAddBlock = (props: QuickAddBlockProps) => {
|
|||||||
size='xs'
|
size='xs'
|
||||||
variant='ontime-subtle-white'
|
variant='ontime-subtle-white'
|
||||||
className={style.quickBtn}
|
className={style.quickBtn}
|
||||||
data-testid='quick-add-event'
|
|
||||||
leftIcon={<IoAdd />}
|
leftIcon={<IoAdd />}
|
||||||
>
|
>
|
||||||
Event {showKbd && <span className={style.keyboard}>{`${deviceAlt} + E`}</span>}
|
Event {shortcutBase && <span className={style.keyboard}>{`${shortcutBase} E`}</span>}
|
||||||
</Button>
|
</Button>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<Tooltip label='Add Delay' openDelay={tooltipDelayMid}>
|
<Tooltip label='Add Delay' openDelay={tooltipDelayMid}>
|
||||||
@@ -92,10 +96,9 @@ const QuickAddBlock = (props: QuickAddBlockProps) => {
|
|||||||
variant='ontime-subtle-white'
|
variant='ontime-subtle-white'
|
||||||
disabled={disableAddDelay}
|
disabled={disableAddDelay}
|
||||||
className={style.quickBtn}
|
className={style.quickBtn}
|
||||||
data-testid='quick-add-delay'
|
|
||||||
leftIcon={<IoAdd />}
|
leftIcon={<IoAdd />}
|
||||||
>
|
>
|
||||||
Delay {showKbd && <span className={style.keyboard}>{`${deviceAlt} + D`}</span>}
|
Delay {shortcutBase && <span className={style.keyboard}>{`${shortcutBase} D`}</span>}
|
||||||
</Button>
|
</Button>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<Tooltip label='Add Block' openDelay={tooltipDelayMid}>
|
<Tooltip label='Add Block' openDelay={tooltipDelayMid}>
|
||||||
@@ -105,15 +108,20 @@ const QuickAddBlock = (props: QuickAddBlockProps) => {
|
|||||||
variant='ontime-subtle-white'
|
variant='ontime-subtle-white'
|
||||||
disabled={disableAddBlock}
|
disabled={disableAddBlock}
|
||||||
className={style.quickBtn}
|
className={style.quickBtn}
|
||||||
data-testid='quick-add-block'
|
|
||||||
leftIcon={<IoAdd />}
|
leftIcon={<IoAdd />}
|
||||||
>
|
>
|
||||||
Block {showKbd && <span className={style.keyboard}>{`${deviceAlt} + B`}</span>}
|
Block {shortcutBase && <span className={style.keyboard}>{`${shortcutBase} B`}</span>}
|
||||||
</Button>
|
</Button>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</div>
|
</div>
|
||||||
<div className={style.options}>
|
<div className={style.options}>
|
||||||
<Checkbox ref={doLinkPrevious} size='sm' variant='ontime-ondark' defaultChecked={linkPrevious}>
|
<Checkbox
|
||||||
|
ref={doLinkPrevious}
|
||||||
|
size='sm'
|
||||||
|
variant='ontime-ondark'
|
||||||
|
isDisabled={!canLinkPrevious}
|
||||||
|
defaultChecked={shouldLinkPrevious}
|
||||||
|
>
|
||||||
Link to previous
|
Link to previous
|
||||||
</Checkbox>
|
</Checkbox>
|
||||||
<Checkbox ref={doPublic} size='sm' variant='ontime-ondark' defaultChecked={defaultPublic}>
|
<Checkbox ref={doPublic} size='sm' variant='ontime-ondark' defaultChecked={defaultPublic}>
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
import { Button, ButtonGroup, MenuButton } from '@chakra-ui/react';
|
import { ButtonGroup } from '@chakra-ui/react';
|
||||||
import { IoAdd } from '@react-icons/all-files/io5/IoAdd';
|
|
||||||
import { IoOptions } from '@react-icons/all-files/io5/IoOptions';
|
import { IoOptions } from '@react-icons/all-files/io5/IoOptions';
|
||||||
import { IoPlay } from '@react-icons/all-files/io5/IoPlay';
|
import { IoPlay } from '@react-icons/all-files/io5/IoPlay';
|
||||||
import { IoSnowOutline } from '@react-icons/all-files/io5/IoSnowOutline';
|
|
||||||
|
|
||||||
import TooltipActionBtn from '../../../common/components/buttons/TooltipActionBtn';
|
import TooltipActionBtn from '../../../common/components/buttons/TooltipActionBtn';
|
||||||
import { AppMode, useAppMode } from '../../../common/stores/appModeStore';
|
import { AppMode, useAppMode } from '../../../common/stores/appModeStore';
|
||||||
@@ -16,20 +14,10 @@ export default function RundownHeader() {
|
|||||||
const setAppMode = useAppMode((state) => state.setMode);
|
const setAppMode = useAppMode((state) => state.setMode);
|
||||||
const setRunMode = () => setAppMode(AppMode.Run);
|
const setRunMode = () => setAppMode(AppMode.Run);
|
||||||
const setEditMode = () => setAppMode(AppMode.Edit);
|
const setEditMode = () => setAppMode(AppMode.Edit);
|
||||||
const setFreezeMode = () => setAppMode(AppMode.Freeze);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={style.header}>
|
<div className={style.header}>
|
||||||
<ButtonGroup isAttached>
|
<ButtonGroup isAttached>
|
||||||
<TooltipActionBtn
|
|
||||||
variant={appMode === AppMode.Freeze ? 'ontime-filled' : 'ontime-outlined'}
|
|
||||||
size='sm'
|
|
||||||
icon={<IoSnowOutline />}
|
|
||||||
clickHandler={setFreezeMode}
|
|
||||||
tooltip='Freeze rundown'
|
|
||||||
aria-label='Freeze rundown'
|
|
||||||
isDisabled
|
|
||||||
/>
|
|
||||||
<TooltipActionBtn
|
<TooltipActionBtn
|
||||||
variant={appMode === AppMode.Run ? 'ontime-filled' : 'ontime-outlined'}
|
variant={appMode === AppMode.Run ? 'ontime-filled' : 'ontime-outlined'}
|
||||||
size='sm'
|
size='sm'
|
||||||
@@ -47,11 +35,7 @@ export default function RundownHeader() {
|
|||||||
aria-label='Edit mode'
|
aria-label='Edit mode'
|
||||||
/>
|
/>
|
||||||
</ButtonGroup>
|
</ButtonGroup>
|
||||||
<RundownMenu>
|
<RundownMenu />
|
||||||
<MenuButton size='sm' as={Button} rightIcon={<IoAdd />} aria-label='Rundown menu' variant='ontime-outlined'>
|
|
||||||
Rundown
|
|
||||||
</MenuButton>
|
|
||||||
</RundownMenu>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,31 +1,16 @@
|
|||||||
import { memo, ReactNode, useCallback } from 'react';
|
import { useCallback } from 'react';
|
||||||
import { Menu, MenuDivider, MenuItem, MenuList } from '@chakra-ui/react';
|
import { Button } from '@chakra-ui/react';
|
||||||
import { IoAdd } from '@react-icons/all-files/io5/IoAdd';
|
import { IoTrash } from '@react-icons/all-files/io5/IoTrash';
|
||||||
import { IoRemoveCircleOutline } from '@react-icons/all-files/io5/IoRemoveCircleOutline';
|
|
||||||
import { IoTimerOutline } from '@react-icons/all-files/io5/IoTimerOutline';
|
|
||||||
import { IoTrashOutline } from '@react-icons/all-files/io5/IoTrashOutline';
|
|
||||||
import { SupportedEvent } from 'ontime-types';
|
|
||||||
|
|
||||||
import { useEventAction } from '../../../common/hooks/useEventAction';
|
import { useEventAction } from '../../../common/hooks/useEventAction';
|
||||||
import { useAppMode } from '../../../common/stores/appModeStore';
|
import { useAppMode } from '../../../common/stores/appModeStore';
|
||||||
import { useEventSelection } from '../useEventSelection';
|
import { useEventSelection } from '../useEventSelection';
|
||||||
|
|
||||||
const RundownMenu = ({ children }: { children: ReactNode }) => {
|
export default function RundownMenu() {
|
||||||
const clearSelectedEvents = useEventSelection((state) => state.clearSelectedEvents);
|
const clearSelectedEvents = useEventSelection((state) => state.clearSelectedEvents);
|
||||||
const setCursor = useAppMode((state) => state.setCursor);
|
const setCursor = useAppMode((state) => state.setCursor);
|
||||||
const { addEvent, deleteAllEvents } = useEventAction();
|
const appMode = useAppMode((state) => state.mode);
|
||||||
|
const { deleteAllEvents } = useEventAction();
|
||||||
const newEvent = useCallback(() => {
|
|
||||||
addEvent({ type: SupportedEvent.Event });
|
|
||||||
}, [addEvent]);
|
|
||||||
|
|
||||||
const newBlock = useCallback(() => {
|
|
||||||
addEvent({ type: SupportedEvent.Block });
|
|
||||||
}, [addEvent]);
|
|
||||||
|
|
||||||
const newDelay = useCallback(() => {
|
|
||||||
addEvent({ type: SupportedEvent.Delay });
|
|
||||||
}, [addEvent]);
|
|
||||||
|
|
||||||
const deleteAll = useCallback(() => {
|
const deleteAll = useCallback(() => {
|
||||||
deleteAllEvents();
|
deleteAllEvents();
|
||||||
@@ -34,25 +19,15 @@ const RundownMenu = ({ children }: { children: ReactNode }) => {
|
|||||||
}, [clearSelectedEvents, deleteAllEvents, setCursor]);
|
}, [clearSelectedEvents, deleteAllEvents, setCursor]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Menu isLazy lazyBehavior='unmount' variant='ontime-on-dark' placement='right-start'>
|
<Button
|
||||||
{children}
|
size='sm'
|
||||||
<MenuList>
|
variant='ontime-outlined'
|
||||||
<MenuItem icon={<IoAdd />} onClick={newEvent}>
|
leftIcon={<IoTrash />}
|
||||||
Add event at start
|
onClick={deleteAll}
|
||||||
</MenuItem>
|
color='#FA5656'
|
||||||
<MenuItem icon={<IoTimerOutline />} onClick={newDelay}>
|
isDisabled={appMode === 'run'}
|
||||||
Add delay at start
|
>
|
||||||
</MenuItem>
|
Clear rundown
|
||||||
<MenuItem icon={<IoRemoveCircleOutline />} onClick={newBlock}>
|
</Button>
|
||||||
Add block at start
|
|
||||||
</MenuItem>
|
|
||||||
<MenuDivider />
|
|
||||||
<MenuItem icon={<IoTrashOutline />} onClick={deleteAll} color='#D20300'>
|
|
||||||
Delete all events
|
|
||||||
</MenuItem>
|
|
||||||
</MenuList>
|
|
||||||
</Menu>
|
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
||||||
export default memo(RundownMenu);
|
|
||||||
|
|||||||
@@ -3,12 +3,24 @@ export const ontimeCheckboxOnDark = {
|
|||||||
border: '1px',
|
border: '1px',
|
||||||
borderColor: '#2d2d2d', // $gray-1100
|
borderColor: '#2d2d2d', // $gray-1100
|
||||||
backgroundColor: '#2d2d2d', // $gray-1100
|
backgroundColor: '#2d2d2d', // $gray-1100
|
||||||
|
_disabled: {
|
||||||
|
color: 'white',
|
||||||
|
borderColor: '#2d2d2d', // $gray-1100
|
||||||
|
backgroundColor: '#2d2d2d', // $gray-1100
|
||||||
|
opacity: 0.6,
|
||||||
|
},
|
||||||
_checked: {
|
_checked: {
|
||||||
borderColor: '#3182ce', // $action-blue
|
borderColor: '#3182ce', // $action-blue
|
||||||
backgroundColor: '#3182ce', //$action-blue
|
backgroundColor: '#3182ce', //$action-blue
|
||||||
|
_disabled: {
|
||||||
|
color: 'white',
|
||||||
|
borderColor: '#3182ce', // $action-blue
|
||||||
|
backgroundColor: '#3182ce', //$action-blue
|
||||||
|
opacity: 0.6,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
_focus: {
|
_focus: {
|
||||||
boxShadow: '0 0 0 1px #578AF4', // $blue-500
|
boxShadow: 'none',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
label: {
|
label: {
|
||||||
|
|||||||
@@ -1,19 +1,27 @@
|
|||||||
export const ontimeMenuOnDark = {
|
export const ontimeMenuOnDark = {
|
||||||
list: {
|
list: {
|
||||||
|
fontSize: 'calc(1rem - 2px)',
|
||||||
borderRadius: '3px',
|
borderRadius: '3px',
|
||||||
border: 'none',
|
borderColor: 'rgba(255, 255, 255, 0.1)',
|
||||||
bg: '#fff', // $gray-50
|
color: '#ececec', // $gray-1030
|
||||||
|
backgroundColor: '#202020', // $gray-1250
|
||||||
zIndex: 100,
|
zIndex: 100,
|
||||||
},
|
},
|
||||||
item: {
|
item: {
|
||||||
letterSpacing: '0.15px',
|
backgroundColor: 'transparent',
|
||||||
color: '#101010', // $gray-1350
|
paddingBlock: '0.5rem',
|
||||||
bg: '#fff', //
|
|
||||||
_hover: {
|
_hover: {
|
||||||
backgroundColor: '#e2e2e2', // $gray-200
|
backgroundColor: 'rgba(0, 0, 0, 0.1)',
|
||||||
|
_disabled: {
|
||||||
|
backgroundColor: 'transparent',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
_disabled: {
|
||||||
|
color: '#b1b1b1', // $gray-400
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
divider: {
|
divider: {
|
||||||
borderColor: '#cfcfcf', // $gray-200
|
borderColor: 'rgba(255, 255, 255, 0.07)',
|
||||||
|
opacity: 1,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -57,8 +57,8 @@ const theme = extendTheme({
|
|||||||
},
|
},
|
||||||
Drawer: {
|
Drawer: {
|
||||||
variants: {
|
variants: {
|
||||||
'ontime': {...ontimeDrawer},
|
ontime: { ...ontimeDrawer },
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
Editable: {
|
Editable: {
|
||||||
variants: {
|
variants: {
|
||||||
|
|||||||
@@ -4,8 +4,8 @@ const fileToUpload = 'e2e/tests/fixtures/test-db.json';
|
|||||||
|
|
||||||
test('project file upload', async ({ page }) => {
|
test('project file upload', async ({ page }) => {
|
||||||
await page.goto('http://localhost:4001/editor');
|
await page.goto('http://localhost:4001/editor');
|
||||||
await page.getByRole('button', { name: 'Rundown menu' }).click();
|
await page.getByRole('button', { name: 'Edit mode' }).click();
|
||||||
await page.getByRole('menuitem', { name: 'Delete all events' }).click();
|
await page.getByRole('button', { name: 'Clear rundown' }).click();
|
||||||
|
|
||||||
await page.getByRole('button', { name: 'toggle settings' }).click();
|
await page.getByRole('button', { name: 'toggle settings' }).click();
|
||||||
await page.getByRole('button', { name: 'Project', exact: true }).click();
|
await page.getByRole('button', { name: 'Project', exact: true }).click();
|
||||||
|
|||||||
@@ -4,9 +4,8 @@ test('delay blocks add time to events', async ({ page }) => {
|
|||||||
await page.goto('http://localhost:4001/editor');
|
await page.goto('http://localhost:4001/editor');
|
||||||
|
|
||||||
// delete all events and add a new one
|
// delete all events and add a new one
|
||||||
await page.getByRole('button', { name: 'Rundown menu' }).click();
|
await page.getByRole('button', { name: 'Edit mode' }).click();
|
||||||
await page.getByRole('menuitem', { name: 'Delete all events' }).click();
|
await page.getByRole('button', { name: 'Clear rundown' }).click();
|
||||||
await page.getByRole('button', { name: 'Rundown menu' }).click();
|
|
||||||
await page.getByRole('button', { name: 'Create event' }).click();
|
await page.getByRole('button', { name: 'Create event' }).click();
|
||||||
|
|
||||||
// add data to new event
|
// add data to new event
|
||||||
@@ -18,8 +17,7 @@ test('delay blocks add time to events', async ({ page }) => {
|
|||||||
await page.getByTestId('rundown').getByPlaceholder('Duration').press('Enter');
|
await page.getByTestId('rundown').getByPlaceholder('Duration').press('Enter');
|
||||||
|
|
||||||
// add delay block
|
// add delay block
|
||||||
await page.getByRole('button', { name: 'Rundown menu' }).click();
|
await page.getByRole('button', { name: 'Delay Alt ⇧ D' }).click();
|
||||||
await page.getByRole('menuitem', { name: 'Add delay at start' }).click();
|
|
||||||
|
|
||||||
// fill positive delay
|
// fill positive delay
|
||||||
await page.getByTestId('delay-input').click();
|
await page.getByTestId('delay-input').click();
|
||||||
@@ -37,8 +35,7 @@ test('delay blocks add time to events', async ({ page }) => {
|
|||||||
|
|
||||||
// add new delay
|
// add new delay
|
||||||
await page.getByTestId('rundown').getByPlaceholder('Start').click();
|
await page.getByTestId('rundown').getByPlaceholder('Start').click();
|
||||||
await page.getByRole('button', { name: 'Rundown menu' }).click();
|
await page.getByRole('button', { name: 'Delay Alt ⇧ D' }).click();
|
||||||
await page.getByRole('menuitem', { name: 'Add delay at start' }).click();
|
|
||||||
await page.getByTestId('delay-input').click();
|
await page.getByTestId('delay-input').click();
|
||||||
await page.getByTestId('delay-input').fill('10m');
|
await page.getByTestId('delay-input').fill('10m');
|
||||||
await page.getByTestId('delay-input').press('Enter');
|
await page.getByTestId('delay-input').press('Enter');
|
||||||
@@ -54,9 +51,10 @@ test('delays are show correctly', async ({ page }) => {
|
|||||||
await page.goto('http://localhost:4001/editor');
|
await page.goto('http://localhost:4001/editor');
|
||||||
|
|
||||||
// add a test event
|
// add a test event
|
||||||
await page.getByRole('button', { name: 'Rundown menu' }).click();
|
await page.getByRole('button', { name: 'Edit mode' }).click();
|
||||||
await page.getByRole('menuitem', { name: 'Delete all events' }).click();
|
await page.getByRole('button', { name: 'Clear rundown' }).click();
|
||||||
await page.getByRole('button', { name: 'Create Event' }).click();
|
await page.getByRole('button', { name: 'Create Event' }).click();
|
||||||
|
|
||||||
await page.getByTestId('time-input-timeStart').click();
|
await page.getByTestId('time-input-timeStart').click();
|
||||||
await page.getByTestId('rundown').getByTestId('time-input-timeStart').click();
|
await page.getByTestId('rundown').getByTestId('time-input-timeStart').click();
|
||||||
await page.getByTestId('rundown').getByTestId('time-input-timeStart').fill('10');
|
await page.getByTestId('rundown').getByTestId('time-input-timeStart').fill('10');
|
||||||
@@ -70,8 +68,7 @@ test('delays are show correctly', async ({ page }) => {
|
|||||||
await expect(page.getByTestId('entry-1').locator('#block-status')).toHaveAttribute('data-ispublic', 'true');
|
await expect(page.getByTestId('entry-1').locator('#block-status')).toHaveAttribute('data-ispublic', 'true');
|
||||||
|
|
||||||
// add a delay
|
// add a delay
|
||||||
await page.getByRole('button', { name: 'Rundown menu' }).click();
|
await page.getByRole('button', { name: 'Delay Alt ⇧ D' }).click();
|
||||||
await page.getByRole('menuitem', { name: 'Add delay at start' }).click();
|
|
||||||
await page.getByTestId('delay-input').click();
|
await page.getByTestId('delay-input').click();
|
||||||
await page.getByTestId('delay-input').fill('1');
|
await page.getByTestId('delay-input').fill('1');
|
||||||
await page.getByTestId('delay-input').press('Enter');
|
await page.getByTestId('delay-input').press('Enter');
|
||||||
|
|||||||
@@ -4,27 +4,26 @@ test('CRUD operations on the rundown', async ({ page }) => {
|
|||||||
await page.goto('http://localhost:4001/editor');
|
await page.goto('http://localhost:4001/editor');
|
||||||
|
|
||||||
// clear rundown
|
// clear rundown
|
||||||
await page.getByRole('button', { name: 'Run mode' }).click();
|
await page.getByRole('button', { name: 'Edit mode' }).click();
|
||||||
await page.getByRole('button', { name: 'Rundown menu' }).click();
|
await page.getByRole('button', { name: 'Clear rundown' }).click();
|
||||||
await page.getByRole('menuitem', { name: 'Delete all events' }).click();
|
|
||||||
|
|
||||||
// create event from the rundown empty button
|
// create event from the rundown empty button
|
||||||
await page.getByRole('button', { name: 'Create Event' }).click();
|
await page.getByRole('button', { name: 'Create Event' }).click();
|
||||||
|
|
||||||
// create blocks using the quick add buttons
|
// create blocks using the quick add buttons
|
||||||
await page.getByRole('button', { name: 'Block' }).click();
|
await page.getByRole('button', { name: 'Block' }).nth(1).click();
|
||||||
await page.getByRole('button', { name: 'Delay' }).click();
|
await page.getByRole('button', { name: 'Delay' }).nth(1).click();
|
||||||
await page.getByTestId('quick-add-event').click();
|
await page.getByRole('button', { name: 'Event', exact: true }).nth(1).click();
|
||||||
|
|
||||||
// test quick add options - start is last end
|
// test quick add options - star2+5-t is last end
|
||||||
await page.getByTestId('entry-2').getByTestId('time-input-duration').fill('20m');
|
await page.getByTestId('entry-2').getByTestId('time-input-duration').fill('20m');
|
||||||
await page.getByTestId('quick-add-event').click();
|
await page.getByRole('button', { name: 'Event', exact: true }).nth(1).click();
|
||||||
await expect(page.getByLabel('Link to previous')).toBeChecked();
|
await expect(page.getByLabel('Link to previous').nth(1)).toBeChecked();
|
||||||
expect(await page.getByTestId('entry-3').getByTestId('time-input-timeStart').inputValue()).toContain('00:30:00');
|
expect(await page.getByTestId('entry-3').getByTestId('time-input-timeStart').inputValue()).toContain('00:30:00');
|
||||||
|
|
||||||
// test quick add options - event is public
|
// test quick add options - event is public
|
||||||
await expect(page.getByLabel('Event is public')).toBeChecked();
|
await expect(page.getByLabel('Event is public').nth(1)).toBeChecked();
|
||||||
await page.getByTestId('quick-add-event').click();
|
await page.getByRole('button', { name: 'Event', exact: true }).nth(1).click();
|
||||||
|
|
||||||
await expect(page.getByTestId('entry-4').locator('#block-status')).toHaveAttribute('data-ispublic', 'true');
|
await expect(page.getByTestId('entry-4').locator('#block-status')).toHaveAttribute('data-ispublic', 'true');
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -3,9 +3,8 @@ import { test, expect } from '@playwright/test';
|
|||||||
test('smoke test operator', async ({ page }) => {
|
test('smoke test operator', async ({ page }) => {
|
||||||
// make some boilerplate
|
// make some boilerplate
|
||||||
await page.goto('http://localhost:4001/editor');
|
await page.goto('http://localhost:4001/editor');
|
||||||
await page.getByRole('button', { name: 'Run mode' }).click();
|
await page.getByRole('button', { name: 'Edit mode' }).click();
|
||||||
await page.getByRole('button', { name: 'Rundown menu' }).click();
|
await page.getByRole('button', { name: 'Clear rundown' }).click();
|
||||||
await page.getByRole('menuitem', { name: 'Delete all events' }).click();
|
|
||||||
await page.getByRole('button', { name: 'Create Event' }).click();
|
await page.getByRole('button', { name: 'Create Event' }).click();
|
||||||
|
|
||||||
await page.getByTestId('time-input-timeStart').fill('1m');
|
await page.getByTestId('time-input-timeStart').fill('1m');
|
||||||
@@ -13,36 +12,40 @@ test('smoke test operator', async ({ page }) => {
|
|||||||
await page.getByTestId('time-input-duration').fill('1m');
|
await page.getByTestId('time-input-duration').fill('1m');
|
||||||
await page.getByTestId('time-input-duration').press('Enter');
|
await page.getByTestId('time-input-duration').press('Enter');
|
||||||
|
|
||||||
await page.getByTestId('quick-add-event').click();
|
await page.getByRole('button', { name: 'Event', exact: true }).nth(1).click();
|
||||||
await page.getByTestId('entry-2').getByTestId('lock__duration').click();
|
await page.getByTestId('entry-2').getByTestId('lock__duration').click();
|
||||||
await page.getByTestId('entry-2').getByTestId('time-input-duration').fill('1m');
|
await page.getByTestId('entry-2').getByTestId('time-input-duration').fill('1m');
|
||||||
await page.getByTestId('entry-2').getByTestId('time-input-duration').press('Enter');
|
await page.getByTestId('entry-2').getByTestId('time-input-duration').press('Enter');
|
||||||
await page.getByTestId('entry-2').getByTestId('time-input-duration').press('Enter');
|
await page.getByTestId('entry-2').getByTestId('time-input-duration').press('Enter');
|
||||||
|
|
||||||
await page.getByTestId('quick-add-event').click();
|
await page.getByRole('button', { name: 'Event Alt E', exact: true }).click();
|
||||||
await page.getByTestId('entry-3').getByTestId('lock__duration').click();
|
await page.getByTestId('entry-3').getByTestId('lock__duration').click();
|
||||||
await page.getByTestId('entry-3').getByTestId('time-input-duration').fill('1m');
|
await page.getByTestId('entry-3').getByTestId('time-input-duration').fill('1m');
|
||||||
await page.getByTestId('entry-3').getByTestId('time-input-duration').press('Enter');
|
await page.getByTestId('entry-3').getByTestId('time-input-duration').press('Enter');
|
||||||
|
|
||||||
await page.getByRole('button', { name: 'Rundown menu' }).click();
|
await page.getByRole('button', { name: 'Block', exact: true }).nth(0).click();
|
||||||
await page.getByRole('menuitem', { name: 'Add block at start' }).click();
|
|
||||||
await page.getByTestId('quick-add-block').click();
|
|
||||||
|
|
||||||
await page.getByRole('button', { name: 'Edit mode' }).click();
|
await page.getByRole('button', { name: 'Edit mode' }).click();
|
||||||
await page.getByTestId('entry-1').getByRole('button', { name: 'Event options' }).first().click();
|
await page.getByTestId('entry-1').click({ button: 'right' });
|
||||||
await page.getByLabel('Title', { exact: true }).click();
|
await page.getByRole('menuitem', { name: 'Event after' }).click();
|
||||||
await page.getByLabel('Title', { exact: true }).fill('title 1');
|
|
||||||
await page.getByLabel('Title', { exact: true }).press('Enter');
|
|
||||||
|
|
||||||
await page.getByTestId('entry-2').getByRole('button', { name: 'Event options' }).first().click();
|
await page.getByTestId('entry-1').getByTestId('block__title').click();
|
||||||
await page.getByLabel('Title', { exact: true }).click();
|
await page.getByTestId('entry-1').getByTestId('block__title').fill('title 1');
|
||||||
await page.getByLabel('Title', { exact: true }).fill('title 2');
|
await page.getByTestId('entry-1').getByTestId('block__title').press('Enter');
|
||||||
await page.getByLabel('Title', { exact: true }).press('Enter');
|
|
||||||
|
|
||||||
await page.getByTestId('entry-3').getByRole('button', { name: 'Event options' }).first().click();
|
await page.getByTestId('entry-2').click({ button: 'right' });
|
||||||
await page.getByLabel('Title', { exact: true }).click();
|
await page.getByRole('menuitem', { name: 'Event after' }).click();
|
||||||
await page.getByLabel('Title', { exact: true }).fill('title 3');
|
|
||||||
await page.getByLabel('Title', { exact: true }).press('Enter');
|
await page.getByTestId('entry-2').getByTestId('block__title').click();
|
||||||
|
await page.getByTestId('entry-2').getByTestId('block__title').fill('title 2');
|
||||||
|
await page.getByTestId('entry-2').getByTestId('block__title').press('Enter');
|
||||||
|
|
||||||
|
await page.getByTestId('entry-3').click({ button: 'right' });
|
||||||
|
await page.getByRole('menuitem', { name: 'Event after' }).click();
|
||||||
|
|
||||||
|
await page.getByTestId('entry-3').getByTestId('block__title').click();
|
||||||
|
await page.getByTestId('entry-3').getByTestId('block__title').fill('title 3');
|
||||||
|
await page.getByTestId('entry-3').getByTestId('block__title').press('Enter');
|
||||||
|
|
||||||
// start an event
|
// start an event
|
||||||
await page.getByTestId('panel-timer-control').getByRole('button', { name: 'Start' }).click();
|
await page.getByTestId('panel-timer-control').getByRole('button', { name: 'Start' }).click();
|
||||||
|
|||||||
@@ -4,8 +4,8 @@ const fileToUpload = 'e2e/tests/fixtures/test-sheet.xlsx';
|
|||||||
|
|
||||||
test('sheet file upload', async ({ page }) => {
|
test('sheet file upload', async ({ page }) => {
|
||||||
await page.goto('http://localhost:4001/editor');
|
await page.goto('http://localhost:4001/editor');
|
||||||
await page.getByRole('button', { name: 'Rundown menu' }).click();
|
await page.getByRole('button', { name: 'Edit mode' }).click();
|
||||||
await page.getByRole('menuitem', { name: 'Delete all events' }).click();
|
await page.getByRole('button', { name: 'Clear rundown' }).click();
|
||||||
|
|
||||||
await page.getByRole('button', { name: 'Toggle settings' }).click();
|
await page.getByRole('button', { name: 'Toggle settings' }).click();
|
||||||
await page.getByRole('button', { name: 'Import spreadsheet' }).click();
|
await page.getByRole('button', { name: 'Import spreadsheet' }).click();
|
||||||
|
|||||||
Reference in New Issue
Block a user