mirror of
https://github.com/cpvalente/ontime.git
synced 2026-09-09 16:19:47 +00:00
styles: v2 (#229)
* style: revise selected style * style: revise editor styles * style: revise viewer styles * chore: update tests
This commit is contained in:
@@ -26,9 +26,10 @@ export default function ActionButtons(props: ActionButtonProps) {
|
|||||||
<MenuButton
|
<MenuButton
|
||||||
as={IconButton}
|
as={IconButton}
|
||||||
aria-label='Options'
|
aria-label='Options'
|
||||||
size='xs'
|
size='sm'
|
||||||
icon={<FiPlus />}
|
icon={<FiPlus />}
|
||||||
colorScheme='blue'
|
colorScheme='blue'
|
||||||
|
variant='ghost'
|
||||||
/>
|
/>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<MenuList style={menuStyle}>
|
<MenuList style={menuStyle}>
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { IconButton, Tooltip } from '@chakra-ui/react';
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
export default function TooltipLoadingActionBtn(props) {
|
export default function TooltipLoadingActionBtn(props) {
|
||||||
const { clickHandler, icon, color, size = 'xs', tooltip, ...rest } = props;
|
const { clickHandler, icon, size = 'xs', tooltip, ...rest } = props;
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
|
|
||||||
const handleClick = useCallback(() => {
|
const handleClick = useCallback(() => {
|
||||||
@@ -29,7 +29,6 @@ export default function TooltipLoadingActionBtn(props) {
|
|||||||
TooltipLoadingActionBtn.propTypes = {
|
TooltipLoadingActionBtn.propTypes = {
|
||||||
clickHandler: PropTypes.func,
|
clickHandler: PropTypes.func,
|
||||||
icon: PropTypes.element,
|
icon: PropTypes.element,
|
||||||
color: PropTypes.string,
|
|
||||||
size: PropTypes.oneOf(['xs', 'sm', 'md', 'lg']),
|
size: PropTypes.oneOf(['xs', 'sm', 'md', 'lg']),
|
||||||
tooltip: PropTypes.string,
|
tooltip: PropTypes.string,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -5,10 +5,10 @@ import PropTypes from 'prop-types';
|
|||||||
import style from './CollapseBar.module.scss';
|
import style from './CollapseBar.module.scss';
|
||||||
|
|
||||||
export default function CollapseBar(props) {
|
export default function CollapseBar(props) {
|
||||||
const { title = 'Collapse bar', isCollapsed, onClick, roll } = props;
|
const { title = 'Collapse bar', isCollapsed, onClick } = props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={roll ? style.headerRoll : style.header}>
|
<div className={style.header}>
|
||||||
{title}
|
{title}
|
||||||
<Icon
|
<Icon
|
||||||
className={isCollapsed ? style.moreCollapsed : style.moreExpanded}
|
className={isCollapsed ? style.moreCollapsed : style.moreExpanded}
|
||||||
@@ -22,5 +22,4 @@ CollapseBar.propTypes = {
|
|||||||
title: PropTypes.string,
|
title: PropTypes.string,
|
||||||
isCollapsed: PropTypes.bool,
|
isCollapsed: PropTypes.bool,
|
||||||
onClick: PropTypes.func,
|
onClick: PropTypes.func,
|
||||||
roll: PropTypes.bool,
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
input[type="color"] {
|
input[type="color"] {
|
||||||
appearance: none;
|
appearance: none;
|
||||||
background-color: $action-blue;
|
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
height: 32px;
|
height: 32px;
|
||||||
width: 32px;
|
width: 32px;
|
||||||
|
|||||||
@@ -133,7 +133,7 @@ export default function TimeInput(props) {
|
|||||||
size='sm'
|
size='sm'
|
||||||
icon={<IoLink style={{ transform: 'rotate(-45deg)' }} />}
|
icon={<IoLink style={{ transform: 'rotate(-45deg)' }} />}
|
||||||
aria-label='automate'
|
aria-label='automate'
|
||||||
colorScheme='blue'
|
colorScheme='whiteAlpha'
|
||||||
style={{ borderRadius: '2px', width: 'min-content' }}
|
style={{ borderRadius: '2px', width: 'min-content' }}
|
||||||
tabIndex={-1}
|
tabIndex={-1}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -2,9 +2,8 @@
|
|||||||
|
|
||||||
.emptyContainer {
|
.emptyContainer {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
|
||||||
text-align: center;
|
text-align: center;
|
||||||
color: $bg-black-300;
|
color: $bg-black-200;
|
||||||
|
|
||||||
.empty {
|
.empty {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ describe('Message Control input blocks', () => {
|
|||||||
// Presenter dialog and button
|
// Presenter dialog and button
|
||||||
// substring match, ignore case
|
// substring match, ignore case
|
||||||
render(<MockMessageControl />);
|
render(<MockMessageControl />);
|
||||||
expect(screen.getByPlaceholderText(/presenter/i)).toBeInTheDocument();
|
expect(screen.getByPlaceholderText(/stage/i)).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Public dialog', async () => {
|
test('Public dialog', async () => {
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ export default function InputRow(props: InputRowProps) {
|
|||||||
value={inputText}
|
value={inputText}
|
||||||
placeholder={placeholder}
|
placeholder={placeholder}
|
||||||
className={style.inline}
|
className={style.inline}
|
||||||
color={text === '' ? '#666' : 'inherit'}
|
color={text === '' ? '#505050' : 'inherit'}
|
||||||
>
|
>
|
||||||
<EditablePreview className={`${style.padleft} ${style.fullWidth}`} />
|
<EditablePreview className={`${style.padleft} ${style.fullWidth}`} />
|
||||||
<EditableInput className={style.padleft} />
|
<EditableInput className={style.padleft} />
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ export default function MessageControl() {
|
|||||||
<div className={style.messageContainer}>
|
<div className={style.messageContainer}>
|
||||||
<InputRow
|
<InputRow
|
||||||
label='Timer screen message'
|
label='Timer screen message'
|
||||||
placeholder='only the presenter screens see this'
|
placeholder='Shown in stage timer'
|
||||||
text={data.presenter.text}
|
text={data.presenter.text}
|
||||||
visible={data.presenter.visible}
|
visible={data.presenter.visible}
|
||||||
changeHandler={(newValue) => setMessage.presenterText(newValue)}
|
changeHandler={(newValue) => setMessage.presenterText(newValue)}
|
||||||
@@ -25,7 +25,7 @@ export default function MessageControl() {
|
|||||||
/>
|
/>
|
||||||
<InputRow
|
<InputRow
|
||||||
label='Public screen message'
|
label='Public screen message'
|
||||||
placeholder='public screens will render this'
|
placeholder='Shown in public screens'
|
||||||
text={data.public.text}
|
text={data.public.text}
|
||||||
visible={data.public.visible}
|
visible={data.public.visible}
|
||||||
changeHandler={(newValue) => setMessage.publicText(newValue)}
|
changeHandler={(newValue) => setMessage.publicText(newValue)}
|
||||||
@@ -33,7 +33,7 @@ export default function MessageControl() {
|
|||||||
/>
|
/>
|
||||||
<InputRow
|
<InputRow
|
||||||
label='Lower third message'
|
label='Lower third message'
|
||||||
placeholder='visible in lower third screen'
|
placeholder='Shown in lower third'
|
||||||
text={data.lower.text}
|
text={data.lower.text}
|
||||||
visible={data.lower.visible}
|
visible={data.lower.visible}
|
||||||
changeHandler={(newValue) => setMessage.lowerText(newValue)}
|
changeHandler={(newValue) => setMessage.lowerText(newValue)}
|
||||||
|
|||||||
@@ -3,7 +3,6 @@
|
|||||||
|
|
||||||
.messageContainer,
|
.messageContainer,
|
||||||
.onAirToggle {
|
.onAirToggle {
|
||||||
@include main-container;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 0.5em;
|
gap: 0.5em;
|
||||||
padding: 0.5em;
|
padding: 0.5em;
|
||||||
@@ -47,7 +46,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.inline {
|
.inline {
|
||||||
background-color: $light-bg-transparent;
|
|
||||||
border-color: $action-blue;
|
border-color: $action-blue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,8 +10,7 @@ import style from '../../editors/Editor.module.scss';
|
|||||||
|
|
||||||
export default function MessageControlExport() {
|
export default function MessageControlExport() {
|
||||||
return (
|
return (
|
||||||
<Box className={style.messages}>
|
<Box className={style.messages} data-testid="panel-messages-control">
|
||||||
<h1>Messages Control</h1>
|
|
||||||
<FiArrowUpRight className={style.corner} onClick={(event) => handleLinks(event, 'messagecontrol')} />
|
<FiArrowUpRight className={style.corner} onClick={(event) => handleLinks(event, 'messagecontrol')} />
|
||||||
<div className={style.content}>
|
<div className={style.content}>
|
||||||
<ErrorBoundary>
|
<ErrorBoundary>
|
||||||
|
|||||||
@@ -8,9 +8,7 @@
|
|||||||
gap: 4px;
|
gap: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.timeContainer,
|
|
||||||
.playbackContainer {
|
.playbackContainer {
|
||||||
@include second-container;
|
|
||||||
padding: 0.5em;
|
padding: 0.5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,8 +10,7 @@ import style from '../../editors/Editor.module.scss';
|
|||||||
|
|
||||||
export default function TimerControlExport() {
|
export default function TimerControlExport() {
|
||||||
return (
|
return (
|
||||||
<Box className={style.playback}>
|
<Box className={style.playback} data-testid="panel-timer-control">
|
||||||
<h1>Timer Control</h1>
|
|
||||||
<FiArrowUpRight className={style.corner} onClick={(event) => handleLinks(event, 'timercontrol')} />
|
<FiArrowUpRight className={style.corner} onClick={(event) => handleLinks(event, 'timercontrol')} />
|
||||||
<div className={style.content}>
|
<div className={style.content}>
|
||||||
<ErrorBoundary>
|
<ErrorBoundary>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { IoArrowUndo } from '@react-icons/all-files/io5/IoArrowUndo';
|
import { IoPlayBack } from '@react-icons/all-files/io5/IoPlayBack';
|
||||||
import { IoPlaySkipBack } from '@react-icons/all-files/io5/IoPlaySkipBack';
|
import { IoPlaySkipBack } from '@react-icons/all-files/io5/IoPlaySkipBack';
|
||||||
import { IoPlaySkipForward } from '@react-icons/all-files/io5/IoPlaySkipForward';
|
import { IoPlaySkipForward } from '@react-icons/all-files/io5/IoPlaySkipForward';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
@@ -30,7 +30,7 @@ export default function Transport(props) {
|
|||||||
clickHandler={() => playbackControl.reload()}
|
clickHandler={() => playbackControl.reload()}
|
||||||
disabled={selectedId == null || isRolling || noEvents}
|
disabled={selectedId == null || isRolling || noEvents}
|
||||||
tooltip='Reload event'
|
tooltip='Reload event'
|
||||||
icon={<IoArrowUndo size='22px' />}
|
icon={<IoPlayBack size='22px' />}
|
||||||
/>
|
/>
|
||||||
<UnloadIconBtn
|
<UnloadIconBtn
|
||||||
clickHandler={() => playbackControl.stop()}
|
clickHandler={() => playbackControl.stop()}
|
||||||
|
|||||||
@@ -114,12 +114,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
h1 {
|
|
||||||
font-size: max(1.5em, 16px);
|
|
||||||
color: $bg-gray-100;
|
|
||||||
padding-bottom: 0.25em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mainContainer {
|
.mainContainer {
|
||||||
.settings,
|
.settings,
|
||||||
.editor,
|
.editor,
|
||||||
@@ -128,9 +122,8 @@ h1 {
|
|||||||
.info {
|
.info {
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
background-color: $bg-gray-1000;
|
background-color: $bg-container-l1;
|
||||||
padding: 0.8em 1.5em;
|
padding: 0.8em 1.5em;
|
||||||
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
@@ -138,9 +131,8 @@ h1 {
|
|||||||
|
|
||||||
.eventEditor {
|
.eventEditor {
|
||||||
border-radius: 3px 3px 0 0;
|
border-radius: 3px 3px 0 0;
|
||||||
background-color: $bg-gray-950;
|
background-color: $bg-container-l1;
|
||||||
border-top: 1px solid $bg-gray-900;
|
border-top: $border-l1;
|
||||||
padding: 0.8em 1.5em;
|
|
||||||
position: absolute;
|
position: absolute;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
width: 100vw;
|
width: 100vw;
|
||||||
@@ -155,14 +147,14 @@ h1 {
|
|||||||
transition: bottom 0.7s;
|
transition: bottom 0.7s;
|
||||||
}
|
}
|
||||||
|
|
||||||
.header {
|
.eventEditorLayout {
|
||||||
display: flex;
|
display: flex;
|
||||||
}
|
}
|
||||||
|
|
||||||
.header {
|
.header {
|
||||||
h1 {
|
background-color: $bg-container-l2;
|
||||||
margin-right: auto;
|
padding: 8px;
|
||||||
}
|
border-left: 1px solid $bg-container-l3;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -194,8 +186,8 @@ h1 {
|
|||||||
|
|
||||||
.playback {
|
.playback {
|
||||||
grid-area: play;
|
grid-area: play;
|
||||||
min-height: 320px;
|
min-height: 270px;
|
||||||
max-height: 320px;
|
max-height: 270px;
|
||||||
min-width: 480px;
|
min-width: 480px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -211,5 +203,5 @@ h1 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.content {
|
.content {
|
||||||
padding-top: 0.5em;
|
padding-top: 24px;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ export default function Editor() {
|
|||||||
<ErrorBoundary>
|
<ErrorBoundary>
|
||||||
<ModalManager isOpen={isSettingsOpen} onClose={onSettingsClose} />
|
<ModalManager isOpen={isSettingsOpen} onClose={onSettingsClose} />
|
||||||
</ErrorBoundary>
|
</ErrorBoundary>
|
||||||
<div className={styles.mainContainer}>
|
<div className={styles.mainContainer} data-testid="event-editor">
|
||||||
<Box id='settings' className={styles.settings}>
|
<Box id='settings' className={styles.settings}>
|
||||||
<ErrorBoundary>
|
<ErrorBoundary>
|
||||||
<MenuBar
|
<MenuBar
|
||||||
|
|||||||
@@ -23,9 +23,11 @@ export default function BlockBlock(props) {
|
|||||||
<TooltipLoadingActionBtn
|
<TooltipLoadingActionBtn
|
||||||
clickHandler={() => actionHandler('delete')}
|
clickHandler={() => actionHandler('delete')}
|
||||||
icon={<IoRemove />}
|
icon={<IoRemove />}
|
||||||
colorScheme='red'
|
|
||||||
tooltip='Delete'
|
tooltip='Delete'
|
||||||
_hover={{ bg: 'red.400' }}
|
variant='ghost'
|
||||||
|
_hover={{ bg: 'red.400', color: 'white' }}
|
||||||
|
color='red.500'
|
||||||
|
size='sm'
|
||||||
/>
|
/>
|
||||||
<ActionButtons showAdd showDelay actionHandler={actionHandler} />
|
<ActionButtons showAdd showDelay actionHandler={actionHandler} />
|
||||||
</HStack>
|
</HStack>
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ export default function DelayBlock(props) {
|
|||||||
<HStack spacing='4px' className={style.actionOverlay}>
|
<HStack spacing='4px' className={style.actionOverlay}>
|
||||||
<Button
|
<Button
|
||||||
onClick={applyDelayHandler}
|
onClick={applyDelayHandler}
|
||||||
size='xs'
|
size='sm'
|
||||||
colorScheme='orange'
|
colorScheme='orange'
|
||||||
_hover={{ bg: 'orange.400' }}
|
_hover={{ bg: 'orange.400' }}
|
||||||
leftIcon={<FiCheck />}
|
leftIcon={<FiCheck />}
|
||||||
@@ -64,9 +64,11 @@ export default function DelayBlock(props) {
|
|||||||
<TooltipLoadingActionBtn
|
<TooltipLoadingActionBtn
|
||||||
clickHandler={deleteHandler}
|
clickHandler={deleteHandler}
|
||||||
icon={<IoRemove />}
|
icon={<IoRemove />}
|
||||||
colorScheme='red'
|
|
||||||
tooltip='Delete'
|
tooltip='Delete'
|
||||||
_hover={{ bg: 'red.400' }}
|
variant='ghost'
|
||||||
|
_hover={{ bg: 'red.400', color: 'white' }}
|
||||||
|
color='red.500'
|
||||||
|
size='sm'
|
||||||
/>
|
/>
|
||||||
<ActionButtons showAdd actionHandler={actionHandler} />
|
<ActionButtons showAdd actionHandler={actionHandler} />
|
||||||
</HStack>
|
</HStack>
|
||||||
|
|||||||
@@ -11,7 +11,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.create {
|
.create {
|
||||||
background-color: rgba($bg-gray-1100, 0.85);
|
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|||||||
@@ -5,13 +5,12 @@
|
|||||||
// layout
|
// layout
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-areas:
|
grid-template-areas:
|
||||||
"binder progb progb progb"
|
|
||||||
"binder pb-actions times actions"
|
"binder pb-actions times actions"
|
||||||
"binder pb-actions title title"
|
"binder pb-actions title title"
|
||||||
"binder pb-actions notes status"
|
"binder pb-actions notes status"
|
||||||
"binder ... ... ...";
|
"binder progb progb progb";
|
||||||
grid-template-columns: $binder-width auto 1fr auto;
|
grid-template-columns: $binder-width auto 1fr auto;
|
||||||
grid-template-rows: $element-spacing 36px 36px 36px $element-spacing;
|
grid-template-rows: 36px 36px 36px $element-spacing;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
margin: 2px 1px;
|
margin: 2px 1px;
|
||||||
padding-right: $clearance;
|
padding-right: $clearance;
|
||||||
@@ -21,16 +20,13 @@
|
|||||||
gap: 2px;
|
gap: 2px;
|
||||||
|
|
||||||
// style - colour
|
// style - colour
|
||||||
background-color: $bg-gray-950;
|
background-color: $bg-container-l2;
|
||||||
border: $block-border;
|
border: $border-l3;
|
||||||
|
|
||||||
// style - text
|
|
||||||
|
|
||||||
|
|
||||||
// variant
|
// variant
|
||||||
&.selected {
|
&.selected {
|
||||||
background-color: $bg-gray-900;
|
background-color: $bg-container-l3;
|
||||||
border: 1px solid $ontime-accent;
|
box-shadow: 0 0 1px 2px $ontime-accent;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.skip {
|
&.skip {
|
||||||
@@ -39,8 +35,7 @@
|
|||||||
|
|
||||||
.eventTimers > .delayNote,
|
.eventTimers > .delayNote,
|
||||||
.eventTitle,
|
.eventTitle,
|
||||||
.eventNote,
|
.eventNote {
|
||||||
{
|
|
||||||
color: $text-gray-disabled;
|
color: $text-gray-disabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -65,11 +60,9 @@
|
|||||||
border-right: 1px solid $bg-gray-900;
|
border-right: 1px solid $bg-gray-900;
|
||||||
|
|
||||||
// style - colour
|
// style - colour
|
||||||
background-color: $bg-gray-950; // override inline
|
background-color: $bg-container-l3; // override inline
|
||||||
color: $text-white;
|
color: $text-white;
|
||||||
|
|
||||||
// style - text
|
|
||||||
|
|
||||||
.drag {
|
.drag {
|
||||||
@include drag-style;
|
@include drag-style;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
@@ -184,7 +177,9 @@
|
|||||||
.progressBg {
|
.progressBg {
|
||||||
grid-area: progb;
|
grid-area: progb;
|
||||||
border-radius: 1px;
|
border-radius: 1px;
|
||||||
background-color: $bg-black-100;
|
background-color: $bg-container-l1;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
margin-left: 4px;
|
||||||
|
|
||||||
// layout
|
// layout
|
||||||
height: 100%;
|
height: 100%;
|
||||||
@@ -194,8 +189,6 @@
|
|||||||
height: 100%;
|
height: 100%;
|
||||||
width: 0;
|
width: 0;
|
||||||
border-radius: 1px 0 0 1px;
|
border-radius: 1px 0 0 1px;
|
||||||
margin-top: 2px;
|
|
||||||
margin-left: 4px;
|
|
||||||
|
|
||||||
// animations
|
// animations
|
||||||
transition: 1s linear;
|
transition: 1s linear;
|
||||||
|
|||||||
@@ -1,14 +1,16 @@
|
|||||||
import { useCallback, useState } from 'react';
|
import { useCallback, useState } from 'react';
|
||||||
import { Draggable } from 'react-beautiful-dnd';
|
import { Draggable } from 'react-beautiful-dnd';
|
||||||
import { Editable, EditableInput, EditablePreview, IconButton, Tooltip } from '@chakra-ui/react';
|
import { Editable, EditableInput, EditablePreview, Tooltip } from '@chakra-ui/react';
|
||||||
import { FiUsers } from '@react-icons/all-files/fi/FiUsers';
|
import { FiUsers } from '@react-icons/all-files/fi/FiUsers';
|
||||||
|
import { IoOptions } from '@react-icons/all-files/io5/IoOptions';
|
||||||
import { IoPause } from '@react-icons/all-files/io5/IoPause';
|
import { IoPause } from '@react-icons/all-files/io5/IoPause';
|
||||||
import { IoPlay } from '@react-icons/all-files/io5/IoPlay';
|
import { IoPlayBackOutline } from '@react-icons/all-files/io5/IoPlayBackOutline';
|
||||||
|
import { IoPlayOutline } from '@react-icons/all-files/io5/IoPlayOutline';
|
||||||
import { IoReload } from '@react-icons/all-files/io5/IoReload';
|
import { IoReload } from '@react-icons/all-files/io5/IoReload';
|
||||||
import { IoRemoveCircleSharp } from '@react-icons/all-files/io5/IoRemoveCircleSharp';
|
import { IoRemoveCircle } from '@react-icons/all-files/io5/IoRemoveCircle';
|
||||||
|
import { IoRemoveCircleOutline } from '@react-icons/all-files/io5/IoRemoveCircleOutline';
|
||||||
import { IoReorderTwo } from '@react-icons/all-files/io5/IoReorderTwo';
|
import { IoReorderTwo } from '@react-icons/all-files/io5/IoReorderTwo';
|
||||||
import { IoReturnDownForward } from '@react-icons/all-files/io5/IoReturnDownForward';
|
import { IoReturnDownForward } from '@react-icons/all-files/io5/IoReturnDownForward';
|
||||||
import { IoSettingsSharp } from '@react-icons/all-files/io5/IoSettingsSharp';
|
|
||||||
import { IoTimerOutline } from '@react-icons/all-files/io5/IoTimerOutline';
|
import { IoTimerOutline } from '@react-icons/all-files/io5/IoTimerOutline';
|
||||||
import { editorEventId } from 'common/atoms/LocalEventSettings';
|
import { editorEventId } from 'common/atoms/LocalEventSettings';
|
||||||
import TooltipActionBtn from 'common/components/buttons/TooltipActionBtn';
|
import TooltipActionBtn from 'common/components/buttons/TooltipActionBtn';
|
||||||
@@ -27,9 +29,8 @@ import style from './EventBlock.module.scss';
|
|||||||
|
|
||||||
const blockBtnStyle = {
|
const blockBtnStyle = {
|
||||||
size: 'sm',
|
size: 'sm',
|
||||||
colorScheme: 'blue',
|
colorScheme: 'white',
|
||||||
variant: 'outline',
|
variant: 'ghost',
|
||||||
borderRadius: '3px',
|
|
||||||
fontSize: '20px',
|
fontSize: '20px',
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -103,6 +104,12 @@ export default function EventBlock(props: EventBlockProps) {
|
|||||||
// Todo: data should come from socket
|
// Todo: data should come from socket
|
||||||
const progress = `${Math.random() * 100}%`;
|
const progress = `${Math.random() * 100}%`;
|
||||||
const eventIsPlaying = selected && playback === 'start';
|
const eventIsPlaying = selected && playback === 'start';
|
||||||
|
const playBtnStyles = { _hover: {} };
|
||||||
|
if (!skip && eventIsPlaying) {
|
||||||
|
playBtnStyles._hover = { bg: '#c05621' };
|
||||||
|
} else if (!skip && !eventIsPlaying) {
|
||||||
|
playBtnStyles._hover = { bg: '#4bffab' };
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Draggable key={eventId} draggableId={eventId} index={index}>
|
<Draggable key={eventId} draggableId={eventId} index={index}>
|
||||||
@@ -136,37 +143,37 @@ export default function EventBlock(props: EventBlockProps) {
|
|||||||
aria-label='Skip event'
|
aria-label='Skip event'
|
||||||
tooltip='Skip event'
|
tooltip='Skip event'
|
||||||
openDelay={tooltipDelayMid}
|
openDelay={tooltipDelayMid}
|
||||||
icon={<IoRemoveCircleSharp />}
|
icon={skip ? <IoRemoveCircle /> : <IoRemoveCircleOutline />}
|
||||||
{...blockBtnStyle}
|
{...blockBtnStyle}
|
||||||
variant={skip ? 'solid' : 'outline'}
|
variant={skip ? 'solid' : 'ghost'}
|
||||||
clickHandler={() => actionHandler('update', { field: 'skip', value: !skip })}
|
clickHandler={() => actionHandler('update', { field: 'skip', value: !skip })}
|
||||||
tabIndex={-1}
|
tabIndex={-1}
|
||||||
disabled={selected}
|
disabled={selected}
|
||||||
/>
|
/>
|
||||||
<TooltipActionBtn
|
<TooltipActionBtn
|
||||||
aria-label='Start event'
|
aria-label='Load event'
|
||||||
tooltip='Start event'
|
tooltip='Load event'
|
||||||
openDelay={tooltipDelayMid}
|
openDelay={tooltipDelayMid}
|
||||||
icon={eventIsPlaying ? <IoPause /> : <IoPlay />}
|
icon={selected ? <IoPlayBackOutline /> : <IoReload />}
|
||||||
disabled={skip}
|
disabled={skip}
|
||||||
{...blockBtnStyle}
|
{...blockBtnStyle}
|
||||||
variant={eventIsPlaying ? 'solid' : 'outline'}
|
clickHandler={() => setPlayback.loadEvent()}
|
||||||
|
tabIndex={-1}
|
||||||
|
/>
|
||||||
|
<TooltipActionBtn
|
||||||
|
aria-label='Toggle start / pause event'
|
||||||
|
tooltip={eventIsPlaying ? 'Pause event' : 'Start event'}
|
||||||
|
openDelay={tooltipDelayMid}
|
||||||
|
icon={eventIsPlaying ? <IoPause /> : <IoPlayOutline />}
|
||||||
|
disabled={skip}
|
||||||
|
{...blockBtnStyle}
|
||||||
|
variant={eventIsPlaying ? 'solid' : 'ghost'}
|
||||||
clickHandler={
|
clickHandler={
|
||||||
eventIsPlaying
|
eventIsPlaying
|
||||||
? () => setPlayback.pause()
|
? () => setPlayback.pause()
|
||||||
: () => setPlayback.startEvent()
|
: () => setPlayback.startEvent()
|
||||||
}
|
}
|
||||||
tabIndex={-1}
|
{...playBtnStyles}
|
||||||
/>
|
|
||||||
<TooltipActionBtn
|
|
||||||
aria-label='Load event'
|
|
||||||
tooltip='Load event'
|
|
||||||
openDelay={tooltipDelayMid}
|
|
||||||
icon={selected ? <IoReload /> : <IoReload />}
|
|
||||||
disabled={skip}
|
|
||||||
{...blockBtnStyle}
|
|
||||||
variant={selected ? 'solid' : 'outline'}
|
|
||||||
clickHandler={() => setPlayback.loadEvent()}
|
|
||||||
tabIndex={-1}
|
tabIndex={-1}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -190,13 +197,17 @@ export default function EventBlock(props: EventBlockProps) {
|
|||||||
</Editable>
|
</Editable>
|
||||||
<span className={style.eventNote}>{note}</span>
|
<span className={style.eventNote}>{note}</span>
|
||||||
<div className={style.eventActions}>
|
<div className={style.eventActions}>
|
||||||
<IconButton
|
<TooltipActionBtn
|
||||||
icon={<IoSettingsSharp />}
|
|
||||||
aria-label='event options'
|
|
||||||
onClick={() => setOpenId((prev) => prev === eventId ? null : eventId)}
|
|
||||||
{...blockBtnStyle}
|
{...blockBtnStyle}
|
||||||
variant={openId === eventId ? 'solid' : 'outline'}
|
size='sm'
|
||||||
|
icon={<IoOptions />}
|
||||||
|
clickHandler={() => setOpenId((prev) => prev === eventId ? null : eventId)}
|
||||||
|
tooltip='Event options'
|
||||||
|
aria-label='Event options'
|
||||||
tabIndex={-1}
|
tabIndex={-1}
|
||||||
|
backgroundColor={openId === eventId ? '#ebedf0' : 'transparent'}
|
||||||
|
color={openId === eventId ? '#333' : '#ebedf0'}
|
||||||
|
_hover={{ bg: '#ebedf0', color: '#333' }}
|
||||||
/>
|
/>
|
||||||
<EventBlockActionMenu
|
<EventBlockActionMenu
|
||||||
showAdd
|
showAdd
|
||||||
|
|||||||
@@ -27,9 +27,8 @@ export default function EventBlockActionMenu(props) {
|
|||||||
|
|
||||||
const blockBtnStyle = {
|
const blockBtnStyle = {
|
||||||
size: 'sm',
|
size: 'sm',
|
||||||
|
variant: 'ghost',
|
||||||
colorScheme: 'blue',
|
colorScheme: 'blue',
|
||||||
variant: 'outline',
|
|
||||||
borderRadius: '3px',
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { createRef, useCallback, useContext, useEffect } from 'react';
|
import { createRef, useCallback, useContext, useEffect } from 'react';
|
||||||
import { DragDropContext, Droppable } from 'react-beautiful-dnd';
|
import { DragDropContext, Droppable } from 'react-beautiful-dnd';
|
||||||
|
import { Button } from '@chakra-ui/react';
|
||||||
import {
|
import {
|
||||||
defaultPublicAtom,
|
defaultPublicAtom,
|
||||||
showQuickEntryAtom,
|
showQuickEntryAtom,
|
||||||
@@ -166,7 +167,12 @@ export default function EventList(props) {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (events.length < 1) {
|
if (events.length < 1) {
|
||||||
return <Empty text='No Events' style={{ marginTop: '10vh' }} />;
|
return (
|
||||||
|
<div className={style.alignCenter}>
|
||||||
|
<Empty text='No Events' style={{ marginTop: '7vh' }} />
|
||||||
|
<Button variant='solid' colorScheme='blue'>Create Event</Button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
let cumulativeDelay = 0;
|
let cumulativeDelay = 0;
|
||||||
let eventIndex = -1;
|
let eventIndex = -1;
|
||||||
|
|||||||
@@ -11,8 +11,7 @@ import style from '../Editor.module.scss';
|
|||||||
export default function EventListExport() {
|
export default function EventListExport() {
|
||||||
return (
|
return (
|
||||||
<CursorProvider>
|
<CursorProvider>
|
||||||
<Box className={style.editor}>
|
<Box className={style.editor} data-testid="panel-event-list">
|
||||||
<h1>Event List</h1>
|
|
||||||
<FiArrowUpRight
|
<FiArrowUpRight
|
||||||
className={style.corner}
|
className={style.corner}
|
||||||
onClick={(event) => handleLinks(event, 'eventlist')}
|
onClick={(event) => handleLinks(event, 'eventlist')}
|
||||||
|
|||||||
@@ -20,15 +20,13 @@ export default function EventListWrapper() {
|
|||||||
}, [emitError, isError]);
|
}, [emitError, isError]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<div className={styles.content}>
|
||||||
<EventListMenu />
|
<EventListMenu />
|
||||||
<div className={styles.content}>
|
{status === 'success' && data ? (
|
||||||
{status === 'success' && data ? (
|
<EventList events={data} />
|
||||||
<EventList events={data} />
|
) : (
|
||||||
) : (
|
<Empty text='Connecting to server' />
|
||||||
<Empty text='Connecting to server' />
|
)}
|
||||||
)}
|
</div>
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
@use '../../../theme/mixins' as *;
|
@use '../../../theme/mixins' as *;
|
||||||
|
|
||||||
.eventContainer {
|
.eventContainer {
|
||||||
@include second-container;
|
|
||||||
margin-top: 1em;
|
margin-top: 1em;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@@ -22,8 +21,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.cursor {
|
.cursor {
|
||||||
box-shadow: 2px 2px 0 $ontime-pink;
|
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
|
outline: 1px solid $action-blue;
|
||||||
}
|
}
|
||||||
|
|
||||||
.bgElement {
|
.bgElement {
|
||||||
@@ -31,3 +30,11 @@
|
|||||||
background: rgba(214, 158, 46, 0.3);
|
background: rgba(214, 158, 46, 0.3);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.alignCenter {
|
||||||
|
text-align: center;
|
||||||
|
flex-direction: column;
|
||||||
|
button {
|
||||||
|
margin-top: 24px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -162,7 +162,7 @@ export default function EventEditor() {
|
|||||||
leftIcon={<FiUsers />}
|
leftIcon={<FiUsers />}
|
||||||
size='sm'
|
size='sm'
|
||||||
colorScheme='blue'
|
colorScheme='blue'
|
||||||
variant={event.isPublic ? 'solid' : 'outline'}
|
variant={event.isPublic ? 'solid' : 'ghost'}
|
||||||
onClick={() => togglePublic(event.isPublic)}
|
onClick={() => togglePublic(event.isPublic)}
|
||||||
>
|
>
|
||||||
{event.isPublic ? 'Event is Public' : 'Make event public'}
|
{event.isPublic ? 'Event is Public' : 'Make event public'}
|
||||||
@@ -180,7 +180,7 @@ export default function EventEditor() {
|
|||||||
<Button
|
<Button
|
||||||
leftIcon={<IoBan />}
|
leftIcon={<IoBan />}
|
||||||
onClick={() => handleSubmit('colour', '')}
|
onClick={() => handleSubmit('colour', '')}
|
||||||
variant='outline'
|
variant='ghost'
|
||||||
colorScheme='blue'
|
colorScheme='blue'
|
||||||
borderRadius='3px'
|
borderRadius='3px'
|
||||||
size='sm'
|
size='sm'
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
@use '../../theme/main' as *;
|
@use '../../theme/main' as *;
|
||||||
|
|
||||||
.eventEditor {
|
.eventEditor {
|
||||||
|
padding: 16px 32px;
|
||||||
|
width: 100%;
|
||||||
overflow-y: scroll;
|
overflow-y: scroll;
|
||||||
max-height: 33vh;
|
max-height: 40vh;
|
||||||
gap: max(16px, 2vh);
|
gap: max(16px, 2vh);
|
||||||
|
|
||||||
display: grid;
|
display: grid;
|
||||||
@@ -11,7 +13,7 @@
|
|||||||
|
|
||||||
.timers,
|
.timers,
|
||||||
.titles {
|
.titles {
|
||||||
background-color: $bg-gray-1100;
|
background-color: $bg-container-l2;
|
||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
padding: 8px 16px;
|
padding: 8px 16px;
|
||||||
}
|
}
|
||||||
@@ -22,6 +24,9 @@
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
}
|
}
|
||||||
|
.timers label:nth-child(1) {
|
||||||
|
margin-top: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
.titles {
|
.titles {
|
||||||
grid-area: titles;
|
grid-area: titles;
|
||||||
@@ -36,6 +41,7 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
|
margin-top: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.left {
|
.left {
|
||||||
@@ -46,8 +52,7 @@
|
|||||||
.right {
|
.right {
|
||||||
padding-left: 16px;
|
padding-left: 16px;
|
||||||
grid-area: right;
|
grid-area: right;
|
||||||
border-left: 1px solid $bg-gray-900;
|
border-left: 1px solid $bg-container-l3;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.osc {
|
.osc {
|
||||||
|
|||||||
@@ -22,17 +22,18 @@ export default function InfoExport() {
|
|||||||
return (
|
return (
|
||||||
<Box className={`${style.eventEditor} ${!openId ? style.noEvent : ''}`}>
|
<Box className={`${style.eventEditor} ${!openId ? style.noEvent : ''}`}>
|
||||||
<ErrorBoundary>
|
<ErrorBoundary>
|
||||||
<div className={style.header}>
|
<div className={style.eventEditorLayout}>
|
||||||
<h1>Event Editor</h1>
|
|
||||||
<IconButton
|
|
||||||
aria-label='Close Menu'
|
|
||||||
icon={<FiX />}
|
|
||||||
onClick={() => setOpenId(null)}
|
|
||||||
{...closeBtnStyle}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className={style.content}>
|
|
||||||
<EventEditor />
|
<EventEditor />
|
||||||
|
<div className={style.header}>
|
||||||
|
<IconButton
|
||||||
|
aria-label='Close Menu'
|
||||||
|
size='lg'
|
||||||
|
icon={<FiX />}
|
||||||
|
onClick={() => setOpenId(null)}
|
||||||
|
{...closeBtnStyle}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</ErrorBoundary>
|
</ErrorBoundary>
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
@@ -36,8 +36,8 @@ export default function Info() {
|
|||||||
<span>{selected}</span>
|
<span>{selected}</span>
|
||||||
</div>
|
</div>
|
||||||
<InfoNif />
|
<InfoNif />
|
||||||
<InfoTitle title='Now' data={titlesNow} roll={data.playback === 'roll'} />
|
<InfoTitle title='Now' data={titlesNow} />
|
||||||
<InfoTitle title='Next' data={titlesNext} roll={data.playback === 'roll'} />
|
<InfoTitle title='Next' data={titlesNext} />
|
||||||
<InfoLogger />
|
<InfoLogger />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -2,15 +2,17 @@
|
|||||||
@use '../../theme/mixins' as *;
|
@use '../../theme/mixins' as *;
|
||||||
|
|
||||||
@mixin container {
|
@mixin container {
|
||||||
margin-top: 1em;
|
|
||||||
@include second-container;
|
@include second-container;
|
||||||
|
margin-top: 1em;
|
||||||
padding: 8px;
|
padding: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.container {
|
.container {
|
||||||
@include container;
|
@include container;
|
||||||
|
border: 1px solid transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.main {
|
.main {
|
||||||
font-size: 0.9em;
|
font-size: 0.9em;
|
||||||
color: $ontime-pink;
|
color: $ontime-pink;
|
||||||
@@ -32,10 +34,6 @@
|
|||||||
color: $header-gray;
|
color: $header-gray;
|
||||||
}
|
}
|
||||||
|
|
||||||
.headerRoll {
|
|
||||||
color: $ontime-roll;
|
|
||||||
}
|
|
||||||
|
|
||||||
.collapsedTitle {
|
.collapsedTitle {
|
||||||
font-size: inherit;
|
font-size: inherit;
|
||||||
|
|
||||||
@@ -78,10 +76,18 @@
|
|||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
}
|
}
|
||||||
|
|
||||||
.if {
|
.interfaceList {
|
||||||
font-size: 0.8em;
|
display: flex;
|
||||||
color: $ontime-accent;
|
flex-wrap: wrap;
|
||||||
background-color: $bg-gray-900;
|
gap: 4px;
|
||||||
padding: 0 0.5em;
|
margin-top: 4px;
|
||||||
margin: 0 0.5em;
|
}
|
||||||
|
|
||||||
|
.interface {
|
||||||
|
font-size: 0.8em;
|
||||||
|
background-color: $bg-container-l3;
|
||||||
|
padding: 0 8px;
|
||||||
|
margin: 0 2px;
|
||||||
|
border-radius: 2px;
|
||||||
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,8 +10,7 @@ import style from '../editors/Editor.module.scss';
|
|||||||
|
|
||||||
export default function InfoExport() {
|
export default function InfoExport() {
|
||||||
return (
|
return (
|
||||||
<Box className={style.info}>
|
<Box className={style.info} data-testid="panel-info">
|
||||||
<h1>Info</h1>
|
|
||||||
<FiArrowUpRight className={style.corner} onClick={(event) => handleLinks(event, 'info')} />
|
<FiArrowUpRight className={style.corner} onClick={(event) => handleLinks(event, 'info')} />
|
||||||
<div className={style.content}>
|
<div className={style.content}>
|
||||||
<ErrorBoundary>
|
<ErrorBoundary>
|
||||||
|
|||||||
@@ -22,7 +22,6 @@
|
|||||||
padding: 0 0.5em;
|
padding: 0 0.5em;
|
||||||
margin: 0 0.5em;
|
margin: 0 0.5em;
|
||||||
|
|
||||||
|
|
||||||
li {
|
li {
|
||||||
display: flex;
|
display: flex;
|
||||||
margin-bottom: 2px;
|
margin-bottom: 2px;
|
||||||
|
|||||||
@@ -19,13 +19,13 @@ export default function InfoNif() {
|
|||||||
onClick={() => setCollapsed((c) => !c)}
|
onClick={() => setCollapsed((c) => !c)}
|
||||||
/>
|
/>
|
||||||
{!collapsed && (status === 'success') &&(
|
{!collapsed && (status === 'success') &&(
|
||||||
<div>
|
<div className={style.interfaceList}>
|
||||||
{data?.networkInterfaces.map((e) => (
|
{data?.networkInterfaces.map((e) => (
|
||||||
<a
|
<a
|
||||||
key={e.address}
|
key={e.address}
|
||||||
href='#!'
|
href='#!'
|
||||||
onClick={() => openLink(baseURL.replace('__IP__', e.address))}
|
onClick={() => openLink(baseURL.replace('__IP__', e.address))}
|
||||||
className={style.if}
|
className={style.interface}
|
||||||
>
|
>
|
||||||
{`${e.name} - ${e.address}`}
|
{`${e.name} - ${e.address}`}
|
||||||
</a>
|
</a>
|
||||||
|
|||||||
@@ -6,10 +6,9 @@ import CollapseBar from '../../common/components/collapseBar/CollapseBar';
|
|||||||
import style from './Info.module.scss';
|
import style from './Info.module.scss';
|
||||||
|
|
||||||
export default function InfoTitle(props) {
|
export default function InfoTitle(props) {
|
||||||
|
const { title, data } = props;
|
||||||
const [collapsed, setCollapsed] = useState(false);
|
const [collapsed, setCollapsed] = useState(false);
|
||||||
|
|
||||||
const { title, data, roll } = props;
|
|
||||||
|
|
||||||
const noTitl = data.title == null || data.title === '';
|
const noTitl = data.title == null || data.title === '';
|
||||||
const noPres = data.presenter == null || data.presenter === '';
|
const noPres = data.presenter == null || data.presenter === '';
|
||||||
const noSubt = data.subtitle == null || data.subtitle === '';
|
const noSubt = data.subtitle == null || data.subtitle === '';
|
||||||
@@ -21,7 +20,6 @@ export default function InfoTitle(props) {
|
|||||||
title={title}
|
title={title}
|
||||||
isCollapsed={collapsed}
|
isCollapsed={collapsed}
|
||||||
onClick={() => setCollapsed((c) => !c)}
|
onClick={() => setCollapsed((c) => !c)}
|
||||||
roll={roll}
|
|
||||||
/>
|
/>
|
||||||
{!collapsed && (
|
{!collapsed && (
|
||||||
<>
|
<>
|
||||||
|
|||||||
@@ -1,89 +0,0 @@
|
|||||||
import { memo, useCallback, useContext } from 'react';
|
|
||||||
import { ButtonGroup, HStack } from '@chakra-ui/react';
|
|
||||||
import { FiTarget } from '@react-icons/all-files/fi/FiTarget';
|
|
||||||
import { IoCaretDown } from '@react-icons/all-files/io5/IoCaretDown';
|
|
||||||
import { IoCaretUp } from '@react-icons/all-files/io5/IoCaretUp';
|
|
||||||
import TooltipActionBtn from 'common/components/buttons/TooltipActionBtn';
|
|
||||||
import { CursorContext } from 'common/context/CursorContext';
|
|
||||||
import { useEventAction } from 'common/hooks/useEventAction';
|
|
||||||
|
|
||||||
import MenuActionButtons from './MenuActionButtons';
|
|
||||||
|
|
||||||
import style from './EventListMenu.module.css';
|
|
||||||
|
|
||||||
const cursorBtnProps = {
|
|
||||||
size: 'sm',
|
|
||||||
color: 'pink.300',
|
|
||||||
borderColor: 'pink.300',
|
|
||||||
variant: 'outline',
|
|
||||||
_hover: { bg: 'pink.400', color: 'white' },
|
|
||||||
};
|
|
||||||
|
|
||||||
const EventListMenu = () => {
|
|
||||||
const { isCursorLocked, toggleCursorLocked, moveCursorUp, moveCursorDown } =
|
|
||||||
useContext(CursorContext);
|
|
||||||
const { addEvent, deleteAllEvents } = useEventAction();
|
|
||||||
|
|
||||||
const actionHandler = useCallback(
|
|
||||||
(action) => {
|
|
||||||
switch (action) {
|
|
||||||
case 'event':
|
|
||||||
addEvent({ type: action });
|
|
||||||
break;
|
|
||||||
case 'delay':
|
|
||||||
addEvent({ type: action });
|
|
||||||
break;
|
|
||||||
case 'block':
|
|
||||||
addEvent({ type: action });
|
|
||||||
break;
|
|
||||||
case 'cursorUp':
|
|
||||||
moveCursorUp();
|
|
||||||
break;
|
|
||||||
case 'cursorDown':
|
|
||||||
moveCursorDown();
|
|
||||||
break;
|
|
||||||
case 'togglelock':
|
|
||||||
toggleCursorLocked();
|
|
||||||
break;
|
|
||||||
case 'deleteall':
|
|
||||||
deleteAllEvents();
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[addEvent, deleteAllEvents, moveCursorDown, moveCursorUp, toggleCursorLocked],
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<HStack className={style.headerButtons}>
|
|
||||||
<ButtonGroup isAttached>
|
|
||||||
<TooltipActionBtn
|
|
||||||
{...cursorBtnProps}
|
|
||||||
clickHandler={() => actionHandler('cursorUp')}
|
|
||||||
icon={<IoCaretUp />}
|
|
||||||
tooltip='Move cursor up Alt + ↑'
|
|
||||||
/>
|
|
||||||
<TooltipActionBtn
|
|
||||||
{...cursorBtnProps}
|
|
||||||
clickHandler={() => actionHandler('cursorDown')}
|
|
||||||
icon={<IoCaretDown />}
|
|
||||||
tooltip='Move cursor down Alt + ↓'
|
|
||||||
/>
|
|
||||||
<TooltipActionBtn
|
|
||||||
{...cursorBtnProps}
|
|
||||||
clickHandler={() => actionHandler('togglelock')}
|
|
||||||
icon={<FiTarget />}
|
|
||||||
tooltip='Lock cursor to current'
|
|
||||||
width='3em'
|
|
||||||
backgroundColor={isCursorLocked && 'pink.400'}
|
|
||||||
color={isCursorLocked ? 'white' : 'pink.400'}
|
|
||||||
variant={isCursorLocked ? 'solid' : 'outline'}
|
|
||||||
/>
|
|
||||||
</ButtonGroup>
|
|
||||||
<MenuActionButtons actionHandler={actionHandler} size='sm' />
|
|
||||||
</HStack>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default memo(EventListMenu);
|
|
||||||
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
.headerButtons {
|
.headerButtons {
|
||||||
align-content: center;
|
align-content: center;
|
||||||
justify-content: flex-end;
|
justify-content: space-between;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,97 @@
|
|||||||
|
import { memo, useCallback, useContext } from 'react';
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
Divider,
|
||||||
|
HStack,
|
||||||
|
IconButton,
|
||||||
|
Menu,
|
||||||
|
MenuButton,
|
||||||
|
MenuItem,
|
||||||
|
MenuList,
|
||||||
|
Tooltip,
|
||||||
|
} from '@chakra-ui/react';
|
||||||
|
import { FiClock } from '@react-icons/all-files/fi/FiClock';
|
||||||
|
import { FiMinusCircle } from '@react-icons/all-files/fi/FiMinusCircle';
|
||||||
|
import { FiPlus } from '@react-icons/all-files/fi/FiPlus';
|
||||||
|
import { FiTrash2 } from '@react-icons/all-files/fi/FiTrash2';
|
||||||
|
import { CursorContext } from 'common/context/CursorContext';
|
||||||
|
|
||||||
|
import { useEventAction } from '../../common/hooks/useEventAction';
|
||||||
|
|
||||||
|
import style from './EventListMenu.module.scss';
|
||||||
|
|
||||||
|
const menuStyle = {
|
||||||
|
color: '#000000',
|
||||||
|
backgroundColor: 'rgba(255,255,255,1)',
|
||||||
|
};
|
||||||
|
|
||||||
|
const EventListMenu = () => {
|
||||||
|
const { isCursorLocked, toggleCursorLocked } = useContext(CursorContext);
|
||||||
|
const { addEvent, deleteAllEvents } = useEventAction();
|
||||||
|
|
||||||
|
type ActionTypes = 'event' | 'delay' | 'block' | 'delete-all' | 'toggle-lock';
|
||||||
|
const actionHandler = useCallback(
|
||||||
|
(action: ActionTypes) => {
|
||||||
|
switch (action) {
|
||||||
|
case 'toggle-lock':
|
||||||
|
toggleCursorLocked();
|
||||||
|
break;
|
||||||
|
case 'event':
|
||||||
|
addEvent({ type: action });
|
||||||
|
break;
|
||||||
|
case 'delay':
|
||||||
|
addEvent({ type: action });
|
||||||
|
break;
|
||||||
|
case 'block':
|
||||||
|
addEvent({ type: action });
|
||||||
|
break;
|
||||||
|
case 'delete-all':
|
||||||
|
deleteAllEvents();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[toggleCursorLocked],
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<HStack className={style.headerButtons}>
|
||||||
|
<Button
|
||||||
|
size='sm'
|
||||||
|
variant={isCursorLocked ? 'solid' : 'ghost'}
|
||||||
|
onClick={() => actionHandler('toggle-lock')}
|
||||||
|
colorScheme='blue'
|
||||||
|
>
|
||||||
|
Lock cursor to current
|
||||||
|
</Button>
|
||||||
|
<Menu isLazy lazyBehavior='unmount'>
|
||||||
|
<Tooltip label='Add / Delete ...'>
|
||||||
|
<MenuButton
|
||||||
|
as={IconButton}
|
||||||
|
aria-label='Create Menu'
|
||||||
|
size='sm'
|
||||||
|
icon={<FiPlus />}
|
||||||
|
colorScheme='blue'
|
||||||
|
variant='outline'
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
<MenuList style={menuStyle}>
|
||||||
|
<MenuItem icon={<FiPlus />} onClick={() => actionHandler('event')}>
|
||||||
|
Add Event first
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem icon={<FiClock />} onClick={() => actionHandler('delay')}>
|
||||||
|
Add Delay first
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem icon={<FiMinusCircle />} onClick={() => actionHandler('block')}>
|
||||||
|
Add Block first
|
||||||
|
</MenuItem>
|
||||||
|
<Divider />
|
||||||
|
<MenuItem icon={<FiTrash2 />} onClick={() => actionHandler('delete-all')} color='red.500'>
|
||||||
|
Delete All
|
||||||
|
</MenuItem>
|
||||||
|
</MenuList>
|
||||||
|
</Menu>
|
||||||
|
</HStack>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default memo(EventListMenu);
|
||||||
@@ -1,61 +0,0 @@
|
|||||||
import {
|
|
||||||
IconButton,
|
|
||||||
Divider,
|
|
||||||
Menu,
|
|
||||||
MenuButton,
|
|
||||||
MenuItem,
|
|
||||||
MenuList,
|
|
||||||
Tooltip,
|
|
||||||
} from '@chakra-ui/react';
|
|
||||||
import { FiClock } from '@react-icons/all-files/fi/FiClock';
|
|
||||||
import { FiMinusCircle } from '@react-icons/all-files/fi/FiMinusCircle';
|
|
||||||
import { FiPlus } from '@react-icons/all-files/fi/FiPlus';
|
|
||||||
import { FiTrash2 } from '@react-icons/all-files/fi/FiTrash2';
|
|
||||||
import { useEventAction } from 'common/hooks/useEventAction';
|
|
||||||
import { Size } from 'common/models/UtilTypes';
|
|
||||||
|
|
||||||
|
|
||||||
interface MenuActionButtonsProps {
|
|
||||||
actionHandler: (action: string) => void;
|
|
||||||
size?: Size;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Todo: add useEventActionsHook
|
|
||||||
export default function MenuActionButtons(props: MenuActionButtonsProps) {
|
|
||||||
const { actionHandler, size = 'xs' } = props;
|
|
||||||
const { deleteAllEvents } = useEventAction();
|
|
||||||
const menuStyle = {
|
|
||||||
color: '#000000',
|
|
||||||
backgroundColor: 'rgba(255,255,255,1)',
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Menu isLazy lazyBehavior='unmount'>
|
|
||||||
<Tooltip label='Add / Delete ...'>
|
|
||||||
<MenuButton
|
|
||||||
as={IconButton}
|
|
||||||
aria-label='Create Menu'
|
|
||||||
size={size}
|
|
||||||
icon={<FiPlus />}
|
|
||||||
colorScheme='blue'
|
|
||||||
/>
|
|
||||||
</Tooltip>
|
|
||||||
<MenuList style={menuStyle}>
|
|
||||||
<MenuItem icon={<FiPlus />} onClick={() => actionHandler('event')}>
|
|
||||||
Add Event first
|
|
||||||
</MenuItem>
|
|
||||||
<MenuItem icon={<FiClock />} onClick={() => actionHandler('delay')}>
|
|
||||||
Add Delay first
|
|
||||||
</MenuItem>
|
|
||||||
<MenuItem icon={<FiMinusCircle />} onClick={() => actionHandler('block')}>
|
|
||||||
Add Block first
|
|
||||||
</MenuItem>
|
|
||||||
<Divider />
|
|
||||||
<MenuItem icon={<FiTrash2 />} onClick={() => deleteAllEvents()} color='red.500'>
|
|
||||||
Delete All
|
|
||||||
</MenuItem>
|
|
||||||
</MenuList>
|
|
||||||
</Menu>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -4,8 +4,8 @@ import { FiHelpCircle } from '@react-icons/all-files/fi/FiHelpCircle';
|
|||||||
import { FiMaximize } from '@react-icons/all-files/fi/FiMaximize';
|
import { FiMaximize } from '@react-icons/all-files/fi/FiMaximize';
|
||||||
import { FiMinimize } from '@react-icons/all-files/fi/FiMinimize';
|
import { FiMinimize } from '@react-icons/all-files/fi/FiMinimize';
|
||||||
import { FiSave } from '@react-icons/all-files/fi/FiSave';
|
import { FiSave } from '@react-icons/all-files/fi/FiSave';
|
||||||
import { FiSettings } from '@react-icons/all-files/fi/FiSettings';
|
|
||||||
import { FiUpload } from '@react-icons/all-files/fi/FiUpload';
|
import { FiUpload } from '@react-icons/all-files/fi/FiUpload';
|
||||||
|
import { IoSettingsOutline } from '@react-icons/all-files/io5/IoSettingsOutline';
|
||||||
import { downloadEvents } from 'common/api/ontimeApi';
|
import { downloadEvents } from 'common/api/ontimeApi';
|
||||||
|
|
||||||
import QuitIconBtn from '../../common/components/buttons/QuitIconBtn';
|
import QuitIconBtn from '../../common/components/buttons/QuitIconBtn';
|
||||||
@@ -116,7 +116,7 @@ export default function MenuBar(props: MenuBarProps) {
|
|||||||
/>
|
/>
|
||||||
<TooltipActionBtn
|
<TooltipActionBtn
|
||||||
{...buttonStyle}
|
{...buttonStyle}
|
||||||
icon={<FiSettings />}
|
icon={<IoSettingsOutline />}
|
||||||
className={isSettingsOpen ? style.open : ''}
|
className={isSettingsOpen ? style.open : ''}
|
||||||
clickHandler={onSettingsOpen}
|
clickHandler={onSettingsOpen}
|
||||||
tooltip='Settings'
|
tooltip='Settings'
|
||||||
|
|||||||
@@ -1,26 +0,0 @@
|
|||||||
import { QueryClientProvider } from '@tanstack/react-query';
|
|
||||||
import { render, screen } from '@testing-library/react';
|
|
||||||
import { vi } from 'vitest';
|
|
||||||
|
|
||||||
import { queryClientMock } from '../../../__mocks__/QueryClient.mock';
|
|
||||||
import MenuActionButtons from '../MenuActionButtons';
|
|
||||||
|
|
||||||
const actionHandler = vi.fn();
|
|
||||||
const renderInMock = () => {
|
|
||||||
render(
|
|
||||||
<QueryClientProvider client={queryClientMock}>
|
|
||||||
<MenuActionButtons actionHandler={actionHandler} />
|
|
||||||
</QueryClientProvider>,
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
test('check that menu bar renders correctly', () => {
|
|
||||||
// need to inject the react query provider
|
|
||||||
renderInMock();
|
|
||||||
|
|
||||||
const b = screen.getByRole('button', {
|
|
||||||
name: /create menu/i,
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(b).toBeInTheDocument();
|
|
||||||
});
|
|
||||||
@@ -83,7 +83,10 @@ export default function TableWrapper() {
|
|||||||
return <span>loading...</span>;
|
return <span>loading...</span>;
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<div className={theme === 'dark' ? style.tableWrapper__dark : style.tableWrapper}>
|
<div
|
||||||
|
className={theme === 'dark' ? style.tableWrapper__dark : style.tableWrapper}
|
||||||
|
data-testid="cuesheet"
|
||||||
|
>
|
||||||
<TableHeader handleCSVExport={exportHandler} featureData={featureData} />
|
<TableHeader handleCSVExport={exportHandler} featureData={featureData} />
|
||||||
<OntimeTable
|
<OntimeTable
|
||||||
tableData={events}
|
tableData={events}
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ export default function Backstage(props) {
|
|||||||
const clock = formatTime(time.clock, formatOptions);
|
const clock = formatTime(time.clock, formatOptions);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='backstage'>
|
<div className='backstage' data-testid="backstage-view">
|
||||||
<NavLogo />
|
<NavLogo />
|
||||||
|
|
||||||
<div className='event-title'>{general.title}</div>
|
<div className='event-title'>{general.title}</div>
|
||||||
|
|||||||
@@ -31,7 +31,7 @@
|
|||||||
.today-container {
|
.today-container {
|
||||||
background-color: var(--outdent-background-color-override, $viewer-outdent-bg-color);
|
background-color: var(--outdent-background-color-override, $viewer-outdent-bg-color);
|
||||||
padding: 1vh 1vw;
|
padding: 1vh 1vw;
|
||||||
border-radius: 1vw;
|
border-radius: $element-border-radius;
|
||||||
|
|
||||||
.label {
|
.label {
|
||||||
font-size: 1.3vw;
|
font-size: 1.3vw;
|
||||||
@@ -50,7 +50,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.event {
|
.event {
|
||||||
border-radius: 0 2vw 2vw 0;
|
border-radius: 0 $element-border-radius $element-border-radius 0;
|
||||||
margin-left: -1vw;
|
margin-left: -1vw;
|
||||||
padding: 1vh 2vw;
|
padding: 1vh 2vw;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
@@ -115,12 +115,12 @@
|
|||||||
|
|
||||||
.clock-container {
|
.clock-container {
|
||||||
grid-area: time;
|
grid-area: time;
|
||||||
border-radius: 0 0 1vw 1vw;
|
border-radius: 0 0 $element-border-radius $element-border-radius;
|
||||||
}
|
}
|
||||||
|
|
||||||
.timer-container {
|
.timer-container {
|
||||||
grid-area: clck;
|
grid-area: clck;
|
||||||
border-radius: 1vw 1vw 0 0;
|
border-radius: $element-border-radius $element-border-radius 0 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.info {
|
.info {
|
||||||
|
|||||||
@@ -90,10 +90,10 @@ export default function Countdown(props) {
|
|||||||
: formatTime(follow.timeEnd + delay, formatOptions);
|
: formatTime(follow.timeEnd + delay, formatOptions);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='countdown'>
|
<div className='countdown' data-testid="countdown-view">
|
||||||
<NavLogo />
|
<NavLogo />
|
||||||
{follow === null ? (
|
{follow === null ? (
|
||||||
<div className='event-select'>
|
<div className='event-select' data-testid="countdown-select">
|
||||||
<span className='event-select__title'>Select an event to follow</span>
|
<span className='event-select__title'>Select an event to follow</span>
|
||||||
<ul className='event-select__events'>
|
<ul className='event-select__events'>
|
||||||
{backstageEvents.length === 0 ? (
|
{backstageEvents.length === 0 ? (
|
||||||
@@ -112,7 +112,7 @@ export default function Countdown(props) {
|
|||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className='countdown-container'>
|
<div className='countdown-container' data-testid="countdown-event">
|
||||||
<div className='timer-group'>
|
<div className='timer-group'>
|
||||||
<div className='aux-timers'>
|
<div className='aux-timers'>
|
||||||
<div className='aux-timers__label'>Time Now</div>
|
<div className='aux-timers__label'>Time Now</div>
|
||||||
|
|||||||
@@ -80,7 +80,7 @@
|
|||||||
color: var(--accent-color-override, $accent-color);
|
color: var(--accent-color-override, $accent-color);
|
||||||
background-color: var(--outdent-background-color-override, $viewer-outdent-bg-color);
|
background-color: var(--outdent-background-color-override, $viewer-outdent-bg-color);
|
||||||
padding: 1vh 2vw;
|
padding: 1vh 2vw;
|
||||||
border-radius: 1vw;
|
border-radius: $element-border-radius;
|
||||||
}
|
}
|
||||||
|
|
||||||
.status {
|
.status {
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
.lower-container {
|
.lower-container {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
padding: 1vh 2vh;
|
padding: 1vh 2vh;
|
||||||
border-radius: 1vh;
|
border-radius: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.message-container {
|
.message-container {
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
width: 45vw;
|
width: 45vw;
|
||||||
border-radius: 0 1vh 1vh 0;
|
border-radius: 0 8px 8px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.message-container {
|
.message-container {
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ export default function Pip(props) {
|
|||||||
const clock = formatTime(time.clock, formatOptions);
|
const clock = formatTime(time.clock, formatOptions);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='pip'>
|
<div className='pip' data-testid="pip-view">
|
||||||
<NavLogo />
|
<NavLogo />
|
||||||
|
|
||||||
<div className='event-title'>{general.title}</div>
|
<div className='event-title'>{general.title}</div>
|
||||||
|
|||||||
@@ -27,7 +27,7 @@
|
|||||||
.timer-container {
|
.timer-container {
|
||||||
background-color: var(--outdent-background-color-override, $viewer-outdent-bg-color);
|
background-color: var(--outdent-background-color-override, $viewer-outdent-bg-color);
|
||||||
padding: 1vh 1vw;
|
padding: 1vh 1vw;
|
||||||
border-radius: 1vw;
|
border-radius: $element-border-radius;
|
||||||
|
|
||||||
.label {
|
.label {
|
||||||
font-size: 1.3vw;
|
font-size: 1.3vw;
|
||||||
@@ -126,11 +126,11 @@
|
|||||||
|
|
||||||
.clock-container {
|
.clock-container {
|
||||||
grid-area: time;
|
grid-area: time;
|
||||||
border-radius: 0 0 1vw 1vw;
|
border-radius: 0 0 $element-border-radius $element-border-radius;
|
||||||
}
|
}
|
||||||
|
|
||||||
.timer-container {
|
.timer-container {
|
||||||
grid-area: clck;
|
grid-area: clck;
|
||||||
border-radius: 1vw 1vw 0 0;
|
border-radius: $element-border-radius $element-border-radius 0 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ export default function Public(props) {
|
|||||||
const clock = formatTime(time.clock, formatOptions);
|
const clock = formatTime(time.clock, formatOptions);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='public-screen'>
|
<div className='public-screen' data-testid="public-view">
|
||||||
<NavLogo />
|
<NavLogo />
|
||||||
|
|
||||||
<div className='event-title'>{general.title}</div>
|
<div className='event-title'>{general.title}</div>
|
||||||
|
|||||||
@@ -31,7 +31,7 @@
|
|||||||
.today-container {
|
.today-container {
|
||||||
background-color: var(--outdent-background-color-override, $viewer-outdent-bg-color);
|
background-color: var(--outdent-background-color-override, $viewer-outdent-bg-color);
|
||||||
padding: 1vh 1vw;
|
padding: 1vh 1vw;
|
||||||
border-radius: 1vw;
|
border-radius: $element-border-radius;
|
||||||
|
|
||||||
.label {
|
.label {
|
||||||
font-size: 1.3vw;
|
font-size: 1.3vw;
|
||||||
@@ -50,7 +50,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.event {
|
.event {
|
||||||
border-radius: 0 2vw 2vw 0;
|
border-radius: 0 $element-border-radius $element-border-radius 0;
|
||||||
margin-left: -1vw;
|
margin-left: -1vw;
|
||||||
padding: 1vh 2vw;
|
padding: 1vh 2vw;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
@@ -114,7 +114,7 @@
|
|||||||
|
|
||||||
.clock-container {
|
.clock-container {
|
||||||
grid-area: time;
|
grid-area: time;
|
||||||
border-radius: 1vw;
|
border-radius: $element-border-radius;
|
||||||
}
|
}
|
||||||
|
|
||||||
.info {
|
.info {
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ export default function StudioClock(props) {
|
|||||||
const [, , secondsNow] = stringFromMillis(time.clock).split(':');
|
const [, , secondsNow] = stringFromMillis(time.clock).split(':');
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='studio-clock'>
|
<div className='studio-clock' data-testid="studio-view">
|
||||||
<NavLogo />
|
<NavLogo />
|
||||||
<div className='clock-container'>
|
<div className='clock-container'>
|
||||||
<div className='studio-timer'>{clock}</div>
|
<div className='studio-timer'>{clock}</div>
|
||||||
|
|||||||
@@ -66,7 +66,10 @@ export default function Timer(props) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={time.finished ? 'stage-timer stage-timer--finished' : 'stage-timer'}>
|
<div
|
||||||
|
className={time.finished ? 'stage-timer stage-timer--finished' : 'stage-timer'}
|
||||||
|
data-testid="timer-view"
|
||||||
|
>
|
||||||
<div className={showOverlay ? 'message-overlay message-overlay--active' : 'message-overlay'}>
|
<div className={showOverlay ? 'message-overlay message-overlay--active' : 'message-overlay'}>
|
||||||
<div className='message'>{pres.text}</div>
|
<div className='message'>{pres.text}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -30,7 +30,7 @@
|
|||||||
.event {
|
.event {
|
||||||
background-color: var(--outdent-background-color-override, $viewer-outdent-bg-color);
|
background-color: var(--outdent-background-color-override, $viewer-outdent-bg-color);
|
||||||
padding: 1vh 2vw;
|
padding: 1vh 2vw;
|
||||||
border-radius: 1vw;
|
border-radius: $element-border-radius;
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
|
|
||||||
&.now {
|
&.now {
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
* {
|
* {
|
||||||
-webkit-font-smoothing: antialiased;
|
-webkit-font-smoothing: antialiased;
|
||||||
-moz-osx-font-smoothing: grayscale;
|
-moz-osx-font-smoothing: grayscale;
|
||||||
scrollbar-color: rgba(255, 255, 255, 0.15) rgba(255, 255, 255, 0.01);
|
scrollbar-color: #2b2b2b rgba(255, 255, 255, 0.01);
|
||||||
scrollbar-width: thin;
|
scrollbar-width: thin;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ $bg-black-100: #070707;
|
|||||||
$bg-black-200: #1a1a1a; // container borders
|
$bg-black-200: #1a1a1a; // container borders
|
||||||
$bg-black-300: #1f1f1f; // container text
|
$bg-black-300: #1f1f1f; // container text
|
||||||
$bg-gray-1100: #232323; // container borders
|
$bg-gray-1100: #232323; // container borders
|
||||||
|
$bg-gray-1050: #242424; // container borders
|
||||||
$bg-gray-1000: #262626; // container borders
|
$bg-gray-1000: #262626; // container borders
|
||||||
$bg-gray-950: #292929;
|
$bg-gray-950: #292929;
|
||||||
$bg-gray-900: #303030;
|
$bg-gray-900: #303030;
|
||||||
@@ -42,7 +43,7 @@ $opacity-disabled: 0.4;
|
|||||||
$notes-color: #d69e2e;
|
$notes-color: #d69e2e;
|
||||||
|
|
||||||
$text-white: #fffffa;
|
$text-white: #fffffa;
|
||||||
$text-gray-disabled: #999;
|
$text-gray-disabled: #505050;
|
||||||
$header-gray: #ccc;
|
$header-gray: #ccc;
|
||||||
$label-gray: #aaa;
|
$label-gray: #aaa;
|
||||||
$clocks: #ddd;
|
$clocks: #ddd;
|
||||||
@@ -64,11 +65,17 @@ $title-gray: #ddd;
|
|||||||
$subtitle-gray: #aaa;
|
$subtitle-gray: #aaa;
|
||||||
|
|
||||||
//////////////////////////////////// block elements
|
//////////////////////////////////// block elements
|
||||||
|
$bg-container-l1: #202020;
|
||||||
|
$bg-container-l2: #232323;
|
||||||
|
$bg-container-l3: #2b2b2b;
|
||||||
|
$border-l1: 1px solid $bg-gray-1000;
|
||||||
|
$border-l3: 1px solid $bg-gray-900;
|
||||||
|
|
||||||
$block-delay-color: #ecc94b;
|
$block-delay-color: #ecc94b;
|
||||||
$delay-text: #d69e2e;
|
$delay-text: #d69e2e;
|
||||||
$block-delay-border: #d69e2e55;
|
$block-delay-border: #d69e2e55;
|
||||||
$block-block-color: #805ad5;
|
$block-block-color: #805ad5;
|
||||||
$block-border: 1px solid $bg-gray-900;
|
$block-border: 1px solid $bg-gray-1100;
|
||||||
|
|
||||||
//////////////////////////////////// viewer cards
|
//////////////////////////////////// viewer cards
|
||||||
@mixin card-title {
|
@mixin card-title {
|
||||||
|
|||||||
@@ -9,13 +9,12 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
@mixin second-container {
|
@mixin second-container {
|
||||||
background-color: $bg-gray-1100;
|
background-color: $bg-container-l2;
|
||||||
border: 1px solid rgba(0, 0, 0, 0.05);
|
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@mixin third-container {
|
@mixin third-container {
|
||||||
background-color: $bg-black-300;
|
background-color: $bg-gray-1100;
|
||||||
border: 1px solid rgba(0, 0, 0, 0.05);
|
border: 1px solid rgba(0, 0, 0, 0.05);
|
||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,8 +7,9 @@ $delay-color: $delay-text;
|
|||||||
// Main Properties of a viewer
|
// Main Properties of a viewer
|
||||||
$viewer-background-color: $bg-black; // --background-color-override
|
$viewer-background-color: $bg-black; // --background-color-override
|
||||||
$viewer-color: $title-white; // --color-override
|
$viewer-color: $title-white; // --color-override
|
||||||
$viewer-outdent-bg-color: $bg-gray-1100; // --outdent-background-color-override
|
$viewer-outdent-bg-color: $bg-container-l1; // --outdent-background-color-override
|
||||||
$viewer-overlay-bg-color: $bg-overlay;
|
$viewer-overlay-bg-color: $bg-overlay;
|
||||||
|
$element-border-radius: 8px;
|
||||||
|
|
||||||
// Properties related to timer
|
// Properties related to timer
|
||||||
$timer-finished-color: $ontime-pink-variant;
|
$timer-finished-color: $ontime-pink-variant;
|
||||||
|
|||||||
@@ -3,38 +3,37 @@ describe('validate routes', () => {
|
|||||||
describe('viewer routes', () => {
|
describe('viewer routes', () => {
|
||||||
it('default to stage timer', () => {
|
it('default to stage timer', () => {
|
||||||
cy.visit('http://localhost:4001/');
|
cy.visit('http://localhost:4001/');
|
||||||
|
cy.get('[data-testid="timer-view"]').should('exist');
|
||||||
cy.contains('Time Now');
|
cy.contains('Time Now');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('renders stage timer', () => {
|
it('renders stage timer routes', () => {
|
||||||
cy.visit('http://localhost:4001/timer');
|
cy.visit('http://localhost:4001/timer');
|
||||||
|
cy.get('[data-testid="timer-view"]').should('exist');
|
||||||
cy.contains('Time Now');
|
cy.contains('Time Now');
|
||||||
});
|
|
||||||
|
|
||||||
it('renders presenter', () => {
|
|
||||||
cy.visit('http://localhost:4001/presenter');
|
cy.visit('http://localhost:4001/presenter');
|
||||||
|
cy.get('[data-testid="timer-view"]').should('exist');
|
||||||
cy.contains('Time Now');
|
cy.contains('Time Now');
|
||||||
});
|
|
||||||
|
|
||||||
it('renders speaker', () => {
|
|
||||||
cy.visit('http://localhost:4001/speaker');
|
cy.visit('http://localhost:4001/speaker');
|
||||||
|
cy.get('[data-testid="timer-view"]').should('exist');
|
||||||
cy.contains('Time Now');
|
cy.contains('Time Now');
|
||||||
});
|
|
||||||
|
|
||||||
it('renders backstage view /stage', () => {
|
|
||||||
cy.visit('http://localhost:4001/stage');
|
cy.visit('http://localhost:4001/stage');
|
||||||
|
cy.get('[data-testid="timer-view"]').should('exist');
|
||||||
cy.contains('Time Now');
|
cy.contains('Time Now');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('renders backstage view /backstage', () => {
|
it('renders backstage routes', () => {
|
||||||
cy.visit('http://localhost:4001/backstage');
|
cy.visit('http://localhost:4001/backstage');
|
||||||
|
cy.get('[data-testid="backstage-view"]').should('exist');
|
||||||
cy.contains('Today');
|
cy.contains('Today');
|
||||||
cy.contains('Time Now');
|
cy.contains('Time Now');
|
||||||
cy.contains('Info');
|
cy.contains('Info');
|
||||||
});
|
|
||||||
|
|
||||||
it('renders backstage view /sm', () => {
|
|
||||||
cy.visit('http://localhost:4001/sm');
|
cy.visit('http://localhost:4001/sm');
|
||||||
|
cy.get('[data-testid="backstage-view"]').should('exist');
|
||||||
cy.contains('Today');
|
cy.contains('Today');
|
||||||
cy.contains('Time Now');
|
cy.contains('Time Now');
|
||||||
cy.contains('Info');
|
cy.contains('Info');
|
||||||
@@ -42,6 +41,7 @@ describe('validate routes', () => {
|
|||||||
|
|
||||||
it('renders public view', () => {
|
it('renders public view', () => {
|
||||||
cy.visit('http://localhost:4001/public');
|
cy.visit('http://localhost:4001/public');
|
||||||
|
cy.get('[data-testid="public-view"]').should('exist');
|
||||||
cy.contains('Today');
|
cy.contains('Today');
|
||||||
cy.contains('Time Now');
|
cy.contains('Time Now');
|
||||||
cy.contains('Info');
|
cy.contains('Info');
|
||||||
@@ -49,8 +49,8 @@ describe('validate routes', () => {
|
|||||||
|
|
||||||
it('renders pip view', () => {
|
it('renders pip view', () => {
|
||||||
cy.visit('http://localhost:4001/pip');
|
cy.visit('http://localhost:4001/pip');
|
||||||
|
cy.get('[data-testid="pip-view"]').should('exist');
|
||||||
cy.contains('Today');
|
cy.contains('Today');
|
||||||
cy.contains('Info');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('renders lower third with no errors', () => {
|
it('renders lower third with no errors', () => {
|
||||||
@@ -60,16 +60,21 @@ describe('validate routes', () => {
|
|||||||
|
|
||||||
it('renders studio clock', () => {
|
it('renders studio clock', () => {
|
||||||
cy.visit('http://localhost:4001/studio');
|
cy.visit('http://localhost:4001/studio');
|
||||||
|
cy.get('[data-testid="studio-view"]').should('exist');
|
||||||
cy.contains('ON AIR');
|
cy.contains('ON AIR');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('renders countdown selection', () => {
|
it('renders countdown selection', () => {
|
||||||
cy.visit('http://localhost:4001/countdown');
|
cy.visit('http://localhost:4001/countdown');
|
||||||
|
cy.get('[data-testid="countdown-view"]').should('exist');
|
||||||
|
cy.get('[data-testid="countdown-select"]').should('exist');
|
||||||
cy.contains('Select an event to follow');
|
cy.contains('Select an event to follow');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('renders countdown with event', () => {
|
it('renders countdown with event', () => {
|
||||||
cy.visit('http://localhost:4001/countdown?event=1');
|
cy.visit('http://localhost:4001/countdown?event=1');
|
||||||
|
cy.get('[data-testid="countdown-view"]').should('exist');
|
||||||
|
cy.get('[data-testid="countdown-event"]').should('exist');
|
||||||
cy.contains('Time Now');
|
cy.contains('Time Now');
|
||||||
cy.contains('Start Time');
|
cy.contains('Start Time');
|
||||||
cy.contains('End Time');
|
cy.contains('End Time');
|
||||||
@@ -78,38 +83,30 @@ describe('validate routes', () => {
|
|||||||
|
|
||||||
describe('clock view timer', () => {
|
describe('clock view timer', () => {
|
||||||
it('renders base route', () => {
|
it('renders base route', () => {
|
||||||
// base route
|
|
||||||
cy.visit('http://localhost:4001/clock');
|
cy.visit('http://localhost:4001/clock');
|
||||||
cy.get('[data-testid="clock-view"]').should('exist');
|
cy.get('[data-testid="clock-view"]').should('exist');
|
||||||
// ontime fallsback to stage timer on errors, so we check for that
|
|
||||||
cy.get('.App').should('not.contain', 'Time Now');
|
cy.get('.App').should('not.contain', 'Time Now');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('minimal timer and options', () => {
|
describe('minimal timer and options', () => {
|
||||||
it('renders base route', () => {
|
it('renders base route', () => {
|
||||||
// base route
|
|
||||||
cy.visit('http://localhost:4001/minimal');
|
cy.visit('http://localhost:4001/minimal');
|
||||||
cy.get('[data-testid="minimal-timer"]').should('exist');
|
cy.get('[data-testid="minimal-timer"]').should('exist');
|
||||||
// ontime fallsback to stage timer on errors, so we check for that
|
|
||||||
cy.get('.App').should('not.contain', 'Time Now');
|
cy.get('.App').should('not.contain', 'Time Now');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('renders with defined options', () => {
|
it('renders with defined options', () => {
|
||||||
// route with options
|
|
||||||
cy.visit(
|
cy.visit(
|
||||||
'http://localhost:4001/minimal?font=arial=1&size=1.5&text=0f0&key=ff0&hideovertime=true&alignx=start&aligny=end&offsetx=100&offsety=-100&hidemessages=true'
|
'http://localhost:4001/minimal?font=arial=1&size=1.5&text=0f0&key=ff0&hideovertime=true&alignx=start&aligny=end&offsetx=100&offsety=-100&hidemessages=true'
|
||||||
);
|
);
|
||||||
cy.get('[data-testid="minimal-timer"]').should('exist');
|
cy.get('[data-testid="minimal-timer"]').should('exist');
|
||||||
// ontime fallback to stage timer on errors, so we check for that
|
|
||||||
cy.get('.App').should('not.contain', 'Time Now');
|
cy.get('.App').should('not.contain', 'Time Now');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('hides nav on given option', () => {
|
it('hides nav on given option', () => {
|
||||||
// route with options for no nav
|
|
||||||
cy.visit('http://localhost:4001/minimal?hidenav=true');
|
cy.visit('http://localhost:4001/minimal?hidenav=true');
|
||||||
cy.get('[data-testid="minimal-timer"]').should('exist');
|
cy.get('[data-testid="minimal-timer"]').should('exist');
|
||||||
// ontime fallback to stage timer on errors, so we check for that
|
|
||||||
cy.get('.App').should('not.contain', 'Time Now');
|
cy.get('.App').should('not.contain', 'Time Now');
|
||||||
cy.get('[data-testid="nav-logo"]').should('not.exist');
|
cy.get('[data-testid="nav-logo"]').should('not.exist');
|
||||||
});
|
});
|
||||||
@@ -118,49 +115,53 @@ describe('validate routes', () => {
|
|||||||
describe('editor routes', () => {
|
describe('editor routes', () => {
|
||||||
it('app editor', () => {
|
it('app editor', () => {
|
||||||
cy.visit('http://localhost:4001/editor');
|
cy.visit('http://localhost:4001/editor');
|
||||||
cy.contains('Event List');
|
cy.get('[data-testid="event-editor"]').should('exist');
|
||||||
cy.contains('Timer Control');
|
cy.get('[data-testid="panel-event-list"]').should('exist');
|
||||||
cy.contains('Messages Control');
|
cy.get('[data-testid="panel-timer-control"]').should('exist');
|
||||||
cy.contains('Info');
|
cy.get('[data-testid="panel-messages-control"]').should('exist');
|
||||||
|
cy.get('[data-testid="panel-info"]').should('exist');
|
||||||
});
|
});
|
||||||
it('self contained eventlist', () => {
|
it('self contained eventlist', () => {
|
||||||
cy.visit('http://localhost:4001/eventlist');
|
cy.visit('http://localhost:4001/eventlist');
|
||||||
cy.get('.App').should('contain', 'Event List');
|
cy.get('[data-testid="panel-event-list"]').should('exist');
|
||||||
cy.get('.App').should('not.contain', 'Timer Control');
|
cy.get('[data-testid="panel-timer-control"]').should('not.exist');
|
||||||
cy.get('.App').should('not.contain', 'Messages Control');
|
cy.get('[data-testid="panel-messages-control"]').should('not.exist');
|
||||||
cy.get('.App').should('not.contain', 'Info');
|
cy.get('[data-testid="panel-info"]').should('not.exist');
|
||||||
});
|
});
|
||||||
it('self contained timercontrol', () => {
|
it('self contained timercontrol', () => {
|
||||||
cy.visit('http://localhost:4001/timercontrol');
|
cy.visit('http://localhost:4001/timercontrol');
|
||||||
cy.get('.App').should('not.contain', 'Event List');
|
cy.get('[data-testid="panel-event-list"]').should('not.exist');
|
||||||
cy.get('.App').should('contain', 'Timer Control');
|
cy.get('[data-testid="panel-timer-control"]').should('exist');
|
||||||
cy.get('.App').should('not.contain', 'Messages Control');
|
cy.get('[data-testid="panel-messages-control"]').should('not.exist');
|
||||||
cy.get('.App').should('not.contain', 'Info');
|
cy.get('[data-testid="panel-info"]').should('not.exist');
|
||||||
});
|
});
|
||||||
it('self contained messagecontrol', () => {
|
it('self contained messagecontrol', () => {
|
||||||
cy.visit('http://localhost:4001/messagecontrol');
|
cy.visit('http://localhost:4001/messagecontrol');
|
||||||
cy.get('.App').should('not.contain', 'Event List');
|
cy.get('[data-testid="panel-event-list"]').should('not.exist');
|
||||||
cy.get('.App').should('not.contain', 'Timer Control');
|
cy.get('[data-testid="panel-timer-control"]').should('not.exist');
|
||||||
cy.get('.App').should('contain', 'Messages Control');
|
cy.get('[data-testid="panel-messages-control"]').should('exist');
|
||||||
cy.get('.App').should('not.contain', 'Info');
|
cy.get('[data-testid="panel-info"]').should('not.exist');
|
||||||
});
|
});
|
||||||
it('self contained info', () => {
|
it('self contained info', () => {
|
||||||
cy.visit('http://localhost:4001/info');
|
cy.visit('http://localhost:4001/info');
|
||||||
cy.get('.App').should('not.contain', 'Event List');
|
cy.get('[data-testid="panel-event-list"]').should('not.exist');
|
||||||
cy.get('.App').should('not.contain', 'Timer Control');
|
cy.get('[data-testid="panel-timer-control"]').should('not.exist');
|
||||||
cy.get('.App').should('not.contain', 'Messages Control');
|
cy.get('[data-testid="panel-messages-control"]').should('not.exist');
|
||||||
cy.get('.App').should('contain', 'Info');
|
cy.get('[data-testid="panel-info"]').should('exist');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('table routes', () => {
|
it('table routes', () => {
|
||||||
cy.visit('http://localhost:4001/table');
|
cy.visit('http://localhost:4001/table');
|
||||||
cy.contains('Running Timer');
|
cy.contains('Running Timer');
|
||||||
|
cy.get('[data-testid="cuesheet"]').should('exist');
|
||||||
|
|
||||||
cy.visit('http://localhost:4001/cuesheet');
|
cy.visit('http://localhost:4001/cuesheet');
|
||||||
cy.contains('Running Timer');
|
cy.contains('Running Timer');
|
||||||
|
cy.get('[data-testid="cuesheet"]').should('exist');
|
||||||
|
|
||||||
cy.visit('http://localhost:4001/cuelist');
|
cy.visit('http://localhost:4001/cuelist');
|
||||||
cy.contains('Running Timer');
|
cy.contains('Running Timer');
|
||||||
|
cy.get('[data-testid="cuesheet"]').should('exist');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user