V2 monorepo (#285)

* refactor(project structure): UI

* refactor(project structure): extract utilities

* refactor(project structure): remove unused

* refactor(project structure): electron

* refactor(project structure): server

refactor: migrate to vitest

refactor: monorepo config

* refactor: extract application menu

* refactor: exit process

* refactor: extract tray menu

* chore: electron build

* Added Seconds in studio clock #282
---------

Co-authored-by: Fabian Posenau <fabian@fphome.de>

---------

Co-authored-by: Fabian Posenau <fabian.p99@gmx.de>
Co-authored-by: Fabian Posenau <fabian@fphome.de>
This commit is contained in:
Carlos Valente
2023-02-14 22:02:15 +01:00
committed by GitHub
parent 3918758d32
commit de9a7a87fd
439 changed files with 11381 additions and 14294 deletions
@@ -0,0 +1,28 @@
import { Playback } from '../../../common/models/OntimeTypes';
import PlaybackDisplay from './PlaybackDisplay';
import Transport from './Transport';
interface PlaybackButtonsProps {
playback: Playback;
selectedId: string | null;
noEvents: boolean;
}
export default function PlaybackButtons(props: PlaybackButtonsProps) {
const { playback, selectedId, noEvents } = props;
return (
<>
<PlaybackDisplay
playback={playback}
selectedId={selectedId}
noEvents={noEvents}
/>
<Transport
playback={playback}
selectedId={selectedId}
noEvents={noEvents}
/>
</>
);
};
@@ -0,0 +1,129 @@
@use '../../../theme/v2Styles' as *;
@use '../../../theme/ontimeColours' as *;
@use '../../../theme/mixins' as *;
.mainContainer {
width: 100%;
display: grid;
margin: 0 auto;
gap: $element-inner-spacing;
}
.timeContainer {
display: grid;
grid-template-areas:
'ind clk clk btn'
'... sta fin btn';
grid-template-rows: 1fr auto;
grid-template-columns: 1.5em 1fr 1fr 5em;
gap: $element-inner-spacing;
justify-items: start;
}
.timer {
grid-area: clk;
white-space: nowrap;
max-width: 300px;
}
.indicators {
grid-area: ind;
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
justify-content: space-evenly;
}
.indRoll,
.indDelay,
.indNegative {
background-color: $gray-1300;
}
.indRoll,
.indRollActive,
.indDelay {
margin: 0 auto;
border-radius: 6px;
width: 12px;
height: 12px;
}
.indRollActive {
background-color: $ontime-roll;
}
.indNegative,
.indNegativeActive {
margin: 0 auto;
width: 90%;
height: 4px;
}
.indNegativeActive {
background-color: $playback-negative;
}
.indDelayActive {
background-color: $ontime-delay;
}
.btn {
grid-area: btn;
display: grid;
grid-template-columns: 1fr 1fr;
grid-template-rows: 1fr 1fr;
width: 100%;
gap: $element-inner-spacing;
}
.minus {
grid-area: min;
display: flex;
flex-direction: column;
}
.start,
.finish,
.roll {
height: 24px;
}
.start {
grid-area: sta;
}
.finish {
grid-area: fin;
}
.roll {
grid-area: 2 / 2 / 2 / 4 ;
}
.time {
color: $section-white;
font-size: $text-body-size;
}
.tag {
color: $label-gray;
font-size: 13px;
}
.rolltag {
color: $ontime-roll;
font-size: $text-body-size;
}
.playbackContainer {
display: flex;
justify-content: space-evenly;
padding-top: 0.5em;
gap: $element-spacing;
}
.invertX {
transform: rotateY(180deg);
}
@@ -0,0 +1,25 @@
import { usePlaybackControl } from '../../../common/hooks/useSocket';
import { Playback } from '../../../common/models/OntimeTypes';
import PlaybackButtons from './PlaybackButtons';
import PlaybackTimer from './PlaybackTimer';
import style from './PlaybackControl.module.scss';
export default function PlaybackControl() {
const { data } = usePlaybackControl();
return (
<div className={style.mainContainer}>
<PlaybackTimer
playback={data.playback as Playback}
selectedId={data.selectedEventId}
/>
<PlaybackButtons
playback={data.playback}
selectedId={data.selectedEventId}
noEvents={data.numEvents < 1}
/>
</div>
);
}
@@ -0,0 +1,55 @@
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 { setPlayback } from '../../../common/hooks/useSocket';
import { Playback } from '../../../common/models/OntimeTypes';
import TapButton from './TapButton';
import style from './PlaybackControl.module.scss';
interface PlaybackProps {
playback: Playback;
selectedId: string | null;
noEvents: boolean;
}
export default function PlaybackDisplay(props: PlaybackProps) {
const { playback, selectedId, noEvents } = props;
const isRolling = playback === 'roll';
const isPlaying = playback === 'play';
const isPaused = playback === 'pause';
const isArmed = playback === 'armed';
return (
<div className={style.playbackContainer}>
<TapButton
onClick={() => setPlayback.start()}
disabled={!selectedId || isRolling}
theme='play'
active={isPlaying}
>
<IoPlay />
</TapButton>
<TapButton
onClick={() => setPlayback.pause()}
disabled={!selectedId || isRolling || isArmed}
theme='pause'
active={isPaused}
>
<IoPause />
</TapButton>
<TapButton
onClick={() => setPlayback.roll()}
disabled={noEvents}
theme='roll'
active={isRolling}
>
<IoTimeOutline />
</TapButton>
</div>
);
}
@@ -0,0 +1,82 @@
import { Tooltip } from '@chakra-ui/react';
import TimerDisplay from '../../../common/components/timer-display/TimerDisplay';
import { setPlayback, useTimer } from '../../../common/hooks/useSocket';
import { Playback } from '../../../common/models/OntimeTypes';
import { stringFromMillis } from '../../../common/utils/time';
import { tooltipDelayMid } from '../../../ontimeConfig';
import TapButton from './TapButton';
import style from './PlaybackControl.module.scss';
interface PlaybackTimerProps {
playback: Playback;
selectedId: string | null;
}
export default function PlaybackTimer(props: PlaybackTimerProps) {
const { playback, selectedId } = props;
const { data: timerData } = useTimer();
// TODO: checkout typescript in utilities
const started = stringFromMillis(timerData?.startedAt, true);
const finish = stringFromMillis(timerData.expectedFinish, true);
const isRolling = playback === 'roll';
const isWaiting = timerData.secondaryTimer !== null && timerData.secondaryTimer > 0 && timerData.current === null;
const disableButtons = selectedId === null || isRolling;
const isOvertime = timerData.current !== null && timerData.current < 0;
return (
<div className={style.timeContainer}>
<div className={style.indicators}>
<Tooltip label='Roll mode active'>
<div className={isRolling ? style.indRollActive : style.indRoll} />
</Tooltip>
<div className={isOvertime ? style.indNegativeActive : style.indNegative} />
<div className={style.indDelay} />
</div>
<div className={style.timer}>
<TimerDisplay time={isWaiting ? timerData.secondaryTimer : timerData.current} small />
</div>
{isWaiting ? (
<div className={style.roll}>
<span className={style.rolltag}>Roll: Countdown to start</span>
</div>
) : (
<>
<div className={style.start}>
<span className={style.tag}>Started at </span>
<span className={style.time}>{started}</span>
</div>
<div className={style.finish}>
<span className={style.tag}>Finish at </span>
<span className={style.time}>{finish}</span>
</div>
</>
)}
<div className={style.btn}>
<Tooltip label='Remove 1 minute' openDelay={tooltipDelayMid} shouldWrapChildren={disableButtons}>
<TapButton onClick={() => setPlayback.delay(-1)} disabled={disableButtons} square>
-1
</TapButton>
</Tooltip>
<Tooltip label='Add 1 minute' openDelay={tooltipDelayMid} shouldWrapChildren={disableButtons}>
<TapButton onClick={() => setPlayback.delay(1)} disabled={disableButtons} square>
+1
</TapButton>
</Tooltip>
<Tooltip label='Remove 5 minutes' openDelay={tooltipDelayMid} shouldWrapChildren={disableButtons}>
<TapButton onClick={() => setPlayback.delay(-5)} disabled={disableButtons} square>
-5
</TapButton>
</Tooltip>
<Tooltip label='Add 5 minutes' openDelay={tooltipDelayMid} shouldWrapChildren={disableButtons}>
<TapButton onClick={() => setPlayback.delay(+5)} disabled={disableButtons} square>
+5
</TapButton>
</Tooltip>
</div>
</div>
);
}
@@ -0,0 +1,84 @@
@use '../../../theme/v2Styles' as *;
@use '../../../theme/ontimeColours' as *;
$button-bg-gray: $gray-1050;
$button-color-white: $gray-50;
@mixin tap-factory($theme-color) {
font-family: $ontime-font-family;
font-size: 22px;
border-radius: $component-border-radius-md;
width: 100%;
aspect-ratio: 3/1;
transition-property: color, background-color;
transition-duration: $transition-time-feedback;
display: grid;
place-content: center;
letter-spacing: 0.5px;
background-color: $button-bg-gray;
color: $theme-color;
&:disabled {
cursor: not-allowed;
opacity: $opacity-disabled;
}
&:hover:not(:disabled) {
color: $button-color-white;
background-color: $theme-color;
}
&:active:not(:disabled) {
color: $button-bg-gray;
background-color: $button-color-white;
transition-property: color, background-color;
transition-duration: $transition-time-action;
}
&.active {
background-color: $theme-color;
color: $button-color-white;
}
}
.tapButton.neutral {
@include tap-factory($gray-50);
&:hover:not(:disabled) {
color: $gray-50;
background-color: $gray-1000;
}
&:active:not(:disabled) {
color: $button-bg-gray;
background-color: $button-color-white;
transition-property: color, background-color;
transition-duration: $transition-time-action;
}
}
.tapButton.play {
@include tap-factory($playback-start);
}
.tapButton.roll {
@include tap-factory($ontime-roll);
}
.tapButton.pause {
@include tap-factory($ontime-paused);
}
.tapButton.ontime {
@include tap-factory($ontime-color);
}
.tapButton.stop {
@include tap-factory($ontime-stop);
}
.tapButton.square {
aspect-ratio: 1;
font-size: 14px;
}
@@ -0,0 +1,31 @@
import { ForwardedRef, forwardRef, PropsWithChildren } from 'react';
import { Playback } from '../../../common/models/OntimeTypes';
import style from './TapButton.module.scss';
interface TapButtonProps {
disabled?: boolean;
square?: boolean;
onClick: () => void;
theme?: Playback | '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;
@@ -0,0 +1,22 @@
import { Box } from '@chakra-ui/react';
import { IoArrowUp } from '@react-icons/all-files/io5/IoArrowUp';
import ErrorBoundary from '../../../common/components/error-boundary/ErrorBoundary';
import { handleLinks } from '../../../common/utils/linkUtils';
import PlaybackControl from './PlaybackControl';
import style from '../../editors/Editor.module.scss';
export default function TimerControlExport() {
return (
<Box className={style.playback} data-testid='panel-timer-control'>
<IoArrowUp className={style.corner} onClick={(event) => handleLinks(event, 'timercontrol')} />
<div className={style.content}>
<ErrorBoundary>
<PlaybackControl />
</ErrorBoundary>
</div>
</Box>
);
}
@@ -0,0 +1,62 @@
import { Tooltip } from '@chakra-ui/react';
import { IoPlaySkipBack } from '@react-icons/all-files/io5/IoPlaySkipBack';
import { IoPlaySkipForward } from '@react-icons/all-files/io5/IoPlaySkipForward';
import { IoReload } from '@react-icons/all-files/io5/IoReload';
import { IoStop } from '@react-icons/all-files/io5/IoStop';
import { setPlayback } from '../../../common/hooks/useSocket';
import { Playback } from '../../../common/models/OntimeTypes';
import { tooltipDelayMid } from '../../../ontimeConfig';
import TapButton from './TapButton';
import style from './PlaybackControl.module.scss';
interface TransportProps {
playback: Playback;
selectedId: string | null;
noEvents: boolean;
}
export default function Transport(props: TransportProps) {
const { playback, selectedId, noEvents } = props;
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 || isRolling}
>
<IoReload className={style.invertX} />
</TapButton>
</Tooltip>
<Tooltip label='Unload Event' openDelay={tooltipDelayMid}>
<TapButton
onClick={() => setPlayback.stop()}
disabled={!selectedId && !isRolling}
theme='stop'
>
<IoStop />
</TapButton>
</Tooltip>
</div>
);
}