mirror of
https://github.com/cpvalente/ontime.git
synced 2026-08-17 21:24:11 +00:00
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:
@@ -0,0 +1,79 @@
|
||||
@use '../../../theme/viewerDefs' as *;
|
||||
|
||||
.minimal-timer {
|
||||
margin: 0;
|
||||
box-sizing: border-box; /* reset */
|
||||
overflow: hidden;
|
||||
width: 100%; /* restrict the page width to viewport */
|
||||
height: 100vh;
|
||||
|
||||
background: var(--background-color-override, $viewer-background-color);
|
||||
color: var(--color-override, $viewer-color);
|
||||
display: grid;
|
||||
place-content: center;
|
||||
|
||||
&--finished {
|
||||
outline: clamp(4px, 1vw, 16px) solid $timer-finished-color;
|
||||
outline-offset: calc(clamp(4px, 1vw, 16px) * -1);
|
||||
transition: $viewer-transition-time;
|
||||
}
|
||||
|
||||
.timer {
|
||||
font-family: var(--font-family-bold-override, $timer-bold-font-family) ;
|
||||
font-size: 20vw;
|
||||
position: relative;
|
||||
color: var(--timer-color-override, $timer-color);
|
||||
opacity: 1;
|
||||
transition: $viewer-transition-time;
|
||||
transition-property: opacity;
|
||||
background-color: transparent;
|
||||
letter-spacing: 0.05em;
|
||||
|
||||
&--paused {
|
||||
opacity: $viewer-opacity-disabled;
|
||||
transition: $viewer-transition-time;
|
||||
}
|
||||
|
||||
&--finished {
|
||||
color: $timer-finished-color;
|
||||
}
|
||||
}
|
||||
|
||||
/* =================== OVERLAY ===================*/
|
||||
|
||||
.message-overlay {
|
||||
position: fixed;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: $viewer-overlay-bg-color;
|
||||
z-index: -1;
|
||||
opacity: 0;
|
||||
transition: $viewer-transition-time;
|
||||
|
||||
&--active {
|
||||
opacity: 1;
|
||||
transition: $viewer-transition-time;
|
||||
transition-property: opacity;
|
||||
z-index: 2;
|
||||
}
|
||||
}
|
||||
|
||||
.message {
|
||||
width: inherit;
|
||||
padding: 2vw;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
color: white;
|
||||
transform: translate(-50%, -50%);
|
||||
-ms-transform: translate(-50%, -50%);
|
||||
font-size: 15vw;
|
||||
line-height: 30vh;
|
||||
text-align: center;
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,179 @@
|
||||
import { useEffect } from 'react';
|
||||
import { useSearchParams } from 'react-router-dom';
|
||||
import { useAtom } from 'jotai';
|
||||
|
||||
import { overrideStylesURL } from '../../../common/api/apiConstants';
|
||||
import { mirrorViewersAtom } from '../../../common/atoms/ViewerSettings';
|
||||
import NavigationMenu from '../../../common/components/navigation-menu/NavigationMenu';
|
||||
import { useRuntimeStylesheet } from '../../../common/hooks/useRuntimeStylesheet';
|
||||
import { PresenterMessageType } from '../../../common/models/PresenterMessage.type';
|
||||
import { TimeManagerType } from '../../../common/models/TimeManager.type';
|
||||
import { ViewSettingsType } from '../../../common/models/ViewSettings.type';
|
||||
import { OverridableOptions } from '../../../common/models/ViewTypes';
|
||||
import { formatDisplay, millisToSeconds } from '../../../common/utils/dateConfig';
|
||||
|
||||
import './MinimalTimer.scss';
|
||||
|
||||
interface MinimalTimerProps {
|
||||
pres: PresenterMessageType;
|
||||
time: TimeManagerType;
|
||||
viewSettings: ViewSettingsType;
|
||||
}
|
||||
|
||||
export default function MinimalTimer(props: MinimalTimerProps) {
|
||||
const { pres, time, viewSettings } = props;
|
||||
const { shouldRender } = useRuntimeStylesheet(viewSettings?.overrideStyles && overrideStylesURL);
|
||||
const [searchParams] = useSearchParams();
|
||||
const [isMirrored] = useAtom(mirrorViewersAtom);
|
||||
|
||||
useEffect(() => {
|
||||
document.title = 'ontime - Minimal Timer';
|
||||
}, []);
|
||||
|
||||
// defer rendering until we load stylesheets
|
||||
if (!shouldRender) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// get config from url: key, text, font, size, hidenav, hideovertime
|
||||
// eg. http://localhost:3000/minimal?key=f00&text=fff
|
||||
// Check for user options
|
||||
const userOptions: OverridableOptions = {
|
||||
size: 1,
|
||||
};
|
||||
|
||||
// key: string
|
||||
// Should be a hex string '#00FF00' with key colour
|
||||
const key = searchParams.get('key');
|
||||
if (key) {
|
||||
userOptions.keyColour = `#${key}`;
|
||||
}
|
||||
|
||||
// textColour: string
|
||||
// Should be a hex string '#ffffff'
|
||||
const textColour = searchParams.get('text');
|
||||
if (textColour) {
|
||||
userOptions.textColour = `#${textColour}`;
|
||||
}
|
||||
|
||||
// textBackground: string
|
||||
// Should be a hex string '#ffffff'
|
||||
const textBackground = searchParams.get('textbg');
|
||||
if (textBackground) {
|
||||
userOptions.textBackground = `#${textBackground}`;
|
||||
}
|
||||
|
||||
// font: string
|
||||
// Should be a string with a font name 'arial'
|
||||
const font = searchParams.get('font');
|
||||
if (font) {
|
||||
userOptions.font = font;
|
||||
}
|
||||
|
||||
// size: multiplier
|
||||
// Should be a number 0.0-n
|
||||
const size = searchParams.get('size');
|
||||
if (size !== null && typeof size !== 'undefined') {
|
||||
if (!Number.isNaN(Number(size))) {
|
||||
userOptions.size = Number(size);
|
||||
}
|
||||
}
|
||||
|
||||
// alignX: flex justification
|
||||
// start | center | end
|
||||
const alignX = searchParams.get('alignx');
|
||||
if (alignX) {
|
||||
if (alignX === 'start' || alignX === 'center' || alignX === 'end') {
|
||||
userOptions.justifyContent = alignX;
|
||||
}
|
||||
}
|
||||
|
||||
// alignX: flex alignment
|
||||
// start | center | end
|
||||
const alignY = searchParams.get('aligny');
|
||||
if (alignY) {
|
||||
if (alignY === 'start' || alignY === 'center' || alignY === 'end') {
|
||||
userOptions.alignItems = alignY;
|
||||
}
|
||||
}
|
||||
|
||||
// offsetX: position in pixels
|
||||
// Should be a number 0 - 1920
|
||||
const offsetX = searchParams.get('offsetx');
|
||||
if (offsetX) {
|
||||
const pixels = Number(offsetX);
|
||||
if (!isNaN(pixels)) {
|
||||
userOptions.left = `${pixels}px`;
|
||||
}
|
||||
}
|
||||
|
||||
// offsetX: position in pixels
|
||||
// Should be a number 0 - 1920
|
||||
const offsetY = searchParams.get('offsety');
|
||||
if (offsetY) {
|
||||
const pixels = Number(offsetY);
|
||||
if (!isNaN(pixels)) {
|
||||
userOptions.top = `${pixels}px`;
|
||||
}
|
||||
}
|
||||
|
||||
const hideOvertime = searchParams.get('hideovertime');
|
||||
userOptions.hideOvertime = Boolean(hideOvertime);
|
||||
|
||||
const hideMessagesOverlay = searchParams.get('hidemessages');
|
||||
userOptions.hideMessagesOverlay = Boolean(hideMessagesOverlay);
|
||||
|
||||
const showOverlay = pres.text !== '' && pres.visible;
|
||||
const isPlaying = time.playback !== 'pause';
|
||||
const isNegative = (time.current ?? 0) < 0;
|
||||
const showFinished = isNegative && !userOptions?.hideOvertime;
|
||||
|
||||
const baseClasses = `minimal-timer ${isMirrored ? 'mirror' : ''}`;
|
||||
|
||||
let stageTimer;
|
||||
if (time.current === null) {
|
||||
stageTimer = '- - : - -';
|
||||
} else {
|
||||
stageTimer = formatDisplay(Math.abs(millisToSeconds(time.current)), true);
|
||||
if (time.current < 0) {
|
||||
stageTimer = `-${stageTimer}`;
|
||||
}
|
||||
}
|
||||
const stageTimerCharacters = stageTimer.replace('/:/g', '').length;
|
||||
|
||||
return (
|
||||
<div
|
||||
className={showFinished ? `${baseClasses} minimal-timer--finished` : baseClasses}
|
||||
style={{
|
||||
backgroundColor: userOptions.keyColour,
|
||||
justifyContent: userOptions.justifyContent,
|
||||
alignItems: userOptions.alignItems,
|
||||
}}
|
||||
data-testid='minimal-timer'
|
||||
>
|
||||
<NavigationMenu />
|
||||
{!hideMessagesOverlay && (
|
||||
<div
|
||||
className={showOverlay ? 'message-overlay message-overlay--active' : 'message-overlay'}
|
||||
>
|
||||
<div className='message'>{pres.text}</div>
|
||||
</div>
|
||||
)}
|
||||
<div
|
||||
className={`timer ${!isPlaying ? 'timer--paused' : ''} ${
|
||||
showFinished ? 'timer--finished' : ''
|
||||
}`}
|
||||
style={{
|
||||
color: userOptions.textColour,
|
||||
fontSize: `${(89 / (stageTimerCharacters - 1)) * (userOptions.size || 1)}vw`,
|
||||
fontFamily: userOptions.font,
|
||||
top: userOptions.top,
|
||||
left: userOptions.left,
|
||||
backgroundColor: userOptions.textBackground,
|
||||
}}
|
||||
>
|
||||
{stageTimer}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user