mirror of
https://github.com/cpvalente/ontime.git
synced 2026-08-10 18:03:47 +00:00
v2 beta 5 (#375)
* chore: update documentation links * fix: sentry has no access to error context * style: clarify event history * fix: issue with clipboard write in safari * style: clarify event history * refactor: improvements to follow logic in rundown * refactor: improvements in go mode * several style tweaks and small improvements
This commit is contained in:
@@ -14,21 +14,18 @@ interface CopyTagProps {
|
||||
export default function CopyTag(props: PropsWithChildren<CopyTagProps>) {
|
||||
const { label, className, size = 'xs', children } = props;
|
||||
|
||||
const handleClick = () => {
|
||||
// we need to this as a promise because safari
|
||||
setTimeout(async () => await navigator.clipboard.writeText(children as string));
|
||||
};
|
||||
|
||||
return (
|
||||
<Tooltip label={label} openDelay={tooltipDelayFast}>
|
||||
<ButtonGroup
|
||||
size={size}
|
||||
isAttached
|
||||
className={className}
|
||||
>
|
||||
<Button variant='ontime-subtle' tabIndex={-1}>{children}</Button>
|
||||
<IconButton
|
||||
aria-label={label}
|
||||
icon={<IoCopy />}
|
||||
variant='ontime-filled'
|
||||
tabIndex={-1}
|
||||
onClick={() => navigator.clipboard.writeText(children as string)}
|
||||
/>
|
||||
<ButtonGroup size={size} isAttached className={className}>
|
||||
<Button variant='ontime-subtle' tabIndex={-1}>
|
||||
{children}
|
||||
</Button>
|
||||
<IconButton aria-label={label} icon={<IoCopy />} variant='ontime-filled' tabIndex={-1} onClick={handleClick} />
|
||||
</ButtonGroup>
|
||||
</Tooltip>
|
||||
);
|
||||
|
||||
@@ -28,12 +28,6 @@ class ErrorBoundary extends React.Component {
|
||||
const eventId = Sentry.captureException(error);
|
||||
this.setState({ eventId, info });
|
||||
});
|
||||
|
||||
try {
|
||||
this.context.emitError(error.toString());
|
||||
} catch (e) {
|
||||
Sentry.captureMessage(`Unable to emit error ${error} ${e}`);
|
||||
}
|
||||
this.reportContent = `${error} ${info.componentStack}`;
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,16 @@ export enum AppMode {
|
||||
Edit = 'edit',
|
||||
}
|
||||
|
||||
const appModeKey = 'ontime-app-mode';
|
||||
|
||||
function getModeFromSession() {
|
||||
return localStorage.getItem(appModeKey) === AppMode.Run ? AppMode.Run : AppMode.Edit;
|
||||
}
|
||||
|
||||
async function persistModeToSession(mode: AppMode) {
|
||||
localStorage.setItem(appModeKey, mode);
|
||||
}
|
||||
|
||||
type AppModeStore = {
|
||||
mode: AppMode;
|
||||
cursor: string | null;
|
||||
@@ -15,11 +25,12 @@ type AppModeStore = {
|
||||
};
|
||||
|
||||
export const useAppMode = create<AppModeStore>()((set) => ({
|
||||
mode: AppMode.Edit,
|
||||
mode: getModeFromSession(),
|
||||
cursor: null,
|
||||
editId: null,
|
||||
setMode: (mode: AppMode) =>
|
||||
set((state) => {
|
||||
persistModeToSession(mode);
|
||||
return mode === AppMode.Edit
|
||||
? {
|
||||
editId: state.cursor,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
export const githubUrl = 'https://www.github.com/cpvalente/ontime';
|
||||
export const apiRepoLatest = 'https://api.github.com/repos/cpvalente/ontime/releases/latest';
|
||||
|
||||
export const gitbookUrl = 'https://cpvalente.gitbook.io';
|
||||
export const gitbookUrl = 'https://ontime.gitbook.io';
|
||||
|
||||
@@ -34,17 +34,26 @@ export default function PlaybackButtons(props: PlaybackButtonsProps) {
|
||||
const isLast = selectedEventIndex === numEvents - 1;
|
||||
const noEvents = numEvents === 0;
|
||||
|
||||
const disableGo = isRolling || noEvents || isLast;
|
||||
const disableGo = isRolling || noEvents || (isLast && !isArmed);
|
||||
const disablePrev = isRolling || noEvents || isFirst;
|
||||
|
||||
const goModeText = selectedEventIndex === null || isArmed ? 'Start' : 'Next';
|
||||
const goModeAction = () => {
|
||||
if (isArmed) {
|
||||
setPlayback.start();
|
||||
} else {
|
||||
setPlayback.startNext();
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={styles.buttonContainer}>
|
||||
<TapButton disabled={disableGo} onClick={() => setPlayback.startNext()} aspect='fill' className={styles.go}>
|
||||
GO
|
||||
<TapButton disabled={disableGo} onClick={goModeAction} aspect='fill' className={styles.go}>
|
||||
{goModeText}
|
||||
</TapButton>
|
||||
<div className={style.playbackContainer}>
|
||||
<TapButton
|
||||
onClick={() => setPlayback.start()}
|
||||
onClick={setPlayback.start}
|
||||
disabled={isStopped || isRolling}
|
||||
theme={Playback.Play}
|
||||
active={isPlaying}
|
||||
@@ -53,7 +62,7 @@ export default function PlaybackButtons(props: PlaybackButtonsProps) {
|
||||
</TapButton>
|
||||
|
||||
<TapButton
|
||||
onClick={() => setPlayback.pause()}
|
||||
onClick={setPlayback.pause}
|
||||
disabled={isStopped || isRolling || isArmed}
|
||||
theme={Playback.Pause}
|
||||
active={isPaused}
|
||||
@@ -63,19 +72,19 @@ export default function PlaybackButtons(props: PlaybackButtonsProps) {
|
||||
</div>
|
||||
<div className={style.transportContainer}>
|
||||
<Tooltip label='Previous event' openDelay={tooltipDelayMid}>
|
||||
<TapButton onClick={() => setPlayback.previous()} disabled={disablePrev}>
|
||||
<TapButton onClick={setPlayback.previous} disabled={disablePrev}>
|
||||
<IoPlaySkipBack />
|
||||
</TapButton>
|
||||
</Tooltip>
|
||||
<Tooltip label='Next event' openDelay={tooltipDelayMid}>
|
||||
<TapButton onClick={() => setPlayback.next()} disabled={disableGo}>
|
||||
<TapButton onClick={setPlayback.next} disabled={disableGo}>
|
||||
<IoPlaySkipForward />
|
||||
</TapButton>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<div className={styles.extra}>
|
||||
<TapButton
|
||||
onClick={() => setPlayback.roll()}
|
||||
onClick={setPlayback.roll}
|
||||
disabled={!isStopped || noEvents}
|
||||
theme={Playback.Roll}
|
||||
active={isRolling}
|
||||
@@ -83,12 +92,12 @@ export default function PlaybackButtons(props: PlaybackButtonsProps) {
|
||||
<IoTimeOutline />
|
||||
</TapButton>
|
||||
<Tooltip label='Reload event' openDelay={tooltipDelayMid}>
|
||||
<TapButton onClick={() => setPlayback.reload()} disabled={isStopped || isRolling}>
|
||||
<TapButton onClick={setPlayback.reload} disabled={isStopped || isRolling}>
|
||||
<IoReload className={style.invertX} />
|
||||
</TapButton>
|
||||
</Tooltip>
|
||||
<Tooltip label='Unload Event' openDelay={tooltipDelayMid}>
|
||||
<TapButton onClick={() => setPlayback.stop()} disabled={isStopped && !isRolling} theme={Playback.Stop}>
|
||||
<TapButton onClick={setPlayback.stop} disabled={isStopped && !isRolling} theme={Playback.Stop}>
|
||||
<IoStop />
|
||||
</TapButton>
|
||||
</Tooltip>
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { memo } from 'react';
|
||||
import { Box, IconButton } from '@chakra-ui/react';
|
||||
import { FiX } from '@react-icons/all-files/fi/FiX';
|
||||
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 { useAppMode } from '../../common/stores/appModeStore';
|
||||
import { cx } from '../../common/utils/styleUtils';
|
||||
|
||||
import EventEditor from './EventEditor';
|
||||
@@ -19,13 +19,11 @@ const closeBtnStyle = {
|
||||
};
|
||||
|
||||
const EventEditorExport = () => {
|
||||
const appMode = useAppMode((state) => state.mode);
|
||||
const editId = useAppMode((state) => state.editId);
|
||||
const setEditId = useAppMode((state) => state.setEditId);
|
||||
|
||||
const editorStyle = cx([style.eventEditor, !editId ? style.noEvent : null]);
|
||||
const removeOpenEvent = () => setEditId(null);
|
||||
const canRemoveOpenId = appMode === AppMode.Run;
|
||||
|
||||
return (
|
||||
<Box className={editorStyle}>
|
||||
@@ -33,13 +31,7 @@ const EventEditorExport = () => {
|
||||
<div className={style.eventEditorLayout}>
|
||||
<EventEditor />
|
||||
<div className={style.header}>
|
||||
<IconButton
|
||||
aria-label='Close Menu'
|
||||
icon={<FiX />}
|
||||
onClick={removeOpenEvent}
|
||||
isDisabled={!canRemoveOpenId}
|
||||
{...closeBtnStyle}
|
||||
/>
|
||||
<IconButton aria-label='Close Menu' icon={<IoClose />} onClick={removeOpenEvent} {...closeBtnStyle} />
|
||||
</div>
|
||||
</div>
|
||||
</ErrorBoundary>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { ReactNode } from 'react';
|
||||
import { MouseEvent, ReactNode } from 'react';
|
||||
import { IoOpenOutline } from '@react-icons/all-files/io5/IoOpenOutline';
|
||||
|
||||
import { openLink } from '../../common/utils/linkUtils';
|
||||
import { cx } from '../../common/utils/styleUtils';
|
||||
|
||||
import style from './ModalLink.module.scss';
|
||||
@@ -14,8 +15,14 @@ interface ModalLinkProps {
|
||||
export default function ModalLink(props: ModalLinkProps) {
|
||||
const { href, inline, children } = props;
|
||||
const classes = cx([style.link, inline ? style.inline : null]);
|
||||
|
||||
const handleClick = (event: MouseEvent) => {
|
||||
event.preventDefault();
|
||||
openLink(href);
|
||||
};
|
||||
|
||||
return (
|
||||
<a href={href} target='_blank' rel='noreferrer' className={classes}>
|
||||
<a href='#!' target='_blank' rel='noreferrer' className={classes} onClick={handleClick}>
|
||||
{children} <IoOpenOutline />
|
||||
</a>
|
||||
);
|
||||
|
||||
@@ -43,7 +43,7 @@ export default function AboutModal(props: AboutModalProps) {
|
||||
<div className={styles.padBottom}>
|
||||
<span className={styles.sectionTitle}>Ontime</span>
|
||||
Free Open Source Software for managing rundowns and event timers
|
||||
<ModalLink href='www.getontime.no'>www.getontime.no</ModalLink>
|
||||
<ModalLink href='https://www.getontime.no'>www.getontime.no</ModalLink>
|
||||
</div>
|
||||
<div className={styles.padBottom}>
|
||||
<span className={styles.sectionTitle}>Current version</span>
|
||||
|
||||
@@ -12,7 +12,7 @@ interface IntegrationModalProps {
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
const oscDocsUrl = 'https://cpvalente.gitbook.io/ontime/control-and-feedback/osc';
|
||||
const oscDocsUrl = 'https://ontime.gitbook.io/v2/control-and-feedback/osc';
|
||||
|
||||
export default function IntegrationModal(props: IntegrationModalProps) {
|
||||
const { isOpen, onClose } = props;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { MutableRefObject, useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { closestCenter, DndContext, DragEndEvent, PointerSensor, useSensor, useSensors } from '@dnd-kit/core';
|
||||
import { arrayMove, SortableContext, verticalListSortingStrategy } from '@dnd-kit/sortable';
|
||||
import { OntimeRundown, Playback, SupportedEvent } from 'ontime-types';
|
||||
@@ -37,7 +37,8 @@ export default function Rundown(props: RundownProps) {
|
||||
const appMode = useAppMode((state) => state.mode);
|
||||
const viewFollowsCursor = appMode === AppMode.Run;
|
||||
const moveCursorTo = useAppMode((state) => state.setCursor);
|
||||
const cursorRef = useRef<HTMLDivElement>();
|
||||
const cursorRef = useRef<HTMLDivElement | null>(null);
|
||||
const scrollRef = useRef<HTMLDivElement | null>(null);
|
||||
|
||||
// DND KIT
|
||||
const sensors = useSensors(useSensor(PointerSensor));
|
||||
@@ -153,19 +154,25 @@ export default function Rundown(props: RundownProps) {
|
||||
|
||||
// when cursor moves, view should follow
|
||||
useEffect(() => {
|
||||
if (!cursorRef?.current) return;
|
||||
function scrollToComponent(
|
||||
componentRef: MutableRefObject<HTMLDivElement>,
|
||||
scrollRef: MutableRefObject<HTMLDivElement>,
|
||||
) {
|
||||
const componentRect = componentRef.current.getBoundingClientRect();
|
||||
const scrollRect = scrollRef.current.getBoundingClientRect();
|
||||
const top = componentRect.top - scrollRect.top + scrollRef.current.scrollTop - 100;
|
||||
scrollRef.current.scrollTo({ top, behavior: 'smooth' });
|
||||
}
|
||||
|
||||
// using start in block parameter causes jumpy behaviour
|
||||
// could alternatively scroll using scrollTo and
|
||||
// calculate position within a range
|
||||
// if the item is near the top half, we are ok
|
||||
// otherwise scroll difference
|
||||
cursorRef.current.scrollIntoView({
|
||||
behavior: 'smooth',
|
||||
block: 'nearest',
|
||||
inline: 'start',
|
||||
});
|
||||
}, [cursorRef]);
|
||||
if (cursorRef.current && scrollRef.current) {
|
||||
// Use requestAnimationFrame to ensure the component is fully loaded
|
||||
window.requestAnimationFrame(() => {
|
||||
scrollToComponent(cursorRef as MutableRefObject<HTMLDivElement>, scrollRef as MutableRefObject<HTMLDivElement>);
|
||||
});
|
||||
}
|
||||
|
||||
// eslint-disable-next-line -- the prompt seems incorrect
|
||||
}, [cursorRef?.current, scrollRef]);
|
||||
|
||||
useEffect(() => {
|
||||
// in run mode, we follow selection
|
||||
@@ -200,9 +207,10 @@ export default function Rundown(props: RundownProps) {
|
||||
let thisEnd = 0;
|
||||
let previousEventId: string | undefined;
|
||||
let eventIndex = -1;
|
||||
let isPast = Boolean(featureData?.selectedEventId);
|
||||
|
||||
return (
|
||||
<div className={style.eventContainer}>
|
||||
<div className={style.eventContainer} ref={scrollRef}>
|
||||
<DndContext onDragEnd={handleOnDragEnd} sensors={sensors} collisionDetection={closestCenter}>
|
||||
<SortableContext items={statefulEntries} strategy={verticalListSortingStrategy}>
|
||||
<div className={style.list}>
|
||||
@@ -225,12 +233,16 @@ export default function Rundown(props: RundownProps) {
|
||||
const isSelected = featureData?.selectedEventId === entry.id;
|
||||
const isNext = featureData?.nextEventId === entry.id;
|
||||
const hasCursor = entry.id === cursor;
|
||||
if (isSelected) {
|
||||
isPast = false;
|
||||
}
|
||||
|
||||
return (
|
||||
<div key={entry.id} ref={hasCursor ? cursorRef : undefined}>
|
||||
<RundownEntry
|
||||
type={entry.type}
|
||||
eventIndex={eventIndex}
|
||||
isPast={isPast}
|
||||
data={entry}
|
||||
selected={isSelected}
|
||||
hasCursor={hasCursor}
|
||||
|
||||
@@ -17,6 +17,7 @@ export type EventItemActions = 'set-cursor' | 'event' | 'delay' | 'block' | 'del
|
||||
interface RundownEntryProps {
|
||||
type: SupportedEvent;
|
||||
eventIndex: number;
|
||||
isPast: boolean;
|
||||
data: OntimeRundownEntry;
|
||||
selected: boolean;
|
||||
hasCursor: boolean;
|
||||
@@ -32,6 +33,7 @@ interface RundownEntryProps {
|
||||
export default function RundownEntry(props: RundownEntryProps) {
|
||||
const {
|
||||
eventIndex,
|
||||
isPast,
|
||||
data,
|
||||
selected,
|
||||
hasCursor,
|
||||
@@ -168,6 +170,7 @@ export default function RundownEntry(props: RundownEntryProps) {
|
||||
delay={delay}
|
||||
previousEnd={previousEnd}
|
||||
colour={data.colour}
|
||||
isPast={isPast}
|
||||
next={next}
|
||||
skip={data.skip}
|
||||
selected={selected}
|
||||
|
||||
@@ -10,9 +10,9 @@ $skip-opacity: 0.1;
|
||||
|
||||
display: grid;
|
||||
grid-template-areas:
|
||||
"binder ... ... ..."
|
||||
"binder pb-actions times actions"
|
||||
"binder pb-actions title title"
|
||||
"binder ... ... ..."
|
||||
"binder pb-actions times actions"
|
||||
"binder pb-actions title next"
|
||||
"binder pb-actions estatus estatus"
|
||||
"binder ... ... ...";
|
||||
|
||||
@@ -25,7 +25,7 @@ $skip-opacity: 0.1;
|
||||
transition-property: background-color;
|
||||
transition-duration: $transition-time-feedback;
|
||||
|
||||
@mixin declare-overrides(){
|
||||
@mixin declare-overrides() {
|
||||
--status-color-override: #{$gray-200};
|
||||
--status-color-active-override: #{$green-400};
|
||||
}
|
||||
@@ -45,7 +45,7 @@ $skip-opacity: 0.1;
|
||||
}
|
||||
|
||||
&.pause {
|
||||
background-color: $orange-700;
|
||||
background-color: rgba($ontime-paused, 0.6);
|
||||
@include declare-overrides;
|
||||
}
|
||||
|
||||
@@ -53,6 +53,10 @@ $skip-opacity: 0.1;
|
||||
outline: 1px solid $block-cursor-color;
|
||||
}
|
||||
|
||||
&.past:not(.skip) {
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
&.skip {
|
||||
border: 1px solid $white-3;
|
||||
|
||||
@@ -121,7 +125,6 @@ $skip-opacity: 0.1;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
|
||||
.eventOptions {
|
||||
margin: $element-spacing 16px $element-spacing 0;
|
||||
}
|
||||
@@ -163,6 +166,16 @@ $skip-opacity: 0.1;
|
||||
line-height: 13px;
|
||||
}
|
||||
|
||||
|
||||
.nextTag {
|
||||
grid-area: next;
|
||||
font-size: 1em;
|
||||
color: $orange-500;
|
||||
letter-spacing: 0.03px;
|
||||
font-weight: 600;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.eventStatus {
|
||||
grid-area: status;
|
||||
display: flex;
|
||||
@@ -171,11 +184,6 @@ $skip-opacity: 0.1;
|
||||
gap: 8px;
|
||||
color: var(--status-color-override, $gray-500);
|
||||
|
||||
.tag {
|
||||
padding-top: 1px;
|
||||
font-size: 0.55em;
|
||||
color: $active-indicator;
|
||||
}
|
||||
|
||||
.statusIcon {
|
||||
width: 16px;
|
||||
|
||||
@@ -26,6 +26,7 @@ interface EventBlockProps {
|
||||
delay: number;
|
||||
previousEnd: number;
|
||||
colour: string;
|
||||
isPast: boolean;
|
||||
next: boolean;
|
||||
skip: boolean;
|
||||
selected: boolean;
|
||||
@@ -59,6 +60,7 @@ export default function EventBlock(props: EventBlockProps) {
|
||||
delay,
|
||||
previousEnd,
|
||||
colour,
|
||||
isPast,
|
||||
next,
|
||||
skip = false,
|
||||
selected,
|
||||
@@ -128,6 +130,7 @@ export default function EventBlock(props: EventBlockProps) {
|
||||
const blockClasses = cx([
|
||||
style.eventBlock,
|
||||
skip ? style.skip : null,
|
||||
isPast ? style.past : null,
|
||||
selected ? style.selected : null,
|
||||
playback ? style[playback] : null,
|
||||
hasCursor ? style.hasCursor : null,
|
||||
|
||||
@@ -113,6 +113,11 @@ const EventBlockInner = (props: EventBlockInnerProps) => {
|
||||
previousEnd={previousEnd}
|
||||
/>
|
||||
<EditableBlockTitle title={title} eventId={eventId} placeholder='Event title' className={style.eventTitle} />
|
||||
{next && (
|
||||
<Tooltip label='Next event' {...tooltipProps}>
|
||||
<span className={style.nextTag}>UP NEXT</span>
|
||||
</Tooltip>
|
||||
)}
|
||||
<EventBlockPlayback
|
||||
eventId={eventId}
|
||||
skip={skip}
|
||||
@@ -127,11 +132,6 @@ const EventBlockInner = (props: EventBlockInnerProps) => {
|
||||
{selected && <EventBlockProgressBar playback={playback} />}
|
||||
</div>
|
||||
<div className={style.eventStatus} tabIndex={-1}>
|
||||
{next && (
|
||||
<Tooltip label='Next event' {...tooltipProps}>
|
||||
<span className={style.tag}>NEXT</span>
|
||||
</Tooltip>
|
||||
)}
|
||||
<Tooltip label={`Time type: ${timerType}`} {...tooltipProps}>
|
||||
<span>
|
||||
<TimerIcon type={timerType} className={style.statusIcon} />
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { ComponentType, useMemo } from 'react';
|
||||
import { Playback, TitleBlock } from 'ontime-types';
|
||||
import { TitleBlock } from 'ontime-types';
|
||||
import { useStore } from 'zustand';
|
||||
|
||||
import useEventData from '../../common/hooks-query/useEventData';
|
||||
@@ -80,7 +80,6 @@ const withData = <P extends object>(Component: ComponentType<P>) => {
|
||||
// get clock string
|
||||
const TimeManagerType = {
|
||||
...timer,
|
||||
finished: playback === Playback.Play && (timer.current ?? 0) < 0 && timer.startedAt,
|
||||
playback,
|
||||
};
|
||||
|
||||
|
||||
@@ -91,7 +91,8 @@ export default function Countdown(props: CountdownProps) {
|
||||
}
|
||||
|
||||
const standby = time.playback !== Playback.Play && time.playback !== Playback.Roll && selectedId === follow?.id;
|
||||
const isRunningFinished = time.finished && runningMessage === TimerMessage.running;
|
||||
const finished = time.playback === Playback.Play && (time.current ?? 0) < 0 && time.startedAt;
|
||||
const isRunningFinished = finished && runningMessage === TimerMessage.running;
|
||||
const isSelected = runningMessage === TimerMessage.running;
|
||||
const delayedTimerStyles = delay > 0 ? 'aux-timers__value--delayed' : '';
|
||||
|
||||
|
||||
@@ -130,8 +130,8 @@ export default function MinimalTimer(props: MinimalTimerProps) {
|
||||
const isNegative =
|
||||
(time.current ?? 0) < 0 && time.timerType !== TimerType.Clock && time.timerType !== TimerType.CountUp;
|
||||
const showEndMessage = (time.current ?? 0) < 0 && viewSettings.endMessage && !hideEndMessage;
|
||||
const showFinished =
|
||||
time.finished && !userOptions?.hideOvertime && (time.timerType !== TimerType.Clock || showEndMessage);
|
||||
const finished = time.playback === Playback.Play && (time.current ?? 0) < 0 && time.startedAt;
|
||||
const showFinished = finished && !userOptions?.hideOvertime && (time.timerType !== TimerType.Clock || showEndMessage);
|
||||
|
||||
const showProgress = time.playback !== Playback.Stop;
|
||||
const showWarning = (time.current ?? 1) < viewSettings.warningThreshold;
|
||||
|
||||
@@ -65,9 +65,10 @@ export default function Timer(props: TimerProps) {
|
||||
const isNegative =
|
||||
(time.current ?? 0) < 0 && time.timerType !== TimerType.Clock && time.timerType !== TimerType.CountUp;
|
||||
|
||||
const finished = time.playback === Playback.Play && (time.current ?? 0) < 0 && time.startedAt;
|
||||
const showEndMessage = (time.current ?? 1) < 0 && viewSettings.endMessage;
|
||||
const showProgress = time.playback !== Playback.Stop;
|
||||
const showFinished = time.finished && (time.timerType !== TimerType.Clock || showEndMessage);
|
||||
const showFinished = finished && (time.timerType !== TimerType.Clock || showEndMessage);
|
||||
const showWarning = (time.current ?? 1) < viewSettings.warningThreshold;
|
||||
const showDanger = (time.current ?? 1) < viewSettings.dangerThreshold;
|
||||
const timerColor =
|
||||
@@ -130,7 +131,7 @@ export default function Timer(props: TimerProps) {
|
||||
/>
|
||||
|
||||
<AnimatePresence>
|
||||
{title.showNow && !time.finished && (
|
||||
{title.showNow && !finished && (
|
||||
<motion.div
|
||||
className='event now'
|
||||
key='now'
|
||||
|
||||
@@ -96,13 +96,6 @@ function getApplicationMenu(isMac, askToQuit) {
|
||||
await shell.openExternal('http://localhost:4001/lower');
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
label: 'PiP',
|
||||
click: async () => {
|
||||
await shell.openExternal('http://localhost:4001/pip');
|
||||
},
|
||||
},
|
||||
{
|
||||
label: 'Studio Clock',
|
||||
click: async () => {
|
||||
@@ -160,7 +153,7 @@ function getApplicationMenu(isMac, askToQuit) {
|
||||
{
|
||||
label: 'Online documentation',
|
||||
click: async () => {
|
||||
await shell.openExternal('https://cpvalente.gitbook.io/ontime/');
|
||||
await shell.openExternal('https://ontime.gitbook.io/');
|
||||
},
|
||||
},
|
||||
],
|
||||
|
||||
Reference in New Issue
Block a user