mirror of
https://github.com/cpvalente/ontime.git
synced 2026-08-21 15:09:10 +00:00
Merge remote-tracking branch 'origin/master' into v1
This commit is contained in:
@@ -29,4 +29,4 @@ export const eventsURL = `${serverURL}/${EVENTS_TABLE}`;
|
||||
export const ontimeURL = `${serverURL}/ontime`;
|
||||
|
||||
export const stylesPath = 'external/styles/override.css';
|
||||
export const overrideStylesURL = `serverURL/${stylesPath}`;
|
||||
export const overrideStylesURL = `${serverURL}/${stylesPath}`;
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
import { atomWithStorage } from 'jotai/utils';
|
||||
|
||||
export const mirrorViewersAtom = atomWithStorage('ontime-viewers-mirrorViewers', false);
|
||||
@@ -3,10 +3,13 @@ import { Link } from 'react-router-dom';
|
||||
import { IconButton, Image } from '@chakra-ui/react';
|
||||
import { IoContract } from '@react-icons/all-files/io5/IoContract';
|
||||
import { IoExpand } from '@react-icons/all-files/io5/IoExpand';
|
||||
import { IoSync } from '@react-icons/all-files/io5/IoSync';
|
||||
import navlogo from 'assets/images/logos/LOGO-72.png';
|
||||
import { AnimatePresence, motion } from 'framer-motion';
|
||||
import { useAtom } from 'jotai';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import { mirrorViewersAtom } from '../../atoms/ViewerSettings';
|
||||
import useFullscreen from '../../hooks/useFullscreen';
|
||||
|
||||
import navigatorConstants from './navigatorConstants';
|
||||
@@ -25,6 +28,7 @@ export default function NavLogo(props) {
|
||||
const { isHidden } = props;
|
||||
const [showNav, setShowNav] = useState(false);
|
||||
const { isFullScreen, toggleFullScreen } = useFullscreen();
|
||||
const [isMirrored, setMirrored] = useAtom(mirrorViewersAtom);
|
||||
|
||||
const handleClick = useCallback(() => {
|
||||
setShowNav((prev) => !prev);
|
||||
@@ -74,6 +78,12 @@ export default function NavLogo(props) {
|
||||
exit={{ opacity: 0, scaleX: 0 }}
|
||||
className={style.actions}
|
||||
>
|
||||
<IconButton
|
||||
aria-label='Mirror screen'
|
||||
icon={<IoSync />}
|
||||
onClick={() => setMirrored((prev) => !prev)}
|
||||
{...navButtonStyle}
|
||||
/>
|
||||
<IconButton
|
||||
aria-label='Toggle Fullscreen'
|
||||
icon={isFullScreen ? <IoContract /> : <IoExpand />}
|
||||
|
||||
@@ -11,7 +11,7 @@ import { TableSettingsContext } from '../../common/context/TableSettingsContext'
|
||||
import useFullscreen from '../../common/hooks/useFullscreen';
|
||||
import { useTimerProvider } from '../../common/hooks/useSocketProvider';
|
||||
import useEvent from '../../common/hooks-query/useEvent';
|
||||
import { formatDisplay } from '../../common/utils/dateConfig';
|
||||
import { formatDisplay, millisToSeconds } from '../../common/utils/dateConfig';
|
||||
import { formatTime } from '../../common/utils/time';
|
||||
import { tooltipDelayFast } from '../../ontimeConfig';
|
||||
|
||||
@@ -19,7 +19,7 @@ import PlaybackIcon from './tableElements/PlaybackIcon';
|
||||
|
||||
import style from './Table.module.scss';
|
||||
|
||||
export default function TableHeader({handleCSVExport, featureData}) {
|
||||
export default function TableHeader({ handleCSVExport, featureData }) {
|
||||
const { followSelected, showSettings, toggleTheme, toggleSettings, toggleFollow } =
|
||||
useContext(TableSettingsContext);
|
||||
const timer = useTimerProvider();
|
||||
@@ -29,11 +29,12 @@ export default function TableHeader({handleCSVExport, featureData}) {
|
||||
const selected = !featureData.numEvents
|
||||
? 'No events'
|
||||
: `Event ${featureData.selectedEventIndex != null ? featureData.selectedEventIndex + 1 : '-'}/${
|
||||
featureData.numEvents ? featureData.numEvents : '-'
|
||||
}`;
|
||||
featureData.numEvents ? featureData.numEvents : '-'
|
||||
}`;
|
||||
|
||||
// prepare presentation variables
|
||||
const timerNow = `${timer.running < 0 ? '-' : ''}${formatDisplay(timer.running)}`;
|
||||
const isOvertime = timer.current < 0;
|
||||
const timerNow = `${isOvertime ? '-' : ''}${formatDisplay(millisToSeconds(timer.current))}`;
|
||||
const timeNow = formatTime(timer.clock, {
|
||||
showSeconds: true,
|
||||
format: 'hh:mm:ss a',
|
||||
|
||||
@@ -4,9 +4,11 @@ import NavLogo from 'common/components/nav/NavLogo';
|
||||
import TitleSide from 'common/components/title-side/TitleSide';
|
||||
import { formatDisplay } from 'common/utils/dateConfig';
|
||||
import { AnimatePresence, motion } from 'framer-motion';
|
||||
import { useAtom } from 'jotai';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import { overrideStylesURL } from '../../../common/api/apiConstants';
|
||||
import { mirrorViewersAtom } from '../../../common/atoms/ViewerSettings';
|
||||
import Paginator from '../../../common/components/paginator/Paginator';
|
||||
import { useRuntimeStylesheet } from '../../../common/hooks/useRuntimeStylesheet';
|
||||
import { getEventsWithDelay } from '../../../common/utils/eventsManager';
|
||||
@@ -26,6 +28,7 @@ export default function Backstage(props) {
|
||||
const [filteredEvents, setFilteredEvents] = useState(null);
|
||||
const [pageNumber, setPageNumber] = useState(0);
|
||||
const [currentPage, setCurrentPage] = useState(0);
|
||||
const [isMirrored] = useAtom(mirrorViewersAtom);
|
||||
|
||||
// Set window title
|
||||
useEffect(() => {
|
||||
@@ -58,7 +61,7 @@ export default function Backstage(props) {
|
||||
const clock = formatTime(time.clock, formatOptions);
|
||||
|
||||
return (
|
||||
<div className='backstage' data-testid="backstage-view">
|
||||
<div className={`backstage ${isMirrored ? 'mirror' : ''}`} data-testid='backstage-view'>
|
||||
<NavLogo />
|
||||
|
||||
<div className='event-title'>{general.title}</div>
|
||||
@@ -159,7 +162,7 @@ Backstage.propTypes = {
|
||||
publ: PropTypes.object,
|
||||
title: PropTypes.object,
|
||||
time: PropTypes.object,
|
||||
backstageEvents: PropTypes.object,
|
||||
backstageEvents: PropTypes.array,
|
||||
selectedId: PropTypes.string,
|
||||
general: PropTypes.object,
|
||||
viewSettings: PropTypes.object,
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import { useEffect } from 'react';
|
||||
import { useSearchParams } from 'react-router-dom';
|
||||
import { useAtom } from 'jotai';
|
||||
|
||||
import { overrideStylesURL } from '../../../common/api/apiConstants';
|
||||
import { mirrorViewersAtom } from '../../../common/atoms/ViewerSettings';
|
||||
import NavLogo from '../../../common/components/nav/NavLogo';
|
||||
import { useRuntimeStylesheet } from '../../../common/hooks/useRuntimeStylesheet';
|
||||
import { TimeManagerType } from '../../../common/models/TimeManaget.type';
|
||||
@@ -25,6 +27,7 @@ export default function Clock(props: ClockProps) {
|
||||
const { time, viewSettings } = props;
|
||||
const { shouldRender } = useRuntimeStylesheet(viewSettings?.overrideStyles && overrideStylesURL);
|
||||
const [searchParams] = useSearchParams();
|
||||
const [isMirrored] = useAtom(mirrorViewersAtom);
|
||||
|
||||
useEffect(() => {
|
||||
document.title = 'ontime - Clock';
|
||||
@@ -125,7 +128,7 @@ export default function Clock(props: ClockProps) {
|
||||
|
||||
return (
|
||||
<div
|
||||
className='clock-view'
|
||||
className={`clock-view ${isMirrored ? 'mirror' : ''}`}
|
||||
style={{
|
||||
backgroundColor: userOptions.keyColour,
|
||||
color: userOptions.textColour,
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { Link, useSearchParams } from 'react-router-dom';
|
||||
import { useAtom } from 'jotai';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import { overrideStylesURL } from '../../../common/api/apiConstants';
|
||||
import { mirrorViewersAtom } from '../../../common/atoms/ViewerSettings';
|
||||
import NavLogo from '../../../common/components/nav/NavLogo';
|
||||
import Empty from '../../../common/components/state/Empty';
|
||||
import { useRuntimeStylesheet } from '../../../common/hooks/useRuntimeStylesheet';
|
||||
@@ -23,6 +25,7 @@ export default function Countdown(props) {
|
||||
const { backstageEvents, time, selectedId, viewSettings } = props;
|
||||
const { shouldRender } = useRuntimeStylesheet(viewSettings?.overrideStyles && overrideStylesURL);
|
||||
const [searchParams] = useSearchParams();
|
||||
const [isMirrored] = useAtom(mirrorViewersAtom);
|
||||
|
||||
const [follow, setFollow] = useState(null);
|
||||
const [runningTimer, setRunningTimer] = useState(0);
|
||||
@@ -90,10 +93,10 @@ export default function Countdown(props) {
|
||||
: formatTime(follow.timeEnd + delay, formatOptions);
|
||||
|
||||
return (
|
||||
<div className='countdown' data-testid="countdown-view">
|
||||
<div className={`countdown ${isMirrored ? 'mirror' : ''}`} data-testid='countdown-view'>
|
||||
<NavLogo />
|
||||
{follow === null ? (
|
||||
<div className='event-select' data-testid="countdown-select">
|
||||
<div className='event-select' data-testid='countdown-select'>
|
||||
<span className='event-select__title'>Select an event to follow</span>
|
||||
<ul className='event-select__events'>
|
||||
{backstageEvents.length === 0 ? (
|
||||
@@ -112,7 +115,7 @@ export default function Countdown(props) {
|
||||
</ul>
|
||||
</div>
|
||||
) : (
|
||||
<div className='countdown-container' data-testid="countdown-event">
|
||||
<div className='countdown-container' data-testid='countdown-event'>
|
||||
<div className='timer-group'>
|
||||
<div className='aux-timers'>
|
||||
<div className='aux-timers__label'>Time Now</div>
|
||||
@@ -139,7 +142,7 @@ export default function Countdown(props) {
|
||||
>
|
||||
{formatDisplay(
|
||||
isSelected ? runningTimer : runningTimer + millisToSeconds(delay),
|
||||
isSelected || time.waiting
|
||||
isSelected || time.waiting,
|
||||
)}
|
||||
</span>
|
||||
<div className='title'>{follow?.title || 'Untitled Event'}</div>
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import { useEffect } from 'react';
|
||||
import { useSearchParams } from 'react-router-dom';
|
||||
import { useAtom } from 'jotai';
|
||||
|
||||
import { overrideStylesURL } from '../../../common/api/apiConstants';
|
||||
import { mirrorViewersAtom } from '../../../common/atoms/ViewerSettings';
|
||||
import NavLogo from '../../../common/components/nav/NavLogo';
|
||||
import { useRuntimeStylesheet } from '../../../common/hooks/useRuntimeStylesheet';
|
||||
import { PresenterMessageType } from '../../../common/models/PresenterMessage.type';
|
||||
@@ -22,6 +24,7 @@ export default function MinimalTimer(props: MinimalTimerProps) {
|
||||
const { pres, time, viewSettings } = props;
|
||||
const { shouldRender } = useRuntimeStylesheet(viewSettings?.overrideStyles && overrideStylesURL);
|
||||
const [searchParams] = useSearchParams();
|
||||
const [isMirrored] = useAtom(mirrorViewersAtom);
|
||||
|
||||
useEffect(() => {
|
||||
document.title = 'ontime - Minimal Timer';
|
||||
@@ -129,12 +132,13 @@ export default function MinimalTimer(props: MinimalTimerProps) {
|
||||
const clean = timer.replace('/:/g', '');
|
||||
const showFinished = time.isNegative && !userOptions?.hideOvertime;
|
||||
|
||||
const baseClasses = `minimal-timer ${isMirrored ? 'mirror' : ''}`;
|
||||
|
||||
return (
|
||||
<div
|
||||
className={showFinished ? 'minimal-timer minimal-timer--finished' : 'minimal-timer'}
|
||||
className={showFinished ? `${baseClasses} minimal-timer--finished` : baseClasses}
|
||||
style={{
|
||||
backgroundColor: userOptions.keyColour,
|
||||
color: userOptions.textColour,
|
||||
justifyContent: userOptions.justifyContent,
|
||||
alignItems: userOptions.alignItems,
|
||||
}}
|
||||
@@ -153,6 +157,7 @@ export default function MinimalTimer(props: MinimalTimerProps) {
|
||||
showFinished ? 'timer--finished' : ''
|
||||
}`}
|
||||
style={{
|
||||
color: userOptions.textColour,
|
||||
fontSize: `${(89 / (clean.length - 1)) * (userOptions.size || 1)}vw`,
|
||||
fontFamily: userOptions.font,
|
||||
top: userOptions.top,
|
||||
|
||||
@@ -4,9 +4,11 @@ import { ReactComponent as Emptyimage } from 'assets/images/empty.svg';
|
||||
import NavLogo from 'common/components/nav/NavLogo';
|
||||
import { formatDisplay } from 'common/utils/dateConfig';
|
||||
import { AnimatePresence, motion } from 'framer-motion';
|
||||
import { useAtom } from 'jotai';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import { overrideStylesURL } from '../../../common/api/apiConstants';
|
||||
import { mirrorViewersAtom } from '../../../common/atoms/ViewerSettings';
|
||||
import Paginator from '../../../common/components/paginator/Paginator';
|
||||
import { useRuntimeStylesheet } from '../../../common/hooks/useRuntimeStylesheet';
|
||||
import { formatTime } from '../../../common/utils/time';
|
||||
@@ -25,6 +27,7 @@ export default function Pip(props) {
|
||||
const [filteredEvents, setFilteredEvents] = useState(null);
|
||||
const [pageNumber, setPageNumber] = useState(0);
|
||||
const [currentPage, setCurrentPage] = useState(0);
|
||||
const [isMirrored] = useAtom(mirrorViewersAtom);
|
||||
|
||||
// Set window title
|
||||
useEffect(() => {
|
||||
@@ -65,7 +68,7 @@ export default function Pip(props) {
|
||||
const clock = formatTime(time.clock, formatOptions);
|
||||
|
||||
return (
|
||||
<div className='pip' data-testid="pip-view">
|
||||
<div className={`pip ${isMirrored ? 'mirror' : ''}`} data-testid='pip-view'>
|
||||
<NavLogo />
|
||||
|
||||
<div className='event-title'>{general.title}</div>
|
||||
|
||||
@@ -3,9 +3,11 @@ import QRCode from 'react-qr-code';
|
||||
import NavLogo from 'common/components/nav/NavLogo';
|
||||
import TitleSide from 'common/components/title-side/TitleSide';
|
||||
import { AnimatePresence, motion } from 'framer-motion';
|
||||
import { useAtom } from 'jotai';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import { overrideStylesURL } from '../../../common/api/apiConstants';
|
||||
import { mirrorViewersAtom } from '../../../common/atoms/ViewerSettings';
|
||||
import Paginator from '../../../common/components/paginator/Paginator';
|
||||
import { useRuntimeStylesheet } from '../../../common/hooks/useRuntimeStylesheet';
|
||||
import { formatTime } from '../../../common/utils/time';
|
||||
@@ -23,6 +25,7 @@ export default function Public(props) {
|
||||
const { shouldRender } = useRuntimeStylesheet(viewSettings?.overrideStyles && overrideStylesURL);
|
||||
const [pageNumber, setPageNumber] = useState(0);
|
||||
const [currentPage, setCurrentPage] = useState(0);
|
||||
const [isMirrored] = useAtom(mirrorViewersAtom);
|
||||
|
||||
useEffect(() => {
|
||||
document.title = 'ontime - Public Screen';
|
||||
@@ -38,7 +41,7 @@ export default function Public(props) {
|
||||
const clock = formatTime(time.clock, formatOptions);
|
||||
|
||||
return (
|
||||
<div className='public-screen' data-testid="public-view">
|
||||
<div className={`public-screen ${isMirrored ? 'mirror' : ''}`} data-testid='public-view'>
|
||||
<NavLogo />
|
||||
|
||||
<div className='event-title'>{general.title}</div>
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useAtom } from 'jotai';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import { overrideStylesURL } from '../../../common/api/apiConstants';
|
||||
import { mirrorViewersAtom } from '../../../common/atoms/ViewerSettings';
|
||||
import NavLogo from '../../../common/components/nav/NavLogo';
|
||||
import useFitText from '../../../common/hooks/useFitText';
|
||||
import { useRuntimeStylesheet } from '../../../common/hooks/useRuntimeStylesheet';
|
||||
@@ -28,6 +30,7 @@ export default function StudioClock(props) {
|
||||
const { fontSize: titleFontSize, ref: titleRef } = useFitText({ maxFontSize: 500 });
|
||||
|
||||
const [schedule, setSchedule] = useState([]);
|
||||
const [isMirrored] = useAtom(mirrorViewersAtom);
|
||||
|
||||
const activeIndicators = [...Array(12).keys()];
|
||||
const secondsIndicators = [...Array(60).keys()];
|
||||
@@ -48,13 +51,13 @@ export default function StudioClock(props) {
|
||||
showEnd: false,
|
||||
});
|
||||
setSchedule(formatted);
|
||||
}, [backstageEvents, nextId, selectedId] );
|
||||
}, [backstageEvents, nextId, selectedId]);
|
||||
|
||||
const clock = formatTime(time.clock, formatOptions);
|
||||
const [, , secondsNow] = stringFromMillis(time.clock).split(':');
|
||||
|
||||
return (
|
||||
<div className='studio-clock' data-testid="studio-view">
|
||||
<div className={`studio-clock ${isMirrored ? 'mirror' : ''}`} data-testid='studio-view'>
|
||||
<NavLogo />
|
||||
<div className='clock-container'>
|
||||
<div className='studio-timer'>{clock}</div>
|
||||
@@ -65,7 +68,8 @@ export default function StudioClock(props) {
|
||||
>
|
||||
{title.titleNext}
|
||||
</div>
|
||||
<div className={time.isNegative ? 'next-countdown' : 'next-countdown next-countdown--overtime'}>
|
||||
<div
|
||||
className={time.isNegative ? 'next-countdown' : 'next-countdown next-countdown--overtime'}>
|
||||
{selectedId != null && formatDisplay(time.running)}
|
||||
</div>
|
||||
<div className='clock-indicators'>
|
||||
|
||||
@@ -5,9 +5,11 @@ import MyProgressBar from 'common/components/myProgressBar/MyProgressBar';
|
||||
import NavLogo from 'common/components/nav/NavLogo';
|
||||
import TitleCard from 'common/components/title-card/TitleCard';
|
||||
import { AnimatePresence, motion } from 'framer-motion';
|
||||
import { useAtom } from 'jotai';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import { overrideStylesURL } from '../../../common/api/apiConstants';
|
||||
import { mirrorViewersAtom } from '../../../common/atoms/ViewerSettings';
|
||||
import { useRuntimeStylesheet } from '../../../common/hooks/useRuntimeStylesheet';
|
||||
import { formatTime } from '../../../common/utils/time';
|
||||
|
||||
@@ -23,6 +25,7 @@ export default function Timer(props) {
|
||||
const { shouldRender } = useRuntimeStylesheet(viewSettings?.overrideStyles && overrideStylesURL);
|
||||
const [elapsed, setElapsed] = useState(true);
|
||||
const [searchParams] = useSearchParams();
|
||||
const [isMirrored] = useAtom(mirrorViewersAtom);
|
||||
|
||||
useEffect(() => {
|
||||
document.title = 'ontime - Timer';
|
||||
@@ -64,12 +67,11 @@ export default function Timer(props) {
|
||||
y: 500,
|
||||
},
|
||||
};
|
||||
const baseClasses = `stage-timer ${isMirrored ? 'mirror' : ''}`;
|
||||
|
||||
return (
|
||||
<div
|
||||
className={time.finished ? 'stage-timer stage-timer--finished' : 'stage-timer'}
|
||||
data-testid="timer-view"
|
||||
>
|
||||
<div className={time.finished ? `${baseClasses} stage-timer--finished` : baseClasses}
|
||||
data-testid='timer-view'>
|
||||
<div className={showOverlay ? 'message-overlay message-overlay--active' : 'message-overlay'}>
|
||||
<div className='message'>{pres.text}</div>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
.mirror {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
@use 'main' as *;
|
||||
@use 'viewerCommon' as *;
|
||||
|
||||
// General styling
|
||||
$accent-color: $ontime-pink; // --accent-color-override
|
||||
|
||||
Reference in New Issue
Block a user