Event editor continue (#722)

* refactor: remove info panel

* feat: editor panel

* wip: event info in drawer

* chore: update tests

* chore: add scss to processor
This commit is contained in:
Carlos Valente
2024-01-22 16:23:11 +01:00
committed by GitHub
parent c7328a33f6
commit cb8ba776e2
86 changed files with 686 additions and 773 deletions
@@ -1,4 +1,3 @@
@use "../../../theme/v2Styles" as *;
@use "../../../theme/mixins" as *;
.link {
@@ -1,5 +1,3 @@
@use '../../../theme/v2Styles' as *;
.header {
font-size: $inner-section-text-size;
font-weight: 600;
@@ -1,5 +1,3 @@
@use '../../../theme/v2Styles' as *;
.delaySymbol {
svg {
font-size: 1.5rem;
@@ -1,5 +1,3 @@
@use '../../../theme/v2Styles' as *;
.errorContainer {
width: 100%;
height: 100%;
@@ -1,6 +1,3 @@
@use '../../../theme/v2Styles' as *;
@use '../../../theme/ontimeColours' as *;
.link {
display: flex;
align-items: center;
@@ -4,6 +4,8 @@ import { Textarea, TextareaProps } from '@chakra-ui/react';
import autosize from 'autosize/dist/autosize';
export const AutoTextArea = (props: TextareaProps) => {
const { value } = props;
const ref = useRef<HTMLTextAreaElement>(null);
useEffect(() => {
@@ -12,7 +14,7 @@ export const AutoTextArea = (props: TextareaProps) => {
return () => {
autosize.destroy(node);
};
}, []);
}, [value]);
return (
<Textarea
@@ -1,24 +1,23 @@
.list {
display: flex;
gap: 4px;
gap: 0.5rem
}
.swatch {
cursor: pointer;
aspect-ratio: 1;
width: 2rem;
height: 2rem;
border-radius: 16px;
border: 4px solid #262626;
aspect-ratio: 1;
border-radius: 99px;
border: 2px solid $gray-1200;
&.selected {
border: 2px solid #578AF4;
border: 2px solid $blue-500;
}
}
.center {
display: grid;
place-content: center;
color: #578AF4;
}
color: $ui-white;
}
@@ -1,15 +1,13 @@
import { useCallback } from 'react';
import { EditorUpdateFields } from '../../../../features/event-editor/EventEditor';
import Swatch from './Swatch';
import style from './SwatchSelect.module.scss';
interface ColourInputProps {
value: string;
name: EditorUpdateFields;
handleChange: (newValue: EditorUpdateFields, name: string) => void;
name: 'colour';
handleChange: (newValue: 'colour', name: string) => void;
}
const colours = [
@@ -1,5 +1,3 @@
@use '../../../../theme/v2Styles' as *;
$input-font-size: 15px;
.delayInput {
@@ -1,6 +1,3 @@
@use "../../../../theme/_ontimeColours" as *;
@use "../../../../theme/_v2Styles" as *;
.swatch {
width: 24px;
height: 24px;
@@ -1,7 +1,7 @@
import { useCallback, useRef } from 'react';
import { Input, Textarea } from '@chakra-ui/react';
import { EventEditorSubmitActions } from '../../../../features/event-editor/EventEditor';
import { EventEditorSubmitActions } from '../../../../features/rundown/event-editor/EventEditor';
import { Size } from '../../../models/Util.type';
import useReactiveTextInput from './useReactiveTextInput';
@@ -1,5 +1,3 @@
@use "../../../../theme/v2Styles" as *;
$input-font-size: 15px;
$input-delayed-border-color: #E69056;
@@ -1,5 +1,3 @@
@use "../../../../theme/v2Styles" as *;
$input-font-size: 15px;
$input-delayed-border-color: #E69056;
@@ -1,6 +1,4 @@
@use '../../../theme/v2Styles' as *;
@use '../../../theme/mixins' as *;
@use '../../../theme/ontimeColours' as *;
$menu-bg: $gray-1200;
$menu-hover-bg: $gray-1350;
@@ -1,5 +1,3 @@
@use '../../../theme/v2Styles' as *;
.container {
display: grid;
place-content: center;
@@ -1,5 +1,3 @@
@use '../../../theme/ontimeColours' as *;
.emptyContainer {
width: 100%;
text-align: center;
@@ -1,6 +1,3 @@
@use '../../../theme/v2Styles' as *;
@use '../../../theme/ontimeColours' as *;
.drawerContent {
background-color: $gray-1250;
}
@@ -1,8 +1,6 @@
@use '../theme/v2Styles' as *;
.wrapper {
background: $bg-container-l1;
width: 100%;
height: 100%;
padding: max(16px, 2vh);
}
}
@@ -1,6 +1,3 @@
@use '../../theme/ontimeColours' as *;
@use '../../theme/v2Styles' as *;
.container {
grid-area: main;
@@ -1,6 +1,3 @@
@use '../../../theme/ontimeColours' as *;
@use '../../../theme/v2Styles' as *;
.tabs,
ul {
list-style: none;
@@ -1,6 +1,3 @@
@use '../../../theme/ontimeColours' as *;
@use '../../../theme/v2Styles' as *;
.header {
font-size: 2rem;
padding-bottom: 0.5rem;
@@ -1,6 +1,3 @@
@use '../../../../theme/ontimeColours' as *;
@use '../../../../theme/v2Styles' as *;
$info-gray: $secondary-text-gray;
$info-hover: $section-white;
@@ -1,5 +1,3 @@
@use '../../../../theme/v2Styles' as *;
.interfaces {
display: flex;
flex-wrap: wrap;
@@ -1,5 +1,3 @@
@use '../../../../theme/ontimeColours' as *;
.current {
color: $blue-400;
background-color: $gray-1350;
@@ -1,5 +1,3 @@
@use '../../../theme/v2Styles' as *;
.inputItems {
display: grid;
grid-template-columns: 1fr auto;
@@ -1,5 +1,3 @@
@use '../../../theme/v2Styles' as *;
.messageContainer {
display: flex;
flex-direction: column;
@@ -1,5 +1,3 @@
@use '../../../../theme/v2Styles' as *;
.buttonContainer {
padding-top: $element-spacing;
display: grid;
@@ -1,6 +1,3 @@
@use '../../../../theme/v2Styles' as *;
@use '../../../../theme/ontimeColours' as *;
.timeContainer {
display: grid;
grid-template-areas:
@@ -1,6 +1,3 @@
@use '../../../../theme/v2Styles' as *;
@use '../../../../theme/ontimeColours' as *;
$button-bg-gray: $gray-1050;
$button-color-white: $gray-50;
@@ -1,6 +1,3 @@
@use '../../theme/ontimeColours' as *;
@use '../../theme/v2Styles' as *;
$table-font-size: calc(1rem - 2px);
$table-header-font-size: calc(1rem - 3px);
@@ -1,6 +1,3 @@
@use '../../theme/v2Styles' as *;
@use '../../theme/ontimeColours' as *;
.tableWrapper {
width: 100%;
height: 100vh;
@@ -1,6 +1,3 @@
@use '../../../theme/ontimeColours' as *;
@use '../../../theme/v2Styles' as *;
$label-colour: $gray-700;
$active-colour: $gray-500;
@@ -1,6 +1,3 @@
@use '../../../theme/v2Styles' as *;
@use '../../../theme/ontimeColours' as *;
.tableSettings {
grid-area: settings;
padding: 1rem;
@@ -1,28 +1,10 @@
@use '../../theme/ontimeColours' as *;
@use '../../theme/v2Styles' as *;
@use './EditorMixin' as editor;
$menu-width: 2.75rem;
$rundown-width: 44.5rem;
$playback-width: 26rem;
@mixin absolute-top-right($distance) {
position: absolute;
top: $distance;
right: $distance;
cursor: pointer;
color: $ui-white;
transition-property: color;
transition-duration: $transition-time-action;
&:hover {
color: $ontime-color;
}
}
.corner {
display: none;
@include absolute-top-right(0.5rem);
transform: rotate(45deg);
@include editor.corner;
}
.mainContainer {
@@ -53,6 +35,12 @@ $playback-width: 26rem;
gap: 0.5rem;
overflow: hidden;
.corner {
/* we show this if the component hasnt been extracted */
display: inline;
}
.rundown,
.playback,
.messages {
@@ -61,21 +49,6 @@ $playback-width: 26rem;
height: 100%;
background-color: $bg-container-l2;
padding: 1rem;
.corner {
/* we show this if the component hasnt been extracted */
display: inline;
}
}
}
.rundown {
grid-area: rundown;
min-width: $rundown-width;
.content {
height: calc(100% - 1.5rem);
overflow: hidden;
}
}
@@ -93,9 +66,3 @@ $playback-width: 26rem;
.content {
padding-top: 1.5rem;
}
.rundown {
.content {
padding-top: 0.5rem;
}
}
@@ -16,7 +16,6 @@ import styles from './Editor.module.scss';
const Rundown = lazy(() => import('../rundown/RundownExport'));
const TimerControl = lazy(() => import('../control/playback/TimerControlExport'));
const MessageControl = lazy(() => import('../control/message/MessageControlExport'));
const EventEditor = lazy(() => import('../event-editor/EventEditorExport'));
const IntegrationModal = lazy(() => import('../modals/integration-modal/IntegrationModal'));
const SettingsModal = lazy(() => import('../modals/settings-modal/SettingsModal'));
@@ -88,7 +87,6 @@ export default function Editor() {
)}
<Overview />
</div>
<EventEditor />
</>
);
}
@@ -0,0 +1,37 @@
@use '../../theme/ontimeColours' as *;
@use '../../theme/ontimeStyles' as *;
@mixin absolute-top-right($distance) {
position: absolute;
top: $distance;
right: $distance;
cursor: pointer;
color: $ui-white;
transition-property: color;
transition-duration: $transition-time-action;
&:hover {
color: $ontime-color;
}
}
@mixin corner() {
display: none;
@include absolute-top-right(0.5rem);
transform: rotate(45deg);
}
@mixin panel() {
display: flex;
position: relative;
border-radius: 8px;
height: 100%;
background-color: $bg-container-l2;
padding: 1rem;
.corner {
/* we show this if the component hasnt been extracted */
display: inline;
}
}
@@ -1,138 +0,0 @@
@use '../../theme/ontimeColours' as *;
@use '../../theme/v2Styles' as *;
.eventEditorContainer {
border-radius: 8px 8px 0 0;
background-color: $bg-container-l2;
box-shadow: $large-bottom-drawer-shadow;
border-top: 1px solid $white-20;
position: absolute;
bottom: 0;
width: 100vw;
left: 0;
z-index: 10;
color: white;
transition: bottom $transition-time-feedback;
&.noEvent {
bottom: -500px;
transition: bottom 0.7s;
}
.eventEditorLayout {
display: flex;
}
.header {
background-color: $gray-1250;
padding: 0.5rem;
border-left: 1px solid $white-10;
border-radius: 0 8px 0 0;
}
}
.eventEditor {
padding: 1rem 2rem 2rem 2rem;
width: 100%;
gap: max(1rem, 2vh);
display: grid;
grid-template-areas: 'time left right';
grid-template-columns: auto 1fr 1fr;
}
.timeOptions {
grid-area: time;
display: flex;
gap: 1.5rem;
.timers,
.timeSettings {
display: flex;
flex-direction: column;
gap: 0.5rem;
}
}
.left,
.right {
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.left {
grid-area: left;
padding: 0 1rem;
border-left: 1px solid $border-color-ondark;
}
.right {
padding-left: 1rem;
grid-area: right;
border-left: 1px solid $border-color-ondark;
}
@mixin input-label() {
font-size: calc(1rem - 3px);
color: $label-gray;
}
.countedInput {
display: flex;
justify-content: space-between;
@include input-label;
}
.inputLabel {
display: block;
@include input-label;
&.delayLabel {
color: $ontime-delay-text;
}
&.publicToggle {
height: 2rem;
display: flex;
align-items: center;
justify-items: center;
gap: 0.5rem;
}
}
.eventActions {
margin-left: auto;
display: flex;
gap: 0.5rem;
}
.spacer {
height: 1.25rem;
}
.inline {
display: flex;
align-items: center;
gap: 1rem;
}
.splitTwo {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 1.5rem;
}
.column {
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.padTop {
margin-top: 0.5rem;
}
.fullHeight {
height: 100%
}
@@ -1,84 +0,0 @@
import { useCallback, useEffect, useState } from 'react';
import { isOntimeEvent, OntimeEvent } from 'ontime-types';
import CopyTag from '../../common/components/copy-tag/CopyTag';
import { useEventAction } from '../../common/hooks/useEventAction';
import useRundown from '../../common/hooks-query/useRundown';
import { useEventSelection } from '../../features/rundown/useEventSelection';
import EventEditorDataLeft from './composite/EventEditorDataLeft';
import EventEditorDataRight from './composite/EventEditorDataRight';
import EventEditorTimes from './composite/EventEditorTimes';
import style from './EventEditor.module.scss';
export type EventEditorSubmitActions = keyof OntimeEvent;
export type EditorUpdateFields = 'cue' | 'title' | 'presenter' | 'subtitle' | 'note' | 'colour';
export default function EventEditor() {
const selectedEvents = useEventSelection((state) => state.selectedEvents);
const { data } = useRundown();
const { updateEvent } = useEventAction();
const [event, setEvent] = useState<OntimeEvent | null>(null);
useEffect(() => {
if (!data) {
setEvent(null);
return;
}
const event = data.find((event) => selectedEvents.has(event.id));
if (event && isOntimeEvent(event)) {
setEvent(event);
}
}, [data, selectedEvents]);
const handleSubmit = useCallback(
(field: EditorUpdateFields, value: string) => {
updateEvent({ id: event?.id, [field]: value });
},
[event?.id, updateEvent],
);
if (!event) {
return <span>Loading...</span>;
}
return (
<div className={style.eventEditor}>
<EventEditorTimes
eventId={event.id}
timeStart={event.timeStart}
timeEnd={event.timeEnd}
duration={event.duration}
delay={event.delay ?? 0}
isPublic={event.isPublic}
endAction={event.endAction}
timerType={event.timerType}
timeWarning={event.timeWarning}
timeDanger={event.timeDanger}
/>
<EventEditorDataLeft
key={`${event.id}-left`}
eventId={event.id}
cue={event.cue}
title={event.title}
presenter={event.presenter}
subtitle={event.subtitle}
handleSubmit={handleSubmit}
/>
<EventEditorDataRight
key={`${event.id}-right`}
note={event.note}
colour={event.colour}
handleSubmit={handleSubmit}
>
<CopyTag label='Event ID'>{event.id}</CopyTag>
<CopyTag label='OSC trigger by id'>{`/ontime/gotoid "${event.id}"`}</CopyTag>
<CopyTag label='OSC trigger by cue'>{`/ontime/gotocue "${event.cue}"`}</CopyTag>
</EventEditorDataRight>
</div>
);
}
@@ -1,42 +0,0 @@
import { memo } from 'react';
import { IconButton } from '@chakra-ui/react';
import { IoClose } from '@react-icons/all-files/io5/IoClose';
import ErrorBoundary from '../../common/components/error-boundary/ErrorBoundary';
import { AppMode, useAppMode } from '../../common/stores/appModeStore';
import { cx } from '../../common/utils/styleUtils';
import { useEventSelection } from '../rundown/useEventSelection';
import EventEditor from './EventEditor';
import style from './EventEditor.module.scss';
const EventEditorExport = () => {
const { clearSelectedEvents, selectedEvents } = useEventSelection();
const { mode } = useAppMode();
const editorStyle = cx([
style.eventEditorContainer,
selectedEvents.size > 1 || selectedEvents.size === 0 || mode === AppMode.Run ? style.noEvent : null,
]);
const removeOpenEvent = () => clearSelectedEvents();
return (
<div className={editorStyle}>
<ErrorBoundary>
<div className={style.eventEditorLayout}>
<EventEditor />
<div className={style.header}>
<IconButton
aria-label='Close Menu'
icon={<IoClose />}
onClick={removeOpenEvent}
variant='ontime-ghosted-white'
/>
</div>
</div>
</ErrorBoundary>
</div>
);
};
export default memo(EventEditorExport);
@@ -1,54 +0,0 @@
import { memo } from 'react';
import { Input } from '@chakra-ui/react';
import { sanitiseCue } from 'ontime-utils';
import { type EditorUpdateFields } from '../EventEditor';
import CountedTextInput from './CountedTextInput';
import style from '../EventEditor.module.scss';
interface EventEditorLeftProps {
eventId: string;
cue: string;
title: string;
presenter: string;
subtitle: string;
handleSubmit: (field: EditorUpdateFields, value: string) => void;
}
const EventEditorDataLeft = (props: EventEditorLeftProps) => {
const { eventId, cue, title, presenter, subtitle, handleSubmit } = props;
const cueSubmitHandler = (_field: string, newValue: string) => {
handleSubmit('cue', sanitiseCue(newValue));
};
return (
<div className={style.left}>
<div className={style.splitTwo}>
<div className={style.column}>
<div className={style.countedInput}>
<label className={style.inputLabel} htmlFor='eventId'>
Event ID (read only)
</label>
</div>
<Input
id='eventId'
size='sm'
variant='ontime-filled'
data-testid='input-textfield'
value={eventId}
readOnly
/>
</div>
<CountedTextInput field='cue' label='Cue' initialValue={cue} submitHandler={cueSubmitHandler} maxLength={10} />
</div>
<CountedTextInput field='title' label='Title' initialValue={title} submitHandler={handleSubmit} />
<CountedTextInput field='presenter' label='Presenter' initialValue={presenter} submitHandler={handleSubmit} />
<CountedTextInput field='subtitle' label='Subtitle' initialValue={subtitle} submitHandler={handleSubmit} />
</div>
);
};
export default memo(EventEditorDataLeft);
@@ -1,33 +0,0 @@
import { memo, PropsWithChildren } from 'react';
import SwatchSelect from '../../../common/components/input/colour-input/SwatchSelect';
import { EditorUpdateFields } from '../EventEditor';
import CountedTextArea from './CountedTextArea';
import style from '../EventEditor.module.scss';
interface EventEditorRightProps {
note: string;
colour: string;
handleSubmit: (field: EditorUpdateFields, value: string) => void;
}
const EventEditorDataRight = (props: PropsWithChildren<EventEditorRightProps>) => {
const { children, note, colour, handleSubmit } = props;
return (
<div className={style.right}>
<div className={style.column}>
<label className={style.inputLabel}>Colour</label>
<div className={style.inline}>
<SwatchSelect name='colour' value={colour} handleChange={handleSubmit} />
</div>
</div>
<CountedTextArea field='note' label='Note' initialValue={note} submitHandler={handleSubmit} />
<div className={style.eventActions}>{children}</div>
</div>
);
};
export default memo(EventEditorDataRight);
@@ -1,176 +0,0 @@
import { memo } from 'react';
import { Select, Switch } from '@chakra-ui/react';
import { EndAction, OntimeEvent, TimerType } from 'ontime-types';
import { calculateDuration, millisToString } from 'ontime-utils';
import TimeInput from '../../../common/components/input/time-input/TimeInput';
import TimeInputWithButton from '../../../common/components/input/time-input/TimeInputWithButton';
import { useEventAction } from '../../../common/hooks/useEventAction';
import { millisToDelayString } from '../../../common/utils/dateConfig';
import { cx } from '../../../common/utils/styleUtils';
import style from '../EventEditor.module.scss';
interface EventEditorTimesProps {
eventId: string;
timeStart: number;
timeEnd: number;
duration: number;
delay: number;
isPublic: boolean;
endAction: EndAction;
timerType: TimerType;
timeWarning: number;
timeDanger: number;
}
type TimeActions =
| 'timeStart'
| 'timeEnd'
| 'durationOverride'
| 'timerType'
| 'endAction'
| 'isPublic'
| 'timeWarning'
| 'timeDanger';
// Todo: add previous end to TimeInput fields
const EventEditorTimes = (props: EventEditorTimesProps) => {
const { eventId, timeStart, timeEnd, duration, delay, isPublic, endAction, timerType, timeWarning, timeDanger } =
props;
const { updateEvent } = useEventAction();
const handleSubmit = (field: TimeActions, value: number | string | boolean) => {
const newEventData: Partial<OntimeEvent> = { id: eventId };
switch (field) {
case 'durationOverride': {
// duration defines timeEnd
newEventData.duration = value as number;
newEventData.timeEnd = timeStart + (value as number);
break;
}
case 'timeStart': {
newEventData.duration = calculateDuration(value as number, timeEnd);
newEventData.timeStart = value as number;
break;
}
case 'timeEnd': {
newEventData.duration = calculateDuration(timeStart, value as number);
newEventData.timeEnd = value as number;
break;
}
case 'isPublic': {
updateEvent({ id: eventId, isPublic: !(value as boolean) });
break;
}
default: {
if (field === 'timerType' || field === 'endAction' || field === 'timeWarning' || field === 'timeDanger') {
// @ts-expect-error -- not sure how to typecheck here
newEventData[field as keyof OntimeEvent] = value as string;
} else {
return;
}
}
}
updateEvent(newEventData);
};
const delayTime = delay !== 0 ? millisToDelayString(delay) : null;
const startLabel = delayTime ? `New start ${millisToString(timeStart + delay)}` : 'Start time';
const endLabel = delayTime ? `New end ${millisToString(timeEnd + delay)}` : 'End time';
const inputTimeLabels = cx([style.inputLabel, delayTime ? style.delayLabel : null]);
return (
<div className={style.timeOptions}>
<div className={style.timers}>
<label className={inputTimeLabels} htmlFor='timeStart'>
{startLabel}
</label>
<TimeInputWithButton
id='timeStart'
name='timeStart'
submitHandler={handleSubmit}
time={timeStart}
delay={delay}
placeholder='Start'
/>
<label className={inputTimeLabels} htmlFor='timeEnd'>
{endLabel}
</label>
<TimeInputWithButton
id='timeEnd'
name='timeEnd'
submitHandler={handleSubmit}
time={timeEnd}
delay={delay}
placeholder='End'
/>
<label className={style.inputLabel} htmlFor='durationOverride'>
Duration
</label>
<TimeInputWithButton
id='durationOverride'
name='durationOverride'
submitHandler={handleSubmit}
time={duration}
placeholder='Duration'
/>
<span className={style.spacer} />
<label className={`${style.inputLabel} ${style.publicToggle}`}>
<Switch isChecked={isPublic} onChange={() => handleSubmit('isPublic', isPublic)} variant='ontime' />
Event is public
</label>
</div>
<div className={style.timeSettings}>
<label className={style.inputLabel}>Timer Type</label>
<Select
size='sm'
name='timerType'
value={timerType}
onChange={(event) => handleSubmit('timerType', event.target.value)}
variant='ontime'
>
<option value={TimerType.CountDown}>Count down</option>
<option value={TimerType.CountUp}>Count up</option>
<option value={TimerType.TimeToEnd}>Time to end</option>
<option value={TimerType.Clock}>Clock</option>
</Select>
<label className={style.inputLabel}>End Action</label>
<Select
size='sm'
name='endAction'
value={endAction}
onChange={(event) => handleSubmit('endAction', event.target.value)}
variant='ontime'
>
<option value={EndAction.None}>None</option>
<option value={EndAction.Stop}>Stop</option>
<option value={EndAction.LoadNext}>Load Next</option>
<option value={EndAction.PlayNext}>Play Next</option>
</Select>
<label className={style.inputLabel} htmlFor='timeWarning'>
Warning Time
</label>
<TimeInput
id='timeWarning'
name='timeWarning'
submitHandler={handleSubmit}
time={timeWarning}
placeholder='Duration'
/>
<label className={style.inputLabel} htmlFor='timeDanger'>
Danger Time
</label>
<TimeInput
id='timeDanger'
name='timeDanger'
submitHandler={handleSubmit}
time={timeDanger}
placeholder='Duration'
/>
</div>
</div>
);
};
export default memo(EventEditorTimes);
@@ -1,5 +1,3 @@
@use '../../theme/ontimeColours' as *;
.menu {
grid-area: menu;
display: flex;
@@ -1,6 +1,3 @@
@use '../../theme/v2Styles' as *;
@use '../../theme/ontimeColours' as *;
$el-padding-with-compensation: 24px; // 16 + 8
@mixin modal-link {
@@ -1,6 +1,3 @@
@use '../../theme/v2Styles' as *;
@use '../../theme/ontimeColours' as *;
.link {
display: flex;
align-items: center;
@@ -1,5 +1,3 @@
@use '../../../theme/v2Styles' as *;
.buttonRow {
justify-content: space-between;
margin-top: $section-spacing;
@@ -1,5 +1,3 @@
@use '../../../theme/ontimeColours' as *;
.screenLoader {
position: absolute;
z-index: 10;
@@ -1,5 +1,3 @@
@use '../../../theme/ontimeColours' as *;
.wrapper {
padding: 0.5rem;
}
@@ -1,5 +1,3 @@
@use '../../../theme/v2Styles' as *;
@use '../../../theme/ontimeColours' as *;
@import "../Modal.module.scss";
.uploadBody {
@@ -1,5 +1,3 @@
@use '../../../../theme/_ontimeColours' as *;
.container {
max-width: 100%;
max-height: max(300px, 30vh);
@@ -1,5 +1,3 @@
@use "../../../../theme/_ontimeColours" as *;
.tag {
font-size: calc(1rem - 3px);
letter-spacing: 0.5px;
@@ -1,6 +1,3 @@
@use '../../../../theme/ontimeColours' as *;
@use '../../../../theme/v2Styles' as *;
.uploadedItem {
margin: 0 auto;
width: 100%;
@@ -1,6 +1,3 @@
@use '../../../../theme/v2Styles' as *;
@use '../../../../theme/ontimeColours' as *;
.importTable {
margin: 0.5rem;
height: fit-content;
@@ -1,5 +1,3 @@
@use '../../../../theme/ontimeColours' as *;
@mixin row {
display: flex;
align-items: center;
@@ -1,6 +1,3 @@
@use '../../../src/theme/v2Styles' as *;
@use '../../../src/theme/ontimeColours' as *;
.operatorContainer {
width: 100vw;
height: 100vh;
@@ -1,6 +1,3 @@
@use '../../../theme/v2Styles' as *;
@use '../../../theme/ontimeColours' as *;
.editModal {
position: absolute;
z-index: 2;
@@ -1,6 +1,3 @@
@use '../../../../src/theme/ontimeColours' as *;
@use '../../../../src/theme/v2Styles' as *;
.followButton {
position: fixed;
bottom: 1.5rem;
@@ -1,6 +1,3 @@
@use '../../../../src/theme/ontimeColours' as *;
@use '../../../../src/theme/v2Styles' as *;
.block {
width: 100%;
padding: 0.25rem 0.5rem;
@@ -1,5 +1,3 @@
@use '../../../../src/theme/v2Styles' as *;
@use '../../../../src/theme/ontimeColours' as *;
@import '../Operator.module.scss';
@mixin clock-size {
@@ -1,6 +1,3 @@
@use '../../../../src/theme/ontimeColours' as *;
@use '../../../../src/theme/v2Styles' as *;
@mixin column {
display: flex;
flex-direction: column;
@@ -1,6 +1,3 @@
@use '../../theme/ontimeColours' as *;
@use '../../theme/v2Styles' as *;
.overview {
grid-area: overview;
display: flex;
@@ -1,8 +1,11 @@
@use '../../theme/v2Styles' as *;
.rundownWrapper {
height: calc(100% - 1.5rem);
overflow: hidden;
padding-top: 0.5rem;
flex: 1;
}
.eventContainer {
flex: 1;
margin-top: 1em;
display: flex;
flex-direction: column;
+2 -4
View File
@@ -33,8 +33,6 @@ export default function Rundown(props: RundownProps) {
const startTimeIsLastEnd = eventSettings.startTimeIsLastEnd;
const showQuickEntry = eventSettings.showQuickEntry;
const isExtracted = window.location.pathname.includes('/rundown');
// cursor
const { cursor, mode: appMode } = useAppMode();
const viewFollowsCursor = appMode === AppMode.Run;
@@ -206,7 +204,7 @@ export default function Rundown(props: RundownProps) {
let isPast = Boolean(featureData?.selectedEventId);
return (
<div className={style.eventContainer} ref={scrollRef}>
<div className={style.eventContainer} ref={scrollRef} data-testid='rundown'>
<DndContext onDragEnd={handleOnDragEnd} sensors={sensors} collisionDetection={closestCenter}>
<SortableContext items={statefulEntries} strategy={verticalListSortingStrategy}>
<div className={style.list}>
@@ -248,7 +246,7 @@ export default function Rundown(props: RundownProps) {
previousEventId={previousEventId}
playback={isSelected ? featureData.playback : undefined}
isRolling={featureData.playback === Playback.Roll}
disableEdit={isExtracted || appMode === AppMode.Run}
disableEdit={appMode === AppMode.Run}
/>
</div>
</div>
@@ -4,17 +4,25 @@ import { IoArrowUp } from '@react-icons/all-files/io5/IoArrowUp';
import ErrorBoundary from '../../common/components/error-boundary/ErrorBoundary';
import { handleLinks } from '../../common/utils/linkUtils';
import EventEditor from './event-editor/EventEditor';
import RundownWrapper from './RundownWrapper';
import style from '../editors/Editor.module.scss';
import style from './RundownWrapper.module.scss';
const RundownExport = () => {
const isExtracted = window.location.pathname.includes('/rundown');
return (
<div className={style.rundown} data-testid='panel-rundown'>
<IoArrowUp className={style.corner} onClick={(event) => handleLinks(event, 'rundown')} />
<ErrorBoundary>
<RundownWrapper />
</ErrorBoundary>
<div className={style.rundownExport} data-testid='panel-rundown'>
{!isExtracted && <IoArrowUp className={style.corner} onClick={(event) => handleLinks(event, 'rundown')} />}
<div className={style.rundown}>
<ErrorBoundary>
<RundownWrapper />
</ErrorBoundary>
<ErrorBoundary>
<EventEditor />
</ErrorBoundary>
</div>
</div>
);
};
@@ -0,0 +1,21 @@
@use '../editors//EditorMixin' as editor;
$rundown-width: 44.5rem;
$editor-width: 25rem;
.rundownExport {
grid-area: rundown;
min-width: calc($rundown-width + $editor-width);
max-height: 100%;
position: relative;
}
.rundown {
@include editor.panel;
}
.corner {
@include editor.corner;
display: inline;
z-index: 2;
}
@@ -4,17 +4,17 @@ import RundownMenu from '../menu/RundownMenu';
import Rundown from './Rundown';
import styles from '../editors/Editor.module.scss';
import styles from './Rundown.module.scss';
export default function RundownWrapper() {
const { data, status } = useRundown();
return (
<>
<div>
<RundownMenu />
<div className={styles.content}>
<div className={styles.rundownWrapper}>
{status === 'success' && data ? <Rundown entries={data} /> : <Empty text='Connecting to server' />}
</div>
</>
</div>
);
}
@@ -1,5 +1,5 @@
@use '../../theme/ontimeColours' as *;
@use '../../theme/v2Styles' as *;
@use '../../theme/ontimeStyles' as *;
$block-gap: 0.25rem;
$block-element-spacing: 0.25rem;
@@ -1,5 +1,3 @@
@use '../../../theme/v2Styles' as *;
.titleEditor {
display: block;
font-size: calc(1rem + 2px);
@@ -1,5 +1,3 @@
@use '../../../theme/v2Styles' as *;
@use '../../../theme/ontimeColours' as *;
@use '../blockMixins' as *;
$skip-opacity: 0.1;
@@ -1,5 +1,3 @@
@use '../../../../theme/v2Styles' as *;
.progressBar {
height: 100%;
width: 0;
@@ -0,0 +1,84 @@
// TODO: unify with RundownWrapper.module.scss
$editor-width: 25rem;
.eventEditor {
min-width: $editor-width;
background-color: $gray-1350;
max-height: 100%;
overflow-y: auto;
flex: 1;
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.header {
padding: 1rem;
}
.content {
padding: 1rem;
flex: 1;
display: flex;
flex-direction: column;
gap: 2rem;
overflow-y: auto;
}
.footer {
padding: 1rem;
}
.timeSettings {
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.column {
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.inputLabel {
display: block;
font-size: calc(1rem - 3px);
color: $label-gray;
margin-bottom: 0.25rem;
&.delayLabel {
color: $ontime-delay-text;
}
}
.switchLabel {
font-size: calc(1rem - 3px);
color: $label-gray;
display: flex;
align-items: center;
gap: 0.5rem;
max-width: max-content;
cursor: pointer;
}
.inline {
display: flex;
align-items: center;
gap: 1rem;
}
.splitTwo {
display: grid;
grid-template-columns: 1fr 1fr;
column-gap: 1.5rem;
row-gap: 0.5rem;
}
.eventActions {
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
}
@@ -0,0 +1,118 @@
import { useCallback, useEffect, useState } from 'react';
import { isOntimeEvent, OntimeEvent } from 'ontime-types';
import CopyTag from '../../../common/components/copy-tag/CopyTag';
import { useEventAction } from '../../../common/hooks/useEventAction';
import useRundown from '../../../common/hooks-query/useRundown';
import { useEventSelection } from '../useEventSelection';
import EventEditorTimes from './composite/EventEditorTimes';
import EventEditorTitles from './composite/EventEditorTitles';
import EventEditorUser from './composite/EventEditorUser';
import style from './EventEditor.module.scss';
export type EventEditorSubmitActions = keyof OntimeEvent;
// TODO: this logic will become dynamic
export type EditorUpdateFields =
| 'cue'
| 'title'
| 'presenter'
| 'subtitle'
| 'note'
| 'colour'
| 'user0'
| 'user1'
| 'user2'
| 'user3'
| 'user4'
| 'user5'
| 'user6'
| 'user7'
| 'user8'
| 'user9';
export default function EventEditor() {
const selectedEvents = useEventSelection((state) => state.selectedEvents);
const { data } = useRundown();
const { updateEvent } = useEventAction();
const [event, setEvent] = useState<OntimeEvent | null>(null);
useEffect(() => {
if (!data) {
setEvent(null);
return;
}
const event = data.find((event) => selectedEvents.has(event.id));
if (event && isOntimeEvent(event)) {
setEvent(event);
}
}, [data, selectedEvents]);
const handleSubmit = useCallback(
(field: EditorUpdateFields, value: string) => {
updateEvent({ id: event?.id, [field]: value });
},
[event?.id, updateEvent],
);
if (!event) {
return <span>Loading...</span>;
}
// Compositing user fields by hand
// this will be replaced by dynamic logic
const userFields = {
user0: event.user0,
user1: event.user1,
user2: event.user2,
user3: event.user3,
user4: event.user4,
user5: event.user5,
user6: event.user6,
user7: event.user7,
user8: event.user8,
user9: event.user9,
};
return (
<div className={style.eventEditor}>
<div>HEADER ACTIONS?</div>
<div className={style.content}>
<EventEditorTimes
key={`${event.id}-times`}
eventId={event.id}
timeStart={event.timeStart}
timeEnd={event.timeEnd}
duration={event.duration}
delay={event.delay ?? 0}
isPublic={event.isPublic}
endAction={event.endAction}
timerType={event.timerType}
timeWarning={event.timeWarning}
timeDanger={event.timeDanger}
/>
<EventEditorTitles
key={`${event.id}-titles`}
eventId={event.id}
cue={event.cue}
title={event.title}
presenter={event.presenter}
subtitle={event.subtitle}
note={event.note}
colour={event.colour}
handleSubmit={handleSubmit}
/>
<EventEditorUser key={`${event.id}-user`} userFields={userFields} handleSubmit={handleSubmit} />
</div>
<div className={style.eventActions}>
<CopyTag label='OSC trigger by id'>{`/ontime/load/id "${event.id}"`}</CopyTag>
<CopyTag label='OSC trigger by cue'>{`/ontime/load/cue "${event.cue}"`}</CopyTag>
</div>
</div>
);
}
@@ -0,0 +1,189 @@
import { memo } from 'react';
import { Select, Switch } from '@chakra-ui/react';
import { EndAction, OntimeEvent, TimerType } from 'ontime-types';
import { calculateDuration, millisToString } from 'ontime-utils';
import TimeInput from '../../../../common/components/input/time-input/TimeInput';
import TimeInputWithButton from '../../../../common/components/input/time-input/TimeInputWithButton';
import { useEventAction } from '../../../../common/hooks/useEventAction';
import { millisToDelayString } from '../../../../common/utils/dateConfig';
import { cx } from '../../../../common/utils/styleUtils';
import style from '../EventEditor.module.scss';
interface EventEditorTimesProps {
eventId: string;
timeStart: number;
timeEnd: number;
duration: number;
delay: number;
isPublic: boolean;
endAction: EndAction;
timerType: TimerType;
timeWarning: number;
timeDanger: number;
}
type TimeActions =
| 'timeStart'
| 'timeEnd'
| 'durationOverride'
| 'timerType'
| 'endAction'
| 'isPublic'
| 'timeWarning'
| 'timeDanger';
const EventEditorTimes = (props: EventEditorTimesProps) => {
const { eventId, timeStart, timeEnd, duration, delay, isPublic, endAction, timerType, timeWarning, timeDanger } =
props;
const { updateEvent } = useEventAction();
const handleSubmit = (field: TimeActions, value: number | string | boolean) => {
const newEventData: Partial<OntimeEvent> = { id: eventId };
switch (field) {
case 'durationOverride': {
// duration defines timeEnd
newEventData.duration = value as number;
newEventData.timeEnd = timeStart + (value as number);
break;
}
case 'timeStart': {
newEventData.duration = calculateDuration(value as number, timeEnd);
newEventData.timeStart = value as number;
break;
}
case 'timeEnd': {
newEventData.duration = calculateDuration(timeStart, value as number);
newEventData.timeEnd = value as number;
break;
}
case 'isPublic': {
updateEvent({ id: eventId, isPublic: !(value as boolean) });
break;
}
default: {
if (field === 'timerType' || field === 'endAction' || field === 'timeWarning' || field === 'timeDanger') {
// @ts-expect-error -- not sure how to typecheck here
newEventData[field as keyof OntimeEvent] = value as string;
} else {
return;
}
}
}
updateEvent(newEventData);
};
const delayTime = delay !== 0 ? millisToDelayString(delay) : null;
const startLabel = delayTime ? `New start ${millisToString(timeStart + delay)}` : 'Start time';
const endLabel = delayTime ? `New end ${millisToString(timeEnd + delay)}` : 'End time';
const inputTimeLabels = cx([style.inputLabel, delayTime ? style.delayLabel : null]);
return (
<div className={style.column}>
<div className={style.inline}>
<div>
<label className={inputTimeLabels} htmlFor='timeStart'>
{startLabel}
</label>
<TimeInputWithButton
id='timeStart'
name='timeStart'
submitHandler={handleSubmit}
time={timeStart}
delay={delay}
placeholder='Start'
/>
</div>
<div>
<label className={inputTimeLabels} htmlFor='timeEnd'>
{endLabel}
</label>
<TimeInputWithButton
id='timeEnd'
name='timeEnd'
submitHandler={handleSubmit}
time={timeEnd}
delay={delay}
placeholder='End'
/>
</div>
<div>
<label className={style.inputLabel} htmlFor='durationOverride'>
Duration
</label>
<TimeInputWithButton
id='durationOverride'
name='durationOverride'
submitHandler={handleSubmit}
time={duration}
placeholder='Duration'
/>
</div>
</div>
<div className={style.splitTwo}>
<label className={style.inputLabel} htmlFor='timeWarning'>
Warning Time
<TimeInput
id='timeWarning'
name='timeWarning'
submitHandler={handleSubmit}
time={timeWarning}
placeholder='Duration'
/>
</label>
<label className={style.inputLabel}>
Timer Type
<Select
size='sm'
name='timerType'
value={timerType}
onChange={(event) => handleSubmit('timerType', event.target.value)}
variant='ontime'
>
<option value={TimerType.CountDown}>Count down</option>
<option value={TimerType.CountUp}>Count up</option>
<option value={TimerType.TimeToEnd}>Time to end</option>
<option value={TimerType.Clock}>Clock</option>
</Select>
</label>
<label className={style.inputLabel} htmlFor='timeDanger'>
Danger Time
<TimeInput
id='timeDanger'
name='timeDanger'
submitHandler={handleSubmit}
time={timeDanger}
placeholder='Duration'
/>
</label>
<label className={style.inputLabel}>
End Action
<Select
size='sm'
name='endAction'
value={endAction}
onChange={(event) => handleSubmit('endAction', event.target.value)}
variant='ontime'
>
<option value={EndAction.None}>None</option>
<option value={EndAction.Stop}>Stop</option>
<option value={EndAction.LoadNext}>Load Next</option>
<option value={EndAction.PlayNext}>Play Next</option>
</Select>
</label>
</div>
<div>
<span className={style.inputLabel}>Event visibility</span>
<label className={style.switchLabel}>
<Switch isChecked={isPublic} onChange={() => handleSubmit('isPublic', isPublic)} variant='ontime' />
{isPublic ? 'Public' : 'Private'}
</label>
</div>
</div>
);
};
export default memo(EventEditorTimes);
@@ -0,0 +1,59 @@
import { memo } from 'react';
import { Input } from '@chakra-ui/react';
import { sanitiseCue } from 'ontime-utils';
import SwatchSelect from '../../../../common/components/input/colour-input/SwatchSelect';
import { type EditorUpdateFields } from '../EventEditor';
import EventTextArea from './EventTextArea';
import EventTextInput from './EventTextInput';
import style from '../EventEditor.module.scss';
interface EventEditorLeftProps {
eventId: string;
cue: string;
title: string;
presenter: string;
subtitle: string;
note: string;
colour: string;
handleSubmit: (field: EditorUpdateFields, value: string) => void;
}
const EventEditorTitles = (props: EventEditorLeftProps) => {
const { eventId, cue, title, presenter, subtitle, note, colour, handleSubmit } = props;
const cueSubmitHandler = (_field: string, newValue: string) => {
handleSubmit('cue', sanitiseCue(newValue));
};
return (
<div className={style.column}>
<div className={style.splitTwo}>
<label className={style.inputLabel} htmlFor='eventId'>
Event ID (read only)
<Input
id='eventId'
size='sm'
variant='ontime-filled'
data-testid='input-textfield'
value={eventId}
readOnly
/>
</label>
<EventTextInput field='cue' label='Cue' initialValue={cue} submitHandler={cueSubmitHandler} maxLength={10} />
</div>
<EventTextInput field='title' label='Title' initialValue={title} submitHandler={handleSubmit} />
<EventTextInput field='presenter' label='Presenter' initialValue={presenter} submitHandler={handleSubmit} />
<EventTextInput field='subtitle' label='Subtitle' initialValue={subtitle} submitHandler={handleSubmit} />
<div>
<label className={style.inputLabel}>Colour</label>
<SwatchSelect name='colour' value={colour} handleChange={handleSubmit} />
</div>
<EventTextArea field='note' label='Note' initialValue={note} submitHandler={handleSubmit} />
</div>
);
};
export default memo(EventEditorTitles);
@@ -0,0 +1,83 @@
import { UserFields } from 'ontime-types';
import useUserFields from '../../../../common/hooks-query/useUserFields';
import { EditorUpdateFields } from '../EventEditor';
import EventTextArea from './EventTextArea';
import style from '../EventEditor.module.scss';
interface EventEditorUserProps {
userFields: UserFields;
handleSubmit: (field: EditorUpdateFields, value: string) => void;
}
export default function EventEditorUser(props: EventEditorUserProps) {
const { userFields, handleSubmit } = props;
const { data } = useUserFields();
return (
<div className={style.column}>
<EventTextArea
field='user0'
label={data?.user0 ?? 'user0'}
initialValue={userFields.user0}
submitHandler={handleSubmit}
/>
<EventTextArea
field='user1'
label={data?.user1 ?? 'user1'}
initialValue={userFields.user1}
submitHandler={handleSubmit}
/>
<EventTextArea
field='user2'
label={data?.user2 ?? 'user2'}
initialValue={userFields.user2}
submitHandler={handleSubmit}
/>
<EventTextArea
field='user3'
label={data?.user3 ?? 'user3'}
initialValue={userFields.user3}
submitHandler={handleSubmit}
/>
<EventTextArea
field='user4'
label={data?.user4 ?? 'user4'}
initialValue={userFields.user4}
submitHandler={handleSubmit}
/>
<EventTextArea
field='user5'
label={data?.user5 ?? 'user5'}
initialValue={userFields.user5}
submitHandler={handleSubmit}
/>
<EventTextArea
field='user6'
label={data?.user6 ?? 'user6'}
initialValue={userFields.user6}
submitHandler={handleSubmit}
/>
<EventTextArea
field='user7'
label={data?.user7 ?? 'user7'}
initialValue={userFields.user7}
submitHandler={handleSubmit}
/>
<EventTextArea
field='user8'
label={data?.user8 ?? 'user8'}
initialValue={userFields.user8}
submitHandler={handleSubmit}
/>
<EventTextArea
field='user9'
label={data?.user9 ?? 'user9'}
initialValue={userFields.user9}
submitHandler={handleSubmit}
/>
</div>
);
}
@@ -1,7 +1,7 @@
import { useCallback } from 'react';
import { Textarea } from '@chakra-ui/react';
import useReactiveTextInput from '../../../common/components/input/text-input/useReactiveTextInput';
import { AutoTextArea } from '../../../../common/components/input/auto-text-area/AutoTextArea';
import useReactiveTextInput from '../../../../common/components/input/text-input/useReactiveTextInput';
import { EditorUpdateFields } from '../EventEditor';
import style from '../EventEditor.module.scss';
@@ -13,7 +13,7 @@ interface CountedTextAreaProps {
submitHandler: (field: EditorUpdateFields, value: string) => void;
}
export default function CountedTextArea(props: CountedTextAreaProps) {
export default function EventTextArea(props: CountedTextAreaProps) {
const { field, label, initialValue, submitHandler } = props;
const submitCallback = useCallback((newValue: string) => submitHandler(field, newValue), [field, submitHandler]);
@@ -21,19 +21,16 @@ export default function CountedTextArea(props: CountedTextAreaProps) {
const { value, onChange, onBlur, onKeyDown } = useReactiveTextInput(initialValue, submitCallback);
return (
<div className={`${style.column} ${style.fullHeight}`}>
<div className={style.countedInput}>
<label className={style.inputLabel} htmlFor={field}>
{label}
</label>
<span className={style.charCount}>{`${value.length} characters`}</span>
</div>
<Textarea
<div>
<label className={style.inputLabel} htmlFor={field}>
{label}
</label>
<AutoTextArea
id={field}
rows={1}
size='sm'
resize='none'
variant='ontime-filled'
style={{ height: '100%' }}
data-testid='input-textarea'
value={value}
onChange={onChange}
@@ -1,7 +1,7 @@
import { useCallback } from 'react';
import { Input, InputProps } from '@chakra-ui/react';
import useReactiveTextInput from '../../../common/components/input/text-input/useReactiveTextInput';
import useReactiveTextInput from '../../../../common/components/input/text-input/useReactiveTextInput';
import { EditorUpdateFields } from '../EventEditor';
import style from '../EventEditor.module.scss';
@@ -13,7 +13,7 @@ interface CountedTextInputProps extends InputProps {
submitHandler: (field: EditorUpdateFields, value: string) => void;
}
export default function CountedTextInput(props: CountedTextInputProps) {
export default function EventTextInput(props: CountedTextInputProps) {
const { field, label, initialValue, submitHandler, maxLength } = props;
const submitCallback = useCallback((newValue: string) => submitHandler(field, newValue), [field, submitHandler]);
@@ -23,13 +23,10 @@ export default function CountedTextInput(props: CountedTextInputProps) {
});
return (
<div className={style.column}>
<div className={style.countedInput}>
<label className={style.inputLabel} htmlFor={field}>
{label}
</label>
<span className={style.charCount}>{`${value.length} characters`}</span>
</div>
<div>
<label className={style.inputLabel} htmlFor={field}>
{label}
</label>
<Input
id={field}
size='sm'
@@ -1,5 +1,3 @@
@use '../../../theme/v2Styles' as *;
.quickAdd {
display: grid;
grid-template-columns: 1fr auto;
@@ -1,5 +1,4 @@
@use '../../../theme/viewerDefs' as *;
@use '../../../theme/v2Styles' as *;
.backstage {
margin: 0;
@@ -1,4 +1,3 @@
@use '../../../theme/ontimeColours' as *;
@use '../../../theme/viewerDefs' as *;
@font-face {
+1 -1
View File
@@ -1,4 +1,4 @@
@use './theme/v2Styles' as *;
@use './theme/ontimeStyles' as *;
* {
-webkit-font-smoothing: antialiased;
+1 -1
View File
@@ -1,4 +1,4 @@
@use "v2Styles" as *;
@use "ontimeStyles" as *;
//////////////////////////////////// general app elements
+4 -4
View File
@@ -1,12 +1,12 @@
@use 'v2Styles' as *;
@use 'ontimeStyles' as *;
@use 'viewerCommon' as *;
@use "ontimeColours" as *;
@use 'ontimeColours' as *;
// General
$viewer-transition-time: 0.5s;
// Text
$viewer-font-family: "Open Sans", "Segoe UI", sans-serif; // --font-family-override
$viewer-font-family: 'Open Sans', 'Segoe UI', sans-serif; // --font-family-override
$viewer-opacity-disabled: 0.6;
// General styling
@@ -25,6 +25,6 @@ $element-border-radius: 8px;
// Properties related to timer
$timer-color: rgba(white, 80%); // --timer-color-override
$timer-finished-color: $playback-negative;
$timer-bold-font-family: "Arial Black", sans-serif; // --card-background-color-override
$timer-bold-font-family: 'Arial Black', sans-serif; // --card-background-color-override
$external-color: rgba(white, 70%); // --external-color-override
+10
View File
@@ -44,4 +44,14 @@ export default defineConfig({
'@': fileURLToPath(new URL('./src', import.meta.url)),
},
},
css: {
preprocessorOptions: {
scss: {
additionalData: `
@use './src/theme/ontimeColours' as *;
@use './src/theme/ontimeStyles' as *;
`,
},
},
},
});
+15 -15
View File
@@ -10,12 +10,12 @@ test('delay blocks add time to events', async ({ page }) => {
await page.getByRole('menuitem', { name: 'Add event at start' }).click();
// add data to new event
await page.getByTestId('panel-rundown').getByPlaceholder('Start').click();
await page.getByTestId('panel-rundown').getByPlaceholder('Start').fill('10m');
await page.getByTestId('panel-rundown').getByPlaceholder('Start').press('Enter');
await page.getByTestId('panel-rundown').getByPlaceholder('End').click();
await page.getByTestId('panel-rundown').getByPlaceholder('End').fill('20m');
await page.getByTestId('panel-rundown').getByPlaceholder('End').press('Enter');
await page.getByTestId('rundown').getByPlaceholder('Start').click();
await page.getByTestId('rundown').getByPlaceholder('Start').fill('10m');
await page.getByTestId('rundown').getByPlaceholder('Start').press('Enter');
await page.getByTestId('rundown').getByPlaceholder('End').click();
await page.getByTestId('rundown').getByPlaceholder('End').fill('20m');
await page.getByTestId('rundown').getByPlaceholder('End').press('Enter');
// add delay block
await page.getByRole('button', { name: 'Event...' }).click();
@@ -33,10 +33,10 @@ test('delay blocks add time to events', async ({ page }) => {
// apply delay
await page.getByRole('button', { name: 'Apply' }).click();
await expect(page.getByTestId('panel-rundown').getByTestId('time-input-timeStart')).toHaveValue('00:08:00');
await expect(page.getByTestId('rundown').getByTestId('time-input-timeStart')).toHaveValue('00:08:00');
// add new delay
await page.getByTestId('panel-rundown').getByPlaceholder('Start').click();
await page.getByTestId('rundown').getByPlaceholder('Start').click();
await page.getByRole('button', { name: 'Event...' }).click();
await page.getByRole('menuitem', { name: 'Add delay at start' }).click();
await page.getByTestId('delay-input').click();
@@ -46,7 +46,7 @@ test('delay blocks add time to events', async ({ page }) => {
// cancel delay
await page.getByRole('button', { name: 'Cancel' }).click();
await expect(page.getByTestId('panel-rundown').getByTestId('time-input-timeStart')).toHaveValue('00:08:00');
await expect(page.getByTestId('rundown').getByTestId('time-input-timeStart')).toHaveValue('00:08:00');
await expect(page.getByText('+10 minNew start: 00:18:00')).toHaveCount(0);
});
@@ -58,12 +58,12 @@ test('delays are show correctly', async ({ page }) => {
await page.getByRole('menuitem', { name: 'Delete all events' }).click();
await page.getByRole('button', { name: 'Create Event' }).click();
await page.getByTestId('time-input-timeStart').click();
await page.getByTestId('panel-rundown').getByTestId('time-input-timeStart').click();
await page.getByTestId('panel-rundown').getByTestId('time-input-timeStart').fill('10');
await page.getByTestId('panel-rundown').getByTestId('time-input-timeStart').press('Enter');
await page.getByTestId('panel-rundown').getByTestId('time-input-timeEnd').click();
await page.getByTestId('panel-rundown').getByTestId('time-input-timeEnd').fill('20');
await page.getByTestId('panel-rundown').getByTestId('time-input-timeEnd').press('Enter');
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').press('Enter');
await page.getByTestId('rundown').getByTestId('time-input-timeEnd').click();
await page.getByTestId('rundown').getByTestId('time-input-timeEnd').fill('20');
await page.getByTestId('rundown').getByTestId('time-input-timeEnd').press('Enter');
await page.getByText('Event title').click();
await page.getByPlaceholder('Event title').fill('test');
await page.getByPlaceholder('Event title').press('Enter');