mirror of
https://github.com/cpvalente/ontime.git
synced 2026-08-29 10:59:07 +00:00
feat: operator view
This commit is contained in:
@@ -9,6 +9,7 @@ import { getOperatorOptions } from '../../common/components/view-params-editor/c
|
|||||||
import ViewParamsEditor from '../../common/components/view-params-editor/ViewParamsEditor';
|
import ViewParamsEditor from '../../common/components/view-params-editor/ViewParamsEditor';
|
||||||
import useFollowComponent from '../../common/hooks/useFollowComponent';
|
import useFollowComponent from '../../common/hooks/useFollowComponent';
|
||||||
import { useOperator } from '../../common/hooks/useSocket';
|
import { useOperator } from '../../common/hooks/useSocket';
|
||||||
|
import useEventData from '../../common/hooks-query/useEventData';
|
||||||
import useRundown from '../../common/hooks-query/useRundown';
|
import useRundown from '../../common/hooks-query/useRundown';
|
||||||
import useUserFields from '../../common/hooks-query/useUserFields';
|
import useUserFields from '../../common/hooks-query/useUserFields';
|
||||||
import { isStringBoolean } from '../../common/utils/viewUtils';
|
import { isStringBoolean } from '../../common/utils/viewUtils';
|
||||||
@@ -25,6 +26,8 @@ const selectedOffset = 50;
|
|||||||
export default function Operator() {
|
export default function Operator() {
|
||||||
const { data, status } = useRundown();
|
const { data, status } = useRundown();
|
||||||
const { data: userFields, status: userFieldsStatus } = useUserFields();
|
const { data: userFields, status: userFieldsStatus } = useUserFields();
|
||||||
|
const { data: projectData, status: projectDataStatus } = useEventData();
|
||||||
|
|
||||||
const featureData = useOperator();
|
const featureData = useOperator();
|
||||||
const [searchParams] = useSearchParams();
|
const [searchParams] = useSearchParams();
|
||||||
|
|
||||||
@@ -69,7 +72,14 @@ export default function Operator() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!data || status === 'loading' || !userFields || userFieldsStatus === 'loading') {
|
if (
|
||||||
|
!data ||
|
||||||
|
status === 'loading' ||
|
||||||
|
!userFields ||
|
||||||
|
userFieldsStatus === 'loading' ||
|
||||||
|
!projectData ||
|
||||||
|
projectDataStatus === 'loading'
|
||||||
|
) {
|
||||||
return <Empty text='Loading...' />;
|
return <Empty text='Loading...' />;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -77,12 +87,14 @@ export default function Operator() {
|
|||||||
const subscribe = searchParams.get('subscribe') as keyof UserFields | null;
|
const subscribe = searchParams.get('subscribe') as keyof UserFields | null;
|
||||||
const subscribedAlias = subscribe ? userFields[subscribe] : '';
|
const subscribedAlias = subscribe ? userFields[subscribe] : '';
|
||||||
const showSeconds = isStringBoolean(searchParams.get('showseconds'));
|
const showSeconds = isStringBoolean(searchParams.get('showseconds'));
|
||||||
const lastEvent = getLastEvent(data);
|
|
||||||
|
|
||||||
const operatorOptions = getOperatorOptions(userFields);
|
const operatorOptions = getOperatorOptions(userFields);
|
||||||
let isPast = Boolean(featureData.selectedEventId);
|
let isPast = Boolean(featureData.selectedEventId);
|
||||||
const hidePast = isStringBoolean(searchParams.get('hidepast'));
|
const hidePast = isStringBoolean(searchParams.get('hidepast'));
|
||||||
|
|
||||||
|
const firstEvent = getLastEvent(data);
|
||||||
|
const lastEvent = getLastEvent(data);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={style.operatorContainer}>
|
<div className={style.operatorContainer}>
|
||||||
<NavigationMenu />
|
<NavigationMenu />
|
||||||
@@ -124,7 +136,15 @@ export default function Operator() {
|
|||||||
<div className={style.spacer} />
|
<div className={style.spacer} />
|
||||||
</div>
|
</div>
|
||||||
<FollowButton isVisible={lockAutoScroll} onClickHandler={handleOffset} />
|
<FollowButton isVisible={lockAutoScroll} onClickHandler={handleOffset} />
|
||||||
<StatusBar playback={featureData.playback} lastEvent={lastEvent} selectedEventId={featureData.selectedEventId} />
|
<StatusBar
|
||||||
|
projectTitle={projectData.title}
|
||||||
|
playback={featureData.playback}
|
||||||
|
selectedEventId={featureData.selectedEventId}
|
||||||
|
firstStart={firstEvent?.timeStart}
|
||||||
|
firstId={firstEvent?.id}
|
||||||
|
lastEnd={lastEvent?.timeEnd}
|
||||||
|
lastId={lastEvent?.id}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,13 @@
|
|||||||
@use '../../../../src/theme/ontimeColours' as *;
|
@use '../../../../src/theme/ontimeColours' as *;
|
||||||
@use '../../../../src/theme/v2Styles' as *;
|
@use '../../../../src/theme/v2Styles' as *;
|
||||||
|
|
||||||
|
@mixin column {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
.statusBar {
|
.statusBar {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
@@ -9,28 +16,55 @@
|
|||||||
z-index: 2;
|
z-index: 2;
|
||||||
|
|
||||||
padding: 0.5rem 1rem;
|
padding: 0.5rem 1rem;
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
border-top: 1px solid $white-10;
|
border-top: 1px solid $white-10;
|
||||||
box-shadow: $large-bottom-drawer-shadow;
|
box-shadow: $large-bottom-drawer-shadow;
|
||||||
|
|
||||||
|
display: grid;
|
||||||
|
grid-template-areas:
|
||||||
|
"playback timer1B timer2B timer3B";
|
||||||
|
grid-template-columns: 1fr auto auto auto;
|
||||||
|
column-gap: 1.5rem;
|
||||||
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.playbackIcon {
|
.playbackIcon {
|
||||||
|
grid-area: playback;
|
||||||
font-size: 2rem;
|
font-size: 2rem;
|
||||||
color: $gray-700;
|
color: $gray-700;
|
||||||
}
|
}
|
||||||
|
|
||||||
.column {
|
.timeNow {
|
||||||
display: flex;
|
grid-area: timer1B;
|
||||||
flex-direction: column;
|
@include column;
|
||||||
align-items: center;
|
|
||||||
text-align: center;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.clock {
|
.elapsedTime {
|
||||||
display: flex;
|
grid-area: timer2B;
|
||||||
gap: 1.5rem;
|
@include column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.runningTime {
|
||||||
|
grid-area: timer3B;
|
||||||
|
@include column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
grid-area: title;
|
||||||
|
font-size: 1.5rem;
|
||||||
|
padding-left: 0.5rem;
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.startTime {
|
||||||
|
grid-area: timer2A;
|
||||||
|
@include column;
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.endTime {
|
||||||
|
grid-area: timer3A;
|
||||||
|
@include column;
|
||||||
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.label {
|
.label {
|
||||||
@@ -44,8 +78,23 @@
|
|||||||
|
|
||||||
// tablet
|
// tablet
|
||||||
@media (min-width: $min-tablet) {
|
@media (min-width: $min-tablet) {
|
||||||
.clock {
|
.statusBar {
|
||||||
|
grid-template-areas:
|
||||||
|
"playback timer1B timer2B timer3B"
|
||||||
|
"title timer1A timer2A timer3A";
|
||||||
|
row-gap: 0.25rem;
|
||||||
|
column-gap: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.startTime {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.endTime {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 1.5rem;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,33 +1,52 @@
|
|||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
import { OntimeEvent, Playback } from 'ontime-types';
|
import { Playback } from 'ontime-types';
|
||||||
import { millisToString } from 'ontime-utils';
|
import { millisToString } from 'ontime-utils';
|
||||||
|
|
||||||
import PlaybackIcon from '../../../common/components/playback-icon/PlaybackIcon';
|
import PlaybackIcon from '../../../common/components/playback-icon/PlaybackIcon';
|
||||||
import useMediaQuery from '../../../common/context/useMediaQuery';
|
|
||||||
import { useTimer } from '../../../common/hooks/useSocket';
|
import { useTimer } from '../../../common/hooks/useSocket';
|
||||||
import { formatTime } from '../../../common/utils/time';
|
import { formatTime } from '../../../common/utils/time';
|
||||||
|
|
||||||
import styles from './StatusBar.module.scss';
|
import styles from './StatusBar.module.scss';
|
||||||
|
|
||||||
export default function StatusBar({
|
interface StatusBarProps {
|
||||||
playback,
|
projectTitle: string;
|
||||||
lastEvent,
|
|
||||||
selectedEventId,
|
|
||||||
}: {
|
|
||||||
playback: Playback;
|
playback: Playback;
|
||||||
lastEvent: OntimeEvent | null;
|
|
||||||
selectedEventId: string | null;
|
selectedEventId: string | null;
|
||||||
}) {
|
firstStart?: number;
|
||||||
const timer = useTimer();
|
firstId?: string;
|
||||||
const isMobile = useMediaQuery('(max-width: 450px)');
|
lastEnd?: number;
|
||||||
|
lastId?: string;
|
||||||
|
}
|
||||||
|
|
||||||
const getTimeEnd = () => {
|
export default function StatusBar(props: StatusBarProps) {
|
||||||
if (lastEvent === null) {
|
const { projectTitle, playback, selectedEventId, firstStart, firstId, lastEnd, lastId } = props;
|
||||||
|
|
||||||
|
const timer = useTimer();
|
||||||
|
|
||||||
|
const getTimeStart = () => {
|
||||||
|
if (firstStart === undefined) {
|
||||||
return '...';
|
return '...';
|
||||||
}
|
}
|
||||||
|
|
||||||
const timeEnd = lastEvent.id === selectedEventId ? timer.expectedFinish : lastEvent.timeEnd;
|
if (selectedEventId) {
|
||||||
return millisToString(timeEnd);
|
if (firstId === selectedEventId) {
|
||||||
|
return millisToString(timer.expectedFinish);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return millisToString(firstStart);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getTimeEnd = () => {
|
||||||
|
if (lastEnd === undefined) {
|
||||||
|
return '...';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selectedEventId) {
|
||||||
|
if (lastId === selectedEventId) {
|
||||||
|
return millisToString(timer.expectedFinish);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return millisToString(lastEnd);
|
||||||
};
|
};
|
||||||
|
|
||||||
// use user defined format
|
// use user defined format
|
||||||
@@ -44,26 +63,28 @@ export default function StatusBar({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.statusBar}>
|
<div className={styles.statusBar}>
|
||||||
|
<span className={styles.title}>{projectTitle}</span>
|
||||||
|
<div className={styles.startTime}>
|
||||||
|
<span className={styles.label}>Scheduled start</span>
|
||||||
|
<span className={styles.timer}>{getTimeStart()}</span>
|
||||||
|
</div>
|
||||||
|
<div className={styles.endTime}>
|
||||||
|
<span className={styles.label}>Scheduled end</span>
|
||||||
|
<span className={styles.timer}>{getTimeEnd()}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
{PlaybackIconComponent}
|
{PlaybackIconComponent}
|
||||||
<div className={styles.clock}>
|
<div className={styles.timeNow}>
|
||||||
<div className={styles.column}>
|
<span className={styles.label}>Time now</span>
|
||||||
<span className={styles.label}>Time now</span>
|
<span className={styles.timer}>{timeNow}</span>
|
||||||
<span className={styles.timer}>{timeNow}</span>
|
</div>
|
||||||
</div>
|
<div className={styles.elapsedTime}>
|
||||||
{!isMobile && (
|
<span className={styles.label}>Elapsed time</span>
|
||||||
<div className={styles.column}>
|
<span className={styles.timer}>{elapsedTime}</span>
|
||||||
<span className={styles.label}>Last end</span>
|
</div>
|
||||||
<span className={styles.timer}>{getTimeEnd()}</span>
|
<div className={styles.runningTime}>
|
||||||
</div>
|
<span className={styles.label}>Running timer</span>
|
||||||
)}
|
<span className={styles.timer}>{runningTime}</span>
|
||||||
<div className={styles.column}>
|
|
||||||
<span className={styles.label}>Elapsed time</span>
|
|
||||||
<span className={styles.timer}>{elapsedTime}</span>
|
|
||||||
</div>
|
|
||||||
<div className={styles.column}>
|
|
||||||
<span className={styles.label}>Running timer</span>
|
|
||||||
<span className={styles.timer}>{runningTime}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user