mirror of
https://github.com/cpvalente/ontime.git
synced 2026-08-12 02:43:50 +00:00
refactor: small tweaks and fixes
This commit is contained in:
@@ -91,9 +91,6 @@ export default function TimeInput<T extends string>(props: TimeInputProps<T>) {
|
||||
(event: KeyboardEvent<HTMLInputElement>) => {
|
||||
if (event.key === 'Enter') {
|
||||
inputRef.current?.blur();
|
||||
validateAndSubmit((event.target as HTMLInputElement).value);
|
||||
} else if (event.key === 'Tab') {
|
||||
validateAndSubmit((event.target as HTMLInputElement).value);
|
||||
}
|
||||
if (event.key === 'Escape') {
|
||||
ignoreChange.current = true;
|
||||
@@ -101,7 +98,7 @@ export default function TimeInput<T extends string>(props: TimeInputProps<T>) {
|
||||
resetValue();
|
||||
}
|
||||
},
|
||||
[resetValue, validateAndSubmit],
|
||||
[resetValue],
|
||||
);
|
||||
|
||||
const onBlurHandler = useCallback(
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
.emptyContainer {
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
color: $gray-1350;
|
||||
color: $white-10;
|
||||
|
||||
.empty {
|
||||
width: 100%;
|
||||
|
||||
@@ -5,7 +5,7 @@ import EmptyImage from '../../../assets/images/empty.svg?react';
|
||||
import style from './Empty.module.scss';
|
||||
|
||||
interface EmptyProps {
|
||||
text: string;
|
||||
text?: string;
|
||||
style?: CSSProperties;
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ export default function Empty(props: EmptyProps) {
|
||||
return (
|
||||
<div className={style.emptyContainer} {...rest}>
|
||||
<EmptyImage className={style.empty} />
|
||||
<span className={style.text}>{text}</span>
|
||||
{text && <span className={style.text}>{text}</span>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ describe('test forgivingStringToMillis()', () => {
|
||||
{ value: '1h0m0s', expect: 1000 * 60 * 60 },
|
||||
{ value: '23h0m0s', expect: 1000 * 60 * 60 * 23 },
|
||||
{ value: '12h12m12s', expect: 12 * 1000 + 12 * 60 * 1000 + 12 * 1000 * 60 * 60 },
|
||||
{ value: '12H12M12S', expect: 12 * 1000 + 12 * 60 * 1000 + 12 * 1000 * 60 * 60 },
|
||||
{ value: '2m', expect: 2 * 60 * 1000 },
|
||||
{ value: '1h5s', expect: 1000 * 60 * 60 + 1000 * 5 },
|
||||
{ value: '1h2m', expect: 1000 * 60 * 60 + 1000 * 60 * 2 },
|
||||
|
||||
@@ -46,13 +46,13 @@ function checkAmPm(value: string) {
|
||||
* @param {string} value
|
||||
*/
|
||||
function checkMatchers(value: string) {
|
||||
const hoursMatch = /(\d+)h/.exec(value);
|
||||
const hoursMatch = /(\d+)h/i.exec(value);
|
||||
const hoursMatchValue = hoursMatch ? parse(hoursMatch[1]) : 0;
|
||||
|
||||
const minutesMatch = /(\d+)m/.exec(value);
|
||||
const minutesMatch = /(\d+)m/i.exec(value);
|
||||
const minutesMatchValue = minutesMatch ? parse(minutesMatch[1]) : 0;
|
||||
|
||||
const secondsMatch = /(\d+)s/.exec(value);
|
||||
const secondsMatch = /(\d+)s/i.exec(value);
|
||||
const secondsMatchValue = secondsMatch ? parse(secondsMatch[1]) : 0;
|
||||
|
||||
if (hoursMatchValue > 0 || minutesMatchValue > 0 || secondsMatchValue > 0) {
|
||||
|
||||
@@ -2,4 +2,4 @@ export const githubUrl = 'https://www.github.com/cpvalente/ontime';
|
||||
export const apiRepoLatest = 'https://api.github.com/repos/cpvalente/ontime/releases/latest';
|
||||
export const websiteUrl = 'https://www.getontime.no';
|
||||
|
||||
export const gitbookUrl = 'https://ontime.gitbook.io';
|
||||
export const documentationUrl = 'https://docs.getontime.no';
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { version } from '../../../../../package.json';
|
||||
import ExternalLink from '../../../../common/components/external-link/ExternalLink';
|
||||
import { gitbookUrl, githubUrl, websiteUrl } from '../../../../externals';
|
||||
import { documentationUrl, githubUrl, websiteUrl } from '../../../../externals';
|
||||
import * as Panel from '../PanelUtils';
|
||||
|
||||
import CheckUpdatesButton from './CheckUpdatesButton';
|
||||
@@ -18,7 +18,7 @@ export default function AboutPanel() {
|
||||
</Panel.Section>
|
||||
<Panel.Section>
|
||||
<Panel.SubHeader>Links</Panel.SubHeader>
|
||||
<ExternalLink href={gitbookUrl}>Read the docs over at GitBook</ExternalLink>
|
||||
<ExternalLink href={documentationUrl}>Read the docs</ExternalLink>
|
||||
<ExternalLink href={githubUrl}>Follow the project on GitHub</ExternalLink>
|
||||
</Panel.Section>
|
||||
<Panel.Section>
|
||||
|
||||
@@ -13,7 +13,7 @@ import * as Panel from '../PanelUtils';
|
||||
|
||||
import style from './GeneralPanel.module.scss';
|
||||
|
||||
const cssOverrideDocsUrl = 'https://ontime.gitbook.io/v2/features/custom-styling';
|
||||
const cssOverrideDocsUrl = 'https://docs.getontime.no/features/custom-styling/';
|
||||
|
||||
export default function ViewSettingsForm() {
|
||||
const { data, status, refetch, isFetching } = useViewSettings();
|
||||
|
||||
+1
-1
@@ -6,7 +6,7 @@ import * as Panel from '../PanelUtils';
|
||||
import HttpIntegrations from './HttpIntegrations';
|
||||
import OscIntegrations from './OscIntegrations';
|
||||
|
||||
const integrationDocsUrl = 'https://ontime.gitbook.io/v2/control-and-feedback/integrations';
|
||||
const integrationDocsUrl = 'https://docs.getontime.no/api/integrations/';
|
||||
|
||||
export default function IntegrationsPanel() {
|
||||
return (
|
||||
|
||||
@@ -147,7 +147,7 @@ export default function ProjectCreateForm(props: ProjectCreateFromProps) {
|
||||
<Input
|
||||
variant='ontime-filled'
|
||||
size='sm'
|
||||
placeholder='www.ontime.gitbook.io'
|
||||
placeholder='http://docs.getontime.no'
|
||||
autoComplete='off'
|
||||
{...register('backstageUrl')}
|
||||
/>
|
||||
|
||||
@@ -142,7 +142,7 @@ export default function ProjectData() {
|
||||
<Input
|
||||
variant='ontime-filled'
|
||||
size='sm'
|
||||
placeholder='www.ontime.gitbook.io'
|
||||
placeholder='http://docs.getontime.no'
|
||||
autoComplete='off'
|
||||
{...register('backstageUrl')}
|
||||
/>
|
||||
|
||||
+1
-1
@@ -11,7 +11,7 @@ import * as Panel from '../PanelUtils';
|
||||
import CustomFieldEntry from './CustomFieldEntry';
|
||||
import CustomFieldForm from './CustomFieldForm';
|
||||
|
||||
const customFieldsDocsUrl = 'https://ontime.gitbook.io/v2/features/user-fields';
|
||||
const customFieldsDocsUrl = 'https://docs.getontime.no/features/custom-fields/';
|
||||
|
||||
export default function ProjectSettingsPanel() {
|
||||
const { data, refetch } = useCustomFields();
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Alert, AlertDescription, AlertIcon } from '@chakra-ui/react';
|
||||
|
||||
import ExternalLink from '../../../../common/components/external-link/ExternalLink';
|
||||
const googleSheetDocsUrl = 'https://ontime.gitbook.io/v2/features/google-sheet';
|
||||
const googleSheetDocsUrl = 'https://docs.getontime.no/features/import-spreadsheet-gsheet/';
|
||||
|
||||
export default function GSheetInfo() {
|
||||
return (
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
grid-area: clk;
|
||||
white-space: nowrap;
|
||||
max-width: 18.75rem;
|
||||
|
||||
min-width: 5em;
|
||||
|
||||
font-family: var(--font-family-override, $viewer-font-family);
|
||||
color: var(--timer-color-override, $timer-color);
|
||||
|
||||
@@ -11,7 +11,7 @@ interface TimerDisplayProps {
|
||||
|
||||
/**
|
||||
* Displays time in ms in formatted timetag
|
||||
* Typically used in production views
|
||||
* Used in editor
|
||||
*/
|
||||
export default function TimerDisplay(props: TimerDisplayProps) {
|
||||
const { time } = props;
|
||||
|
||||
@@ -48,8 +48,7 @@ $panel-gap: 0.5rem;
|
||||
}
|
||||
|
||||
.left {
|
||||
flex-grow: 1;
|
||||
flex-shrink: 1;
|
||||
flex: 1 2 auto; /* flex-grow: 1, flex-shrink: 2, flex-basis: auto */
|
||||
min-width: $min-playback-width;
|
||||
max-width: $max-playback-width;
|
||||
display: flex;
|
||||
|
||||
@@ -18,7 +18,7 @@ import OntimeModalFooter from '../OntimeModalFooter';
|
||||
|
||||
import style from './SettingsModal.module.scss';
|
||||
|
||||
const aliasesDocsUrl = 'https://ontime.gitbook.io/v2/features/url-aliases';
|
||||
const aliasesDocsUrl = 'https://docs.getontime.no/features/url-presets/';
|
||||
|
||||
// we wrap the array in an object to be simplify react-hook-form
|
||||
type Aliases = {
|
||||
|
||||
@@ -122,7 +122,7 @@ export default function ProjectDataForm() {
|
||||
{...inputProps}
|
||||
variant='ontime-filled-on-light'
|
||||
size='sm'
|
||||
placeholder='www.ontime.gitbook.io'
|
||||
placeholder='https://docs.getontime.no'
|
||||
isDisabled={disableInputs}
|
||||
{...register('backstageUrl')}
|
||||
/>
|
||||
|
||||
@@ -15,7 +15,7 @@ import style from './Overview.module.scss';
|
||||
* @param time
|
||||
* @returns
|
||||
*/
|
||||
function formattedTime(time: MaybeNumber) {
|
||||
function formatedTime(time: MaybeNumber) {
|
||||
return millisToString(time, { fallback: timerPlaceholder });
|
||||
}
|
||||
|
||||
@@ -27,13 +27,13 @@ export default function Overview() {
|
||||
<ErrorBoundary>
|
||||
<TitlesOverview />
|
||||
<div className={style.column}>
|
||||
<TimeRow label='Planned start' value={formattedTime(plannedStart)} className={style.start} />
|
||||
<TimeRow label='Actual start' value={formattedTime(actualStart)} className={style.start} />
|
||||
<TimeRow label='Planned start' value={formatedTime(plannedStart)} className={style.start} />
|
||||
<TimeRow label='Actual start' value={formatedTime(actualStart)} className={style.start} />
|
||||
</div>
|
||||
<RuntimeOverview />
|
||||
<div className={style.column}>
|
||||
<TimeRow label='Planned end' value={formattedTime(plannedEnd)} className={style.end} />
|
||||
<TimeRow label='Expected end' value={formattedTime(expectedEnd)} className={style.end} />
|
||||
<TimeRow label='Planned end' value={formatedTime(plannedEnd)} className={style.end} />
|
||||
<TimeRow label='Expected end' value={formatedTime(expectedEnd)} className={style.end} />
|
||||
</div>
|
||||
</ErrorBoundary>
|
||||
</div>
|
||||
@@ -51,24 +51,32 @@ function TitlesOverview() {
|
||||
);
|
||||
}
|
||||
|
||||
function getOffsetText(offset: MaybeNumber): string {
|
||||
if (offset === null) {
|
||||
return enDash;
|
||||
}
|
||||
const isAhead = offset <= 0;
|
||||
let offsetText = millisToString(Math.abs(offset), { fallback: enDash });
|
||||
if (offsetText !== enDash) {
|
||||
offsetText = isAhead ? `+${offsetText}` : `${enDash}${offsetText}`;
|
||||
}
|
||||
return offsetText;
|
||||
}
|
||||
|
||||
function RuntimeOverview() {
|
||||
const { clock, numEvents, selectedEventIndex, offset } = useRuntimePlaybackOverview();
|
||||
|
||||
const current = selectedEventIndex !== null ? selectedEventIndex + 1 : enDash;
|
||||
const ofTotal = numEvents || enDash;
|
||||
const progressText = numEvents ? `${current} of ${ofTotal}` : '-';
|
||||
|
||||
const isAhead = offset <= 0;
|
||||
let offsetText = millisToString(Math.abs(offset), { fallback: enDash });
|
||||
if (offsetText !== enDash) {
|
||||
offsetText = isAhead ? `+${offsetText}` : `${enDash}${offsetText}`;
|
||||
}
|
||||
const offsetText = getOffsetText(offset);
|
||||
const offsetClasses = offset === null ? undefined : offset > 0 ? style.behind : style.ahead;
|
||||
|
||||
return (
|
||||
<>
|
||||
<TimeColumn label='Progress' value={progressText} />
|
||||
<TimeColumn label='Offset' value={offsetText} className={isAhead ? style.ahead : style.behind} />
|
||||
<TimeColumn label='Time now' value={formattedTime(clock)} />
|
||||
<TimeColumn label='Offset' value={offsetText} className={offsetClasses} />
|
||||
<TimeColumn label='Time now' value={formatedTime(clock)} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -254,7 +254,7 @@ export default function Rundown({ data }: RundownProps) {
|
||||
data={event}
|
||||
loaded={isLoaded}
|
||||
hasCursor={hasCursor}
|
||||
next={isNext}
|
||||
isNext={isNext}
|
||||
previousEnd={previousEnd}
|
||||
previousEventId={previousEventId}
|
||||
playback={isLoaded ? featureData.playback : undefined}
|
||||
|
||||
@@ -14,7 +14,7 @@ export default function RundownEmpty(props: RundownEmptyProps) {
|
||||
|
||||
return (
|
||||
<div className={style.alignCenter}>
|
||||
<Empty text='No data yet' style={{ marginTop: '7vh' }} />
|
||||
<Empty style={{ marginTop: '7vh' }} />
|
||||
<Button onClick={handleAddNew} variant='ontime-filled' className={style.spaceTop} leftIcon={<IoAdd />}>
|
||||
Create Event
|
||||
</Button>
|
||||
|
||||
@@ -22,7 +22,7 @@ interface RundownEntryProps {
|
||||
loaded: boolean;
|
||||
eventIndex: number;
|
||||
hasCursor: boolean;
|
||||
next: boolean;
|
||||
isNext: boolean;
|
||||
previousEnd: MaybeNumber;
|
||||
previousEventId?: string;
|
||||
playback?: Playback; // we only care about this if this event is playing
|
||||
@@ -30,7 +30,7 @@ interface RundownEntryProps {
|
||||
}
|
||||
|
||||
export default function RundownEntry(props: RundownEntryProps) {
|
||||
const { isPast, data, loaded, hasCursor, next, previousEnd, previousEventId, playback, isRolling, eventIndex } =
|
||||
const { isPast, data, loaded, hasCursor, isNext, previousEnd, previousEventId, playback, isRolling, eventIndex } =
|
||||
props;
|
||||
const { emitError } = useEmitLog();
|
||||
const { addEvent, updateEvent, batchUpdateEvents, deleteEvent, swapEvents } = useEventAction();
|
||||
@@ -138,7 +138,7 @@ export default function RundownEntry(props: RundownEntryProps) {
|
||||
previousEnd={previousEnd}
|
||||
colour={data.colour}
|
||||
isPast={isPast}
|
||||
next={next}
|
||||
isNext={isNext}
|
||||
skip={data.skip}
|
||||
loaded={loaded}
|
||||
hasCursor={hasCursor}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
@use '../editors//EditorMixin' as editor;
|
||||
@use '../editors/EditorMixin' as editor;
|
||||
|
||||
.rundownExport {
|
||||
height: 100%;
|
||||
flex: 1;
|
||||
flex: 1 1 auto; /* flex-grow: 1, flex-shrink: 1, flex-basis: auto */
|
||||
|
||||
&.extracted {
|
||||
.list {
|
||||
@@ -34,8 +34,7 @@
|
||||
padding-left: 0;
|
||||
box-shadow: $box-shadow-right;
|
||||
|
||||
flex-grow: 1;
|
||||
flex-shrink: 1;
|
||||
flex: 1 2 auto; /* flex-grow: 1, flex-shrink: 2, flex-basis: auto */
|
||||
min-width: 38rem;
|
||||
max-width: 45rem;
|
||||
}
|
||||
@@ -49,8 +48,7 @@
|
||||
background-color: $gray-1325;
|
||||
border-radius: 0 8px 8px 0;
|
||||
|
||||
flex-grow: 1;
|
||||
flex-shrink: 1;
|
||||
flex: 1 1 auto; /* flex-grow: 1, flex-shrink: 1, flex-basis: auto */
|
||||
min-width: 30rem;
|
||||
max-width: 45rem;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { Editable, EditableInput, EditablePreview } from '@chakra-ui/react';
|
||||
import { useCallback } from 'react';
|
||||
import { Input } from '@chakra-ui/react';
|
||||
|
||||
import useReactiveTextInput from '../../../common/components/input/text-input/useReactiveTextInput';
|
||||
import { useEventAction } from '../../../common/hooks/useEventAction';
|
||||
import { cx } from '../../../common/utils/styleUtils';
|
||||
|
||||
@@ -15,39 +16,40 @@ interface TitleEditorProps {
|
||||
|
||||
export default function EditableBlockTitle(props: TitleEditorProps) {
|
||||
const { title, eventId, placeholder, className } = props;
|
||||
const [blockTitle, setBlockTitle] = useState<string>(title || '');
|
||||
const { updateEvent } = useEventAction();
|
||||
|
||||
useEffect(() => {
|
||||
setBlockTitle(title);
|
||||
}, [title]);
|
||||
|
||||
const handleTitle = useCallback(
|
||||
const submitCallback = useCallback(
|
||||
(text: string) => {
|
||||
if (text === title) {
|
||||
return;
|
||||
}
|
||||
|
||||
const cleanVal = text.trim();
|
||||
setBlockTitle(cleanVal);
|
||||
|
||||
updateEvent({ id: eventId, title: cleanVal });
|
||||
},
|
||||
[title, updateEvent, eventId],
|
||||
);
|
||||
|
||||
const classes = cx([className, style.eventTitle, !blockTitle ? style.noTitle : null]);
|
||||
const { value, onChange, onBlur, onKeyDown } = useReactiveTextInput(title, submitCallback, {
|
||||
submitOnEnter: true,
|
||||
});
|
||||
|
||||
const classes = cx([className, style.eventTitle, !value ? style.noTitle : null]);
|
||||
|
||||
return (
|
||||
<Editable
|
||||
variant='ontime'
|
||||
value={blockTitle}
|
||||
<Input
|
||||
data-testid='block__title'
|
||||
variant='ontime-ghosted'
|
||||
value={value}
|
||||
className={classes}
|
||||
placeholder={placeholder}
|
||||
onChange={(value) => setBlockTitle(value)}
|
||||
onSubmit={(value) => handleTitle(value)}
|
||||
>
|
||||
<EditablePreview className={style.preview} />
|
||||
<EditableInput />
|
||||
</Editable>
|
||||
onChange={onChange}
|
||||
onBlur={onBlur}
|
||||
onKeyDown={onKeyDown}
|
||||
autoComplete='off'
|
||||
fontWeight='600'
|
||||
letterSpacing='0.25px'
|
||||
paddingLeft='0'
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ $skip-opacity: 0.1;
|
||||
grid-template-areas:
|
||||
'binder ... ... ...'
|
||||
'binder pb-actions times actions'
|
||||
'binder pb-actions title next'
|
||||
'binder pb-actions title title'
|
||||
'binder pb-actions estatus estatus'
|
||||
'binder ... ... ...';
|
||||
|
||||
@@ -127,15 +127,30 @@ $skip-opacity: 0.1;
|
||||
.eventTimers {
|
||||
grid-area: times;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: $block-clearance;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.eventTitle {
|
||||
.titleSection {
|
||||
grid-area: title;
|
||||
overflow: hidden;
|
||||
max-height: calc(2.5em + 2px);
|
||||
line-height: 1.25em;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
.nextTag {
|
||||
font-size: 1rem;
|
||||
color: $orange-500;
|
||||
letter-spacing: 0.03px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.eventTitle {
|
||||
overflow: hidden;
|
||||
max-height: calc(2.5em + 2px);
|
||||
line-height: 1.25em;
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.eventActions {
|
||||
@@ -146,10 +161,11 @@ $skip-opacity: 0.1;
|
||||
|
||||
.progressBg {
|
||||
grid-area: progb;
|
||||
border-radius: 2px;
|
||||
border-radius: 1px;
|
||||
background-color: $gray-1100;
|
||||
opacity: 1;
|
||||
height: 100%;
|
||||
overflow: hidden; /* clip foreground border radius*/
|
||||
}
|
||||
|
||||
.progressBg.hidden {
|
||||
@@ -184,15 +200,6 @@ $skip-opacity: 0.1;
|
||||
overflow-y: hidden;
|
||||
}
|
||||
|
||||
.nextTag {
|
||||
grid-area: next;
|
||||
font-size: 1rem;
|
||||
color: $orange-500;
|
||||
letter-spacing: 0.03px;
|
||||
font-weight: 600;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.eventStatus {
|
||||
grid-area: status;
|
||||
display: flex;
|
||||
|
||||
@@ -40,7 +40,7 @@ interface EventBlockProps {
|
||||
previousEnd: MaybeNumber;
|
||||
colour: string;
|
||||
isPast: boolean;
|
||||
next: boolean;
|
||||
isNext: boolean;
|
||||
skip: boolean;
|
||||
loaded: boolean;
|
||||
hasCursor: boolean;
|
||||
@@ -76,7 +76,7 @@ export default function EventBlock(props: EventBlockProps) {
|
||||
previousEnd,
|
||||
colour,
|
||||
isPast,
|
||||
next,
|
||||
isNext,
|
||||
skip = false,
|
||||
loaded,
|
||||
hasCursor,
|
||||
@@ -240,7 +240,7 @@ export default function EventBlock(props: EventBlockProps) {
|
||||
className={blockClasses}
|
||||
ref={setNodeRef}
|
||||
style={dragStyle}
|
||||
onMouseDown={handleFocusClick}
|
||||
onClick={handleFocusClick}
|
||||
onContextMenu={onContextMenu}
|
||||
id='event-block'
|
||||
>
|
||||
@@ -268,7 +268,7 @@ export default function EventBlock(props: EventBlockProps) {
|
||||
title={title}
|
||||
note={note}
|
||||
delay={delay}
|
||||
next={next}
|
||||
isNext={isNext}
|
||||
skip={skip}
|
||||
loaded={loaded}
|
||||
playback={playback}
|
||||
|
||||
@@ -41,7 +41,7 @@ interface EventBlockInnerProps {
|
||||
title: string;
|
||||
note: string;
|
||||
delay: number;
|
||||
next: boolean;
|
||||
isNext: boolean;
|
||||
skip: boolean;
|
||||
loaded: boolean;
|
||||
playback?: Playback;
|
||||
@@ -63,7 +63,7 @@ const EventBlockInner = (props: EventBlockInnerProps) => {
|
||||
title,
|
||||
note,
|
||||
delay,
|
||||
next,
|
||||
isNext,
|
||||
skip = false,
|
||||
loaded,
|
||||
playback,
|
||||
@@ -100,12 +100,14 @@ const EventBlockInner = (props: EventBlockInnerProps) => {
|
||||
linkStart={linkStart}
|
||||
/>
|
||||
</div>
|
||||
<EditableBlockTitle title={title} eventId={eventId} placeholder='Event title' className={style.eventTitle} />
|
||||
{next && (
|
||||
<Tooltip label='Next event' {...tooltipProps}>
|
||||
<span className={style.nextTag}>UP NEXT</span>
|
||||
</Tooltip>
|
||||
)}
|
||||
<div className={style.titleSection}>
|
||||
<EditableBlockTitle title={title} eventId={eventId} placeholder='Event title' className={style.eventTitle} />
|
||||
{isNext && (
|
||||
<Tooltip label='Next event' {...tooltipProps}>
|
||||
<span className={style.nextTag}>UP NEXT</span>
|
||||
</Tooltip>
|
||||
)}
|
||||
</div>
|
||||
<EventBlockPlayback
|
||||
eventId={eventId}
|
||||
skip={skip}
|
||||
@@ -117,7 +119,7 @@ const EventBlockInner = (props: EventBlockInnerProps) => {
|
||||
<div className={style.statusElements} id='block-status' data-ispublic={isPublic}>
|
||||
<span className={style.eventNote}>{note}</span>
|
||||
<div className={loaded ? style.progressBg : `${style.progressBg} ${style.hidden}`}>
|
||||
{loaded && <EventBlockProgressBar playback={playback} />}
|
||||
{loaded && <EventBlockProgressBar />}
|
||||
</div>
|
||||
<div className={style.eventStatus} tabIndex={-1}>
|
||||
<Tooltip label={`Time type: ${timerType}`} {...tooltipProps}>
|
||||
|
||||
@@ -10,6 +10,7 @@ $gap-left: calc(2rem + 0.25rem + 3rem);
|
||||
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
@mixin indicator($bg-colour) {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { memo } from 'react';
|
||||
import { memo, MouseEvent } from 'react';
|
||||
import { IoPause } from '@react-icons/all-files/io5/IoPause';
|
||||
import { IoPlay } from '@react-icons/all-files/io5/IoPlay';
|
||||
import { IoReload } from '@react-icons/all-files/io5/IoReload';
|
||||
@@ -40,11 +40,13 @@ const EventBlockPlayback = (props: EventBlockPlaybackProps) => {
|
||||
const { eventId, skip, isPlaying, isPaused, loaded, disablePlayback } = props;
|
||||
const { updateEvent } = useEventAction();
|
||||
|
||||
const toggleSkip = () => {
|
||||
const toggleSkip = (event: MouseEvent) => {
|
||||
event.stopPropagation();
|
||||
updateEvent({ id: eventId, skip: !skip });
|
||||
};
|
||||
|
||||
const actionHandler = () => {
|
||||
const actionHandler = (event: MouseEvent) => {
|
||||
event.stopPropagation();
|
||||
// is playing -> pause
|
||||
// is paused -> continue
|
||||
// otherwise -> start
|
||||
@@ -57,6 +59,11 @@ const EventBlockPlayback = (props: EventBlockPlaybackProps) => {
|
||||
}
|
||||
};
|
||||
|
||||
const load = (event: MouseEvent) => {
|
||||
event.stopPropagation();
|
||||
setEventPlayback.loadEvent(eventId);
|
||||
};
|
||||
|
||||
const buttonVariant: Partial<StyleVariant> = {};
|
||||
|
||||
if (isPaused) {
|
||||
@@ -103,7 +110,7 @@ const EventBlockPlayback = (props: EventBlockPlaybackProps) => {
|
||||
isDisabled={disablePlayback}
|
||||
{...tooltipProps}
|
||||
{...blockBtnStyle}
|
||||
clickHandler={() => setEventPlayback.loadEvent(eventId)}
|
||||
clickHandler={load}
|
||||
tabIndex={-1}
|
||||
/>
|
||||
<TooltipActionBtn
|
||||
|
||||
+1
-16
@@ -5,20 +5,5 @@
|
||||
|
||||
transition: 1s linear;
|
||||
transition-property: width;
|
||||
|
||||
&.play {
|
||||
background-color: $playback-start;
|
||||
}
|
||||
|
||||
&.overtime {
|
||||
background-color: $playback-negative;
|
||||
}
|
||||
|
||||
&.pause {
|
||||
background-color: $ontime-paused;
|
||||
}
|
||||
|
||||
&.roll {
|
||||
background-color: $ontime-roll;
|
||||
}
|
||||
background-color: $gray-200;
|
||||
}
|
||||
|
||||
@@ -1,14 +1,10 @@
|
||||
import { MaybeNumber, Playback } from 'ontime-types';
|
||||
import { MaybeNumber } from 'ontime-types';
|
||||
|
||||
import { useTimer } from '../../../../common/hooks/useSocket';
|
||||
import { clamp } from '../../../../common/utils/math';
|
||||
|
||||
import style from './EventBlockProgressBar.module.scss';
|
||||
|
||||
interface EventBlockProgressBarProps {
|
||||
playback?: Playback;
|
||||
}
|
||||
|
||||
export function getPercentComplete(remaining: MaybeNumber, total: MaybeNumber): number {
|
||||
if (remaining === null || total === null) {
|
||||
return 0;
|
||||
@@ -25,10 +21,9 @@ export function getPercentComplete(remaining: MaybeNumber, total: MaybeNumber):
|
||||
return clamp(100 - (remaining * 100) / total, 0, 100);
|
||||
}
|
||||
|
||||
export default function EventBlockProgressBar(props: EventBlockProgressBarProps) {
|
||||
const { playback } = props;
|
||||
export default function EventBlockProgressBar() {
|
||||
const timer = useTimer();
|
||||
|
||||
const progress = `${getPercentComplete(timer.current, timer.duration)}%`;
|
||||
return <div className={`${style.progressBar} ${playback ? style[playback] : ''}`} style={{ width: progress }} />;
|
||||
return <div className={style.progressBar} style={{ width: progress }} />;
|
||||
}
|
||||
|
||||
@@ -63,6 +63,7 @@
|
||||
font-size: calc(1rem - 3px);
|
||||
color: $label-gray;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
max-width: max-content;
|
||||
cursor: pointer;
|
||||
|
||||
@@ -135,7 +135,7 @@ const EventEditorTimes = (props: EventEditorTimesProps) => {
|
||||
<div>
|
||||
<span className={style.inputLabel}>Event Visibility</span>
|
||||
<label className={style.switchLabel}>
|
||||
<Switch size='sm' isChecked={isPublic} onChange={() => handleSubmit('isPublic', isPublic)} variant='ontime' />
|
||||
<Switch size='md' isChecked={isPublic} onChange={() => handleSubmit('isPublic', isPublic)} variant='ontime' />
|
||||
{isPublic ? 'Public' : 'Private'}
|
||||
</label>
|
||||
</div>
|
||||
|
||||
@@ -22,15 +22,16 @@ export default function RundownHeader() {
|
||||
<div className={style.header}>
|
||||
<ButtonGroup isAttached>
|
||||
<TooltipActionBtn
|
||||
variant={appMode === AppMode.Freeze ? 'ontime-filled' : 'ontime-ghosted'}
|
||||
variant={appMode === AppMode.Freeze ? 'ontime-filled' : 'ontime-outlined'}
|
||||
size='sm'
|
||||
icon={<IoSnowOutline />}
|
||||
clickHandler={setFreezeMode}
|
||||
tooltip='Freeze rundown'
|
||||
aria-label='Freeze rundown'
|
||||
isDisabled
|
||||
/>
|
||||
<TooltipActionBtn
|
||||
variant={appMode === AppMode.Run ? 'ontime-filled' : 'ontime-ghosted'}
|
||||
variant={appMode === AppMode.Run ? 'ontime-filled' : 'ontime-outlined'}
|
||||
size='sm'
|
||||
icon={<IoPlay />}
|
||||
clickHandler={setRunMode}
|
||||
@@ -38,7 +39,7 @@ export default function RundownHeader() {
|
||||
aria-label='Run mode'
|
||||
/>
|
||||
<TooltipActionBtn
|
||||
variant={appMode === AppMode.Edit ? 'ontime-filled' : 'ontime-ghosted'}
|
||||
variant={appMode === AppMode.Edit ? 'ontime-filled' : 'ontime-outlined'}
|
||||
size='sm'
|
||||
icon={<IoOptions />}
|
||||
clickHandler={setEditMode}
|
||||
@@ -47,7 +48,7 @@ export default function RundownHeader() {
|
||||
/>
|
||||
</ButtonGroup>
|
||||
<RundownMenu>
|
||||
<MenuButton size='sm' as={Button} rightIcon={<IoAdd />} aria-label='Rundown menu' variant='ontime-ghosted'>
|
||||
<MenuButton size='sm' as={Button} rightIcon={<IoAdd />} aria-label='Rundown menu' variant='ontime-outlined'>
|
||||
Rundown
|
||||
</MenuButton>
|
||||
</RundownMenu>
|
||||
|
||||
@@ -26,4 +26,5 @@
|
||||
color: $blue-500;
|
||||
margin-right: 0.5rem;
|
||||
font-size: 1.5em;
|
||||
display: grid;
|
||||
}
|
||||
|
||||
@@ -30,6 +30,19 @@
|
||||
transition: $viewer-transition-time;
|
||||
}
|
||||
|
||||
.blackout {
|
||||
position: absolute;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
background-color: #000;
|
||||
z-index: 2;
|
||||
opacity: 0;
|
||||
transition: opacity $viewer-transition-time;
|
||||
&--active {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* =================== CLOCK ===================*/
|
||||
|
||||
.clock-container {
|
||||
|
||||
@@ -145,7 +145,7 @@ export default function Timer(props: TimerProps) {
|
||||
}
|
||||
const stageTimerCharacters = display.replace('/:/g', '').length;
|
||||
|
||||
const baseClasses = `stage-timer ${isMirrored ? 'mirror' : ''} ${showBlackout ? 'blackout' : ''}`;
|
||||
const baseClasses = `stage-timer ${isMirrored ? 'mirror' : ''}`;
|
||||
let timerFontSize = 89 / (stageTimerCharacters - 1);
|
||||
// we need to shrink the timer if the external is going to be there
|
||||
if (showExternal) {
|
||||
@@ -162,6 +162,7 @@ export default function Timer(props: TimerProps) {
|
||||
<div className={showFinished ? `${baseClasses} stage-timer--finished` : baseClasses} data-testid='timer-view'>
|
||||
<NavigationMenu />
|
||||
<ViewParamsEditor paramFields={timerOptions} />
|
||||
<div className={showBlackout ? 'blackout blackout--active' : 'blackout'} />
|
||||
{!userOptions.hideMessage && (
|
||||
<div className={showOverlay ? 'message-overlay message-overlay--active' : 'message-overlay'}>
|
||||
<div className={`message ${showBlinking ? 'blink' : ''}`}>{pres.text}</div>
|
||||
|
||||
@@ -33,8 +33,19 @@ html,
|
||||
background-color: $bg-container-l1;
|
||||
}
|
||||
|
||||
// workaround for chakra
|
||||
// // https://github.com/chakra-ui/chakra-ui/issues/417
|
||||
/* smaller root size for MacOS laptops */
|
||||
@media (max-width: 1600px) {
|
||||
body,
|
||||
html,
|
||||
.App {
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* workaround for chakra
|
||||
* https://github.com/chakra-ui/chakra-ui/issues/417
|
||||
*/
|
||||
option {
|
||||
color: initial;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,3 @@
|
||||
.mirror {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
.blackout {
|
||||
opacity: 0;
|
||||
}
|
||||
@@ -3,6 +3,7 @@ const commonStyles = {
|
||||
backgroundColor: '#262626', // $gray-1200
|
||||
color: '#e2e2e2', // $gray-200
|
||||
border: '1px solid transparent',
|
||||
borderRadius: '3px',
|
||||
_hover: {
|
||||
backgroundColor: '#2d2d2d', // $gray-1100
|
||||
},
|
||||
@@ -25,6 +26,18 @@ export const ontimeInputFilled = {
|
||||
},
|
||||
};
|
||||
|
||||
export const ontimeInputGhosted = {
|
||||
field: {
|
||||
...commonStyles,
|
||||
backgroundColor: 'transparent',
|
||||
color: '#f6f6f6', // $gray-50
|
||||
_hover: {
|
||||
backgroundColor: 'transparent', // $gray-1100
|
||||
border: '1px solid #2B5ABC', // $blue-500
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const ontimeInputFilledOnLight = {
|
||||
field: {
|
||||
backgroundColor: 'white',
|
||||
|
||||
@@ -23,6 +23,7 @@ import { ontimeTab } from './ontimeTab';
|
||||
import {
|
||||
ontimeInputFilled,
|
||||
ontimeInputFilledOnLight,
|
||||
ontimeInputGhosted,
|
||||
ontimeTextAreaFilled,
|
||||
ontimeTextAreaFilledOnLight,
|
||||
ontimeTextAreaTransparent,
|
||||
@@ -71,6 +72,7 @@ const theme = extendTheme({
|
||||
},
|
||||
variants: {
|
||||
'ontime-filled': { ...ontimeInputFilled },
|
||||
'ontime-ghosted': { ...ontimeInputGhosted },
|
||||
'ontime-filled-on-light': { ...ontimeInputFilledOnLight },
|
||||
},
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user