V2 styles part2 (#233)

* style: create tag to display network messages
* style: create button style for playback
* style: review style for QuickEntry
* style: review style for Info panel
* style: review style for Event blocks
* style: review style for Message control panel
* style: review style for Playback control
* style: review application styles
* style: review block styles
* refactor: TapButton has active state
* style: small style tweaks
* refactor: type checks
This commit is contained in:
Carlos Valente
2022-10-26 15:12:57 +02:00
committed by GitHub
parent 1b1aced296
commit 54442dc9a1
42 changed files with 611 additions and 405 deletions
@@ -3,8 +3,6 @@ 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 { tooltipDelayMid } from '../../../ontimeConfig';
interface ActionButtonProps {
showAdd?: boolean;
showDelay?: boolean;
@@ -22,14 +20,14 @@ export default function ActionButtons(props: ActionButtonProps) {
return (
<Menu isLazy lazyBehavior='unmount'>
<Tooltip label='Add ...' openDelay={tooltipDelayMid}>
<Tooltip label='Add ...'>
<MenuButton
as={IconButton}
aria-label='Options'
size='sm'
icon={<FiPlus />}
colorScheme='blue'
variant='ghost'
colorScheme='white'
variant='outline'
/>
</Tooltip>
<MenuList style={menuStyle}>
@@ -1,34 +0,0 @@
import { useCallback, useState } from 'react';
import { IconButton, Tooltip } from '@chakra-ui/react';
import PropTypes from 'prop-types';
export default function TooltipLoadingActionBtn(props) {
const { clickHandler, icon, size = 'xs', tooltip, ...rest } = props;
const [loading, setLoading] = useState(false);
const handleClick = useCallback(() => {
setLoading(true);
clickHandler();
}, [clickHandler, setLoading]);
return (
<Tooltip label={tooltip} shouldWrapChildren={loading}>
<IconButton
aria-label={tooltip}
size={size}
icon={icon}
onClick={handleClick}
disabled={loading}
isLoading={loading}
{...rest}
/>
</Tooltip>
);
}
TooltipLoadingActionBtn.propTypes = {
clickHandler: PropTypes.func,
icon: PropTypes.element,
size: PropTypes.oneOf(['xs', 'sm', 'md', 'lg']),
tooltip: PropTypes.string,
};
@@ -0,0 +1,33 @@
import { useCallback, useState } from 'react';
import { IconButton, IconButtonProps, Tooltip } from '@chakra-ui/react';
interface TooltipLoadingActionBtnProps extends IconButtonProps {
clickHandler: () => void;
tooltip: string;
openDelay?: number;
}
export default function TooltipLoadingActionBtn(props: TooltipLoadingActionBtnProps) {
const { clickHandler, icon, size = 'xs', tooltip, openDelay = 0, ...rest } = props;
const [loading, setLoading] = useState(false);
const handleClick = useCallback(() => {
setLoading(true);
clickHandler();
}, [clickHandler, setLoading]);
return (
<Tooltip label={tooltip} shouldWrapChildren={loading} openDelay={openDelay}>
<IconButton
{...rest}
aria-label={tooltip}
size={size}
icon={icon}
onClick={handleClick}
disabled={loading}
isLoading={loading}
/>
</Tooltip>
);
}
@@ -1,21 +1,14 @@
@use '../../../theme/main' as *;
.header,
.headerRoll {
.header {
padding: 0;
margin: 0;
font-size: 0.9em;
display: flex;
justify-content: space-between;
}
.header {
color: $header-gray;
}
.headerRoll {
color: $ontime-roll;
}
.moreExpanded,
.moreCollapsed {
@@ -1,11 +1,12 @@
import { useCallback, useContext, useEffect, useRef, useState } from 'react';
import { IconButton, Input, InputGroup, InputLeftElement } from '@chakra-ui/react';
import { IoLink } from '@react-icons/all-files/io5/IoLink';
import { Button, Input, InputGroup, InputLeftElement, Tooltip } from '@chakra-ui/react';
import { LoggingContext } from 'common/context/LoggingContext';
import { forgivingStringToMillis } from 'common/utils/dateConfig';
import { stringFromMillis } from 'common/utils/time';
import PropTypes from 'prop-types';
import { tooltipDelayFast } from '../../../ontimeConfig';
import style from './TimeInput.module.scss';
export default function TimeInput(props) {
@@ -126,18 +127,46 @@ export default function TimeInput(props) {
const isDelayed = delay != null && delay !== 0;
/*
<IconButton
size='sm'
icon="s"
aria-label='automate'
colorScheme='white'
style={{ borderRadius: '2px', width: 'min-content' }}
tabIndex={-1}
variant='ghost'
/>
*/
const buttonInitial = () => {
if (name === 'timeStart') return 'S';
if (name === 'timeEnd') return 'E';
if (name === 'duration') return 'D';
}
const buttonTooltip = () => {
if (name === 'timeStart') return 'Start';
if (name === 'timeEnd') return 'End';
if (name === 'duration') return 'Duration';
}
return (
<InputGroup size='sm' className={`${style.timeInput} ${isDelayed ? style.delayed : ''}`}>
<InputLeftElement width='fit-content'>
<IconButton
<Tooltip label={buttonTooltip()} openDelay={tooltipDelayFast}>
<Button
size='sm'
icon={<IoLink style={{ transform: 'rotate(-45deg)' }} />}
aria-label='automate'
colorScheme='blue'
style={{ borderRadius: '2px', width: 'min-content' }}
variant='filled'
tabIndex={-1}
variant='ghost'
/>
backgroundColor='#303030'
color='#fffffa'
borderRadius='2px 0 0 2px'
border={isDelayed ? "1px solid #d69e2e55" : "1px solid transparent"}
>
{buttonInitial()}
</Button>
</Tooltip>
</InputLeftElement>
<Input
ref={inputRef}
@@ -0,0 +1,26 @@
@use '../../../theme/main' as *;
.copyTag {
display: flex;
border-radius: 2px;
border: 1px solid $action-blue;
cursor: pointer;
font-size: 0.75em;
width: max-content;
&:active {
border: 1px solid $text-white;
}
}
.label {
background-color: $action-blue;
color: $text-white;
padding: 0 4px;
font-weight: 600;
}
.text {
color: $label-gray;
padding: 0 4px;
}
@@ -0,0 +1,34 @@
import { PropsWithChildren } from 'react';
import { Tooltip } from '@chakra-ui/react';
import { tooltipDelayFast } from '../../../ontimeConfig';
import style from './CopyTag.module.scss';
interface CopyTagProps {
label?: string;
className?: string;
}
export default function CopyTag(props: PropsWithChildren<CopyTagProps>) {
const { label, className, children } = props;
return (
<Tooltip label='Click to copy' openDelay={tooltipDelayFast}>
<button
className={`${style.copyTag} ${className}`}
onClick={() => navigator.clipboard.writeText(children as string)}
tabIndex={-1}
>
{label && (
<span className={style.label}>
{label}
</span>
)}
<span className={style.text}>
{children}
</span>
</button>
</Tooltip>
);
}
+4 -1
View File
@@ -28,10 +28,13 @@ export function formatDisplay(seconds, hideZero = false) {
/**
* @description Converts milliseconds to seconds
* @param {number} millis - time in seconds
* @param {number | null} millis - time in seconds
* @returns {number} Amount in seconds
*/
export const millisToSeconds = (millis) => {
if (millis === null) {
return 0;
}
return millis < 0 ? Math.ceil(millis / mts) : Math.floor(millis / mts);
};
+1 -1
View File
@@ -24,7 +24,7 @@ export const nowInMillis = () => {
/**
* @description Converts milliseconds to string representing time
* @param {number} ms - time in milliseconds
* @param {number | null} ms - time in milliseconds
* @param {boolean} showSeconds - weather to show the seconds
* @param {string} delim - character between HH MM SS
* @param {string} ifNull - what to return if value is null
@@ -2,6 +2,7 @@ import { IconButton, Tooltip } from '@chakra-ui/react';
import { IoMicOffOutline } from '@react-icons/all-files/io5/IoMicOffOutline';
import { IoMicSharp } from '@react-icons/all-files/io5/IoMicSharp';
import CopyTag from '../../../common/components/osc-tag/CopyTag';
import { useMessageControlProvider } from '../../../common/hooks/useSocketProvider';
import { tooltipDelayMid } from '../../../ontimeConfig';
@@ -52,8 +53,11 @@ export default function MessageControl() {
aria-label='Toggle On Air'
/>
</Tooltip>
<span className={style.onAirLabel}>On Air</span>
<span className={style.oscLabel}>{`/ontime/offAir << OSC >> /ontime/onAir`}</span>
<div className={style.onAirLabel}>On Air</div>
<div className={style.oscLabel}>
<CopyTag label='OSC'>/ontime/offAir</CopyTag>
<CopyTag label='OSC'>/ontime/offAir</CopyTag>
</div>
</div>
</>
);
@@ -1,15 +1,20 @@
@use '../../../theme/main' as *;
@use '../../../theme/mixins' as *;
@mixin message-control-label() {
font-size: 0.9em;
color: $label-gray;
}
.messageContainer,
.onAirToggle {
display: flex;
gap: 0.5em;
padding: 0.5em;
}
.messageContainer {
flex-direction: column;
gap: 4px;
.inputItems {
display: grid;
@@ -20,8 +25,7 @@
.label {
padding: 0;
margin: 0;
font-size: 0.9em;
color: $label-gray;
@include message-control-label;
}
.inputRowActive {
@@ -33,25 +37,30 @@
.onAirToggle {
margin-top: 1em;
align-items: center;
display: grid;
grid-template-areas:
'btn label'
'btn osc';
grid-template-columns: 2.5em 1fr;
grid-template-rows: 1.2em 0.8em;
'btn label'
'btn osc';
grid-template-columns: auto 1fr;
grid-template-rows: 1fr 1fr;
align-items: center;
.btn {
aspect-ratio: 1;
grid-area: btn;
height: 100%;
margin-right: 16px;
}
.onAirLabel {
grid-area: label;
font-size: 1.2em;
@include message-control-label;
}
.oscLabel {
@include osc-label;
grid-area: osc;
display: flex;
gap: 4px;
}
}
@@ -1,43 +0,0 @@
import PropTypes from 'prop-types';
import PauseIconBtn from '../../../common/components/buttons/PauseIconBtn';
import RollIconBtn from '../../../common/components/buttons/RollIconBtn';
import StartIconBtn from '../../../common/components/buttons/StartIconBtn';
import style from './PlaybackControl.module.scss';
export default function Playback(props) {
const { playback, selectedId, playbackControl, noEvents } = props;
const isRolling = playback === 'roll';
return (
<div className={style.playbackContainer}>
<StartIconBtn
active={playback === 'start'}
clickhandler={() => playbackControl.start()}
disabled={!selectedId || isRolling || noEvents}
/>
<PauseIconBtn
active={playback === 'pause'}
clickhandler={() => playbackControl.pause()}
disabled={!selectedId || isRolling || noEvents || playback !== 'start'}
/>
<RollIconBtn
active={playback === 'roll'}
disabled={playback === 'roll' || noEvents}
clickhandler={() => playbackControl.roll()}
/>
</div>
);
}
Playback.propTypes = {
playback: PropTypes.string,
selectedId: PropTypes.string,
playbackControl: PropTypes.shape({
start: PropTypes.func,
pause: PropTypes.func,
roll: PropTypes.func,
}).isRequired,
noEvents: PropTypes.bool.isRequired,
};
@@ -0,0 +1,61 @@
import { Tooltip } from '@chakra-ui/react';
import { IoPause } from '@react-icons/all-files/io5/IoPause';
import { IoPlay } from '@react-icons/all-files/io5/IoPlay';
import { IoTimeOutline } from '@react-icons/all-files/io5/IoTimeOutline';
import { usePlaybackControlProvider } from '../../../common/hooks/useSocketProvider';
import { Playstate } from '../../../common/models/OntimeTypes';
import { tooltipDelayMid } from '../../../ontimeConfig';
import TapButton from './TapButton';
import style from './PlaybackControl.module.scss';
interface PlaybackProps {
playback: Playstate;
selectedId: string;
noEvents: boolean;
}
export default function Playback(props: PlaybackProps) {
const { playback, selectedId, noEvents } = props;
const isRolling = playback === 'roll';
const { setPlayback } = usePlaybackControlProvider();
return (
<div className={style.playbackContainer}>
<Tooltip label='Start playback' openDelay={100}>
<TapButton
onClick={() => setPlayback.start()}
disabled={!selectedId || isRolling || noEvents}
theme='start'
active={playback === 'start'}
>
<IoPlay />
</TapButton>
</Tooltip>
<Tooltip label='Pause playback' openDelay={tooltipDelayMid}>
<TapButton
onClick={() => setPlayback.pause()}
disabled={!selectedId || isRolling || noEvents || playback !== 'start'}
theme='pause'
active={playback === 'pause'}
>
<IoPause />
</TapButton>
</Tooltip>
<Tooltip label='Start roll mode' openDelay={tooltipDelayMid}>
<TapButton
onClick={() => setPlayback.roll()}
disabled={playback === 'roll' || noEvents}
theme='roll'
active={isRolling}
>
<IoTimeOutline />
</TapButton>
</Tooltip>
</div>
);
}
@@ -8,9 +8,6 @@
gap: 4px;
}
.playbackContainer {
padding: 0.5em;
}
.timeContainer {
display: grid;
@@ -65,7 +62,7 @@
}
.indNegativeActive {
background-color: $ontime-pink;
background-color: $ontime-pink-variant;
}
.indDelayActive {
@@ -74,11 +71,11 @@
.btn {
grid-area: btn;
display: flex;
flex-direction: row;
flex-wrap: wrap;
align-items: center;
justify-content: space-around;
display: grid;
grid-template-columns: 1fr 1fr;
grid-template-rows: 1fr 1fr;
width: 100%;
gap: 4px;
}
.minus {
@@ -87,6 +84,12 @@
flex-direction: column;
}
.start,
.finish,
.roll {
height: 24px;
}
.start {
grid-area: sta;
}
@@ -1,4 +1,5 @@
import { usePlaybackControlProvider } from '../../../common/hooks/useSocketProvider';
import { Playstate } from '../../../common/models/OntimeTypes';
import PlaybackButtons from './PlaybackButtons';
import PlaybackTimer from './PlaybackTimer';
@@ -6,20 +7,18 @@ import PlaybackTimer from './PlaybackTimer';
import style from './PlaybackControl.module.scss';
export default function PlaybackControl() {
const { data, setPlayback } = usePlaybackControlProvider();
const { data } = usePlaybackControlProvider();
return (
<div className={style.mainContainer}>
<PlaybackTimer
playback={data.playback}
playback={data.playback as Playstate}
selectedId={data.selectedEventId}
handleIncrement={(amount) => setPlayback.delay(amount)}
/>
<PlaybackButtons
playback={data.playback}
selectedId={data.selectedEventId}
noEvents={data.numEvents < 1}
playbackControl={setPlayback}
/>
</div>
);
@@ -1,39 +1,34 @@
import { memo } from 'react';
import { Button, Tooltip } from '@chakra-ui/react';
import { Tooltip } from '@chakra-ui/react';
import TimerDisplay from 'common/components/countdown/TimerDisplay';
import PropTypes from 'prop-types';
import { useTimerProvider } from '../../../common/hooks/useSocketProvider';
import {
usePlaybackControlProvider,
useTimerProvider,
} from '../../../common/hooks/useSocketProvider';
import { Playstate } from '../../../common/models/OntimeTypes';
import { millisToSeconds } from '../../../common/utils/dateConfig';
import { stringFromMillis } from '../../../common/utils/time';
import { tooltipDelayMid } from '../../../ontimeConfig';
import TapButton from './TapButton';
import style from './PlaybackControl.module.scss';
const areEqual = (prevProps, nextProps) => {
return (
prevProps.playback === nextProps.playback &&
prevProps.selectedId === nextProps.selectedId
);
};
interface PlaybackTimerProps {
playback: Playstate;
selectedId: string | null;
}
const incrementProps = {
size: 'sm',
width: '2em',
colorScheme: 'white',
variant: 'outline',
fontSize: '12px',
};
const PlaybackTimer = (props) => {
const { playback, handleIncrement, selectedId } = props;
export default function PlaybackTimer(props: PlaybackTimerProps) {
const { playback, selectedId } = props;
const { setPlayback } = usePlaybackControlProvider();
const timerData = useTimerProvider();
const started = stringFromMillis(timerData.startedAt, true);
const finish = stringFromMillis(timerData.expectedFinish, true);
const isRolling = playback === 'roll';
const isWaiting = timerData.secondaryTimer > 0 && timerData.current == null;
const isWaiting = timerData.secondaryTimer !== null && timerData.secondaryTimer > 0 && timerData.current === null;
const disableButtons = selectedId == null || isRolling;
const isOvertime = timerData.current < 0;
const isOvertime = timerData.current !== null && timerData.current < 0;
return (
<div className={style.timeContainer}>
@@ -68,55 +63,43 @@ const PlaybackTimer = (props) => {
</>
)}
<div className={style.btn}>
<Tooltip label='Remove 1 minute' openDelay={tooltipDelayMid} shouldWrapChildren={disableButtons}>
<Button
{...incrementProps}
<Tooltip label='Remove 1 minute' openDelay={tooltipDelayMid}
shouldWrapChildren={disableButtons}>
<TapButton
onClick={() => setPlayback.delay(-5)}
disabled={disableButtons}
onClick={() => handleIncrement(-1)}
_hover={!disableButtons && { bg: '#ebedf0', color: '#333' }}
>
-1
</Button>
</Tooltip>
<Tooltip label='Add 1 minute' openDelay={tooltipDelayMid} shouldWrapChildren={disableButtons}>
<Button
{...incrementProps}
disabled={disableButtons}
onClick={() => handleIncrement(1)}
_hover={!disableButtons && { bg: '#ebedf0', color: '#333' }}
>
+1
</Button>
</Tooltip>
<Tooltip label='Remove 5 minutes' openDelay={tooltipDelayMid} shouldWrapChildren={disableButtons}>
<Button
{...incrementProps}
disabled={disableButtons}
onClick={() => handleIncrement(-5)}
_hover={!disableButtons && { bg: '#ebedf0', color: '#333' }}
>
square>
-5
</Button>
</TapButton>
</Tooltip>
<Tooltip label='Add 5 minutes' openDelay={tooltipDelayMid} shouldWrapChildren={disableButtons}>
<Button
{...incrementProps}
<Tooltip label='Remove 1 minute' openDelay={tooltipDelayMid}
shouldWrapChildren={disableButtons}>
<TapButton
onClick={() => setPlayback.delay(-5)}
disabled={disableButtons}
onClick={() => handleIncrement(5)}
_hover={!disableButtons && { bg: '#ebedf0', color: '#333' }}
>
+5
</Button>
square>
-1
</TapButton>
</Tooltip>
<Tooltip label='Remove 1 minute' openDelay={tooltipDelayMid}
shouldWrapChildren={disableButtons}>
<TapButton
onClick={() => setPlayback.delay(-5)}
disabled={disableButtons}
square>
1
</TapButton>
</Tooltip>
<Tooltip label='Remove 1 minute' openDelay={tooltipDelayMid}
shouldWrapChildren={disableButtons}>
<TapButton
onClick={() => setPlayback.delay(-5)}
disabled={disableButtons}
square>
5
</TapButton>
</Tooltip>
</div>
</div>
);
};
export default memo(PlaybackTimer, areEqual);
PlaybackTimer.propTypes = {
playback: PropTypes.string,
handleIncrement: PropTypes.func.isRequired,
selectedId: PropTypes.string,
};
@@ -0,0 +1,65 @@
@use '../../../theme/main' as *;
@mixin tap-factory($theme-front, $theme-bg, $theme-high) {
font-family: "Open Sans", sans-serif;
font-size: 22px;
background-color: $theme-bg;
color: $theme-front;
border-radius: 3px;
width: 100%;
aspect-ratio: 3/1;
box-shadow: $theme-high 0 0 2px;
transition: background-color 0.3s;
display: grid;
place-content: center;
&:disabled {
cursor: not-allowed;
background-color: $opacity-disabled;
box-shadow: none;
opacity: $opacity-disabled;
}
&:hover:not(:disabled) {
background-color: $theme-high;
}
&:active:not(:disabled) {
color: $theme-bg;
background-color: $theme-front;
transition: background-color 0.15s;
}
&.active {
background-color: $theme-high;
}
}
.tapButton.neutral{
@include tap-factory(rgba(255, 255, 255, 0.867), #303030, #363636);
}
.tapButton.start {
@include tap-factory(rgba(255, 255, 255, 0.867), #303030, $ontime-accent);
}
.tapButton.roll {
@include tap-factory(rgba(255, 255, 255, 0.867), #303030, $ontime-roll);
}
.tapButton.pause {
@include tap-factory(rgba(255, 255, 255, 0.867), #303030, $ontime-paused);
}
.tapButton.ontime {
@include tap-factory(rgba(255, 255, 255, 0.867), #303030, $ontime-pink);
}
.tapButton.stop {
@include tap-factory(rgba(255, 255, 255, 0.867), #303030, $ontime-red);
}
.tapButton.square {
aspect-ratio: 1;
font-size: 14px;
}
@@ -0,0 +1,31 @@
import { ForwardedRef, forwardRef, PropsWithChildren } from 'react';
import { Playstate } from '../../../common/models/OntimeTypes';
import style from './TapButton.module.scss';
interface TapButtonProps {
disabled?: boolean;
square?: boolean;
onClick: () => void;
theme?: Playstate | 'neutral';
active?: boolean;
}
const TapButton = forwardRef((props: PropsWithChildren<TapButtonProps>, ref: ForwardedRef<HTMLButtonElement> ) => {
const { children, disabled, onClick, theme = 'neutral', square, active } = props;
return (
<button
className={`${style.tapButton} ${style[theme]} ${square ? style.square : ''} ${active ? style.active : ''}`}
disabled={disabled}
type='button'
onClick={onClick}
ref={ref}
>
{children}
</button>
);
});
TapButton.displayName = "TabButton";
export default TapButton;
@@ -1,53 +0,0 @@
import { IoPlayBack } from '@react-icons/all-files/io5/IoPlayBack';
import { IoPlaySkipBack } from '@react-icons/all-files/io5/IoPlaySkipBack';
import { IoPlaySkipForward } from '@react-icons/all-files/io5/IoPlaySkipForward';
import PropTypes from 'prop-types';
import TransportIconBtn from '../../../common/components/buttons/TransportIconBtn';
import UnloadIconBtn from '../../../common/components/buttons/UnloadIconBtn';
import style from './PlaybackControl.module.scss';
export default function Transport(props) {
const { playback, selectedId, playbackControl, noEvents } = props;
const isRolling = playback === 'roll';
return (
<div className={style.playbackContainer}>
<TransportIconBtn
clickHandler={() => playbackControl.previous()}
disabled={isRolling || noEvents}
tooltip='Previous event'
icon={<IoPlaySkipBack size='22px' />}
/>
<TransportIconBtn
clickHandler={() => playbackControl.next()}
disabled={isRolling || noEvents}
tooltip='Next event'
icon={<IoPlaySkipForward size='22px' />}
/>
<TransportIconBtn
clickHandler={() => playbackControl.reload()}
disabled={selectedId == null || isRolling || noEvents}
tooltip='Reload event'
icon={<IoPlayBack size='22px' />}
/>
<UnloadIconBtn
clickHandler={() => playbackControl.stop()}
disabled={(selectedId == null && !isRolling) || noEvents}
/>
</div>
);
}
Transport.propTypes = {
playback: PropTypes.string,
selectedId: PropTypes.string,
playbackControl: PropTypes.shape({
previous: PropTypes.func,
next: PropTypes.func,
reload: PropTypes.func,
stop: PropTypes.func,
}).isRequired,
noEvents: PropTypes.bool.isRequired,
};
@@ -0,0 +1,63 @@
import { Tooltip } from '@chakra-ui/react';
import { IoPlayBack } from '@react-icons/all-files/io5/IoPlayBack';
import { IoPlaySkipBack } from '@react-icons/all-files/io5/IoPlaySkipBack';
import { IoPlaySkipForward } from '@react-icons/all-files/io5/IoPlaySkipForward';
import { IoStop } from '@react-icons/all-files/io5/IoStop';
import { usePlaybackControlProvider } from '../../../common/hooks/useSocketProvider';
import { Playstate } from '../../../common/models/OntimeTypes';
import { tooltipDelayMid } from '../../../ontimeConfig';
import TapButton from './TapButton';
import style from './PlaybackControl.module.scss';
interface TransportProps {
playback: Playstate;
selectedId: string;
noEvents: boolean;
}
export default function Transport(props: TransportProps) {
const { playback, selectedId, noEvents } = props;
const { setPlayback } = usePlaybackControlProvider();
const isRolling = playback === 'roll';
return (
<div className={style.playbackContainer}>
<Tooltip label='Previous event' openDelay={tooltipDelayMid}>
<TapButton
onClick={() => setPlayback.previous()}
disabled={isRolling || noEvents}
>
<IoPlaySkipBack />
</TapButton>
</Tooltip>
<Tooltip label='Next event' openDelay={tooltipDelayMid}>
<TapButton
onClick={() => setPlayback.next()}
disabled={isRolling || noEvents}
>
<IoPlaySkipForward />
</TapButton>
</Tooltip>
<Tooltip label='Reload event' openDelay={tooltipDelayMid}>
<TapButton
onClick={() => setPlayback.reload()}
disabled={selectedId == null || isRolling || noEvents}
>
<IoPlayBack />
</TapButton>
</Tooltip>
<Tooltip label='Unload Event' openDelay={100}>
<TapButton
onClick={() => setPlayback.stop()}
disabled={(selectedId == null && !isRolling) || noEvents}
theme='stop'
>
<IoStop />
</TapButton>
</Tooltip>
</div>
);
}
@@ -131,7 +131,7 @@
.eventEditor {
border-radius: 3px 3px 0 0;
background-color: $bg-container-l1;
background-color: $bg-black;
border-top: 1px solid $bg-gray-900;
position: absolute;
bottom: 0;
@@ -152,7 +152,7 @@
}
.header {
background-color: $bg-container-l2;
background-color: $bg-black;
padding: 8px;
border-left: 1px solid $bg-container-l3;
}
@@ -186,8 +186,8 @@
.playback {
grid-area: play;
min-height: 270px;
max-height: 270px;
min-height: 275px;
max-height: 380px;
min-width: 480px;
}
@@ -14,7 +14,7 @@ $block-border-radius: 3px;
flex-direction: row;
flex-wrap: wrap;
align-content: center;
opacity: 0.8;
opacity: 0.6;
transition: linear 0.1s;
}
@@ -26,7 +26,7 @@ $block-border-radius: 3px;
@mixin drag-style() {
font-size: 20px;
text-align: center;
opacity: 0.1;
opacity: 0.3;
cursor: grab;
transition: opacity 0.3s;
&:hover {
@@ -24,10 +24,10 @@ export default function BlockBlock(props) {
clickHandler={() => actionHandler('delete')}
icon={<IoRemove />}
tooltip='Delete'
variant='ghost'
_hover={{ bg: 'red.400', color: 'white' }}
color='red.500'
variant='outline'
colorScheme='white'
size='sm'
aria-label='Delete'
/>
<ActionButtons showAdd showDelay actionHandler={actionHandler} />
</HStack>
@@ -6,24 +6,21 @@
.block {
@include block-spacing;
box-sizing: content-box;
display: grid;
grid-template-columns: 40px 1fr;
align-items: center;
height: 40px;
border-radius: 2px 2px $block-border-radius $block-border-radius;
background-color: rgba(107, 70, 193, 0.4);
background: linear-gradient(
0deg,
rgba(107, 70, 193, 0.4) 0%,
rgba(128, 90, 213, 0.4) 20%,
rgba(107, 70, 193, 0.15) 21%
);
border-radius: 2px;
border: $border-l3;
border-bottom: 4px solid $block-block-color;
background-color: $bg-container-l2;
}
/* ================ DRAG ================ */
.drag {
@include drag-style;
color: $block-block-color;
}
/* ============== ACTION ================ */
@@ -55,9 +55,10 @@ export default function DelayBlock(props) {
<Button
onClick={applyDelayHandler}
size='sm'
colorScheme='orange'
_hover={{ bg: 'orange.400' }}
color="#F57C13"
borderColor="#F57C13"
leftIcon={<FiCheck />}
variant='outline'
>
Apply delay
</Button>
@@ -65,9 +66,8 @@ export default function DelayBlock(props) {
clickHandler={deleteHandler}
icon={<IoRemove />}
tooltip='Delete'
variant='ghost'
_hover={{ bg: 'red.400', color: 'white' }}
color='red.500'
variant='outline'
colorScheme='white'
size='sm'
/>
<ActionButtons showAdd actionHandler={actionHandler} />
@@ -6,20 +6,17 @@
.delay {
@include block-spacing;
box-sizing: content-box;
display: grid;
grid-template-columns: 32px 1fr auto;
grid-template-areas: 'drag inpt btns';
align-items: center;
height: 40px;
border-radius: 2px;
border: $border-l3;
border-top: 4px solid $block-delay-color;
background-color: $bg-container-l2;
border-radius: $block-border-radius $block-border-radius 2px 2px;
background-color: rgba(214, 158, 46, 0.4);
background: linear-gradient(
180deg,
rgba(214, 158, 46, 0.4) 0%,
rgba(214, 158, 46, 0.4) 20%,
rgba(214, 158, 46, 0.17) 21%
);
}
/* ================ DRAG ================ */
@@ -27,6 +24,7 @@
.drag {
grid-area: drag;
@include drag-style;
color: $block-delay-color;
}
/* =============== INPUT ================ */
@@ -12,7 +12,7 @@
grid-template-columns: $binder-width auto 1fr auto;
grid-template-rows: 36px 36px 36px $element-spacing;
align-items: center;
margin: 2px 2px;
margin:4px 2px;
padding-right: $clearance;
// style - general
@@ -26,7 +26,6 @@
// variant
&.selected {
background-color: $bg-container-l3;
box-shadow: 0 0 1px 2px $ontime-accent;
}
&.skip {
@@ -87,7 +86,7 @@
height: 100%;
.delayNote {
font-size: 10px;
font-size: 11px;
color: $text-delay;
}
}
@@ -148,9 +147,8 @@
border: 1px solid transparent;
&.enabled {
color: $ontime-accent;
border: 1px solid $ontime-accent;
background-color: $bg-gray-1100;
color: $text-white;
background-color: $ontime-accent;
}
}
@@ -3,7 +3,7 @@ import { Draggable } from 'react-beautiful-dnd';
import { Editable, EditableInput, EditablePreview, Tooltip } from '@chakra-ui/react';
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 { 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';
@@ -108,7 +108,7 @@ export default function EventBlock(props: EventBlockProps) {
if (!skip && eventIsPlaying) {
playBtnStyles._hover = { bg: '#c05621' };
} else if (!skip && !eventIsPlaying) {
playBtnStyles._hover = { bg: '#4bffab' };
playBtnStyles._hover = { };
}
return (
@@ -161,19 +161,15 @@ export default function EventBlock(props: EventBlockProps) {
tabIndex={-1}
/>
<TooltipActionBtn
aria-label='Toggle start / pause event'
tooltip={eventIsPlaying ? 'Pause event' : 'Start event'}
aria-label='Start event'
tooltip='Start event'
openDelay={tooltipDelayMid}
icon={eventIsPlaying ? <IoPause /> : <IoPlayOutline />}
icon={eventIsPlaying ? <IoPlay /> : <IoPlayOutline />}
disabled={skip}
{...blockBtnStyle}
variant={eventIsPlaying ? 'solid' : 'ghost'}
clickHandler={
eventIsPlaying
? () => setPlayback.pause()
: () => setPlayback.startEvent()
}
{...playBtnStyles}
clickHandler={() => setPlayback.startEvent()}
backgroundColor={eventIsPlaying ? '#58A151' : undefined}
tabIndex={-1}
/>
</div>
@@ -27,8 +27,8 @@ export default function EventBlockActionMenu(props) {
const blockBtnStyle = {
size: 'sm',
variant: 'ghost',
colorScheme: 'blue',
variant: 'outline',
colorScheme: 'whiteAlpha',
};
return (
@@ -15,7 +15,7 @@ import { useAtomValue } from 'jotai';
import PropTypes from 'prop-types';
import useSubscription from '../../../common/hooks/useSubscription';
import EntryBlock from '../entry-block/EntryBlock';
import QuickAddBlock from '../quick-add-block/QuickAddBlock';
import EventListItem from './EventListItem';
@@ -231,7 +231,7 @@ export default function EventList(props) {
/>
</div>
{((showQuickEntry && index === cursor) || isLast) && (
<EntryBlock
<QuickAddBlock
showKbd={index === cursor}
previousId={e.id}
previousEventId={previousEventId}
@@ -27,7 +27,7 @@
.bgElement {
&.delayed {
background: rgba(214, 158, 46, 0.3);
background: rgba($block-delay-color, 0.3);
}
}
@@ -1,6 +1,6 @@
@use '../../../theme/main' as *;
.create {
.quickAdd {
background-color: $bg-container-l2;
display: flex;
align-items: center;
@@ -81,4 +81,5 @@
border-color: $text-gray-disabled;
color: $text-gray-disabled;
pointer-events: none;
cursor: not-allowed;
}
@@ -8,9 +8,9 @@ import { useAtomValue } from 'jotai';
import { tooltipDelayMid } from '../../../ontimeConfig';
import style from './EntryBlock.module.scss';
import style from './QuickAddBlock.module.scss';
interface EntryBlockProps {
interface QuickAddBlockProps {
showKbd: boolean;
previousId?: string;
previousEventId: string | null;
@@ -18,7 +18,7 @@ interface EntryBlockProps {
disableAddBlock: boolean;
}
export default function EntryBlock(props: EntryBlockProps) {
export default function QuickAddBlock(props: QuickAddBlockProps) {
const {
showKbd,
previousId,
@@ -61,7 +61,7 @@ export default function EntryBlock(props: EntryBlockProps) {
}, [addEvent, doPublic, doStartTime, emitError, previousId, previousEventId]);
return (
<div className={style.create}>
<div className={style.quickAdd}>
<Tooltip label='Add Event' openDelay={tooltipDelayMid}>
<span
className={style.createEvent}
@@ -1,5 +1,5 @@
import { useCallback, useContext, useEffect, useState } from 'react';
import { Button } from '@chakra-ui/react';
import { Button, Select } from '@chakra-ui/react';
import { FiUsers } from '@react-icons/all-files/fi/FiUsers';
import { IoBan } from '@react-icons/all-files/io5/IoBan';
import { editorEventId } from 'common/atoms/LocalEventSettings';
@@ -17,6 +17,7 @@ import { useAtom } from 'jotai';
import useEventsList from '../../common/hooks-query/useEventsList';
import style from './EventEditor.module.scss';
import CopyTag from '../../common/components/osc-tag/CopyTag';
export default function EventEditor() {
const [openId] = useAtom(editorEventId);
@@ -104,40 +105,57 @@ export default function EventEditor() {
return (
<div className={style.eventEditor}>
<div className={style.timers}>
<label className={style.inputLabel}>
Start time {delayed && <span className={style.delayLabel}>{addedTime}</span>}
{delayed && <div className={style.delayLabel}>{newStart}</div>}
</label>
<TimeInput
name='timeStart'
submitHandler={handleSubmit}
validationHandler={timerValidationHandler}
time={event.timeStart}
delay={0}
placeholder='Start'
/>
<label className={style.inputLabel}>
End time {delayed && <span className={style.delayLabel}>{addedTime}</span>}
{delayed && <div className={style.delayLabel}>{newEnd}</div>}
</label>
<TimeInput
name='timeEnd'
submitHandler={handleSubmit}
validationHandler={timerValidationHandler}
time={event.timeEnd}
delay={0}
placeholder='End'
/>
<label className={style.inputLabel}>Duration</label>
<TimeInput
name='duration'
submitHandler={handleSubmit}
validationHandler={timerValidationHandler}
time={event.duration}
delay={0}
placeholder='Duration'
/>
<div className={style.timeOptions}>
<div className={style.timers}>
<label className={style.inputLabel}>
Start time {delayed && <span className={style.delayLabel}>{addedTime}</span>}
{delayed && <div className={style.delayLabel}>{newStart}</div>}
</label>
<TimeInput
name='timeStart'
submitHandler={handleSubmit}
validationHandler={timerValidationHandler}
time={event.timeStart}
delay={0}
placeholder='Start'
/>
<label className={style.inputLabel}>
End time {delayed && <span className={style.delayLabel}>{addedTime}</span>}
{delayed && <div className={style.delayLabel}>{newEnd}</div>}
</label>
<TimeInput
name='timeEnd'
submitHandler={handleSubmit}
validationHandler={timerValidationHandler}
time={event.timeEnd}
delay={0}
placeholder='End'
/>
<label className={style.inputLabel}>Duration</label>
<TimeInput
name='duration'
submitHandler={handleSubmit}
validationHandler={timerValidationHandler}
time={event.duration}
delay={0}
placeholder='Duration'
/>
</div>
<div className={style.timeSettings}>
<label className={style.inputLabel}>Timer type</label>
<Select size='sm' variant='filled' color='black'>
<option value='option1'>Start to end</option>
<option value='option2'>Duration</option>
<option value='option3'>Follow previous</option>
<option value='option3'>Start only</option>
</Select>
<label className={style.inputLabel}>Countdown style</label>
<Select size='sm' variant='filled' color='black'>
<option value='option1'>Count down</option>
<option value='option2'>Count up</option>
<option value='option3'>Clock</option>
</Select>
</div>
</div>
<div className={style.titles}>
<div className={style.left}>
@@ -199,7 +217,7 @@ export default function EventEditor() {
/>
</div>
</div>
<div className={style.osc}>{`OSC Trigger /ontime/gotoid/${event.id}`}</div>
<CopyTag label='OSC trigger' className={style.osc}>{`/ontime/gotoid/${event.id}`}</CopyTag>
</div>
</div>
);
@@ -8,26 +8,39 @@
gap: max(16px, 2vh);
display: grid;
grid-template-areas: 'timers titles';
grid-template-areas: 'timeOptions titles';
grid-template-columns: auto 1fr;
.timers,
.timeOptions,
.titles {
background-color: $bg-container-l2;
background-color: $bg-container-over;
border-radius: 2px;
padding: 8px 16px;
}
.timers {
grid-area: timers;
.timeOptions {
grid-area: timeOptions;
display: flex;
gap: 24px;
}
.timers, .timeSettings {
display: flex;
flex-direction: column;
gap: 8px;
}
.timers label:nth-child(1) {
.timers label:nth-child(1),
.timeSettings label:nth-child(1),
{
margin-top: 4px;
}
.timeSettings {
display: flex;
flex-direction: column;
gap: 8px;
}
.titles {
grid-area: titles;
display: grid;
@@ -57,10 +70,7 @@
.osc {
grid-area: tag;
font-size: 14px;
color: $ontime-accent;
text-align: right;
user-select: text;
justify-self: end;
}
}
+2 -2
View File
@@ -36,8 +36,8 @@ export default function Info() {
<span>{selected}</span>
</div>
<InfoNif />
<InfoTitle title='Now' data={titlesNow} />
<InfoTitle title='Next' data={titlesNext} />
<InfoTitle title='Playing Now' data={titlesNow} />
<InfoTitle title='Playing Next' data={titlesNext} />
<InfoLogger />
</>
);
-15
View File
@@ -11,7 +11,6 @@
@include container;
}
.main {
font-size: 0.9em;
color: $label-gray;
@@ -19,20 +18,6 @@
justify-content: space-between;
}
.header,
.headerRoll {
padding: 0;
margin: 0;
font-size: 0.9em;
color: $header-gray;
display: flex;
justify-content: space-between;
}
.header {
color: $header-gray;
}
.collapsedTitle {
font-size: inherit;
@@ -18,7 +18,6 @@
overflow-y: scroll;
font-size: 0.8em;
user-select: text;
@include third-container;
padding: 0 0.5em;
margin: 0 0.5em;
@@ -78,8 +77,8 @@
}
div.active {
background: $ontime-accent;
color: darken($ontime-accent, 70%);
background: $ontime-roll;
color: white;
}
.clear {
@@ -4,3 +4,9 @@
gap: 8px;
padding-top: 24px;
}
.labelledSwitch {
display: flex;
align-items: center;
gap: 8px;
}
+18 -21
View File
@@ -1,6 +1,5 @@
import { memo, useCallback, useContext } from 'react';
import {
Button,
Divider,
HStack,
IconButton,
@@ -8,6 +7,7 @@ import {
MenuButton,
MenuItem,
MenuList,
Switch,
Tooltip,
} from '@chakra-ui/react';
import { FiClock } from '@react-icons/all-files/fi/FiClock';
@@ -29,13 +29,10 @@ const EventListMenu = () => {
const { isCursorLocked, toggleCursorLocked } = useContext(CursorContext);
const { addEvent, deleteAllEvents } = useEventAction();
type ActionTypes = 'event' | 'delay' | 'block' | 'delete-all' | 'toggle-lock';
const actionHandler = useCallback(
type ActionTypes = 'event' | 'delay' | 'block' | 'delete-all';
const eventAction = useCallback(
(action: ActionTypes) => {
switch (action) {
case 'toggle-lock':
toggleCursorLocked();
break;
case 'event':
addEvent({ type: action });
break;
@@ -55,14 +52,14 @@ const EventListMenu = () => {
return (
<HStack className={style.headerButtons}>
<Button
size='sm'
variant={isCursorLocked ? 'solid' : 'ghost'}
onClick={() => actionHandler('toggle-lock')}
colorScheme='blue'
>
<label className={style.labelledSwitch}>
<Switch
defaultChecked={isCursorLocked}
onChange={(event) => toggleCursorLocked(event.target.checked)}
colorScheme='blue'
/>
Lock cursor to current
</Button>
</label>
<Menu isLazy lazyBehavior='unmount'>
<Tooltip label='Add / Delete ...'>
<MenuButton
@@ -70,22 +67,22 @@ const EventListMenu = () => {
aria-label='Create Menu'
size='sm'
icon={<FiPlus />}
colorScheme='blue'
colorScheme='white'
variant='outline'
/>
</Tooltip>
<MenuList style={menuStyle}>
<MenuItem icon={<FiPlus />} onClick={() => actionHandler('event')}>
Add Event first
<MenuItem icon={<FiPlus />} onClick={() => eventAction('event')}>
Add Event at start
</MenuItem>
<MenuItem icon={<FiClock />} onClick={() => actionHandler('delay')}>
Add Delay first
<MenuItem icon={<FiClock />} onClick={() => eventAction('delay')}>
Add Delay at start
</MenuItem>
<MenuItem icon={<FiMinusCircle />} onClick={() => actionHandler('block')}>
Add Block first
<MenuItem icon={<FiMinusCircle />} onClick={() => eventAction('block')}>
Add Block at start
</MenuItem>
<Divider />
<MenuItem icon={<FiTrash2 />} onClick={() => actionHandler('delete-all')} color='red.500'>
<MenuItem icon={<FiTrash2 />} onClick={() => eventAction('delete-all')} color='red.500'>
Delete All
</MenuItem>
</MenuList>
+12 -8
View File
@@ -17,15 +17,17 @@ $bg-gray-100: #c0c0c0; // borders and whatnot
$bg-overlay: rgba(0, 0, 0, 0.85);
$ontime-accent: #4bffab;
// $ontime-accent: #4bffab);
$ontime-accent: #58A151;
$ontime-accent-text: mix($bg-black, $ontime-accent, 10%);
$ontime-pink: #ff7597;
$ontime-pink-variant: #ff6969;
$ontime-roll: #2b6cb0;
$ontime-delay: #dd6b20;
$ontime-roll: #0274B6;
$ontime-delay: #F57C13;
$action-blue: #3182ce;
$ontime-paused: #c05621;
$opacity-disabled: 0.4;
$ontime-red: #E4281E;
//rgba(255, 255, 255, 0.39); - $bg-gray-700
//rgba(255, 255, 255, 0.13); - $bg-gray-900
@@ -40,15 +42,15 @@ $opacity-disabled: 0.4;
// outdent in level2 - $bg-gray-950
//////////////////////////////////// editor
$notes-color: #d69e2e;
$notes-color: #9fd8ff;
$text-white: #fffffa;
$text-gray-disabled: #505050;
$header-gray: #ccc;
$label-gray: #aaa;
$header-gray: $label-gray;
$clocks: #ddd;
$bg-gray: #f4f4f8;
$text-delay: #a97d24;
$text-delay: #F57C13;
$light-bg: #2b6cb0;
$light-bg-transparent: #2b6cb055;
@@ -65,16 +67,18 @@ $title-gray: #ddd;
$subtitle-gray: #aaa;
//////////////////////////////////// block elements
$bg-container-over: #0b1521;
$bg-container-over-l1: #132337;
$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: #E2720D;
$delay-text: #d69e2e;
$block-delay-border: #d69e2e55;
$block-block-color: #805ad5;
$block-block-color: #7347AD;
$block-border: 1px solid $bg-gray-1100;
//////////////////////////////////// viewer cards
-7
View File
@@ -18,10 +18,3 @@
border: 1px solid rgba(0, 0, 0, 0.05);
border-radius: 2px;
}
@mixin osc-label {
color: $ontime-accent-text;
font-size: 0.8em;
-webkit-user-select: text;
user-select: text;
}