mirror of
https://github.com/cpvalente/ontime.git
synced 2026-08-05 07:28:01 +00:00
refactor: style cleanups and migrations
fix: close modal with button refactor: small type improvements refactor: migrate time inputs refactor: migrate tooltips refactor: prevent component resizing
This commit is contained in:
committed by
Carlos Valente
parent
6facd6a666
commit
17a7d035bf
+15
-12
@@ -1,4 +1,5 @@
|
||||
import { BrowserRouter } from 'react-router-dom';
|
||||
import { Tooltip } from '@base-ui-components/react/tooltip';
|
||||
import { ChakraProvider } from '@chakra-ui/react';
|
||||
import { QueryClientProvider } from '@tanstack/react-query';
|
||||
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
|
||||
@@ -20,20 +21,22 @@ function App() {
|
||||
<ChakraProvider disableGlobalStyle resetCSS theme={theme}>
|
||||
<QueryClientProvider client={ontimeQueryClient}>
|
||||
<AppContextProvider>
|
||||
<BrowserRouter basename={baseURI}>
|
||||
<div className='App'>
|
||||
<Tooltip.Provider>
|
||||
<BrowserRouter basename={baseURI}>
|
||||
<div className='App'>
|
||||
<ErrorBoundary>
|
||||
<TranslationProvider>
|
||||
<IdentifyOverlay />
|
||||
<AppRouter />
|
||||
</TranslationProvider>
|
||||
</ErrorBoundary>
|
||||
<ReactQueryDevtools initialIsOpen={false} />
|
||||
</div>
|
||||
<ErrorBoundary>
|
||||
<TranslationProvider>
|
||||
<IdentifyOverlay />
|
||||
<AppRouter />
|
||||
</TranslationProvider>
|
||||
<div id='identify-portal' />
|
||||
</ErrorBoundary>
|
||||
<ReactQueryDevtools initialIsOpen={false} />
|
||||
</div>
|
||||
<ErrorBoundary>
|
||||
<div id='identify-portal' />
|
||||
</ErrorBoundary>
|
||||
</BrowserRouter>
|
||||
</BrowserRouter>
|
||||
</Tooltip.Provider>
|
||||
</AppContextProvider>
|
||||
</QueryClientProvider>
|
||||
</ChakraProvider>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ButtonHTMLAttributes } from 'react';
|
||||
import { ButtonHTMLAttributes, forwardRef } from 'react';
|
||||
|
||||
import { cx } from '../../utils/styleUtils';
|
||||
|
||||
@@ -17,20 +17,21 @@ interface IconButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
|
||||
size?: 'small' | 'medium' | 'large' | 'xlarge';
|
||||
}
|
||||
|
||||
export default function IconButton({
|
||||
className,
|
||||
children,
|
||||
variant = 'subtle',
|
||||
size = 'medium',
|
||||
...buttonProps
|
||||
}: IconButtonProps) {
|
||||
return (
|
||||
<button
|
||||
className={cx([style.baseIconButton, style[variant], style[size], className])}
|
||||
type='button'
|
||||
{...buttonProps}
|
||||
>
|
||||
{children}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
const IconButton = forwardRef<HTMLButtonElement, IconButtonProps>(
|
||||
({ className, children, variant = 'subtle', size = 'medium', ...buttonProps }, ref) => {
|
||||
return (
|
||||
<button
|
||||
ref={ref}
|
||||
className={cx([style.baseIconButton, style[variant], style[size], className])}
|
||||
type='button'
|
||||
{...buttonProps}
|
||||
>
|
||||
{children}
|
||||
</button>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
IconButton.displayName = 'IconButton';
|
||||
|
||||
export default IconButton;
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
import { MouseEvent } from 'react';
|
||||
import { IconButton, IconButtonProps, Tooltip } from '@chakra-ui/react';
|
||||
|
||||
interface TooltipActionBtnProps extends IconButtonProps {
|
||||
clickHandler: (event: MouseEvent) => void | Promise<void>;
|
||||
tooltip: string;
|
||||
openDelay?: number;
|
||||
}
|
||||
|
||||
export default function TooltipActionBtn(props: TooltipActionBtnProps) {
|
||||
const { clickHandler, icon, size = 'xs', tooltip, openDelay = 0, className, ...rest } = props;
|
||||
return (
|
||||
<Tooltip label={tooltip} openDelay={openDelay}>
|
||||
<IconButton {...rest} size={size} icon={icon} onClick={clickHandler} className={className} />
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
@@ -1,8 +1,7 @@
|
||||
import { IoChevronDown, IoChevronUp } from 'react-icons/io5';
|
||||
import { Tooltip } from '@chakra-ui/react';
|
||||
|
||||
import { tooltipDelayFast } from '../../../ontimeConfig';
|
||||
import { millisToDelayString } from '../../utils/dateConfig';
|
||||
import Tooltip from '../tooltip/Tooltip';
|
||||
|
||||
import style from './DelayIndicator.module.scss';
|
||||
|
||||
@@ -23,8 +22,8 @@ export default function DelayIndicator(props: DelayIndicatorProps) {
|
||||
: millisToDelayString(delayValue);
|
||||
|
||||
return (
|
||||
<Tooltip openDelay={tooltipDelayFast} label={delayString}>
|
||||
<span className={style.delaySymbol}>{delayValue < 0 ? <IoChevronDown /> : <IoChevronUp />}</span>
|
||||
<Tooltip text={delayString} render={<span />} className={style.delaySymbol}>
|
||||
{delayValue < 0 ? <IoChevronDown /> : <IoChevronUp />}
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -18,8 +18,20 @@ interface NullableTimeInputProps<T extends string> {
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export default function NullableTimeInput<T extends string>(props: NullableTimeInputProps<T>) {
|
||||
const { id, name, submitHandler, time, emptyDisplay, placeholder, disabled, align = 'center', className } = props;
|
||||
/**
|
||||
* Similar to TimeInput, but allows clearing the time value
|
||||
*/
|
||||
export default function NullableTimeInput<T extends string>({
|
||||
id,
|
||||
name,
|
||||
submitHandler,
|
||||
time,
|
||||
emptyDisplay,
|
||||
placeholder,
|
||||
disabled,
|
||||
align = 'center',
|
||||
className,
|
||||
}: NullableTimeInputProps<T>) {
|
||||
const inputRef = useRef<HTMLInputElement | null>(null);
|
||||
const [value, setValue] = useState<string>('');
|
||||
const ignoreChange = useRef(false);
|
||||
|
||||
@@ -3,4 +3,8 @@
|
||||
max-width: 7.5em;
|
||||
letter-spacing: 0.5px;
|
||||
font-variant-numeric: tabular-nums;
|
||||
|
||||
&.delayed {
|
||||
border: 1px solid $ontime-delay-text;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,11 +14,21 @@ interface TimeInputProps<T extends string> {
|
||||
placeholder?: string;
|
||||
disabled?: boolean;
|
||||
align?: 'left' | 'center';
|
||||
delayed?: boolean;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export default function TimeInput<T extends string>(props: TimeInputProps<T>) {
|
||||
const { id, name, submitHandler, time, placeholder, disabled, align = 'center', className } = props;
|
||||
export default function TimeInput<T extends string>({
|
||||
id,
|
||||
name,
|
||||
submitHandler,
|
||||
time,
|
||||
placeholder,
|
||||
disabled,
|
||||
align = 'center',
|
||||
delayed,
|
||||
className,
|
||||
}: TimeInputProps<T>) {
|
||||
const inputRef = useRef<HTMLInputElement | null>(null);
|
||||
const [value, setValue] = useState<string>('');
|
||||
const ignoreChange = useRef(false);
|
||||
@@ -122,7 +132,7 @@ export default function TimeInput<T extends string>(props: TimeInputProps<T>) {
|
||||
disabled={disabled}
|
||||
ref={inputRef}
|
||||
data-testid={`time-input-${name}`}
|
||||
className={cx([style.timeInput, className])}
|
||||
className={cx([style.timeInput, delayed && style.delayed, className])}
|
||||
placeholder={placeholder}
|
||||
onFocus={handleFocus}
|
||||
onChange={(event) => setValue(event.target.value)}
|
||||
@@ -130,7 +140,6 @@ export default function TimeInput<T extends string>(props: TimeInputProps<T>) {
|
||||
onKeyDown={onKeyDownHandler}
|
||||
value={value}
|
||||
maxLength={8}
|
||||
autoComplete='off'
|
||||
style={{
|
||||
textAlign: align,
|
||||
}}
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
$input-delayed-border-color: $ontime-delay-text;
|
||||
|
||||
.timeInput {
|
||||
border: 1px solid transparent;
|
||||
color: $label-gray;
|
||||
|
||||
&.delayed {
|
||||
border: 1px solid $input-delayed-border-color;
|
||||
}
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
import { PropsWithChildren } from 'react';
|
||||
import { InputGroup } from '@chakra-ui/react';
|
||||
|
||||
import { cx } from '../../../utils/styleUtils';
|
||||
|
||||
import TimeInput from './TimeInput';
|
||||
|
||||
import style from './TimeInputWithButton.module.scss';
|
||||
|
||||
interface TimeInputWithButtonProps<T extends string> {
|
||||
name: T;
|
||||
submitHandler: (field: T, value: string) => void;
|
||||
time?: number;
|
||||
hasDelay?: boolean;
|
||||
disabled?: boolean;
|
||||
placeholder: string;
|
||||
}
|
||||
|
||||
export default function TimeInputWithButton<T extends string>(props: PropsWithChildren<TimeInputWithButtonProps<T>>) {
|
||||
const { name, submitHandler, time, hasDelay, placeholder, disabled, children } = props;
|
||||
|
||||
const inputClasses = cx([style.timeInput, hasDelay ? style.delayed : null]);
|
||||
|
||||
return (
|
||||
<InputGroup size='sm' className={inputClasses} width='fit-content'>
|
||||
<TimeInput<T>
|
||||
name={name}
|
||||
submitHandler={submitHandler}
|
||||
time={time}
|
||||
placeholder={placeholder}
|
||||
align='left'
|
||||
disabled={disabled}
|
||||
/>
|
||||
{children}
|
||||
</InputGroup>
|
||||
);
|
||||
}
|
||||
@@ -15,4 +15,9 @@
|
||||
&:hover {
|
||||
color: $ontime-color;
|
||||
}
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
box-shadow: 0 1px 0 0 currentColor;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
border-radius: 3px;
|
||||
box-shadow: $box-shadow-l1;
|
||||
border: 1px solid $gray-1100;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.backdrop {
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
.tooltip {
|
||||
font-size: calc(1rem - 3px);
|
||||
background-color: $ui-white;
|
||||
color: $ui-black;
|
||||
padding: 0.125rem 0.5rem;
|
||||
border-radius: 2px;
|
||||
line-height: 1.25em;
|
||||
max-width: 200px;
|
||||
|
||||
transform-origin: var(--transform-origin);
|
||||
transition:
|
||||
transform 150ms,
|
||||
opacity 150ms;
|
||||
|
||||
&[data-starting-style],
|
||||
&[data-ending-style] {
|
||||
opacity: 0;
|
||||
transform: scale(0.9);
|
||||
}
|
||||
|
||||
&[data-instant] {
|
||||
transition-duration: 0ms;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
import { PropsWithChildren } from 'react';
|
||||
import { Tooltip as BaseTooltip } from '@base-ui-components/react/tooltip';
|
||||
|
||||
import style from './Tooltip.module.scss';
|
||||
|
||||
interface TooltipProps extends BaseTooltip.Trigger.Props {
|
||||
text: string;
|
||||
}
|
||||
|
||||
export default function Tooltip({ text, children, ...triggerProps }: PropsWithChildren<TooltipProps>) {
|
||||
return (
|
||||
<BaseTooltip.Root>
|
||||
<BaseTooltip.Trigger {...triggerProps}>{children}</BaseTooltip.Trigger>
|
||||
<BaseTooltip.Portal>
|
||||
<BaseTooltip.Positioner side='bottom' sideOffset={4}>
|
||||
<BaseTooltip.Popup className={style.tooltip}>
|
||||
<BaseTooltip.Arrow />
|
||||
{text}
|
||||
</BaseTooltip.Popup>
|
||||
</BaseTooltip.Positioner>
|
||||
</BaseTooltip.Portal>
|
||||
</BaseTooltip.Root>
|
||||
);
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
.corner {
|
||||
position: absolute;
|
||||
top: 1rem;
|
||||
right: 2rem;
|
||||
position: fixed;
|
||||
top: 6rem;
|
||||
right: 4rem;
|
||||
z-index: $zindex-floating;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import { Fragment } from 'react';
|
||||
import { Tooltip } from '@chakra-ui/react';
|
||||
|
||||
import Tooltip from '../../../common/components/tooltip/Tooltip';
|
||||
import { isKeyEnter } from '../../../common/utils/keyEvent';
|
||||
import { cx } from '../../../common/utils/styleUtils';
|
||||
import { tooltipDelayFast } from '../../../ontimeConfig';
|
||||
import { SettingsOption, SettingsOptionId, useAppSettingsMenu } from '../useAppSettingsMenu';
|
||||
import useAppSettingsNavigation from '../useAppSettingsNavigation';
|
||||
|
||||
@@ -26,7 +25,7 @@ export default function PanelList({ selectedPanel, location }: PanelListProps) {
|
||||
const isSelected = selectedPanel === panel.id;
|
||||
if (panel.highlight) {
|
||||
return (
|
||||
<Tooltip key={panel.id} label={panel.highlight} openDelay={tooltipDelayFast} shouldWrapChildren>
|
||||
<Tooltip key={panel.id} text={panel.highlight} render={<span />}>
|
||||
<PanelListItem panel={panel} location={location} isSelected={isSelected} />
|
||||
</Tooltip>
|
||||
);
|
||||
|
||||
@@ -23,13 +23,13 @@ export default function FeaturePanel({ location }: PanelBaseProps) {
|
||||
<Panel.Section>
|
||||
<Panel.Card>
|
||||
<Panel.SubHeader>Share Ontime Link</Panel.SubHeader>
|
||||
<Panel.Divider />
|
||||
{!isOntimeCloud && (
|
||||
<>
|
||||
<Panel.Paragraph>Ontime is streaming on the following network interfaces</Panel.Paragraph>
|
||||
<InfoNif />
|
||||
</>
|
||||
)}
|
||||
<Panel.Divider />
|
||||
<GenerateLinkFormExport />
|
||||
</Panel.Card>
|
||||
</Panel.Section>
|
||||
|
||||
@@ -73,18 +73,12 @@ export default function GenerateLinkForm({ hostOptions, pathOptions, isLockedToV
|
||||
};
|
||||
|
||||
return (
|
||||
<Panel.Section as='form' onSubmit={handleSubmit(onSubmit)} onKeyDown={(event) => preventEscape(event)}>
|
||||
<form onSubmit={handleSubmit(onSubmit)} onKeyDown={(event) => preventEscape(event)}>
|
||||
{errors.root && <Panel.Error>{errors.root.message}</Panel.Error>}
|
||||
{!isLockedToView ? (
|
||||
<Info>
|
||||
<Panel.Paragraph>
|
||||
You can generate a link to share with your team or to use in automation (such as companion).
|
||||
</Panel.Paragraph>
|
||||
</Info>
|
||||
<Info>You can generate a link to share with your team or to use in automation (such as companion).</Info>
|
||||
) : (
|
||||
<Info>
|
||||
<Panel.Paragraph>You can generate a link to share with your team</Panel.Paragraph>
|
||||
</Info>
|
||||
<Info>You can generate a link to share with your team</Info>
|
||||
)}
|
||||
<Panel.ListGroup>
|
||||
<Panel.ListItem>
|
||||
@@ -136,6 +130,6 @@ export default function GenerateLinkForm({ hostOptions, pathOptions, isLockedToV
|
||||
</div>
|
||||
</Panel.ListItem>
|
||||
</Panel.ListGroup>
|
||||
</Panel.Section>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -8,10 +8,10 @@ import { postUrlPresets } from '../../../../common/api/urlPresets';
|
||||
import { maybeAxiosError } from '../../../../common/api/utils';
|
||||
import Button from '../../../../common/components/buttons/Button';
|
||||
import IconButton from '../../../../common/components/buttons/IconButton';
|
||||
import TooltipActionBtn from '../../../../common/components/buttons/TooltipActionBtn';
|
||||
import Info from '../../../../common/components/info/Info';
|
||||
import Input from '../../../../common/components/input/input/Input';
|
||||
import ExternalLink from '../../../../common/components/link/external-link/ExternalLink';
|
||||
import Tooltip from '../../../../common/components/tooltip/Tooltip';
|
||||
import useUrlPresets from '../../../../common/hooks-query/useUrlPresets';
|
||||
import { preventEscape } from '../../../../common/utils/keyEvent';
|
||||
import { handleLinks } from '../../../../common/utils/linkUtils';
|
||||
@@ -194,17 +194,15 @@ export default function UrlPresetsForm() {
|
||||
<Panel.Error>{maybeUrlError}</Panel.Error>
|
||||
</td>
|
||||
<Panel.InlineElements relation='inner' as='td'>
|
||||
<TooltipActionBtn
|
||||
size='sm'
|
||||
isDisabled={!canTest}
|
||||
clickHandler={(event) => handleLinks(preset.alias, event)}
|
||||
tooltip='Test preset'
|
||||
aria-label='Test preset'
|
||||
variant='ontime-ghosted'
|
||||
color='#e2e2e2' // $gray-200
|
||||
icon={<IoOpenOutline />}
|
||||
<Tooltip
|
||||
text='Test preset'
|
||||
render={<IconButton variant='ghosted-white' />}
|
||||
data-testid={`field__test_${index}`}
|
||||
/>
|
||||
onClick={(event) => handleLinks(preset.alias, event)}
|
||||
disabled={!canTest}
|
||||
>
|
||||
<IoOpenOutline />
|
||||
</Tooltip>
|
||||
<IconButton
|
||||
onClick={() => remove(index)}
|
||||
variant='ghosted-destructive'
|
||||
|
||||
+9
-5
@@ -1,12 +1,13 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useFieldArray, useForm } from 'react-hook-form';
|
||||
import { IoAdd, IoTrash } from 'react-icons/io5';
|
||||
import { Select, Tooltip } from '@chakra-ui/react';
|
||||
import { Select } from '@chakra-ui/react';
|
||||
import { ImportMap, isAlphanumericWithSpace } from 'ontime-utils';
|
||||
|
||||
import Button from '../../../../../../common/components/buttons/Button';
|
||||
import IconButton from '../../../../../../common/components/buttons/IconButton';
|
||||
import Input from '../../../../../../common/components/input/input/Input';
|
||||
import Tooltip from '../../../../../../common/components/tooltip/Tooltip';
|
||||
import * as Panel from '../../../../panel-utils/PanelUtils';
|
||||
import useGoogleSheet from '../useGoogleSheet';
|
||||
import { useSheetStore } from '../useSheetStore';
|
||||
@@ -97,10 +98,13 @@ export default function ImportMapForm(props: ImportMapFormProps) {
|
||||
Import options
|
||||
<Panel.InlineElements>
|
||||
{!isSpreadsheet && (
|
||||
<Tooltip label='Revoke the google authentication'>
|
||||
<Button onClick={handleRevoke} disabled={isLoading}>
|
||||
Revoke
|
||||
</Button>
|
||||
<Tooltip
|
||||
text='Revoke the google authentication'
|
||||
render={<Button />}
|
||||
onClick={handleRevoke}
|
||||
disabled={isLoading}
|
||||
>
|
||||
Revoke
|
||||
</Tooltip>
|
||||
)}
|
||||
<Button onClick={onCancel} disabled={isLoading}>
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
import { IoArrowDown, IoArrowUp, IoBan, IoFlag, IoTime } from 'react-icons/io5';
|
||||
import { Tooltip } from '@chakra-ui/react';
|
||||
import { TimerPhase, TimerType } from 'ontime-types';
|
||||
|
||||
import { Corner } from '../../../common/components/editor-utils/EditorUtils';
|
||||
import Tooltip from '../../../common/components/tooltip/Tooltip';
|
||||
import { useMessagePreview } from '../../../common/hooks/useSocket';
|
||||
import useViewSettings from '../../../common/hooks-query/useViewSettings';
|
||||
import { handleLinks } from '../../../common/utils/linkUtils';
|
||||
import { cx, timerPlaceholder } from '../../../common/utils/styleUtils';
|
||||
import { tooltipDelayMid } from '../../../ontimeConfig';
|
||||
|
||||
import style from './MessageControl.module.scss';
|
||||
|
||||
@@ -64,20 +63,45 @@ export default function TimerPreview() {
|
||||
{secondary !== null && <div className={style.secondaryContent}>{secondary}</div>}
|
||||
</div>
|
||||
<div className={style.eventStatus}>
|
||||
<Tooltip label='Time type: Count down' openDelay={tooltipDelayMid} shouldWrapChildren>
|
||||
<IoArrowDown className={style.statusIcon} data-active={timerType === TimerType.CountDown} />
|
||||
<Tooltip
|
||||
text='Time type: Count down'
|
||||
render={<span />}
|
||||
className={style.statusIcon}
|
||||
data-active={timerType === TimerType.CountDown}
|
||||
>
|
||||
<IoArrowDown />
|
||||
</Tooltip>
|
||||
<Tooltip label='Time type: Count up' openDelay={tooltipDelayMid} shouldWrapChildren>
|
||||
<IoArrowUp className={style.statusIcon} data-active={timerType === TimerType.CountUp} />
|
||||
<Tooltip
|
||||
text='Time type: Count up'
|
||||
render={<span />}
|
||||
className={style.statusIcon}
|
||||
data-active={timerType === TimerType.CountUp}
|
||||
>
|
||||
<IoArrowUp />
|
||||
</Tooltip>
|
||||
<Tooltip label='Time type: Clock' openDelay={tooltipDelayMid} shouldWrapChildren>
|
||||
<IoTime className={style.statusIcon} data-active={timerType === TimerType.Clock} />
|
||||
<Tooltip
|
||||
text='Time type: Clock'
|
||||
render={<span />}
|
||||
className={style.statusIcon}
|
||||
data-active={timerType === TimerType.Clock}
|
||||
>
|
||||
<IoTime />
|
||||
</Tooltip>
|
||||
<Tooltip label='Time type: None' openDelay={tooltipDelayMid} shouldWrapChildren>
|
||||
<IoBan className={style.statusIcon} data-active={timerType === TimerType.None} />
|
||||
<Tooltip
|
||||
text='Time type: None'
|
||||
render={<span />}
|
||||
className={style.statusIcon}
|
||||
data-active={timerType === TimerType.None}
|
||||
>
|
||||
<IoBan />
|
||||
</Tooltip>
|
||||
<Tooltip label={countToEnd ? 'Count to end' : 'Count duration'} openDelay={tooltipDelayMid} shouldWrapChildren>
|
||||
<IoFlag className={style.statusIcon} data-active={countToEnd} />
|
||||
<Tooltip
|
||||
text={countToEnd ? 'Count to end' : 'Count duration'}
|
||||
render={<span />}
|
||||
className={style.statusIcon}
|
||||
data-active={countToEnd}
|
||||
>
|
||||
<IoFlag />
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
}
|
||||
|
||||
.auxTimers {
|
||||
display: flex;
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr 1fr;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
import { IoAdd, IoRemove } from 'react-icons/io5';
|
||||
import { Tooltip } from '@chakra-ui/react';
|
||||
import { useLocalStorage } from '@mantine/hooks';
|
||||
import { Playback } from 'ontime-types';
|
||||
import { MILLIS_PER_HOUR, parseUserTime } from 'ontime-utils';
|
||||
|
||||
import TimeInput from '../../../../common/components/input/time-input/TimeInput';
|
||||
import { setPlayback } from '../../../../common/hooks/useSocket';
|
||||
import { tooltipDelayMid } from '../../../../ontimeConfig';
|
||||
import TapButton from '../tap-button/TapButton';
|
||||
|
||||
import style from './AddTime.module.scss';
|
||||
@@ -41,16 +39,12 @@ export default function AddTime(props: AddTimeProps) {
|
||||
<div className={style.addTime}>
|
||||
<TimeInput name='addtime' submitHandler={handleTimeChange} time={timeInMs} placeholder='Add time' />
|
||||
<div className={style.addButtons}>
|
||||
<Tooltip label='Remove time' openDelay={tooltipDelayMid} shouldWrapChildren>
|
||||
<TapButton onClick={() => handleAddTime('remove')} disabled={doDisableButtons} className={style.tallButtons}>
|
||||
<IoRemove />
|
||||
</TapButton>
|
||||
</Tooltip>
|
||||
<Tooltip label='Add time' openDelay={tooltipDelayMid} shouldWrapChildren>
|
||||
<TapButton onClick={() => handleAddTime('add')} disabled={doDisableButtons} className={style.tallButtons}>
|
||||
<IoAdd />
|
||||
</TapButton>
|
||||
</Tooltip>
|
||||
<TapButton onClick={() => handleAddTime('remove')} disabled={doDisableButtons} className={style.tallButtons}>
|
||||
<IoRemove />
|
||||
</TapButton>
|
||||
<TapButton onClick={() => handleAddTime('add')} disabled={doDisableButtons} className={style.tallButtons}>
|
||||
<IoAdd />
|
||||
</TapButton>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -22,3 +22,24 @@
|
||||
gap: 0.25rem;
|
||||
height: 1.5rem;
|
||||
}
|
||||
|
||||
.fakeInput {
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
max-width: 7.5em;
|
||||
height: 2rem;
|
||||
display: grid;
|
||||
place-content: center;
|
||||
|
||||
font-size: 1rem;
|
||||
font-weight: 400;
|
||||
color: $gray-200;
|
||||
border-radius: $component-border-radius-md;
|
||||
border: 1px solid transparent;
|
||||
letter-spacing: 0.5px;
|
||||
font-variant-numeric: tabular-nums;
|
||||
|
||||
padding-inline: 0.5em;
|
||||
outline: none;
|
||||
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import { IoArrowDown, IoArrowUp, IoPause, IoPlay, IoStop } from 'react-icons/io5';
|
||||
import { Playback, SimpleDirection, SimplePlayback } from 'ontime-types';
|
||||
import { parseUserTime } from 'ontime-utils';
|
||||
import { millisToString, parseUserTime } from 'ontime-utils';
|
||||
|
||||
import TimeInput from '../../../../common/components/input/time-input/TimeInput';
|
||||
import { setAuxTimer, useAuxTimerControl, useAuxTimerTime } from '../../../../common/hooks/useSocket';
|
||||
@@ -22,7 +22,7 @@ export function AuxTimer({ index }: AuxTimerProps) {
|
||||
setDirection(index, newDirection);
|
||||
};
|
||||
|
||||
const canStop = playback !== SimplePlayback.Stop;
|
||||
const isActive = playback !== SimplePlayback.Stop;
|
||||
const playbackAction = playback === SimplePlayback.Start ? 'pause' : 'play';
|
||||
|
||||
return (
|
||||
@@ -30,15 +30,15 @@ export function AuxTimer({ index }: AuxTimerProps) {
|
||||
Aux Timer {index}
|
||||
<div className={style.controls}>
|
||||
<div className={style.input}>
|
||||
<AuxTimerInput index={index} />
|
||||
<TapButton onClick={toggleDirection} aspect='tight'>
|
||||
<AuxTimerInput index={index} isActive={isActive} />
|
||||
<TapButton onClick={toggleDirection} aspect='tight' disabled={isActive}>
|
||||
{direction === SimpleDirection.CountDown && <IoArrowDown data-testid={`aux-timer-direction-${index}`} />}
|
||||
{direction === SimpleDirection.CountUp && <IoArrowUp data-testid={`aux-timer-direction-${index}`} />}
|
||||
</TapButton>
|
||||
</div>
|
||||
<div className={style.twoSides}>
|
||||
<AuxTogglePlay index={index} action={playbackAction} />
|
||||
<TapButton onClick={() => stop(index)} theme={Playback.Stop} disabled={!canStop}>
|
||||
<TapButton onClick={() => stop(index)} theme={Playback.Stop} disabled={!isActive}>
|
||||
<IoStop data-testid={`aux-timer-stop-${index}`} />
|
||||
</TapButton>
|
||||
</div>
|
||||
@@ -47,11 +47,12 @@ export function AuxTimer({ index }: AuxTimerProps) {
|
||||
);
|
||||
}
|
||||
|
||||
interface AuxTimerInput {
|
||||
interface AuxTimerInputProps {
|
||||
index: number;
|
||||
isActive: boolean;
|
||||
}
|
||||
|
||||
function AuxTimerInput({ index }: AuxTimerProps) {
|
||||
function AuxTimerInput({ index, isActive }: AuxTimerInputProps) {
|
||||
const newTimeInMs = useAuxTimerTime(index);
|
||||
const { setDuration } = setAuxTimer;
|
||||
|
||||
@@ -60,6 +61,14 @@ function AuxTimerInput({ index }: AuxTimerProps) {
|
||||
setDuration(index, newTimeInMs);
|
||||
};
|
||||
|
||||
if (isActive) {
|
||||
return (
|
||||
<div className={style.fakeInput} data-testid={`time-label-aux${index}`}>
|
||||
{millisToString(newTimeInMs)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<TimeInput submitHandler={handleTimeUpdate} name={`aux${index}`} time={newTimeInMs} placeholder={`Aux ${index}`} />
|
||||
);
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
import { useMemo } from 'react';
|
||||
import { IoPause, IoPlay, IoPlaySkipBack, IoPlaySkipForward, IoReload, IoStop, IoTime } from 'react-icons/io5';
|
||||
import { Tooltip } from '@chakra-ui/react';
|
||||
import { Playback, TimerPhase } from 'ontime-types';
|
||||
import { validatePlayback } from 'ontime-utils';
|
||||
|
||||
import { setPlayback } from '../../../../common/hooks/useSocket';
|
||||
import { tooltipDelayMid } from '../../../../ontimeConfig';
|
||||
import TapButton from '../tap-button/TapButton';
|
||||
|
||||
import style from './PlaybackButtons.module.scss';
|
||||
@@ -66,31 +64,23 @@ export default function PlaybackButtons(props: PlaybackButtonsProps) {
|
||||
</TapButton>
|
||||
</div>
|
||||
<div className={style.transportContainer}>
|
||||
<Tooltip label='Previous event' openDelay={tooltipDelayMid}>
|
||||
<TapButton onClick={setPlayback.previous} disabled={disablePrev}>
|
||||
<IoPlaySkipBack />
|
||||
</TapButton>
|
||||
</Tooltip>
|
||||
<Tooltip label='Next event' openDelay={tooltipDelayMid}>
|
||||
<TapButton onClick={setPlayback.next} disabled={disableNext}>
|
||||
<IoPlaySkipForward />
|
||||
</TapButton>
|
||||
</Tooltip>
|
||||
<TapButton onClick={setPlayback.previous} disabled={disablePrev}>
|
||||
<IoPlaySkipBack />
|
||||
</TapButton>
|
||||
<TapButton onClick={setPlayback.next} disabled={disableNext}>
|
||||
<IoPlaySkipForward />
|
||||
</TapButton>
|
||||
</div>
|
||||
<div className={style.extra}>
|
||||
<TapButton onClick={setPlayback.roll} disabled={disableRoll} theme={Playback.Roll} active={isRolling}>
|
||||
<IoTime />
|
||||
</TapButton>
|
||||
<Tooltip label='Reload event' openDelay={tooltipDelayMid}>
|
||||
<TapButton onClick={setPlayback.reload} disabled={disableReload}>
|
||||
<IoReload className={style.invertX} />
|
||||
</TapButton>
|
||||
</Tooltip>
|
||||
<Tooltip label='Unload Event' openDelay={tooltipDelayMid}>
|
||||
<TapButton onClick={setPlayback.stop} disabled={disableStop} theme={Playback.Stop}>
|
||||
<IoStop />
|
||||
</TapButton>
|
||||
</Tooltip>
|
||||
<TapButton onClick={setPlayback.reload} disabled={disableReload}>
|
||||
<IoReload className={style.invertX} />
|
||||
</TapButton>
|
||||
<TapButton onClick={setPlayback.stop} disabled={disableStop} theme={Playback.Stop}>
|
||||
<IoStop />
|
||||
</TapButton>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { PropsWithChildren } from 'react';
|
||||
import { Tooltip } from '@chakra-ui/react';
|
||||
import { MaybeNumber, Playback, TimerPhase } from 'ontime-types';
|
||||
import { dayInMs, millisToString } from 'ontime-utils';
|
||||
|
||||
import AppLink from '../../../../common/components/link/app-link/AppLink';
|
||||
import Tooltip from '../../../../common/components/tooltip/Tooltip';
|
||||
import { useTimer } from '../../../../common/hooks/useSocket';
|
||||
import useReport from '../../../../common/hooks-query/useReport';
|
||||
import { formatDuration } from '../../../../common/utils/time';
|
||||
@@ -43,13 +43,9 @@ export default function PlaybackTimer(props: PropsWithChildren<PlaybackTimerProp
|
||||
return (
|
||||
<div className={style.timeContainer}>
|
||||
<div className={style.indicators}>
|
||||
<Tooltip label={rollLabel}>
|
||||
<div className={style.indicatorRoll} data-active={isRolling} />
|
||||
</Tooltip>
|
||||
<Tooltip text={rollLabel} render={<div />} className={style.indicatorRoll} data-active={isRolling} />
|
||||
<div className={style.indicatorNegative} data-active={isOvertime} />
|
||||
<Tooltip label={addedTimeLabel}>
|
||||
<div className={style.indicatorDelay} data-active={hasAddedTime} />
|
||||
</Tooltip>
|
||||
<Tooltip text={addedTimeLabel} render={<div />} className={style.indicatorDelay} data-active={hasAddedTime} />
|
||||
</div>
|
||||
<TimerDisplay time={isWaiting ? timer.secondaryTimer : timer.current} />
|
||||
<div className={style.status}>
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { Tooltip } from '@chakra-ui/react';
|
||||
|
||||
import Tooltip from '../../../common/components/tooltip/Tooltip';
|
||||
import { cx } from '../../../common/utils/styleUtils';
|
||||
|
||||
import style from './TimeLayout.module.scss';
|
||||
@@ -29,8 +28,12 @@ export function TimeRow({ label, value, daySpan, muted, className }: TimeLayoutP
|
||||
<div className={style.row}>
|
||||
<span className={style.label}>{label}</span>
|
||||
{daySpan ? (
|
||||
<Tooltip label={`Event spans over ${daySpan + 1} days`}>
|
||||
<span className={cx([style.clock, style.daySpan, className])}>{value}</span>
|
||||
<Tooltip
|
||||
text={`Event spans over ${daySpan + 1} days`}
|
||||
render={<span />}
|
||||
className={cx([style.clock, style.daySpan, className])}
|
||||
>
|
||||
{value}
|
||||
</Tooltip>
|
||||
) : (
|
||||
<span className={cx([style.clock, muted && style.muted, className])}>{value}</span>
|
||||
|
||||
@@ -22,6 +22,7 @@ $block-cursor-color: $orange-400;
|
||||
border-radius: $block-border-radius;
|
||||
position: relative;
|
||||
color: $block-text-color;
|
||||
overflow: hidden;
|
||||
|
||||
min-width: $block-width;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { memo } from 'react';
|
||||
import { IoInformationCircle } from 'react-icons/io5';
|
||||
import { Tooltip } from '@chakra-ui/react';
|
||||
import { EndAction, TimerType, TimeStrategy } from 'ontime-types';
|
||||
import { millisToString, parseUserTime } from 'ontime-utils';
|
||||
|
||||
@@ -8,6 +7,7 @@ import * as Editor from '../../../../common/components/editor-utils/EditorUtils'
|
||||
import TimeInput from '../../../../common/components/input/time-input/TimeInput';
|
||||
import Select from '../../../../common/components/select/Select';
|
||||
import Switch from '../../../../common/components/switch/Switch';
|
||||
import Tooltip from '../../../../common/components/tooltip/Tooltip';
|
||||
import { useEntryActions } from '../../../../common/hooks/useEntryAction';
|
||||
import { millisToDelayString } from '../../../../common/utils/dateConfig';
|
||||
import TimeInputFlow from '../../time-input-flow/TimeInputFlow';
|
||||
@@ -126,11 +126,12 @@ function EventEditorTimes({
|
||||
|
||||
<div className={style.column}>
|
||||
<Editor.Title>
|
||||
<Tooltip label='Changes how the timer is displayed in different views. It is not reflected in the rundown'>
|
||||
<span>
|
||||
Display Options
|
||||
<IoInformationCircle className={style.tooltipIcon} />
|
||||
</span>
|
||||
<Tooltip
|
||||
text='Changes how the timer is displayed in different views. It is not reflected in the rundown'
|
||||
render={<span />}
|
||||
>
|
||||
Display Options
|
||||
<IoInformationCircle className={style.tooltipIcon} />
|
||||
</Tooltip>
|
||||
</Editor.Title>
|
||||
<div className={style.splitTwo}>
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { Fragment, useCallback, useMemo, useState } from 'react';
|
||||
import { IoAlertCircle, IoCheckmarkCircle, IoTrash } from 'react-icons/io5';
|
||||
import { Tooltip } from '@chakra-ui/react';
|
||||
import { TimerLifeCycle, timerLifecycleValues, Trigger } from 'ontime-types';
|
||||
import { generateId } from 'ontime-utils';
|
||||
|
||||
@@ -8,6 +7,7 @@ import Button from '../../../../common/components/buttons/Button';
|
||||
import IconButton from '../../../../common/components/buttons/IconButton';
|
||||
import Select from '../../../../common/components/select/Select';
|
||||
import Tag from '../../../../common/components/tag/Tag';
|
||||
import Tooltip from '../../../../common/components/tooltip/Tooltip';
|
||||
import { useEntryActions } from '../../../../common/hooks/useEntryAction';
|
||||
import useAutomationSettings from '../../../../common/hooks-query/useAutomationSettings';
|
||||
|
||||
@@ -107,7 +107,7 @@ function EventTriggerForm({ eventId, triggers }: EventTriggerFormProps) {
|
||||
Add
|
||||
</Button>
|
||||
{validationError !== undefined ? (
|
||||
<Tooltip label={validationError} shouldWrapChildren>
|
||||
<Tooltip text={validationError} render={<span />}>
|
||||
<IoAlertCircle className={style.errorLabel} />
|
||||
</Tooltip>
|
||||
) : (
|
||||
|
||||
@@ -2,21 +2,21 @@
|
||||
|
||||
.block {
|
||||
@include block-styling;
|
||||
overflow: hidden;
|
||||
|
||||
margin-block: 1rem 0.25rem;
|
||||
display: grid;
|
||||
grid-template-columns: 2rem 1fr;
|
||||
grid-template-areas: 'binder header';
|
||||
align-items: center;
|
||||
// TODO(style fix): groups have an extra bottom margin which interrupt colour
|
||||
margin-block: 0.25rem;
|
||||
|
||||
&.hasCursor {
|
||||
outline: 1px solid $block-cursor-color;
|
||||
}
|
||||
|
||||
&.expanded {
|
||||
margin-block: 1rem 0;
|
||||
border-radius: $block-border-radius $block-border-radius 0 0;
|
||||
border-bottom: 0.25rem solid color-mix(in srgb, transparent 90%, var(--user-bg, transparent) 10%);
|
||||
}
|
||||
|
||||
.binder {
|
||||
@@ -29,8 +29,12 @@
|
||||
place-content: center;
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
&:focus {
|
||||
outline: 1px solid $blue-500;
|
||||
outline-offset: -1px;
|
||||
}
|
||||
}
|
||||
|
||||
.header {
|
||||
grid-area: header;
|
||||
|
||||
@@ -102,8 +102,9 @@ export default function RundownBlock({ data, hasCursor, collapsed, onCollapse }:
|
||||
onClick={handleFocusClick}
|
||||
onContextMenu={onContextMenu}
|
||||
style={{
|
||||
...(binderColours ? { '--user-bg': binderColours.backgroundColor } : {}),
|
||||
// ...(binderColours ? { '--user-bg': binderColours.backgroundColor } : {}),
|
||||
...dragStyle,
|
||||
'--user-bg': data.colour || '#929292',
|
||||
}}
|
||||
data-testid='rundown-block'
|
||||
>
|
||||
|
||||
@@ -4,12 +4,11 @@
|
||||
@include block-styling;
|
||||
|
||||
margin-block: 0.25rem;
|
||||
padding-right: 0.25rem;
|
||||
background-color: $block-bg2;
|
||||
padding-right: 0.5rem;
|
||||
|
||||
display: grid;
|
||||
grid-template-columns: 2rem 1fr auto;
|
||||
grid-template-areas: 'drag inpt btns';
|
||||
grid-template-columns: 2rem 1fr auto auto;
|
||||
align-items: center;
|
||||
height: $secondary-block-height;
|
||||
gap: 0.5rem;
|
||||
@@ -21,12 +20,4 @@
|
||||
|
||||
.drag {
|
||||
@include drag-style;
|
||||
grid-area: drag;
|
||||
}
|
||||
|
||||
.actionButtons {
|
||||
grid-area: btns;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
@@ -55,23 +55,24 @@ export default function RundownDelay({ data, hasCursor }: RundownDelayProps) {
|
||||
deleteEntry([data.id]);
|
||||
};
|
||||
|
||||
const blockClasses = cx([style.delay, hasCursor ? style.hasCursor : null]);
|
||||
|
||||
return (
|
||||
<div className={blockClasses} ref={setNodeRef} style={dragStyle} data-testid='rundown-delay'>
|
||||
<div
|
||||
className={cx([style.delay, hasCursor ? style.hasCursor : null])}
|
||||
ref={setNodeRef}
|
||||
style={dragStyle}
|
||||
data-testid='rundown-delay'
|
||||
>
|
||||
<span className={style.drag} ref={handleRef} {...dragAttributes} {...dragListeners}>
|
||||
<IoReorderTwo />
|
||||
</span>
|
||||
<DelayInput eventId={data.id} duration={data.duration} />
|
||||
<div className={style.actionButtons}>
|
||||
<Button onClick={applyDelayHandler} variant='ghosted-white'>
|
||||
<IoCheckmarkDone /> Make permanent
|
||||
</Button>
|
||||
<Button onClick={cancelDelayHandler} variant='ghosted-white'>
|
||||
<IoClose />
|
||||
Cancel
|
||||
</Button>
|
||||
</div>
|
||||
<Button onClick={applyDelayHandler} variant='ghosted-white'>
|
||||
<IoCheckmarkDone /> Make permanent
|
||||
</Button>
|
||||
<Button onClick={cancelDelayHandler} variant='ghosted-white'>
|
||||
<IoClose />
|
||||
Cancel
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ $skip-opacity: 0.2;
|
||||
@include block-styling;
|
||||
background-color: $block-bg;
|
||||
margin-block: 0.25rem;
|
||||
overflow: initial;
|
||||
|
||||
display: grid;
|
||||
grid-template-areas:
|
||||
@@ -15,7 +16,7 @@ $skip-opacity: 0.2;
|
||||
'binder pb-actions estatus estatus'
|
||||
'binder ... ... ...';
|
||||
|
||||
grid-template-columns: $block-binder-width 3rem 1fr auto;
|
||||
grid-template-columns: $block-binder-width 3rem 1fr 3rem;
|
||||
grid-template-rows: 0.125rem 2rem 2rem auto 0.125rem;
|
||||
align-items: center;
|
||||
padding-right: $block-clearance;
|
||||
@@ -92,11 +93,10 @@ $skip-opacity: 0.2;
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
|
||||
border-radius: $block-border-radius 0 0 $block-border-radius;
|
||||
|
||||
background-color: $gray-1050; // to override inline
|
||||
color: $section-white;
|
||||
font-size: 1rem;
|
||||
border-radius: 3px 0 0 3px;
|
||||
|
||||
.drag {
|
||||
@include drag-style;
|
||||
|
||||
@@ -10,11 +10,10 @@ import {
|
||||
IoPlaySkipForward,
|
||||
IoTime,
|
||||
} from 'react-icons/io5';
|
||||
import { Tooltip } from '@chakra-ui/react';
|
||||
import { EndAction, Playback, TimerType, TimeStrategy } from 'ontime-types';
|
||||
|
||||
import Tooltip from '../../../common/components/tooltip/Tooltip';
|
||||
import { cx } from '../../../common/utils/styleUtils';
|
||||
import { tooltipDelayMid } from '../../../ontimeConfig';
|
||||
import EditableBlockTitle from '../common/EditableBlockTitle';
|
||||
import TimeInputFlow from '../time-input-flow/TimeInputFlow';
|
||||
|
||||
@@ -137,25 +136,17 @@ function RundownEventInner({
|
||||
{loaded && <EventBlockProgressBar />}
|
||||
</div>
|
||||
<div className={style.eventStatus} tabIndex={-1}>
|
||||
<Tooltip label={`Time type: ${timerType}`} openDelay={tooltipDelayMid}>
|
||||
<span>
|
||||
<TimerIcon type={timerType} className={style.statusIcon} />
|
||||
</span>
|
||||
<Tooltip text={`Time type: ${timerType}`} render={<span />}>
|
||||
<TimerIcon type={timerType} className={style.statusIcon} />
|
||||
</Tooltip>
|
||||
<Tooltip label={`End action: ${endAction}`} openDelay={tooltipDelayMid}>
|
||||
<span>
|
||||
<EndActionIcon action={endAction} className={style.statusIcon} />
|
||||
</span>
|
||||
<Tooltip text={`End action: ${endAction}`} render={<span />}>
|
||||
<EndActionIcon action={endAction} className={style.statusIcon} />
|
||||
</Tooltip>
|
||||
<Tooltip label={`${countToEnd ? 'Count to End' : 'Count duration'}`} openDelay={tooltipDelayMid}>
|
||||
<span>
|
||||
<IoFlag className={`${style.statusIcon} ${countToEnd ? style.active : style.disabled}`} />
|
||||
</span>
|
||||
<Tooltip text={`${countToEnd ? 'Count to End' : 'Count duration'}`} render={<span />}>
|
||||
<IoFlag className={`${style.statusIcon} ${countToEnd ? style.active : style.disabled}`} />
|
||||
</Tooltip>
|
||||
<Tooltip label='Event has Triggers' openDelay={tooltipDelayMid}>
|
||||
<span>
|
||||
<IoFlash className={`${style.statusIcon} ${hasTriggers ? style.active : style.disabled}`} />
|
||||
</span>
|
||||
<Tooltip text='Event has Triggers' render={<span />}>
|
||||
<IoFlash className={`${style.statusIcon} ${hasTriggers ? style.active : style.disabled}`} />
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -5,7 +5,7 @@ $gap-left: calc(2rem + 0.25rem + 3rem);
|
||||
font-size: calc(1rem - 5px);
|
||||
position: absolute;
|
||||
top: -1em;
|
||||
z-index: $zindex-floating;;
|
||||
z-index: $zindex-floating;
|
||||
margin-left: $gap-left;
|
||||
|
||||
display: flex;
|
||||
|
||||
+1
-3
@@ -1,11 +1,9 @@
|
||||
.chip {
|
||||
background-color: $gray-1100;
|
||||
white-space: nowrap;
|
||||
|
||||
font-size: calc(1rem - 3px);
|
||||
color: $label-gray;
|
||||
padding: 0.125rem 0.5rem;
|
||||
border-radius: 2px;
|
||||
justify-self: end;
|
||||
|
||||
&.over {
|
||||
color: $ontime-delay-text;
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
import { useMemo } from 'react';
|
||||
import { IoCheckmarkCircle } from 'react-icons/io5';
|
||||
import { Tooltip } from '@chakra-ui/react';
|
||||
import { isPlaybackActive, MILLIS_PER_MINUTE, MILLIS_PER_SECOND } from 'ontime-utils';
|
||||
|
||||
import Tooltip from '../../../../common/components/tooltip/Tooltip';
|
||||
import { usePlayback } from '../../../../common/hooks/useSocket';
|
||||
import useReport from '../../../../common/hooks-query/useReport';
|
||||
import { cx } from '../../../../common/utils/styleUtils';
|
||||
import { formatDuration, formatTime, useTimeUntilStart } from '../../../../common/utils/time';
|
||||
import { tooltipDelayFast } from '../../../../ontimeConfig';
|
||||
|
||||
import style from './RundownEventChip.module.scss';
|
||||
|
||||
@@ -39,7 +38,7 @@ export default function RundownEventChip({
|
||||
const { playback } = usePlayback();
|
||||
|
||||
if (isLoaded) {
|
||||
return null; //TODO: the is a small flash of 'DUE' on the loaded event as clock data arrives before isLoaded propagates
|
||||
return null;
|
||||
}
|
||||
|
||||
const playbackActive = isPlaybackActive(playback);
|
||||
@@ -51,16 +50,14 @@ export default function RundownEventChip({
|
||||
if (playbackActive) {
|
||||
// we extracted the component to avoid unnecessary calculations and re-renders
|
||||
return (
|
||||
<Tooltip label='Expected time until start' openDelay={tooltipDelayFast}>
|
||||
<div className={className}>
|
||||
<EventUntil
|
||||
timeStart={timeStart}
|
||||
delay={delay}
|
||||
dayOffset={dayOffset}
|
||||
totalGap={totalGap}
|
||||
isLinkedToLoaded={isLinkedToLoaded}
|
||||
/>
|
||||
</div>
|
||||
<Tooltip text='Expected time until start' render={<span />} className={className}>
|
||||
<EventUntil
|
||||
timeStart={timeStart}
|
||||
delay={delay}
|
||||
dayOffset={dayOffset}
|
||||
totalGap={totalGap}
|
||||
isLinkedToLoaded={isLinkedToLoaded}
|
||||
/>
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
@@ -113,7 +110,7 @@ function EventReport(props: EventReportProps) {
|
||||
const absDifference = Math.abs(difference);
|
||||
|
||||
if (absDifference < MILLIS_PER_SECOND) {
|
||||
return ['ontime', 'ontime', 'Event finished ontime'];
|
||||
return ['ontime', 'under', 'Event finished on time'];
|
||||
}
|
||||
|
||||
const isOver = difference > 0;
|
||||
@@ -131,10 +128,8 @@ function EventReport(props: EventReportProps) {
|
||||
}
|
||||
|
||||
return (
|
||||
<Tooltip label={tooltip} openDelay={tooltipDelayFast}>
|
||||
<div className={cx([style.chip, style[overUnderStyle], className])}>
|
||||
{value === 'ontime' ? <IoCheckmarkCircle size='1.1rem' /> : value}
|
||||
</div>
|
||||
<Tooltip text={tooltip} render={<span />} className={cx([style.chip, style[overUnderStyle], className])}>
|
||||
{value === 'ontime' ? <IoCheckmarkCircle size='1.1rem' /> : value}
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,28 +1,13 @@
|
||||
import { memo, MouseEvent } from 'react';
|
||||
import { IoPause, IoPlay, IoReload, IoRemoveCircle, IoRemoveCircleOutline } from 'react-icons/io5';
|
||||
|
||||
import TooltipActionBtn from '../../../../common/components/buttons/TooltipActionBtn';
|
||||
import IconButton from '../../../../common/components/buttons/IconButton';
|
||||
import Tooltip from '../../../../common/components/tooltip/Tooltip';
|
||||
import { useEntryActions } from '../../../../common/hooks/useEntryAction';
|
||||
import { setEventPlayback } from '../../../../common/hooks/useSocket';
|
||||
import { tooltipDelayMid } from '../../../../ontimeConfig';
|
||||
|
||||
import style from '../RundownEvent.module.scss';
|
||||
|
||||
const blockBtnStyle = {
|
||||
size: 'sm',
|
||||
};
|
||||
|
||||
type StyleVariant = {
|
||||
'aria-label': string;
|
||||
tooltip: string;
|
||||
backgroundColor: string;
|
||||
_hover: { backgroundColor?: string };
|
||||
};
|
||||
|
||||
const tooltipProps = {
|
||||
openDelay: tooltipDelayMid,
|
||||
};
|
||||
|
||||
interface RundownEventPlaybackProps {
|
||||
eventId: string;
|
||||
skip: boolean;
|
||||
@@ -67,67 +52,66 @@ function RundownEventPlayback({
|
||||
setEventPlayback.loadEvent(eventId);
|
||||
};
|
||||
|
||||
const buttonVariant: Partial<StyleVariant> = {};
|
||||
|
||||
if (isPaused) {
|
||||
// continue
|
||||
buttonVariant['aria-label'] = 'Continue event';
|
||||
buttonVariant.tooltip = 'Continue event';
|
||||
buttonVariant.backgroundColor = '#339E4E';
|
||||
buttonVariant._hover = { backgroundColor: '#339E4Eee' };
|
||||
} else if (isPlaying) {
|
||||
// pause
|
||||
buttonVariant['aria-label'] = 'Pause event';
|
||||
buttonVariant.tooltip = 'Pause event';
|
||||
buttonVariant.backgroundColor = '#c05621';
|
||||
buttonVariant._hover = { backgroundColor: '#c05621ee' };
|
||||
} else {
|
||||
// start
|
||||
buttonVariant['aria-label'] = 'Start event';
|
||||
buttonVariant.tooltip = 'Start event';
|
||||
if (!disablePlayback) {
|
||||
buttonVariant._hover = { backgroundColor: '#339E4E' };
|
||||
const playButtonStyles: { tooltip: string; backgroundColor: string | undefined } = (() => {
|
||||
if (isPaused) {
|
||||
return {
|
||||
tooltip: 'Continue event',
|
||||
backgroundColor: '#339E4E',
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if (isPlaying) {
|
||||
return {
|
||||
tooltip: 'Pause event',
|
||||
backgroundColor: '#c05621',
|
||||
};
|
||||
}
|
||||
return {
|
||||
tooltip: 'Start event',
|
||||
backgroundColor: undefined,
|
||||
};
|
||||
})();
|
||||
|
||||
return (
|
||||
<div className={style.playbackActions}>
|
||||
<TooltipActionBtn
|
||||
variant='ontime-subtle-white'
|
||||
<Tooltip
|
||||
text='Skip event'
|
||||
render={<IconButton variant='subtle-white' />}
|
||||
onClick={toggleSkip}
|
||||
tabIndex={-1}
|
||||
disabled={loaded}
|
||||
style={{
|
||||
background: skip ? '#9A0000' : undefined,
|
||||
}}
|
||||
aria-label='Skip event'
|
||||
tooltip='Skip event'
|
||||
icon={skip ? <IoRemoveCircle /> : <IoRemoveCircleOutline />}
|
||||
backgroundColor={skip ? '#B20000' : undefined}
|
||||
_hover={{ backgroundColor: '#FF7878' }}
|
||||
{...tooltipProps}
|
||||
{...blockBtnStyle}
|
||||
clickHandler={toggleSkip}
|
||||
>
|
||||
{skip ? <IoRemoveCircle /> : <IoRemoveCircleOutline />}
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip
|
||||
text='Load event'
|
||||
render={<IconButton variant='subtle-white' />}
|
||||
onClick={load}
|
||||
tabIndex={-1}
|
||||
isDisabled={loaded}
|
||||
/>
|
||||
<TooltipActionBtn
|
||||
variant='ontime-subtle-white'
|
||||
disabled={disablePlayback}
|
||||
aria-label='Load event'
|
||||
tooltip='Load event'
|
||||
icon={<IoReload className={style.flip} />}
|
||||
isDisabled={disablePlayback}
|
||||
{...tooltipProps}
|
||||
{...blockBtnStyle}
|
||||
clickHandler={load}
|
||||
>
|
||||
<IoReload className={style.flip} />
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip
|
||||
text={playButtonStyles.tooltip}
|
||||
render={<IconButton variant='subtle-white' />}
|
||||
onClick={actionHandler}
|
||||
tabIndex={-1}
|
||||
/>
|
||||
<TooltipActionBtn
|
||||
variant='ontime-subtle-white'
|
||||
aria-label='Start event'
|
||||
tooltip='Start event'
|
||||
icon={!isPlaying ? <IoPlay /> : <IoPause />}
|
||||
isDisabled={disablePlayback}
|
||||
{...tooltipProps}
|
||||
{...blockBtnStyle}
|
||||
{...buttonVariant}
|
||||
clickHandler={actionHandler}
|
||||
tabIndex={-1}
|
||||
/>
|
||||
disabled={disablePlayback}
|
||||
style={{
|
||||
backgroundColor: playButtonStyles.backgroundColor,
|
||||
}}
|
||||
aria-label={isPlaying ? 'Pause event' : 'Start event'}
|
||||
>
|
||||
{!isPlaying ? <IoPlay /> : <IoPause />}
|
||||
</Tooltip>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,30 +1,63 @@
|
||||
.timeLabel {
|
||||
position: absolute;
|
||||
z-index: $zindex-floating;
|
||||
font-size: 0.75rem;
|
||||
font-weight: 600;
|
||||
top: 2px;
|
||||
right: 4px;
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.timeAction {
|
||||
opacity: 0.4;
|
||||
background: $gray-1050;
|
||||
color: $gray-500;
|
||||
cursor: pointer;
|
||||
padding-right: 0.5em;
|
||||
height: 2rem;
|
||||
width: 2rem;
|
||||
display: grid;
|
||||
place-content: center;
|
||||
aspect-ratio: 1;
|
||||
|
||||
&:hover:not(:disabled):not(:active) {
|
||||
background: $gray-1000;
|
||||
}
|
||||
|
||||
&:active:not(:disabled) {
|
||||
background: $gray-1300;
|
||||
}
|
||||
|
||||
&:focus {
|
||||
outline: 1px solid $blue-500;
|
||||
outline-offset: -1px;
|
||||
}
|
||||
|
||||
&.active {
|
||||
opacity: 1;
|
||||
color: var(--status-color-active-override, $active-indicator);
|
||||
}
|
||||
.fourtyfive {
|
||||
transform: rotate(-45deg);
|
||||
}
|
||||
}
|
||||
|
||||
.fourtyfive {
|
||||
transform: rotate(-45deg);
|
||||
}
|
||||
|
||||
.timerNote {
|
||||
width: 1.25em;
|
||||
color: $blue-500;
|
||||
margin-right: 0.5rem;
|
||||
font-size: 1.5em;
|
||||
display: grid;
|
||||
}
|
||||
|
||||
.inputGroup {
|
||||
border: 1px solid transparent;
|
||||
width: fit-content;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
border-radius: $component-border-radius-md;
|
||||
|
||||
&.delayed {
|
||||
border: 1px solid $ontime-delay-text;
|
||||
}
|
||||
|
||||
input {
|
||||
max-width: 6.5em;
|
||||
border-radius: $component-border-radius-md 0 0 $component-border-radius-md;
|
||||
border: none;
|
||||
padding-right: 0;
|
||||
}
|
||||
|
||||
button {
|
||||
width: 1.5rem;
|
||||
height: 2rem;
|
||||
border-radius: 0 $component-border-radius-md $component-border-radius-md 0;
|
||||
border: none;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
import { memo } from 'react';
|
||||
import { IoAlertCircleOutline, IoLink, IoLockClosed, IoLockOpenOutline, IoUnlink } from 'react-icons/io5';
|
||||
import { InputRightElement, Tooltip } from '@chakra-ui/react';
|
||||
import { TimeField, TimeStrategy } from 'ontime-types';
|
||||
import { dayInMs } from 'ontime-utils';
|
||||
|
||||
import * as Editor from '../../../common/components/editor-utils/EditorUtils';
|
||||
import TimeInputWithButton from '../../../common/components/input/time-input/TimeInputWithButton';
|
||||
import TimeInput from '../../../common/components/input/time-input/TimeInput';
|
||||
import Tooltip from '../../../common/components/tooltip/Tooltip';
|
||||
import { useEntryActions } from '../../../common/hooks/useEntryAction';
|
||||
import { cx } from '../../../common/utils/styleUtils';
|
||||
import { tooltipDelayFast, tooltipDelayMid } from '../../../ontimeConfig';
|
||||
|
||||
import style from './TimeInputFlow.module.scss';
|
||||
|
||||
@@ -25,8 +24,17 @@ interface EventBlockTimerProps {
|
||||
}
|
||||
|
||||
export default memo(TimeInputFlow);
|
||||
function TimeInputFlow(props: EventBlockTimerProps) {
|
||||
const { eventId, countToEnd, timeStart, timeEnd, duration, timeStrategy, linkStart, delay, showLabels } = props;
|
||||
function TimeInputFlow({
|
||||
eventId,
|
||||
countToEnd,
|
||||
timeStart,
|
||||
timeEnd,
|
||||
duration,
|
||||
timeStrategy,
|
||||
linkStart,
|
||||
delay,
|
||||
showLabels,
|
||||
}: EventBlockTimerProps) {
|
||||
const { updateEntry, updateTimer } = useEntryActions();
|
||||
|
||||
// In sync with EventEditorTimes
|
||||
@@ -52,86 +60,80 @@ function TimeInputFlow(props: EventBlockTimerProps) {
|
||||
}
|
||||
|
||||
const hasDelay = delay !== 0;
|
||||
|
||||
const isLockedEnd = timeStrategy === TimeStrategy.LockEnd;
|
||||
const isLockedDuration = timeStrategy === TimeStrategy.LockDuration;
|
||||
|
||||
const activeStart = cx([style.timeAction, linkStart && style.active]);
|
||||
const activeEnd = cx([style.timeAction, isLockedEnd && style.active]);
|
||||
const activeDuration = cx([style.timeAction, isLockedDuration && style.active]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div>
|
||||
{showLabels && <Editor.Label className={style.sectionTitle}>Start time</Editor.Label>}
|
||||
<TimeInputWithButton<TimeField>
|
||||
name='timeStart'
|
||||
submitHandler={handleSubmit}
|
||||
time={timeStart}
|
||||
hasDelay={hasDelay}
|
||||
placeholder='Start'
|
||||
disabled={linkStart}
|
||||
>
|
||||
<Tooltip label='Link start to previous end' openDelay={tooltipDelayMid}>
|
||||
<InputRightElement className={activeStart} onClick={() => handleLink(!linkStart)}>
|
||||
<span className={style.timeLabel}>S</span>
|
||||
<span className={style.fourtyfive}>{linkStart ? <IoLink /> : <IoUnlink />}</span>
|
||||
</InputRightElement>
|
||||
<div className={cx([style.inputGroup, hasDelay && style.delayed])}>
|
||||
<TimeInput
|
||||
name='timeStart'
|
||||
submitHandler={handleSubmit}
|
||||
time={timeStart}
|
||||
placeholder='Start'
|
||||
align='left'
|
||||
disabled={linkStart}
|
||||
/>
|
||||
<Tooltip
|
||||
text='Link start to previous end'
|
||||
className={cx([style.timeAction, linkStart && style.active])}
|
||||
onClick={() => handleLink(!linkStart)}
|
||||
>
|
||||
<span className={style.fourtyfive}>{linkStart ? <IoLink /> : <IoUnlink />}</span>
|
||||
</Tooltip>
|
||||
</TimeInputWithButton>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
{showLabels && <Editor.Label>End time</Editor.Label>}
|
||||
<TimeInputWithButton<TimeField>
|
||||
name='timeEnd'
|
||||
submitHandler={handleSubmit}
|
||||
time={timeEnd}
|
||||
hasDelay={hasDelay}
|
||||
disabled={isLockedDuration}
|
||||
placeholder='End'
|
||||
>
|
||||
<Tooltip label='Lock end' openDelay={tooltipDelayMid}>
|
||||
<InputRightElement
|
||||
className={activeEnd}
|
||||
onClick={() => handleChangeStrategy(TimeStrategy.LockEnd)}
|
||||
data-testid='lock__end'
|
||||
>
|
||||
<span className={style.timeLabel}>E</span>
|
||||
{isLockedEnd ? <IoLockClosed /> : <IoLockOpenOutline />}
|
||||
</InputRightElement>
|
||||
<div className={cx([style.inputGroup, hasDelay && style.delayed])}>
|
||||
<TimeInput
|
||||
name='timeEnd'
|
||||
submitHandler={handleSubmit}
|
||||
time={timeEnd}
|
||||
placeholder='End'
|
||||
align='left'
|
||||
disabled={isLockedDuration}
|
||||
/>
|
||||
<Tooltip
|
||||
text='Lock end'
|
||||
className={cx([style.timeAction, isLockedEnd && style.active])}
|
||||
onClick={() => handleChangeStrategy(TimeStrategy.LockEnd)}
|
||||
data-testid='lock__end'
|
||||
>
|
||||
{isLockedEnd ? <IoLockClosed /> : <IoLockOpenOutline />}
|
||||
</Tooltip>
|
||||
</TimeInputWithButton>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
{showLabels && <Editor.Label>Duration</Editor.Label>}
|
||||
<TimeInputWithButton<TimeField>
|
||||
name='duration'
|
||||
submitHandler={handleSubmit}
|
||||
time={duration}
|
||||
disabled={isLockedEnd}
|
||||
placeholder='Duration'
|
||||
>
|
||||
<Tooltip label='Lock duration' openDelay={tooltipDelayMid}>
|
||||
<InputRightElement
|
||||
className={activeDuration}
|
||||
onClick={() => handleChangeStrategy(TimeStrategy.LockDuration)}
|
||||
data-testid='lock__duration'
|
||||
>
|
||||
<span className={style.timeLabel}>D</span>
|
||||
{isLockedDuration ? <IoLockClosed /> : <IoLockOpenOutline />}
|
||||
</InputRightElement>
|
||||
<div className={cx([style.inputGroup, hasDelay && style.delayed])}>
|
||||
<TimeInput
|
||||
name='duration'
|
||||
submitHandler={handleSubmit}
|
||||
time={duration}
|
||||
placeholder='Duration'
|
||||
align='left'
|
||||
disabled={isLockedEnd}
|
||||
/>
|
||||
<Tooltip
|
||||
text='Lock duration'
|
||||
className={cx([style.timeAction, isLockedDuration && style.active])}
|
||||
onClick={() => handleChangeStrategy(TimeStrategy.LockDuration)}
|
||||
data-testid='lock__duration'
|
||||
>
|
||||
{isLockedDuration ? <IoLockClosed /> : <IoLockOpenOutline />}
|
||||
</Tooltip>
|
||||
</TimeInputWithButton>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{warnings.length > 0 && (
|
||||
<div className={style.timerNote} data-testid='event-warning'>
|
||||
<Tooltip label={warnings.join(' - ')} openDelay={tooltipDelayFast} variant='ontime-ondark' shouldWrapChildren>
|
||||
<IoAlertCircleOutline />
|
||||
</Tooltip>
|
||||
</div>
|
||||
<Tooltip text={warnings.join(' - ')} className={style.timerNote} data-testid='event-warning' render={<span />}>
|
||||
<IoAlertCircleOutline />
|
||||
</Tooltip>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -6,7 +6,7 @@ import { OntimeEntry, TimeField } from 'ontime-types';
|
||||
|
||||
import { useEntryActions } from '../../../common/hooks/useEntryAction';
|
||||
import { useFollowSelected } from '../../../common/hooks/useFollowComponent';
|
||||
import { AppMode,sessionKeys } from '../../../ontimeConfig';
|
||||
import { AppMode, sessionKeys } from '../../../ontimeConfig';
|
||||
import { usePersistedCuesheetOptions } from '../cuesheet.options';
|
||||
|
||||
import CuesheetBody from './cuesheet-table-elements/CuesheetBody';
|
||||
|
||||
-1
@@ -1,7 +1,6 @@
|
||||
@import "../CuesheetTable.module.scss";
|
||||
|
||||
.eventRow {
|
||||
vertical-align: top;
|
||||
background: color-mix(in srgb, transparent 92%, var(--user-bg, $gray-500) 8%);
|
||||
border-left: 4px solid var(--user-bg, $gray-500);
|
||||
|
||||
|
||||
@@ -49,7 +49,8 @@ export default function EventRow({
|
||||
const [cuesheetMode] = useSessionStorage<AppMode>({
|
||||
key: sessionKeys.cuesheetMode,
|
||||
defaultValue: AppMode.Edit,
|
||||
}); const ownRef = useRef<HTMLTableRowElement>(null);
|
||||
});
|
||||
const ownRef = useRef<HTMLTableRowElement>(null);
|
||||
|
||||
const isVisible = useVisibleRowsStore((state) => state.visibleRows.has(rowId));
|
||||
|
||||
|
||||
@@ -13,7 +13,8 @@
|
||||
|
||||
.about {
|
||||
@extend .column;
|
||||
font-size: calc(1rem - 2px);
|
||||
font-size: calc(1rem - 3px);
|
||||
padding-inline: 0.25rem;
|
||||
}
|
||||
|
||||
.inline {
|
||||
|
||||
@@ -24,17 +24,12 @@ interface WelcomeProps {
|
||||
export default function Welcome({ onClose }: WelcomeProps) {
|
||||
const navigate = useNavigate();
|
||||
|
||||
/** handle cleanup actions before request closing the modal */
|
||||
const handleClose = () => {
|
||||
onClose();
|
||||
};
|
||||
|
||||
/** handle loading a selected project */
|
||||
const handleLoadProject = async (filename: string) => {
|
||||
try {
|
||||
await loadProject(filename);
|
||||
await invalidateAllCaches();
|
||||
handleClose();
|
||||
onClose();
|
||||
} catch (_error) {
|
||||
/** no error handling for now */
|
||||
}
|
||||
@@ -45,7 +40,7 @@ export default function Welcome({ onClose }: WelcomeProps) {
|
||||
try {
|
||||
await loadDemo();
|
||||
await invalidateAllCaches();
|
||||
handleClose();
|
||||
onClose();
|
||||
} catch (_error) {
|
||||
/** no error handling for now */
|
||||
}
|
||||
@@ -54,13 +49,13 @@ export default function Welcome({ onClose }: WelcomeProps) {
|
||||
/** handle redirect to create modal */
|
||||
const handleCallCreate = () => {
|
||||
navigate('/editor?settings=project__create');
|
||||
handleClose();
|
||||
onClose();
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal
|
||||
isOpen
|
||||
onClose={handleClose}
|
||||
onClose={() => onClose()}
|
||||
showBackdrop
|
||||
bodyElements={
|
||||
<div className={style.sections}>
|
||||
@@ -74,7 +69,7 @@ export default function Welcome({ onClose }: WelcomeProps) {
|
||||
<div className={style.column}>
|
||||
<div className={style.header}>
|
||||
Welcome to Ontime
|
||||
<IconButton aria-label='close welcome modal' variant='subtle-white'>
|
||||
<IconButton aria-label='close welcome modal' variant='subtle-white' onClick={() => onClose()}>
|
||||
<IoClose />
|
||||
</IconButton>
|
||||
</div>
|
||||
@@ -87,7 +82,7 @@ export default function Welcome({ onClose }: WelcomeProps) {
|
||||
<th>Last Used</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<WelcomeProjectList loadProject={handleLoadProject} onClose={handleClose} />
|
||||
<WelcomeProjectList loadProject={handleLoadProject} onClose={() => onClose()} />
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
@@ -97,7 +92,7 @@ export default function Welcome({ onClose }: WelcomeProps) {
|
||||
<div className={style.column}>
|
||||
<div className={style.buttonRow}>
|
||||
<Button onClick={handleLoadDemo}>Load demo project</Button>
|
||||
<ImportProjectButton onFinish={handleClose} />
|
||||
<ImportProjectButton onFinish={() => onClose()} />
|
||||
<Button variant='primary' onClick={handleCallCreate}>
|
||||
Create new...
|
||||
</Button>
|
||||
|
||||
@@ -35,13 +35,14 @@ import { dispatchFromAdapter } from '../api-integration/integration.controller.j
|
||||
import { generateId } from 'ontime-utils';
|
||||
import { authenticateSocket } from '../middleware/authenticate.js';
|
||||
|
||||
type ClientId = string;
|
||||
let instance: SocketServer | null = null;
|
||||
|
||||
class SocketServer implements IAdapter {
|
||||
private readonly MAX_PAYLOAD = 1024 * 256; // 256Kb
|
||||
|
||||
private wss: WebSocketServer | null;
|
||||
private readonly clients: Map<string, Client>;
|
||||
private readonly clients: Map<ClientId, Client>;
|
||||
private lastConnection: Date | null = null;
|
||||
private shouldShowWelcome = true;
|
||||
|
||||
@@ -52,7 +53,7 @@ class SocketServer implements IAdapter {
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-this-alias -- this logic is used to ensure singleton
|
||||
instance = this;
|
||||
this.clients = new Map<string, Client>();
|
||||
this.clients = new Map<ClientId, Client>();
|
||||
this.wss = null;
|
||||
}
|
||||
|
||||
@@ -165,7 +166,7 @@ class SocketServer implements IAdapter {
|
||||
};
|
||||
}
|
||||
|
||||
private getOrCreateClient(clientId: string): Client {
|
||||
private getOrCreateClient(clientId: ClientId): Client {
|
||||
if (!this.clients.has(clientId)) {
|
||||
this.clients.set(clientId, {
|
||||
type: 'unknown',
|
||||
@@ -183,11 +184,11 @@ class SocketServer implements IAdapter {
|
||||
this.sendAsJson(MessageTag.ClientList, payload);
|
||||
}
|
||||
|
||||
public getClientList(): string[] {
|
||||
public getClientList(): ClientId[] {
|
||||
return Array.from(this.clients.keys());
|
||||
}
|
||||
|
||||
public renameClient(target: string, name: string) {
|
||||
public renameClient(target: ClientId, name: string) {
|
||||
const previousData = this.clients.get(target);
|
||||
if (!previousData) {
|
||||
throw new Error(`Client "${target}" not found`);
|
||||
@@ -198,7 +199,7 @@ class SocketServer implements IAdapter {
|
||||
this.sendClientList();
|
||||
}
|
||||
|
||||
public redirectClient(target: string, path: string) {
|
||||
public redirectClient(target: ClientId, path: string) {
|
||||
const previousData = this.clients.get(target);
|
||||
if (!previousData) {
|
||||
throw new Error(`Client "${target}" not found`);
|
||||
@@ -206,7 +207,7 @@ class SocketServer implements IAdapter {
|
||||
this.sendAsJson(MessageTag.ClientRedirect, { target, path });
|
||||
}
|
||||
|
||||
public identifyClient(target: string, identify: boolean) {
|
||||
public identifyClient(target: ClientId, identify: boolean) {
|
||||
const previousData = this.clients.get(target);
|
||||
if (!previousData) {
|
||||
throw new Error(`Client "${target}" not found`);
|
||||
|
||||
@@ -15,10 +15,10 @@ import { parseRundowns } from '../../api-data/rundown/rundown.parser.js';
|
||||
import { getCurrentRundown, getProjectCustomFields } from '../../api-data/rundown/rundown.dao.js';
|
||||
import { parseExcel } from '../../api-data/excel/excel.parser.js';
|
||||
import { parseCustomFields } from '../../api-data/custom-fields/customFields.parser.js';
|
||||
import { consoleSubdued } from '../../utils/console.js';
|
||||
|
||||
import { cellRequestFromEvent, type ClientSecret, getA1Notation, isClientSecret } from './sheetUtils.js';
|
||||
import { catchCommonImportXlsxError } from './googleApi.utils.js';
|
||||
import { consoleError, consoleSubdued } from '../../utils/console.js';
|
||||
|
||||
const sheetScope = 'https://www.googleapis.com/auth/spreadsheets';
|
||||
const codesUrl = 'https://oauth2.googleapis.com/device/code';
|
||||
|
||||
@@ -7,13 +7,13 @@ test('Aux timer buttons', async ({ page }) => {
|
||||
await page.getByTestId('time-input-aux1').press('Enter');
|
||||
await expect(page.getByTestId('time-input-aux1')).toHaveValue('12:34:56');
|
||||
await page.getByTestId('aux-timer-start-1').click();
|
||||
await expect(page.getByTestId('time-input-aux1')).toHaveValue('12:34:53', { timeout: 4000 });
|
||||
await expect(page.getByTestId('time-label-aux1')).toHaveText('12:34:53', { timeout: 4000 });
|
||||
await page.getByTestId('aux-timer-pause-1').click();
|
||||
await expect(page.getByTestId('time-input-aux1')).toHaveValue('12:34:53');
|
||||
await expect(page.getByTestId('time-label-aux1')).toHaveText('12:34:53');
|
||||
await page.getByTestId('aux-timer-stop-1').click();
|
||||
await expect(page.getByTestId('time-input-aux1')).toHaveValue('12:34:56');
|
||||
await page.getByTestId('aux-timer-direction-1').click();
|
||||
await page.getByTestId('aux-timer-start-1').click();
|
||||
await expect(page.getByTestId('time-input-aux1')).toHaveValue('12:34:59', { timeout: 4000 });
|
||||
await expect(page.getByTestId('time-label-aux1')).toHaveText('12:34:59', { timeout: 4000 });
|
||||
await page.getByTestId('aux-timer-stop-1').click();
|
||||
});
|
||||
|
||||
@@ -19,9 +19,9 @@ test('Copy-paste', async ({ page }) => {
|
||||
await page.getByTestId('block__title').press('Enter');
|
||||
|
||||
// copy paste below
|
||||
await page.locator('div').filter({ hasText: /^4$/ }).click();
|
||||
await page.locator('div').filter({ hasText: /^4$/ }).press('Control+c');
|
||||
await page.locator('div').filter({ hasText: /^4$/ }).press('Control+v');
|
||||
await page.getByTestId('rundown-event').locator('div').filter({ hasText: '4' }).click();
|
||||
await page.getByTestId('rundown-event').locator('div').filter({ hasText: '4' }).press('Control+c');
|
||||
await page.getByTestId('rundown-event').locator('div').filter({ hasText: '4' }).press('Control+v');
|
||||
|
||||
// assert
|
||||
await expect(page.getByTestId('entry-2')).toBeVisible();
|
||||
@@ -29,9 +29,9 @@ test('Copy-paste', async ({ page }) => {
|
||||
await expect(page.getByTestId('entry-2').getByTestId('rundown-event')).toContainText('5');
|
||||
|
||||
// copy paste above
|
||||
await page.locator('div').filter({ hasText: /^5$/ }).click();
|
||||
await page.locator('div').filter({ hasText: /^5$/ }).press('Control+c');
|
||||
await page.locator('div').filter({ hasText: /^5$/ }).press('Control+Shift+v');
|
||||
await page.getByTestId('rundown-event').locator('div').filter({ hasText: '5' }).click();
|
||||
await page.getByTestId('rundown-event').locator('div').filter({ hasText: '5' }).press('Control+c');
|
||||
await page.getByTestId('rundown-event').locator('div').filter({ hasText: '5' }).press('Control+Shift+v');
|
||||
|
||||
// assert
|
||||
await expect(page.getByTestId('entry-2')).toBeVisible();
|
||||
@@ -63,7 +63,7 @@ test('Move', async ({ page }) => {
|
||||
// copy move up
|
||||
await page.getByTestId('entry-3').getByTestId('rundown-event').getByText('3').click();
|
||||
await page.getByTestId('entry-3').getByTestId('rundown-event').filter({ hasText: '3' }).press('Alt+Control+ArrowUp');
|
||||
await page.getByTestId('entry-2').locator('div').filter({ hasText: /^3$/ }).press('Alt+Control+ArrowUp');
|
||||
await page.getByTestId('entry-3').getByTestId('rundown-event').filter({ hasText: '3' }).press('Alt+Control+ArrowUp');
|
||||
await expect(page.getByTestId('entry-1').getByTestId('rundown-event')).toContainText('3');
|
||||
});
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ test('show warning when event starts next day midnight', async ({ page }) => {
|
||||
|
||||
await page.getByRole('button', { name: 'Create Event' }).click();
|
||||
await page.getByRole('button', { name: 'Event' }).nth(4).click();
|
||||
await page.getByTestId('entry-2').getByText('E').click();
|
||||
await page.getByTestId('entry-2').getByTestId('lock__end').click();
|
||||
await page.getByTestId('entry-2').getByTestId('time-input-timeEnd').click();
|
||||
await page.getByTestId('entry-2').getByTestId('time-input-timeEnd').fill('0');
|
||||
await page.getByTestId('entry-2').getByTestId('time-input-timeEnd').press('Enter');
|
||||
|
||||
@@ -7,7 +7,7 @@ test('time until absolute', async ({ page }) => {
|
||||
await page.getByRole('button', { name: 'Delete all' }).click();
|
||||
|
||||
await page.getByRole('button', { name: 'Create Event' }).click();
|
||||
await page.getByRole('button', { name: 'Event' }).nth(4).click();
|
||||
await page.getByRole('button', { name: 'Event', exact: true }).nth(1).click();
|
||||
await page.getByRole('button', { name: 'Event', exact: true }).nth(1).click();
|
||||
await page.getByRole('button', { name: 'Event', exact: true }).nth(1).click();
|
||||
|
||||
|
||||
@@ -24,6 +24,8 @@ test('sheet file upload', async ({ page }) => {
|
||||
await page.getByRole('button', { name: 'Import preview' }).click();
|
||||
await page.getByRole('button', { name: 'Apply' }).click();
|
||||
await page.getByRole('button', { name: 'Return' }).click();
|
||||
|
||||
await page.getByRole('button', { name: 'Close settings' }).scrollIntoViewIfNeeded();
|
||||
await page.getByRole('button', { name: 'Close settings' }).click();
|
||||
|
||||
// asset test events
|
||||
|
||||
Reference in New Issue
Block a user