style: editor tweaks (#736)

* style: menu tweaks

* style: editor tweaks

* style: freeze
This commit is contained in:
Carlos Valente
2024-01-26 14:47:27 +01:00
committed by GitHub
parent c146954a2f
commit f6557a9814
32 changed files with 178 additions and 249 deletions
@@ -10,14 +10,12 @@ import {
IconButton, IconButton,
Tooltip, Tooltip,
} from '@chakra-ui/react'; } from '@chakra-ui/react';
import { FiPower } from '@react-icons/all-files/fi/FiPower'; import { IoPowerOutline } from '@react-icons/all-files/io5/IoPowerOutline';
import { Size } from '../../models/Util.type';
import { useEmitLog } from '../../stores/logger'; import { useEmitLog } from '../../stores/logger';
interface QuitIconBtnProps { interface QuitIconBtnProps {
clickHandler: () => void; clickHandler: () => void;
size?: Size;
disabled?: boolean; disabled?: boolean;
} }
@@ -42,7 +40,7 @@ const quitBtnStyle = {
}; };
export default function QuitIconBtn(props: QuitIconBtnProps) { export default function QuitIconBtn(props: QuitIconBtnProps) {
const { clickHandler, size = 'lg', disabled, ...rest } = props; const { clickHandler, disabled, ...rest } = props;
const [isOpen, setIsOpen] = useState(false); const [isOpen, setIsOpen] = useState(false);
const { emitInfo } = useEmitLog(); const { emitInfo } = useEmitLog();
const onClose = () => setIsOpen(false); const onClose = () => setIsOpen(false);
@@ -67,8 +65,8 @@ export default function QuitIconBtn(props: QuitIconBtnProps) {
<Tooltip label='Quit Application'> <Tooltip label='Quit Application'>
<IconButton <IconButton
aria-label='Quit Application' aria-label='Quit Application'
size={size} size='lg'
icon={<FiPower />} icon={<IoPowerOutline />}
onClick={() => setIsOpen(true)} onClick={() => setIsOpen(true)}
isDisabled={disabled} isDisabled={disabled}
{...quitBtnStyle} {...quitBtnStyle}
@@ -81,7 +79,7 @@ export default function QuitIconBtn(props: QuitIconBtnProps) {
<AlertDialogHeader fontSize='lg' fontWeight='bold'> <AlertDialogHeader fontSize='lg' fontWeight='bold'>
Ontime Shutdown Ontime Shutdown
</AlertDialogHeader> </AlertDialogHeader>
<AlertDialogBody>This will shutdown the program and all running servers. Are you sure?</AlertDialogBody> <AlertDialogBody>This will shutdown the Ontime server. Are you sure?</AlertDialogBody>
<AlertDialogFooter> <AlertDialogFooter>
<Button ref={cancelRef} onClick={onClose} variant='ghost'> <Button ref={cancelRef} onClick={onClose} variant='ghost'>
Cancel Cancel
@@ -1,22 +0,0 @@
.header {
font-size: $inner-section-text-size;
font-weight: 600;
display: flex;
justify-content: space-between;
color: $section-white;
margin-top: $main-spacing;
border-bottom: 1px solid $border-color-ondark;
padding-bottom: $element-inner-spacing;
margin-bottom: $element-spacing;
cursor: pointer;
}
.moreExpanded {
transform: scaleY(-1);
transition: transform $transition-time-feedback;
}
.moreCollapsed {
transform: scaleY(1);
transition: transform $transition-time-feedback;
}
@@ -1,20 +0,0 @@
import { FiChevronUp } from '@react-icons/all-files/fi/FiChevronUp';
import style from './CollapseBar.module.scss';
interface CollapseBarProps {
title: string;
isCollapsed: boolean;
onClick: () => void;
}
export default function CollapseBar(props: CollapseBarProps) {
const { title = 'Collapse bar', isCollapsed, onClick } = props;
return (
<div className={style.header} onClick={onClick}>
{title}
<FiChevronUp className={isCollapsed ? style.moreCollapsed : style.moreExpanded} />
</div>
);
}
@@ -1,6 +1,5 @@
.contextMenuButton { .contextMenuButton {
position: absolute; position: absolute;
cursor: pointer;
} }
.contextMenuBackdrop { .contextMenuBackdrop {
@@ -1,19 +0,0 @@
import { PropsWithChildren, useState } from 'react';
import CollapseBar from '../../common/components/collapse-bar/CollapseBar';
interface CollapsableInfoProps {
title: string;
}
export default function CollapsableInfo(props: PropsWithChildren<CollapsableInfoProps>) {
const { title, children } = props;
const [collapsed, setCollapsed] = useState(false);
return (
<>
<CollapseBar title={title} isCollapsed={collapsed} onClick={() => setCollapsed((prev) => !prev)} />
{!collapsed && children}
</>
);
}
@@ -4,6 +4,7 @@
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
gap: 0.5em; gap: 0.5em;
padding-bottom: 1rem
} }
.gap { .gap {
@@ -13,3 +14,7 @@
.open { .open {
background: $blue-700; background: $blue-700;
} }
.bottom {
margin-top: auto;
}
+22 -15
View File
@@ -1,4 +1,6 @@
import { memo, useCallback, useEffect, useState } from 'react'; import { memo, useCallback, useEffect, useState } from 'react';
import { IconButton, MenuButton, Tooltip } from '@chakra-ui/react';
import { IoAdd } from '@react-icons/all-files/io5/IoAdd';
import { IoCloud } from '@react-icons/all-files/io5/IoCloud'; import { IoCloud } from '@react-icons/all-files/io5/IoCloud';
import { IoCloudOutline } from '@react-icons/all-files/io5/IoCloudOutline'; import { IoCloudOutline } from '@react-icons/all-files/io5/IoCloudOutline';
import { IoColorWand } from '@react-icons/all-files/io5/IoColorWand'; import { IoColorWand } from '@react-icons/all-files/io5/IoColorWand';
@@ -10,14 +12,18 @@ import { IoPlay } from '@react-icons/all-files/io5/IoPlay';
import { IoPushOutline } from '@react-icons/all-files/io5/IoPushOutline'; import { IoPushOutline } from '@react-icons/all-files/io5/IoPushOutline';
import { IoSaveOutline } from '@react-icons/all-files/io5/IoSaveOutline'; import { IoSaveOutline } from '@react-icons/all-files/io5/IoSaveOutline';
import { IoSettingsOutline } from '@react-icons/all-files/io5/IoSettingsOutline'; import { IoSettingsOutline } from '@react-icons/all-files/io5/IoSettingsOutline';
import { IoSnowOutline } from '@react-icons/all-files/io5/IoSnowOutline';
import { downloadCSV, downloadRundown } from '../../common/api/ontimeApi'; import { downloadCSV, downloadRundown } from '../../common/api/ontimeApi';
import QuitIconBtn from '../../common/components/buttons/QuitIconBtn'; import QuitIconBtn from '../../common/components/buttons/QuitIconBtn';
import TooltipActionBtn from '../../common/components/buttons/TooltipActionBtn'; import TooltipActionBtn from '../../common/components/buttons/TooltipActionBtn';
import useElectronEvent from '../../common/hooks/useElectronEvent'; import useElectronEvent from '../../common/hooks/useElectronEvent';
import { AppMode, useAppMode } from '../../common/stores/appModeStore'; import { AppMode, useAppMode } from '../../common/stores/appModeStore';
import { cx } from '../../common/utils/styleUtils';
import ExportModal, { ExportType } from '../modals/export-modal/ExportModal'; import ExportModal, { ExportType } from '../modals/export-modal/ExportModal';
import RundownMenu from './RundownMenu';
import style from './MenuBar.module.scss'; import style from './MenuBar.module.scss';
interface MenuBarProps { interface MenuBarProps {
@@ -40,7 +46,7 @@ interface MenuBarProps {
const buttonStyle = { const buttonStyle = {
fontSize: '1.25em', fontSize: '1.25em',
size: 'lg', size: 'md',
colorScheme: 'white', colorScheme: 'white',
_hover: { _hover: {
background: 'rgba(255, 255, 255, 0.10)', // $white-10 background: 'rgba(255, 255, 255, 0.10)', // $white-10
@@ -128,8 +134,7 @@ const MenuBar = (props: MenuBarProps) => {
return ( return (
<div className={style.menu}> <div className={style.menu}>
<QuitIconBtn disabled={!isElectron} clickHandler={sendShutdown} size='md' /> <QuitIconBtn disabled={!isElectron} clickHandler={sendShutdown} />
<div className={style.gap} /> <div className={style.gap} />
<TooltipActionBtn <TooltipActionBtn
{...buttonStyle} {...buttonStyle}
@@ -148,7 +153,6 @@ const MenuBar = (props: MenuBarProps) => {
clickHandler={onUploadOpen} clickHandler={onUploadOpen}
tooltip='Import project file' tooltip='Import project file'
aria-label='Import project file' aria-label='Import project file'
size='sm'
/> />
<TooltipActionBtn <TooltipActionBtn
{...buttonStyle} {...buttonStyle}
@@ -157,12 +161,22 @@ const MenuBar = (props: MenuBarProps) => {
clickHandler={() => setIsModalOpen(true)} clickHandler={() => setIsModalOpen(true)}
tooltip='Export project file' tooltip='Export project file'
aria-label='Export project file' aria-label='Export project file'
size='sm'
/> />
<ExportModal onClose={onModalClose} isOpen={isModalOpen} /> <ExportModal onClose={onModalClose} isOpen={isModalOpen} />
<div className={style.gap} /> <div className={style.gap} />
<RundownMenu>
<Tooltip label='Rundown...'>
<MenuButton as={IconButton} icon={<IoAdd />} {...buttonStyle} aria-label='Rundown menu' />
</Tooltip>
</RundownMenu>
<TooltipActionBtn
{...buttonStyle}
icon={<IoSnowOutline />}
className={appMode === AppMode.Run ? style.open : ''}
clickHandler={setRunMode}
tooltip='Freeze rundown'
aria-label='Freeze rundown'
/>
<TooltipActionBtn <TooltipActionBtn
{...buttonStyle} {...buttonStyle}
icon={<IoPlay />} icon={<IoPlay />}
@@ -170,7 +184,6 @@ const MenuBar = (props: MenuBarProps) => {
clickHandler={setRunMode} clickHandler={setRunMode}
tooltip='Run mode' tooltip='Run mode'
aria-label='Run mode' aria-label='Run mode'
size='sm'
/> />
<TooltipActionBtn <TooltipActionBtn
{...buttonStyle} {...buttonStyle}
@@ -179,9 +192,7 @@ const MenuBar = (props: MenuBarProps) => {
clickHandler={setEditMode} clickHandler={setEditMode}
tooltip='Edit mode' tooltip='Edit mode'
aria-label='Edit mode' aria-label='Edit mode'
size='sm'
/> />
<div className={style.gap} /> <div className={style.gap} />
<TooltipActionBtn <TooltipActionBtn
{...buttonStyle} {...buttonStyle}
@@ -191,7 +202,6 @@ const MenuBar = (props: MenuBarProps) => {
clickHandler={onSheetsOpen} clickHandler={onSheetsOpen}
tooltip='Sheets' tooltip='Sheets'
aria-label='Sheets' aria-label='Sheets'
size='sm'
/> />
<TooltipActionBtn <TooltipActionBtn
{...buttonStyle} {...buttonStyle}
@@ -201,7 +211,6 @@ const MenuBar = (props: MenuBarProps) => {
clickHandler={onIntegrationOpen} clickHandler={onIntegrationOpen}
tooltip='Integrations' tooltip='Integrations'
aria-label='Integrations' aria-label='Integrations'
size='sm'
/> />
<TooltipActionBtn <TooltipActionBtn
{...buttonStyle} {...buttonStyle}
@@ -211,7 +220,6 @@ const MenuBar = (props: MenuBarProps) => {
clickHandler={onSettingsOpen} clickHandler={onSettingsOpen}
tooltip='Settings' tooltip='Settings'
aria-label='Settings' aria-label='Settings'
size='sm'
/> />
<div className={style.gap} /> <div className={style.gap} />
<TooltipActionBtn <TooltipActionBtn
@@ -226,12 +234,11 @@ const MenuBar = (props: MenuBarProps) => {
<div className={style.gap} /> <div className={style.gap} />
<TooltipActionBtn <TooltipActionBtn
{...buttonStyle} {...buttonStyle}
className={isSettingsOpen ? style.open : ''} className={cx([isSettingsOpen ? style.open : null, style.bottom])}
icon={<IoSettingsOutline />} icon={<IoSettingsOutline />}
clickHandler={() => openSettings()} clickHandler={() => openSettings()}
tooltip='About' tooltip='About'
aria-label='About' aria-label='About'
size='sm'
/> />
</div> </div>
); );
+23 -29
View File
@@ -1,17 +1,15 @@
import { memo, useCallback } from 'react'; import { memo, ReactNode, useCallback } from 'react';
import { Button, Menu, MenuButton, MenuDivider, MenuItem, MenuList } from '@chakra-ui/react'; import { Menu, MenuDivider, MenuItem, MenuList } from '@chakra-ui/react';
import { FiMinusCircle } from '@react-icons/all-files/fi/FiMinusCircle';
import { FiTrash2 } from '@react-icons/all-files/fi/FiTrash2';
import { IoAdd } from '@react-icons/all-files/io5/IoAdd'; import { IoAdd } from '@react-icons/all-files/io5/IoAdd';
import { IoRemoveCircleOutline } from '@react-icons/all-files/io5/IoRemoveCircleOutline';
import { IoTimerOutline } from '@react-icons/all-files/io5/IoTimerOutline'; import { IoTimerOutline } from '@react-icons/all-files/io5/IoTimerOutline';
import { IoTrashOutline } from '@react-icons/all-files/io5/IoTrashOutline';
import { SupportedEvent } from 'ontime-types'; import { SupportedEvent } from 'ontime-types';
import { useEventAction } from '../../common/hooks/useEventAction'; import { useEventAction } from '../../common/hooks/useEventAction';
import { useEventSelection } from '../../features/rundown/useEventSelection'; import { useEventSelection } from '../../features/rundown/useEventSelection';
import style from './RundownMenu.module.scss'; const RundownMenu = ({ children }: { children: ReactNode }) => {
const RundownMenu = () => {
const { clearSelectedEvents } = useEventSelection(); const { clearSelectedEvents } = useEventSelection();
const { addEvent, deleteAllEvents } = useEventAction(); const { addEvent, deleteAllEvents } = useEventAction();
@@ -35,28 +33,24 @@ const RundownMenu = () => {
}, [deleteAllEvents, clearSelectedEvents]); }, [deleteAllEvents, clearSelectedEvents]);
return ( return (
<div className={style.headerButtons}> <Menu isLazy lazyBehavior='unmount' variant='ontime-on-dark' placement='right-start'>
<Menu isLazy lazyBehavior='unmount' variant='ontime-on-dark'> {children}
<MenuButton as={Button} leftIcon={<IoAdd />} size='sm' variant='ontime-subtle'> <MenuList>
Event... <MenuItem icon={<IoAdd />} onClick={newEvent}>
</MenuButton> Add event at start
<MenuList> </MenuItem>
<MenuItem icon={<IoAdd />} onClick={newEvent}> <MenuItem icon={<IoTimerOutline />} onClick={newDelay}>
Add event at start Add delay at start
</MenuItem> </MenuItem>
<MenuItem icon={<IoTimerOutline />} onClick={newDelay}> <MenuItem icon={<IoRemoveCircleOutline />} onClick={newBlock}>
Add delay at start Add block at start
</MenuItem> </MenuItem>
<MenuItem icon={<FiMinusCircle />} onClick={newBlock}> <MenuDivider />
Add block at start <MenuItem icon={<IoTrashOutline />} onClick={deleteAll} color='#D20300'>
</MenuItem> Delete all events
<MenuDivider /> </MenuItem>
<MenuItem icon={<FiTrash2 />} onClick={deleteAll} color='#D20300'> </MenuList>
Delete all events </Menu>
</MenuItem>
</MenuList>
</Menu>
</div>
); );
}; };
@@ -7,7 +7,6 @@ import { HttpSettings, TimerLifeCycle } from 'ontime-types';
import { useEmitLog } from '../../../../common/stores/logger'; import { useEmitLog } from '../../../../common/stores/logger';
import { startsWithHttp } from '../../../../common/utils/regex'; import { startsWithHttp } from '../../../../common/utils/regex';
import collapseStyles from '../../../../common/components/collapse-bar/CollapseBar.module.scss';
import styles from '../../Modal.module.scss'; import styles from '../../Modal.module.scss';
interface SubscriptionRowProps { interface SubscriptionRowProps {
@@ -52,7 +51,7 @@ export default function SubscriptionRow(props: SubscriptionRowProps) {
<span className={`${styles.sectionTitle} ${styles.main}`}>{sectionTitle}</span> <span className={`${styles.sectionTitle} ${styles.main}`}>{sectionTitle}</span>
{visible && <span className={styles.sectionSubtitle}>{subtitle}</span>} {visible && <span className={styles.sectionSubtitle}>{subtitle}</span>}
</div> </div>
<FiChevronUp className={visible ? collapseStyles.moreCollapsed : collapseStyles.moreExpanded} /> <FiChevronUp />
</div> </div>
{visible && ( {visible && (
<> <>
@@ -6,7 +6,6 @@ import type { OscSubscription, TimerLifeCycle } from 'ontime-types';
import { useEmitLog } from '../../../../common/stores/logger'; import { useEmitLog } from '../../../../common/stores/logger';
import collapseStyles from '../../../../common/components/collapse-bar/CollapseBar.module.scss';
import styles from '../../Modal.module.scss'; import styles from '../../Modal.module.scss';
interface OscSubscriptionRowProps { interface OscSubscriptionRowProps {
@@ -51,7 +50,7 @@ export default function OscSubscriptionRow(props: OscSubscriptionRowProps) {
<span className={`${styles.sectionTitle} ${styles.main}`}>{sectionTitle}</span> <span className={`${styles.sectionTitle} ${styles.main}`}>{sectionTitle}</span>
{visible && <span className={styles.sectionSubtitle}>{subtitle}</span>} {visible && <span className={styles.sectionSubtitle}>{subtitle}</span>}
</div> </div>
<FiChevronUp className={visible ? collapseStyles.moreCollapsed : collapseStyles.moreExpanded} /> <FiChevronUp />
</div> </div>
{visible && ( {visible && (
<> <>
@@ -1,7 +1,6 @@
.rundownWrapper { .rundownWrapper {
height: calc(100% - 1.5rem); height: calc(100% - 1.5rem);
overflow: hidden; width: 100%;
padding-top: 0.5rem;
} }
.eventContainer { .eventContainer {
@@ -248,7 +248,6 @@ export default function Rundown(props: RundownProps) {
previousEventId={previousEventId} previousEventId={previousEventId}
playback={isSelected ? featureData.playback : undefined} playback={isSelected ? featureData.playback : undefined}
isRolling={featureData.playback === Playback.Roll} isRolling={featureData.playback === Playback.Roll}
disableEdit={appMode === AppMode.Run}
/> />
</div> </div>
</div> </div>
@@ -37,23 +37,11 @@ interface RundownEntryProps {
previousEventId?: string; previousEventId?: string;
playback?: Playback; // we only care about this if this event is playing playback?: Playback; // we only care about this if this event is playing
isRolling: boolean; // we need to know even if not related to this event isRolling: boolean; // we need to know even if not related to this event
disableEdit: boolean;
} }
export default function RundownEntry(props: RundownEntryProps) { export default function RundownEntry(props: RundownEntryProps) {
const { const { isPast, data, selected, hasCursor, next, previousEnd, previousEventId, playback, isRolling, eventIndex } =
isPast, props;
data,
selected,
hasCursor,
next,
previousEnd,
previousEventId,
playback,
isRolling,
disableEdit,
eventIndex,
} = props;
const { emitError } = useEmitLog(); const { emitError } = useEmitLog();
const { addEvent, updateEvent, batchUpdateEvents, deleteEvent, swapEvents } = useEventAction(); const { addEvent, updateEvent, batchUpdateEvents, deleteEvent, swapEvents } = useEventAction();
const { cursor } = useAppMode(); const { cursor } = useAppMode();
@@ -173,7 +161,6 @@ export default function RundownEntry(props: RundownEntryProps) {
playback={playback} playback={playback}
isRolling={isRolling} isRolling={isRolling}
actionHandler={actionHandler} actionHandler={actionHandler}
disableEdit={disableEdit}
/> />
); );
} else if (data.type === SupportedEvent.Block) { } else if (data.type === SupportedEvent.Block) {
@@ -0,0 +1,40 @@
@use '../editors//EditorMixin' as editor;
$rundown-width: 44.5rem;
$editor-width: 25rem;
.rundownExport {
grid-area: rundown;
max-height: 100%;
height: 100%;
}
.rundown {
display: flex;
max-height: 100%;
height: 100%;
}
.corner {
@include editor.corner;
display: inline;
z-index: 2;
}
.list {
@include editor.panel;
height: inherit;
box-shadow: $box-shadow-right;
min-width: $rundown-width;
}
.side {
margin: 2rem 0;
padding: 1rem;
background-color: $gray-1325;
border-radius: 0 8px 8px 0;
min-width: $editor-width;
flex: 1;
max-height: 100%;
overflow: auto;
}
@@ -7,21 +7,25 @@ import { handleLinks } from '../../common/utils/linkUtils';
import EventEditor from './event-editor/EventEditor'; import EventEditor from './event-editor/EventEditor';
import RundownWrapper from './RundownWrapper'; import RundownWrapper from './RundownWrapper';
import style from './RundownWrapper.module.scss'; import style from './RundownExport.module.scss';
const RundownExport = () => { const RundownExport = () => {
const isExtracted = window.location.pathname.includes('/rundown'); const isExtracted = window.location.pathname.includes('/rundown');
return ( return (
<div className={style.rundownExport} data-testid='panel-rundown'> <div className={style.rundownExport} data-testid='panel-rundown'>
{!isExtracted && <IoArrowUp className={style.corner} onClick={(event) => handleLinks(event, 'rundown')} />}
<div className={style.rundown}> <div className={style.rundown}>
<ErrorBoundary> <div className={style.list}>
<RundownWrapper /> <ErrorBoundary>
</ErrorBoundary> {!isExtracted && <IoArrowUp className={style.corner} onClick={(event) => handleLinks(event, 'rundown')} />}
<ErrorBoundary> <RundownWrapper />
<EventEditor /> </ErrorBoundary>
</ErrorBoundary> </div>
<div className={style.side}>
<ErrorBoundary>
<EventEditor />
</ErrorBoundary>
</div>
</div> </div>
</div> </div>
); );
@@ -1,21 +0,0 @@
@use '../editors//EditorMixin' as editor;
$rundown-width: 44.5rem;
$editor-width: 25rem;
.rundownExport {
grid-area: rundown;
min-width: calc($rundown-width + $editor-width);
max-height: 100%;
position: relative;
}
.rundown {
@include editor.panel;
}
.corner {
@include editor.corner;
display: inline;
z-index: 2;
}
@@ -1,6 +1,5 @@
import Empty from '../../common/components/state/Empty'; import Empty from '../../common/components/state/Empty';
import useRundown from '../../common/hooks-query/useRundown'; import useRundown from '../../common/hooks-query/useRundown';
import RundownMenu from '../menu/RundownMenu';
import Rundown from './Rundown'; import Rundown from './Rundown';
@@ -10,11 +9,8 @@ export default function RundownWrapper() {
const { data, status } = useRundown(); const { data, status } = useRundown();
return ( return (
<div> <div className={styles.rundownWrapper}>
<RundownMenu /> {status === 'success' && data ? <Rundown entries={data} /> : <Empty text='Connecting to server' />}
<div className={styles.rundownWrapper}>
{status === 'success' && data ? <Rundown entries={data} /> : <Empty text='Connecting to server' />}
</div>
</div> </div>
); );
} }
@@ -66,7 +66,6 @@ interface EventBlockProps {
value: unknown; value: unknown;
}, },
) => void; ) => void;
disableEdit: boolean;
} }
export default function EventBlock(props: EventBlockProps) { export default function EventBlock(props: EventBlockProps) {
@@ -93,7 +92,6 @@ export default function EventBlock(props: EventBlockProps) {
playback, playback,
isRolling, isRolling,
actionHandler, actionHandler,
disableEdit,
} = props; } = props;
const { selectedEventId, setSelectedEventId, clearSelectedEventId } = useEventIdSwapping(); const { selectedEventId, setSelectedEventId, clearSelectedEventId } = useEventIdSwapping();
const { selectedEvents, setSelectedEvents } = useEventSelection(); const { selectedEvents, setSelectedEvents } = useEventSelection();
@@ -280,7 +278,6 @@ export default function EventBlock(props: EventBlockProps) {
playback={playback} playback={playback}
isRolling={isRolling} isRolling={isRolling}
actionHandler={actionHandler} actionHandler={actionHandler}
disableEdit={disableEdit}
/> />
)} )}
</div> </div>
@@ -1,9 +1,8 @@
import { memo, MouseEvent, useCallback, useEffect, useState } from 'react'; import { memo, useEffect, useState } from 'react';
import { Tooltip } from '@chakra-ui/react'; import { Tooltip } from '@chakra-ui/react';
import { BiArrowToBottom } from '@react-icons/all-files/bi/BiArrowToBottom'; import { BiArrowToBottom } from '@react-icons/all-files/bi/BiArrowToBottom';
import { IoArrowDown } from '@react-icons/all-files/io5/IoArrowDown'; import { IoArrowDown } from '@react-icons/all-files/io5/IoArrowDown';
import { IoArrowUp } from '@react-icons/all-files/io5/IoArrowUp'; import { IoArrowUp } from '@react-icons/all-files/io5/IoArrowUp';
import { IoOptions } from '@react-icons/all-files/io5/IoOptions';
import { IoPeople } from '@react-icons/all-files/io5/IoPeople'; import { IoPeople } from '@react-icons/all-files/io5/IoPeople';
import { IoPlay } from '@react-icons/all-files/io5/IoPlay'; import { IoPlay } from '@react-icons/all-files/io5/IoPlay';
import { IoPlayForward } from '@react-icons/all-files/io5/IoPlayForward'; import { IoPlayForward } from '@react-icons/all-files/io5/IoPlayForward';
@@ -12,11 +11,9 @@ import { IoStop } from '@react-icons/all-files/io5/IoStop';
import { IoTime } from '@react-icons/all-files/io5/IoTime'; import { IoTime } from '@react-icons/all-files/io5/IoTime';
import { EndAction, Playback, TimerType } from 'ontime-types'; import { EndAction, Playback, TimerType } from 'ontime-types';
import TooltipActionBtn from '../../../common/components/buttons/TooltipActionBtn';
import { tooltipDelayMid } from '../../../ontimeConfig'; import { tooltipDelayMid } from '../../../ontimeConfig';
import EditableBlockTitle from '../common/EditableBlockTitle'; import EditableBlockTitle from '../common/EditableBlockTitle';
import { EventItemActions } from '../RundownEntry'; import { EventItemActions } from '../RundownEntry';
import { useEventSelection } from '../useEventSelection';
import BlockActionMenu from './composite/BlockActionMenu'; import BlockActionMenu from './composite/BlockActionMenu';
import EventBlockPlayback from './composite/EventBlockPlayback'; import EventBlockPlayback from './composite/EventBlockPlayback';
@@ -25,10 +22,6 @@ import EventBlockTimers from './composite/EventBlockTimers';
import style from './EventBlock.module.scss'; import style from './EventBlock.module.scss';
const blockBtnStyle = {
size: 'sm',
};
const tooltipProps = { const tooltipProps = {
openDelay: tooltipDelayMid, openDelay: tooltipDelayMid,
}; };
@@ -51,7 +44,6 @@ interface EventBlockInnerProps {
playback?: Playback; playback?: Playback;
isRolling: boolean; isRolling: boolean;
actionHandler: (action: EventItemActions, payload?: any) => void; actionHandler: (action: EventItemActions, payload?: any) => void;
disableEdit: boolean;
} }
const EventBlockInner = (props: EventBlockInnerProps) => { const EventBlockInner = (props: EventBlockInnerProps) => {
@@ -72,29 +64,14 @@ const EventBlockInner = (props: EventBlockInnerProps) => {
playback, playback,
isRolling, isRolling,
actionHandler, actionHandler,
disableEdit,
} = props; } = props;
const [renderInner, setRenderInner] = useState(false); const [renderInner, setRenderInner] = useState(false);
const { clearSelectedEvents, selectedEvents } = useEventSelection();
const isOpen = selectedEvents.size === 1 && selectedEvents.has(eventId);
useEffect(() => { useEffect(() => {
setRenderInner(true); setRenderInner(true);
}, []); }, []);
//TODO: fix this
const toggleOpenEvent = useCallback(
(_event: MouseEvent) => {
// if (isOpen) {
// event.stopPropagation();
// clearSelectedEvents();
// }
},
[clearSelectedEvents, isOpen],
);
const eventIsPlaying = playback === Playback.Play; const eventIsPlaying = playback === Playback.Play;
const eventIsPaused = playback === Playback.Pause; const eventIsPaused = playback === Playback.Pause;
@@ -149,19 +126,6 @@ const EventBlockInner = (props: EventBlockInnerProps) => {
</div> </div>
</div> </div>
<div className={style.eventActions}> <div className={style.eventActions}>
<TooltipActionBtn
{...blockBtnStyle}
variant='ontime-subtle-white'
size='sm'
icon={<IoOptions />}
clickHandler={toggleOpenEvent}
tooltip='Event options'
aria-label='Event options'
tabIndex={-1}
backgroundColor={isOpen ? '#2B5ABC' : undefined}
color={isOpen ? 'white' : '#f6f6f6'}
isDisabled={disableEdit}
/>
<BlockActionMenu showClone enableDelete={!selected} actionHandler={actionHandler} /> <BlockActionMenu showClone enableDelete={!selected} actionHandler={actionHandler} />
</div> </div>
</> </>
@@ -1,20 +1,10 @@
// TODO: unify with RundownWrapper.module.scss
$editor-width: 25rem;
.eventEditor { .eventEditor {
min-width: $editor-width;
background-color: $gray-1350;
max-height: 100%; max-height: 100%;
overflow-y: auto; overflow-y: auto;
flex: 1; flex: 1;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 0.5rem;
}
.header {
padding: 1rem;
} }
.content { .content {
@@ -28,19 +18,23 @@ $editor-width: 25rem;
} }
.footer { .footer {
padding: 1rem; border-top: 1px solid $white-10;
padding-top: 1rem;
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
} }
.timeSettings { .timeSettings {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 0.5rem; gap: 1rem;
} }
.column { .column {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 0.5rem; gap: 1rem;
} }
.inputLabel { .inputLabel {
@@ -74,11 +68,5 @@ $editor-width: 25rem;
display: grid; display: grid;
grid-template-columns: 1fr 1fr; grid-template-columns: 1fr 1fr;
column-gap: 1.5rem; column-gap: 1.5rem;
row-gap: 0.5rem; row-gap: 1rem;
}
.eventActions {
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
} }
@@ -50,6 +50,8 @@ export default function EventEditor() {
if (event && isOntimeEvent(event)) { if (event && isOntimeEvent(event)) {
setEvent(event); setEvent(event);
} else {
setEvent(null);
} }
}, [data, selectedEvents]); }, [data, selectedEvents]);
@@ -61,7 +63,11 @@ export default function EventEditor() {
); );
if (!event) { if (!event) {
return <span data-testid='editor-container'>Loading...</span>; return (
<div className={style.eventEditor} data-testid='editor-container'>
Select an event to edit
</div>
);
} }
// Compositing user fields by hand // Compositing user fields by hand
@@ -81,7 +87,6 @@ export default function EventEditor() {
return ( return (
<div className={style.eventEditor} data-testid='editor-container'> <div className={style.eventEditor} data-testid='editor-container'>
<div>HEADER ACTIONS?</div>
<div className={style.content}> <div className={style.content}>
<EventEditorTimes <EventEditorTimes
key={`${event.id}-times`} key={`${event.id}-times`}
@@ -109,7 +114,7 @@ export default function EventEditor() {
/> />
<EventEditorUser key={`${event.id}-user`} userFields={userFields} handleSubmit={handleSubmit} /> <EventEditorUser key={`${event.id}-user`} userFields={userFields} handleSubmit={handleSubmit} />
</div> </div>
<div className={style.eventActions}> <div className={style.footer}>
<CopyTag label='OSC trigger by id'>{`/ontime/load/id "${event.id}"`}</CopyTag> <CopyTag label='OSC trigger by id'>{`/ontime/load/id "${event.id}"`}</CopyTag>
<CopyTag label='OSC trigger by cue'>{`/ontime/load/cue "${event.cue}"`}</CopyTag> <CopyTag label='OSC trigger by cue'>{`/ontime/load/cue "${event.cue}"`}</CopyTag>
</div> </div>
+1
View File
@@ -38,6 +38,7 @@ option {
/* width */ /* width */
::-webkit-scrollbar { ::-webkit-scrollbar {
width: 6px; width: 6px;
height: 6px;
} }
/* Track */ /* Track */
@@ -28,6 +28,7 @@ $gray-1100: #2d2d2d;
$gray-1200: #262626; $gray-1200: #262626;
$gray-1250: #202020; $gray-1250: #202020;
$gray-1300: #1a1a1a; $gray-1300: #1a1a1a;
$gray-1325: #151515;
$gray-1350: #101010; $gray-1350: #101010;
$pure-white: #fff; $pure-white: #fff;
$ui-white: $gray-50; $ui-white: $gray-50;
+1
View File
@@ -36,6 +36,7 @@ $bg-container-onlight: $gray-100;
$box-shadow-l1: rgba(0, 0, 0, 0.15) 0 3px 3px 0; $box-shadow-l1: rgba(0, 0, 0, 0.15) 0 3px 3px 0;
$box-shadow-l2: rgba(0, 0, 0, 0.15) 0 3px 3px 0; $box-shadow-l2: rgba(0, 0, 0, 0.15) 0 3px 3px 0;
$box-shadow-right: rgba(0, 0, 0, 0.15) 3px 0 3px 0;
$large-bottom-drawer-shadow: rgba(0, 0, 0, 0.35) 0 3px 6px 6px; $large-bottom-drawer-shadow: rgba(0, 0, 0, 0.35) 0 3px 6px 6px;
$large-top-drawer-shadow: rgba(0, 0, 0, 0.35) 0 1px 6px 3px; $large-top-drawer-shadow: rgba(0, 0, 0, 0.35) 0 1px 6px 3px;
+1 -1
View File
@@ -54,7 +54,7 @@ export class TimerService {
* @param {number} amount * @param {number} amount
*/ */
addTime(amount: number) { addTime(amount: number) {
if (state.eventNow === null) { if (state.timer.current === null) {
return; return;
} }
stateMutations.timer.addTime(amount); stateMutations.timer.addTime(amount);
@@ -340,7 +340,30 @@ describe('getCurrent()', () => {
const current = getCurrent(state); const current = getCurrent(state);
expect(current).toBe(35); expect(current).toBe(35);
}); });
describe('on timers of type time-to-end', () => { describe('on timers of type time-to-end', () => {
it('current time is the time to end even if it hasnt started, this is weird, but by design', () => {
const state = {
eventNow: {
timeEnd: 100,
timerType: TimerType.TimeToEnd,
},
clock: 30,
timer: {
addedTime: 0,
duration: 100,
startedAt: null,
finishedAt: null,
},
_timer: {
pausedAt: null,
},
} as TState;
const current = getCurrent(state);
expect(current).toBe(70);
});
it('current time is the time to end', () => { it('current time is the time to end', () => {
const state = { const state = {
eventNow: { eventNow: {
@@ -1,4 +1,12 @@
import { LogOrigin, OntimeBaseEvent, OntimeBlock, OntimeDelay, OntimeEvent, SupportedEvent, isOntimeEvent } from 'ontime-types'; import {
LogOrigin,
OntimeBaseEvent,
OntimeBlock,
OntimeDelay,
OntimeEvent,
SupportedEvent,
isOntimeEvent,
} from 'ontime-types';
import { generateId, getCueCandidate } from 'ontime-utils'; import { generateId, getCueCandidate } from 'ontime-utils';
import { DataProvider } from '../../classes/data-provider/DataProvider.js'; import { DataProvider } from '../../classes/data-provider/DataProvider.js';
import { block as blockDef, delay as delayDef } from '../../models/eventsDefinition.js'; import { block as blockDef, delay as delayDef } from '../../models/eventsDefinition.js';
+1 -4
View File
@@ -315,10 +315,6 @@ export const stateMutations = {
addTime(amount: number) { addTime(amount: number) {
mutate((state) => { mutate((state) => {
// TODO: what kind of validation go here or in the consumer? // TODO: what kind of validation go here or in the consumer?
if (state.timer.startedAt === null) {
return;
}
state.timer.addedTime += amount; state.timer.addedTime += amount;
state.timer.expectedFinish += amount; state.timer.expectedFinish += amount;
state.timer.current += amount; state.timer.current += amount;
@@ -407,6 +403,7 @@ export const stateMutations = {
// TODO: can we simplify the didUpdate and shouldNotify // TODO: can we simplify the didUpdate and shouldNotify
_didUpdate = true; _didUpdate = true;
} }
return { return {
didUpdate: _didUpdate, didUpdate: _didUpdate,
doRoll: _doRoll, doRoll: _doRoll,
+1 -1
View File
@@ -4,7 +4,7 @@ const fileToUpload = 'e2e/tests/fixtures/test-db.json';
test('test project file upload', async ({ page }) => { test('test project file upload', async ({ page }) => {
await page.goto('http://localhost:4001/editor'); await page.goto('http://localhost:4001/editor');
await page.getByRole('button', { name: 'Event...' }).click(); await page.getByRole('button', { name: 'Rundown menu' }).click();
await page.getByRole('menuitem', { name: 'Delete all events' }).click(); await page.getByRole('menuitem', { name: 'Delete all events' }).click();
await page.getByRole('button', { name: 'Import project file' }).click(); await page.getByRole('button', { name: 'Import project file' }).click();
+6 -6
View File
@@ -4,9 +4,9 @@ test('delay blocks add time to events', async ({ page }) => {
await page.goto('http://localhost:4001/editor'); await page.goto('http://localhost:4001/editor');
// delete all events and add a new one // delete all events and add a new one
await page.getByRole('button', { name: 'Event...' }).click(); await page.getByRole('button', { name: 'Rundown menu' }).click();
await page.getByRole('menuitem', { name: 'Delete all events' }).click(); await page.getByRole('menuitem', { name: 'Delete all events' }).click();
await page.getByRole('button', { name: 'Event...' }).click(); await page.getByRole('button', { name: 'Rundown menu' }).click();
await page.getByRole('menuitem', { name: 'Add event at start' }).click(); await page.getByRole('menuitem', { name: 'Add event at start' }).click();
// add data to new event // add data to new event
@@ -18,7 +18,7 @@ test('delay blocks add time to events', async ({ page }) => {
await page.getByTestId('rundown').getByPlaceholder('End').press('Enter'); await page.getByTestId('rundown').getByPlaceholder('End').press('Enter');
// add delay block // add delay block
await page.getByRole('button', { name: 'Event...' }).click(); await page.getByRole('button', { name: 'Rundown menu' }).click();
await page.getByRole('menuitem', { name: 'Add delay at start' }).click(); await page.getByRole('menuitem', { name: 'Add delay at start' }).click();
// fill positive delay // fill positive delay
@@ -37,7 +37,7 @@ test('delay blocks add time to events', async ({ page }) => {
// add new delay // add new delay
await page.getByTestId('rundown').getByPlaceholder('Start').click(); await page.getByTestId('rundown').getByPlaceholder('Start').click();
await page.getByRole('button', { name: 'Event...' }).click(); await page.getByRole('button', { name: 'Rundown menu' }).click();
await page.getByRole('menuitem', { name: 'Add delay at start' }).click(); await page.getByRole('menuitem', { name: 'Add delay at start' }).click();
await page.getByTestId('delay-input').click(); await page.getByTestId('delay-input').click();
await page.getByTestId('delay-input').fill('10m'); await page.getByTestId('delay-input').fill('10m');
@@ -54,7 +54,7 @@ test('delays are show correctly', async ({ page }) => {
await page.goto('http://localhost:4001/editor'); await page.goto('http://localhost:4001/editor');
// add a test event // add a test event
await page.getByRole('button', { name: 'Event...' }).click(); await page.getByRole('button', { name: 'Rundown menu' }).click();
await page.getByRole('menuitem', { name: 'Delete all events' }).click(); await page.getByRole('menuitem', { name: 'Delete all events' }).click();
await page.getByRole('button', { name: 'Create Event' }).click(); await page.getByRole('button', { name: 'Create Event' }).click();
await page.getByTestId('time-input-timeStart').click(); await page.getByTestId('time-input-timeStart').click();
@@ -72,7 +72,7 @@ test('delays are show correctly', async ({ page }) => {
await page.getByRole('menuitem', { name: 'Toggle public' }).click(); await page.getByRole('menuitem', { name: 'Toggle public' }).click();
// add a delay // add a delay
await page.getByRole('button', { name: 'Event...' }).click(); await page.getByRole('button', { name: 'Rundown menu' }).click();
await page.getByRole('menuitem', { name: 'Add delay at start' }).click(); await page.getByRole('menuitem', { name: 'Add delay at start' }).click();
await page.getByTestId('delay-input').click(); await page.getByTestId('delay-input').click();
await page.getByTestId('delay-input').fill('1'); await page.getByTestId('delay-input').fill('1');
+1 -1
View File
@@ -5,7 +5,7 @@ test('CRUD operations on the rundown', async ({ page }) => {
// clear rundown // clear rundown
await page.getByRole('button', { name: 'Run mode' }).click(); await page.getByRole('button', { name: 'Run mode' }).click();
await page.getByRole('button', { name: 'Event...' }).click(); await page.getByRole('button', { name: 'Rundown menu' }).click();
await page.getByRole('menuitem', { name: 'Delete all events' }).click(); await page.getByRole('menuitem', { name: 'Delete all events' }).click();
// create event from the rundown empty button // create event from the rundown empty button
+2 -2
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: 'Run mode' }).click(); await page.getByRole('button', { name: 'Run mode' }).click();
await page.getByRole('button', { name: 'Event...' }).click(); await page.getByRole('button', { name: 'Rundown menu' }).click();
await page.getByRole('menuitem', { name: 'Delete all events' }).click(); await page.getByRole('menuitem', { name: 'Delete all events' }).click();
await page.getByRole('button', { name: 'Create Event' }).click(); await page.getByRole('button', { name: 'Create Event' }).click();
@@ -23,7 +23,7 @@ test('smoke test operator', async ({ page }) => {
await page.getByTestId('entry-3').getByTestId('time-input-durationOverride').fill('1m'); await page.getByTestId('entry-3').getByTestId('time-input-durationOverride').fill('1m');
await page.getByTestId('entry-3').getByTestId('time-input-durationOverride').press('Enter'); await page.getByTestId('entry-3').getByTestId('time-input-durationOverride').press('Enter');
await page.getByRole('button', { name: 'Event...' }).click(); await page.getByRole('button', { name: 'Rundown menu' }).click();
await page.getByRole('menuitem', { name: 'Add block at start' }).click(); await page.getByRole('menuitem', { name: 'Add block at start' }).click();
await page.getByTestId('quick-add-block').click(); await page.getByTestId('quick-add-block').click();