mirror of
https://github.com/cpvalente/ontime.git
synced 2026-08-17 05:13:32 +00:00
@@ -5,6 +5,7 @@ import { IoSunny } from '@react-icons/all-files/io5/IoSunny';
|
||||
import { IoSunnyOutline } from '@react-icons/all-files/io5/IoSunnyOutline';
|
||||
|
||||
import { setMessage, useMessageControl } from '../../../common/hooks/useSocket';
|
||||
import { enDash } from '../../../common/utils/styleUtils';
|
||||
|
||||
import InputRow from './InputRow';
|
||||
|
||||
@@ -64,7 +65,7 @@ export default function MessageControl() {
|
||||
</div>
|
||||
<InputRow
|
||||
label='External Message'
|
||||
placeholder='-'
|
||||
placeholder={enDash}
|
||||
readonly
|
||||
text={message.external.text || ''}
|
||||
visible={message.external.visible || false}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { MaybeNumber } from 'ontime-types';
|
||||
import { millisToString } from 'ontime-utils';
|
||||
|
||||
import { cx } from '../../../../common/utils/styleUtils';
|
||||
import { cx, timerPlaceholder } from '../../../../common/utils/styleUtils';
|
||||
|
||||
import style from './TimerDisplay.module.scss';
|
||||
|
||||
@@ -11,16 +11,17 @@ interface TimerDisplayProps {
|
||||
|
||||
/**
|
||||
* Displays time in ms in formatted timetag
|
||||
* Typically used in production views
|
||||
*/
|
||||
export default function TimerDisplay(props: TimerDisplayProps) {
|
||||
const { time } = props;
|
||||
|
||||
if (time == null) {
|
||||
return <div className={style.timer}>-- : -- : --</div>;
|
||||
return <div className={style.timer}>{timerPlaceholder}</div>;
|
||||
}
|
||||
|
||||
const isNegative = time < 0;
|
||||
const display = millisToString(Math.abs(time), { fallback: '-- : -- : --' });
|
||||
const display = millisToString(Math.abs(time), { fallback: timerPlaceholder });
|
||||
const classes = cx([style.timer, isNegative ? style.finished : null]);
|
||||
|
||||
return <div className={classes}>{display}</div>;
|
||||
|
||||
@@ -1,12 +1,6 @@
|
||||
$table-font-size: calc(1rem - 2px);
|
||||
$table-header-font-size: calc(1rem - 3px);
|
||||
|
||||
@mixin ellipsis-overflow() {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.cuesheetContainer {
|
||||
grid-area: table;
|
||||
display: flex;
|
||||
|
||||
@@ -10,7 +10,7 @@ interface DelayRowProps {
|
||||
|
||||
function DelayRow(props: DelayRowProps) {
|
||||
const { duration } = props;
|
||||
const delayTime = millisToDelayString(duration);
|
||||
const delayTime = millisToDelayString(duration, 'expanded');
|
||||
|
||||
return (
|
||||
<tr className={style.delayRow}>
|
||||
|
||||
@@ -8,7 +8,7 @@ import { Playback, ProjectData } from 'ontime-types';
|
||||
import PlaybackIcon from '../../../common/components/playback-icon/PlaybackIcon';
|
||||
import useFullscreen from '../../../common/hooks/useFullscreen';
|
||||
import useProjectData from '../../../common/hooks-query/useProjectData';
|
||||
import { cx } from '../../../common/utils/styleUtils';
|
||||
import { cx, enDash } from '../../../common/utils/styleUtils';
|
||||
import { tooltipDelayFast } from '../../../ontimeConfig';
|
||||
import { useCuesheetSettings } from '../store/CuesheetSettings';
|
||||
|
||||
@@ -42,15 +42,15 @@ export default function CuesheetTableHeader({ handleExport, featureData }: Cuesh
|
||||
|
||||
const selected = !featureData.numEvents
|
||||
? 'No events'
|
||||
: `Event ${featureData.selectedEventIndex != null ? featureData.selectedEventIndex + 1 : '-'}/${
|
||||
featureData.numEvents ? featureData.numEvents : '-'
|
||||
: `Event ${featureData.selectedEventIndex != null ? featureData.selectedEventIndex + 1 : enDash}/${
|
||||
featureData.numEvents ? featureData.numEvents : enDash
|
||||
}`;
|
||||
|
||||
return (
|
||||
<div className={style.header}>
|
||||
<div className={style.event}>
|
||||
<div className={style.title}>{project?.title || '-'}</div>
|
||||
<div className={style.eventNow}>{featureData?.titleNow || '-'}</div>
|
||||
<div className={style.title}>{project?.title || enDash}</div>
|
||||
<div className={style.eventNow}>{featureData?.titleNow || enDash}</div>
|
||||
</div>
|
||||
<div className={style.playback}>
|
||||
<div className={style.playbackLabel}>{selected}</div>
|
||||
|
||||
@@ -3,6 +3,8 @@ import { UseFormRegister } from 'react-hook-form';
|
||||
import { IconButton, Input, InputGroup, InputRightElement } from '@chakra-ui/react';
|
||||
import { IoEyeOutline } from '@react-icons/all-files/io5/IoEyeOutline';
|
||||
|
||||
import { enDash } from '../../../common/utils/styleUtils';
|
||||
|
||||
interface FormInput {
|
||||
[key: string]: string;
|
||||
}
|
||||
@@ -21,7 +23,7 @@ export default function ModalPinInput({ register, formName, isDisabled }: ModalP
|
||||
type={isVisible ? 'text' : 'password'}
|
||||
maxLength={4}
|
||||
{...register(formName)}
|
||||
placeholder='-'
|
||||
placeholder={enDash}
|
||||
isDisabled={isDisabled}
|
||||
/>
|
||||
<InputRightElement>
|
||||
|
||||
@@ -2,54 +2,26 @@
|
||||
grid-area: overview;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: start;
|
||||
justify-content: space-between;
|
||||
font-size: $inner-section-text-size;
|
||||
gap: 2rem;
|
||||
padding-left: 1rem;
|
||||
padding-right: 0.5rem;
|
||||
}
|
||||
|
||||
.titles {
|
||||
flex: 1;
|
||||
padding: 0 1rem;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 1.5rem;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
@include ellipsis-overflow;
|
||||
}
|
||||
|
||||
.description {
|
||||
font-size: 1rem;
|
||||
color: $label-gray;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
@include ellipsis-overflow;
|
||||
}
|
||||
|
||||
.inline {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.25rem;
|
||||
.ahead {
|
||||
color: $green-500;
|
||||
}
|
||||
|
||||
@mixin indicator($bg-color) {
|
||||
&::before {
|
||||
content: '';
|
||||
background-color: $bg-color;
|
||||
display: inline-flex;
|
||||
height: 0.75em;
|
||||
width: 0.75em;
|
||||
vertical-align: middle;
|
||||
margin-right: 0.25rem;
|
||||
}
|
||||
}
|
||||
|
||||
.start {
|
||||
@include indicator($green-500);
|
||||
}
|
||||
|
||||
.end {
|
||||
@include indicator($red-500);
|
||||
.behind {
|
||||
color: $ontime-delay-text;
|
||||
}
|
||||
|
||||
@@ -1,34 +1,39 @@
|
||||
import { Tooltip } from '@chakra-ui/react';
|
||||
import { MaybeNumber } from 'ontime-types';
|
||||
import { millisToString } from 'ontime-utils';
|
||||
|
||||
import ErrorBoundary from '../../common/components/error-boundary/ErrorBoundary';
|
||||
import PlaybackIcon from '../../common/components/playback-icon/PlaybackIcon';
|
||||
import { useRuntimeOverview } from '../../common/hooks/useSocket';
|
||||
import { useRuntimeOverview, useRuntimePlaybackOverview } from '../../common/hooks/useSocket';
|
||||
import useProjectData from '../../common/hooks-query/useProjectData';
|
||||
import { formatTime } from '../../common/utils/time';
|
||||
import { enDash, timerPlaceholder } from '../../common/utils/styleUtils';
|
||||
|
||||
import styles from './Overview.module.scss';
|
||||
import { TimeColumn, TimeRow } from './composite/TimeLayout';
|
||||
|
||||
import style from './Overview.module.scss';
|
||||
|
||||
/**
|
||||
* Encapsulates the logic for formatting time in overview
|
||||
* @param time
|
||||
* @returns
|
||||
*/
|
||||
function formattedTime(time: MaybeNumber) {
|
||||
return millisToString(time, { fallback: timerPlaceholder });
|
||||
}
|
||||
|
||||
export default function Overview() {
|
||||
const { plannedEnd, plannedStart, actualStart, expectedEnd } = useRuntimeOverview();
|
||||
|
||||
return (
|
||||
<div className={styles.overview}>
|
||||
<div className={style.overview}>
|
||||
<ErrorBoundary>
|
||||
<TitlesOverview />
|
||||
<div className={styles.clocks}>
|
||||
<Tooltip label='Planned start'>
|
||||
<div className={styles.start}>Planned start</div>
|
||||
</Tooltip>
|
||||
<Tooltip label='Actual start'>
|
||||
<div className={styles.start}>Actual start</div>
|
||||
</Tooltip>
|
||||
<div className={style.column}>
|
||||
<TimeRow label='Planned start' value={formattedTime(plannedStart)} className={style.start} />
|
||||
<TimeRow label='Actual start' value={formattedTime(actualStart)} className={style.start} />
|
||||
</div>
|
||||
<RuntimeOverview />
|
||||
<div className={styles.clocks}>
|
||||
<Tooltip label='Planned end'>
|
||||
<div className={styles.end}>Planned end</div>
|
||||
</Tooltip>
|
||||
<Tooltip label='Expected end'>
|
||||
<div className={styles.end}>Expected end</div>
|
||||
</Tooltip>
|
||||
<div className={style.column}>
|
||||
<TimeRow label='Planned end' value={formattedTime(plannedEnd)} className={style.end} />
|
||||
<TimeRow label='Expected end' value={formattedTime(expectedEnd)} className={style.end} />
|
||||
</div>
|
||||
</ErrorBoundary>
|
||||
</div>
|
||||
@@ -39,31 +44,31 @@ function TitlesOverview() {
|
||||
const { data } = useProjectData();
|
||||
|
||||
return (
|
||||
<div className={styles.titles}>
|
||||
<div className={styles.title}>{data.title}</div>
|
||||
<div className={styles.description}>{data.description}</div>
|
||||
<div className={style.titles}>
|
||||
<div className={style.title}>{data.title}</div>
|
||||
<div className={style.description}>{data.description}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function RuntimeOverview() {
|
||||
const { playback, clock, numEvents, selectedEventIndex } = useRuntimeOverview();
|
||||
const { clock, numEvents, selectedEventIndex, offset } = useRuntimePlaybackOverview();
|
||||
|
||||
const current = selectedEventIndex !== null ? selectedEventIndex + 1 : '-';
|
||||
const ofTotal = numEvents || '-';
|
||||
const current = selectedEventIndex !== null ? selectedEventIndex + 1 : enDash;
|
||||
const ofTotal = numEvents || enDash;
|
||||
const progressText = numEvents ? `${current} of ${ofTotal}` : '';
|
||||
|
||||
const display = formatTime(clock);
|
||||
const isAhead = offset <= 0;
|
||||
let offsetText = millisToString(Math.abs(offset), { fallback: enDash });
|
||||
if (offsetText !== enDash) {
|
||||
offsetText = isAhead ? `+${offsetText}` : `${enDash}${offsetText}`;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={styles.clocks}>
|
||||
<div className={styles.inline}>
|
||||
<PlaybackIcon state={playback} skipTooltip />
|
||||
{display}
|
||||
</div>
|
||||
<div className={styles.inline}>
|
||||
<span>{`(${current} / ${ofTotal})`}</span>
|
||||
Over / Under
|
||||
</div>
|
||||
</div>
|
||||
<>
|
||||
<TimeColumn label='Progress' value={progressText} />
|
||||
<TimeColumn label='Offset' value={offsetText} className={isAhead ? style.ahead : style.behind} />
|
||||
<TimeColumn label='Time now' value={formattedTime(clock)} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
.label {
|
||||
color: $label-gray;
|
||||
font-size: calc(1rem - 2px);
|
||||
width: 10em; // a number large enough to force right alignment
|
||||
}
|
||||
|
||||
.clock {
|
||||
text-align: left;
|
||||
font-size: 1.5rem;
|
||||
letter-spacing: 0.5px;
|
||||
min-width: 5em;
|
||||
|
||||
&::after {
|
||||
content: '\200b';
|
||||
}
|
||||
}
|
||||
|
||||
.column {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.label {
|
||||
line-height: 0.9em;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
.row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
|
||||
.label {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.clock {
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
import { cx } from '../../../common/utils/styleUtils';
|
||||
|
||||
import style from './TimeLayout.module.scss';
|
||||
|
||||
interface TimeLayoutProps {
|
||||
label: string;
|
||||
value: string;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export function TimeColumn({ label, value, className }: TimeLayoutProps) {
|
||||
return (
|
||||
<div className={style.column}>
|
||||
<span className={style.label}>{label}</span>
|
||||
<span className={cx([style.clock, className])}>{value}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function TimeRow({ label, value, className }: TimeLayoutProps) {
|
||||
return (
|
||||
<div className={style.row}>
|
||||
<span className={style.label}>{label}</span>
|
||||
<span className={cx([style.clock, className])}>{value}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -64,9 +64,9 @@ const EventEditorTimes = (props: EventEditorTimesProps) => {
|
||||
|
||||
const hasDelay = delay !== 0;
|
||||
const delayLabel = hasDelay
|
||||
? `Event is ${millisToDelayString(delay)}. New schedule ${millisToString(timeStart + delay)} → ${millisToString(
|
||||
timeEnd + delay,
|
||||
)}`
|
||||
? `Event is ${millisToDelayString(delay, 'expanded')}. New schedule ${millisToString(
|
||||
timeStart + delay,
|
||||
)} → ${millisToString(timeEnd + delay)}`
|
||||
: '';
|
||||
|
||||
return (
|
||||
|
||||
@@ -10,6 +10,7 @@ import ViewParamsEditor from '../../../common/components/view-params-editor/View
|
||||
import { useRuntimeStylesheet } from '../../../common/hooks/useRuntimeStylesheet';
|
||||
import { ViewExtendedTimer } from '../../../common/models/TimeManager.type';
|
||||
import { OverridableOptions } from '../../../common/models/View.types';
|
||||
import { timerPlaceholder } from '../../../common/utils/styleUtils';
|
||||
import { isStringBoolean } from '../../../common/utils/viewUtils';
|
||||
import { useTranslation } from '../../../translation/TranslationProvider';
|
||||
import { getTimerByType } from '../common/viewerUtils';
|
||||
@@ -153,7 +154,7 @@ export default function MinimalTimer(props: MinimalTimerProps) {
|
||||
: viewSettings.normalColor;
|
||||
|
||||
const stageTimer = getTimerByType(time);
|
||||
let display = millisToString(stageTimer, { fallback: '-- : -- : --' });
|
||||
let display = millisToString(stageTimer, { fallback: timerPlaceholder });
|
||||
if (stageTimer !== null) {
|
||||
if (hideTimerSeconds) {
|
||||
display = removeSeconds(display);
|
||||
|
||||
@@ -12,6 +12,7 @@ import { getTimerOptions } from '../../../common/components/view-params-editor/c
|
||||
import ViewParamsEditor from '../../../common/components/view-params-editor/ViewParamsEditor';
|
||||
import { useRuntimeStylesheet } from '../../../common/hooks/useRuntimeStylesheet';
|
||||
import { ViewExtendedTimer } from '../../../common/models/TimeManager.type';
|
||||
import { timerPlaceholder } from '../../../common/utils/styleUtils';
|
||||
import { formatTime, getDefaultFormat } from '../../../common/utils/time';
|
||||
import { isStringBoolean } from '../../../common/utils/viewUtils';
|
||||
import { useTranslation } from '../../../translation/TranslationProvider';
|
||||
@@ -115,7 +116,7 @@ export default function Timer(props: TimerProps) {
|
||||
: viewSettings.normalColor;
|
||||
|
||||
const stageTimer = getTimerByType(time);
|
||||
let display = millisToString(stageTimer, { fallback: '-- : -- : --' });
|
||||
let display = millisToString(stageTimer, { fallback: timerPlaceholder });
|
||||
if (stageTimer !== null) {
|
||||
if (hideTimerSeconds) {
|
||||
display = removeSeconds(display);
|
||||
|
||||
Reference in New Issue
Block a user