Skip to content

Commit e2a73c7

Browse files
committed
Remove the bug in timeJump when switch route
1 parent 6a36997 commit e2a73c7

File tree

6 files changed

+143
-99
lines changed

6 files changed

+143
-99
lines changed

src/backend/controllers/createTree/index.ts renamed to src/backend/controllers/createTree/createTree.ts

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ import {
4242
getHooksStateAndUpdateMethod,
4343
getStateAndContextData,
4444
filterAndFormatData,
45-
} from '../../controllers/createTree/statePropExtractors';
45+
} from './statePropExtractors';
4646

4747
let rtidCounter = 0;
4848
const nextJSDefaultComponent = new Set(['ReactDevOverlay', 'Portal']);
@@ -92,22 +92,22 @@ export default function createTree(
9292
treeBaseDuration,
9393
_debugHookTypes,
9494
} = currentFiberNode;
95-
console.log('LinkFiber', {
96-
currentFiberNode,
97-
// tag,
98-
// elementType,
99-
componentName:
100-
elementType?._context?.displayName || //For ContextProvider
101-
elementType?._result?.name || //For lazy Component
102-
elementType?.render?.name ||
103-
elementType?.name ||
104-
elementType,
105-
// memoizedProps,
106-
// memoizedState,
107-
// stateNode,
108-
// dependencies,
109-
// _debugHookTypes,
110-
});
95+
// console.log('LinkFiber', {
96+
// currentFiberNode,
97+
// // tag,
98+
// // elementType,
99+
// componentName:
100+
// elementType?._context?.displayName || //For ContextProvider
101+
// elementType?._result?.name || //For lazy Component
102+
// elementType?.render?.name ||
103+
// elementType?.name ||
104+
// elementType,
105+
// // memoizedProps,
106+
// // memoizedState,
107+
// // stateNode,
108+
// // dependencies,
109+
// // _debugHookTypes,
110+
// });
111111

