styles: v2 (#229)

* style: revise selected style
* style: revise editor styles
* style: revise viewer styles
* chore: update tests
This commit is contained in:
Carlos Valente
2022-10-21 19:50:25 +02:00
committed by GitHub
parent 08d2ebcc5e
commit d8ee0d4f82
62 changed files with 340 additions and 398 deletions
@@ -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()}
+10 -18
View File
@@ -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;
} }
+1 -1
View File
@@ -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>
+2 -2
View File
@@ -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 />
</> </>
); );
+17 -11
View File
@@ -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;
} }
+1 -2
View File
@@ -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;
+2 -2
View File
@@ -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>
+1 -3
View File
@@ -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,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>
);
}
+2 -2
View File
@@ -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();
});
+4 -1
View File
@@ -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>
+4 -1
View File
@@ -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>
+1 -1
View File
@@ -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 {
+1 -1
View File
@@ -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;
} }
+9 -2
View File
@@ -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 {
+2 -3
View File
@@ -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;
} }
+2 -1
View File
@@ -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;
+40 -39
View File
@@ -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');
}); });
}); });