+
+
+
+
,
timelineIndex: number | -1,
uniqueSuspendersOnly: boolean,
+ playing: boolean,
};
type ACTION_SUSPENSE_TREE_MUTATION = {
@@ -60,12 +61,27 @@ type ACTION_SUSPENSE_SET_TIMELINE_INDEX = {
type: 'SUSPENSE_SET_TIMELINE_INDEX',
payload: number,
};
+type ACTION_SUSPENSE_SKIP_TIMELINE_INDEX = {
+ type: 'SUSPENSE_SKIP_TIMELINE_INDEX',
+ payload: boolean,
+};
+type ACTION_SUSPENSE_PLAY_PAUSE = {
+ type: 'SUSPENSE_PLAY_PAUSE',
+ payload: 'toggle' | 'play' | 'pause',
+};
+type ACTION_SUSPENSE_PLAY_TICK = {
+ type: 'SUSPENSE_PLAY_TICK',
+};
+
export type SuspenseTreeAction =
| ACTION_SUSPENSE_TREE_MUTATION
| ACTION_SET_SUSPENSE_LINEAGE
| ACTION_SELECT_SUSPENSE_BY_ID
| ACTION_SET_SUSPENSE_TIMELINE
- | ACTION_SUSPENSE_SET_TIMELINE_INDEX;
+ | ACTION_SUSPENSE_SET_TIMELINE_INDEX
+ | ACTION_SUSPENSE_SKIP_TIMELINE_INDEX
+ | ACTION_SUSPENSE_PLAY_PAUSE
+ | ACTION_SUSPENSE_PLAY_TICK;
export type SuspenseTreeDispatch = (action: SuspenseTreeAction) => void;
const SuspenseTreeStateContext: ReactContext =
@@ -107,6 +123,7 @@ function getInitialState(store: Store): SuspenseTreeState {
timeline: [],
timelineIndex: -1,
uniqueSuspendersOnly,
+ playing: false,
};
} else {
const timeline = store.getSuspendableDocumentOrderSuspense(
@@ -128,6 +145,7 @@ function getInitialState(store: Store): SuspenseTreeState {
timeline,
timelineIndex,
uniqueSuspendersOnly,
+ playing: false,
};
}
@@ -234,6 +252,7 @@ function SuspenseTreeContextController({children}: Props): React.Node {
...state,
selectedSuspenseID,
selectedRootID,
+ playing: false, // pause
};
}
case 'SET_SUSPENSE_LINEAGE': {
@@ -247,6 +266,7 @@ function SuspenseTreeContextController({children}: Props): React.Node {
lineage,
selectedSuspenseID: suspenseID,
selectedRootID,
+ playing: false, // pause
};
}
case 'SET_SUSPENSE_TIMELINE': {
@@ -302,6 +322,79 @@ function SuspenseTreeContextController({children}: Props): React.Node {
lineage: nextLineage,
selectedSuspenseID: nextSelectedSuspenseID,
timelineIndex: nextTimelineIndex,
+ playing: false, // pause
+ };
+ }
+ case 'SUSPENSE_SKIP_TIMELINE_INDEX': {
+ const direction = action.payload;
+ const nextTimelineIndex =
+ state.timelineIndex + (direction ? 1 : -1);
+ if (
+ nextTimelineIndex < 0 ||
+ nextTimelineIndex > state.timeline.length - 1
+ ) {
+ return state;
+ }
+ const nextSelectedSuspenseID = state.timeline[nextTimelineIndex];
+ const nextLineage = store.getSuspenseLineage(
+ nextSelectedSuspenseID,
+ );
+ return {
+ ...state,
+ lineage: nextLineage,
+ selectedSuspenseID: nextSelectedSuspenseID,
+ timelineIndex: nextTimelineIndex,
+ playing: false, // pause
+ };
+ }
+ case 'SUSPENSE_PLAY_PAUSE': {
+ const mode = action.payload;
+
+ let nextTimelineIndex = state.timelineIndex;
+ let nextSelectedSuspenseID = state.selectedSuspenseID;
+ let nextLineage = state.lineage;
+
+ if (
+ !state.playing &&
+ mode !== 'pause' &&
+ nextTimelineIndex === state.timeline.length - 1
+ ) {
+ // If we're restarting at the end. Then loop around and start again from the beginning.
+ nextTimelineIndex = 0;
+ nextSelectedSuspenseID = state.timeline[nextTimelineIndex];
+ nextLineage = store.getSuspenseLineage(nextSelectedSuspenseID);
+ }
+
+ return {
+ ...state,
+ lineage: nextLineage,
+ selectedSuspenseID: nextSelectedSuspenseID,
+ timelineIndex: nextTimelineIndex,
+ playing: mode === 'toggle' ? !state.playing : mode === 'play',
+ };
+ }
+ case 'SUSPENSE_PLAY_TICK': {
+ if (!state.playing) {
+ // We stopped but haven't yet cleaned up the callback. Noop.
+ return state;
+ }
+ // Advance time
+ const nextTimelineIndex = state.timelineIndex + 1;
+ if (nextTimelineIndex > state.timeline.length - 1) {
+ return state;
+ }
+ const nextSelectedSuspenseID = state.timeline[nextTimelineIndex];
+ const nextLineage = store.getSuspenseLineage(
+ nextSelectedSuspenseID,
+ );
+ // Stop once we reach the end.
+ const nextPlaying = nextTimelineIndex < state.timeline.length - 1;
+ return {
+ ...state,
+ lineage: nextLineage,
+ selectedSuspenseID: nextSelectedSuspenseID,
+ timelineIndex: nextTimelineIndex,
+ playing: nextPlaying,
};
}
default: