fix: recover cursor logic (#773)

This commit is contained in:
Carlos Valente
2024-02-13 18:43:41 +01:00
committed by GitHub
parent 67c448572c
commit 71dace669b
12 changed files with 59 additions and 38 deletions
@@ -17,8 +17,9 @@ function persistModeToSession(mode: AppMode) {
type AppModeStore = { type AppModeStore = {
mode: AppMode; mode: AppMode;
cursor: string | null;
setMode: (mode: AppMode) => void; setMode: (mode: AppMode) => void;
cursor: string | null;
setCursor: (cursor: string | null) => void;
}; };
export const useAppMode = create<AppModeStore>()((set) => ({ export const useAppMode = create<AppModeStore>()((set) => ({
@@ -31,4 +32,5 @@ export const useAppMode = create<AppModeStore>()((set) => ({
return { mode }; return { mode };
}); });
}, },
setCursor: (cursor: string | null) => set(() => ({ cursor })),
})); }));
+14 -10
View File
@@ -34,7 +34,7 @@ export default function Rundown({ data }: RundownProps) {
const showQuickEntry = eventSettings.showQuickEntry; const showQuickEntry = eventSettings.showQuickEntry;
// cursor // cursor
const { cursor, mode: appMode } = useAppMode(); const { cursor, mode: appMode, setCursor } = useAppMode();
const viewFollowsCursor = appMode === AppMode.Run; const viewFollowsCursor = appMode === AppMode.Run;
const cursorRef = useRef<HTMLDivElement | null>(null); const cursorRef = useRef<HTMLDivElement | null>(null);
const scrollRef = useRef<HTMLDivElement | null>(null); const scrollRef = useRef<HTMLDivElement | null>(null);
@@ -95,9 +95,9 @@ export default function Rundown({ data }: RundownProps) {
return; return;
} }
const nextEvent = const nextEvent =
cursor == null ? getFirstNormal(rundown, order) : getNextNormal(rundown, order, cursor)?.nextEvent; cursor === null ? getFirstNormal(rundown, order) : getNextNormal(rundown, order, cursor)?.nextEvent;
if (nextEvent) { if (nextEvent) {
// moveCursorTo(nextEvent.id, nextEvent.type === SupportedEvent.Event); setCursor(nextEvent.id);
} }
break; break;
} }
@@ -107,9 +107,11 @@ export default function Rundown({ data }: RundownProps) {
} }
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- we check for this before // eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- we check for this before
const previousEvent = const previousEvent =
cursor == null ? getFirstNormal(rundown, order) : getPreviousNormal(rundown, order, cursor).previousEvent; cursor === null
? getFirstNormal(rundown, order)
: getPreviousNormal(rundown, order, cursor).previousEvent;
if (previousEvent) { if (previousEvent) {
// moveCursorTo(previousEvent.id, previousEvent.type === SupportedEvent.Event); setCursor(previousEvent.id);
} }
break; break;
} }
@@ -138,11 +140,13 @@ export default function Rundown({ data }: RundownProps) {
if (order.length < 2 || cursor == null) { if (order.length < 2 || cursor == null) {
return; return;
} }
// Alt + Ctrl + Arrow Down
if (event.code == 'ArrowDown') { if (event.code == 'ArrowDown') {
const { nextEvent, nextIndex } = getNextNormal(rundown, order, cursor); const { nextEvent, nextIndex } = getNextNormal(rundown, order, cursor);
if (nextEvent && nextIndex !== null) { if (nextEvent && nextIndex !== null) {
reorderEvent(cursor, nextIndex - 1, nextIndex); reorderEvent(cursor, nextIndex - 1, nextIndex);
} }
// Alt + Ctrl + Arrow Up
} else if (event.code == 'ArrowUp') { } else if (event.code == 'ArrowUp') {
const { previousEvent, previousIndex } = getPreviousNormal(rundown, order, cursor); const { previousEvent, previousIndex } = getPreviousNormal(rundown, order, cursor);
if (previousEvent && previousIndex !== null) { if (previousEvent && previousIndex !== null) {
@@ -151,7 +155,7 @@ export default function Rundown({ data }: RundownProps) {
} }
} }
}, },
[cursor, insertAtCursor, order, rundown, reorderEvent], [order, cursor, rundown, setCursor, insertAtCursor, reorderEvent],
); );
// we copy the state from the store here // we copy the state from the store here
@@ -231,10 +235,10 @@ export default function Rundown({ data }: RundownProps) {
previousEventId = event.id; previousEventId = event.id;
} }
const isLast = index === order.length - 1; const isLast = index === order.length - 1;
const isSelected = featureData?.selectedEventId === event.id; const isLoaded = featureData?.selectedEventId === event.id;
const isNext = featureData?.nextEventId === event.id; const isNext = featureData?.nextEventId === event.id;
const hasCursor = event.id === cursor; const hasCursor = event.id === cursor;
if (isSelected) { if (isLoaded) {
isPast = false; isPast = false;
} }
@@ -248,12 +252,12 @@ export default function Rundown({ data }: RundownProps) {
isPast={isPast} isPast={isPast}
eventIndex={eventIndex} eventIndex={eventIndex}
data={event} data={event}
selected={isSelected} loaded={isLoaded}
hasCursor={hasCursor} hasCursor={hasCursor}
next={isNext} next={isNext}
previousEnd={previousEnd} previousEnd={previousEnd}
previousEventId={previousEventId} previousEventId={previousEventId}
playback={isSelected ? featureData.playback : undefined} playback={isLoaded ? featureData.playback : undefined}
isRolling={featureData.playback === Playback.Roll} isRolling={featureData.playback === Playback.Roll}
/> />
</div> </div>
@@ -19,7 +19,7 @@ interface RundownEntryProps {
type: SupportedEvent; type: SupportedEvent;
isPast: boolean; isPast: boolean;
data: OntimeRundownEntry; data: OntimeRundownEntry;
selected: boolean; loaded: boolean;
eventIndex: number; eventIndex: number;
hasCursor: boolean; hasCursor: boolean;
next: boolean; next: boolean;
@@ -30,7 +30,7 @@ interface RundownEntryProps {
} }
export default function RundownEntry(props: RundownEntryProps) { export default function RundownEntry(props: RundownEntryProps) {
const { isPast, data, selected, hasCursor, next, previousEnd, previousEventId, playback, isRolling, eventIndex } = const { isPast, data, loaded, hasCursor, next, previousEnd, previousEventId, playback, isRolling, eventIndex } =
props; props;
const { emitError } = useEmitLog(); const { emitError } = useEmitLog();
const { addEvent, updateEvent, batchUpdateEvents, deleteEvent, swapEvents } = useEventAction(); const { addEvent, updateEvent, batchUpdateEvents, deleteEvent, swapEvents } = useEventAction();
@@ -140,7 +140,7 @@ export default function RundownEntry(props: RundownEntryProps) {
isPast={isPast} isPast={isPast}
next={next} next={next}
skip={data.skip} skip={data.skip}
selected={selected} loaded={loaded}
hasCursor={hasCursor} hasCursor={hasCursor}
playback={playback} playback={playback}
isRolling={isRolling} isRolling={isRolling}
@@ -13,7 +13,8 @@ $block-bg: $gray-1250;
$block-bg2: $gray-1050; // for delay and blocks $block-bg2: $gray-1050; // for delay and blocks
$block-box-shadow: rgba(0, 0, 0, 0.5) 0 0 3px 2px; $block-box-shadow: rgba(0, 0, 0, 0.5) 0 0 3px 2px;
$secondary-block-height: 2.5rem; $secondary-block-height: 2.5rem;
$block-cursor-color: $blue-400; $block-selected-color: $blue-400;
$block-cursor-color: $orange-400;
@mixin block-styling() { @mixin block-styling() {
box-sizing: content-box; box-sizing: content-box;
@@ -28,7 +28,7 @@ $skip-opacity: 0.1;
--status-color-active-override: #{$green-400}; --status-color-active-override: #{$green-400};
} }
&.selected { &.loaded {
background-color: $gray-1325; background-color: $gray-1325;
} }
@@ -47,6 +47,10 @@ $skip-opacity: 0.1;
@include declare-overrides; @include declare-overrides;
} }
&.selected {
outline: 1px solid $block-selected-color;
}
&.hasCursor { &.hasCursor {
outline: 1px solid $block-cursor-color; outline: 1px solid $block-cursor-color;
} }
@@ -10,6 +10,7 @@ import { IoSwapVertical } from '@react-icons/all-files/io5/IoSwapVertical';
import { EndAction, MaybeNumber, MaybeString, OntimeEvent, Playback, TimerType, TimeStrategy } from 'ontime-types'; import { EndAction, MaybeNumber, MaybeString, OntimeEvent, Playback, TimerType, TimeStrategy } from 'ontime-types';
import { useContextMenu } from '../../../common/hooks/useContextMenu'; import { useContextMenu } from '../../../common/hooks/useContextMenu';
import { useAppMode } from '../../../common/stores/appModeStore';
import copyToClipboard from '../../../common/utils/copyToClipboard'; import copyToClipboard from '../../../common/utils/copyToClipboard';
import { cx, getAccessibleColour } from '../../../common/utils/styleUtils'; import { cx, getAccessibleColour } from '../../../common/utils/styleUtils';
import type { EventItemActions } from '../RundownEntry'; import type { EventItemActions } from '../RundownEntry';
@@ -41,7 +42,7 @@ interface EventBlockProps {
isPast: boolean; isPast: boolean;
next: boolean; next: boolean;
skip: boolean; skip: boolean;
selected: boolean; loaded: boolean;
hasCursor: boolean; hasCursor: boolean;
playback?: Playback; playback?: Playback;
isRolling: boolean; isRolling: boolean;
@@ -77,7 +78,7 @@ export default function EventBlock(props: EventBlockProps) {
isPast, isPast,
next, next,
skip = false, skip = false,
selected, loaded,
hasCursor, hasCursor,
playback, playback,
isRolling, isRolling,
@@ -85,6 +86,7 @@ export default function EventBlock(props: EventBlockProps) {
} = props; } = props;
const { selectedEventId, setSelectedEventId, clearSelectedEventId } = useEventIdSwapping(); const { selectedEventId, setSelectedEventId, clearSelectedEventId } = useEventIdSwapping();
const { selectedEvents, setSelectedEvents } = useEventSelection(); const { selectedEvents, setSelectedEvents } = useEventSelection();
const setCursor = useAppMode((state) => state.setCursor);
const handleRef = useRef<null | HTMLSpanElement>(null); const handleRef = useRef<null | HTMLSpanElement>(null);
const [isVisible, setIsVisible] = useState(false); const [isVisible, setIsVisible] = useState(false);
@@ -205,13 +207,15 @@ export default function EventBlock(props: EventBlockProps) {
}; };
}, [handleRef]); }, [handleRef]);
const isSelected = selectedEvents.has(eventId);
const blockClasses = cx([ const blockClasses = cx([
style.eventBlock, style.eventBlock,
skip ? style.skip : null, skip ? style.skip : null,
isPast ? style.past : null, isPast ? style.past : null,
selected ? style.selected : null, loaded ? style.loaded : null,
playback ? style[playback] : null, playback ? style[playback] : null,
selectedEvents.has(eventId) ? style.hasCursor : null, isSelected ? style.selected : null,
hasCursor ? style.hasCursor : null,
]); ]);
const handleFocusClick = (event: MouseEvent) => { const handleFocusClick = (event: MouseEvent) => {
@@ -227,9 +231,8 @@ export default function EventBlock(props: EventBlockProps) {
// UI indexes are 1 based // UI indexes are 1 based
const index = eventIndex - 1; const index = eventIndex - 1;
const editMode = getSelectionMode(event); const editMode = getSelectionMode(event);
return setSelectedEvents({ id: eventId, index, selectMode: editMode }); setSelectedEvents({ id: eventId, index, selectMode: editMode });
setCursor(eventId);
// moveCursorTo(eventId, true);
}; };
return ( return (
@@ -267,7 +270,7 @@ export default function EventBlock(props: EventBlockProps) {
delay={delay} delay={delay}
next={next} next={next}
skip={skip} skip={skip}
selected={selected} loaded={loaded}
playback={playback} playback={playback}
isRolling={isRolling} isRolling={isRolling}
actionHandler={actionHandler} actionHandler={actionHandler}
@@ -42,7 +42,7 @@ interface EventBlockInnerProps {
delay: number; delay: number;
next: boolean; next: boolean;
skip: boolean; skip: boolean;
selected: boolean; loaded: boolean;
playback?: Playback; playback?: Playback;
isRolling: boolean; isRolling: boolean;
actionHandler: (action: EventItemActions, payload?: any) => void; actionHandler: (action: EventItemActions, payload?: any) => void;
@@ -64,7 +64,7 @@ const EventBlockInner = (props: EventBlockInnerProps) => {
delay, delay,
next, next,
skip = false, skip = false,
selected, loaded,
playback, playback,
isRolling, isRolling,
actionHandler, actionHandler,
@@ -110,13 +110,13 @@ const EventBlockInner = (props: EventBlockInnerProps) => {
skip={skip} skip={skip}
isPlaying={eventIsPlaying} isPlaying={eventIsPlaying}
isPaused={eventIsPaused} isPaused={eventIsPaused}
selected={selected} loaded={loaded}
disablePlayback={skip || isRolling} disablePlayback={skip || isRolling}
/> />
<div className={style.statusElements} id='block-status' data-ispublic={isPublic}> <div className={style.statusElements} id='block-status' data-ispublic={isPublic}>
<span className={style.eventNote}>{note}</span> <span className={style.eventNote}>{note}</span>
<div className={selected ? style.progressBg : `${style.progressBg} ${style.hidden}`}> <div className={loaded ? style.progressBg : `${style.progressBg} ${style.hidden}`}>
{selected && <EventBlockProgressBar playback={playback} />} {loaded && <EventBlockProgressBar playback={playback} />}
</div> </div>
<div className={style.eventStatus} tabIndex={-1}> <div className={style.eventStatus} tabIndex={-1}>
<Tooltip label={`Time type: ${timerType}`} {...tooltipProps}> <Tooltip label={`Time type: ${timerType}`} {...tooltipProps}>
@@ -137,7 +137,7 @@ const EventBlockInner = (props: EventBlockInnerProps) => {
</div> </div>
</div> </div>
<div className={style.eventActions}> <div className={style.eventActions}>
<BlockActionMenu showClone enableDelete={!selected} actionHandler={actionHandler} /> <BlockActionMenu showClone enableDelete={!loaded} actionHandler={actionHandler} />
</div> </div>
</> </>
); );
@@ -34,7 +34,7 @@ export default function BlockActionMenu(props: BlockActionMenuProps) {
aria-label='Event options' aria-label='Event options'
icon={<IoEllipsisHorizontal />} icon={<IoEllipsisHorizontal />}
tabIndex={-1} tabIndex={-1}
variant='ontime-subtle-white' variant='ontime-ghosted-white'
size='sm' size='sm'
className={className} className={className}
/> />
@@ -32,12 +32,12 @@ interface EventBlockPlaybackProps {
skip: boolean; skip: boolean;
isPlaying: boolean; isPlaying: boolean;
isPaused: boolean; isPaused: boolean;
selected: boolean; loaded: boolean;
disablePlayback: boolean; disablePlayback: boolean;
} }
const EventBlockPlayback = (props: EventBlockPlaybackProps) => { const EventBlockPlayback = (props: EventBlockPlaybackProps) => {
const { eventId, skip, isPlaying, isPaused, selected, disablePlayback } = props; const { eventId, skip, isPlaying, isPaused, loaded, disablePlayback } = props;
const { updateEvent } = useEventAction(); const { updateEvent } = useEventAction();
const toggleSkip = () => { const toggleSkip = () => {
@@ -93,7 +93,7 @@ const EventBlockPlayback = (props: EventBlockPlaybackProps) => {
{...blockBtnStyle} {...blockBtnStyle}
clickHandler={toggleSkip} clickHandler={toggleSkip}
tabIndex={-1} tabIndex={-1}
isDisabled={selected} isDisabled={loaded}
/> />
<TooltipActionBtn <TooltipActionBtn
variant='ontime-subtle-white' variant='ontime-subtle-white'
@@ -15,8 +15,6 @@
.quickBtn { .quickBtn {
font-weight: 400; font-weight: 400;
width: auto;
padding: 0 1rem;
} }
} }
@@ -25,7 +23,7 @@
padding: 0 0.25rem; padding: 0 0.25rem;
color: $label-gray; color: $label-gray;
border-radius: 2px; border-radius: 2px;
background-color: rgba(0, 0, 0, 0.1); background-color: $black-10
} }
.options { .options {
@@ -1,5 +1,6 @@
import { memo, useCallback, useRef } from 'react'; import { memo, useCallback, useRef } from 'react';
import { Button, Checkbox, Tooltip } from '@chakra-ui/react'; import { Button, Checkbox, Tooltip } from '@chakra-ui/react';
import { IoAdd } from '@react-icons/all-files/io5/IoAdd';
import { SupportedEvent } from 'ontime-types'; import { SupportedEvent } from 'ontime-types';
import { useEventAction } from '../../../common/hooks/useEventAction'; import { useEventAction } from '../../../common/hooks/useEventAction';
@@ -82,6 +83,7 @@ const QuickAddBlock = (props: QuickAddBlockProps) => {
variant='ontime-subtle-white' variant='ontime-subtle-white'
className={style.quickBtn} className={style.quickBtn}
data-testid='quick-add-event' data-testid='quick-add-event'
leftIcon={<IoAdd />}
> >
Event {showKbd && <span className={style.keyboard}>{`${deviceAlt} + E`}</span>} Event {showKbd && <span className={style.keyboard}>{`${deviceAlt} + E`}</span>}
</Button> </Button>
@@ -94,6 +96,7 @@ const QuickAddBlock = (props: QuickAddBlockProps) => {
disabled={disableAddDelay} disabled={disableAddDelay}
className={style.quickBtn} className={style.quickBtn}
data-testid='quick-add-delay' data-testid='quick-add-delay'
leftIcon={<IoAdd />}
> >
Delay {showKbd && <span className={style.keyboard}>{`${deviceAlt} + D`}</span>} Delay {showKbd && <span className={style.keyboard}>{`${deviceAlt} + D`}</span>}
</Button> </Button>
@@ -106,6 +109,7 @@ const QuickAddBlock = (props: QuickAddBlockProps) => {
disabled={disableAddBlock} disabled={disableAddBlock}
className={style.quickBtn} className={style.quickBtn}
data-testid='quick-add-block' data-testid='quick-add-block'
leftIcon={<IoAdd />}
> >
Block {showKbd && <span className={style.keyboard}>{`${deviceAlt} + B`}</span>} Block {showKbd && <span className={style.keyboard}>{`${deviceAlt} + B`}</span>}
</Button> </Button>
+6 -1
View File
@@ -52,7 +52,12 @@ export const ontimeButtonGhostedWhite = {
...ontimeButtonSubtle, ...ontimeButtonSubtle,
backgroundColor: 'transparent', backgroundColor: 'transparent',
color: 'white', color: 'white',
_hover: { background: '#ebedf0', color: '#333' }, _hover: {
background: '#404040', // $gray-1000
},
_active: {
background: '#2d2d2d', // $gray-1100
},
}; };
export const ontimeButtonGhosted = { export const ontimeButtonGhosted = {