mirror of
https://github.com/cpvalente/ontime.git
synced 2026-08-14 11:53:49 +00:00
V3 (#657)
* refactor: cleanup routes * style: smaller base font * chore: upgrade dependencies * chore: lock node version to electron * refactor: pass HTTP to integration controller (#652) * refactor: deprecate onair control * refactor: remove playback router * Several project files user folder (#617) * chore: automated screenshots (#667) * feat: app settings (#658) * refactor: remove deprecated event data (#674) * Studio clock (#663) --------- Co-authored-by: Carlos Valente <carlosvalente@pm.me> * Feat: reorder events with alt+ctrl + arrow up/down (#645) * Warning and danger per event (#677) --------- Co-authored-by: Fabian Posenau <fabian@fphome.de> * refactor: stabilise actionHandler (#683) Co-authored-by: Fabian Posenau <fabian@fphome.de> * improvement: hide seconds (#675) * wip: overview (#688) * fix: focus cursor (#695) * refactor: update lower third (#665) * Refactor/time formatting (#696) --------- Co-authored-by: Carlos Valente <34649812+cpvalente@users.noreply.github.com> Co-authored-by: Carlos Valente <carlosvalente@pm.me> * feat: multiple selection (#703) --------- Co-authored-by: asharonbaltazar <asharonbaltazar@outlook.com> Co-authored-by: Alex <ac@omnivox.dk> * fix: test - go to `Edit mode` befor tying to click `Event options` button (#708) * refactor: runtime service (#715) * fix: issue with loosing cursor position on message (#719) * remove info panel (#721) * Event editor continue (#722) * update API - part (#709) --------- Co-authored-by: Carlos Valente <34649812+cpvalente@users.noreply.github.com> Co-authored-by: Carlos Valente <carlosvalente@pm.me> * refactor: update timers (#729) * feat: many timers (#706) --------- Co-authored-by: arc-alex <ac@omnivox.dk> * refactor: excel cleanup (#734) * refactor: allow import of blocks and skip import (#735) * Project manager (#697) * refactor: UI for linking events (#763) * upgraded pipeline actions (#777) * Over under (#771) * custom fields (#744) --------- Co-authored-by: Carlos Valente <carlosvalente@pm.me> * Sheets settings (#774) --------- Co-authored-by: arc-alex <ac@omnivox.dk> * style: tweaks to lower thirds (#785) * refactor: delays account for gaps (#784) * refactor: partial state updates (#780) * feat: generate crash report (#787) * Sheet use limited input device auth flow (#782) --------- Co-authored-by: cv <34649812+cpvalente@users.noreply.github.com> Co-authored-by: Carlos Valente <carlosvalente@pm.me> * Custom fields views (#789) * refactor: deprecate presenter and subtitle (#795) * refactor: organise API around resources (#798) --------- Co-authored-by: Bianca Procopio <biancahprocopio@gmail.com> * Time to end (#804) * Skip fixes (#805) * fix: onair derives from playback * Param nav (#822) --------- Co-authored-by: Alex Christoffer Rasmussen <ac@omnivox.dk> * refactor: download files from interface (#831) * Quick options (#814) * End pause (#832) * chore: bump node version in docker (#834) * refactor: follow in run mode (#840) * fix: uncaught error in http integration (#837) * Apply project (#843) Co-authored-by: Matteo Gheza <matteo.gheza07@gmail.com> Co-authored-by: Ary <arylmoraesn@gmail.com> Co-authored-by: Alex Christoffer Rasmussen <ac@omnivox.dk> Co-authored-by: Fabian Posenau <19673098+kellhogs@users.noreply.github.com> Co-authored-by: Fabian Posenau <fabian@fphome.de> Co-authored-by: Alex Rohleder <alexrohleder96@gmail.com> Co-authored-by: asharonbaltazar <asharonbaltazar@outlook.com> Co-authored-by: Bianca Procopio <biancahprocopio@gmail.com> Co-authored-by: Fabian Posenau <fabianpos99+github@gmail.com>
This commit is contained in:
@@ -1,6 +1,3 @@
|
||||
@use '../../../src/theme/v2Styles' as *;
|
||||
@use '../../../src/theme/ontimeColours' as *;
|
||||
|
||||
.operatorContainer {
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
@@ -43,3 +40,8 @@
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.schedule {
|
||||
display: flex;
|
||||
gap: 0.25em;
|
||||
}
|
||||
|
||||
@@ -1,20 +1,21 @@
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { useSearchParams } from 'react-router-dom';
|
||||
import { isOntimeEvent, OntimeEvent, SupportedEvent, UserFields } from 'ontime-types';
|
||||
import { getFirstEvent, getLastEvent } from 'ontime-utils';
|
||||
import { CustomField, CustomFields, isOntimeEvent, OntimeEvent, SupportedEvent } from 'ontime-types';
|
||||
import { getFirstEventNormal, getLastEventNormal } from 'ontime-utils';
|
||||
|
||||
import NavigationMenu from '../../common/components/navigation-menu/NavigationMenu';
|
||||
import Empty from '../../common/components/state/Empty';
|
||||
import { getOperatorOptions } from '../../common/components/view-params-editor/constants';
|
||||
import ViewParamsEditor from '../../common/components/view-params-editor/ViewParamsEditor';
|
||||
import useFollowComponent from '../../common/hooks/useFollowComponent';
|
||||
import { useOperator } from '../../common/hooks/useSocket';
|
||||
import { useWindowTitle } from '../../common/hooks/useWindowTitle';
|
||||
import useCustomFields from '../../common/hooks-query/useCustomFields';
|
||||
import useProjectData from '../../common/hooks-query/useProjectData';
|
||||
import useRundown from '../../common/hooks-query/useRundown';
|
||||
import useSettings from '../../common/hooks-query/useSettings';
|
||||
import useUserFields from '../../common/hooks-query/useUserFields';
|
||||
import { debounce } from '../../common/utils/debounce';
|
||||
import { isStringBoolean } from '../../common/utils/viewUtils';
|
||||
import { getDefaultFormat } from '../../common/utils/time';
|
||||
import { getPropertyValue, isStringBoolean } from '../viewers/common/viewUtils';
|
||||
|
||||
import EditModal from './edit-modal/EditModal';
|
||||
import FollowButton from './follow-button/FollowButton';
|
||||
@@ -26,15 +27,15 @@ import style from './Operator.module.scss';
|
||||
|
||||
const selectedOffset = 50;
|
||||
|
||||
type TitleFields = Pick<OntimeEvent, 'title' | 'subtitle' | 'presenter'>;
|
||||
type TitleFields = Pick<OntimeEvent, 'title'>;
|
||||
export type EditEvent = Pick<OntimeEvent, 'id' | 'cue'> & { fieldLabel?: string; fieldValue: string };
|
||||
export type PartialEdit = EditEvent & {
|
||||
field: keyof UserFields;
|
||||
field: keyof CustomFields;
|
||||
};
|
||||
|
||||
export default function Operator() {
|
||||
const { data, status } = useRundown();
|
||||
const { data: userFields, status: userFieldsStatus } = useUserFields();
|
||||
const { data: customFields, status: customFieldStatus } = useCustomFields();
|
||||
const { data: projectData, status: projectDataStatus } = useProjectData();
|
||||
|
||||
const timeoutId = useRef<NodeJS.Timeout | null>(null);
|
||||
@@ -51,15 +52,12 @@ export default function Operator() {
|
||||
const scrollRef = useRef<HTMLDivElement | null>(null);
|
||||
const scrollToComponent = useFollowComponent({
|
||||
followRef: selectedRef,
|
||||
scrollRef: scrollRef,
|
||||
scrollRef,
|
||||
doFollow: !lockAutoScroll,
|
||||
topOffset: selectedOffset,
|
||||
});
|
||||
|
||||
// Set window title
|
||||
useEffect(() => {
|
||||
document.title = 'ontime - Operator';
|
||||
}, []);
|
||||
useWindowTitle('Operator');
|
||||
|
||||
// reset scroll if nothing is selected
|
||||
useEffect(() => {
|
||||
@@ -106,7 +104,7 @@ export default function Operator() {
|
||||
|
||||
const handleEdit = useCallback(
|
||||
(event: EditEvent) => {
|
||||
const field = searchParams.get('subscribe') as keyof UserFields | null;
|
||||
const field = searchParams.get('subscribe') as keyof CustomField | null;
|
||||
|
||||
if (field) {
|
||||
setEditEvent({ ...event, field });
|
||||
@@ -115,8 +113,8 @@ export default function Operator() {
|
||||
[searchParams],
|
||||
);
|
||||
|
||||
const missingData = !data || !userFields || !projectData;
|
||||
const isLoading = status === 'pending' || userFieldsStatus === 'pending' || projectDataStatus === 'pending';
|
||||
const missingData = !data || !customFields || !projectData;
|
||||
const isLoading = status === 'pending' || customFieldStatus === 'pending' || projectDataStatus === 'pending';
|
||||
|
||||
if (missingData || isLoading) {
|
||||
return <Empty text='Loading...' />;
|
||||
@@ -124,24 +122,22 @@ export default function Operator() {
|
||||
|
||||
// get fields which the user subscribed to
|
||||
const shouldEdit = searchParams.get('shouldEdit');
|
||||
const subscribe = searchParams.get('subscribe') as keyof UserFields | null;
|
||||
const subscribe = searchParams.get('subscribe') as keyof CustomFields;
|
||||
const canEdit = shouldEdit && subscribe;
|
||||
|
||||
const main = searchParams.get('main') as keyof TitleFields | null;
|
||||
const secondary = searchParams.get('secondary') as keyof TitleFields | null;
|
||||
const subscribedAlias = subscribe ? userFields[subscribe] : '';
|
||||
const showSeconds = isStringBoolean(searchParams.get('showseconds'));
|
||||
const secondary = searchParams.get('secondary');
|
||||
|
||||
const operatorOptions = getOperatorOptions(userFields, settings?.timeFormat ?? '24');
|
||||
const defaultFormat = getDefaultFormat(settings?.timeFormat);
|
||||
const operatorOptions = getOperatorOptions(customFields, defaultFormat);
|
||||
let isPast = Boolean(featureData.selectedEventId);
|
||||
const hidePast = isStringBoolean(searchParams.get('hidepast'));
|
||||
|
||||
const firstEvent = getFirstEvent(data);
|
||||
const lastEvent = getLastEvent(data);
|
||||
const { firstEvent } = getFirstEventNormal(data.rundown, data.order);
|
||||
const { lastEvent } = getLastEventNormal(data.rundown, data.order);
|
||||
|
||||
return (
|
||||
<div className={style.operatorContainer}>
|
||||
<NavigationMenu />
|
||||
<ViewParamsEditor paramFields={operatorOptions} />
|
||||
{editEvent && <EditModal event={editEvent} onClose={() => setEditEvent(null)} />}
|
||||
|
||||
@@ -162,7 +158,8 @@ export default function Operator() {
|
||||
)}
|
||||
|
||||
<div className={style.operatorEvents} onWheel={handleScroll} onTouchMove={handleScroll} ref={scrollRef}>
|
||||
{data.map((entry) => {
|
||||
{data.order.map((eventId) => {
|
||||
const entry = data.rundown[eventId];
|
||||
if (isOntimeEvent(entry)) {
|
||||
const isSelected = featureData.selectedEventId === entry.id;
|
||||
if (isSelected) {
|
||||
@@ -175,8 +172,8 @@ export default function Operator() {
|
||||
}
|
||||
|
||||
const mainField = main ? entry?.[main] || entry.title : entry.title;
|
||||
const secondaryField = secondary ? entry?.[secondary] || entry.subtitle : entry.subtitle;
|
||||
const subscribedData = (subscribe ? entry?.[subscribe] : undefined) || '';
|
||||
const secondaryField = getPropertyValue(entry, secondary) ?? '';
|
||||
const subscribedData = entry.custom[subscribe]?.value;
|
||||
|
||||
return (
|
||||
<OperatorEvent
|
||||
@@ -192,8 +189,7 @@ export default function Operator() {
|
||||
delay={entry.delay}
|
||||
isSelected={isSelected}
|
||||
subscribed={subscribedData}
|
||||
subscribedAlias={subscribedAlias}
|
||||
showSeconds={showSeconds}
|
||||
subscribeLabel={subscribe}
|
||||
isPast={isPast}
|
||||
selectedRef={isSelected ? selectedRef : undefined}
|
||||
onLongPress={canEdit ? handleEdit : () => undefined}
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
import { useCallback } from 'react';
|
||||
import { useSearchParams } from 'react-router-dom';
|
||||
import { useDisclosure } from '@chakra-ui/react';
|
||||
|
||||
import FloatingNavigation from '../../common/components/navigation-menu/FloatingNavigation';
|
||||
import ProductionNavigationMenu from '../../common/components/navigation-menu/ProductionNavigationMenu';
|
||||
import ProtectRoute from '../../common/components/protect-route/ProtectRoute';
|
||||
|
||||
import Operator from './Operator';
|
||||
|
||||
export default function OperatorExport() {
|
||||
const [searchParams, setSearchParams] = useSearchParams();
|
||||
const { isOpen, onOpen, onClose } = useDisclosure();
|
||||
|
||||
const showEditFormDrawer = useCallback(() => {
|
||||
searchParams.set('edit', 'true');
|
||||
setSearchParams(searchParams);
|
||||
}, [searchParams, setSearchParams]);
|
||||
|
||||
const toggleMenu = isOpen ? onClose : onOpen;
|
||||
|
||||
return (
|
||||
<ProtectRoute permission='operator'>
|
||||
<FloatingNavigation toggleMenu={toggleMenu} toggleSettings={showEditFormDrawer} />
|
||||
<ProductionNavigationMenu isMenuOpen={isOpen} onMenuClose={onClose} />
|
||||
<Operator />
|
||||
</ProtectRoute>
|
||||
);
|
||||
}
|
||||
@@ -1,6 +1,3 @@
|
||||
@use '../../../theme/v2Styles' as *;
|
||||
@use '../../../theme/ontimeColours' as *;
|
||||
|
||||
.editModal {
|
||||
position: absolute;
|
||||
z-index: 2;
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { useRef, useState } from 'react';
|
||||
import { Button, Textarea } from '@chakra-ui/react';
|
||||
import { OntimeEvent } from 'ontime-types';
|
||||
|
||||
import { useEventAction } from '../../../common/hooks/useEventAction';
|
||||
import type { PartialEdit } from '../Operator';
|
||||
@@ -15,19 +14,18 @@ interface EditModalProps {
|
||||
export default function EditModal(props: EditModalProps) {
|
||||
const { event, onClose } = props;
|
||||
|
||||
const { updateEvent } = useEventAction();
|
||||
const { updateCustomField } = useEventAction();
|
||||
const [loading, setLoading] = useState(false);
|
||||
const inputRef = useRef<HTMLTextAreaElement | null>(null);
|
||||
|
||||
const handleSave = async () => {
|
||||
setLoading(true);
|
||||
const newValue = inputRef.current?.value;
|
||||
if (newValue === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
const partialEvent: Partial<OntimeEvent> = {
|
||||
id: event.id,
|
||||
[event.field]: newValue,
|
||||
};
|
||||
await updateEvent(partialEvent);
|
||||
await updateCustomField(event.id, event.field, newValue);
|
||||
setLoading(false);
|
||||
onClose();
|
||||
};
|
||||
@@ -43,6 +41,7 @@ export default function EditModal(props: EditModalProps) {
|
||||
placeholder={`Add value for ${fieldLabel} field`}
|
||||
defaultValue={event.fieldValue}
|
||||
isDisabled={loading}
|
||||
resize='none'
|
||||
/>
|
||||
<div className={style.buttonRow}>
|
||||
<Button variant='ontime-subtle' onClick={onClose} isDisabled={loading}>
|
||||
|
||||
@@ -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 {
|
||||
@@ -81,10 +79,12 @@
|
||||
justify-self: end;
|
||||
}
|
||||
|
||||
.running {
|
||||
.runningTime {
|
||||
@include clock-size;
|
||||
grid-area: running;
|
||||
justify-self: end;
|
||||
display: flex;
|
||||
gap: 0.5em;
|
||||
}
|
||||
|
||||
.fields {
|
||||
@@ -102,7 +102,7 @@
|
||||
}
|
||||
|
||||
.value {
|
||||
color: $orange-500
|
||||
color: $orange-500;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,8 @@ import DelayIndicator from '../../../common/components/delay-indicator/DelayIndi
|
||||
import useLongPress from '../../../common/hooks/useLongPress';
|
||||
import { useTimer } from '../../../common/hooks/useSocket';
|
||||
import { cx, getAccessibleColour } from '../../../common/utils/styleUtils';
|
||||
import { formatTime } from '../../../common/utils/time';
|
||||
import ClockTime from '../../viewers/common/clock-time/ClockTime';
|
||||
import RunningTime from '../../viewers/common/running-time/RunningTime';
|
||||
import type { EditEvent } from '../Operator';
|
||||
|
||||
import style from './OperatorEvent.module.scss';
|
||||
@@ -21,8 +22,7 @@ interface OperatorEventProps {
|
||||
delay?: number;
|
||||
isSelected: boolean;
|
||||
subscribed?: string;
|
||||
subscribedAlias: string;
|
||||
showSeconds: boolean;
|
||||
subscribeLabel: string;
|
||||
isPast: boolean;
|
||||
selectedRef?: RefObject<HTMLDivElement>;
|
||||
onLongPress: (event: EditEvent) => void;
|
||||
@@ -30,8 +30,8 @@ interface OperatorEventProps {
|
||||
|
||||
// extract this to contain re-renders
|
||||
function RollingTime() {
|
||||
const timer = useTimer();
|
||||
return <>{formatTime(timer.current, { showSeconds: true, format: 'hh:mm:ss' })}</>;
|
||||
const { current } = useTimer();
|
||||
return <RunningTime value={current} />;
|
||||
}
|
||||
|
||||
function OperatorEvent(props: OperatorEventProps) {
|
||||
@@ -47,8 +47,7 @@ function OperatorEvent(props: OperatorEventProps) {
|
||||
delay,
|
||||
isSelected,
|
||||
subscribed,
|
||||
subscribedAlias,
|
||||
showSeconds,
|
||||
subscribeLabel: subscribedAlias,
|
||||
isPast,
|
||||
selectedRef,
|
||||
onLongPress,
|
||||
@@ -61,10 +60,6 @@ function OperatorEvent(props: OperatorEventProps) {
|
||||
};
|
||||
|
||||
const mouseHandlers = useLongPress(handleLongPress, { threshold: 800 });
|
||||
|
||||
const start = formatTime(timeStart, { showSeconds });
|
||||
const end = formatTime(timeEnd, { showSeconds });
|
||||
|
||||
const cueColours = colour && getAccessibleColour(colour);
|
||||
|
||||
const operatorClasses = cx([
|
||||
@@ -82,13 +77,15 @@ function OperatorEvent(props: OperatorEventProps) {
|
||||
|
||||
<span className={style.mainField}>{main}</span>
|
||||
<span className={style.schedule}>
|
||||
{start} - {end}
|
||||
<ClockTime value={timeStart} preferredFormat12='h:mm' preferredFormat24='HH:mm' />
|
||||
-
|
||||
<ClockTime value={timeEnd} preferredFormat12='h:mm' preferredFormat24='HH:mm' />
|
||||
</span>
|
||||
|
||||
<span className={style.secondaryField}>{secondary}</span>
|
||||
<span className={style.running}>
|
||||
<span className={style.runningTime}>
|
||||
<DelayIndicator delayValue={delay} />
|
||||
{isSelected ? <RollingTime /> : formatTime(duration, { showSeconds: true, format: 'hh:mm:ss' })}
|
||||
{isSelected ? <RollingTime /> : <RunningTime value={duration} hideLeadingZero />}
|
||||
</span>
|
||||
|
||||
<div className={style.fields}>
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
@use '../../../../src/theme/ontimeColours' as *;
|
||||
@use '../../../../src/theme/v2Styles' as *;
|
||||
|
||||
@mixin column {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { ViewSettings } from 'ontime-types';
|
||||
|
||||
import MultiPartProgressBar from '../../../common/components/multi-part-progress-bar/MultiPartProgressBar';
|
||||
import { useTimer } from '../../../common/hooks/useSocket';
|
||||
import { useProgressData } from '../../../common/hooks/useSocket';
|
||||
|
||||
import styles from './StatusBar.module.scss';
|
||||
|
||||
@@ -11,18 +11,17 @@ interface StatusBarProgressProps {
|
||||
|
||||
export default function StatusBarProgress(props: StatusBarProgressProps) {
|
||||
const { viewSettings } = props;
|
||||
|
||||
const timer = useTimer();
|
||||
const totalTime = (timer.duration ?? 0) + (timer.addedTime ?? 0);
|
||||
const { addedTime, current, duration, timeWarning, timeDanger } = useProgressData();
|
||||
const totalTime = (duration ?? 0) + (addedTime ?? 0);
|
||||
|
||||
return (
|
||||
<MultiPartProgressBar
|
||||
now={timer.current}
|
||||
now={current}
|
||||
complete={totalTime}
|
||||
normalColor={viewSettings.normalColor}
|
||||
warning={viewSettings.warningThreshold}
|
||||
warning={timeWarning}
|
||||
warningColor={viewSettings.warningColor}
|
||||
danger={viewSettings.dangerThreshold}
|
||||
danger={timeDanger}
|
||||
dangerColor={viewSettings.dangerColor}
|
||||
className={styles.progressOverride}
|
||||
/>
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { useMemo } from 'react';
|
||||
import { Playback } from 'ontime-types';
|
||||
import { millisToString } from 'ontime-utils';
|
||||
import { MaybeNumber, Playback } from 'ontime-types';
|
||||
|
||||
import PlaybackIcon from '../../../common/components/playback-icon/PlaybackIcon';
|
||||
import { useTimer } from '../../../common/hooks/useSocket';
|
||||
import { useClock, useTimer } from '../../../common/hooks/useSocket';
|
||||
import { cx } from '../../../common/utils/styleUtils';
|
||||
import { formatTime } from '../../../common/utils/time';
|
||||
import ClockTime from '../../viewers/common/clock-time/ClockTime';
|
||||
import RunningTime from '../../viewers/common/running-time/RunningTime';
|
||||
|
||||
import styles from './StatusBar.module.scss';
|
||||
|
||||
@@ -23,31 +23,32 @@ export default function StatusBarTimers(props: StatusBarTimersProps) {
|
||||
const { projectTitle, playback, selectedEventId, firstStart, firstId, lastEnd, lastId } = props;
|
||||
|
||||
const timer = useTimer();
|
||||
const { clock } = useClock();
|
||||
|
||||
const getTimeStart = () => {
|
||||
const getTimeStart = (): MaybeNumber => {
|
||||
if (firstStart === undefined) {
|
||||
return '...';
|
||||
return null;
|
||||
}
|
||||
|
||||
if (selectedEventId) {
|
||||
if (firstId === selectedEventId) {
|
||||
return millisToString(timer.expectedFinish);
|
||||
return timer.expectedFinish;
|
||||
}
|
||||
}
|
||||
return millisToString(firstStart);
|
||||
return firstStart;
|
||||
};
|
||||
|
||||
const getTimeEnd = () => {
|
||||
const getTimeEnd = (): MaybeNumber => {
|
||||
if (lastEnd === undefined) {
|
||||
return '...';
|
||||
return null;
|
||||
}
|
||||
|
||||
if (selectedEventId) {
|
||||
if (lastId === selectedEventId) {
|
||||
return millisToString(timer.expectedFinish);
|
||||
return timer.expectedFinish;
|
||||
}
|
||||
}
|
||||
return millisToString(lastEnd);
|
||||
return lastEnd;
|
||||
};
|
||||
|
||||
const PlaybackIconComponent = useMemo(() => {
|
||||
@@ -56,38 +57,30 @@ export default function StatusBarTimers(props: StatusBarTimersProps) {
|
||||
return <PlaybackIcon state={playback} skipTooltip className={classes} />;
|
||||
}, [playback]);
|
||||
|
||||
// use user defined format
|
||||
const timeNow = formatTime(timer.clock, {
|
||||
showSeconds: true,
|
||||
});
|
||||
|
||||
const runningTime = millisToString(timer.current);
|
||||
const elapsedTime = millisToString(timer.elapsed);
|
||||
|
||||
return (
|
||||
<div className={styles.timers}>
|
||||
{PlaybackIconComponent}
|
||||
<div className={styles.timeNow}>
|
||||
<span className={styles.label}>Time now</span>
|
||||
<span className={styles.timer}>{timeNow}</span>
|
||||
<ClockTime className={styles.timer} value={clock} />
|
||||
</div>
|
||||
<div className={styles.elapsedTime}>
|
||||
<span className={styles.label}>Elapsed time</span>
|
||||
<span className={styles.timer}>{elapsedTime}</span>
|
||||
<RunningTime className={styles.timer} value={timer.elapsed} />
|
||||
</div>
|
||||
<div className={styles.runningTime}>
|
||||
<span className={styles.label}>Running timer</span>
|
||||
<span className={styles.timer}>{runningTime}</span>
|
||||
<RunningTime className={styles.timer} value={timer.current} />
|
||||
</div>
|
||||
|
||||
<span className={styles.title}>{projectTitle}</span>
|
||||
<div className={styles.startTime}>
|
||||
<span className={styles.label}>Scheduled start</span>
|
||||
<span className={styles.timer}>{getTimeStart()}</span>
|
||||
<ClockTime className={styles.timer} value={getTimeStart()} />
|
||||
</div>
|
||||
<div className={styles.endTime}>
|
||||
<span className={styles.label}>Scheduled end</span>
|
||||
<span className={styles.timer}>{getTimeEnd()}</span>
|
||||
<ClockTime className={styles.timer} value={getTimeEnd()} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user