mirror of
https://github.com/cpvalente/ontime.git
synced 2026-08-09 09:23:51 +00:00
Feat/mac (#109)
* chore: upgrade dependencies * feat/mac: draft pipeline * feat/mac: use public folder for transient files * feat/mac: set app fullscreen * feat/mac: cmd + , toggles menu * feat/mac: update readme * refact: easier login process * refact prevent style issues with safari * refact reduce css download * style: cleanup paginator design * style: studio clock is responsive * style: fix spacing style issues with safari
This commit is contained in:
@@ -1,105 +0,0 @@
|
||||
import React, { memo } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import style from './PlaybackControl.module.scss';
|
||||
import StartIconBtn from 'common/components/buttons/StartIconBtn';
|
||||
import PauseIconBtn from 'common/components/buttons/PauseIconBtn';
|
||||
import PrevIconBtn from 'common/components/buttons/PrevIconBtn';
|
||||
import NextIconBtn from 'common/components/buttons/NextIconBtn';
|
||||
import RollIconBtn from 'common/components/buttons/RollIconBtn';
|
||||
import UnloadIconBtn from 'common/components/buttons/UnloadIconBtn';
|
||||
import ReloadIconButton from 'common/components/buttons/ReloadIconBtn';
|
||||
|
||||
const areEqual = (prevProps, nextProps) => {
|
||||
return (
|
||||
prevProps.playback === nextProps.playback
|
||||
&& prevProps.selectedId === nextProps.selectedId
|
||||
&& prevProps.noEvents === nextProps.noEvents
|
||||
);
|
||||
};
|
||||
|
||||
const Playback = (props) => {
|
||||
const { playback, selectedId, playbackControl, noEvents } = props;
|
||||
const isRolling = playback === 'roll';
|
||||
|
||||
return (
|
||||
<div className={style.playbackContainer}>
|
||||
<StartIconBtn
|
||||
active={playback === 'start'}
|
||||
clickhandler={() => playbackControl('start')}
|
||||
disabled={!selectedId || isRolling || noEvents}
|
||||
/>
|
||||
<PauseIconBtn
|
||||
active={playback === 'pause'}
|
||||
clickhandler={() => playbackControl('pause')}
|
||||
disabled={!selectedId || isRolling || noEvents || playback !== 'start'}
|
||||
/>
|
||||
<RollIconBtn
|
||||
active={playback === 'roll'}
|
||||
disabled={playback === 'roll' || noEvents}
|
||||
clickhandler={() => playbackControl('roll')}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const Transport = (props) => {
|
||||
const { playback, selectedId, playbackControl, noEvents } = props;
|
||||
const isRolling = playback === 'roll';
|
||||
|
||||
return (
|
||||
<div className={style.playbackContainer}>
|
||||
<PrevIconBtn
|
||||
clickhandler={() => playbackControl('previous')}
|
||||
disabled={isRolling || noEvents}
|
||||
/>
|
||||
<NextIconBtn
|
||||
clickhandler={() => playbackControl('next')}
|
||||
disabled={isRolling || noEvents}
|
||||
/>
|
||||
<ReloadIconButton
|
||||
clickhandler={() => playbackControl('reload')}
|
||||
disabled={selectedId == null || isRolling || noEvents}
|
||||
/>
|
||||
<UnloadIconBtn
|
||||
clickhandler={() => playbackControl('unload')}
|
||||
disabled={(selectedId == null && !isRolling) || noEvents}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const PlaybackButtons = (props) => {
|
||||
const { playback, selectedId, noEvents, playbackControl } = props;
|
||||
return (
|
||||
<>
|
||||
<Playback
|
||||
playback={playback}
|
||||
selectedId={selectedId}
|
||||
noEvents={noEvents}
|
||||
playbackControl={playbackControl}
|
||||
/>
|
||||
<Transport
|
||||
playback={playback}
|
||||
selectedId={selectedId}
|
||||
noEvents={noEvents}
|
||||
playbackControl={playbackControl}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(PlaybackButtons, areEqual);
|
||||
|
||||
PlaybackButtons.propTypes = {
|
||||
playback: PropTypes.string,
|
||||
selectedId: PropTypes.string,
|
||||
playbackControl: PropTypes.func.isRequired,
|
||||
noEvents: PropTypes.bool.isRequired,
|
||||
};
|
||||
|
||||
Transport.propTypes = {
|
||||
playback: PropTypes.string,
|
||||
selectedId: PropTypes.string,
|
||||
playbackControl: PropTypes.func.isRequired,
|
||||
noEvents: PropTypes.bool.isRequired,
|
||||
};
|
||||
@@ -1,140 +0,0 @@
|
||||
import React from 'react';
|
||||
import style from './PlaybackControl.module.scss';
|
||||
import Countdown from 'common/components/countdown/Countdown';
|
||||
import { stringFromMillis } from 'ontime-utils/time';
|
||||
import { Tooltip } from '@chakra-ui/react';
|
||||
import { Button } from '@chakra-ui/button';
|
||||
import { memo } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
const areEqual = (prevProps, nextProps) => {
|
||||
return (
|
||||
prevProps.timer.running === nextProps.timer.running &&
|
||||
prevProps.timer.isNegative === nextProps.timer.isNegative &&
|
||||
prevProps.timer.expectedFinish === nextProps.timer.expectedFinish &&
|
||||
prevProps.timer.startedAt === nextProps.timer.startedAt &&
|
||||
prevProps.playback === nextProps.playback &&
|
||||
prevProps.timer.secondary === nextProps.timer.secondary &&
|
||||
prevProps.selectedId === nextProps.selectedId
|
||||
);
|
||||
};
|
||||
|
||||
const incrementProps = {
|
||||
size: 'sm',
|
||||
width: '2.9em',
|
||||
colorScheme: 'whiteAlpha',
|
||||
variant: 'outline',
|
||||
_focus: { boxShadow: 'none' },
|
||||
};
|
||||
|
||||
const PlaybackTimer = (props) => {
|
||||
const { timer, playback, handleIncrement, selectedId } = props;
|
||||
const started = stringFromMillis(timer.startedAt, true);
|
||||
const finish = stringFromMillis(timer.expectedFinish, true);
|
||||
const isRolling = playback === 'roll';
|
||||
const isWaiting = timer.secondary > 0 && timer.running == null;
|
||||
const disableButtons = selectedId == null || isRolling;
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className={style.timeContainer}>
|
||||
<div className={style.indicators}>
|
||||
<Tooltip label='Roll mode active'>
|
||||
<div className={isRolling ? style.indRollActive : style.indRoll} />
|
||||
</Tooltip>
|
||||
<div
|
||||
className={timer.isNegative ? style.indNegativeActive : style.indNegative}
|
||||
/>
|
||||
<div className={style.indDelay} />
|
||||
</div>
|
||||
<div className={style.timer}>
|
||||
<Countdown
|
||||
time={isWaiting ? timer.secondary : timer.running}
|
||||
isNegative={timer.isNegative}
|
||||
small
|
||||
/>
|
||||
</div>
|
||||
{isWaiting ? (
|
||||
<div className={style.roll}>
|
||||
<span className={style.rolltag}>Roll: Countdown to start</span>
|
||||
<span className={style.time}>FIX</span>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<div className={style.start}>
|
||||
<span className={style.tag}>Started at </span>
|
||||
<span className={style.time}>{started}</span>
|
||||
</div>
|
||||
<div className={style.finish}>
|
||||
<span className={style.tag}>Finish at </span>
|
||||
<span className={style.time}>{finish}</span>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
<div className={style.btn}>
|
||||
<Tooltip
|
||||
label='Remove 1 minute'
|
||||
delay={500}
|
||||
shouldWrapChildren={disableButtons}
|
||||
>
|
||||
<Button
|
||||
{...incrementProps}
|
||||
disabled={disableButtons}
|
||||
onClick={() => handleIncrement(-1)}
|
||||
>
|
||||
-1
|
||||
</Button>
|
||||
</Tooltip>
|
||||
<Tooltip
|
||||
label='Add 1 minute'
|
||||
delay={500}
|
||||
shouldWrapChildren={disableButtons}
|
||||
>
|
||||
<Button
|
||||
{...incrementProps}
|
||||
disabled={disableButtons}
|
||||
onClick={() => handleIncrement(1)}
|
||||
>
|
||||
+1
|
||||
</Button>
|
||||
</Tooltip>
|
||||
<Tooltip
|
||||
label='Remove 5 minutes'
|
||||
delay={500}
|
||||
shouldWrapChildren={disableButtons}
|
||||
>
|
||||
<Button
|
||||
{...incrementProps}
|
||||
disabled={disableButtons}
|
||||
onClick={() => handleIncrement(-5)}
|
||||
>
|
||||
-5
|
||||
</Button>
|
||||
</Tooltip>
|
||||
<Tooltip
|
||||
label='Add 5 minutes'
|
||||
delay={500}
|
||||
shouldWrapChildren={disableButtons}
|
||||
>
|
||||
<Button
|
||||
{...incrementProps}
|
||||
disabled={disableButtons}
|
||||
onClick={() => handleIncrement(5)}
|
||||
>
|
||||
+5
|
||||
</Button>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(PlaybackTimer, areEqual);
|
||||
|
||||
PlaybackTimer.propTypes = {
|
||||
timer: PropTypes.object.isRequired,
|
||||
playback: PropTypes.string,
|
||||
handleIncrement: PropTypes.func.isRequired,
|
||||
selectedId: PropTypes.string,
|
||||
};
|
||||
@@ -1,6 +1,7 @@
|
||||
import React from 'react';
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import SocketProvider from 'app/context/socketContext';
|
||||
import MessageControl from '../MessageControl';
|
||||
import MessageControl from '../message/MessageControl';
|
||||
|
||||
// need to inject the socket provider to make component
|
||||
// render without failing
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import React from 'react';
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import SocketProvider from 'app/context/socketContext';
|
||||
import PlaybackControl from '../PlaybackControl';
|
||||
import PlaybackControl from '../playback/PlaybackControl';
|
||||
|
||||
test('check that playback control renders', async () => {
|
||||
// need to inject the socket provider to make component
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
import React from 'react';
|
||||
import { Editable, EditableInput, EditablePreview } from '@chakra-ui/editable';
|
||||
import VisibleIconBtn from '../../../common/components/buttons/VisibleIconBtn';
|
||||
import style from './MessageControl.module.scss';
|
||||
|
||||
const inputProps = {
|
||||
size: 'sm',
|
||||
};
|
||||
|
||||
export default function InputRow(props) {
|
||||
const { label, placeholder, text, visible, actionHandler, changeHandler } = props;
|
||||
|
||||
return (
|
||||
<>
|
||||
<span className={style.label}>{label}</span>
|
||||
<div className={style.inputItems}>
|
||||
<Editable
|
||||
onChange={(event) => changeHandler(event)}
|
||||
value={text}
|
||||
placeholder={placeholder}
|
||||
className={style.inline}
|
||||
color={text === '' ? '#666' : 'inherit'}
|
||||
>
|
||||
<EditablePreview className={style.padleft} />
|
||||
<EditableInput className={style.padleft} />
|
||||
</Editable>
|
||||
<VisibleIconBtn
|
||||
active={visible || undefined}
|
||||
actionHandler={actionHandler}
|
||||
{...inputProps}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
+2
-34
@@ -1,41 +1,9 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { Editable, EditableInput, EditablePreview } from '@chakra-ui/editable';
|
||||
import { useSocket } from 'app/context/socketContext';
|
||||
import VisibleIconBtn from 'common/components/buttons/VisibleIconBtn';
|
||||
import OnAirIconBtn from '../../common/components/buttons/OnAirIconBtn';
|
||||
import InputRow from './InputRow';
|
||||
import OnAirIconBtn from '../../../common/components/buttons/OnAirIconBtn';
|
||||
import style from './MessageControl.module.scss';
|
||||
|
||||
const inputProps = {
|
||||
size: 'sm',
|
||||
};
|
||||
|
||||
const InputRow = (props) => {
|
||||
const { label, placeholder, text, visible, actionHandler, changeHandler } = props;
|
||||
|
||||
return (
|
||||
<>
|
||||
<span className={style.label}>{label}</span>
|
||||
<div className={style.inputItems}>
|
||||
<Editable
|
||||
onChange={(event) => changeHandler(event)}
|
||||
value={text}
|
||||
placeholder={placeholder}
|
||||
className={style.inline}
|
||||
color={text === '' ? '#666' : 'inherit'}
|
||||
>
|
||||
<EditablePreview className={style.padleft} />
|
||||
<EditableInput className={style.padleft} />
|
||||
</Editable>
|
||||
<VisibleIconBtn
|
||||
active={visible || undefined}
|
||||
actionHandler={actionHandler}
|
||||
{...inputProps}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default function MessageControl() {
|
||||
const socket = useSocket();
|
||||
const [pres, setPres] = useState({
|
||||
+5
-5
@@ -1,5 +1,5 @@
|
||||
@use '../../styles/main' as *;
|
||||
@use '../../styles/mixins' as *;
|
||||
@use '../../../styles/main' as *;
|
||||
@use '../../../styles/mixins' as *;
|
||||
|
||||
.messageContainer,
|
||||
.onAirToggle {
|
||||
@@ -22,13 +22,13 @@
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
font-size: 0.9em;
|
||||
color: #ccc;
|
||||
color: $label-gray;
|
||||
}
|
||||
|
||||
.inline {
|
||||
border-radius: 4px;
|
||||
background-color: rgba(255, 255, 255, 0.05);
|
||||
border: 1px solid rgba(255, 255, 255, 0.05);
|
||||
background-color: $input-bg;
|
||||
border: $input-border;
|
||||
}
|
||||
|
||||
.padleft {
|
||||
@@ -0,0 +1,38 @@
|
||||
import React from 'react';
|
||||
import StartIconBtn from '../../../common/components/buttons/StartIconBtn';
|
||||
import PauseIconBtn from '../../../common/components/buttons/PauseIconBtn';
|
||||
import RollIconBtn from '../../../common/components/buttons/RollIconBtn';
|
||||
import PropTypes from 'prop-types';
|
||||
import style from './PlaybackControl.module.scss';
|
||||
|
||||
export default function Playback(props) {
|
||||
const { playback, selectedId, playbackControl, noEvents } = props;
|
||||
const isRolling = playback === 'roll';
|
||||
|
||||
return (
|
||||
<div className={style.playbackContainer}>
|
||||
<StartIconBtn
|
||||
active={playback === 'start'}
|
||||
clickhandler={() => playbackControl('start')}
|
||||
disabled={!selectedId || isRolling || noEvents}
|
||||
/>
|
||||
<PauseIconBtn
|
||||
active={playback === 'pause'}
|
||||
clickhandler={() => playbackControl('pause')}
|
||||
disabled={!selectedId || isRolling || noEvents || playback !== 'start'}
|
||||
/>
|
||||
<RollIconBtn
|
||||
active={playback === 'roll'}
|
||||
disabled={playback === 'roll' || noEvents}
|
||||
clickhandler={() => playbackControl('roll')}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Playback.propTypes = {
|
||||
playback: PropTypes.string,
|
||||
selectedId: PropTypes.string,
|
||||
playbackControl: PropTypes.func.isRequired,
|
||||
noEvents: PropTypes.bool.isRequired,
|
||||
};
|
||||
@@ -0,0 +1,41 @@
|
||||
import React, { memo } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import Transport from './Transport';
|
||||
import Playback from './Playback';
|
||||
|
||||
const areEqual = (prevProps, nextProps) => {
|
||||
return (
|
||||
prevProps.playback === nextProps.playback &&
|
||||
prevProps.selectedId === nextProps.selectedId &&
|
||||
prevProps.noEvents === nextProps.noEvents
|
||||
);
|
||||
};
|
||||
|
||||
const PlaybackButtons = (props) => {
|
||||
const { playback, selectedId, noEvents, playbackControl } = props;
|
||||
return (
|
||||
<>
|
||||
<Playback
|
||||
playback={playback}
|
||||
selectedId={selectedId}
|
||||
noEvents={noEvents}
|
||||
playbackControl={playbackControl}
|
||||
/>
|
||||
<Transport
|
||||
playback={playback}
|
||||
selectedId={selectedId}
|
||||
noEvents={noEvents}
|
||||
playbackControl={playbackControl}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(PlaybackButtons, areEqual);
|
||||
|
||||
PlaybackButtons.propTypes = {
|
||||
playback: PropTypes.string,
|
||||
selectedId: PropTypes.string,
|
||||
playbackControl: PropTypes.func.isRequired,
|
||||
noEvents: PropTypes.bool.isRequired,
|
||||
};
|
||||
+1
-1
@@ -63,7 +63,7 @@ export default function PlaybackControl() {
|
||||
};
|
||||
}, [socket]);
|
||||
|
||||
const playbackControl = async (action, payload) => {
|
||||
const playbackControl = async (action) => {
|
||||
switch (action) {
|
||||
case 'start':
|
||||
socket.emit('set-playstate', 'start');
|
||||
+12
-12
@@ -1,16 +1,16 @@
|
||||
@use '../../../styles/main' as *;
|
||||
@use '../../../styles/mixins' as *;
|
||||
|
||||
.mainContainer {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
display: grid;
|
||||
margin: 0 auto;
|
||||
flex-direction: column;
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
.timeContainer,
|
||||
.playbackContainer {
|
||||
background-color: rgba(0, 0, 0, 0.05);
|
||||
border: 1px solid rgba(0, 0, 0, 0.05);
|
||||
border-radius: 4px;
|
||||
@include second-container;
|
||||
padding: 0.5em;
|
||||
}
|
||||
|
||||
@@ -43,7 +43,7 @@
|
||||
.indRoll,
|
||||
.indDelay,
|
||||
.indNegative {
|
||||
background-color: rgba(0, 0, 0, 0.05);
|
||||
background-color: $bg-black-300;
|
||||
}
|
||||
|
||||
.indRoll,
|
||||
@@ -56,7 +56,7 @@
|
||||
}
|
||||
|
||||
.indRollActive {
|
||||
background-color: #2b6cb0;
|
||||
background-color: $ontime-roll;
|
||||
}
|
||||
|
||||
.indNegative,
|
||||
@@ -67,11 +67,11 @@
|
||||
}
|
||||
|
||||
.indNegativeActive {
|
||||
background-color: #ff7597;
|
||||
background-color: $ontime-pink;
|
||||
}
|
||||
|
||||
.indDelayActive {
|
||||
background-color: #dd6b20;
|
||||
background-color: $ontime-delay;
|
||||
}
|
||||
|
||||
.btn {
|
||||
@@ -102,17 +102,17 @@
|
||||
}
|
||||
|
||||
.time {
|
||||
color: #ccc;
|
||||
color: $header-gray;
|
||||
font-size: 1.1em;
|
||||
}
|
||||
|
||||
.tag {
|
||||
color: #aaa;
|
||||
color: $label-gray;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
.rolltag {
|
||||
color: #2b6cb0;
|
||||
color: $ontime-roll;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,103 @@
|
||||
import React, { memo } from 'react';
|
||||
import Countdown from 'common/components/countdown/Countdown';
|
||||
import { Tooltip } from '@chakra-ui/react';
|
||||
import { Button } from '@chakra-ui/button';
|
||||
import PropTypes from 'prop-types';
|
||||
import { stringFromMillis } from '../../../common/utils/time';
|
||||
import style from './PlaybackControl.module.scss';
|
||||
|
||||
const areEqual = (prevProps, nextProps) => {
|
||||
return (
|
||||
prevProps.timer.running === nextProps.timer.running &&
|
||||
prevProps.timer.isNegative === nextProps.timer.isNegative &&
|
||||
prevProps.timer.expectedFinish === nextProps.timer.expectedFinish &&
|
||||
prevProps.timer.startedAt === nextProps.timer.startedAt &&
|
||||
prevProps.playback === nextProps.playback &&
|
||||
prevProps.timer.secondary === nextProps.timer.secondary &&
|
||||
prevProps.selectedId === nextProps.selectedId
|
||||
);
|
||||
};
|
||||
|
||||
const incrementProps = {
|
||||
size: 'sm',
|
||||
width: '2.9em',
|
||||
colorScheme: 'whiteAlpha',
|
||||
variant: 'outline',
|
||||
_focus: { boxShadow: 'none' },
|
||||
};
|
||||
|
||||
const PlaybackTimer = (props) => {
|
||||
const { timer, playback, handleIncrement, selectedId } = props;
|
||||
const started = stringFromMillis(timer.startedAt, true);
|
||||
const finish = stringFromMillis(timer.expectedFinish, true);
|
||||
const isRolling = playback === 'roll';
|
||||
const isWaiting = timer.secondary > 0 && timer.running == null;
|
||||
const disableButtons = selectedId == null || isRolling;
|
||||
|
||||
return (
|
||||
<div className={style.timeContainer}>
|
||||
<div className={style.indicators}>
|
||||
<Tooltip label='Roll mode active'>
|
||||
<div className={isRolling ? style.indRollActive : style.indRoll} />
|
||||
</Tooltip>
|
||||
<div className={timer.isNegative ? style.indNegativeActive : style.indNegative} />
|
||||
<div className={style.indDelay} />
|
||||
</div>
|
||||
<div className={style.timer}>
|
||||
<Countdown
|
||||
time={isWaiting ? timer.secondary : timer.running}
|
||||
isNegative={timer.isNegative}
|
||||
small
|
||||
/>
|
||||
</div>
|
||||
{isWaiting ? (
|
||||
<div className={style.roll}>
|
||||
<span className={style.rolltag}>Roll: Countdown to start</span>
|
||||
<span className={style.time}>FIX</span>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<div className={style.start}>
|
||||
<span className={style.tag}>Started at </span>
|
||||
<span className={style.time}>{started}</span>
|
||||
</div>
|
||||
<div className={style.finish}>
|
||||
<span className={style.tag}>Finish at </span>
|
||||
<span className={style.time}>{finish}</span>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
<div className={style.btn}>
|
||||
<Tooltip label='Remove 1 minute' delay={500} shouldWrapChildren={disableButtons}>
|
||||
<Button {...incrementProps} disabled={disableButtons} onClick={() => handleIncrement(-1)}>
|
||||
-1
|
||||
</Button>
|
||||
</Tooltip>
|
||||
<Tooltip label='Add 1 minute' delay={500} shouldWrapChildren={disableButtons}>
|
||||
<Button {...incrementProps} disabled={disableButtons} onClick={() => handleIncrement(1)}>
|
||||
+1
|
||||
</Button>
|
||||
</Tooltip>
|
||||
<Tooltip label='Remove 5 minutes' delay={500} shouldWrapChildren={disableButtons}>
|
||||
<Button {...incrementProps} disabled={disableButtons} onClick={() => handleIncrement(-5)}>
|
||||
-5
|
||||
</Button>
|
||||
</Tooltip>
|
||||
<Tooltip label='Add 5 minutes' delay={500} shouldWrapChildren={disableButtons}>
|
||||
<Button {...incrementProps} disabled={disableButtons} onClick={() => handleIncrement(5)}>
|
||||
+5
|
||||
</Button>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(PlaybackTimer, areEqual);
|
||||
|
||||
PlaybackTimer.propTypes = {
|
||||
timer: PropTypes.object.isRequired,
|
||||
playback: PropTypes.string,
|
||||
handleIncrement: PropTypes.func.isRequired,
|
||||
selectedId: PropTypes.string,
|
||||
};
|
||||
@@ -0,0 +1,40 @@
|
||||
import React from 'react';
|
||||
import PrevIconBtn from '../../../common/components/buttons/PrevIconBtn';
|
||||
import NextIconBtn from '../../../common/components/buttons/NextIconBtn';
|
||||
import ReloadIconButton from '../../../common/components/buttons/ReloadIconBtn';
|
||||
import UnloadIconBtn from '../../../common/components/buttons/UnloadIconBtn';
|
||||
import PropTypes from 'prop-types';
|
||||
import style from './PlaybackControl.module.scss';
|
||||
|
||||
export default function Transport(props) {
|
||||
const { playback, selectedId, playbackControl, noEvents } = props;
|
||||
const isRolling = playback === 'roll';
|
||||
|
||||
return (
|
||||
<div className={style.playbackContainer}>
|
||||
<PrevIconBtn
|
||||
clickhandler={() => playbackControl('previous')}
|
||||
disabled={isRolling || noEvents}
|
||||
/>
|
||||
<NextIconBtn
|
||||
clickhandler={() => playbackControl('next')}
|
||||
disabled={isRolling || noEvents}
|
||||
/>
|
||||
<ReloadIconButton
|
||||
clickhandler={() => playbackControl('reload')}
|
||||
disabled={selectedId == null || isRolling || noEvents}
|
||||
/>
|
||||
<UnloadIconBtn
|
||||
clickhandler={() => playbackControl('unload')}
|
||||
disabled={(selectedId == null && !isRolling) || noEvents}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
Transport.propTypes = {
|
||||
playback: PropTypes.string,
|
||||
selectedId: PropTypes.string,
|
||||
playbackControl: PropTypes.func.isRequired,
|
||||
noEvents: PropTypes.bool.isRequired,
|
||||
};
|
||||
@@ -1,4 +1,5 @@
|
||||
import React from 'react';
|
||||
import { HStack } from '@chakra-ui/react';
|
||||
import { Draggable } from 'react-beautiful-dnd';
|
||||
import { FiMoreVertical } from '@react-icons/all-files/fi/FiMoreVertical';
|
||||
import DeleteIconBtn from 'common/components/buttons/DeleteIconBtn';
|
||||
@@ -20,10 +21,10 @@ export default function BlockBlock(props) {
|
||||
<span className={style.drag} {...provided.dragHandleProps}>
|
||||
<FiMoreVertical />
|
||||
</span>
|
||||
<div className={style.actionOverlay}>
|
||||
<HStack spacing='0.5em' className={style.actionOverlay}>
|
||||
<DeleteIconBtn actionHandler={actionHandler} />
|
||||
<ActionButtons showAdd showDelay actionHandler={actionHandler} />
|
||||
</div>
|
||||
</HStack>
|
||||
</div>
|
||||
)}
|
||||
</Draggable>
|
||||
|
||||
@@ -41,7 +41,6 @@
|
||||
align-content: center;
|
||||
align-self: flex-start;
|
||||
padding-top: 0.2em;
|
||||
gap: 0.5em;
|
||||
opacity: 0.8;
|
||||
transition: linear 0.1s;
|
||||
justify-self: end;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import React from 'react';
|
||||
import { HStack } from '@chakra-ui/react';
|
||||
import { Draggable } from 'react-beautiful-dnd';
|
||||
import { FiMoreVertical } from '@react-icons/all-files/fi/FiMoreVertical';
|
||||
import { millisToMinutes } from 'common/utils/dateConfig';
|
||||
@@ -16,7 +17,7 @@ export default function DelayBlock(props) {
|
||||
eventsHandler('applyDelay', { id: data.id, duration: data.duration });
|
||||
};
|
||||
|
||||
let delayValue = data.duration != null ? millisToMinutes(data.duration) : undefined;
|
||||
const delayValue = data.duration != null ? millisToMinutes(data.duration) : undefined;
|
||||
return (
|
||||
<Draggable key={data.id} draggableId={data.id} index={index}>
|
||||
{(provided) => (
|
||||
@@ -25,11 +26,11 @@ export default function DelayBlock(props) {
|
||||
<FiMoreVertical />
|
||||
</span>
|
||||
<DelayInput className={style.input} value={delayValue} actionHandler={actionHandler} />
|
||||
<div className={style.actionOverlay}>
|
||||
<HStack spacing='0.5em' className={style.actionOverlay}>
|
||||
<ApplyIconBtn clickhandler={applyDelayHandler} />
|
||||
<DeleteIconBtn actionHandler={actionHandler} />
|
||||
<ActionButtons showAdd actionHandler={actionHandler} />
|
||||
</div>
|
||||
</HStack>
|
||||
</div>
|
||||
)}
|
||||
</Draggable>
|
||||
|
||||
@@ -52,7 +52,6 @@
|
||||
align-content: center;
|
||||
align-self: flex-start;
|
||||
padding-top: 0.2em;
|
||||
gap: 0.5em;
|
||||
opacity: 0.8;
|
||||
transition: linear 0.1s;
|
||||
}
|
||||
|
||||
@@ -11,8 +11,8 @@ import { CollapseProvider } from '../../app/context/CollapseContext';
|
||||
import styles from './Editor.module.scss';
|
||||
|
||||
const EventListWrapper = lazy(() => import('features/editors/list/EventListWrapper'));
|
||||
const PlaybackControl = lazy(() => import('features/control/PlaybackControl'));
|
||||
const MessageControl = lazy(() => import('features/control/MessageControl'));
|
||||
const PlaybackControl = lazy(() => import('features/control/playback/PlaybackControl'));
|
||||
const MessageControl = lazy(() => import('features/control/message/MessageControl'));
|
||||
const Info = lazy(() => import('features/info/Info'));
|
||||
|
||||
export default function Editor() {
|
||||
@@ -35,7 +35,7 @@ export default function Editor() {
|
||||
<CollapseProvider>
|
||||
<Box id='settings' className={styles.settings}>
|
||||
<ErrorBoundary>
|
||||
<MenuBar onOpen={onOpen} isOpen={isOpen} />
|
||||
<MenuBar onOpen={onOpen} isOpen={isOpen} onClose={onClose} />
|
||||
</ErrorBoundary>
|
||||
</Box>
|
||||
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
@use '../../styles/main' as *;
|
||||
|
||||
.mainContainer {
|
||||
background: linear-gradient(90deg, #202020 0%, #121212 100%);
|
||||
background: $bg-black;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
margin: auto;
|
||||
color: #fffd;
|
||||
color: $title-white;
|
||||
padding: max(16px, 2vh);
|
||||
|
||||
display: grid;
|
||||
@@ -94,14 +96,14 @@
|
||||
|
||||
h1 {
|
||||
font-size: max(1.5em, 16px);
|
||||
color: rgba(255, 255, 255, 0.63);
|
||||
color: $bg-gray-100;
|
||||
padding-bottom: 0.25em;
|
||||
}
|
||||
|
||||
.mainContainer > div {
|
||||
border-radius: 0.5em;
|
||||
height: 100%;
|
||||
background-color: rgba(255, 255, 255, 0.13);
|
||||
background-color: $bg-gray-1000;
|
||||
padding: 0.8em 1.5em;
|
||||
|
||||
display: flex;
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
import React from 'react';
|
||||
import { HStack } from '@chakra-ui/react';
|
||||
import { FiMoreVertical } from '@react-icons/all-files/fi/FiMoreVertical';
|
||||
import EventTimes from '../../../common/components/eventTimes/EventTimes';
|
||||
import EditableText from '../../../common/input/EditableText';
|
||||
import PublicIconBtn from '../../../common/components/buttons/PublicIconBtn';
|
||||
import ActionButtons from '../list/ActionButtons';
|
||||
import PropTypes from 'prop-types';
|
||||
import style from './EventBlock.module.scss';
|
||||
|
||||
export default function CollapsedBlock (props) {
|
||||
const { provided, data, next, delay, delayValue, previousEnd, actionHandler } = props;
|
||||
|
||||
return (
|
||||
<>
|
||||
<span className={style.drag} {...provided.dragHandleProps}>
|
||||
<FiMoreVertical />
|
||||
</span>
|
||||
|
||||
<div className={style.indicators}>
|
||||
<span className={next ? style.next : style.nextDisabled}>Next</span>
|
||||
{delayValue != null && <span className={style.delayValue}>{delayValue}</span>}
|
||||
</div>
|
||||
<EventTimes
|
||||
actionHandler={actionHandler}
|
||||
timeStart={data.timeStart}
|
||||
timeEnd={data.timeEnd}
|
||||
delay={delay}
|
||||
previousEnd={previousEnd}
|
||||
className={style.time}
|
||||
/>
|
||||
<div className={style.titleContainer}>
|
||||
<EditableText
|
||||
label='Title'
|
||||
defaultValue={data.title}
|
||||
placeholder='Add Title'
|
||||
submitHandler={(v) => actionHandler('update', { field: 'title', value: v })}
|
||||
/>
|
||||
</div>
|
||||
<HStack spacing='0.5em' className={style.actionOverlay}>
|
||||
<PublicIconBtn actionHandler={actionHandler} active={data.isPublic} />
|
||||
<ActionButtons showAdd showDelay showBlock actionHandler={actionHandler} />
|
||||
</HStack>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
CollapsedBlock.propTypes = {
|
||||
provided: PropTypes.any.isRequired,
|
||||
data: PropTypes.object.isRequired,
|
||||
next: PropTypes.bool.isRequired,
|
||||
delay: PropTypes.any,
|
||||
delayValue: PropTypes.string,
|
||||
previousEnd: PropTypes.number.isRequired,
|
||||
actionHandler: PropTypes.func.isRequired,
|
||||
};
|
||||
@@ -1,143 +1,13 @@
|
||||
import React, { useContext, useMemo } from 'react';
|
||||
import Icon from '@chakra-ui/icon';
|
||||
import { FiChevronUp } from '@react-icons/all-files/fi/FiChevronUp';
|
||||
import { FiMoreVertical } from '@react-icons/all-files/fi/FiMoreVertical';
|
||||
import { Draggable } from 'react-beautiful-dnd';
|
||||
import EventTimes from 'common/components/eventTimes/EventTimes';
|
||||
import EventTimesVertical from 'common/components/eventTimes/EventTimesVertical';
|
||||
import EditableText from 'common/input/EditableText';
|
||||
import ActionButtons from '../list/ActionButtons';
|
||||
import PublicIconBtn from 'common/components/buttons/PublicIconBtn';
|
||||
import DeleteIconBtn from 'common/components/buttons/DeleteIconBtn';
|
||||
import { millisToMinutes } from 'common/utils/dateConfig';
|
||||
import PropTypes from 'prop-types';
|
||||
import { CollapseContext } from '../../../app/context/CollapseContext';
|
||||
import style from './EventBlock.module.css';
|
||||
|
||||
const ExpandedBlock = (props) => {
|
||||
const { provided, data, eventIndex, next, delay, delayValue, previousEnd, actionHandler } = props;
|
||||
|
||||
const oscid = data?.id || '...';
|
||||
|
||||
return (
|
||||
<>
|
||||
<span className={style.drag} {...provided.dragHandleProps}>
|
||||
<FiMoreVertical />
|
||||
</span>
|
||||
|
||||
<div className={style.indicators}>
|
||||
<span className={next ? style.next : style.nextDisabled}>Next</span>
|
||||
{delayValue != null && <span className={style.delayValue}>{delayValue}</span>}
|
||||
</div>
|
||||
<div className={style.timeExpanded}>
|
||||
<EventTimesVertical
|
||||
actionHandler={actionHandler}
|
||||
timeStart={data.timeStart}
|
||||
timeEnd={data.timeEnd}
|
||||
duration={data.duration}
|
||||
delay={delay}
|
||||
previousEnd={previousEnd}
|
||||
className={style.time}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className={style.titleContainer}>
|
||||
<EditableText
|
||||
label='Title'
|
||||
defaultValue={data.title}
|
||||
placeholder='Add Title'
|
||||
submitHandler={(v) => actionHandler('update', { field: 'title', value: v })}
|
||||
/>
|
||||
<EditableText
|
||||
label='Presenter'
|
||||
defaultValue={data.presenter}
|
||||
placeholder='Add Presenter name'
|
||||
submitHandler={(v) => actionHandler('update', { field: 'presenter', value: v })}
|
||||
/>
|
||||
<EditableText
|
||||
label='Subtitle'
|
||||
defaultValue={data.subtitle}
|
||||
placeholder='Add Subtitle'
|
||||
submitHandler={(v) => actionHandler('update', { field: 'subtitle', value: v })}
|
||||
/>
|
||||
<EditableText
|
||||
label='Note'
|
||||
defaultValue={data.note}
|
||||
placeholder='Add Note'
|
||||
style={{ color: '#d69e2e' }}
|
||||
maxchar={160}
|
||||
submitHandler={(v) => actionHandler('update', { field: 'note', value: v })}
|
||||
/>
|
||||
<span className={style.oscLabel}>
|
||||
{`/ontime/goto ${eventIndex + 1} << OSC >> /ontime/gotoid ${oscid}`}
|
||||
</span>
|
||||
</div>
|
||||
<div className={style.actionOverlay}>
|
||||
<PublicIconBtn actionHandler={actionHandler} active={data.isPublic} />
|
||||
<ActionButtons showAdd showDelay showBlock actionHandler={actionHandler} />
|
||||
<DeleteIconBtn actionHandler={actionHandler} />
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
ExpandedBlock.propTypes = {
|
||||
provided: PropTypes.any.isRequired,
|
||||
data: PropTypes.object.isRequired,
|
||||
eventIndex: PropTypes.number.isRequired,
|
||||
next: PropTypes.bool.isRequired,
|
||||
delay: PropTypes.number,
|
||||
delayValue: PropTypes.string,
|
||||
previousEnd: PropTypes.number,
|
||||
actionHandler: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
const CollapsedBlock = (props) => {
|
||||
const { provided, data, next, delay, delayValue, previousEnd, actionHandler } = props;
|
||||
|
||||
return (
|
||||
<>
|
||||
<span className={style.drag} {...provided.dragHandleProps}>
|
||||
<FiMoreVertical />
|
||||
</span>
|
||||
|
||||
<div className={style.indicators}>
|
||||
<span className={next ? style.next : style.nextDisabled}>Next</span>
|
||||
{delayValue != null && <span className={style.delayValue}>{delayValue}</span>}
|
||||
</div>
|
||||
<EventTimes
|
||||
actionHandler={actionHandler}
|
||||
timeStart={data.timeStart}
|
||||
timeEnd={data.timeEnd}
|
||||
delay={delay}
|
||||
previousEnd={previousEnd}
|
||||
className={style.time}
|
||||
/>
|
||||
<div className={style.titleContainer}>
|
||||
<EditableText
|
||||
label='Title'
|
||||
defaultValue={data.title}
|
||||
placeholder='Add Title'
|
||||
submitHandler={(v) => actionHandler('update', { field: 'title', value: v })}
|
||||
/>
|
||||
</div>
|
||||
<div className={style.actionOverlay}>
|
||||
<PublicIconBtn actionHandler={actionHandler} active={data.isPublic} />
|
||||
<ActionButtons showAdd showDelay showBlock actionHandler={actionHandler} />
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
CollapsedBlock.propTypes = {
|
||||
provided: PropTypes.any.isRequired,
|
||||
data: PropTypes.object.isRequired,
|
||||
next: PropTypes.bool.isRequired,
|
||||
delay: PropTypes.any,
|
||||
delayValue: PropTypes.string,
|
||||
previousEnd: PropTypes.number.isRequired,
|
||||
actionHandler: PropTypes.func.isRequired,
|
||||
};
|
||||
import CollapsedBlock from './CollapsedBlock';
|
||||
import ExpandedBlock from './ExpandedBlock';
|
||||
import style from './EventBlock.module.scss';
|
||||
|
||||
export default function EventBlock(props) {
|
||||
const { data, selected, delay, index, eventIndex, previousEnd, actionHandler, next } = props;
|
||||
|
||||
+4
-3
@@ -1,3 +1,5 @@
|
||||
@use '../../../styles/main' as *;
|
||||
|
||||
/* ============= COMMON ============= */
|
||||
.event {
|
||||
margin: 0.2em 0;
|
||||
@@ -8,11 +10,11 @@
|
||||
border-radius: 8px;
|
||||
font-size: 15px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.05);
|
||||
background-color: $bg-gray-950;
|
||||
|
||||
display: grid;
|
||||
gap: 0.5em;
|
||||
|
||||
background-color: rgba(255, 255, 255, 0.02);
|
||||
}
|
||||
|
||||
.active {
|
||||
@@ -69,7 +71,7 @@
|
||||
.nextDisabled {
|
||||
width: max-content;
|
||||
padding: 0 0.2em;
|
||||
color: #4bffab;
|
||||
color: $ontime-accent;
|
||||
transition: 0.3s;
|
||||
}
|
||||
|
||||
@@ -199,7 +201,6 @@
|
||||
align-content: center;
|
||||
align-self: flex-start;
|
||||
padding-top: 0.2em;
|
||||
gap: 0.5em;
|
||||
opacity: 0.8;
|
||||
transition: linear 0.1s;
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
import React from 'react';
|
||||
import { VStack } from '@chakra-ui/react';
|
||||
import { FiMoreVertical } from '@react-icons/all-files/fi/FiMoreVertical';
|
||||
import EventTimesVertical from '../../../common/components/eventTimes/EventTimesVertical';
|
||||
import EditableText from '../../../common/input/EditableText';
|
||||
import PublicIconBtn from '../../../common/components/buttons/PublicIconBtn';
|
||||
import ActionButtons from '../list/ActionButtons';
|
||||
import DeleteIconBtn from '../../../common/components/buttons/DeleteIconBtn';
|
||||
import PropTypes from 'prop-types';
|
||||
import style from './EventBlock.module.scss';
|
||||
|
||||
export default function ExpandedBlock(props) {
|
||||
const { provided, data, eventIndex, next, delay, delayValue, previousEnd, actionHandler } = props;
|
||||
|
||||
const oscid = data?.id || '...';
|
||||
|
||||
return (
|
||||
<>
|
||||
<span className={style.drag} {...provided.dragHandleProps}>
|
||||
<FiMoreVertical />
|
||||
</span>
|
||||
|
||||
<div className={style.indicators}>
|
||||
<span className={next ? style.next : style.nextDisabled}>Next</span>
|
||||
{delayValue != null && <span className={style.delayValue}>{delayValue}</span>}
|
||||
</div>
|
||||
<div className={style.timeExpanded}>
|
||||
<EventTimesVertical
|
||||
actionHandler={actionHandler}
|
||||
timeStart={data.timeStart}
|
||||
timeEnd={data.timeEnd}
|
||||
duration={data.duration}
|
||||
delay={delay}
|
||||
previousEnd={previousEnd}
|
||||
className={style.time}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className={style.titleContainer}>
|
||||
<EditableText
|
||||
label='Title'
|
||||
defaultValue={data.title}
|
||||
placeholder='Add Title'
|
||||
submitHandler={(v) => actionHandler('update', { field: 'title', value: v })}
|
||||
/>
|
||||
<EditableText
|
||||
label='Presenter'
|
||||
defaultValue={data.presenter}
|
||||
placeholder='Add Presenter name'
|
||||
submitHandler={(v) => actionHandler('update', { field: 'presenter', value: v })}
|
||||
/>
|
||||
<EditableText
|
||||
label='Subtitle'
|
||||
defaultValue={data.subtitle}
|
||||
placeholder='Add Subtitle'
|
||||
submitHandler={(v) => actionHandler('update', { field: 'subtitle', value: v })}
|
||||
/>
|
||||
<EditableText
|
||||
label='Note'
|
||||
defaultValue={data.note}
|
||||
placeholder='Add Note'
|
||||
style={{ color: '#d69e2e' }}
|
||||
maxchar={160}
|
||||
submitHandler={(v) => actionHandler('update', { field: 'note', value: v })}
|
||||
/>
|
||||
<span className={style.oscLabel}>
|
||||
{`/ontime/goto ${eventIndex + 1} << OSC >> /ontime/gotoid ${oscid}`}
|
||||
</span>
|
||||
</div>
|
||||
<VStack spacing='0.5em' className={style.actionOverlay}>
|
||||
<PublicIconBtn actionHandler={actionHandler} active={data.isPublic} />
|
||||
<ActionButtons showAdd showDelay showBlock actionHandler={actionHandler} />
|
||||
<DeleteIconBtn actionHandler={actionHandler} />
|
||||
</VStack>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
ExpandedBlock.propTypes = {
|
||||
provided: PropTypes.any.isRequired,
|
||||
data: PropTypes.object.isRequired,
|
||||
eventIndex: PropTypes.number.isRequired,
|
||||
next: PropTypes.bool.isRequired,
|
||||
delay: PropTypes.number,
|
||||
delayValue: PropTypes.string,
|
||||
previousEnd: PropTypes.number,
|
||||
actionHandler: PropTypes.func.isRequired,
|
||||
};
|
||||
@@ -127,7 +127,7 @@ export default function EventList(props) {
|
||||
}, [selectedId, isCursorLocked]);
|
||||
|
||||
if (events.length < 1) {
|
||||
return <Empty text='No Events' />;
|
||||
return <Empty text='No Events' style={{marginTop: "10vh"}} />;
|
||||
}
|
||||
|
||||
// DND
|
||||
@@ -178,7 +178,7 @@ export default function EventList(props) {
|
||||
)}
|
||||
<div
|
||||
ref={cursor === index ? cursorRef : undefined}
|
||||
className={cursor === index ? style.cursor : undefined}
|
||||
className={cursor === index ? style.cursor : ''}
|
||||
>
|
||||
<EventListItem
|
||||
type={e.type}
|
||||
|
||||
@@ -27,7 +27,6 @@ const EventListItem = (props) => {
|
||||
eventsHandler,
|
||||
delay,
|
||||
previousEnd,
|
||||
...rest
|
||||
} = props;
|
||||
const { emitError } = useContext(LoggingContext);
|
||||
const { starTimeIsLastEnd, defaultPublic } = useContext(LocalEventSettingsContext);
|
||||
@@ -42,6 +41,7 @@ const EventListItem = (props) => {
|
||||
(start, end) => (start > end ? end + 86400000 - start : end - start),
|
||||
[]
|
||||
);
|
||||
|
||||
// Create / delete new events
|
||||
const actionHandler = useCallback(
|
||||
(action, payload) => {
|
||||
@@ -101,7 +101,7 @@ const EventListItem = (props) => {
|
||||
break;
|
||||
}
|
||||
},
|
||||
[data, defaultPublic, emitError, eventsHandler, index, starTimeIsLastEnd]
|
||||
[calculateDuration, data, defaultPublic, emitError, eventsHandler, index, starTimeIsLastEnd]
|
||||
);
|
||||
|
||||
switch (type) {
|
||||
|
||||
@@ -39,7 +39,7 @@ export default function EventListWrapper() {
|
||||
}
|
||||
|
||||
// optimistically update object, temp ID until refetch
|
||||
let optimistic = [...previousEvents];
|
||||
const optimistic = [...previousEvents];
|
||||
optimistic.splice(newEvent.order, 0, {
|
||||
...newEvent,
|
||||
id: new Date().toISOString(),
|
||||
@@ -128,8 +128,7 @@ export default function EventListWrapper() {
|
||||
// Snapshot the previous value
|
||||
const previousEvents = queryClient.getQueryData(EVENTS_TABLE);
|
||||
|
||||
let filtered = [...previousEvents];
|
||||
filtered.filter((e) => e.id === 'eventId');
|
||||
const filtered = [...previousEvents].filter((e) => e.id === 'eventId')
|
||||
|
||||
// optimistically update object
|
||||
queryClient.setQueryData(EVENTS_TABLE, filtered);
|
||||
@@ -158,7 +157,7 @@ export default function EventListWrapper() {
|
||||
// Snapshot the previous value
|
||||
const previousEvents = queryClient.getQueryData(EVENTS_TABLE);
|
||||
|
||||
let clear = [];
|
||||
const clear = [];
|
||||
|
||||
// optimistically update object
|
||||
queryClient.setQueryData(EVENTS_TABLE, clear);
|
||||
@@ -225,13 +224,13 @@ export default function EventListWrapper() {
|
||||
|
||||
// Events API
|
||||
const eventsHandler = useCallback(
|
||||
async (action, payload, options = undefined) => {
|
||||
async (action, payload, options) => {
|
||||
switch (action) {
|
||||
case 'add':
|
||||
try {
|
||||
let newEvent = { ...payload };
|
||||
const newEvent = { ...payload };
|
||||
// there is an option to pass an index of an array to use as start time
|
||||
if (options?.startIsLastEnd !== undefined) {
|
||||
if (typeof options?.startIsLastEnd !== 'undefined') {
|
||||
newEvent.timeStart = data[options.startIsLastEnd].timeEnd || 0;
|
||||
}
|
||||
// hard coding duration value to be as expected for now
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
@use '../../../styles/main' as *;
|
||||
@use '../../../styles/mixins' as *;
|
||||
|
||||
.eventContainer {
|
||||
@include second-container;
|
||||
margin-top: 1em;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background-color: rgba(0, 0, 0, 0.05);
|
||||
border: 1px solid rgba(0, 0, 0, 0.05);
|
||||
border-radius: 4px;
|
||||
padding: 8px;
|
||||
overflow-y: scroll;
|
||||
height: 100%;
|
||||
@@ -21,14 +22,6 @@
|
||||
}
|
||||
|
||||
.cursor {
|
||||
width: 100%;
|
||||
background: linear-gradient(
|
||||
180deg,
|
||||
#ff7597 2%,
|
||||
#0001 3%,
|
||||
#0001 97%,
|
||||
#ff7597 98%
|
||||
);
|
||||
|
||||
box-shadow: 2px 2px 0 $ontime-pink;
|
||||
border-radius: 14px;
|
||||
}
|
||||
|
||||
@@ -3,11 +3,7 @@
|
||||
|
||||
@mixin container {
|
||||
margin-top: 1em;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background-color: rgba(0, 0, 0, 0.05);
|
||||
border: 1px solid rgba(0, 0, 0, 0.05);
|
||||
border-radius: 4px;
|
||||
@include second-container;
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
@@ -73,7 +69,7 @@
|
||||
|
||||
.emptyLabel {
|
||||
font-size: 0.8em;
|
||||
color: #555;
|
||||
color: $bg-gray-700;
|
||||
}
|
||||
|
||||
.notes {
|
||||
@@ -85,26 +81,12 @@
|
||||
.if {
|
||||
font-size: 0.8em;
|
||||
color: $ontime-accent;
|
||||
@include container-bg;
|
||||
background-color: $bg-gray-900;
|
||||
padding: 0 0.5em;
|
||||
margin: 0 0.5em;
|
||||
}
|
||||
|
||||
ul > li {
|
||||
font-size: 0.9em;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.moreExpanded,
|
||||
.moreCollapsed {
|
||||
cursor: pointer;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.moreExpanded {
|
||||
transform: scaleY(-1);
|
||||
transition: transform 0.3s;
|
||||
}
|
||||
|
||||
.moreCollapsed {
|
||||
transform: scaleY(1);
|
||||
transition: transform 0.3s;
|
||||
color: $text-white;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import React, { useContext, useEffect, useState } from 'react';
|
||||
import style from './InfoLogger.module.scss';
|
||||
import CollapseBar from "../../common/components/collapseBar/CollapseBar";
|
||||
import { HStack } from '@chakra-ui/react';
|
||||
import CollapseBar from '../../common/components/collapseBar/CollapseBar';
|
||||
import { LoggingContext } from '../../app/context/LoggingContext';
|
||||
import style from './InfoLogger.module.scss';
|
||||
|
||||
export default function InfoLogger() {
|
||||
const { logData, clearLog } = useContext(LoggingContext);
|
||||
@@ -16,6 +17,10 @@ export default function InfoLogger() {
|
||||
const [showUser, setShowUser] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
if (!logData) {
|
||||
return;
|
||||
}
|
||||
|
||||
const matchers = [];
|
||||
if (showUser) {
|
||||
matchers.push('USER');
|
||||
@@ -36,12 +41,10 @@ export default function InfoLogger() {
|
||||
matchers.push('PLAYBACK');
|
||||
}
|
||||
|
||||
const d = logData.filter((d) => (
|
||||
matchers.some((m) => d.origin === m)
|
||||
))
|
||||
const d = logData.filter((d) => matchers.some((m) => d.origin === m));
|
||||
|
||||
setData(d);
|
||||
},[logData, showUser, showClient, showServer, showPlayback, showRx, showTx])
|
||||
}, [logData, showUser, showClient, showServer, showPlayback, showRx, showTx]);
|
||||
|
||||
const disableOthers = (toEnable) => {
|
||||
toEnable === 'USER' ? setShowUser(true) : setShowUser(false);
|
||||
@@ -50,62 +53,75 @@ export default function InfoLogger() {
|
||||
toEnable === 'RX' ? setShowRx(true) : setShowRx(false);
|
||||
toEnable === 'TX' ? setShowTx(true) : setShowTx(false);
|
||||
toEnable === 'PLAYBACK' ? setShowPlayback(true) : setShowPlayback(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={collapsed ? style.container : style.container__expanded}>
|
||||
<CollapseBar title='Log' isCollapsed={collapsed} onClick={() => setCollapsed((c) => !c)} />
|
||||
{!collapsed && (
|
||||
<>
|
||||
<div className={style.toggleBar}>
|
||||
<HStack className={style.toggleBar}>
|
||||
<div
|
||||
onClick={() => setShowUser((s) => !s)}
|
||||
onAuxClick={() => disableOthers('USER')}
|
||||
className={(showUser) ? style.active : null}>
|
||||
className={showUser ? style.active : null}
|
||||
>
|
||||
USER
|
||||
</div>
|
||||
<div
|
||||
onClick={() => setShowClient((s) => !s)}
|
||||
onAuxClick={() => disableOthers('CLIENT')}
|
||||
className={(showClient) ? style.active : null}>
|
||||
className={showClient ? style.active : null}
|
||||
>
|
||||
CLIENT
|
||||
</div>
|
||||
<div
|
||||
onClick={() => setShowServer((s) => !s)}
|
||||
onAuxClick={() => disableOthers('SERVER')}
|
||||
className={(showServer) ? style.active : null}>
|
||||
className={showServer ? style.active : null}
|
||||
>
|
||||
SERVER
|
||||
</div>
|
||||
<div
|
||||
onClick={() => setShowPlayback((s) => !s)}
|
||||
onAuxClick={() => disableOthers('PLAYBACK')}
|
||||
className={(showPlayback) ? style.active : null}>
|
||||
className={showPlayback ? style.active : null}
|
||||
>
|
||||
Playback
|
||||
</div>
|
||||
<div
|
||||
onClick={() => setShowRx((s) => !s)}
|
||||
onAuxClick={() => disableOthers('RX')}
|
||||
className={(showRx) ? style.active : null}>
|
||||
className={showRx ? style.active : null}
|
||||
>
|
||||
RX
|
||||
</div>
|
||||
<div
|
||||
onClick={() => setShowTx((s) => !s)}
|
||||
onAuxClick={() => disableOthers('TX')}
|
||||
className={(showTx) ? style.active : null}>
|
||||
className={showTx ? style.active : null}
|
||||
>
|
||||
TX
|
||||
</div>
|
||||
<div
|
||||
onClick={clearLog}
|
||||
className={style.clear}>
|
||||
<div onClick={clearLog} className={style.clear}>
|
||||
Clear
|
||||
</div>
|
||||
</div>
|
||||
</HStack>
|
||||
<ul className={style.log}>
|
||||
{data.map((d) => (
|
||||
<li key={d.id} className={d.level === 'INFO' ? style.info : d.level === 'WARN' ? style.warn : d.level === 'ERROR' ? style.error : ''}>
|
||||
<div
|
||||
className={style.time}
|
||||
>{d.time}</div>
|
||||
<li
|
||||
key={d.id}
|
||||
className={
|
||||
d.level === 'INFO'
|
||||
? style.info
|
||||
: d.level === 'WARN'
|
||||
? style.warn
|
||||
: d.level === 'ERROR'
|
||||
? style.error
|
||||
: ''
|
||||
}
|
||||
>
|
||||
<div className={style.time}>{d.time}</div>
|
||||
<div className={style.origin}>{d.origin}</div>
|
||||
<div className={style.msg}>{d.text}</div>
|
||||
</li>
|
||||
|
||||
@@ -17,8 +17,11 @@
|
||||
height: 100%;
|
||||
overflow-y: scroll;
|
||||
font-size: 0.8em;
|
||||
user-select:text;
|
||||
@include container-bg;
|
||||
user-select: text;
|
||||
@include third-container;
|
||||
padding: 0 0.5em;
|
||||
margin: 0 0.5em;
|
||||
|
||||
|
||||
li {
|
||||
display: flex;
|
||||
@@ -36,39 +39,33 @@
|
||||
}
|
||||
|
||||
li.info {
|
||||
color: #aaa;
|
||||
color: $info-gray;
|
||||
}
|
||||
li.warn {
|
||||
color: #dd6b20;
|
||||
color: $warning-orange;
|
||||
}
|
||||
li.error {
|
||||
color: #f00;
|
||||
color: $error-red;
|
||||
}
|
||||
|
||||
.entry:hover {
|
||||
color: #ddd;
|
||||
li:hover {
|
||||
color: $info-gray-hover;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.info {
|
||||
color: #fff;
|
||||
color: $text-white;
|
||||
}
|
||||
|
||||
.error {
|
||||
color: red;
|
||||
}
|
||||
|
||||
.client {
|
||||
color: lightblue;
|
||||
}
|
||||
|
||||
.toggleBar {
|
||||
display: flex;
|
||||
font-size: 0.7em;
|
||||
justify-content: flex-start;
|
||||
gap: 1em;
|
||||
padding: 0.5em 0;
|
||||
padding: 0.5em 8px;
|
||||
font-weight: 600;
|
||||
|
||||
div {
|
||||
@@ -85,6 +82,7 @@
|
||||
}
|
||||
|
||||
.clear {
|
||||
margin-left: auto;
|
||||
border: 1px solid rgba($ontime-pink, 0.5);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,10 +20,9 @@ export default function InfoNif() {
|
||||
isCollapsed={collapsed}
|
||||
onClick={() => setCollapsed((c) => !c)}
|
||||
/>
|
||||
{!collapsed && (
|
||||
{!collapsed && (status === 'success') &&(
|
||||
<div>
|
||||
{status === 'success' &&
|
||||
data?.networkInterfaces.map((e) => (
|
||||
{data?.networkInterfaces.map((e) => (
|
||||
<a
|
||||
key={e.address}
|
||||
href='#!'
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import React, { useState } from 'react';
|
||||
import { Icon } from '@chakra-ui/react';
|
||||
import { FiChevronUp } from '@react-icons/all-files/fi/FiChevronUp';
|
||||
import style from './Info.module.scss';
|
||||
import CollapseBar from '../../common/components/collapseBar/CollapseBar';
|
||||
|
||||
export default function InfoTitle(props) {
|
||||
const [collapsed, setCollapsed] = useState(false);
|
||||
@@ -15,42 +14,28 @@ export default function InfoTitle(props) {
|
||||
|
||||
return (
|
||||
<div className={style.container}>
|
||||
<div className={roll ? style.headerRoll : style.header}>
|
||||
{title}
|
||||
{collapsed && (
|
||||
<span className={style.collapsedTitle}>{data.title}</span>
|
||||
)}
|
||||
<Icon
|
||||
className={collapsed ? style.moreCollapsed : style.moreExpanded}
|
||||
as={FiChevronUp}
|
||||
onClick={() => setCollapsed((c) => !c)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<CollapseBar
|
||||
title={title}
|
||||
isCollapsed={collapsed}
|
||||
onClick={() => setCollapsed((c) => !c)}
|
||||
roll={roll}
|
||||
/>
|
||||
{!collapsed && (
|
||||
<>
|
||||
<div className={style.labelContainer}>
|
||||
<span className={noTitl ? style.emptyLabel : style.label}>
|
||||
Title
|
||||
</span>
|
||||
<span className={noTitl ? style.emptyLabel : style.label}>Title</span>
|
||||
{data.title}
|
||||
</div>
|
||||
<div className={style.labelContainer}>
|
||||
<span className={noPres ? style.emptyLabel : style.label}>
|
||||
Presenter
|
||||
</span>
|
||||
<span className={noPres ? style.emptyLabel : style.label}>Presenter</span>
|
||||
{data.presenter}
|
||||
</div>
|
||||
<div className={style.labelContainer}>
|
||||
<span className={noSubt ? style.emptyLabel : style.label}>
|
||||
Subtitle
|
||||
</span>
|
||||
<span className={noSubt ? style.emptyLabel : style.label}>Subtitle</span>
|
||||
{data.subtitle}
|
||||
</div>
|
||||
<div className={style.notes}>
|
||||
<span className={noNote ? style.emptyLabel : style.label}>
|
||||
Note
|
||||
</span>
|
||||
<span className={noNote ? style.emptyLabel : style.label}>Note</span>
|
||||
{data.note}
|
||||
</div>
|
||||
</>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React, { memo, useContext } from 'react';
|
||||
import { Divider } from '@chakra-ui/react';
|
||||
import { ButtonGroup, Divider, HStack } from '@chakra-ui/react';
|
||||
import { CursorContext } from '../../app/context/CursorContext';
|
||||
import MenuActionButtons from './MenuActionButtons';
|
||||
import CollapseBtn from 'common/components/buttons/CollapseBtn';
|
||||
@@ -42,20 +42,25 @@ const EventListMenu = ({ eventsHandler }) => {
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={style.headerButtons}>
|
||||
<ExpandBtn size='sm' clickhandler={() => eventsHandler('expandall')} />
|
||||
<CollapseBtn size='sm' clickhandler={() => eventsHandler('collapseall')} />
|
||||
<HStack className={style.headerButtons}>
|
||||
<ButtonGroup isAttached>
|
||||
<ExpandBtn size='sm' clickhandler={() => eventsHandler('expandall')} />
|
||||
<CollapseBtn size='sm' clickhandler={() => eventsHandler('collapseall')} />
|
||||
</ButtonGroup>
|
||||
<Divider orientation='vertical' />
|
||||
<CursorUpBtn size='sm' clickhandler={() => actionHandler('cursorUp')} />
|
||||
<CursorDownBtn size='sm' clickhandler={() => actionHandler('cursorDown')} />
|
||||
<CursorLockedBtn
|
||||
size='sm'
|
||||
clickhandler={() => actionHandler('togglelock')}
|
||||
active={isCursorLocked}
|
||||
/>
|
||||
<ButtonGroup isAttached>
|
||||
<CursorUpBtn size='sm' clickhandler={() => actionHandler('cursorUp')} />
|
||||
<CursorDownBtn size='sm' clickhandler={() => actionHandler('cursorDown')} />
|
||||
<CursorLockedBtn
|
||||
size='sm'
|
||||
clickhandler={() => actionHandler('togglelock')}
|
||||
active={isCursorLocked}
|
||||
width='3em'
|
||||
/>
|
||||
</ButtonGroup>
|
||||
<Divider orientation='vertical' />
|
||||
<MenuActionButtons actionHandler={actionHandler} size='sm' />
|
||||
</div>
|
||||
</HStack>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,11 +1,4 @@
|
||||
.headerButtons {
|
||||
display: flex;
|
||||
gap: 0.5em;
|
||||
align-content: center;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.menu {
|
||||
color: #000;
|
||||
background-color: rgba(255, 255, 255, 0.67);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useContext, useRef } from 'react';
|
||||
import React, { useCallback, useContext, useEffect, useRef } from 'react';
|
||||
import { useMutation, useQueryClient } from 'react-query';
|
||||
import { downloadEvents, uploadEvents } from 'app/api/ontimeApi';
|
||||
import { EVENTS_TABLE } from 'app/api/apiConstants';
|
||||
@@ -12,9 +12,10 @@ import HelpIconBtn from './buttons/HelpIconBtn';
|
||||
import UploadIconBtn from './buttons/UploadIconBtn';
|
||||
import { LoggingContext } from '../../app/context/LoggingContext';
|
||||
import PropTypes from 'prop-types';
|
||||
import { VStack } from '@chakra-ui/react';
|
||||
|
||||
export default function MenuBar(props) {
|
||||
const { isOpen, onOpen } = props;
|
||||
const { isOpen, onOpen, onClose } = props;
|
||||
const { emitError } = useContext(LoggingContext);
|
||||
const hiddenFileInput = useRef(null);
|
||||
const queryClient = useQueryClient();
|
||||
@@ -35,7 +36,7 @@ export default function MenuBar(props) {
|
||||
};
|
||||
|
||||
const buttonStyle = {
|
||||
fontSize: '1.5em'
|
||||
fontSize: '1.5em',
|
||||
};
|
||||
|
||||
const handleUpload = (event) => {
|
||||
@@ -44,7 +45,7 @@ export default function MenuBar(props) {
|
||||
|
||||
// Limit file size to 1MB
|
||||
if (fileUploaded.size > 1000000) {
|
||||
emitError('Error: File size limit (1MB) exceeded')
|
||||
emitError('Error: File size limit (1MB) exceeded');
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -53,10 +54,10 @@ export default function MenuBar(props) {
|
||||
try {
|
||||
uploaddb.mutate(fileUploaded);
|
||||
} catch (error) {
|
||||
emitError(`Failed uploading file: ${error}`)
|
||||
emitError(`Failed uploading file: ${error}`);
|
||||
}
|
||||
} else {
|
||||
emitError('Error: File type unknown')
|
||||
emitError('Error: File type unknown');
|
||||
}
|
||||
|
||||
// reset input value
|
||||
@@ -65,7 +66,7 @@ export default function MenuBar(props) {
|
||||
|
||||
const handleIPC = (action) => {
|
||||
// Stop crashes when testing locally
|
||||
if (window.process?.type === undefined) {
|
||||
if (typeof window.process?.type === 'undefined') {
|
||||
if (action === 'help') {
|
||||
window.open('https://cpvalente.gitbook.io/ontime/');
|
||||
}
|
||||
@@ -92,27 +93,45 @@ export default function MenuBar(props) {
|
||||
}
|
||||
};
|
||||
|
||||
// Handle keyboard shortcuts
|
||||
const handleKeyPress = useCallback(
|
||||
(e) => {
|
||||
// handle held key
|
||||
if (e.repeat) return;
|
||||
// check if the alt key is pressed
|
||||
if (e.ctrlKey) {
|
||||
if (e.key === ',') {
|
||||
// if we are in electron
|
||||
if (window.process?.type === undefined) return;
|
||||
if (window.process.type === 'renderer') {
|
||||
// open if not open
|
||||
isOpen ? onClose() : onOpen();
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
[isOpen, onClose, onOpen]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
// attach the event listener
|
||||
document.addEventListener('keydown', handleKeyPress);
|
||||
|
||||
// remove the event listener
|
||||
return () => {
|
||||
document.removeEventListener('keydown', handleKeyPress);
|
||||
};
|
||||
}, [handleKeyPress]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<VStack>
|
||||
<QuitIconBtn size='lg' clickhandler={() => handleIPC('shutdown')} />
|
||||
<MaxIconBtn
|
||||
style={{ ...buttonStyle }}
|
||||
size='lg'
|
||||
clickhandler={() => handleIPC('max')}
|
||||
/>
|
||||
<MinIconBtn
|
||||
style={{ ...buttonStyle }}
|
||||
size='lg'
|
||||
clickhandler={() => handleIPC('min')}
|
||||
/>
|
||||
<MaxIconBtn style={{ ...buttonStyle }} size='lg' clickhandler={() => handleIPC('max')} />
|
||||
<MinIconBtn style={{ ...buttonStyle }} size='lg' clickhandler={() => handleIPC('min')} />
|
||||
<div className={style.gap} />
|
||||
<HelpIconBtn
|
||||
style={{ ...buttonStyle }}
|
||||
size='lg'
|
||||
clickhandler={() => handleIPC('help')}
|
||||
/>
|
||||
<HelpIconBtn style={{ ...buttonStyle }} size='lg' clickhandler={() => handleIPC('help')} />
|
||||
<SettingsIconBtn
|
||||
style={{...buttonStyle}}
|
||||
style={{ ...buttonStyle }}
|
||||
size='lg'
|
||||
className={isOpen ? style.open : ''}
|
||||
clickhandler={onOpen}
|
||||
@@ -126,22 +145,14 @@ export default function MenuBar(props) {
|
||||
onChange={handleUpload}
|
||||
accept='.json, .xlsx'
|
||||
/>
|
||||
<UploadIconBtn
|
||||
style={{ ...buttonStyle }}
|
||||
size='lg'
|
||||
clickhandler={handleClick}
|
||||
/>
|
||||
<DownloadIconBtn
|
||||
style={{ ...buttonStyle }}
|
||||
size='lg'
|
||||
clickhandler={handleDownload}
|
||||
/>
|
||||
</>
|
||||
<UploadIconBtn style={{ ...buttonStyle }} size='lg' clickhandler={handleClick} />
|
||||
<DownloadIconBtn style={{ ...buttonStyle }} size='lg' clickhandler={handleDownload} />
|
||||
</VStack>
|
||||
);
|
||||
}
|
||||
|
||||
MenuBar.propTypes = {
|
||||
isOpen: PropTypes.bool,
|
||||
onOpen: PropTypes.func.isRequired,
|
||||
onClose: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
|
||||
@@ -1,21 +1,23 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import MenuBar from "../MenuBar";
|
||||
import {queryClientMock} from "../../../__mocks__/QueryClient.mock";
|
||||
import {QueryClientProvider} from "react-query";
|
||||
import MenuBar from '../MenuBar';
|
||||
import { queryClientMock } from '../../../__mocks__/QueryClient.mock';
|
||||
import { QueryClientProvider } from 'react-query';
|
||||
|
||||
const onOpenHandler = jest.fn();
|
||||
const onCloseHandler = jest.fn();
|
||||
|
||||
const renderInMock = () => {
|
||||
render(
|
||||
<QueryClientProvider client={queryClientMock}>
|
||||
<MenuBar onOpen={onOpenHandler} />
|
||||
<MenuBar onOpen={onOpenHandler} onClose={onCloseHandler} />
|
||||
</QueryClientProvider>
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
test('check that menu bar renders correctly', () => {
|
||||
// need to inject the react query provider
|
||||
renderInMock();
|
||||
|
||||
const nButtons = screen.getAllByRole("button").length;
|
||||
const nButtons = screen.getAllByRole('button').length;
|
||||
expect(nButtons).toBe(7);
|
||||
});
|
||||
|
||||
@@ -1,21 +1,23 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import MenuBar from "../MenuBar";
|
||||
import {queryClientMock} from "../../../__mocks__/QueryClient.mock";
|
||||
import {QueryClientProvider} from "react-query";
|
||||
import MenuBar from '../MenuBar';
|
||||
import { queryClientMock } from '../../../__mocks__/QueryClient.mock';
|
||||
import { QueryClientProvider } from 'react-query';
|
||||
|
||||
const onOpenHandler = jest.fn();
|
||||
const onCloseHandler = jest.fn();
|
||||
|
||||
const renderInMock = () => {
|
||||
render(
|
||||
<QueryClientProvider client={queryClientMock}>
|
||||
<MenuBar onOpen={onOpenHandler} />
|
||||
<MenuBar onOpen={onOpenHandler} onClose={onCloseHandler} />
|
||||
</QueryClientProvider>
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
test('check that menu bar renders correctly', () => {
|
||||
// need to inject the react query provider
|
||||
renderInMock();
|
||||
|
||||
const nButtons = screen.getAllByRole("button").length;
|
||||
const nButtons = screen.getAllByRole('button').length;
|
||||
expect(nButtons).toBe(7);
|
||||
});
|
||||
|
||||
@@ -78,7 +78,7 @@ export default function AppSettingsModal() {
|
||||
|
||||
// we might not have changed this
|
||||
if (f.pinCode !== data.pinCode) {
|
||||
let e = { status: false, message: '' };
|
||||
const e = { status: false, message: '' };
|
||||
|
||||
// Validate fields
|
||||
if (f.pinCode === '' || f.pinCode == null) {
|
||||
|
||||
@@ -45,13 +45,11 @@ export default function IntegrationSettingsModal() {
|
||||
// set fields with error
|
||||
if (e.status) {
|
||||
emitError(`Invalid Input: ${e.message}`);
|
||||
return;
|
||||
} else {
|
||||
await postInfo(f);
|
||||
setChanged(false);
|
||||
setSubmitting(false);
|
||||
}
|
||||
|
||||
setSubmitting(false);
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -97,7 +97,7 @@ export default function OscSettingsModal() {
|
||||
setSubmitting(true);
|
||||
|
||||
const f = formData;
|
||||
let e = { status: false, message: '' };
|
||||
const e = { status: false, message: '' };
|
||||
|
||||
// Validate fields
|
||||
if (f.port < 1024 || f.port > 65535) {
|
||||
@@ -137,7 +137,7 @@ export default function OscSettingsModal() {
|
||||
/**
|
||||
* Handles change of input field in local state
|
||||
* @param {string} field - object parameter to update
|
||||
* @param {(string | number)} value - new object parameter value
|
||||
* @param {(string | number | boolean)} value - new object parameter value
|
||||
*/
|
||||
const handleChange = (field, value) => {
|
||||
const temp = { ...formData };
|
||||
|
||||
@@ -34,7 +34,7 @@ export default function TableOptionsModal() {
|
||||
|
||||
// validation step makes clean string
|
||||
const validatedFields = { ...userFields };
|
||||
let errors = false;
|
||||
const errors = false;
|
||||
for (const field in validatedFields) {
|
||||
validatedFields[field] = validatedFields[field].trim();
|
||||
}
|
||||
|
||||
@@ -7,10 +7,10 @@ import { FiSettings } from '@react-icons/all-files/fi/FiSettings';
|
||||
import { IoMoon } from '@react-icons/all-files/io5/IoMoon';
|
||||
import { FiTarget } from '@react-icons/all-files/fi/FiTarget';
|
||||
import { useSocket } from '../../app/context/socketContext';
|
||||
import { stringFromMillis } from 'ontime-utils/time';
|
||||
import { formatDisplay } from '../../common/utils/dateConfig';
|
||||
import { Tooltip } from '@chakra-ui/tooltip';
|
||||
import PlaybackIcon from './tableElements/PlaybackIcon';
|
||||
import { stringFromMillis } from '../../common/utils/time';
|
||||
import style from './Table.module.scss';
|
||||
|
||||
export default function TableHeader() {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React from 'react';
|
||||
import { FiCheck } from '@react-icons/all-files/fi/FiCheck';
|
||||
import { stringFromMillis } from 'ontime-utils/time';
|
||||
import EditableCell from './tableElements/EditableCell';
|
||||
import { stringFromMillis } from '../../common/utils/time.js';
|
||||
|
||||
/**
|
||||
* React - Table column object
|
||||
|
||||
@@ -3,9 +3,9 @@ import React, { useEffect, useState } from 'react';
|
||||
import { fetchAllEvents } from 'app/api/eventsApi';
|
||||
import { fetchEvent } from 'app/api/eventApi';
|
||||
import { useSocket } from 'app/context/socketContext';
|
||||
import { stringFromMillis } from 'ontime-utils/time';
|
||||
import { useFetch } from 'app/hooks/useFetch';
|
||||
import { EVENT_TABLE, EVENTS_TABLE } from 'app/api/apiConstants';
|
||||
import { stringFromMillis } from '../../common/utils/time';
|
||||
|
||||
const withSocket = (Component) => {
|
||||
return (props) => {
|
||||
|
||||
@@ -1,16 +1,19 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import QRCode from 'react-qr-code';
|
||||
import { formatDisplay } from 'common/utils/dateConfig';
|
||||
import style from './StageManager.module.css';
|
||||
import Paginator from 'common/components/views/Paginator';
|
||||
import NavLogo from 'common/components/nav/NavLogo';
|
||||
import { AnimatePresence, motion } from 'framer-motion';
|
||||
import TitleSide from 'common/components/views/TitleSide';
|
||||
import {getEventsWithDelay} from "../../../common/utils/eventsManager";
|
||||
import { getEventsWithDelay } from '../../../common/utils/eventsManager';
|
||||
import { titleVariants } from '../common/animation';
|
||||
import style from './StageManager.module.scss';
|
||||
|
||||
export default function StageManager(props) {
|
||||
const { publ, title, time, backstageEvents, selectedId, general } = props;
|
||||
const [filteredEvents, setFilteredEvents] = useState(null);
|
||||
const [pageNumber, setPageNumber] = useState(0);
|
||||
const [currentPage, setCurrentPage] = useState(0);
|
||||
|
||||
// Set window title
|
||||
useEffect(() => {
|
||||
@@ -20,11 +23,9 @@ export default function StageManager(props) {
|
||||
// calculate delays if any
|
||||
useEffect(() => {
|
||||
if (backstageEvents == null) return;
|
||||
|
||||
const f = getEventsWithDelay(backstageEvents)
|
||||
|
||||
const f = getEventsWithDelay(backstageEvents);
|
||||
setFilteredEvents(f);
|
||||
}, [backstageEvents]);
|
||||
}, [backstageEvents]);
|
||||
|
||||
// Format messages
|
||||
const showPubl = publ.text !== '' && publ.visible;
|
||||
@@ -37,22 +38,6 @@ export default function StageManager(props) {
|
||||
if (time.isNegative) stageTimer = `-${stageTimer}`;
|
||||
}
|
||||
|
||||
// motion
|
||||
const titleVariants = {
|
||||
hidden: {
|
||||
x: -1500,
|
||||
},
|
||||
visible: {
|
||||
x: 0,
|
||||
transition: {
|
||||
duration: 1,
|
||||
},
|
||||
},
|
||||
exit: {
|
||||
x: -1500,
|
||||
},
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={style.container__gray}>
|
||||
<NavLogo />
|
||||
@@ -102,17 +87,28 @@ export default function StageManager(props) {
|
||||
</AnimatePresence>
|
||||
|
||||
<div className={style.todayContainer}>
|
||||
<div className={style.label}>Today</div>
|
||||
<div className={style.entriesContainer}>
|
||||
<Paginator selectedId={selectedId} events={filteredEvents} isBackstage />
|
||||
<div className={style.todayHeaderBlock}>
|
||||
<div className={style.label}>Today</div>
|
||||
<div className={style.nav}>
|
||||
{pageNumber > 1 &&
|
||||
[...Array(pageNumber).keys()].map((i) => (
|
||||
<div
|
||||
key={i}
|
||||
className={i === currentPage ? style.navItemSelected : style.navItem}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<Paginator
|
||||
selectedId={selectedId}
|
||||
events={filteredEvents}
|
||||
isBackstage
|
||||
setCurrentPage={setCurrentPage}
|
||||
setPageNumber={setPageNumber}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div
|
||||
className={
|
||||
showPubl ? style.publicContainer : style.publicContainerHidden
|
||||
}
|
||||
>
|
||||
<div className={showPubl ? style.publicContainer : style.publicContainerHidden}>
|
||||
<div className={style.label}>Public message</div>
|
||||
<div className={style.message}>{publ.text}</div>
|
||||
</div>
|
||||
@@ -134,11 +130,7 @@ export default function StageManager(props) {
|
||||
</div>
|
||||
<div className={style.qr}>
|
||||
{general.url != null && general.url !== '' && (
|
||||
<QRCode
|
||||
value={general.url}
|
||||
size={window.innerWidth / 12}
|
||||
level='L'
|
||||
/>
|
||||
<QRCode value={general.url} size={window.innerWidth / 12} level='L' />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,178 +0,0 @@
|
||||
.container__gray,
|
||||
.container__grayFinished {
|
||||
margin: 0;
|
||||
box-sizing: border-box; /* reset */
|
||||
overflow: hidden;
|
||||
width: 100%; /* restrict the page width to viewport */
|
||||
|
||||
background: linear-gradient(90deg, #252525 0%, #121212 100%);
|
||||
height: 100vh;
|
||||
color: #fffd;
|
||||
font-weight: 300;
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr 1fr 3vw 2fr;
|
||||
grid-template-rows: 15vh 1fr 1fr 0 13vh 13vh;
|
||||
grid-template-areas:
|
||||
' titl titl titl . schd'
|
||||
' now now now . schd'
|
||||
' next next .... . schd'
|
||||
' ... .... .... . ....'
|
||||
' publ publ clck . info'
|
||||
' publ publ time . info';
|
||||
gap: 1vw;
|
||||
padding: 1vw;
|
||||
}
|
||||
|
||||
.label {
|
||||
font-size: 1.3vw;
|
||||
color: #ff7597;
|
||||
}
|
||||
|
||||
.eventTitle {
|
||||
grid-area: titl;
|
||||
font-size: 3vw;
|
||||
font-weight: 600;
|
||||
text-decoration: underline #ff7597 0.5vh;
|
||||
padding-top: 0.2vh;
|
||||
padding-left: 1vw;
|
||||
}
|
||||
|
||||
/* =================== TITLES ===================*/
|
||||
|
||||
.infoContainer > div {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.nextContainer > div,
|
||||
.nowContainer > div {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.nowContainer,
|
||||
.nextContainer,
|
||||
.todayContainer {
|
||||
background-color: rgba(255, 255, 255, 0.05);
|
||||
padding: 1vh 2vw;
|
||||
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.clockContainer,
|
||||
.countdownContainer,
|
||||
.publicContainer,
|
||||
.publicContainerHidden {
|
||||
background-color: rgba(255, 255, 255, 0.05);
|
||||
padding: 1vh 1vw;
|
||||
}
|
||||
|
||||
.publicContainer {
|
||||
opacity: 1;
|
||||
transition: 0.5s;
|
||||
transition-property: opacity;
|
||||
}
|
||||
|
||||
.publicContainerHidden {
|
||||
opacity: 0;
|
||||
transition: 0.5s;
|
||||
transition-property: opacity;
|
||||
}
|
||||
|
||||
.todayContainer,
|
||||
.infoContainer {
|
||||
background-color: rgba(255, 255, 255, 0.07);
|
||||
padding: 2.5vh 2vw;
|
||||
}
|
||||
|
||||
.infoContainer,
|
||||
.todayContainer,
|
||||
.publicContainer,
|
||||
.publicContainerHidden {
|
||||
border-radius: 1vw;
|
||||
}
|
||||
|
||||
.nowContainer,
|
||||
.nextContainer {
|
||||
margin-left: -1vw;
|
||||
}
|
||||
|
||||
.nowContainer {
|
||||
background-color: rgba(255, 255, 255, 0.09);
|
||||
grid-area: now;
|
||||
border-radius: 0 2vw 2vw 0;
|
||||
}
|
||||
|
||||
.publicContainer,
|
||||
.publicContainerHidden {
|
||||
grid-area: publ;
|
||||
}
|
||||
|
||||
.nextContainer {
|
||||
grid-area: next;
|
||||
border-radius: 0 2vw 2vw 0;
|
||||
}
|
||||
|
||||
/* =================== SCHEDULE ===================*/
|
||||
|
||||
.todayContainer {
|
||||
grid-area: schd;
|
||||
display: grid;
|
||||
grid-template-rows: 5vh 1fr 3vh;
|
||||
margin-top: 3vh;
|
||||
height: 95%;
|
||||
}
|
||||
|
||||
/* =================== OVERLAY ===================*/
|
||||
|
||||
.message {
|
||||
font-size: 3vw;
|
||||
line-height: 3vw;
|
||||
padding: 0.5vh 0 0.5vh 1vw;
|
||||
}
|
||||
|
||||
.infoContainer {
|
||||
grid-area: info;
|
||||
display: grid;
|
||||
|
||||
grid-template-rows: 3vh minmax(0, 1fr);
|
||||
grid-template-columns: 3fr 1fr;
|
||||
grid-template-areas:
|
||||
'titl .'
|
||||
'binf qr';
|
||||
gap: 0.5vw;
|
||||
}
|
||||
|
||||
.infoMessages {
|
||||
grid-area: binf;
|
||||
font-size: 1.5vw;
|
||||
line-height: 2vw;
|
||||
white-space: pre-line;
|
||||
}
|
||||
|
||||
.qr {
|
||||
align-self: center;
|
||||
justify-self: center;
|
||||
grid-area: qr;
|
||||
}
|
||||
|
||||
/* =================== MAIN ===================*/
|
||||
|
||||
.clockContainer {
|
||||
grid-area: time;
|
||||
border-radius: 0 0 1vw 1vw;
|
||||
}
|
||||
|
||||
.countdownContainer {
|
||||
grid-area: clck;
|
||||
border-radius: 1vw 1vw 0 0;
|
||||
}
|
||||
|
||||
.clock {
|
||||
font-family: 'Open Sans', sans-serif;
|
||||
font-size: 3vw;
|
||||
line-height: 3vw;
|
||||
text-align: center;
|
||||
letter-spacing: 0.25vw;
|
||||
color: #ddd;
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
@use '../../../styles/main' as *;
|
||||
@use '../../../styles/viewers' as *;
|
||||
|
||||
.container__gray,
|
||||
.container__grayFinished {
|
||||
@include viewer-container;
|
||||
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr 1fr 3vw 2fr;
|
||||
grid-template-rows: 15vh 1fr 1fr 0 13vh 13vh;
|
||||
grid-template-areas:
|
||||
' titl titl titl . schd'
|
||||
' now now now . schd'
|
||||
' next next .... . schd'
|
||||
' ... .... .... . ....'
|
||||
' publ publ clck . info'
|
||||
' publ publ time . info';
|
||||
gap: 1vw;
|
||||
padding: 1vw;
|
||||
}
|
||||
|
||||
.eventTitle {
|
||||
grid-area: titl;
|
||||
@include viewer-event-title;
|
||||
}
|
||||
|
||||
.nowContainer {
|
||||
grid-area: now;
|
||||
background-color: $bg-gray-950;
|
||||
border-radius: 0 2vw 2vw 0;
|
||||
}
|
||||
|
||||
.publicContainer,
|
||||
.publicContainerHidden {
|
||||
grid-area: publ;
|
||||
}
|
||||
|
||||
.nextContainer {
|
||||
grid-area: next;
|
||||
border-radius: 0 2vw 2vw 0;
|
||||
}
|
||||
|
||||
.todayContainer {
|
||||
grid-area: schd;
|
||||
}
|
||||
|
||||
.infoContainer {
|
||||
grid-area: info;
|
||||
}
|
||||
|
||||
.clockContainer {
|
||||
grid-area: time;
|
||||
border-radius: 0 0 1vw 1vw;
|
||||
}
|
||||
|
||||
.countdownContainer {
|
||||
grid-area: clck;
|
||||
border-radius: 1vw 1vw 0 0;
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
// used in both sm and public views
|
||||
export const titleVariants = {
|
||||
hidden: {
|
||||
x: -1500,
|
||||
},
|
||||
visible: {
|
||||
x: 0,
|
||||
transition: {
|
||||
duration: 1,
|
||||
},
|
||||
},
|
||||
exit: {
|
||||
x: -1500,
|
||||
},
|
||||
};
|
||||
@@ -1,13 +1,16 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import QRCode from 'react-qr-code';
|
||||
import style from './Public.module.css';
|
||||
import Paginator from 'common/components/views/Paginator';
|
||||
import NavLogo from 'common/components/nav/NavLogo';
|
||||
import { AnimatePresence, motion } from 'framer-motion';
|
||||
import TitleSide from 'common/components/views/TitleSide';
|
||||
import { titleVariants } from '../common/animation';
|
||||
import style from './Public.module.scss';
|
||||
|
||||
export default function Public(props) {
|
||||
const { publ, publicTitle, time, events, publicSelectedId, general } = props;
|
||||
const [pageNumber, setPageNumber] = useState(0);
|
||||
const [currentPage, setCurrentPage] = useState(0);
|
||||
|
||||
// Set window title
|
||||
useEffect(() => {
|
||||
@@ -18,20 +21,7 @@ export default function Public(props) {
|
||||
const showPubl = publ.text !== '' && publ.visible;
|
||||
|
||||
// motion
|
||||
const titleVariants = {
|
||||
hidden: {
|
||||
x: -1500,
|
||||
},
|
||||
visible: {
|
||||
x: 0,
|
||||
transition: {
|
||||
duration: 1,
|
||||
},
|
||||
},
|
||||
exit: {
|
||||
x: -1500,
|
||||
},
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={style.container__gray}>
|
||||
<NavLogo />
|
||||
@@ -81,10 +71,25 @@ export default function Public(props) {
|
||||
</AnimatePresence>
|
||||
|
||||
<div className={style.todayContainer}>
|
||||
<div className={style.label}>Today</div>
|
||||
<div className={style.entriesContainer}>
|
||||
<Paginator selectedId={publicSelectedId} events={events} />
|
||||
<div className={style.todayHeaderBlock}>
|
||||
<div className={style.label}>Today</div>
|
||||
<div className={style.nav}>
|
||||
{pageNumber > 1 &&
|
||||
[...Array(pageNumber).keys()].map((i) => (
|
||||
<div
|
||||
key={i}
|
||||
className={i === currentPage ? style.navItemSelected : style.navItem}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<Paginator
|
||||
selectedId={publicSelectedId}
|
||||
events={events}
|
||||
isBackstage
|
||||
setCurrentPage={setCurrentPage}
|
||||
setPageNumber={setPageNumber}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div
|
||||
|
||||
@@ -1,172 +0,0 @@
|
||||
.container__gray,
|
||||
.container__grayFinished {
|
||||
margin: 0;
|
||||
box-sizing: border-box; /* reset */
|
||||
overflow: hidden;
|
||||
width: 100%; /* restrict the page width to viewport */
|
||||
|
||||
background: linear-gradient(90deg, #252525 0%, #121212 100%);
|
||||
height: 100vh;
|
||||
color: #fffd;
|
||||
font-weight: 300;
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr 1fr 3vw 2fr;
|
||||
grid-template-rows: 15vh 1fr 1fr 0 13vh 13vh;
|
||||
grid-template-areas:
|
||||
' titl titl titl . schd'
|
||||
' now now now . schd'
|
||||
' next next .... . schd'
|
||||
' ... .... .... . ....'
|
||||
' publ publ .... . info'
|
||||
' publ publ time . info';
|
||||
gap: 1vw;
|
||||
padding: 1vw;
|
||||
}
|
||||
|
||||
.label {
|
||||
font-size: 1.3vw;
|
||||
color: #ff7597;
|
||||
}
|
||||
|
||||
.eventTitle {
|
||||
grid-area: titl;
|
||||
font-size: 3vw;
|
||||
font-weight: 600;
|
||||
text-decoration: underline #ff7597 0.5vh;
|
||||
padding-top: 0.2vh;
|
||||
padding-left: 1vw;
|
||||
}
|
||||
|
||||
/* =================== TITLES ===================*/
|
||||
|
||||
.infoContainer > div {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.nextContainer > div,
|
||||
.nowContainer > div {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.nowContainer,
|
||||
.nextContainer,
|
||||
.todayContainer {
|
||||
background-color: rgba(255, 255, 255, 0.05);
|
||||
padding: 1vh 2vw;
|
||||
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.clockContainer,
|
||||
.publicContainer,
|
||||
.publicContainerHidden {
|
||||
background-color: rgba(255, 255, 255, 0.05);
|
||||
padding: 1vh 1vw;
|
||||
}
|
||||
|
||||
.publicContainer {
|
||||
opacity: 1;
|
||||
transition: 0.5s;
|
||||
transition-property: opacity;
|
||||
}
|
||||
|
||||
.publicContainerHidden {
|
||||
opacity: 0;
|
||||
transition: 0.5s;
|
||||
transition-property: opacity;
|
||||
}
|
||||
|
||||
.todayContainer,
|
||||
.infoContainer {
|
||||
background-color: rgba(255, 255, 255, 0.07);
|
||||
padding: 2.5vh 2vw;
|
||||
}
|
||||
|
||||
.infoContainer,
|
||||
.todayContainer,
|
||||
.publicContainer,
|
||||
.publicContainerHidden {
|
||||
border-radius: 1vw;
|
||||
}
|
||||
|
||||
.nowContainer,
|
||||
.nextContainer {
|
||||
margin-left: -1vw;
|
||||
}
|
||||
|
||||
.nowContainer {
|
||||
background-color: rgba(255, 255, 255, 0.09);
|
||||
grid-area: now;
|
||||
border-radius: 0 2vw 2vw 0;
|
||||
}
|
||||
|
||||
.publicContainer,
|
||||
.publicContainerHidden {
|
||||
grid-area: publ;
|
||||
}
|
||||
|
||||
.nextContainer {
|
||||
grid-area: next;
|
||||
border-radius: 0 2vw 2vw 0;
|
||||
}
|
||||
|
||||
/* =================== SCHEDULE ===================*/
|
||||
|
||||
.todayContainer {
|
||||
grid-area: schd;
|
||||
display: grid;
|
||||
grid-template-rows: 5vh 1fr 3vh;
|
||||
margin-top: 3vh;
|
||||
height: 95%;
|
||||
}
|
||||
|
||||
/* =================== OVERLAY ===================*/
|
||||
|
||||
.message {
|
||||
font-size: 3vw;
|
||||
line-height: 3vw;
|
||||
padding: 0.5vh 0 0.5vh 1vw;
|
||||
}
|
||||
|
||||
.infoContainer {
|
||||
grid-area: info;
|
||||
display: grid;
|
||||
|
||||
grid-template-rows: 3vh minmax(0, 1fr);
|
||||
grid-template-columns: 3fr 1fr;
|
||||
grid-template-areas:
|
||||
'titl .'
|
||||
'binf qr';
|
||||
gap: 0.5vw;
|
||||
}
|
||||
|
||||
.infoMessages {
|
||||
grid-area: binf;
|
||||
font-size: 1.5vw;
|
||||
line-height: 2vw;
|
||||
white-space: pre-line;
|
||||
}
|
||||
|
||||
.qr {
|
||||
align-self: center;
|
||||
justify-self: center;
|
||||
grid-area: qr;
|
||||
}
|
||||
|
||||
/* =================== MAIN ===================*/
|
||||
|
||||
.clockContainer {
|
||||
grid-area: time;
|
||||
border-radius: 1vw;
|
||||
}
|
||||
|
||||
.clock {
|
||||
font-family: 'Open Sans', sans-serif;
|
||||
font-size: 3vw;
|
||||
line-height: 3vw;
|
||||
text-align: center;
|
||||
letter-spacing: 0.25vw;
|
||||
color: #ddd;
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
@use '../../../styles/main' as *;
|
||||
@use '../../../styles/viewers' as *;
|
||||
|
||||
.container__gray,
|
||||
.container__grayFinished {
|
||||
@include viewer-container;
|
||||
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr 1fr 3vw 2fr;
|
||||
grid-template-rows: 15vh 1fr 1fr 0 13vh 13vh;
|
||||
grid-template-areas:
|
||||
' titl titl titl . schd'
|
||||
' now now now . schd'
|
||||
' next next .... . schd'
|
||||
' ... .... .... . ....'
|
||||
' publ publ .... . info'
|
||||
' publ publ time . info';
|
||||
gap: 1vw;
|
||||
padding: 1vw;
|
||||
}
|
||||
|
||||
.eventTitle {
|
||||
grid-area: titl;
|
||||
@include viewer-event-title;
|
||||
}
|
||||
|
||||
.nowContainer {
|
||||
grid-area: now;
|
||||
background-color: $bg-gray-950;
|
||||
border-radius: 0 2vw 2vw 0;
|
||||
}
|
||||
|
||||
.publicContainer,
|
||||
.publicContainerHidden {
|
||||
grid-area: publ;
|
||||
}
|
||||
|
||||
.nextContainer {
|
||||
grid-area: next;
|
||||
border-radius: 0 2vw 2vw 0;
|
||||
}
|
||||
|
||||
.todayContainer {
|
||||
grid-area: schd;
|
||||
}
|
||||
|
||||
.infoContainer {
|
||||
grid-area: info;
|
||||
}
|
||||
|
||||
.clockContainer {
|
||||
grid-area: time;
|
||||
border-radius: 1vw;
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, { useEffect, useLayoutEffect, useRef, useState } from 'react';
|
||||
import QRCode from 'react-qr-code';
|
||||
import style from './Pip.module.css';
|
||||
import style from './Pip.module.scss';
|
||||
import Paginator from 'common/components/views/Paginator';
|
||||
import NavLogo from 'common/components/nav/NavLogo';
|
||||
import { AnimatePresence, motion } from 'framer-motion';
|
||||
@@ -12,6 +12,8 @@ export default function Pip(props) {
|
||||
const [size, setSize] = useState('');
|
||||
const ref = useRef(null);
|
||||
const [filteredEvents, setFilteredEvents] = useState(null);
|
||||
const [pageNumber, setPageNumber] = useState(0);
|
||||
const [currentPage, setCurrentPage] = useState(0);
|
||||
|
||||
// calculcate pip size
|
||||
useLayoutEffect(() => {
|
||||
@@ -29,7 +31,7 @@ export default function Pip(props) {
|
||||
useEffect(() => {
|
||||
if (backstageEvents == null) return;
|
||||
|
||||
let events = [...backstageEvents];
|
||||
const events = [...backstageEvents];
|
||||
|
||||
// Add running delay
|
||||
let delay = 0;
|
||||
@@ -43,9 +45,7 @@ export default function Pip(props) {
|
||||
}
|
||||
|
||||
// filter just events
|
||||
let filtered = events.filter((e) => e.type === 'event');
|
||||
|
||||
setFilteredEvents(filtered);
|
||||
setFilteredEvents(events.filter((e) => e.type === 'event'));
|
||||
}, [backstageEvents]);
|
||||
|
||||
// Format messages
|
||||
@@ -61,15 +61,27 @@ export default function Pip(props) {
|
||||
<div className={style.eventTitle}>{general.title}</div>
|
||||
|
||||
<div className={style.todayContainer}>
|
||||
<div className={style.label}>Today</div>
|
||||
<div className={style.entriesContainer}>
|
||||
<Paginator
|
||||
selectedId={selectedId}
|
||||
events={filteredEvents}
|
||||
limit={15}
|
||||
time={20}
|
||||
/>
|
||||
<div className={style.todayHeaderBlock}>
|
||||
<div className={style.label}>Today</div>
|
||||
<div className={style.nav}>
|
||||
{pageNumber > 1 &&
|
||||
[...Array(pageNumber).keys()].map((i) => (
|
||||
<div
|
||||
key={i}
|
||||
className={i === currentPage ? style.navItemSelected : style.navItem}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<Paginator
|
||||
selectedId={selectedId}
|
||||
events={filteredEvents}
|
||||
isBackstage
|
||||
limit={14}
|
||||
time={20}
|
||||
setCurrentPage={setCurrentPage}
|
||||
setPageNumber={setPageNumber}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className={style.pip} ref={ref}>
|
||||
|
||||
@@ -1,136 +0,0 @@
|
||||
.container__gray,
|
||||
.container__grayFinished {
|
||||
margin: 0;
|
||||
box-sizing: border-box; /* reset */
|
||||
overflow: hidden;
|
||||
width: 100%; /* restrict the page width to viewport */
|
||||
|
||||
background: linear-gradient(90deg, #252525 0%, #121212 100%);
|
||||
height: 100vh;
|
||||
color: #fffd;
|
||||
font-weight: 300;
|
||||
display: grid;
|
||||
grid-template-columns: 20vw 20vw 18vw 3vw 1fr;
|
||||
grid-template-rows: 60vh 1fr 1fr 1fr;
|
||||
grid-template-areas:
|
||||
' pip pip pip . schd'
|
||||
' titl titl titl . schd'
|
||||
' info info clck . schd'
|
||||
' info info time . schd';
|
||||
gap: 1vw;
|
||||
padding: 1vw;
|
||||
}
|
||||
|
||||
.label {
|
||||
font-size: 1.3vw;
|
||||
color: #ff7597;
|
||||
}
|
||||
|
||||
.eventTitle {
|
||||
grid-area: titl;
|
||||
font-size: 3vw;
|
||||
font-weight: 600;
|
||||
text-decoration: underline #ff7597 0.5vh;
|
||||
padding-top: 0.2vh;
|
||||
padding-left: 1vw;
|
||||
}
|
||||
|
||||
.pip {
|
||||
grid-area: pip;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
border: 1px solid rgba(255, 255, 2555, 0.07);
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
display: grid;
|
||||
place-content: center;
|
||||
}
|
||||
|
||||
.empty {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.piptext {
|
||||
color: rgba(255, 255, 255, 0.13);
|
||||
font-weight: 600;
|
||||
font-size: 4vh;
|
||||
}
|
||||
|
||||
/* =================== TITLES ===================*/
|
||||
|
||||
.infoContainer > div {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.infoContainer,
|
||||
.clockContainer,
|
||||
.countdownContainer {
|
||||
background-color: rgba(255, 255, 255, 0.05);
|
||||
padding: 1vh 1vw;
|
||||
}
|
||||
|
||||
.todayContainer {
|
||||
background-color: rgba(255, 255, 255, 0.07);
|
||||
padding: 2.5vh 2vw;
|
||||
}
|
||||
|
||||
.infoContainer,
|
||||
.todayContainer {
|
||||
border-radius: 1vw;
|
||||
}
|
||||
|
||||
.infoContainer {
|
||||
grid-area: info;
|
||||
display: grid;
|
||||
|
||||
grid-template-rows: 3vh minmax(0, 1fr);
|
||||
grid-template-columns: 3fr 1fr;
|
||||
grid-template-areas:
|
||||
'titl qr'
|
||||
'binf qr';
|
||||
gap: 0.5vw;
|
||||
}
|
||||
|
||||
.infoMessages {
|
||||
grid-area: binf;
|
||||
font-size: 1.5vw;
|
||||
line-height: 2vw;
|
||||
white-space: pre-line;
|
||||
}
|
||||
|
||||
.qr {
|
||||
align-self: center;
|
||||
justify-self: center;
|
||||
grid-area: qr;
|
||||
}
|
||||
|
||||
/* =================== SCHEDULE ===================*/
|
||||
|
||||
.todayContainer {
|
||||
grid-area: schd;
|
||||
display: grid;
|
||||
grid-template-rows: 5vh 1fr 3vh;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
/* =================== MAIN ===================*/
|
||||
|
||||
.clockContainer {
|
||||
grid-area: time;
|
||||
border-radius: 0 0 1vw 1vw;
|
||||
}
|
||||
|
||||
.countdownContainer {
|
||||
grid-area: clck;
|
||||
border-radius: 1vw 1vw 0 0;
|
||||
}
|
||||
|
||||
.clock {
|
||||
font-family: 'Open Sans', sans-serif;
|
||||
font-size: 3vw;
|
||||
line-height: 3vw;
|
||||
text-align: center;
|
||||
letter-spacing: 0.25vw;
|
||||
color: #ddd;
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
@use '../../../styles/main' as *;
|
||||
@use '../../../styles/viewers' as *;
|
||||
|
||||
.container__gray,
|
||||
.container__grayFinished {
|
||||
@include viewer-container;
|
||||
|
||||
display: grid;
|
||||
grid-template-columns: 20vw 20vw 18vw 3vw 1fr;
|
||||
grid-template-rows: 60vh 1fr 1fr 1fr;
|
||||
grid-template-areas:
|
||||
' pip pip pip . schd'
|
||||
' titl titl titl . schd'
|
||||
' info info clck . schd'
|
||||
' info info time . schd';
|
||||
gap: 1vw;
|
||||
padding: 1vw;
|
||||
}
|
||||
|
||||
.eventTitle {
|
||||
grid-area: titl;
|
||||
@include viewer-event-title;
|
||||
}
|
||||
|
||||
.pip {
|
||||
grid-area: pip;
|
||||
background-color: $bg-black-100;
|
||||
border: 1px solid $bg-black-200;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
display: grid;
|
||||
place-content: center;
|
||||
}
|
||||
|
||||
.empty {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.piptext {
|
||||
color: $bg-gray-900;
|
||||
font-weight: 600;
|
||||
font-size: 4vh;
|
||||
}
|
||||
|
||||
.infoContainer,
|
||||
.clockContainer,
|
||||
.countdownContainer {
|
||||
background-color: $bg-gray-1000;
|
||||
padding: 1vh 1vw;
|
||||
}
|
||||
|
||||
.todayContainer {
|
||||
grid-area: schd;
|
||||
}
|
||||
|
||||
.countdownContainer {
|
||||
grid-area: clck;
|
||||
border-radius: 1vw 1vw 0 0;
|
||||
}
|
||||
|
||||
.clockContainer {
|
||||
border-radius: 0 0 1vw 1vw;
|
||||
}
|
||||
@@ -65,51 +65,51 @@ const Lower = (props) => {
|
||||
// Check for user options
|
||||
useEffect(() => {
|
||||
// create aux
|
||||
let options = {};
|
||||
const options = {};
|
||||
|
||||
// preset: selector
|
||||
// Should be a number 1-n
|
||||
let p = parseInt(searchParams.get('preset'));
|
||||
const p = parseInt(searchParams.get('preset'));
|
||||
if (!isNaN(p)) setPreset(p);
|
||||
|
||||
// size: multiplier
|
||||
// Should be a number 0.0-n
|
||||
let s = searchParams.get('size');
|
||||
const s = searchParams.get('size');
|
||||
if (s) options.size = s;
|
||||
|
||||
// transitionIn: seconds
|
||||
// Should be a number 0-n
|
||||
let t = parseInt(searchParams.get('transition'));
|
||||
const t = parseInt(searchParams.get('transition'));
|
||||
if (!isNaN(t)) options.transitionIn = t;
|
||||
|
||||
// textColour: string
|
||||
// Should be a hex string '#ffffff'
|
||||
let c = searchParams.get('text');
|
||||
const c = searchParams.get('text');
|
||||
if (c) options.textColour = `#${c}`;
|
||||
|
||||
// bgColour: string
|
||||
// Should be a hex string '#ffffff'
|
||||
let b = searchParams.get('bg');
|
||||
const b = searchParams.get('bg');
|
||||
if (b) options.bgColour = `#${b}`;
|
||||
|
||||
// key: string
|
||||
// Should be a hex string '#00FF00' with key colour
|
||||
let k = searchParams.get('key');
|
||||
const k = searchParams.get('key');
|
||||
if (k) options.keyColour = `#${k}`;
|
||||
|
||||
// fadeOut: seconds
|
||||
// Should be a number 0-n
|
||||
let f = parseInt(searchParams.get('fadeout'));
|
||||
const f = parseInt(searchParams.get('fadeout'));
|
||||
if (!isNaN(f)) options.fadeOut = f;
|
||||
|
||||
// x: pixels
|
||||
// Should be a number 0-n
|
||||
let x = parseInt(searchParams.get('x'));
|
||||
const x = parseInt(searchParams.get('x'));
|
||||
if (!isNaN(x)) options.posX = x;
|
||||
|
||||
// y: pixels
|
||||
// Should be a number 0-n
|
||||
let y = parseInt(searchParams.get('y'));
|
||||
const y = parseInt(searchParams.get('y'));
|
||||
if (!isNaN(y)) options.posY = y;
|
||||
|
||||
setLowerOptions({
|
||||
|
||||
@@ -18,7 +18,7 @@ export default function StudioClock(props) {
|
||||
|
||||
const activeIndicators = [...Array(12).keys()];
|
||||
const secondsIndicators = [...Array(60).keys()];
|
||||
const MAX_TITLES = 8;
|
||||
const MAX_TITLES = 10;
|
||||
|
||||
// Set window title
|
||||
useEffect(() => {
|
||||
@@ -45,7 +45,7 @@ export default function StudioClock(props) {
|
||||
<div
|
||||
ref={ref}
|
||||
className={style.nextTitle}
|
||||
style={{ fontSize, height: '100px', width: '100%', maxWidth: '680px' }}
|
||||
style={{ fontSize, height: '10vh', width: '100%', maxWidth: '82%' }}
|
||||
>
|
||||
{title.titleNext}
|
||||
</div>
|
||||
@@ -58,7 +58,7 @@ export default function StudioClock(props) {
|
||||
key={i}
|
||||
className={style.hours__active}
|
||||
style={{
|
||||
transform: `rotate(${(360 / 12) * i - 90}deg) translateX(380px)`,
|
||||
transform: `rotate(${(360 / 12) * i - 90}deg) translateX(40vh)`,
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
@@ -67,7 +67,7 @@ export default function StudioClock(props) {
|
||||
key={i}
|
||||
className={i <= secondsNow ? style.min__active : style.min}
|
||||
style={{
|
||||
transform: `rotate(${(360 / 60) * i - 90}deg) translateX(415px)`,
|
||||
transform: `rotate(${(360 / 60) * i - 90}deg) translateX(43vh)`,
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
@@ -94,8 +94,8 @@ export default function StudioClock(props) {
|
||||
}
|
||||
|
||||
StudioClock.propTypes = {
|
||||
title: PropTypes.string,
|
||||
time: PropTypes.number,
|
||||
title: PropTypes.object,
|
||||
time: PropTypes.object,
|
||||
backstageEvents: PropTypes.array,
|
||||
selectedId: PropTypes.string,
|
||||
nextId: PropTypes.string,
|
||||
|
||||
@@ -3,6 +3,18 @@
|
||||
src: local('digital-7'), url('./../../../assets/fonts/digital-7.monoitalic.ttf') format('truetype') ;
|
||||
}
|
||||
|
||||
/* =============== CLOCK STUFF ==================*/
|
||||
|
||||
$clock-size: 90vh;
|
||||
$size-hours: min(3vh, 20px);
|
||||
$half-hours: min(1.5vh, 10px);
|
||||
$size-min: min(2.5vh, 18px);
|
||||
$half-min: min(1.25vh, 9px);
|
||||
$red-active: #c53030;
|
||||
$red-idle: #300000;
|
||||
$cyan-active: #0ff;
|
||||
$cyan-idle: #0aa;
|
||||
|
||||
.container {
|
||||
margin: 0;
|
||||
box-sizing: border-box; /* reset */
|
||||
@@ -14,66 +26,27 @@
|
||||
background: #000;
|
||||
|
||||
display: grid;
|
||||
grid-template-columns: 1000px 1fr;
|
||||
gap: 50px;
|
||||
grid-template-columns: 95vh 1fr;
|
||||
gap: 2vw;
|
||||
grid-template-areas: "clck schd";
|
||||
|
||||
/* =============== CLOCK STUFF ==================*/
|
||||
|
||||
$clock-size: 900px;
|
||||
$size-hours: 20px;
|
||||
$half-hours: 10px;
|
||||
$size-min: 18px;
|
||||
$half-min: 9px;
|
||||
$red-active: #c53030;
|
||||
$red-idle: #300000;
|
||||
$cyan-active: #0ff;
|
||||
$cyan-idle: #0aa;
|
||||
|
||||
.clockContainer {
|
||||
display: grid;
|
||||
place-content: center;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
grid-area: clck;
|
||||
width: $clock-size;
|
||||
height: $clock-size;
|
||||
aspect-ratio: 1;
|
||||
text-align: center;
|
||||
position: relative;
|
||||
margin: auto;
|
||||
margin: 4vh auto;
|
||||
font-family: digital-clock, monospace;
|
||||
text-transform: uppercase;
|
||||
|
||||
.time {
|
||||
margin-top: 175px;
|
||||
color: $red-active;
|
||||
font-size: 300px;
|
||||
line-height: 0.8em;
|
||||
}
|
||||
.nextTitle:after,
|
||||
.nextCountdown:after,
|
||||
.nextCountdown__overtime:after {
|
||||
content: '\200b';
|
||||
}
|
||||
.nextTitle {
|
||||
color:$cyan-idle;
|
||||
line-height: 100px;
|
||||
}
|
||||
.nextCountdown,
|
||||
.nextCountdown__overtime {
|
||||
font-size: 100px;
|
||||
line-height: 1em;
|
||||
}
|
||||
.nextCountdown {
|
||||
color: $cyan-active;
|
||||
text-shadow: rgb(0,100,100) 0 0 20px;
|
||||
}
|
||||
.nextCountdown::before {
|
||||
content: '-';
|
||||
}
|
||||
.nextCountdown__overtime {
|
||||
color: darken($red-active, 10%);
|
||||
}
|
||||
.indicators {
|
||||
.indicators {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
@@ -85,6 +58,7 @@
|
||||
position: absolute;
|
||||
background: $red-idle;
|
||||
}
|
||||
|
||||
.min,
|
||||
.min__active {
|
||||
min-height: $size-min;
|
||||
@@ -92,60 +66,100 @@
|
||||
top: calc(50% - #{$half_min});
|
||||
left: calc(50% - #{$half_min});
|
||||
}
|
||||
|
||||
.hours,
|
||||
.hours__active{
|
||||
min-height:$size-hours;
|
||||
.hours__active {
|
||||
min-height: $size-hours;
|
||||
width: $size-hours;
|
||||
top: calc(50% - #{$half_hours});
|
||||
left: calc(50% - #{$half_hours});
|
||||
}
|
||||
|
||||
.min__active,
|
||||
.hours__active {
|
||||
background: $red-active;
|
||||
box-shadow: 0 0 10px 2px rgba(255,0,0,0.25);
|
||||
box-shadow: 0 0 10px 2px rgba(255, 0, 0, 0.25);
|
||||
}
|
||||
}
|
||||
|
||||
.time {
|
||||
color: $red-active;
|
||||
font-size: calc(#{$clock-size} / 3);
|
||||
margin-top: calc(50% - calc(#{$clock-size} / 7));
|
||||
line-height: 0.8em;
|
||||
}
|
||||
|
||||
.nextTitle:after,
|
||||
.nextCountdown:after,
|
||||
.nextCountdown__overtime:after {
|
||||
content: '\200b';
|
||||
}
|
||||
|
||||
.nextTitle {
|
||||
color: $cyan-idle;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.nextCountdown,
|
||||
.nextCountdown__overtime {
|
||||
font-size: 10vh;
|
||||
line-height: 1em;
|
||||
}
|
||||
|
||||
.nextCountdown {
|
||||
color: $cyan-active;
|
||||
text-shadow: rgb(0, 100, 100) 0 0 20px;
|
||||
}
|
||||
|
||||
.nextCountdown::before {
|
||||
content: '-';
|
||||
}
|
||||
|
||||
.nextCountdown__overtime {
|
||||
color: darken($red-active, 10%);
|
||||
}
|
||||
}
|
||||
|
||||
/* ============= SCHEDULE STUFF =================*/
|
||||
|
||||
.scheduleContainer {
|
||||
grid-area: schd;
|
||||
margin: 50px 0;
|
||||
padding-right: 50px;
|
||||
margin: 4vh 0;
|
||||
font-family: digital-clock, monospace;
|
||||
text-transform: uppercase;
|
||||
|
||||
.onAir,
|
||||
.onAir__idle{
|
||||
padding-bottom: 50px;
|
||||
font-size: 170px;
|
||||
.onAir__idle {
|
||||
padding-bottom: 2vh;
|
||||
font-size: 15vh;
|
||||
line-height: 0.9em;
|
||||
}
|
||||
|
||||
.onAir {
|
||||
color: $red-active;
|
||||
}
|
||||
|
||||
.onAir__idle {
|
||||
color: $red-idle;
|
||||
}
|
||||
|
||||
.schedule {
|
||||
|
||||
ul {
|
||||
color: $cyan-idle;
|
||||
font-size: 3.75vh;
|
||||
line-height: 1em;
|
||||
list-style: none;
|
||||
font-size: 40px;
|
||||
}
|
||||
|
||||
li {
|
||||
margin-bottom: 0.5em;
|
||||
padding-left: 0.5em;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 20px;
|
||||
margin-bottom: 1.5vh;
|
||||
}
|
||||
|
||||
.now {
|
||||
color: $cyan-active;
|
||||
}
|
||||
|
||||
.next {
|
||||
color: $red-active;
|
||||
}
|
||||
@@ -153,14 +167,14 @@
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 1600px) {
|
||||
@media only screen and (max-width: 1200px) {
|
||||
.container {
|
||||
display: grid;
|
||||
grid-template-areas: "clck";
|
||||
grid-template-columns: 100%;
|
||||
place-content: center;
|
||||
}
|
||||
.scheduleContainer{
|
||||
.scheduleContainer {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ export default function MinimalTimer(props) {
|
||||
const showOverlay = pres.text !== '' && pres.visible;
|
||||
const isPlaying = time.playstate !== 'pause';
|
||||
const timer = formatDisplay(time.running, true);
|
||||
const clean = timer.replaceAll(':', '');
|
||||
const clean = timer.replace('/:/g', '');
|
||||
|
||||
return (
|
||||
<div className={time.finished ? style.containerFinished : style.container}>
|
||||
|
||||
@@ -43,7 +43,7 @@
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: rgba(0, 0, 0, 0.85);
|
||||
background-color: $bg-overlay;
|
||||
z-index: -1;
|
||||
opacity: 0;
|
||||
transition: 0.5s;
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
overflow: hidden;
|
||||
width: 100%; /* restrict the page width to viewport */
|
||||
|
||||
background: radial-gradient(circle, $bg-black-gradient 0%, $bg-black 80%);
|
||||
background: $bg-black;
|
||||
height: 100vh;
|
||||
color: $title-white;
|
||||
display: grid;
|
||||
@@ -31,7 +31,7 @@
|
||||
|
||||
.nowContainer,
|
||||
.nextContainer {
|
||||
background-color: rgba(255, 255, 255, 0.05);
|
||||
background-color: $bg-gray-1000;
|
||||
padding: 1vh 2vw;
|
||||
border-radius: 1vw;
|
||||
max-width: 100%;
|
||||
@@ -98,7 +98,7 @@
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: rgba(0, 0, 0, 0.85);
|
||||
background-color: $bg-overlay;
|
||||
z-index: -1;
|
||||
opacity: 0;
|
||||
transition: 0.5s;
|
||||
|
||||
Reference in New Issue
Block a user