From f83bd69df68100aea0765295f93334af12fed355 Mon Sep 17 00:00:00 2001 From: Ryan Dang Date: Fri, 26 Jul 2019 14:44:56 -0400 Subject: [PATCH 1/6] tree structure implemented --- package/index.js | 1 - package/linkFiber.js | 8 ++++++++ package/package-lock.json | 5 ----- package/tree.js | 33 +++++++++++++++++++++++++++++++++ 4 files changed, 41 insertions(+), 6 deletions(-) create mode 100644 package/linkFiber.js delete mode 100644 package/package-lock.json create mode 100644 package/tree.js diff --git a/package/index.js b/package/index.js index 014aa2a85..ffd9b96c9 100644 --- a/package/index.js +++ b/package/index.js @@ -8,7 +8,6 @@ const unlinkState = require('./unlinkState')(snapShot); const getShot = () => snapShot.map(({ component }) => component.state); window.addEventListener('message', ({ data: { action, payload } }) => { - console.log(action, payload); if (action === 'jumpToSnap') { timeJump(payload); } else if (action === 'stepToSnap') { diff --git a/package/linkFiber.js b/package/linkFiber.js new file mode 100644 index 000000000..7f280d06c --- /dev/null +++ b/package/linkFiber.js @@ -0,0 +1,8 @@ +// links component state tree to library +// changes the setState method to also update our snapshot + +module.exports = (snapShotTree, mode) => { + return (container) => { + const fiber = container._reactRootContainer._internalRoot.current.child; + }; +}; diff --git a/package/package-lock.json b/package/package-lock.json deleted file mode 100644 index a52addbc6..000000000 --- a/package/package-lock.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "name": "react-time-travel", - "version": "1.0.1", - "lockfileVersion": 1 -} diff --git a/package/tree.js b/package/tree.js new file mode 100644 index 000000000..8c89b4471 --- /dev/null +++ b/package/tree.js @@ -0,0 +1,33 @@ +class Tree { + constructor(component) { + this.component = component; + this.children = []; + } + + appendChild(component) { + const child = new Tree(component); + this.children.push(child); + return child; + } + + print() { + const children = ['children: ']; + this.children.forEach((child) => { + children.push(child.component.state); + }); + if (children.length === 1) console.log(this.component.state); + else console.log(this.component.state, ...children); + this.children.forEach((child) => { + child.print(); + }); + } +} + +const tree = new Tree({ state: 1 }); +tree.appendChild({ state: 2 }); +const three = tree.appendChild({ state: 3 }); +three.appendChild({ state: 4 }); +three.appendChild({ state: 5 }); +tree.print(); + +module.exports = Tree; From 57b47a010ca7a883b854d1dc866463b9025d722f Mon Sep 17 00:00:00 2001 From: Ryan Dang Date: Sat, 27 Jul 2019 14:43:59 -0400 Subject: [PATCH 2/6] refactored code to work with react fiber --- package/index.js | 15 ++++----- package/linkFiber.js | 65 +++++++++++++++++++++++++++++++++++++-- package/package-lock.json | 13 ++++++++ package/package.json | 5 ++- package/timeJump.js | 32 ++++++++++++++++--- package/tree.js | 21 ++++++++----- 6 files changed, 126 insertions(+), 25 deletions(-) create mode 100644 package/package-lock.json diff --git a/package/index.js b/package/index.js index ffd9b96c9..5380335e2 100644 --- a/package/index.js +++ b/package/index.js @@ -1,23 +1,20 @@ -const snapShot = []; +const snapShot = { tree: null }; + const mode = { jumping: false }; -const linkState = require('./linkState')(snapShot, mode); +const linkFiber = require('./linkFiber')(snapShot, mode); const timeJump = require('./timeJump')(snapShot, mode); -const unlinkState = require('./unlinkState')(snapShot); -const getShot = () => snapShot.map(({ component }) => component.state); +const getTree = () => snapShot.tree.getCopy(); window.addEventListener('message', ({ data: { action, payload } }) => { if (action === 'jumpToSnap') { timeJump(payload); - } else if (action === 'stepToSnap') { - payload.forEach(snap => timeJump(snap)); } }); module.exports = { - linkState, - unlinkState, timeJump, - getShot, + linkFiber, + getTree, }; diff --git a/package/linkFiber.js b/package/linkFiber.js index 7f280d06c..cea87dd97 100644 --- a/package/linkFiber.js +++ b/package/linkFiber.js @@ -1,8 +1,69 @@ // links component state tree to library // changes the setState method to also update our snapshot +const Tree = require('./tree'); -module.exports = (snapShotTree, mode) => { +module.exports = (snap, mode) => { + let fiberRoot = null; + + function changeSetState(component) { + // check that setState hasn't been changed yet + if (component.setState.name === 'newSetState') return; + + // make a copy of setState + const oldSetState = component.setState.bind(component); + + function newSetState(state, callback = () => { }) { + // continue normal setState functionality, except add sending message middleware + oldSetState(state, () => { + updateSnapShotTree(); + callback(); + }); + } + + // replace component's setState so developer doesn't change syntax + component.setState = newSetState; + } + + function createTree(currentFiber, tree = new Tree('root')) { + if (!currentFiber) return tree; + + const { sibling, stateNode, child } = currentFiber; + + let nextTree = tree; + if (stateNode && stateNode.state) { + nextTree = tree.appendChild(stateNode); + changeSetState(stateNode); + } + + // iterate through siblings + createTree(sibling, tree); + // iterate through children + createTree(child, nextTree); + + return tree; + } + + // mutates origin tree and merges it with target + function mergeTrees(origin, target) { + target.children.forEach((child, i) => { + // check if child exists in origin tree + if (!origin.children[i]) { + origin.children.push(child); + } else { + mergeTrees(origin.children[i], child); + } + }); + // remove any unnecessary children + origin.children.splice(target.children.length); + } + + function updateSnapShotTree() { + const { current } = fiberRoot; + const curr = createTree(current); + mergeTrees(snap.tree, curr); + } return (container) => { - const fiber = container._reactRootContainer._internalRoot.current.child; + fiberRoot = container._reactRootContainer._internalRoot; + snap.tree = createTree(fiberRoot.current); }; }; diff --git a/package/package-lock.json b/package/package-lock.json new file mode 100644 index 000000000..6435aec19 --- /dev/null +++ b/package/package-lock.json @@ -0,0 +1,13 @@ +{ + "name": "react-time-travel", + "version": "1.0.1", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "lodash": { + "version": "4.17.15", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", + "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==" + } + } +} diff --git a/package/package.json b/package/package.json index dd5d8c98f..54f7fc307 100644 --- a/package/package.json +++ b/package/package.json @@ -20,5 +20,8 @@ "react-timetravel" ], "author": "Bryan Lee, Josh Kim, Ryan Dang, Sierra Swaby", - "license": "ISC" + "license": "ISC", + "dependencies": { + "lodash": "^4.17.15" + } } diff --git a/package/timeJump.js b/package/timeJump.js index d023b3d30..d3aefab8b 100644 --- a/package/timeJump.js +++ b/package/timeJump.js @@ -1,9 +1,31 @@ -module.exports = (snapShot, mode) => { - return (newSnapShot) => { - mode.jumping = true; - newSnapShot.forEach(async (state, i) => { - await snapShot[i].setStateAsync(state); +// traverses given tree by accessing children through coords array +function traverseTree(tree, coords) { + let curr = tree; + coords.forEach((coord) => { + curr = curr.children[coord]; + }); + return curr; +} + +module.exports = (origin, mode) => { + // recursively change state of tree + function jump(target, coords = []) { + const originNode = traverseTree(origin.tree, coords); + + // set the state of the origin tree + originNode.component.setState(target.component.state, () => { + // iterate through new children once state has been set + target.children.forEach((child, i) => { + jump(child, coords.concat(i)); + }); }); + } + + + return (target) => { + // setting mode disables setState from posting messages to window + mode.jumping = true; + jump(target); mode.jumping = false; }; }; diff --git a/package/tree.js b/package/tree.js index 8c89b4471..e0bd7df2c 100644 --- a/package/tree.js +++ b/package/tree.js @@ -1,6 +1,6 @@ class Tree { constructor(component) { - this.component = component; + this.component = (component === 'root') ? { state: 'root', setState: (partial, callback) => callback() } : component; this.children = []; } @@ -10,6 +10,18 @@ class Tree { return child; } + getCopy(copy = new Tree(null)) { + const { state } = this.component; + if (!copy.component) copy.component = { state }; + + // copy state of children + copy.children = this.children.map(child => new Tree({ state: child.component.state })); + + // copy children's children recursively + this.children.forEach((child, i) => child.getCopy(copy.children[i])); + return copy; + } + print() { const children = ['children: ']; this.children.forEach((child) => { @@ -23,11 +35,4 @@ class Tree { } } -const tree = new Tree({ state: 1 }); -tree.appendChild({ state: 2 }); -const three = tree.appendChild({ state: 3 }); -three.appendChild({ state: 4 }); -three.appendChild({ state: 5 }); -tree.print(); - module.exports = Tree; From b81ebfb85ec229f1d8ed7f8af57bf2df8577033f Mon Sep 17 00:00:00 2001 From: Ryan Dang Date: Sat, 27 Jul 2019 14:47:05 -0400 Subject: [PATCH 3/6] deleted obselete files --- package/linkState.js | 65 --------------------------------------- package/package-lock.json | 13 -------- package/package.json | 3 -- package/unlinkState.js | 12 -------- 4 files changed, 93 deletions(-) delete mode 100644 package/linkState.js delete mode 100644 package/package-lock.json delete mode 100644 package/unlinkState.js diff --git a/package/linkState.js b/package/linkState.js deleted file mode 100644 index 69e324b60..000000000 --- a/package/linkState.js +++ /dev/null @@ -1,65 +0,0 @@ -// links component state to library -// changes the setState method to also update our snapshot - -module.exports = (snapShot, mode) => { - const unlinkState = require('./unlinkState')(snapShot); - - // send message to window containing component states - // unless library is currently jumping through time - function sendSnapShot() { - if (mode.jumping) return; - const payload = snapShot.map(({ component }) => component.state); - window.postMessage({ - action: 'recordSnap', - payload, - }); - } - - function changeSetState(component) { - // make a copy of setState - const oldSetState = component.setState.bind(component); - - let first = true; - function newSetState(state, callback = () => { }) { - // if setState is being called for the first time, this conditional sends the initial snapshot - if (first) { - first = false; - sendSnapShot(); - } - - // continue normal setState functionality, except add sending message middleware - oldSetState(state, () => { - sendSnapShot(); - callback(); - }); - } - - // convert setState to promise - const setStateAsync = (state) => { - return new Promise(resolve => oldSetState(state, resolve)); - }; - - // add component to snapshot - snapShot.push({ component, setStateAsync }); - - // replace component's setState so developer doesn't change syntax - component.setState = newSetState; - } - - function changeComponentWillUnmount(component) { - let oldComponentWillUnmount = () => { }; - // if componentWillUnmount is defined, then create copy by value - if (typeof component.componentWillUnmount === 'function') oldComponentWillUnmount = component.componentWillUnmount.bind(component); - // replace componentWillUnmount - component.componentWillUnmount = () => { - oldComponentWillUnmount(); - unlinkState(component); - }; - } - - - return (component) => { - changeSetState(component); - changeComponentWillUnmount(component); - }; -}; diff --git a/package/package-lock.json b/package/package-lock.json deleted file mode 100644 index 6435aec19..000000000 --- a/package/package-lock.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "name": "react-time-travel", - "version": "1.0.1", - "lockfileVersion": 1, - "requires": true, - "dependencies": { - "lodash": { - "version": "4.17.15", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", - "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==" - } - } -} diff --git a/package/package.json b/package/package.json index 54f7fc307..a4f2bf06f 100644 --- a/package/package.json +++ b/package/package.json @@ -21,7 +21,4 @@ ], "author": "Bryan Lee, Josh Kim, Ryan Dang, Sierra Swaby", "license": "ISC", - "dependencies": { - "lodash": "^4.17.15" - } } diff --git a/package/unlinkState.js b/package/unlinkState.js deleted file mode 100644 index 0ca39468a..000000000 --- a/package/unlinkState.js +++ /dev/null @@ -1,12 +0,0 @@ -module.exports = (snapShot) => { - return (component) => { - let snapShotIndex = snapShot.length; - for (let i = 0; i < snapShot.length; i += 1) { - const { component: comp } = snapShot[i]; - if (component === comp) { - snapShotIndex = i; - } - } - snapShot.splice(snapShotIndex, 1); - }; -}; From 0d034dd09e978d81a8262c8a860cc7c0ae2a9f6f Mon Sep 17 00:00:00 2001 From: Ryan Dang Date: Sat, 27 Jul 2019 14:51:21 -0400 Subject: [PATCH 4/6] formatted message listener in index --- package/index.js | 9 +++++++-- package/tree.js | 5 +++++ 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/package/index.js b/package/index.js index 5380335e2..c5746182d 100644 --- a/package/index.js +++ b/package/index.js @@ -8,8 +8,13 @@ const timeJump = require('./timeJump')(snapShot, mode); const getTree = () => snapShot.tree.getCopy(); window.addEventListener('message', ({ data: { action, payload } }) => { - if (action === 'jumpToSnap') { - timeJump(payload); + switch (action) { + case 'jumpToSnap': + timeJump(payload); + break; + case 'stepToSnap': + const { speed, steps } = payload; + default: } }); diff --git a/package/tree.js b/package/tree.js index e0bd7df2c..a8c28d5fe 100644 --- a/package/tree.js +++ b/package/tree.js @@ -1,5 +1,8 @@ class Tree { constructor(component) { + // special case when component is root + // give it a special state = 'root' + // a setState function that just calls the callback instantly this.component = (component === 'root') ? { state: 'root', setState: (partial, callback) => callback() } : component; this.children = []; } @@ -10,6 +13,7 @@ class Tree { return child; } + // deep copies only the state of each component and creates a new tree getCopy(copy = new Tree(null)) { const { state } = this.component; if (!copy.component) copy.component = { state }; @@ -22,6 +26,7 @@ class Tree { return copy; } + // print out the tree in the console print() { const children = ['children: ']; this.children.forEach((child) => { From 69ef5b8c292d14a030672431ae7ff82fd41812d3 Mon Sep 17 00:00:00 2001 From: Ryan Dang Date: Sat, 27 Jul 2019 14:59:54 -0400 Subject: [PATCH 5/6] added a step route for messaging --- package/index.js | 7 +++++-- package/linkFiber.js | 16 +--------------- package/package.json | 2 +- 3 files changed, 7 insertions(+), 18 deletions(-) diff --git a/package/index.js b/package/index.js index c5746182d..4aa6b2f41 100644 --- a/package/index.js +++ b/package/index.js @@ -12,8 +12,11 @@ window.addEventListener('message', ({ data: { action, payload } }) => { case 'jumpToSnap': timeJump(payload); break; - case 'stepToSnap': - const { speed, steps } = payload; + case 'stepToSnap': { + const { steps, speed } = payload; + console.log('i would like to step through here'); + break; + } default: } }); diff --git a/package/linkFiber.js b/package/linkFiber.js index cea87dd97..6b04076c1 100644 --- a/package/linkFiber.js +++ b/package/linkFiber.js @@ -43,24 +43,10 @@ module.exports = (snap, mode) => { return tree; } - // mutates origin tree and merges it with target - function mergeTrees(origin, target) { - target.children.forEach((child, i) => { - // check if child exists in origin tree - if (!origin.children[i]) { - origin.children.push(child); - } else { - mergeTrees(origin.children[i], child); - } - }); - // remove any unnecessary children - origin.children.splice(target.children.length); - } - function updateSnapShotTree() { const { current } = fiberRoot; const curr = createTree(current); - mergeTrees(snap.tree, curr); + snap.tree = curr; } return (container) => { fiberRoot = container._reactRootContainer._internalRoot; diff --git a/package/package.json b/package/package.json index a4f2bf06f..dd5d8c98f 100644 --- a/package/package.json +++ b/package/package.json @@ -20,5 +20,5 @@ "react-timetravel" ], "author": "Bryan Lee, Josh Kim, Ryan Dang, Sierra Swaby", - "license": "ISC", + "license": "ISC" } From 7363fe6f40a0e058e934962a4a3f5209edb35af5 Mon Sep 17 00:00:00 2001 From: Ryan Dang Date: Sat, 27 Jul 2019 15:10:52 -0400 Subject: [PATCH 6/6] npm package now sends message to window everytime state is updated --- package/linkFiber.js | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/package/linkFiber.js b/package/linkFiber.js index 6b04076c1..29ddab9cb 100644 --- a/package/linkFiber.js +++ b/package/linkFiber.js @@ -4,6 +4,17 @@ const Tree = require('./tree'); module.exports = (snap, mode) => { let fiberRoot = null; + let first = true; + + function sendSnapshot() { + // don't send messages while jumping + if (mode.jumping) return; + const payload = snap.tree.getCopy(); + window.postMessage({ + action: 'recordSnap', + payload, + }); + } function changeSetState(component) { // check that setState hasn't been changed yet @@ -14,8 +25,14 @@ module.exports = (snap, mode) => { function newSetState(state, callback = () => { }) { // continue normal setState functionality, except add sending message middleware + if (first) { + updateSnapShotTree(); + sendSnapshot(); + first = false; + } oldSetState(state, () => { updateSnapShotTree(); + sendSnapshot(); callback(); }); } @@ -45,11 +62,11 @@ module.exports = (snap, mode) => { function updateSnapShotTree() { const { current } = fiberRoot; - const curr = createTree(current); - snap.tree = curr; + snap.tree = createTree(current); } return (container) => { - fiberRoot = container._reactRootContainer._internalRoot; - snap.tree = createTree(fiberRoot.current); + const { _reactRootContainer: { _internalRoot } } = container; + fiberRoot = _internalRoot; + updateSnapShotTree(); }; };