Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@ import {Internals} from 'remotion';
import {StudioServerConnectionCtx} from '../../helpers/client-id';
import {useKeybinding} from '../../helpers/use-keybinding';
import {deleteSelectedTimelineItems} from './delete-selected-timeline-item';
import {useTimelineSelection} from './TimelineSelection';
import {
useCurrentTimelineSelectionStateAsRef,
useTimelineSelection,
} from './TimelineSelection';

export const TimelineDeleteKeybindings: React.FC = () => {
const keybindings = useKeybinding();
Expand All @@ -14,25 +17,26 @@ export const TimelineDeleteKeybindings: React.FC = () => {
Internals.OverrideIdsToNodePathsGettersContext,
);
const {setCodeValues} = useContext(Internals.VisualModeSettersContext);
const {canSelect, selectedItems, clearSelection} = useTimelineSelection();
const {canSelect} = useTimelineSelection();
const currentSelection = useCurrentTimelineSelectionStateAsRef();

useEffect(() => {
if (
!canSelect ||
previewServerState.type !== 'connected' ||
selectedItems.length === 0
) {
if (!canSelect || previewServerState.type !== 'connected') {
return;
}

const {clientId} = previewServerState;
const currentSelection = selectedItems;
const backspace = keybindings.registerKeybinding({
event: 'keydown',
key: 'Backspace',
callback: () => {
const {selectedItems, clearSelection} = currentSelection.current;
if (selectedItems.length === 0) {
return;
}

const deletePromise = deleteSelectedTimelineItems({
selections: currentSelection,
selections: selectedItems,
sequences,
overrideIdsToNodePaths: overrideIdToNodePathMappings,
setCodeValues,
Expand All @@ -57,11 +61,10 @@ export const TimelineDeleteKeybindings: React.FC = () => {
};
}, [
canSelect,
clearSelection,
currentSelection,
keybindings,
overrideIdToNodePathMappings,
previewServerState,
selectedItems,
sequences,
setCodeValues,
]);
Expand Down
54 changes: 44 additions & 10 deletions packages/studio/src/components/Timeline/TimelineSelection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import React, {
useContext,
useEffect,
useMemo,
useRef,
useState,
type CSSProperties,
} from 'react';
Expand Down Expand Up @@ -83,15 +84,22 @@ type TimelineSelectionContextValue = {
readonly clearSelection: () => void;
};

const TimelineSelectionContext = createContext<TimelineSelectionContextValue>({
const defaultTimelineSelectionContextValue: TimelineSelectionContextValue = {
canSelect: false,
selectedItems: [],
isSelected: () => false,
selectItem: () => undefined,
selectItems: () => undefined,
containsSelection: () => false,
clearSelection: () => undefined,
});
};

const TimelineSelectionContext = createContext<TimelineSelectionContextValue>(
defaultTimelineSelectionContextValue,
);

const CurrentTimelineSelectionContext =
createContext<React.RefObject<TimelineSelectionContextValue> | null>(null);

const getTimelineSelectionKey = (item: TimelineSelection): string => {
const rowKey = timelineNodePathInfoToKey(item.nodePathInfo);
Expand Down Expand Up @@ -150,23 +158,34 @@ export const TimelineSelectAllKeybindings: React.FC<{
readonly timeline: readonly TrackWithHash[];
}> = ({timeline}) => {
const keybindings = useKeybinding();
const {canSelect, selectItems} = useTimelineSelection();
const {canSelect} = useTimelineSelection();
const currentSelection = useCurrentTimelineSelectionStateAsRef();

const selectableSequenceSelections = useMemo(
() => getSelectableTimelineSequenceSelections(timeline),
[timeline],
);
const selectableSequenceSelectionsRef = useRef(selectableSequenceSelections);
selectableSequenceSelectionsRef.current = selectableSequenceSelections;

useEffect(() => {
if (!canSelect || selectableSequenceSelections.length === 0) {
if (!canSelect) {
return;
}

const selectAll = keybindings.registerKeybinding({
event: 'keydown',
key: 'a',
callback: () => {
selectItems(selectableSequenceSelections);
const latestSelectableSequenceSelections =
selectableSequenceSelectionsRef.current;
if (latestSelectableSequenceSelections.length === 0) {
return;
}

currentSelection.current.selectItems(
latestSelectableSequenceSelections,
);
},
commandCtrlKey: true,
preventDefault: true,
Expand All @@ -177,7 +196,7 @@ export const TimelineSelectAllKeybindings: React.FC<{
return () => {
selectAll.unregister();
};
}, [canSelect, keybindings, selectableSequenceSelections, selectItems]);
}, [canSelect, currentSelection, keybindings]);

return null;
};
Expand Down Expand Up @@ -267,19 +286,34 @@ export const TimelineSelectionProvider: React.FC<{
clearSelection,
],
);
const currentSelection = useRef(value);
currentSelection.current = value;

return (
<TimelineSelectionContext.Provider value={value}>
{children}
<TimelineDeleteKeybindings />
</TimelineSelectionContext.Provider>
<CurrentTimelineSelectionContext.Provider value={currentSelection}>
<TimelineSelectionContext.Provider value={value}>
{children}
<TimelineDeleteKeybindings />
</TimelineSelectionContext.Provider>
</CurrentTimelineSelectionContext.Provider>
);
};

export const useTimelineSelection = () => {
return useContext(TimelineSelectionContext);
};

export const useCurrentTimelineSelectionStateAsRef = () => {
const currentSelection = useContext(CurrentTimelineSelectionContext);
if (currentSelection === null) {
throw new Error(
'useCurrentTimelineSelectionStateAsRef must be used inside TimelineSelectionProvider',
);
}

return currentSelection;
};

export const useTimelineRowSelection = (
nodePathInfo: SequenceNodePathInfo | null,
) => {
Expand Down
Loading