V2 event block (#261)

* feat: implement progress bar on running event

* refactor: use event action hook

* refactor: align backend data
This commit is contained in:
Carlos Valente
2022-11-22 22:53:52 +01:00
committed by GitHub
parent b54c7dd454
commit 3626b5b357
8 changed files with 181 additions and 129 deletions
+3 -2
View File
@@ -131,8 +131,9 @@ export const setEventPlayback = {
const emptyTimer = { const emptyTimer = {
clock: 0, clock: 0,
running: 0, current: 0,
isNegative: false, secondaryTimer: null,
duration: null,
startedAt: null, startedAt: null,
expectedFinish: null, expectedFinish: null,
}; };
@@ -1,18 +1,74 @@
@use '../../../theme/main' as *; @use '../../../theme/main' as *;
@use '../blockMixins' as *; @use '../blockMixins' as *;
.statusElements {
grid-area: estatus;
display: grid;
grid-template-areas:
"notes status"
"progb progb";
gap: 2px;
grid-template-rows: auto 4px;
.eventNote {
grid-area: notes;
display: block;
font-size: 15px;
color: $notes-color;
line-height: 15px;
}
.eventStatus {
grid-area: status;
display: flex;
justify-content: flex-end;
gap: $element-spacing;
.statusIcon {
width: 24px;
height: 24px;
border-radius: 12px;
padding: 4px;
color: $text-gray-disabled;
}
.statusIcon.statusNext {
border: 1px solid transparent;
&.enabled {
color: $text-white;
background-color: $ontime-accent;
}
}
.statusIcon.statusDelay {
&.enabled {
color: $text-white;
background-color: $ontime-delay;
}
}
.statusIcon.statusPublic {
&.enabled {
color: $text-white;
background-color: $ontime-roll;
}
}
}
}
.eventBlock { .eventBlock {
// layout // layout
display: grid; display: grid;
grid-template-areas: grid-template-areas:
"binder pb-actions times actions" "binder pb-actions times actions"
"binder pb-actions title title" "binder pb-actions title title"
"binder pb-actions notes status" "binder pb-actions estatus estatus";
"binder progb progb progb";
grid-template-columns: $binder-width auto 1fr auto; grid-template-columns: $binder-width auto 1fr auto;
grid-template-rows: 36px 36px 36px $element-spacing; grid-template-rows: 36px 36px 36px;
align-items: center; align-items: center;
margin:4px 2px; margin: 4px 2px;
padding-right: $clearance; padding-right: $clearance;
// style - general // style - general
@@ -23,7 +79,7 @@
background-color: $bg-container-l2; background-color: $bg-container-l2;
border: $border-l3; border: $border-l3;
// variant // variant
&.selected { &.selected {
background-color: $bg-container-l3; background-color: $bg-container-l3;
} }
@@ -112,14 +168,6 @@
} }
} }
.eventNote {
grid-area: notes;
display: block;
font-size: 14px;
color: $notes-color;
line-height: 15px;
}
.eventActions { .eventActions {
grid-area: actions; grid-area: actions;
@@ -128,44 +176,6 @@
justify-content: flex-end; justify-content: flex-end;
} }
.eventStatus {
grid-area: status;
display: flex;
justify-content: flex-end;
gap: $element-spacing;
.statusIcon {
width: 24px;
height: 24px;
border-radius: 12px;
padding: 4px;
color: $text-gray-disabled;
}
.statusIcon.statusNext {
border: 1px solid transparent;
&.enabled {
color: $text-white;
background-color: $ontime-accent;
}
}
.statusIcon.statusDelay {
&.enabled {
color: $text-white;
background-color: $ontime-delay;
}
}
.statusIcon.statusPublic {
&.enabled {
color: $text-white;
background-color: $ontime-roll;
}
}
}
.eventOptions { .eventOptions {
margin: $element-spacing 16px $element-spacing 0; margin: $element-spacing 16px $element-spacing 0;
@@ -176,36 +186,12 @@
grid-area: progb; grid-area: progb;
border-radius: 1px; border-radius: 1px;
background-color: $bg-container-l1; background-color: $bg-container-l1;
margin-bottom: 4px; opacity: 1;
margin-left: 4px;
// layout // layout
height: 100%; height: 100%;
.progressBar {
// layout
height: 100%;
width: 0;
border-radius: 1px 0 0 1px;
// animations
transition: 1s linear;
transition-property: width;
&.start {
background-color: $ontime-accent;
}
&.pause {
background-color: $ontime-paused;
}
&.roll {
background-color: $ontime-roll;
}
&.overtime {
background-color: red;
}
}
} }
.progressBg.hidden {
opacity: 0;
}
@@ -17,12 +17,14 @@ import TooltipActionBtn from 'common/components/buttons/TooltipActionBtn';
import { getAccessibleColour } from 'common/utils/styleUtils'; import { getAccessibleColour } from 'common/utils/styleUtils';
import { useAtom } from 'jotai'; import { useAtom } from 'jotai';
import { useEventAction } from '../../../common/hooks/useEventAction';
import { setEventPlayback } from '../../../common/hooks/useSocket'; import { setEventPlayback } from '../../../common/hooks/useSocket';
import { Playstate } from '../../../common/models/OntimeTypes'; import { Playstate } from '../../../common/models/OntimeTypes';
import { tooltipDelayMid } from '../../../ontimeConfig'; import { tooltipDelayMid } from '../../../ontimeConfig';
import { EventItemActions } from '../RundownEntry'; import { EventItemActions } from '../RundownEntry';
import EventBlockActionMenu from './composite/EventBlockActionMenu'; import EventBlockActionMenu from './composite/EventBlockActionMenu';
import EventBlockProgressBar from './composite/EventBlockProgressBar';
import EventBlockTimers from './composite/EventBlockTimers'; import EventBlockTimers from './composite/EventBlockTimers';
import style from './EventBlock.module.scss'; import style from './EventBlock.module.scss';
@@ -80,6 +82,7 @@ export default function EventBlock(props: EventBlockProps) {
} = props; } = props;
const [openId, setOpenId] = useAtom(editorEventId); const [openId, setOpenId] = useAtom(editorEventId);
const { updateEvent } = useEventAction();
const [blockTitle, setBlockTitle] = useState<string>(title || ''); const [blockTitle, setBlockTitle] = useState<string>(title || '');
const binderColours = colour && getAccessibleColour(colour); const binderColours = colour && getAccessibleColour(colour);
@@ -99,14 +102,11 @@ export default function EventBlock(props: EventBlockProps) {
const cleanVal = text.trim(); const cleanVal = text.trim();
setBlockTitle(cleanVal); setBlockTitle(cleanVal);
// Todo: no need for action handler updateEvent({ id: eventId, title: cleanVal });
actionHandler('update', { field: 'title', value: cleanVal });
}, },
[actionHandler, title], [updateEvent, title],
); );
// Todo: data should come from socket
const progress = `${Math.random() * 100}%`;
const eventIsPlaying = selected && playback === 'start'; const eventIsPlaying = selected && playback === 'start';
const playBtnStyles = { _hover: {} }; const playBtnStyles = { _hover: {} };
if (!skip && eventIsPlaying) { if (!skip && eventIsPlaying) {
@@ -136,12 +136,6 @@ export default function EventBlock(props: EventBlockProps) {
</span> </span>
{eventIndex} {eventIndex}
</div> </div>
<div className={selected ? style.progressBg : ''}>
<div
className={`${style.progressBar} ${playback ? style[playback] : ''}`}
style={{ width: progress }}
/>
</div>
<div className={style.playbackActions}> <div className={style.playbackActions}>
<TooltipActionBtn <TooltipActionBtn
aria-label='Skip event' aria-label='Skip event'
@@ -195,7 +189,42 @@ export default function EventBlock(props: EventBlockProps) {
<EditablePreview className={style.eventTitle__preview} /> <EditablePreview className={style.eventTitle__preview} />
<EditableInput /> <EditableInput />
</Editable> </Editable>
<span className={style.eventNote}>{note}</span> <div className={style.statusElements}>
<span className={style.eventNote}>{note}</span>
<div className={selected ? style.progressBg : `${style.progressBg} ${style.hidden}`}>
<EventBlockProgressBar playback={playback} />
</div>
<div className={style.eventStatus}>
<Tooltip label='Next event' isDisabled={!next} {...tooltipProps}>
<span
className={`${style.statusIcon} ${style.statusNext} ${next ? style.enabled : ''}`}
>
<IoReturnDownForward />
</span>
</Tooltip>
<Tooltip label='Event has delay' isDisabled={!hasDelay} {...tooltipProps}>
<span
className={`${style.statusIcon} ${style.statusDelay} ${
hasDelay ? style.enabled : ''
}`}
>
<IoTimerOutline />
</span>
</Tooltip>
<Tooltip
label={`${isPublic ? 'Event is public' : 'Event is private'}`}
{...tooltipProps}
>
<span
className={`${style.statusIcon} ${style.statusPublic} ${
isPublic ? style.enabled : ''
}`}
>
<FiUsers />
</span>
</Tooltip>
</div>
</div>
<div className={style.eventActions}> <div className={style.eventActions}>
<TooltipActionBtn <TooltipActionBtn
{...blockBtnStyle} {...blockBtnStyle}
@@ -217,36 +246,6 @@ export default function EventBlock(props: EventBlockProps) {
actionHandler={actionHandler} actionHandler={actionHandler}
/> />
</div> </div>
<div className={style.eventStatus}>
<Tooltip label='Next event' isDisabled={!next} {...tooltipProps}>
<span
className={`${style.statusIcon} ${style.statusNext} ${next ? style.enabled : ''}`}
>
<IoReturnDownForward />
</span>
</Tooltip>
<Tooltip label='Event has delay' isDisabled={!hasDelay} {...tooltipProps}>
<span
className={`${style.statusIcon} ${style.statusDelay} ${
hasDelay ? style.enabled : ''
}`}
>
<IoTimerOutline />
</span>
</Tooltip>
<Tooltip
label={`${isPublic ? 'Event is public' : 'Event is private'}`}
{...tooltipProps}
>
<span
className={`${style.statusIcon} ${style.statusPublic} ${
isPublic ? style.enabled : ''
}`}
>
<FiUsers />
</span>
</Tooltip>
</div>
</div> </div>
)} )}
</Draggable> </Draggable>
@@ -0,0 +1,29 @@
@use '../../../../theme/main' as *;
.progressBar {
// layout
height: 100%;
width: 0;
border-radius: 1px 0 0 1px;
// animations
transition: 1s linear;
transition-property: width;
&.start {
background-color: $ontime-accent;
}
&.pause {
background-color: $ontime-paused;
}
&.roll {
background-color: $ontime-roll;
}
&.overtime {
background-color: $ontime-pink-variant;
}
}
@@ -0,0 +1,34 @@
import { useTimer } from '../../../../common/hooks/useSocket';
import { Playstate } from '../../../../common/models/OntimeTypes';
import { clamp } from '../../../../common/utils/math';
import style from './EventBlockProgressBar.module.scss';
interface EventBlockProgressBarProps {
playback?: Playstate;
}
export default function EventBlockProgressBar(props: EventBlockProgressBarProps) {
const { playback } = props;
const { data: timer } = useTimer();
const now = Math.floor(Math.max((timer?.current ?? 1) / 1000, 0));
const complete = (timer?.duration ?? 1) / 1000;
const elapsed = clamp(100 - (now * 100) / complete, 0, 100);
const progress = `${elapsed}%`;
if (timer?.current != null && timer?.current < 0) {
return (
<div
className={`${style.progressBar} ${style.overtime}`}
style={{ width: '100%' }}
/>
);
}
return (
<div
className={`${style.progressBar} ${playback ? style[playback] : ''}`}
style={{ width: progress }}
/>
);
}
+4 -1
View File
@@ -1,3 +1,6 @@
$transition-time-action: 0.1s;
$transition-time-feedback: 0.3s;
//////////////////////////////////// general app colours //////////////////////////////////// general app colours
$bg-black-gradient: #202020; $bg-black-gradient: #202020;
@@ -42,7 +45,7 @@ $ontime-red: #E4281E;
// outdent in level2 - $bg-gray-950 // outdent in level2 - $bg-gray-950
//////////////////////////////////// editor //////////////////////////////////// editor
$notes-color: #9fd8ff; $notes-color: #f6f6f6;
$text-white: #fffffa; $text-white: #fffffa;
$text-gray-disabled: #505050; $text-gray-disabled: #505050;
+1 -1
View File
@@ -119,8 +119,8 @@ export class EventTimer extends Timer {
current: this.current, current: this.current,
secondaryTimer: this.secondaryTimer, secondaryTimer: this.secondaryTimer,
duration: this.duration, duration: this.duration,
expectedFinish: this._getExpectedFinish(),
startedAt: this._startedAt, startedAt: this._startedAt,
expectedFinish: this._getExpectedFinish(),
}; };
this.socket.send('ontime-timer', featureData); this.socket.send('ontime-timer', featureData);
} }
+3 -3
View File
@@ -1,5 +1,5 @@
import { Server } from 'node-osc'; import { Server } from 'node-osc';
import { PlaybackService } from '../services/playbackService'; import { PlaybackService } from '../services/playbackService.js';
import { messageManager } from '../classes/message-manager/MessageManager.js'; import { messageManager } from '../classes/message-manager/MessageManager.js';
import { socketProvider } from '../classes/socket/SocketController.js'; import { socketProvider } from '../classes/socket/SocketController.js';
import { ADDRESS_MESSAGE_CONTROL } from '../classes/socket/socketConfig.js'; import { ADDRESS_MESSAGE_CONTROL } from '../classes/socket/socketConfig.js';
@@ -22,7 +22,7 @@ export const initiateOSC = (config) => {
oscServer.on('error', console.error); oscServer.on('error', console.error);
oscServer.on('message', function (msg) { oscServer.on('message', function(msg) {
// message should look like /ontime/{path} {args} where // message should look like /ontime/{path} {args} where
// ontime: fixed message for app // ontime: fixed message for app
// path: command to be called // path: command to be called
@@ -125,7 +125,7 @@ export const initiateOSC = (config) => {
if (isNaN(eventIndex) || eventIndex <= 0) { if (isNaN(eventIndex) || eventIndex <= 0) {
socketProvider.error( socketProvider.error(
'RX', 'RX',
`OSC IN: event index not recognised or out of range ${eventIndex}`, `OSC IN: event index not recognised or out of range ${eventIndex}`
); );
} else { } else {
PlaybackService.loadByIndex(eventIndex - 1); PlaybackService.loadByIndex(eventIndex - 1);