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