* feat(text-background): add discrete text background to <Minimal>
* refactor: convert <Minimal> to typescript
* feat(clock): create <Clock> view
* feat(clock): version bump
This commit is contained in:
Carlos Valente
2022-09-20 21:19:17 +02:00
committed by GitHub
parent 70436059d8
commit d86afee477
10 changed files with 260 additions and 22 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "ontime-ui",
"version": "1.8.0",
"version": "1.9.0",
"private": true,
"dependencies": {
"@chakra-ui/react": "^2.3.2",
+4
View File
@@ -12,6 +12,7 @@ const Table = lazy(() => import('features/table/ProtectedTable'));
const TimerView = lazy(() => import('features/viewers/timer/Timer'));
const MinimalTimerView = lazy(() => import('features/viewers/minimal-timer/MinimalTimer'));
const ClockView = lazy(() => import('features/viewers/clock/Clock'));
const Countdown = lazy(() => import('features/viewers/countdown/Countdown'));
const Backstage = lazy(() => import('features/viewers/backstage/Backstage'));
@@ -22,6 +23,7 @@ const StudioClock = lazy(() => import('features/viewers/studio/StudioClock'));
const STimer = withSocket(TimerView);
const SMinimalTimer = withSocket(MinimalTimerView);
const SClock = withSocket(ClockView);
const SCountdown = withSocket(Countdown);
const SBackstage = withSocket(Backstage);
const SPublic = withSocket(Public);
@@ -64,6 +66,8 @@ export default function AppRouter() {
<Route path='/minimalTimer' element={<SMinimalTimer />} />
<Route path='/simpleTimer' element={<SMinimalTimer />} />
<Route path='/clock' element={<SClock />} />
<Route path='/countdown' element={<SCountdown />} />
<Route path='/sm' element={<SBackstage />} />
@@ -1,5 +1,6 @@
const navigatorConstants = [
{ url: '/timer', label: 'Timer' },
{ url: '/clock', label: 'Clock' },
{ url: '/minimal', label: 'Minimal Timer' },
{ url: '/backstage', label: 'Backstage' },
{ url: '/public', label: 'Public' },
+20
View File
@@ -0,0 +1,20 @@
type Playstate = 'roll' | 'start' | 'pause' | 'stop';
export type TimeManager = {
clock: number,
running: number,
isNegative: boolean;
startedAt: null | number;
expectedFinish: null | number;
finished: boolean;
playstate: Playstate
}
export type PresenterMessageData = {
text: string;
visible: boolean;
}
export type ViewSettings = {
overrideStyles: boolean;
}
+14
View File
@@ -0,0 +1,14 @@
export type OverridableOptions = {
keyColour?: string;
textColour?: string;
textBackground?: string;
font?: string;
size?: number;
justifyContent?: 'start' | 'center' | 'end';
alignItems?: 'start' | 'center' | 'end';
left?: string;
top?: string;
hideNav?: boolean;
hideOvertime?: boolean;
hideMessagesOverlay?: boolean;
}
@@ -0,0 +1,23 @@
@use '../../../theme/viewerDefs' as *;
.clock-view {
background: var(--background-color-override, $viewer-background-color);
height: 100vh;
color: var(--color-override, $viewer-color);
display: flex;
align-items: center;
justify-content: center;
gap: 1vw;
border: 1vw solid transparent;
.clock {
font-family: "Arial Black", sans-serif;
font-size: 20vw;
position: relative;
color: var(--timer-color-override, $viewer-color);
opacity: 1;
transition: 0.5s;
transition-property: opacity;
background-color: transparent;
}
}
+154
View File
@@ -0,0 +1,154 @@
import { useEffect } from 'react';
import { useSearchParams } from 'react-router-dom';
import { overrideStylesURL } from '../../../common/api/apiConstants';
import NavLogo from '../../../common/components/nav/NavLogo';
import { useRuntimeStylesheet } from '../../../common/hooks/useRuntimeStylesheet';
import {
TimeManager,
ViewSettings,
} from '../../../common/models/OntimeTypes';
import { OverridableOptions } from '../../../common/models/ViewTypes';
import { formatTime } from '../../../common/utils/time';
import './Clock.scss';
interface ClockProps {
time: TimeManager;
viewSettings: ViewSettings;
}
const formatOptions = {
showSeconds: true,
format: 'hh:mm:ss a',
};
export default function Clock(props: ClockProps) {
const { time, viewSettings } = props;
const { shouldRender } = useRuntimeStylesheet(viewSettings?.overrideStyles && overrideStylesURL);
const [searchParams] = useSearchParams();
useEffect(() => {
document.title = 'ontime - Clock';
}, []);
// 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 hideNav = searchParams.get('hidenav');
userOptions.hideNav = Boolean(hideNav);
const clock = formatTime(time.clock, formatOptions);
const clean = clock.replace('/:/g', '');
return (
<div
className='clock-view'
style={{
backgroundColor: userOptions.keyColour,
color: userOptions.textColour,
justifyContent: userOptions.justifyContent,
alignItems: userOptions.alignItems,
}}
data-testid='clock-view'
>
{!userOptions?.hideNav && <NavLogo />}
<div
className='clock'
style={{
fontSize: `${(89 / (clean.length - 1)) * (userOptions.size || 1)}vw`,
fontFamily: userOptions.font,
top: userOptions.top,
left: userOptions.left,
backgroundColor: userOptions.textBackground,
}}
>
{clock}
</div>
</div>
);
}
@@ -23,6 +23,7 @@
opacity: 1;
transition: 0.5s;
transition-property: opacity;
background-color: transparent;
&--paused {
opacity: 0.6;
@@ -1,15 +1,26 @@
import { useEffect } from 'react';
import { useSearchParams } from 'react-router-dom';
import PropTypes from 'prop-types';
import { overrideStylesURL } from '../../../common/api/apiConstants';
import NavLogo from '../../../common/components/nav/NavLogo';
import { useRuntimeStylesheet } from '../../../common/hooks/useRuntimeStylesheet';
import {
PresenterMessageData,
TimeManager,
ViewSettings,
} from '../../../common/models/OntimeTypes';
import { OverridableOptions } from '../../../common/models/ViewTypes';
import { formatDisplay } from '../../../common/utils/dateConfig';
import './MinimalTimer.scss';
export default function MinimalTimer(props) {
interface MinimalTimerProps {
pres: PresenterMessageData;
time: TimeManager;
viewSettings: ViewSettings;
}
export default function MinimalTimer(props: MinimalTimerProps) {
const { pres, time, viewSettings } = props;
const { shouldRender } = useRuntimeStylesheet(viewSettings?.overrideStyles && overrideStylesURL);
const [searchParams] = useSearchParams();
@@ -26,7 +37,9 @@ export default function MinimalTimer(props) {
// 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 = {};
const userOptions: OverridableOptions = {
size: 1,
};
// key: string
// Should be a hex string '#00FF00' with key colour
@@ -42,6 +55,13 @@ export default function MinimalTimer(props) {
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');
@@ -52,8 +72,10 @@ export default function MinimalTimer(props) {
// size: multiplier
// Should be a number 0.0-n
const size = searchParams.get('size');
if (size) {
userOptions.size = size;
if (size !== null && typeof size !== 'undefined') {
if (!Number.isNaN(Number(size))) {
userOptions.size = Number(size);
}
}
// alignX: flex justification
@@ -95,19 +117,13 @@ export default function MinimalTimer(props) {
}
const hideNav = searchParams.get('hidenav');
if (hideNav) {
userOptions.hideNav = hideNav;
}
userOptions.hideNav = Boolean(hideNav);
const hideOvertime = searchParams.get('hideovertime');
if (hideOvertime) {
userOptions.hideOvertime = hideOvertime;
}
userOptions.hideOvertime = Boolean(hideOvertime);
const hideMessagesOverlay = searchParams.get('hidemessages');
if (hideMessagesOverlay) {
userOptions.hideMessagesOverlay = hideMessagesOverlay;
}
userOptions.hideMessagesOverlay = Boolean(hideMessagesOverlay);
const showOverlay = pres.text !== '' && pres.visible;
const isPlaying = time.playstate !== 'pause';
@@ -139,10 +155,11 @@ export default function MinimalTimer(props) {
showFinished ? 'timer--finished' : ''
}`}
style={{
fontSize: `${(89 / (clean.length - 1)) * userOptions.size}vw`,
fontSize: `${(89 / (clean.length - 1)) * (userOptions.size || 1)}vw`,
fontFamily: userOptions.font,
top: userOptions.top,
left: userOptions.left,
backgroundColor: userOptions.textBackground,
}}
>
{time.isNegative ? `-${timer}` : timer}
@@ -150,9 +167,3 @@ export default function MinimalTimer(props) {
</div>
);
}
MinimalTimer.propTypes = {
pres: PropTypes.object,
time: PropTypes.object,
viewSettings: PropTypes.object,
};
@@ -92,6 +92,16 @@ describe('validate routes', () => {
cy.contains('Running Timer');
});
describe('clock view timer', () => {
it('renders base route', () => {
// base route
cy.visit('http://localhost:4001/clock');
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');
});
});
describe('minimal timer and options', () => {
it('renders base route', () => {
// base route