112112
// TODO: Understand this if statement
113113
if (tag === HostComponent) {

src/backend/controllers/timeJump.ts

Lines changed: 109 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -5,97 +5,129 @@ import routes from '../models/routes';
55
/* eslint-disable max-len */
66
/* eslint-disable no-param-reassign */
77
import componentActionsRecord from '../models/masterState';
8+
import { Status } from '../types/backendTypes';
9+
import Tree from '../models/tree';
810
const circularComponentTable = new Set();
911

1012
/**
11-
* This file contains necessary functionality for time-travel feature
13+
* This file contains necessary functionality for time-travel feature.
1214
*
13-
* Default Export:
14-
* @function timeJump
15-
* @param origin The latest snapshot, linked to the fiber (changes to origin will change app)
16-
* @param mode The current mode (i.e. jumping, time-traveling, or paused)
17-
* @returns A function that takes a target snapshot and a boolean flag checking for firstCall, then invokes `jump` on that target snapshot
15+
* The target snapshot portrays some past state we want to travel to `jump` recursively and setState for any stateful component.
16+
*
17+
* @param mode - The current mode (i.e. jumping, time-traveling, or paused)
18+
* @returns A function that takes a `target` snapshot and a boolean flag checking for `firstCall`, then invokes `jump` on that target snapshot
1819
*
19-
* The target snapshot portrays some past state we want to travel to.
20-
* `jump` recursively sets state for any stateful components.
2120
*/
22-
export default function timeJump(mode) {
23-
// Recursively change state of tree
24-
// Set the state of the origin tree if the component is stateful
25-
async function jump(target): Promise<void> {
26-
if (!target) return;
27-
// Base Case: if has visited, return
28-
if (circularComponentTable.has(target)) {
29-
return;
21+
export default function timeJump(mode: Status) {
22+
// payload from index.ts is assigned to target
23+
/**
24+
* @param target - The target snapshot to re-render
25+
* @param firstCall - A boolean flag checking for `firstCall`
26+
*/
27+
return (target: Tree, firstCall = false): void => {
28+
// Setting mode disables setState from posting messages to window
29+
mode.jumping = true;
30+
// Clearn the circularComponentTable
31+
if (firstCall) circularComponentTable.clear();
32+
// Determine if user is navigating to another site
33+
const navigating: boolean = routes.navigate(target.route);
34+
35+
if (navigating) {
36+
// Initiate popStateHandler to aid the removeListener for 'popstate'
37+
const popStateHandler = () => {
38+
initiateJump(target, mode);
39+
};
40+
// removeEventListener('popstate', popStateHandler);
41+
// Background will "perform" popstate till get to the correct history location?
42+
addEventListener('popstate', popStateHandler);
3043
} else {
31-
circularComponentTable.add(target);
32-
}
33-
// ------------------------STATELESS/ROOT COMPONENT-------------------------
34-
// Since stateless component has no data to update, continue to traverse its child nodes:
35-
if (target.state === 'stateless' || target.state === 'root') {
36-
target.children.forEach((child) => jump(child));
37-
return;
44+
// Intiate the jump
45+
initiateJump(target, mode);
3846
}
47+
};
48+
}
3949

40-
// Destructure component data:
41-
const { index, state, hooksIndex, hooksState } = target.componentData;
42-
// ------------------------STATEFUL CLASS COMPONENT-------------------------
43-
// for stateful class components
44-
// check if it is a stateful class component
45-
// if yes, find the component by its index and assign it to a variable
46-
// call that components setState method to reset state to the state at the time of the jump snapshot
47-
//index can be zero => falsy value => DO NOT REMOVE UNDEFINED
48-
if (index !== undefined) {
49-
// Obtain component data & its update method at the given index
50-
const classComponent = componentActionsRecord.getComponentByIndex(index);
51-
if (classComponent && classComponent.setState) {
52-
await classComponent.setState(
53-
// prevState contains the states of the snapshots we are jumping FROM, not jumping TO
54-
(prevState) => state,
55-
);
56-
}
57-
// Iterate through new children after state has been set
58-
target.children.forEach((child) => jump(child));
59-
return;
50+
/**
51+
* This function initiate the request for jump and will pause the jump when user moves mouse over the body of the document.
52+
* @param target - The target snapshot to re-render
53+
* @param mode - The current mode (i.e. jumping, time-traveling, or paused)
54+
*/
55+
async function initiateJump(target, mode): Promise<void> {
56+
console.log('JUMP');
57+
updateTreeState(target).then(() => {
58+
document.body.onmouseover = () => {
59+
mode.jumping = false;
60+
};
61+
});
62+
}
63+
64+
/**
65+
* This recursive function receives the target snapshot and will update the state of the fiber tree if the component is statefu
66+
* @param target - The target snapshot to re-render
67+
*/
68+
async function updateTreeState(target): Promise<void> {
69+
if (!target) return;
70+
// Base Case: if has visited, return
71+
if (circularComponentTable.has(target)) {
72+
return;
73+
} else {
74+
circularComponentTable.add(target);
75+
}
76+
// console.log(target.name);
77+
// ------------------------STATELESS/ROOT COMPONENT-------------------------
78+
// Since stateless component has no data to update, continue to traverse its child nodes:
79+
if (target.state === 'stateless' || target.state === 'root') {
80+
target.children.forEach((child) => updateTreeState(child));
81+
return;
82+
}
83+
84+
// Destructure component data:
85+
const { index, state, hooksIndex, hooksState } = target.componentData;
86+
// ------------------------STATEFUL CLASS COMPONENT-------------------------
87+
// for stateful class components
88+
// check if it is a stateful class component
89+
// if yes, find the component by its index and assign it to a variable
90+
// call that components setState method to reset state to the state at the time of the jump snapshot
91+
//index can be zero => falsy value => DO NOT REMOVE UNDEFINED
92+
if (index !== undefined) {
93+
// Obtain component data & its update method at the given index
94+
const classComponent = componentActionsRecord.getComponentByIndex(index);
95+
// If the user navigate to another page during jumps, Routes methods will popState until find a match => this cause changes in componentActionRecord => keep the if statement, otherwise will run into Uncaught Promise type error.
96+
if (classComponent?.setState) {
97+
// Update component state
98+
await classComponent.setState(
99+
// prevState contains the states of the snapshots we are jumping FROM, not jumping TO
100+
(prevState) => state,
101+
);
60102
}
103+
// Else statement is to ensure if a mismatch, this popstate is not the correct componentActionRecord. Return immediately to avoid traverse the entire tree
104+
else return;
61105

62-
// ----------------------STATEFUL FUNCTIONAL COMPONENT----------------------
63-
// check if component states are set with hooks
64-
// if yes, grab all relevant components for this snapshot by its index
65-
// call dispatch on each component passing in the corresponding currState value
66-
//index can be zero => falsy value => DO NOT REMOVE UNDEFINED
67-
if (hooksIndex !== undefined) {
68-
// Obtain component data & its update method at the given index
69-
const functionalComponent = componentActionsRecord.getComponentByIndexHooks(hooksIndex);
106+
// Iterate through new children after state has been set
107+
target.children.forEach((child) => updateTreeState(child));
108+
return;
109+
}
110+
111+
// ----------------------STATEFUL FUNCTIONAL COMPONENT----------------------
112+
// check if component states are set with hooks
113+
// if yes, grab all relevant components for this snapshot by its index
114+
// call dispatch on each component passing in the corresponding currState value
115+
//index can be zero => falsy value => DO NOT REMOVE UNDEFINED
116+
if (hooksIndex !== undefined) {
117+
// Obtain component data & its update method at the given index
118+
const functionalComponent = componentActionsRecord.getComponentByIndexHooks(hooksIndex);
119+
// If the user navigate to another page during jumps, Routes methods will popState until find a match => this cause changes in componentActionRecord => keep the if statement, otherwise will run into Uncaught Promise type error.
120+
if (functionalComponent[0]?.dispatch) {
121+
// Update component state
70122
for (let i in functionalComponent) {
71123
await functionalComponent[i].dispatch(Object.values(hooksState)[i]);
72124
}
73-
// Iterate through new children after state has been set
74-
target.children.forEach((child) => jump(child));
75-
return;
76125
}
77-
}
126+
// Else statement is to ensure if a mismatch, this popstate is not the correct componentActionRecord. Return immediately to avoid traverse the entire tree
127+
else return;
78128

79-
// payload from index.ts is assigned to target
80-
return (target, firstCall = false) => {
81-
// * Setting mode disables setState from posting messages to window
82-
mode.jumping = true;
83-
if (firstCall) circularComponentTable.clear();
84-
const navigating: boolean = routes.navigate(target.route);
85-
if (navigating) {
86-
addEventListener('popstate', (event) => {
87-
jump(target).then(() => {
88-
document.body.onmouseover = () => {
89-
mode.jumping = false;
90-
};
91-
});
92-
});
93-
} else {
94-
jump(target).then(() => {
95-
document.body.onmouseover = () => {
96-
mode.jumping = false;
97-
};
98-
});
99-
}
100-
};
129+
// Iterate through new children after state has been set
130+
target.children.forEach((child) => updateTreeState(child));
131+
return;
132+
}
101133
}

src/backend/models/masterState.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,11 +29,19 @@ export default {
2929
return index - 1;
3030
},
3131
// ----------------------------CLASS COMPONENT--------------------------------
32-
/* inputIndex will always be a fixed number (coming in timeJump.ts) */
32+
/**
33+
* This function is used for stateful Class Component to retrieve an object that has the bound setState method
34+
* @param inputIndex - index of component inside `componentActionsRecord` coming from `timeJump.ts`
35+
* @returns - an object containing the bound setState method
36+
*/
3337
getComponentByIndex: (inputIndex: number): any | undefined => componentActionsRecord[inputIndex],
3438

3539
//---------------------------FUNCTIONAL COMPONENT-----------------------------
36-
// this is used for react hooks - hooks will be passed in as an array from timeJump.ts
40+
/**
41+
* This function is used for Functional Component to retrieve an array of objects that have the bound dispatch methods.
42+
* @param inputIndex - index of component inside `componentActionsRecord` coming from `timeJump.ts`
43+
* @returns - an array of objects containing the bound dispatch methods
44+
*/
3745
getComponentByIndexHooks: (inputIndex: Array<number> = []): any[] | undefined =>
3846
inputIndex.map((index) => componentActionsRecord[index]),
3947
};

src/backend/models/routes.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
/**
55
* @class Route instances are created by the addRoute method on Routes. A Route instance has two properties: the url of the route and a unique id.
66
*/
7-
class Route {
7+
export class Route {
88
url: string;
99

1010
id: number;
@@ -73,9 +73,11 @@ class Routes {
7373
* Rebuilds the browser history stack using the copy of the stack maintained in the `routeHistory` stack. https://developer.mozilla.org/en-US/docs/Web/API/History/replaceState, https://developer.mozilla.org/en-US/docs/Web/API/History/pushState
7474
*/
7575
private rebuildHistory(url: string): void {
76+
console.log('REBUILD');
7677
window.history.replaceState('', '', this.routeHistory[this.current + 1].url);
7778

7879
for (let i = this.current + 2; i < this.routeHistory.length; i += 1) {
80+
console.log('REBUILD');
7981
window.history.pushState('', '', this.routeHistory[i].url);
8082
}
8183

@@ -114,6 +116,7 @@ class Routes {
114116
// if delta != 0 => need to navigate to another page
115117
if (delta !== 0) {
116118
// Navigate to that page based on delta steps
119+
console.log({ delta });
117120
window.history.go(delta);
118121
return true;
119122
}

src/backend/models/tree.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
/* eslint-disable max-classes-per-file */
66
/* eslint-disable no-console */
77
/* eslint-disable no-param-reassign */
8+
import { Route } from './routes';
89

910
let copyInstances = 0; // Tells you if we have already made a copy of current tree??
1011
const circularComponentTable = new Set<Tree>(); // Keeps track of the nodes added to the tree
@@ -59,7 +60,7 @@ class Tree {
5960

6061
rtid: any;
6162

62-
route: {};
63+
route: Route;
6364

6465
// Duplicate names: add a unique number ID
6566
// Create an object 'componentNames' to store each component name as a key and it's frequency of use as its value

src/backend/routers/snapShot.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { Snapshot, Status, FiberRoot } from '../types/backendTypes';
22
// passes the data down to its components
33
import componentActionsRecord from '../models/masterState';
44
import routes from '../models/routes';
5-
import createTree from '../controllers/createTree';
5+
import createTree from '../controllers/createTree/CreateTree';
66

77
// ---------------------------UPDATE TREE SNAP SHOT-----------------------------
88
/**

0 commit comments

Comments
 (0)