refactor: expose editor panels in small device

This commit is contained in:
Carlos Valente
2025-06-23 06:59:34 +02:00
committed by Carlos Valente
parent b991627cb7
commit 984dcb0181
52 changed files with 336 additions and 131 deletions
+1 -1
View File
@@ -18,7 +18,7 @@ import ViewLoader from './views/ViewLoader';
import { ONTIME_VERSION } from './ONTIME_VERSION'; import { ONTIME_VERSION } from './ONTIME_VERSION';
import { sentryDsn, sentryRecommendedIgnore } from './sentry.config'; import { sentryDsn, sentryRecommendedIgnore } from './sentry.config';
const Editor = React.lazy(() => import('./features/editors/ProtectedEditor')); const Editor = React.lazy(() => import('./views/editor/ProtectedEditor'));
const Cuesheet = React.lazy(() => import('./views/cuesheet/ProtectedCuesheet')); const Cuesheet = React.lazy(() => import('./views/cuesheet/ProtectedCuesheet'));
const Operator = React.lazy(() => import('./features/operator/OperatorExport')); const Operator = React.lazy(() => import('./features/operator/OperatorExport'));
@@ -53,14 +53,46 @@
} }
} }
.destructive {
background: $red-700;
color: $ui-white;
&:hover:not(:disabled):not(:active) {
background: $red-600;
color: $ui-white;
}
&:active:not(:disabled) {
background: $red-800;
border-color: $red-900;
}
}
.subtle-destructive {
background: $gray-1050;
color: $red-400;
&:hover:not(:disabled):not(:active) {
background: $gray-1000;
color: $red-500;
}
&:active:not(:disabled) {
background: $gray-1100;
border-color: $gray-1250;
}
}
.medium { .medium {
height: 2rem; height: 2rem;
} }
.large { .large {
height: 2.5rem; height: 2.5rem;
font-weight: 600;
} }
.xlarge { .xlarge {
height: 3rem; height: 3rem;
font-weight: 600;
} }
@@ -5,7 +5,7 @@ import { cx } from '../../utils/styleUtils';
import style from './Button.module.scss'; import style from './Button.module.scss';
interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> { interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
variant?: 'subtle' | 'primary'; variant?: 'subtle' | 'primary' | 'destructive' | 'subtle-destructive';
size?: 'medium' | 'large' | 'xlarge'; size?: 'medium' | 'large' | 'xlarge';
} }
@@ -16,8 +16,22 @@
border: 1px solid $gray-1200; border: 1px solid $gray-1200;
} }
.backdrop {
position: fixed;
inset: 0;
z-index: $zindex-backdrop;
background-color: $backdrop-color;
transition: opacity 300ms cubic-bezier(0.45, 1.005, 0, 1.005);
&[data-starting-style],
&[data-ending-style] {
opacity: 0;
}
}
.title { .title {
font-size: 1.25rem; font-size: 1rem;
font-weight: 600;
padding-block: 1rem; padding-block: 1rem;
display: flex; display: flex;
@@ -10,12 +10,21 @@ interface DialogProps {
isOpen: boolean; isOpen: boolean;
title: string; title: string;
showCloseButton?: boolean; showCloseButton?: boolean;
showBackdrop?: boolean;
bodyElements: ReactNode; bodyElements: ReactNode;
footerElements: ReactNode; footerElements: ReactNode;
onClose: () => void; onClose: () => void;
} }
export default function Dialog({ isOpen, title, showCloseButton, bodyElements, footerElements, onClose }: DialogProps) { export default function Dialog({
isOpen,
title,
showCloseButton,
showBackdrop,
bodyElements,
footerElements,
onClose,
}: DialogProps) {
return ( return (
<BaseDialog.Root <BaseDialog.Root
open={isOpen} open={isOpen}
@@ -24,6 +33,7 @@ export default function Dialog({ isOpen, title, showCloseButton, bodyElements, f
}} }}
> >
<BaseDialog.Portal> <BaseDialog.Portal>
{showBackdrop && <BaseDialog.Backdrop className={style.backdrop} />}
<BaseDialog.Popup className={style.dialog}> <BaseDialog.Popup className={style.dialog}>
<div className={style.title}> <div className={style.title}>
{title} {title}
@@ -40,3 +40,9 @@
margin-bottom: $element-inner-spacing; margin-bottom: $element-inner-spacing;
max-width: max-content; // prevent label from taking entire row max-width: max-content; // prevent label from taking entire row
} }
.separator {
width: 1px;
height: 0.75em;
background-color: $border-color-ondark;
}
@@ -2,7 +2,7 @@ import type { HTMLAttributes, LabelHTMLAttributes } from 'react';
import { IconBaseProps } from 'react-icons'; import { IconBaseProps } from 'react-icons';
import { IoArrowUp } from 'react-icons/io5'; import { IoArrowUp } from 'react-icons/io5';
import { cx } from '../../../common/utils/styleUtils'; import { cx } from '../../utils/styleUtils';
import style from './EditorUtils.module.scss'; import style from './EditorUtils.module.scss';
@@ -27,3 +27,7 @@ export function Label({ children, className, ...elementProps }: LabelHTMLAttribu
</label> </label>
); );
} }
export function Separator({ className, ...elementProps }: HTMLAttributes<HTMLDivElement>) {
return <div className={cx([style.separator, className])} {...elementProps} />;
}
@@ -3,7 +3,6 @@ import { create } from 'zustand';
export enum AppMode { export enum AppMode {
Run = 'run', Run = 'run',
Edit = 'edit', Edit = 'edit',
Freeze = 'freeze',
} }
const appModeKey = 'ontime-app-mode'; const appModeKey = 'ontime-app-mode';
@@ -1,7 +1,11 @@
import { IoEye, IoEyeOffOutline } from 'react-icons/io5'; import { IoEye, IoEyeOffOutline } from 'react-icons/io5';
import TooltipActionBtn from '../../../common/components/buttons/TooltipActionBtn'; import TooltipActionBtn from '../../../common/components/buttons/TooltipActionBtn';
import { setMessage, useExternalMessageInput as useSecondaryMessageInput, useTimerMessageInput } from '../../../common/hooks/useSocket'; import {
setMessage,
useExternalMessageInput as useSecondaryMessageInput,
useTimerMessageInput,
} from '../../../common/hooks/useSocket';
import { tooltipDelayMid } from '../../../ontimeConfig'; import { tooltipDelayMid } from '../../../ontimeConfig';
import InputRow from './InputRow'; import InputRow from './InputRow';
@@ -1,15 +1,15 @@
import { memo } from 'react'; import { memo } from 'react';
import { Corner } from '../../../common/components/editor-utils/EditorUtils';
import ErrorBoundary from '../../../common/components/error-boundary/ErrorBoundary'; import ErrorBoundary from '../../../common/components/error-boundary/ErrorBoundary';
import ViewNavigationMenu from '../../../common/components/navigation-menu/ViewNavigationMenu'; import ViewNavigationMenu from '../../../common/components/navigation-menu/ViewNavigationMenu';
import ProtectRoute from '../../../common/components/protect-route/ProtectRoute'; import ProtectRoute from '../../../common/components/protect-route/ProtectRoute';
import { handleLinks } from '../../../common/utils/linkUtils'; import { handleLinks } from '../../../common/utils/linkUtils';
import { cx } from '../../../common/utils/styleUtils'; import { cx } from '../../../common/utils/styleUtils';
import { Corner } from '../../editors/editor-utils/EditorUtils';
import MessageControl from './MessageControl'; import MessageControl from './MessageControl';
import style from '../../editors/Editor.module.scss'; import style from '../../../views/editor/Editor.module.scss';
export default memo(MessageControlExport); export default memo(MessageControlExport);
function MessageControlExport() { function MessageControlExport() {
@@ -2,12 +2,12 @@ import { IoArrowDown, IoArrowUp, IoBan, IoFlag, IoTime } from 'react-icons/io5';
import { Tooltip } from '@chakra-ui/react'; import { Tooltip } from '@chakra-ui/react';
import { TimerPhase, TimerType } from 'ontime-types'; import { TimerPhase, TimerType } from 'ontime-types';
import { Corner } from '../../../common/components/editor-utils/EditorUtils';
import { useMessagePreview } from '../../../common/hooks/useSocket'; import { useMessagePreview } from '../../../common/hooks/useSocket';
import useViewSettings from '../../../common/hooks-query/useViewSettings'; import useViewSettings from '../../../common/hooks-query/useViewSettings';
import { handleLinks } from '../../../common/utils/linkUtils'; import { handleLinks } from '../../../common/utils/linkUtils';
import { cx, timerPlaceholder } from '../../../common/utils/styleUtils'; import { cx, timerPlaceholder } from '../../../common/utils/styleUtils';
import { tooltipDelayMid } from '../../../ontimeConfig'; import { tooltipDelayMid } from '../../../ontimeConfig';
import { Corner } from '../../editors/editor-utils/EditorUtils';
import style from './MessageControl.module.scss'; import style from './MessageControl.module.scss';
@@ -1,14 +1,14 @@
import { memo } from 'react'; import { memo } from 'react';
import { Corner } from '../../../common/components/editor-utils/EditorUtils';
import ErrorBoundary from '../../../common/components/error-boundary/ErrorBoundary'; import ErrorBoundary from '../../../common/components/error-boundary/ErrorBoundary';
import ViewNavigationMenu from '../../../common/components/navigation-menu/ViewNavigationMenu'; import ViewNavigationMenu from '../../../common/components/navigation-menu/ViewNavigationMenu';
import ProtectRoute from '../../../common/components/protect-route/ProtectRoute'; import ProtectRoute from '../../../common/components/protect-route/ProtectRoute';
import { handleLinks } from '../../../common/utils/linkUtils'; import { handleLinks } from '../../../common/utils/linkUtils';
import { Corner } from '../../editors/editor-utils/EditorUtils';
import PlaybackControl from './PlaybackControl'; import PlaybackControl from './PlaybackControl';
import style from '../../editors/Editor.module.scss'; import style from '../../../views/editor/Editor.module.scss';
export default memo(TimerControlExport); export default memo(TimerControlExport);
function TimerControlExport() { function TimerControlExport() {
@@ -7,5 +7,5 @@
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
gap: 2rem; gap: 1rem;
} }
@@ -16,6 +16,11 @@
display: flex; display: flex;
flex-direction: column; flex-direction: column;
padding-inline: 1rem; padding-inline: 1rem;
:is([data-target='small-device']) & {
padding-inline: 0;
overflow-x: auto;
}
} }
.empty { .empty {
@@ -1,7 +1,8 @@
import { IoAdd } from 'react-icons/io5'; import { IoAdd } from 'react-icons/io5';
import { Button } from '@chakra-ui/react';
import { SupportedEntry } from 'ontime-types'; import { SupportedEntry } from 'ontime-types';
import Button from '../../common/components/buttons/Button';
import * as Editor from '../../common/components/editor-utils/EditorUtils';
import Empty from '../../common/components/state/Empty'; import Empty from '../../common/components/state/Empty';
import style from './Empty.module.scss'; import style from './Empty.module.scss';
@@ -17,12 +18,15 @@ export default function RundownEmpty(props: RundownEmptyProps) {
<div className={style.empty}> <div className={style.empty}>
<Empty style={{ marginTop: '5vh', marginBottom: '3rem' }} /> <Empty style={{ marginTop: '5vh', marginBottom: '3rem' }} />
<div className={style.inline}> <div className={style.inline}>
<Button onClick={() => handleAddNew(SupportedEntry.Event)} variant='ontime-filled' leftIcon={<IoAdd />}> <Button onClick={() => handleAddNew(SupportedEntry.Event)} variant='primary' size='large'>
<IoAdd />
Create Event Create Event
</Button> </Button>
<Button onClick={() => handleAddNew(SupportedEntry.Block)} variant='ontime-filled' leftIcon={<IoAdd />}> <Editor.Separator />
Create Block
<Button onClick={() => handleAddNew(SupportedEntry.Block)} variant='primary' size='large'>
<IoAdd /> Create Block
</Button> </Button>
</div> </div>
</div> </div>
@@ -1,4 +1,4 @@
@use '../editors/EditorMixin' as editor; @use '../../views/editor/EditorMixin' as editor;
.rundownExport { .rundownExport {
height: 100%; height: 100%;
@@ -1,6 +1,7 @@
import { memo } from 'react'; import { memo } from 'react';
import { ContextMenu } from '../../common/components/context-menu/ContextMenu'; import { ContextMenu } from '../../common/components/context-menu/ContextMenu';
import { Corner } from '../../common/components/editor-utils/EditorUtils';
import ErrorBoundary from '../../common/components/error-boundary/ErrorBoundary'; import ErrorBoundary from '../../common/components/error-boundary/ErrorBoundary';
import ViewNavigationMenu from '../../common/components/navigation-menu/ViewNavigationMenu'; import ViewNavigationMenu from '../../common/components/navigation-menu/ViewNavigationMenu';
import ProtectRoute from '../../common/components/protect-route/ProtectRoute'; import ProtectRoute from '../../common/components/protect-route/ProtectRoute';
@@ -8,7 +9,6 @@ import { useIsSmallDevice } from '../../common/hooks/useIsSmallDevice';
import { useAppMode } from '../../common/stores/appModeStore'; import { useAppMode } from '../../common/stores/appModeStore';
import { handleLinks } from '../../common/utils/linkUtils'; import { handleLinks } from '../../common/utils/linkUtils';
import { cx } from '../../common/utils/styleUtils'; import { cx } from '../../common/utils/styleUtils';
import { Corner } from '../editors/editor-utils/EditorUtils';
import RundownEventEditor from './event-editor/RundownEventEditor'; import RundownEventEditor from './event-editor/RundownEventEditor';
import FinderPlacement from './placements/FinderPlacement'; import FinderPlacement from './placements/FinderPlacement';
@@ -26,13 +26,17 @@ function RundownExport() {
if (isSmallDevice && isExtracted) { if (isSmallDevice && isExtracted) {
return ( return (
<ProtectRoute permission='editor'> <ProtectRoute permission='editor'>
<div className={cx([style.rundownExport, style.extracted])} data-testid='panel-rundown'> <div
className={cx([style.rundownExport, style.extracted])}
data-target='small-device'
data-testid='panel-rundown'
>
<FinderPlacement /> <FinderPlacement />
<ViewNavigationMenu supressSettings /> <ViewNavigationMenu supressSettings />
<div className={style.content}> <div className={style.content}>
<ErrorBoundary> <ErrorBoundary>
<ContextMenu> <ContextMenu>
<RundownWrapper /> <RundownWrapper isSmallDevice />
</ContextMenu> </ContextMenu>
</ErrorBoundary> </ErrorBoundary>
</div> </div>
@@ -2,16 +2,21 @@ import Empty from '../../common/components/state/Empty';
import useRundown from '../../common/hooks-query/useRundown'; import useRundown from '../../common/hooks-query/useRundown';
import RundownHeader from './rundown-header/RundownHeader'; import RundownHeader from './rundown-header/RundownHeader';
import RundownHeaderMobile from './rundown-header/RundownHeaderMobile';
import Rundown from './Rundown'; import Rundown from './Rundown';
import styles from './Rundown.module.scss'; import styles from './Rundown.module.scss';
export default function RundownWrapper() { interface RundownWrapperProps {
isSmallDevice?: boolean;
}
export default function RundownWrapper({ isSmallDevice }: RundownWrapperProps) {
const { data, status } = useRundown(); const { data, status } = useRundown();
return ( return (
<div className={styles.rundownWrapper}> <div className={styles.rundownWrapper}>
<RundownHeader /> {isSmallDevice ? <RundownHeaderMobile /> : <RundownHeader />}
{status === 'success' && data ? <Rundown data={data} /> : <Empty text='Connecting to server' />} {status === 'success' && data ? <Rundown data={data} /> : <Empty text='Connecting to server' />}
</div> </div>
); );
@@ -1,10 +1,10 @@
import { useCallback } from 'react'; import { useCallback } from 'react';
import { OntimeEvent } from 'ontime-types'; import { OntimeEvent } from 'ontime-types';
import * as Editor from '../../../common/components/editor-utils/EditorUtils';
import AppLink from '../../../common/components/link/app-link/AppLink'; import AppLink from '../../../common/components/link/app-link/AppLink';
import { useEntryActions } from '../../../common/hooks/useEntryAction'; import { useEntryActions } from '../../../common/hooks/useEntryAction';
import useCustomFields from '../../../common/hooks-query/useCustomFields'; import useCustomFields from '../../../common/hooks-query/useCustomFields';
import * as Editor from '../../editors/editor-utils/EditorUtils';
import EventCustom from './composite/EventEditorCustom'; import EventCustom from './composite/EventEditorCustom';
import EventEditorTimes from './composite/EventEditorTimes'; import EventEditorTimes from './composite/EventEditorTimes';
@@ -1,8 +1,8 @@
import { memo, PropsWithChildren } from 'react'; import { memo, PropsWithChildren } from 'react';
import { Kbd } from '@chakra-ui/react'; import { Kbd } from '@chakra-ui/react';
import * as Editor from '../../../common/components/editor-utils/EditorUtils';
import { deviceAlt, deviceMod } from '../../../common/utils/deviceUtils'; import { deviceAlt, deviceMod } from '../../../common/utils/deviceUtils';
import * as Editor from '../../editors/editor-utils/EditorUtils';
import style from './EventEditorEmpty.module.scss'; import style from './EventEditorEmpty.module.scss';
@@ -4,10 +4,10 @@ import { Select, Switch, Tooltip } from '@chakra-ui/react';
import { EndAction, TimerType, TimeStrategy } from 'ontime-types'; import { EndAction, TimerType, TimeStrategy } from 'ontime-types';
import { millisToString, parseUserTime } from 'ontime-utils'; import { millisToString, parseUserTime } from 'ontime-utils';
import * as Editor from '../../../../common/components/editor-utils/EditorUtils';
import TimeInput from '../../../../common/components/input/time-input/TimeInput'; import TimeInput from '../../../../common/components/input/time-input/TimeInput';
import { useEntryActions } from '../../../../common/hooks/useEntryAction'; import { useEntryActions } from '../../../../common/hooks/useEntryAction';
import { millisToDelayString } from '../../../../common/utils/dateConfig'; import { millisToDelayString } from '../../../../common/utils/dateConfig';
import * as Editor from '../../../editors/editor-utils/EditorUtils';
import TimeInputFlow from '../../time-input-flow/TimeInputFlow'; import TimeInputFlow from '../../time-input-flow/TimeInputFlow';
import style from '../EventEditor.module.scss'; import style from '../EventEditor.module.scss';
@@ -2,8 +2,8 @@ import { memo } from 'react';
import { Input } from '@chakra-ui/react'; import { Input } from '@chakra-ui/react';
import { sanitiseCue } from 'ontime-utils'; import { sanitiseCue } from 'ontime-utils';
import * as Editor from '../../../../common/components/editor-utils/EditorUtils';
import SwatchSelect from '../../../../common/components/input/colour-input/SwatchSelect'; import SwatchSelect from '../../../../common/components/input/colour-input/SwatchSelect';
import * as Editor from '../../../editors/editor-utils/EditorUtils';
import { type EditorUpdateFields } from '../EventEditor'; import { type EditorUpdateFields } from '../EventEditor';
import EventTextArea from './EventTextArea'; import EventTextArea from './EventTextArea';
@@ -1,8 +1,8 @@
import { type CSSProperties, useCallback, useRef } from 'react'; import { type CSSProperties, useCallback, useRef } from 'react';
import * as Editor from '../../../../common/components/editor-utils/EditorUtils';
import { AutoTextArea } from '../../../../common/components/input/auto-text-area/AutoTextArea'; import { AutoTextArea } from '../../../../common/components/input/auto-text-area/AutoTextArea';
import useReactiveTextInput from '../../../../common/components/input/text-input/useReactiveTextInput'; import useReactiveTextInput from '../../../../common/components/input/text-input/useReactiveTextInput';
import * as Editor from '../../../editors/editor-utils/EditorUtils';
import { EditorUpdateFields } from '../EventEditor'; import { EditorUpdateFields } from '../EventEditor';
interface CountedTextAreaProps { interface CountedTextAreaProps {
@@ -1,8 +1,8 @@
import { useCallback, useRef } from 'react'; import { useCallback, useRef } from 'react';
import { Input, InputProps } from '@chakra-ui/react'; import { Input, InputProps } from '@chakra-ui/react';
import * as Editor from '../../../../common/components/editor-utils/EditorUtils';
import useReactiveTextInput from '../../../../common/components/input/text-input/useReactiveTextInput'; import useReactiveTextInput from '../../../../common/components/input/text-input/useReactiveTextInput';
import * as Editor from '../../../editors/editor-utils/EditorUtils';
import { EditorUpdateFields } from '../EventEditor'; import { EditorUpdateFields } from '../EventEditor';
interface EventTextInputProps extends InputProps { interface EventTextInputProps extends InputProps {
@@ -2,7 +2,7 @@ import { memo } from 'react';
import { useDisclosure } from '@chakra-ui/react'; import { useDisclosure } from '@chakra-ui/react';
import { useHotkeys } from '@mantine/hooks'; import { useHotkeys } from '@mantine/hooks';
import Finder from '../../editors/finder/Finder'; import Finder from '../../../views/editor/finder/Finder';
export default memo(FinderPlacement); export default memo(FinderPlacement);
@@ -1,7 +1,59 @@
.header { .header {
padding-inline: 1rem 2rem; padding-inline: calc(2.5rem - 6px) calc(1rem + 2px);
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: space-between; gap: 1px;
:is([data-target='small-device']) & {
padding-inline: 0;
overflow-x: auto;
}
}
.group {
display: flex;
gap: 0.25rem;
}
.button {
box-sizing: border-box;
display: flex;
align-items: center;
justify-content: center;
height: 2rem;
padding-inline: 1em;
border: 1px solid transparent;
border-radius: $component-border-radius-md;
color: $gray-400;
font-size: calc(1rem - 2px);
font-weight: 600;
&:focus-visible {
background: transparent;
}
&:hover:not(:disabled):not(:active) {
background: $gray-1000;
}
&:active {
background: $gray-1100;
}
&[data-pressed] {
background: $blue-700;
color: $ui-white;
&:hover:not(:disabled):not(:active) {
background: $blue-600;
}
}
}
.apart {
margin-left: auto;
}
.separator {
margin-inline: 1rem;
} }
@@ -1,6 +1,10 @@
import { Button, ButtonGroup } from '@chakra-ui/react'; import { memo } from 'react';
import { Toggle } from '@base-ui-components/react/toggle';
import { ToggleGroup } from '@base-ui-components/react/toggle-group';
import { Toolbar } from '@base-ui-components/react/toolbar';
import { OffsetMode } from 'ontime-types'; import { OffsetMode } from 'ontime-types';
import * as Editor from '../../../common/components/editor-utils/EditorUtils';
import { setOffsetMode, useOffsetMode } from '../../../common/hooks/useSocket'; import { setOffsetMode, useOffsetMode } from '../../../common/hooks/useSocket';
import { AppMode, useAppMode } from '../../../common/stores/appModeStore'; import { AppMode, useAppMode } from '../../../common/stores/appModeStore';
@@ -8,41 +12,50 @@ import RundownMenu from './RundownMenu';
import style from './RundownHeader.module.scss'; import style from './RundownHeader.module.scss';
export default function RundownHeader() { export default memo(RundownHeader);
function RundownHeader() {
const appMode = useAppMode((state) => state.mode); const appMode = useAppMode((state) => state.mode);
const setAppMode = useAppMode((state) => state.setMode); const setAppMode = useAppMode((state) => state.setMode);
const setRunMode = () => setAppMode(AppMode.Run);
const setEditMode = () => setAppMode(AppMode.Edit);
const { offsetMode } = useOffsetMode(); const { offsetMode } = useOffsetMode();
const toggleAppMode = (mode: AppMode[]) => {
// we need to stop user from deselecting a mode
const newValue = mode.at(0);
if (!newValue) return;
setAppMode(newValue);
};
const toggleOffsetMode = (mode: OffsetMode[]) => {
// we need to stop user from deselecting a mode
const newValue = mode.at(0);
if (!newValue) return;
setOffsetMode(newValue);
};
return ( return (
<div className={style.header}> <Toolbar.Root className={style.header}>
<ButtonGroup isAttached> <ToggleGroup value={[appMode]} onValueChange={toggleAppMode} className={style.group}>
<Button size='sm' variant={appMode === AppMode.Run ? 'ontime-filled' : 'ontime-subtle'} onClick={setRunMode}> <Toolbar.Button render={<Toggle />} value={AppMode.Run} className={style.button}>
Run Run
</Button> </Toolbar.Button>
<Button size='sm' variant={appMode === AppMode.Edit ? 'ontime-filled' : 'ontime-subtle'} onClick={setEditMode}> <Toolbar.Button render={<Toggle />} value={AppMode.Edit} className={style.button}>
Edit Edit
</Button> </Toolbar.Button>
</ButtonGroup> </ToggleGroup>
<ButtonGroup isAttached>
<Button <Editor.Separator className={style.separator} />
size='sm'
variant={offsetMode === OffsetMode.Absolute ? 'ontime-filled' : 'ontime-subtle'} <ToggleGroup value={[offsetMode]} onValueChange={toggleOffsetMode} className={style.group}>
onClick={() => setOffsetMode(OffsetMode.Absolute)} <Toolbar.Button render={<Toggle />} value={OffsetMode.Absolute} className={style.button}>
>
Absolute Absolute
</Button> </Toolbar.Button>
<Button <Toolbar.Button render={<Toggle />} value={OffsetMode.Relative} className={style.button}>
size='sm'
variant={offsetMode === OffsetMode.Relative ? 'ontime-filled' : 'ontime-subtle'}
onClick={() => setOffsetMode(OffsetMode.Relative)}
>
Relative Relative
</Button> </Toolbar.Button>
</ButtonGroup> </ToggleGroup>
<RundownMenu /> <RundownMenu />
</div> </Toolbar.Root>
); );
} }
@@ -0,0 +1,57 @@
import { memo } from 'react';
import { Toggle } from '@base-ui-components/react/toggle';
import { ToggleGroup } from '@base-ui-components/react/toggle-group';
import { Toolbar } from '@base-ui-components/react/toolbar';
import { OffsetMode } from 'ontime-types';
import * as Editor from '../../../common/components/editor-utils/EditorUtils';
import { setOffsetMode, useOffsetMode } from '../../../common/hooks/useSocket';
import { AppMode, useAppMode } from '../../../common/stores/appModeStore';
import style from './RundownHeader.module.scss';
export default memo(RundownHeader);
function RundownHeader() {
const appMode = useAppMode((state) => state.mode);
const setAppMode = useAppMode((state) => state.setMode);
const { offsetMode } = useOffsetMode();
const toggleAppMode = (mode: AppMode[]) => {
// we need to stop user from deselecting a mode
const newValue = mode.at(0);
if (!newValue) return;
setAppMode(newValue);
};
const toggleOffsetMode = (mode: OffsetMode[]) => {
// we need to stop user from deselecting a mode
const newValue = mode.at(0);
if (!newValue) return;
setOffsetMode(newValue);
};
return (
<Toolbar.Root className={style.header}>
<ToggleGroup value={[appMode]} onValueChange={toggleAppMode} className={style.group}>
<Toolbar.Button render={<Toggle />} value={AppMode.Run} className={style.button}>
Run
</Toolbar.Button>
<Toolbar.Button render={<Toggle />} value={AppMode.Edit} className={style.button}>
Edit
</Toolbar.Button>
</ToggleGroup>
<Editor.Separator className={style.separator} />
<ToggleGroup value={[offsetMode]} onValueChange={toggleOffsetMode} className={style.group}>
<Toolbar.Button render={<Toggle />} value={OffsetMode.Absolute} className={style.button}>
Absolute
</Toolbar.Button>
<Toolbar.Button render={<Toggle />} value={OffsetMode.Relative} className={style.button}>
Relative
</Toolbar.Button>
</ToggleGroup>
</Toolbar.Root>
);
}
@@ -1,66 +1,62 @@
import { useCallback, useRef } from 'react'; import { memo, useCallback } from 'react';
import { IoTrash } from 'react-icons/io5'; import { IoTrash } from 'react-icons/io5';
import { import { useDisclosure } from '@mantine/hooks';
AlertDialog,
AlertDialogBody,
AlertDialogContent,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogOverlay,
Button,
useDisclosure,
} from '@chakra-ui/react';
import Button from '../../../common/components/buttons/Button';
import Dialog from '../../../common/components/dialog/Dialog';
import { useEntryActions } from '../../../common/hooks/useEntryAction'; import { useEntryActions } from '../../../common/hooks/useEntryAction';
import { useAppMode } from '../../../common/stores/appModeStore'; import { AppMode, useAppMode } from '../../../common/stores/appModeStore';
import { useEventSelection } from '../useEventSelection'; import { useEventSelection } from '../useEventSelection';
export default function RundownMenu() { import style from './RundownHeader.module.scss';
export default memo(RundownMenu);
function RundownMenu() {
const [isOpen, handlers] = useDisclosure();
const clearSelectedEvents = useEventSelection((state) => state.clearSelectedEvents); const clearSelectedEvents = useEventSelection((state) => state.clearSelectedEvents);
const appMode = useAppMode((state) => state.mode); const appMode = useAppMode((state) => state.mode);
const { deleteAllEntries } = useEntryActions(); const { deleteAllEntries } = useEntryActions();
const { isOpen, onOpen, onClose } = useDisclosure();
const cancelRef = useRef<HTMLButtonElement | null>(null);
const deleteAll = useCallback(() => { const deleteAll = useCallback(() => {
deleteAllEntries(); deleteAllEntries();
clearSelectedEvents(); clearSelectedEvents();
onClose(); handlers.close();
}, [clearSelectedEvents, deleteAllEntries, onClose]); }, [clearSelectedEvents, deleteAllEntries, handlers]);
return ( return (
<> <>
<Button <Button
size='sm' variant='subtle-destructive'
variant='ontime-outlined' className={style.apart}
leftIcon={<IoTrash />} onClick={() => handlers.open()}
onClick={onOpen} disabled={appMode === AppMode.Run}
color='#FA5656'
isDisabled={appMode === 'run'}
> >
Clear rundown <IoTrash />
Clear All
</Button> </Button>
<AlertDialog variant='ontime' isOpen={isOpen} leastDestructiveRef={cancelRef} onClose={onClose}> <Dialog
<AlertDialogOverlay> isOpen={isOpen}
<AlertDialogContent> onClose={handlers.close}
<AlertDialogHeader fontSize='lg' fontWeight='bold'> title='Clear Rundown'
Clear rundown showBackdrop
</AlertDialogHeader> showCloseButton
<AlertDialogBody> bodyElements={
You will lose all data in your rundown. <br /> Are you sure? <>
</AlertDialogBody> You will lose all data in your rundown. <br /> Are you sure?
<AlertDialogFooter> </>
<Button ref={cancelRef} onClick={onClose} variant='ontime-ghosted-white'> }
Cancel footerElements={
</Button> <>
<Button colorScheme='red' onClick={deleteAll} ml={4}> <Button size='large' onClick={handlers.close}>
Delete all Cancel
</Button> </Button>
</AlertDialogFooter> <Button variant='destructive' size='large' onClick={deleteAll}>
</AlertDialogContent> Delete all
</AlertDialogOverlay> </Button>
</AlertDialog> </>
}
/>
</> </>
); );
} }
@@ -4,11 +4,11 @@ import { InputRightElement, Tooltip } from '@chakra-ui/react';
import { TimeField, TimeStrategy } from 'ontime-types'; import { TimeField, TimeStrategy } from 'ontime-types';
import { dayInMs } from 'ontime-utils'; import { dayInMs } from 'ontime-utils';
import * as Editor from '../../../common/components/editor-utils/EditorUtils';
import TimeInputWithButton from '../../../common/components/input/time-input/TimeInputWithButton'; import TimeInputWithButton from '../../../common/components/input/time-input/TimeInputWithButton';
import { useEntryActions } from '../../../common/hooks/useEntryAction'; import { useEntryActions } from '../../../common/hooks/useEntryAction';
import { cx } from '../../../common/utils/styleUtils'; import { cx } from '../../../common/utils/styleUtils';
import { tooltipDelayFast, tooltipDelayMid } from '../../../ontimeConfig'; import { tooltipDelayFast, tooltipDelayMid } from '../../../ontimeConfig';
import * as Editor from '../../editors/editor-utils/EditorUtils';
import style from './TimeInputFlow.module.scss'; import style from './TimeInputFlow.module.scss';
@@ -3,7 +3,7 @@ import { Button, Checkbox } from '@chakra-ui/react';
import { Column } from '@tanstack/react-table'; import { Column } from '@tanstack/react-table';
import { OntimeEntry } from 'ontime-types'; import { OntimeEntry } from 'ontime-types';
import * as Editor from '../../../../features/editors/editor-utils/EditorUtils'; import * as Editor from '../../../../common/components/editor-utils/EditorUtils';
import style from './CuesheetTableSettings.module.scss'; import style from './CuesheetTableSettings.module.scss';
@@ -6,17 +6,17 @@ import { useHotkeys } from '@mantine/hooks';
import NavigationMenu from '../../common/components/navigation-menu/NavigationMenu'; import NavigationMenu from '../../common/components/navigation-menu/NavigationMenu';
import { useElectronListener } from '../../common/hooks/useElectronEvent'; import { useElectronListener } from '../../common/hooks/useElectronEvent';
import { useWindowTitle } from '../../common/hooks/useWindowTitle'; import { useWindowTitle } from '../../common/hooks/useWindowTitle';
import AppSettings from '../app-settings/AppSettings'; import AppSettings from '../../features/app-settings/AppSettings';
import useAppSettingsNavigation from '../app-settings/useAppSettingsNavigation'; import useAppSettingsNavigation from '../../features/app-settings/useAppSettingsNavigation';
import { EditorOverview } from '../overview/Overview'; import { EditorOverview } from '../../features/overview/Overview';
import WelcomePlacement from './welcome/WelcomePlacement'; import WelcomePlacement from './welcome/WelcomePlacement';
import styles from './Editor.module.scss'; import styles from './Editor.module.scss';
const Rundown = lazy(() => import('../rundown/RundownExport')); const Rundown = lazy(() => import('../../features/rundown/RundownExport'));
const TimerControl = lazy(() => import('../control/playback/TimerControlExport')); const TimerControl = lazy(() => import('../../features/control/playback/TimerControlExport'));
const MessageControl = lazy(() => import('../control/message/MessageControlExport')); const MessageControl = lazy(() => import('../../features/control/message/MessageControlExport'));
export default function Editor() { export default function Editor() {
const { isOpen: isSettingsOpen, setLocation, close } = useAppSettingsNavigation(); const { isOpen: isSettingsOpen, setLocation, close } = useAppSettingsNavigation();
@@ -3,7 +3,7 @@ import { Input, Modal, ModalBody, ModalContent, ModalFooter, ModalOverlay } from
import { useDebouncedCallback } from '@mantine/hooks'; import { useDebouncedCallback } from '@mantine/hooks';
import { SupportedEntry } from 'ontime-types'; import { SupportedEntry } from 'ontime-types';
import { useEventSelection } from '../../rundown/useEventSelection'; import { useEventSelection } from '../../../features/rundown/useEventSelection';
import useFinder from './useFinder'; import useFinder from './useFinder';
@@ -4,9 +4,9 @@ import { Button, Checkbox, Modal, ModalBody, ModalCloseButton, ModalContent, Mod
import { loadDemo, loadProject } from '../../../common/api/db'; import { loadDemo, loadProject } from '../../../common/api/db';
import { postShowWelcomeDialog } from '../../../common/api/settings'; import { postShowWelcomeDialog } from '../../../common/api/settings';
import { invalidateAllCaches } from '../../../common/api/utils'; import { invalidateAllCaches } from '../../../common/api/utils';
import * as Editor from '../../../common/components/editor-utils/EditorUtils';
import ExternalLink from '../../../common/components/link/external-link/ExternalLink'; import ExternalLink from '../../../common/components/link/external-link/ExternalLink';
import { appVersion, discordUrl, documentationUrl, websiteUrl } from '../../../externals'; import { appVersion, discordUrl, documentationUrl, websiteUrl } from '../../../externals';
import * as Editor from '../editor-utils/EditorUtils';
import ImportProjectButton from './composite/ImportProjectButton'; import ImportProjectButton from './composite/ImportProjectButton';
import WelcomeProjectList from './composite/WelcomeProjectList'; import WelcomeProjectList from './composite/WelcomeProjectList';
+1 -1
View File
@@ -14,7 +14,7 @@ test('project file upload', async ({ page }) => {
} }
await page.getByRole('button', { name: 'Edit' }).click(); await page.getByRole('button', { name: 'Edit' }).click();
await page.getByRole('button', { name: 'Clear rundown' }).click(); await page.getByRole('button', { name: 'Clear all' }).click();
await page.getByRole('button', { name: 'Delete all' }).click(); await page.getByRole('button', { name: 'Delete all' }).click();
await page.getByRole('button', { name: 'toggle settings' }).click(); await page.getByRole('button', { name: 'toggle settings' }).click();
+2 -2
View File
@@ -5,7 +5,7 @@ test('delay blocks add time to events', async ({ page }) => {
// delete all events and add a new one // delete all events and add a new one
await page.getByRole('button', { name: 'Edit' }).click(); await page.getByRole('button', { name: 'Edit' }).click();
await page.getByRole('button', { name: 'Clear rundown' }).click(); await page.getByRole('button', { name: 'Clear all' }).click();
await page.getByRole('button', { name: 'Delete all' }).click(); await page.getByRole('button', { name: 'Delete all' }).click();
await page.getByRole('button', { name: 'Create event' }).click(); await page.getByRole('button', { name: 'Create event' }).click();
@@ -53,7 +53,7 @@ test('delays are show correctly', async ({ page }) => {
// add a test event // add a test event
await page.getByRole('button', { name: 'Edit' }).click(); await page.getByRole('button', { name: 'Edit' }).click();
await page.getByRole('button', { name: 'Clear rundown' }).click(); await page.getByRole('button', { name: 'Clear all' }).click();
await page.getByRole('button', { name: 'Delete all' }).click(); await page.getByRole('button', { name: 'Delete all' }).click();
await page.getByRole('button', { name: 'Create Event' }).click(); await page.getByRole('button', { name: 'Create Event' }).click();
+1 -1
View File
@@ -4,7 +4,7 @@ test('CRUD operations on the rundown', async ({ page }) => {
await page.goto('http://localhost:4001/editor'); await page.goto('http://localhost:4001/editor');
await page.getByRole('button', { name: 'Edit' }).click(); await page.getByRole('button', { name: 'Edit' }).click();
await page.getByRole('button', { name: 'Clear rundown' }).click(); await page.getByRole('button', { name: 'Clear all' }).click();
await page.getByRole('button', { name: 'Delete all' }).click(); await page.getByRole('button', { name: 'Delete all' }).click();
// create event from the rundown empty button // create event from the rundown empty button
+1 -1
View File
@@ -4,7 +4,7 @@ test('smoke test operator', async ({ page }) => {
// make some boilerplate // make some boilerplate
await page.goto('http://localhost:4001/editor'); await page.goto('http://localhost:4001/editor');
await page.getByRole('button', { name: 'Edit' }).click(); await page.getByRole('button', { name: 'Edit' }).click();
await page.getByRole('button', { name: 'Clear rundown' }).click(); await page.getByRole('button', { name: 'Clear all' }).click();
await page.getByRole('button', { name: 'Delete all' }).click(); await page.getByRole('button', { name: 'Delete all' }).click();
await page.getByRole('button', { name: 'Create Event' }).click(); await page.getByRole('button', { name: 'Create Event' }).click();
@@ -4,7 +4,7 @@ test('Copy-paste', async ({ page }) => {
await page.goto('http://localhost:4001/rundown'); await page.goto('http://localhost:4001/rundown');
// clear rundown // clear rundown
await page.getByRole('button', { name: 'Clear rundown' }).click(); await page.getByRole('button', { name: 'Clear all' }).click();
await page.getByRole('button', { name: 'Delete all' }).click(); await page.getByRole('button', { name: 'Delete all' }).click();
// create event // create event
@@ -43,7 +43,7 @@ test('Move', async ({ page }) => {
await page.goto('http://localhost:4001/rundown'); await page.goto('http://localhost:4001/rundown');
// clear rundown // clear rundown
await page.getByRole('button', { name: 'Clear rundown' }).click(); await page.getByRole('button', { name: 'Clear all' }).click();
await page.getByRole('button', { name: 'Delete all' }).click(); await page.getByRole('button', { name: 'Delete all' }).click();
// create events // create events
@@ -67,7 +67,7 @@ test('Add block', async ({ page }) => {
await page.goto('http://localhost:4001/rundown'); await page.goto('http://localhost:4001/rundown');
// clear rundown // clear rundown
await page.getByRole('button', { name: 'Clear rundown' }).click(); await page.getByRole('button', { name: 'Clear all' }).click();
await page.getByRole('button', { name: 'Delete all' }).click(); await page.getByRole('button', { name: 'Delete all' }).click();
// create events // create events
@@ -95,7 +95,7 @@ test('Add delay', async ({ page }) => {
await page.goto('http://localhost:4001/rundown'); await page.goto('http://localhost:4001/rundown');
// clear rundown // clear rundown
await page.getByRole('button', { name: 'Clear rundown' }).click(); await page.getByRole('button', { name: 'Clear all' }).click();
await page.getByRole('button', { name: 'Delete all' }).click(); await page.getByRole('button', { name: 'Delete all' }).click();
//create events //create events
@@ -116,7 +116,7 @@ test('Add event', async ({ page }) => {
await page.goto('http://localhost:4001/rundown'); await page.goto('http://localhost:4001/rundown');
// clear rundown // clear rundown
await page.getByRole('button', { name: 'Clear rundown' }).click(); await page.getByRole('button', { name: 'Clear all' }).click();
await page.getByRole('button', { name: 'Delete all' }).click(); await page.getByRole('button', { name: 'Delete all' }).click();
//create events //create events
@@ -138,7 +138,7 @@ test('Delete event', async ({ page }) => {
// clear rundown // clear rundown
await page.goto('http://localhost:4001/rundown'); await page.goto('http://localhost:4001/rundown');
await page.getByRole('button', { name: 'Clear rundown' }).click(); await page.getByRole('button', { name: 'Clear all' }).click();
await page.getByRole('button', { name: 'Delete all' }).click(); await page.getByRole('button', { name: 'Delete all' }).click();
//create event //create event
+2 -2
View File
@@ -3,7 +3,7 @@ import { expect, test } from '@playwright/test';
test('show warning when event crosses midnight', async ({ page }) => { test('show warning when event crosses midnight', async ({ page }) => {
await page.goto('http://localhost:4001/editor'); await page.goto('http://localhost:4001/editor');
await page.getByRole('button', { name: 'Clear rundown' }).click(); await page.getByRole('button', { name: 'Clear all' }).click();
await page.getByRole('button', { name: 'Delete all' }).click(); await page.getByRole('button', { name: 'Delete all' }).click();
await page.getByRole('button', { name: 'Create Event' }).click(); await page.getByRole('button', { name: 'Create Event' }).click();
@@ -23,7 +23,7 @@ test('show warning when event crosses midnight', async ({ page }) => {
test('show warning when event starts next day midnight', async ({ page }) => { test('show warning when event starts next day midnight', async ({ page }) => {
await page.goto('http://localhost:4001/editor'); await page.goto('http://localhost:4001/editor');
await page.getByRole('button', { name: 'Clear rundown' }).click(); await page.getByRole('button', { name: 'Clear all' }).click();
await page.getByRole('button', { name: 'Delete all' }).click(); await page.getByRole('button', { name: 'Delete all' }).click();
await page.getByRole('button', { name: 'Create Event' }).click(); await page.getByRole('button', { name: 'Create Event' }).click();
+2 -2
View File
@@ -3,7 +3,7 @@ import { expect, test } from '@playwright/test';
test('time until absolute', async ({ page }) => { test('time until absolute', async ({ page }) => {
await page.goto('http://localhost:4001/editor'); await page.goto('http://localhost:4001/editor');
await page.getByRole('button', { name: 'Clear rundown' }).click(); await page.getByRole('button', { name: 'Clear all' }).click();
await page.getByRole('button', { name: 'Delete all' }).click(); await page.getByRole('button', { name: 'Delete all' }).click();
await page.getByRole('button', { name: 'Create Event' }).click(); await page.getByRole('button', { name: 'Create Event' }).click();
@@ -40,7 +40,7 @@ test('time until absolute', async ({ page }) => {
test('time until relative', async ({ page }) => { test('time until relative', async ({ page }) => {
await page.goto('http://localhost:4001/editor'); await page.goto('http://localhost:4001/editor');
await page.getByRole('button', { name: 'Clear rundown' }).click(); await page.getByRole('button', { name: 'Clear all' }).click();
await page.getByRole('button', { name: 'Delete all' }).click(); await page.getByRole('button', { name: 'Delete all' }).click();
await page.getByRole('button', { name: 'Create Event' }).click(); await page.getByRole('button', { name: 'Create Event' }).click();
@@ -4,7 +4,7 @@ test('Rearrange while playing', async ({ page }) => {
await page.goto('http://localhost:4001/rundown'); await page.goto('http://localhost:4001/rundown');
// clear rundown // clear rundown
await page.getByRole('button', { name: 'Clear rundown' }).click(); await page.getByRole('button', { name: 'Clear all' }).click();
await page.getByRole('button', { name: 'Delete all' }).click(); await page.getByRole('button', { name: 'Delete all' }).click();
// create events // create events
@@ -5,7 +5,7 @@ const fileToUpload = 'e2e/tests/fixtures/test-sheet.xlsx';
test('sheet file upload', async ({ page }) => { test('sheet file upload', async ({ page }) => {
await page.goto('http://localhost:4001/editor'); await page.goto('http://localhost:4001/editor');
await page.getByRole('button', { name: 'Edit' }).click(); await page.getByRole('button', { name: 'Edit' }).click();
await page.getByRole('button', { name: 'Clear rundown' }).click(); await page.getByRole('button', { name: 'Clear all' }).click();
await page.getByRole('button', { name: 'Delete all' }).click(); await page.getByRole('button', { name: 'Delete all' }).click();
await page.getByRole('button', { name: 'Toggle settings' }).click(); await page.getByRole('button', { name: 'Toggle settings' }).click();