Skip to content

Commit 8188481

Browse files
Merge pull request #11 from oslabs-beta/backend
Backend
2 parents 9023bf5 + 8d9ac04 commit 8188481

File tree

8 files changed

+257
-78
lines changed

8 files changed

+257
-78
lines changed

demo-app/src/client/Components/Nav.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,9 @@ function Nav() {
1313
<Link className='link' to='/buttons'>
1414
Counter
1515
</Link>
16-
<Link className='link' to='/test'>
16+
{/* <Link className='link' to='/test'>
1717
Test
18-
</Link>
18+
</Link> */}
1919
</div>
2020
);
2121
}

src/backend/controllers/timeJump.ts

Lines changed: 39 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -19,30 +19,40 @@ const circularComponentTable = new Set();
1919
*
2020
*/
2121
export default function timeJump(mode: Status) {
22-
// payload from index.ts is assigned to target
2322
/**
24-
* @param target - The target snapshot to re-render
23+
* The target snapshot to re-render
24+
*/
25+
let target;
26+
/**
27+
* This function is to aid the removeListener for 'popstate'
28+
*/
29+
// IMPORTANT: DO NOT move this function into return function. This function is out here so that it will not be redefined any time the return function is invoked. This is importatnt for removeEventListener for popstate to work.
30+
const popStateHandler = () => {
31+
initiateJump(target, mode);
32+
};
33+
34+
/**
35+
* @param inputTarget - The target snapshot to re-render. The payload from index.ts is assigned to inputTarget
2536
* @param firstCall - A boolean flag checking for `firstCall`
2637
*/
27-
return (target: Tree, firstCall = false): void => {
38+
return (inputTarget: Tree, firstCall = false): void => {
2839
// Setting mode disables setState from posting messages to window
2940
mode.jumping = true;
41+
// Set target for popStateHandler usage:
42+
target = inputTarget;
3043
// Clearn the circularComponentTable
3144
if (firstCall) circularComponentTable.clear();
32-
// Determine if user is navigating to another site
33-
const navigating: boolean = routes.navigate(target.route);
34-
45+
// Determine if user is navigating to another route
46+
// NOTE: Inside routes.navigate, if user is navigating, we will invoke history.go, which will go back/forth based on # of delta steps. This will trigger a popstate event. Since history.go is an async method, the event listener is the only way to invoke timeJump after we have arrived at the desirable route.
47+
const navigating: boolean = routes.navigate(inputTarget.route);
3548
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?
49+
// Remove 'popstate' listener to avoid duplicate listeners
50+
removeEventListener('popstate', popStateHandler);
51+
// To invoke initateJump after history.go is complete
4252
addEventListener('popstate', popStateHandler);
4353
} else {
44-
// Intiate the jump
45-
initiateJump(target, mode);
54+
// Intiate the jump immideately if not navigating
55+
initiateJump(inputTarget, mode);
4656
}
4757
};
4858
}
@@ -53,10 +63,10 @@ export default function timeJump(mode: Status) {
5363
* @param mode - The current mode (i.e. jumping, time-traveling, or paused)
5464
*/
5565
async function initiateJump(target, mode): Promise<void> {
56-
console.log('JUMP');
5766
updateTreeState(target).then(() => {
5867
document.body.onmouseover = () => {
5968
mode.jumping = false;
69+
console.log('mouseover');
6070
};
6171
});
6272
}
@@ -84,48 +94,34 @@ async function updateTreeState(target): Promise<void> {
8494
// Destructure component data:
8595
const { index, state, hooksIndex, hooksState } = target.componentData;
8696
// ------------------------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
97+
// Check if it is a stateful class component
98+
// Index can be zero => falsy value => DO NOT REMOVE UNDEFINED
9299
if (index !== undefined) {
93-
// Obtain component data & its update method at the given index
100+
// Obtain the BOUND update method at the given index
94101
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-
);
102-
}
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;
105-
102+
// Update component state
103+
await classComponent.setState(
104+
// prevState contains the states of the snapshots we are jumping FROM, not jumping TO
105+
(prevState) => state,
106+
);
106107
// Iterate through new children after state has been set
107108
target.children.forEach((child) => updateTreeState(child));
108109
return;
109110
}
110111

