From 10852eecdde41854acfc212354e861359d7553fa Mon Sep 17 00:00:00 2001 From: Carlos Valente <34649812+cpvalente@users.noreply.github.com> Date: Tue, 2 Apr 2024 20:09:27 +0200 Subject: [PATCH] Rundown modes (#864) * refactor: simplify settings * refactor: quick add around selection * refactor: no action menu in delay block * refactor: no action menu in event block * style: context menu dark * refactor: context menu actions --- .../src/common/stores/editorSettings.ts | 11 -- .../interface-panel/EditorSettingsForm.tsx | 124 +++++++++++------- apps/client/src/features/rundown/Rundown.tsx | 16 ++- .../src/features/rundown/RundownEntry.tsx | 32 ++++- .../rundown/block-block/BlockBlock.tsx | 35 ++--- .../rundown/delay-block/DelayBlock.tsx | 16 +-- .../event-block/EventBlock.module.scss | 9 +- .../rundown/event-block/EventBlock.tsx | 53 ++++---- .../rundown/event-block/EventBlockInner.tsx | 7 - .../event-block/composite/BlockActionMenu.tsx | 64 --------- .../quick-add-block/QuickAddBlock.module.scss | 2 +- .../rundown/quick-add-block/QuickAddBlock.tsx | 28 ++-- .../rundown/rundown-header/RundownHeader.tsx | 20 +-- .../rundown/rundown-header/RundownMenu.tsx | 59 +++------ apps/client/src/theme/ontimeCheckbox.ts | 14 +- apps/client/src/theme/ontimeMenu.ts | 22 +++- apps/client/src/theme/theme.ts | 4 +- e2e/tests/000-upload-showfile.spec.ts | 4 +- e2e/tests/features/203-delay-block.spec.ts | 19 ++- e2e/tests/features/204-editor-crud.spec.ts | 21 ++- e2e/tests/features/205-operator.spec.ts | 43 +++--- .../features/301-spreadsheet-import.spec.ts | 4 +- 22 files changed, 276 insertions(+), 331 deletions(-) delete mode 100644 apps/client/src/features/rundown/event-block/composite/BlockActionMenu.tsx diff --git a/apps/client/src/common/stores/editorSettings.ts b/apps/client/src/common/stores/editorSettings.ts index b5e8dafd4..0a74311f0 100644 --- a/apps/client/src/common/stores/editorSettings.ts +++ b/apps/client/src/common/stores/editorSettings.ts @@ -3,7 +3,6 @@ import { create } from 'zustand'; import { booleanFromLocalStorage } from '../utils/localStorage'; type EditorSettings = { - showQuickEntry: boolean; linkPrevious: boolean; defaultPublic: boolean; defaultDuration: string; @@ -12,14 +11,12 @@ type EditorSettings = { type EditorSettingsStore = { eventSettings: EditorSettings; setLocalEventSettings: (newState: EditorSettings) => void; - setShowQuickEntry: (showQuickEntry: boolean) => void; setLinkPrevious: (linkPrevious: boolean) => void; setDefaultPublic: (defaultPublic: boolean) => void; setDefaultDuration: (defaultDuration: string) => void; }; enum EditorSettingsKeys { - ShowQuickEntry = 'ontime-show-quick-entry', LinkPrevious = 'ontime-link-previous', DefaultPublic = 'ontime-default-public', DefaultDuration = 'ontime-default-duration', @@ -27,7 +24,6 @@ enum EditorSettingsKeys { export const useEditorSettings = create((set) => ({ eventSettings: { - showQuickEntry: booleanFromLocalStorage(EditorSettingsKeys.ShowQuickEntry, false), linkPrevious: booleanFromLocalStorage(EditorSettingsKeys.LinkPrevious, true), defaultPublic: booleanFromLocalStorage(EditorSettingsKeys.DefaultPublic, true), defaultDuration: localStorage.getItem(EditorSettingsKeys.DefaultDuration) ?? '00:10:00', @@ -35,18 +31,11 @@ export const useEditorSettings = create((set) => ({ setLocalEventSettings: (value) => set(() => { - localStorage.setItem(EditorSettingsKeys.ShowQuickEntry, String(value.showQuickEntry)); localStorage.setItem(EditorSettingsKeys.LinkPrevious, String(value.linkPrevious)); localStorage.setItem(EditorSettingsKeys.DefaultPublic, String(value.defaultPublic)); return { eventSettings: value }; }), - setShowQuickEntry: (showQuickEntry) => - set((state) => { - localStorage.setItem(EditorSettingsKeys.ShowQuickEntry, String(showQuickEntry)); - return { eventSettings: { ...state.eventSettings, showQuickEntry } }; - }), - setLinkPrevious: (linkPrevious) => set((state) => { localStorage.setItem(EditorSettingsKeys.LinkPrevious, String(linkPrevious)); diff --git a/apps/client/src/features/app-settings/panel/interface-panel/EditorSettingsForm.tsx b/apps/client/src/features/app-settings/panel/interface-panel/EditorSettingsForm.tsx index 29f7d1f3f..5130dd19f 100644 --- a/apps/client/src/features/app-settings/panel/interface-panel/EditorSettingsForm.tsx +++ b/apps/client/src/features/app-settings/panel/interface-panel/EditorSettingsForm.tsx @@ -7,7 +7,6 @@ import * as Panel from '../PanelUtils'; export default function EditorSettingsForm() { const eventSettings = useEditorSettings((state) => state.eventSettings); - const setShowQuickEntry = useEditorSettings((state) => state.setShowQuickEntry); const setLinkPrevious = useEditorSettings((state) => state.setLinkPrevious); const setDefaultPublic = useEditorSettings((state) => state.setDefaultPublic); const setDefaultDuration = useEditorSettings((state) => state.setDefaultDuration); @@ -19,53 +18,82 @@ export default function EditorSettingsForm() { Editor settings - - - - setShowQuickEntry(event.target.checked)} - /> - - - - setLinkPrevious(event.target.checked)} - /> - - - - - name='defaultDuration' - submitHandler={(_field, value) => setDefaultDuration(value)} - time={durationInMs} - placeholder='00:10:00' - /> - - - - setDefaultPublic(event.target.checked)} - /> - - + + Rundown options + + + + + name='defaultDuration' + submitHandler={(_field, value) => setDefaultDuration(value)} + time={durationInMs} + placeholder='00:10:00' + /> + + + + setLinkPrevious(event.target.checked)} + /> + + + + setDefaultPublic(event.target.checked)} + /> + + + + + Play mode + + + + + + + + + + + + + Edit mode + + + + + + + + + + + ); diff --git a/apps/client/src/features/rundown/Rundown.tsx b/apps/client/src/features/rundown/Rundown.tsx index 4c7df353c..3d059d901 100644 --- a/apps/client/src/features/rundown/Rundown.tsx +++ b/apps/client/src/features/rundown/Rundown.tsx @@ -39,7 +39,6 @@ export default function Rundown({ data }: RundownProps) { const eventSettings = useEditorSettings((state) => state.eventSettings); const defaultPublic = eventSettings.defaultPublic; const linkPrevious = eventSettings.linkPrevious; - const showQuickEntry = eventSettings.showQuickEntry; // cursor const { cursor, mode: appMode, setCursor } = useAppMode(); @@ -219,6 +218,8 @@ export default function Rundown({ data }: RundownProps) { // all events before the current selected are in the past let isPast = Boolean(featureData?.selectedEventId); + const isEditMode = appMode === AppMode.Edit; + return (
@@ -248,6 +249,7 @@ export default function Rundown({ data }: RundownProps) { thisId = eventId; } } + const isFirst = index === 0; const isLast = index === order.length - 1; const isLoaded = featureData?.selectedEventId === event.id; const isNext = featureData?.nextEventId === event.id; @@ -258,6 +260,14 @@ export default function Rundown({ data }: RundownProps) { return ( + {isEditMode && (hasCursor || isFirst) && ( + + )}
{isOntimeEvent(event) &&
{eventIndex}
}
@@ -277,9 +287,9 @@ export default function Rundown({ data }: RundownProps) { />
- {((showQuickEntry && hasCursor) || isLast) && ( + {isEditMode && (hasCursor || isLast) && ( ); } else if (data.type === SupportedEvent.Block) { - return ; + return actionHandler('delete')} />; } else if (data.type === SupportedEvent.Delay) { - return ; + return ; } return null; } diff --git a/apps/client/src/features/rundown/block-block/BlockBlock.tsx b/apps/client/src/features/rundown/block-block/BlockBlock.tsx index c1248664e..16dc3da4f 100644 --- a/apps/client/src/features/rundown/block-block/BlockBlock.tsx +++ b/apps/client/src/features/rundown/block-block/BlockBlock.tsx @@ -1,32 +1,24 @@ -import { useEffect, useRef } from 'react'; +import { useRef } from 'react'; +import { IconButton } from '@chakra-ui/react'; import { useSortable } from '@dnd-kit/sortable'; import { CSS } from '@dnd-kit/utilities'; import { IoReorderTwo } from '@react-icons/all-files/io5/IoReorderTwo'; -import { OntimeBlock, OntimeEvent } from 'ontime-types'; +import { IoTrash } from '@react-icons/all-files/io5/IoTrash'; +import { OntimeBlock } from 'ontime-types'; import { cx } from '../../../common/utils/styleUtils'; import EditableBlockTitle from '../common/EditableBlockTitle'; -import BlockActionMenu from '../event-block/composite/BlockActionMenu'; -import type { EventItemActions } from '../RundownEntry'; import style from './BlockBlock.module.scss'; interface BlockBlockProps { data: OntimeBlock; hasCursor: boolean; - actionHandler: ( - action: EventItemActions, - payload?: - | number - | { - field: keyof Omit | 'durationOverride'; - value: unknown; - }, - ) => void; + onDelete: () => void; } export default function BlockBlock(props: BlockBlockProps) { - const { data, hasCursor, actionHandler } = props; + const { data, hasCursor, onDelete } = props; const handleRef = useRef(null); const { @@ -45,12 +37,6 @@ export default function BlockBlock(props: BlockBlockProps) { transition, }; - useEffect(() => { - if (hasCursor) { - handleRef?.current?.focus(); - } - }, [hasCursor]); - const blockClasses = cx([style.block, hasCursor ? style.hasCursor : null]); return ( @@ -59,7 +45,14 @@ export default function BlockBlock(props: BlockBlockProps) { - + } + variant='ontime-subtle' + color='#FA5656' + onClick={onDelete} + />
); } diff --git a/apps/client/src/features/rundown/delay-block/DelayBlock.tsx b/apps/client/src/features/rundown/delay-block/DelayBlock.tsx index 136035375..e99f2a491 100644 --- a/apps/client/src/features/rundown/delay-block/DelayBlock.tsx +++ b/apps/client/src/features/rundown/delay-block/DelayBlock.tsx @@ -5,32 +5,21 @@ import { CSS } from '@dnd-kit/utilities'; import { IoCheckmark } from '@react-icons/all-files/io5/IoCheckmark'; import { IoClose } from '@react-icons/all-files/io5/IoClose'; import { IoReorderTwo } from '@react-icons/all-files/io5/IoReorderTwo'; -import { OntimeDelay, OntimeEvent } from 'ontime-types'; +import { OntimeDelay } from 'ontime-types'; import DelayInput from '../../../common/components/input/delay-input/DelayInput'; import { useEventAction } from '../../../common/hooks/useEventAction'; import { cx } from '../../../common/utils/styleUtils'; -import BlockActionMenu from '../event-block/composite/BlockActionMenu'; -import type { EventItemActions } from '../RundownEntry'; import style from './DelayBlock.module.scss'; interface DelayBlockProps { data: OntimeDelay; hasCursor: boolean; - actionHandler: ( - action: EventItemActions, - payload?: - | number - | { - field: keyof Omit | 'durationOverride'; - value: unknown; - }, - ) => void; } export default function DelayBlock(props: DelayBlockProps) { - const { data, hasCursor, actionHandler } = props; + const { data, hasCursor } = props; const { applyDelay, deleteEvent } = useEventAction(); const handleRef = useRef(null); @@ -79,7 +68,6 @@ export default function DelayBlock(props: DelayBlockProps) { - ); diff --git a/apps/client/src/features/rundown/event-block/EventBlock.module.scss b/apps/client/src/features/rundown/event-block/EventBlock.module.scss index 8f66bff92..2328b52e0 100644 --- a/apps/client/src/features/rundown/event-block/EventBlock.module.scss +++ b/apps/client/src/features/rundown/event-block/EventBlock.module.scss @@ -9,7 +9,7 @@ $skip-opacity: 0.2; display: grid; grid-template-areas: 'binder ... ... ...' - 'binder pb-actions times actions' + 'binder pb-actions times ...' 'binder pb-actions title title' 'binder pb-actions estatus estatus' 'binder ... ... ...'; @@ -55,7 +55,6 @@ $skip-opacity: 0.2; outline: 1px solid $block-cursor-color; } - /* we stop the eventActions from having opacity to fix issue with dropdown drawing order */ &.past:not(.skip) { .timerNote, .statusElements, @@ -153,12 +152,6 @@ $skip-opacity: 0.2; } } -.eventActions { - grid-area: actions; - height: 100%; - text-align: end; -} - .progressBg { grid-area: progb; border-radius: 1px; diff --git a/apps/client/src/features/rundown/event-block/EventBlock.tsx b/apps/client/src/features/rundown/event-block/EventBlock.tsx index 67a84ab3e..907399acc 100644 --- a/apps/client/src/features/rundown/event-block/EventBlock.tsx +++ b/apps/client/src/features/rundown/event-block/EventBlock.tsx @@ -2,16 +2,18 @@ import { MouseEvent, useEffect, useLayoutEffect, useRef, useState } from 'react' import { useSortable } from '@dnd-kit/sortable'; import { CSS } from '@dnd-kit/utilities'; import { IoAdd } from '@react-icons/all-files/io5/IoAdd'; -import { IoCopyOutline } from '@react-icons/all-files/io5/IoCopyOutline'; +import { IoDuplicateOutline } from '@react-icons/all-files/io5/IoDuplicateOutline'; import { IoPeople } from '@react-icons/all-files/io5/IoPeople'; import { IoPeopleOutline } from '@react-icons/all-files/io5/IoPeopleOutline'; +import { IoRemoveCircleOutline } from '@react-icons/all-files/io5/IoRemoveCircleOutline'; import { IoReorderTwo } from '@react-icons/all-files/io5/IoReorderTwo'; import { IoSwapVertical } from '@react-icons/all-files/io5/IoSwapVertical'; +import { IoTimerOutline } from '@react-icons/all-files/io5/IoTimerOutline'; +import { IoTrash } from '@react-icons/all-files/io5/IoTrash'; import { EndAction, MaybeNumber, MaybeString, OntimeEvent, Playback, TimerType, TimeStrategy } from 'ontime-types'; import { useContextMenu } from '../../../common/hooks/useContextMenu'; import { useAppMode } from '../../../common/stores/appModeStore'; -import copyToClipboard from '../../../common/utils/copyToClipboard'; import { cx, getAccessibleColour } from '../../../common/utils/styleUtils'; import type { EventItemActions } from '../RundownEntry'; import { useEventIdSwapping } from '../useEventIdSwapping'; @@ -96,31 +98,25 @@ export default function EventBlock(props: EventBlockProps) { selectedEvents.size > 1 ? [ { - label: 'Visiblity', - group: [ - { - label: 'Make public', - icon: IoPeople, - onClick: () => - actionHandler('update', { - field: 'isPublic', - value: true, - }), - }, - { - label: 'Make private', - icon: IoPeopleOutline, - onClick: () => - actionHandler('update', { - field: 'isPublic', - value: false, - }), - }, - ], + label: 'Make public', + icon: IoPeople, + onClick: () => + actionHandler('update', { + field: 'isPublic', + value: true, + }), + }, + { + label: 'Make private', + icon: IoPeopleOutline, + onClick: () => + actionHandler('update', { + field: 'isPublic', + value: false, + }), }, ] : [ - { label: `Copy ID: ${eventId}`, icon: IoCopyOutline, onClick: () => copyToClipboard(eventId) }, { label: 'Toggle public', icon: IoPeopleOutline, @@ -145,6 +141,14 @@ export default function EventBlock(props: EventBlockProps) { }, isDisabled: selectedEventId == null || selectedEventId === eventId, }, + { withDivider: true, label: 'Clone', icon: IoDuplicateOutline, onClick: () => actionHandler('clone') }, + { withDivider: true, label: 'Event before', icon: IoAdd, onClick: () => actionHandler('event-before') }, + { label: 'Event after', icon: IoAdd, onClick: () => actionHandler('event') }, + { label: 'Block before', icon: IoRemoveCircleOutline, onClick: () => actionHandler('block-before') }, + { label: 'Block after', icon: IoRemoveCircleOutline, onClick: () => actionHandler('block') }, + { label: 'Delay before', icon: IoTimerOutline, onClick: () => actionHandler('delay-before') }, + { label: 'Delay after', icon: IoTimerOutline, onClick: () => actionHandler('delay') }, + { withDivider: true, label: 'Delete', icon: IoTrash, onClick: () => actionHandler('delete') }, ], ); @@ -281,7 +285,6 @@ export default function EventBlock(props: EventBlockProps) { loaded={loaded} playback={playback} isRolling={isRolling} - actionHandler={actionHandler} /> )} diff --git a/apps/client/src/features/rundown/event-block/EventBlockInner.tsx b/apps/client/src/features/rundown/event-block/EventBlockInner.tsx index e279ddba3..827fe3052 100644 --- a/apps/client/src/features/rundown/event-block/EventBlockInner.tsx +++ b/apps/client/src/features/rundown/event-block/EventBlockInner.tsx @@ -14,10 +14,8 @@ import { EndAction, MaybeString, Playback, TimerType, TimeStrategy } from 'ontim import { cx } from '../../../common/utils/styleUtils'; import { tooltipDelayMid } from '../../../ontimeConfig'; import EditableBlockTitle from '../common/EditableBlockTitle'; -import { EventItemActions } from '../RundownEntry'; import TimeInputFlow from '../time-input-flow/TimeInputFlow'; -import BlockActionMenu from './composite/BlockActionMenu'; import EventBlockPlayback from './composite/EventBlockPlayback'; import EventBlockProgressBar from './composite/EventBlockProgressBar'; @@ -46,7 +44,6 @@ interface EventBlockInnerProps { loaded: boolean; playback?: Playback; isRolling: boolean; - actionHandler: (action: EventItemActions, payload?: any) => void; } const EventBlockInner = (props: EventBlockInnerProps) => { @@ -68,7 +65,6 @@ const EventBlockInner = (props: EventBlockInnerProps) => { loaded, playback, isRolling, - actionHandler, } = props; const [renderInner, setRenderInner] = useState(false); @@ -139,9 +135,6 @@ const EventBlockInner = (props: EventBlockInnerProps) => { -
- -
); }; diff --git a/apps/client/src/features/rundown/event-block/composite/BlockActionMenu.tsx b/apps/client/src/features/rundown/event-block/composite/BlockActionMenu.tsx deleted file mode 100644 index 04452cfa3..000000000 --- a/apps/client/src/features/rundown/event-block/composite/BlockActionMenu.tsx +++ /dev/null @@ -1,64 +0,0 @@ -import { useCallback } from 'react'; -import { IconButton, Menu, MenuButton, MenuDivider, MenuItem, MenuList, Tooltip } from '@chakra-ui/react'; -import { IoAdd } from '@react-icons/all-files/io5/IoAdd'; -import { IoDuplicateOutline } from '@react-icons/all-files/io5/IoDuplicateOutline'; -import { IoEllipsisHorizontal } from '@react-icons/all-files/io5/IoEllipsisHorizontal'; -import { IoRemoveCircleOutline } from '@react-icons/all-files/io5/IoRemoveCircleOutline'; -import { IoTimerOutline } from '@react-icons/all-files/io5/IoTimerOutline'; -import { IoTrashBinSharp } from '@react-icons/all-files/io5/IoTrashBinSharp'; - -import { tooltipDelayMid } from '../../../../ontimeConfig'; -import { EventItemActions } from '../../RundownEntry'; - -interface BlockActionMenuProps { - enableDelete?: boolean; - showClone?: boolean; - actionHandler: (action: EventItemActions, payload?: any) => void; - className?: string; -} - -export default function BlockActionMenu(props: BlockActionMenuProps) { - const { enableDelete, showClone, actionHandler, className } = props; - - const handleAddEvent = useCallback(() => actionHandler('event'), [actionHandler]); - const handleAddDelay = useCallback(() => actionHandler('delay'), [actionHandler]); - const handleAddBlock = useCallback(() => actionHandler('block'), [actionHandler]); - const handleClone = useCallback(() => actionHandler('clone'), [actionHandler]); - const handleDelete = useCallback(() => actionHandler('delete'), [actionHandler]); - - return ( - - - } - tabIndex={-1} - variant='ontime-ghosted-white' - size='sm' - className={className} - /> - - - } onClick={handleAddEvent}> - Add Event after - - } onClick={handleAddDelay}> - Add Delay after - - } onClick={handleAddBlock}> - Add Block after - - {showClone && ( - } onClick={handleClone}> - Clone event - - )} - - } onClick={handleDelete} isDisabled={!enableDelete} color='#D20300'> - Delete - - - - ); -} diff --git a/apps/client/src/features/rundown/quick-add-block/QuickAddBlock.module.scss b/apps/client/src/features/rundown/quick-add-block/QuickAddBlock.module.scss index 4a95ad6f0..e5b146389 100644 --- a/apps/client/src/features/rundown/quick-add-block/QuickAddBlock.module.scss +++ b/apps/client/src/features/rundown/quick-add-block/QuickAddBlock.module.scss @@ -23,7 +23,7 @@ padding: 0 0.25rem; color: $label-gray; border-radius: 2px; - background-color: $black-10 + background-color: $black-10; } .options { diff --git a/apps/client/src/features/rundown/quick-add-block/QuickAddBlock.tsx b/apps/client/src/features/rundown/quick-add-block/QuickAddBlock.tsx index f85cf6e91..90f58a0fa 100644 --- a/apps/client/src/features/rundown/quick-add-block/QuickAddBlock.tsx +++ b/apps/client/src/features/rundown/quick-add-block/QuickAddBlock.tsx @@ -12,14 +12,14 @@ import { tooltipDelayMid } from '../../../ontimeConfig'; import style from './QuickAddBlock.module.scss'; interface QuickAddBlockProps { - showKbd: boolean; - previousEventId: string; + showKbd: 'above' | 'below' | 'none'; + previousEventId?: string; disableAddDelay?: boolean; disableAddBlock: boolean; } const QuickAddBlock = (props: QuickAddBlockProps) => { - const { showKbd, previousEventId, disableAddDelay = true, disableAddBlock } = props; + const { showKbd = 'none', previousEventId, disableAddDelay = true, disableAddBlock } = props; const { addEvent } = useEventAction(); const { emitError } = useEmitLog(); @@ -28,6 +28,8 @@ const QuickAddBlock = (props: QuickAddBlockProps) => { const { defaultPublic, linkPrevious } = useEditorSettings((state) => state.eventSettings); + const shortcutBase = showKbd === 'none' ? '' : `${deviceAlt} ${showKbd === 'above' ? '⇧' : ''}`; + const handleCreateEvent = useCallback( (eventType: SupportedEvent) => { switch (eventType) { @@ -70,6 +72,9 @@ const QuickAddBlock = (props: QuickAddBlockProps) => { [previousEventId, addEvent, emitError], ); + const canLinkPrevious = Boolean(previousEventId); + const shouldLinkPrevious = Boolean(linkPrevious) && canLinkPrevious; + return (
@@ -79,10 +84,9 @@ const QuickAddBlock = (props: QuickAddBlockProps) => { size='xs' variant='ontime-subtle-white' className={style.quickBtn} - data-testid='quick-add-event' leftIcon={} > - Event {showKbd && {`${deviceAlt} + E`}} + Event {shortcutBase && {`${shortcutBase} E`}} @@ -92,10 +96,9 @@ const QuickAddBlock = (props: QuickAddBlockProps) => { variant='ontime-subtle-white' disabled={disableAddDelay} className={style.quickBtn} - data-testid='quick-add-delay' leftIcon={} > - Delay {showKbd && {`${deviceAlt} + D`}} + Delay {shortcutBase && {`${shortcutBase} D`}} @@ -105,15 +108,20 @@ const QuickAddBlock = (props: QuickAddBlockProps) => { variant='ontime-subtle-white' disabled={disableAddBlock} className={style.quickBtn} - data-testid='quick-add-block' leftIcon={} > - Block {showKbd && {`${deviceAlt} + B`}} + Block {shortcutBase && {`${shortcutBase} B`}}
- + Link to previous diff --git a/apps/client/src/features/rundown/rundown-header/RundownHeader.tsx b/apps/client/src/features/rundown/rundown-header/RundownHeader.tsx index 6875c1fb0..f30635196 100644 --- a/apps/client/src/features/rundown/rundown-header/RundownHeader.tsx +++ b/apps/client/src/features/rundown/rundown-header/RundownHeader.tsx @@ -1,8 +1,6 @@ -import { Button, ButtonGroup, MenuButton } from '@chakra-ui/react'; -import { IoAdd } from '@react-icons/all-files/io5/IoAdd'; +import { ButtonGroup } from '@chakra-ui/react'; import { IoOptions } from '@react-icons/all-files/io5/IoOptions'; import { IoPlay } from '@react-icons/all-files/io5/IoPlay'; -import { IoSnowOutline } from '@react-icons/all-files/io5/IoSnowOutline'; import TooltipActionBtn from '../../../common/components/buttons/TooltipActionBtn'; import { AppMode, useAppMode } from '../../../common/stores/appModeStore'; @@ -16,20 +14,10 @@ export default function RundownHeader() { const setAppMode = useAppMode((state) => state.setMode); const setRunMode = () => setAppMode(AppMode.Run); const setEditMode = () => setAppMode(AppMode.Edit); - const setFreezeMode = () => setAppMode(AppMode.Freeze); return (
- } - clickHandler={setFreezeMode} - tooltip='Freeze rundown' - aria-label='Freeze rundown' - isDisabled - /> - - } aria-label='Rundown menu' variant='ontime-outlined'> - Rundown - - +
); } diff --git a/apps/client/src/features/rundown/rundown-header/RundownMenu.tsx b/apps/client/src/features/rundown/rundown-header/RundownMenu.tsx index ef409e266..12493d8b2 100644 --- a/apps/client/src/features/rundown/rundown-header/RundownMenu.tsx +++ b/apps/client/src/features/rundown/rundown-header/RundownMenu.tsx @@ -1,31 +1,16 @@ -import { memo, ReactNode, useCallback } from 'react'; -import { Menu, MenuDivider, MenuItem, MenuList } from '@chakra-ui/react'; -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 { IoTrashOutline } from '@react-icons/all-files/io5/IoTrashOutline'; -import { SupportedEvent } from 'ontime-types'; +import { useCallback } from 'react'; +import { Button } from '@chakra-ui/react'; +import { IoTrash } from '@react-icons/all-files/io5/IoTrash'; import { useEventAction } from '../../../common/hooks/useEventAction'; import { useAppMode } from '../../../common/stores/appModeStore'; import { useEventSelection } from '../useEventSelection'; -const RundownMenu = ({ children }: { children: ReactNode }) => { +export default function RundownMenu() { const clearSelectedEvents = useEventSelection((state) => state.clearSelectedEvents); const setCursor = useAppMode((state) => state.setCursor); - const { addEvent, deleteAllEvents } = useEventAction(); - - const newEvent = useCallback(() => { - addEvent({ type: SupportedEvent.Event }); - }, [addEvent]); - - const newBlock = useCallback(() => { - addEvent({ type: SupportedEvent.Block }); - }, [addEvent]); - - const newDelay = useCallback(() => { - addEvent({ type: SupportedEvent.Delay }); - }, [addEvent]); + const appMode = useAppMode((state) => state.mode); + const { deleteAllEvents } = useEventAction(); const deleteAll = useCallback(() => { deleteAllEvents(); @@ -34,25 +19,15 @@ const RundownMenu = ({ children }: { children: ReactNode }) => { }, [clearSelectedEvents, deleteAllEvents, setCursor]); return ( - - {children} - - } onClick={newEvent}> - Add event at start - - } onClick={newDelay}> - Add delay at start - - } onClick={newBlock}> - Add block at start - - - } onClick={deleteAll} color='#D20300'> - Delete all events - - - + ); -}; - -export default memo(RundownMenu); +} diff --git a/apps/client/src/theme/ontimeCheckbox.ts b/apps/client/src/theme/ontimeCheckbox.ts index 003937d95..345c928fb 100644 --- a/apps/client/src/theme/ontimeCheckbox.ts +++ b/apps/client/src/theme/ontimeCheckbox.ts @@ -3,12 +3,24 @@ export const ontimeCheckboxOnDark = { border: '1px', borderColor: '#2d2d2d', // $gray-1100 backgroundColor: '#2d2d2d', // $gray-1100 + _disabled: { + color: 'white', + borderColor: '#2d2d2d', // $gray-1100 + backgroundColor: '#2d2d2d', // $gray-1100 + opacity: 0.6, + }, _checked: { borderColor: '#3182ce', // $action-blue backgroundColor: '#3182ce', //$action-blue + _disabled: { + color: 'white', + borderColor: '#3182ce', // $action-blue + backgroundColor: '#3182ce', //$action-blue + opacity: 0.6, + }, }, _focus: { - boxShadow: '0 0 0 1px #578AF4', // $blue-500 + boxShadow: 'none', }, }, label: { diff --git a/apps/client/src/theme/ontimeMenu.ts b/apps/client/src/theme/ontimeMenu.ts index b38ddebad..59f01daa4 100644 --- a/apps/client/src/theme/ontimeMenu.ts +++ b/apps/client/src/theme/ontimeMenu.ts @@ -1,19 +1,27 @@ export const ontimeMenuOnDark = { list: { + fontSize: 'calc(1rem - 2px)', borderRadius: '3px', - border: 'none', - bg: '#fff', // $gray-50 + borderColor: 'rgba(255, 255, 255, 0.1)', + color: '#ececec', // $gray-1030 + backgroundColor: '#202020', // $gray-1250 zIndex: 100, }, item: { - letterSpacing: '0.15px', - color: '#101010', // $gray-1350 - bg: '#fff', // + backgroundColor: 'transparent', + paddingBlock: '0.5rem', _hover: { - backgroundColor: '#e2e2e2', // $gray-200 + backgroundColor: 'rgba(0, 0, 0, 0.1)', + _disabled: { + backgroundColor: 'transparent', + }, + }, + _disabled: { + color: '#b1b1b1', // $gray-400 }, }, divider: { - borderColor: '#cfcfcf', // $gray-200 + borderColor: 'rgba(255, 255, 255, 0.07)', + opacity: 1, }, }; diff --git a/apps/client/src/theme/theme.ts b/apps/client/src/theme/theme.ts index a34c6cb8b..7681c3bdf 100644 --- a/apps/client/src/theme/theme.ts +++ b/apps/client/src/theme/theme.ts @@ -57,8 +57,8 @@ const theme = extendTheme({ }, Drawer: { variants: { - 'ontime': {...ontimeDrawer}, - } + ontime: { ...ontimeDrawer }, + }, }, Editable: { variants: { diff --git a/e2e/tests/000-upload-showfile.spec.ts b/e2e/tests/000-upload-showfile.spec.ts index d642f27b8..fa3de721a 100644 --- a/e2e/tests/000-upload-showfile.spec.ts +++ b/e2e/tests/000-upload-showfile.spec.ts @@ -4,8 +4,8 @@ const fileToUpload = 'e2e/tests/fixtures/test-db.json'; test('project file upload', async ({ page }) => { await page.goto('http://localhost:4001/editor'); - await page.getByRole('button', { name: 'Rundown menu' }).click(); - await page.getByRole('menuitem', { name: 'Delete all events' }).click(); + await page.getByRole('button', { name: 'Edit mode' }).click(); + await page.getByRole('button', { name: 'Clear rundown' }).click(); await page.getByRole('button', { name: 'toggle settings' }).click(); await page.getByRole('button', { name: 'Project', exact: true }).click(); diff --git a/e2e/tests/features/203-delay-block.spec.ts b/e2e/tests/features/203-delay-block.spec.ts index 6b5030be7..97e2e2f13 100644 --- a/e2e/tests/features/203-delay-block.spec.ts +++ b/e2e/tests/features/203-delay-block.spec.ts @@ -4,9 +4,8 @@ test('delay blocks add time to events', async ({ page }) => { await page.goto('http://localhost:4001/editor'); // delete all events and add a new one - await page.getByRole('button', { name: 'Rundown menu' }).click(); - await page.getByRole('menuitem', { name: 'Delete all events' }).click(); - await page.getByRole('button', { name: 'Rundown menu' }).click(); + await page.getByRole('button', { name: 'Edit mode' }).click(); + await page.getByRole('button', { name: 'Clear rundown' }).click(); await page.getByRole('button', { name: 'Create event' }).click(); // add data to new event @@ -18,8 +17,7 @@ test('delay blocks add time to events', async ({ page }) => { await page.getByTestId('rundown').getByPlaceholder('Duration').press('Enter'); // add delay block - await page.getByRole('button', { name: 'Rundown menu' }).click(); - await page.getByRole('menuitem', { name: 'Add delay at start' }).click(); + await page.getByRole('button', { name: 'Delay Alt ⇧ D' }).click(); // fill positive delay await page.getByTestId('delay-input').click(); @@ -37,8 +35,7 @@ test('delay blocks add time to events', async ({ page }) => { // add new delay await page.getByTestId('rundown').getByPlaceholder('Start').click(); - await page.getByRole('button', { name: 'Rundown menu' }).click(); - await page.getByRole('menuitem', { name: 'Add delay at start' }).click(); + await page.getByRole('button', { name: 'Delay Alt ⇧ D' }).click(); await page.getByTestId('delay-input').click(); await page.getByTestId('delay-input').fill('10m'); await page.getByTestId('delay-input').press('Enter'); @@ -54,9 +51,10 @@ test('delays are show correctly', async ({ page }) => { await page.goto('http://localhost:4001/editor'); // add a test event - await page.getByRole('button', { name: 'Rundown menu' }).click(); - await page.getByRole('menuitem', { name: 'Delete all events' }).click(); + await page.getByRole('button', { name: 'Edit mode' }).click(); + await page.getByRole('button', { name: 'Clear rundown' }).click(); await page.getByRole('button', { name: 'Create Event' }).click(); + await page.getByTestId('time-input-timeStart').click(); await page.getByTestId('rundown').getByTestId('time-input-timeStart').click(); await page.getByTestId('rundown').getByTestId('time-input-timeStart').fill('10'); @@ -70,8 +68,7 @@ test('delays are show correctly', async ({ page }) => { await expect(page.getByTestId('entry-1').locator('#block-status')).toHaveAttribute('data-ispublic', 'true'); // add a delay - await page.getByRole('button', { name: 'Rundown menu' }).click(); - await page.getByRole('menuitem', { name: 'Add delay at start' }).click(); + await page.getByRole('button', { name: 'Delay Alt ⇧ D' }).click(); await page.getByTestId('delay-input').click(); await page.getByTestId('delay-input').fill('1'); await page.getByTestId('delay-input').press('Enter'); diff --git a/e2e/tests/features/204-editor-crud.spec.ts b/e2e/tests/features/204-editor-crud.spec.ts index e37b4871f..ab5f33915 100644 --- a/e2e/tests/features/204-editor-crud.spec.ts +++ b/e2e/tests/features/204-editor-crud.spec.ts @@ -4,27 +4,26 @@ test('CRUD operations on the rundown', async ({ page }) => { await page.goto('http://localhost:4001/editor'); // clear rundown - await page.getByRole('button', { name: 'Run mode' }).click(); - await page.getByRole('button', { name: 'Rundown menu' }).click(); - await page.getByRole('menuitem', { name: 'Delete all events' }).click(); + await page.getByRole('button', { name: 'Edit mode' }).click(); + await page.getByRole('button', { name: 'Clear rundown' }).click(); // create event from the rundown empty button await page.getByRole('button', { name: 'Create Event' }).click(); // create blocks using the quick add buttons - await page.getByRole('button', { name: 'Block' }).click(); - await page.getByRole('button', { name: 'Delay' }).click(); - await page.getByTestId('quick-add-event').click(); + await page.getByRole('button', { name: 'Block' }).nth(1).click(); + await page.getByRole('button', { name: 'Delay' }).nth(1).click(); + await page.getByRole('button', { name: 'Event', exact: true }).nth(1).click(); - // test quick add options - start is last end + // test quick add options - star2+5-t is last end await page.getByTestId('entry-2').getByTestId('time-input-duration').fill('20m'); - await page.getByTestId('quick-add-event').click(); - await expect(page.getByLabel('Link to previous')).toBeChecked(); + await page.getByRole('button', { name: 'Event', exact: true }).nth(1).click(); + await expect(page.getByLabel('Link to previous').nth(1)).toBeChecked(); expect(await page.getByTestId('entry-3').getByTestId('time-input-timeStart').inputValue()).toContain('00:30:00'); // test quick add options - event is public - await expect(page.getByLabel('Event is public')).toBeChecked(); - await page.getByTestId('quick-add-event').click(); + await expect(page.getByLabel('Event is public').nth(1)).toBeChecked(); + await page.getByRole('button', { name: 'Event', exact: true }).nth(1).click(); await expect(page.getByTestId('entry-4').locator('#block-status')).toHaveAttribute('data-ispublic', 'true'); }); diff --git a/e2e/tests/features/205-operator.spec.ts b/e2e/tests/features/205-operator.spec.ts index 8bdf603e8..8dfb704f5 100644 --- a/e2e/tests/features/205-operator.spec.ts +++ b/e2e/tests/features/205-operator.spec.ts @@ -3,9 +3,8 @@ import { test, expect } from '@playwright/test'; test('smoke test operator', async ({ page }) => { // make some boilerplate await page.goto('http://localhost:4001/editor'); - await page.getByRole('button', { name: 'Run mode' }).click(); - await page.getByRole('button', { name: 'Rundown menu' }).click(); - await page.getByRole('menuitem', { name: 'Delete all events' }).click(); + await page.getByRole('button', { name: 'Edit mode' }).click(); + await page.getByRole('button', { name: 'Clear rundown' }).click(); await page.getByRole('button', { name: 'Create Event' }).click(); await page.getByTestId('time-input-timeStart').fill('1m'); @@ -13,36 +12,40 @@ test('smoke test operator', async ({ page }) => { await page.getByTestId('time-input-duration').fill('1m'); await page.getByTestId('time-input-duration').press('Enter'); - await page.getByTestId('quick-add-event').click(); + await page.getByRole('button', { name: 'Event', exact: true }).nth(1).click(); await page.getByTestId('entry-2').getByTestId('lock__duration').click(); await page.getByTestId('entry-2').getByTestId('time-input-duration').fill('1m'); await page.getByTestId('entry-2').getByTestId('time-input-duration').press('Enter'); await page.getByTestId('entry-2').getByTestId('time-input-duration').press('Enter'); - await page.getByTestId('quick-add-event').click(); + await page.getByRole('button', { name: 'Event Alt E', exact: true }).click(); await page.getByTestId('entry-3').getByTestId('lock__duration').click(); await page.getByTestId('entry-3').getByTestId('time-input-duration').fill('1m'); await page.getByTestId('entry-3').getByTestId('time-input-duration').press('Enter'); - await page.getByRole('button', { name: 'Rundown menu' }).click(); - await page.getByRole('menuitem', { name: 'Add block at start' }).click(); - await page.getByTestId('quick-add-block').click(); + await page.getByRole('button', { name: 'Block', exact: true }).nth(0).click(); await page.getByRole('button', { name: 'Edit mode' }).click(); - await page.getByTestId('entry-1').getByRole('button', { name: 'Event options' }).first().click(); - await page.getByLabel('Title', { exact: true }).click(); - await page.getByLabel('Title', { exact: true }).fill('title 1'); - await page.getByLabel('Title', { exact: true }).press('Enter'); + await page.getByTestId('entry-1').click({ button: 'right' }); + await page.getByRole('menuitem', { name: 'Event after' }).click(); - await page.getByTestId('entry-2').getByRole('button', { name: 'Event options' }).first().click(); - await page.getByLabel('Title', { exact: true }).click(); - await page.getByLabel('Title', { exact: true }).fill('title 2'); - await page.getByLabel('Title', { exact: true }).press('Enter'); + await page.getByTestId('entry-1').getByTestId('block__title').click(); + await page.getByTestId('entry-1').getByTestId('block__title').fill('title 1'); + await page.getByTestId('entry-1').getByTestId('block__title').press('Enter'); - await page.getByTestId('entry-3').getByRole('button', { name: 'Event options' }).first().click(); - await page.getByLabel('Title', { exact: true }).click(); - await page.getByLabel('Title', { exact: true }).fill('title 3'); - await page.getByLabel('Title', { exact: true }).press('Enter'); + await page.getByTestId('entry-2').click({ button: 'right' }); + await page.getByRole('menuitem', { name: 'Event after' }).click(); + + await page.getByTestId('entry-2').getByTestId('block__title').click(); + await page.getByTestId('entry-2').getByTestId('block__title').fill('title 2'); + await page.getByTestId('entry-2').getByTestId('block__title').press('Enter'); + + await page.getByTestId('entry-3').click({ button: 'right' }); + await page.getByRole('menuitem', { name: 'Event after' }).click(); + + await page.getByTestId('entry-3').getByTestId('block__title').click(); + await page.getByTestId('entry-3').getByTestId('block__title').fill('title 3'); + await page.getByTestId('entry-3').getByTestId('block__title').press('Enter'); // start an event await page.getByTestId('panel-timer-control').getByRole('button', { name: 'Start' }).click(); diff --git a/e2e/tests/features/301-spreadsheet-import.spec.ts b/e2e/tests/features/301-spreadsheet-import.spec.ts index 5f56fc125..98ecb5607 100644 --- a/e2e/tests/features/301-spreadsheet-import.spec.ts +++ b/e2e/tests/features/301-spreadsheet-import.spec.ts @@ -4,8 +4,8 @@ const fileToUpload = 'e2e/tests/fixtures/test-sheet.xlsx'; test('sheet file upload', async ({ page }) => { await page.goto('http://localhost:4001/editor'); - await page.getByRole('button', { name: 'Rundown menu' }).click(); - await page.getByRole('menuitem', { name: 'Delete all events' }).click(); + await page.getByRole('button', { name: 'Edit mode' }).click(); + await page.getByRole('button', { name: 'Clear rundown' }).click(); await page.getByRole('button', { name: 'Toggle settings' }).click(); await page.getByRole('button', { name: 'Import spreadsheet' }).click();