111112
// ----------------------STATEFUL FUNCTIONAL COMPONENT----------------------
112-
// check if component states are set with hooks
113+
// Check if it is a stateful functional component
113114
// if yes, grab all relevant components for this snapshot by its index
114115
// call dispatch on each component passing in the corresponding currState value
115116
//index can be zero => falsy value => DO NOT REMOVE UNDEFINED
116117
if (hooksIndex !== undefined) {
117-
// Obtain component data & its update method at the given index
118+
// Obtain the array of BOUND update methods at the given indexes.
119+
// NOTE: each useState will be a separate update method. So if a component have 3 useState, we will obtain an array of 3 update methods.
118120
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
122-
for (let i in functionalComponent) {
123-
await functionalComponent[i].dispatch(Object.values(hooksState)[i]);
124-
}
121+
// Update component state
122+
for (let i in functionalComponent) {
123+
await functionalComponent[i].dispatch(Object.values(hooksState)[i]);
125124
}
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;
128-
129125
// Iterate through new children after state has been set
130126
target.children.forEach((child) => updateTreeState(child));
131127
return;

src/backend/models/masterState.ts

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,24 +4,35 @@
44
/* eslint-disable guard-for-in */
55
/* eslint-disable no-restricted-syntax */
66

7+
/**
8+
* @type ComponentAction - an array of actions that can be performed on a component
9+
*/
710
type ComponentAction = any[];
811

9-
// HookState is an array that contains a "component" for
10-
// every single state change that occurs in the app
12+
// The HookState data structure is an array that holds the current value of a hook's state, as well as a dispatch function that is used to update that state.
1113
// Information on these components include ComponentData as well as state
1214
// For class components, there will be one "component" for each snapshot
1315
// For functional components that utilize Hooks, there will be one "component"
1416
// for each setter/getter every time we have a new snapshot
1517
let componentActionsRecord: ComponentAction = [];
18+
// index keeps track of the current position in the array
1619
let index: number;
1720
index = 0;
1821

1922
export default {
23+
/**
24+
* @function clear - Clears componentActionsRecord
25+
*/
2026
clear: () => {
2127
componentActionsRecord = [];
2228
index = 0;
2329
},
24-
// Adds new component to ComponentActionsRecord
30+
31+
/**
32+
* @function saveNew - Adds a new component to the componentActionsRecord array and returns its index.
33+
* @param component
34+
* @returns number
35+
*/
2536
saveNew: (component): number => {
2637
componentActionsRecord[index] = component;
2738
index++;
@@ -30,18 +41,24 @@ export default {
3041
},
3142
// ----------------------------CLASS COMPONENT--------------------------------
3243
/**
33-
* This function is used for stateful Class Component to retrieve an object that has the bound setState method
44+
* @function getComponentByIndex - This function is used for stateful Class Component to retrieve an object that has the bound setState method
3445
* @param inputIndex - index of component inside `componentActionsRecord` coming from `timeJump.ts`
3546
* @returns - an object containing the bound setState method
3647
*/
3748
getComponentByIndex: (inputIndex: number): any | undefined => componentActionsRecord[inputIndex],
3849

3950
//---------------------------FUNCTIONAL COMPONENT-----------------------------
4051
/**
41-
* This function is used for Functional Component to retrieve an array of objects that have the bound dispatch methods.
52+
* @function getComponentByIndexHooks - This function is used for Functional Component to retrieve an array of objects that have the bound dispatch methods.
4253
* @param inputIndex - index of component inside `componentActionsRecord` coming from `timeJump.ts`
4354
* @returns - an array of objects containing the bound dispatch methods
4455
*/
4556
getComponentByIndexHooks: (inputIndex: Array<number> = []): any[] | undefined =>
4657
inputIndex.map((index) => componentActionsRecord[index]),
58+
// ----------------------------------DEBUGGING--------------------------------
59+
/**
60+
* @function getAllComponents - This method is used for debugging purpose to access the array of setState/dispatch methods
61+
* @returns - an array of objects containing the bound methods for updating state
62+
*/
63+
getAllComponents: (): any[] => componentActionsRecord,
4764
};

src/backend/models/routes.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/* eslint-disable max-classes-per-file */
22
/* eslint-disable max-len */
3-
3+
import componentActionsRecord from './masterState';
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
*/
@@ -93,7 +93,6 @@ class Routes {
9393
*/
9494
navigate(route: Route): boolean {
9595
let targetIndex: number | undefined;
96-
9796
// Loop through the routeHistory stack
9897
for (let i = 0; i < this.routeHistory.length; i += 1) {
9998
// If within the route history, found a match of url & id from the passed in route, update `targetIndex`
@@ -116,7 +115,6 @@ class Routes {
116115
// if delta != 0 => need to navigate to another page
117116
if (delta !== 0) {
118117
// Navigate to that page based on delta steps
119-
console.log({ delta });
120118
window.history.go(delta);
121119
return true;
122120
}

src/backend/models/tree.ts

Lines changed: 24 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,12 @@
77
/* eslint-disable no-param-reassign */
88
import { Route } from './routes';
99

10-
let copyInstances = 0; // Tells you if we have already made a copy of current tree??
11-
const circularComponentTable = new Set<Tree>(); // Keeps track of the nodes added to the tree
12-
let componentNames = {}; // {componentName: frequency of use} => component name as a key and it's frequency of use as its value
10+
// The circularComponentTable is used to handle circular references in the state object. It keeps tracks of the components that have already been serialized. When a component is encountered for the first time, it is added to the table along with a unique identifier. If the same component is encountered again, the identifier is used to reference the previously serialized component in the output instead of serializing it again, thus avoiding the infinite loop.
11+
const circularComponentTable = new Set<Tree>();
12+
// Used to keep track of which objects have already been copied during the serialization process. This is necessary to handle circular references correctly. When an object is serialized, all its properties are copied to the serialized object. If an object property is an object itself, it needs to be serialized recursively. However, if the object being serialized has already been serialized before, it should not be serialized again to prevent an infinite loop.
13+
let copyInstances = 0;
14+
// ComponentNames is used to store a mapping between a component's unique identifier and its name. This mapping is used to reconstruct the component instances during deserialization.
15+
let componentNames = {};
1316

1417
// Functions dont serialize properly so we need to scrub for that
1518
export function scrubUnserializableMembers(tree: Tree): Tree {
@@ -20,6 +23,11 @@ export function scrubUnserializableMembers(tree: Tree): Tree {
2023
}
2124

2225
// Making a deep clone of state becuase we want to make a copy
26+
/**
27+
* @function serializeState - In the context of React, state is often used to store data that determines the behavior and appearance of a component. By serializing the state, we can preserve the component's data across page refreshes, server-side rendering, and other transitions. Additionally, by serializing the state and passing it to a child component, we can create a deep clone of the state, which allows the child component to manipulate the state without affecting the original component. This is useful in situations where we want to keep the state of the parent component immutable, but still allow child components to modify a copy of the state.
28+
* @param state - Object that contains the current state of the application or system that needs to be serialized.
29+
* @returns
30+
*/
2331
export function serializeState(state) {
2432
try {
2533
// makes a deep clone
@@ -33,15 +41,14 @@ export function serializeState(state) {
3341
/**
3442
* This is the current snapshot that is being sent to the snapshots array.
3543
* Creates a Tree
36-
* @param state - {string| {}} - the tree's current state
37-
* @param name - {string} - the tree's name
38-
* @param componentData - {props: {}} - Data in the component tree
39-
* @param chilren - {(Tree | string)[]} - An array of children nodes
40-
* @param parent - {Tree} - the parent node
41-
* @param isExpanded - {boolean}
42-
* @param rtid - {any}
43-
* @param route -
44-
* @parent generates a new tree (recursive call)
44+
* @param state - the current state of the component represented by this node.
45+
* @param name - the name of the component represented by this node.
46+
* @param componentData - an object containing the props of the component represented by this node.
47+
* @param chilren - an array of child nodes.
48+
* @param parent - a reference to the parent node.
49+
* @param isExpanded - a boolean value indicating whether the node is expanded in the UI.
50+
* @param rtid - a unique identifier for the node.
51+
* @param route - an object representing the route associated with the node.
4552
*/
4653
class Tree {
4754
state: string | {};
@@ -87,6 +94,11 @@ class Tree {
8794
}
8895

8996
// Returns a unique name ready to be used for when new components gets added to the tree
97+
/**
98+
* @function checkForDuplicates - Generates a unique name for a component that is being added to the component tree
99+
* @param name
100+
* @returns
101+
*/
90102
checkForDuplicates(name: string): string {
91103
// check for empty name
92104
if (name === '' && typeof this.rtid === 'string') {

0 commit comments

Comments
 (0)