Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/reduce bundle size #106

Open
wants to merge 29 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
78fa2fc
feat: break into smaller bundles based on pages
icyJoseph Jul 3, 2020
7b95d18
feat: lazy load Hexapod and use semantic routing to display components
icyJoseph Jul 6, 2020
bb32e8d
chore: Add jsdom-sixteen and bump up @testing-library/react
icyJoseph Jul 6, 2020
971eb20
test: Use waitFor to deal with Suspenseful modules
icyJoseph Jul 6, 2020
fd2f65b
test: Update test, the plot no longer renders hidden on landing page
icyJoseph Jul 6, 2020
66fd770
feat: Start to load Plotly before first DOM commit
icyJoseph Jul 6, 2020
d178edb
chore: Configure jest collectCoverageFrom
icyJoseph Jul 7, 2020
fd9e0e9
feat: Implement a sleep util w/ test
icyJoseph Jul 7, 2020
f53cc02
test: Pass testId to mocked Plotly div
icyJoseph Jul 7, 2020
9af7154
feat: Lazy load Plotly with retry option
icyJoseph Jul 7, 2020
c10bfd4
Merge branch 'master' into feat/reduce-bundle-size
icyJoseph Jul 7, 2020
2f5ccd4
refactor: Shrink App's render method
icyJoseph Jul 7, 2020
f0c087e
test: fix coverage collect globbing pattern
icyJoseph Jul 7, 2020
429a4a9
feat: render HexapodPlot ASAP, but hide it if not needed
icyJoseph Jul 7, 2020
195cb69
Merge branch 'master' into feat/reduce-bundle-size
icyJoseph Jul 7, 2020
ffc108a
fix: Update ikParams and PatternPose function names
icyJoseph Jul 7, 2020
da2d6f5
feat: Redirect back to Root on 404
icyJoseph Jul 8, 2020
5ad2a60
feat: Less aggressive preloading
icyJoseph Jul 8, 2020
057a30d
Merge branch 'master' into feat/reduce-bundle-size
icyJoseph Jul 8, 2020
1f40226
refactor: Remove duplicated function names
icyJoseph Jul 8, 2020
c077bf2
refactor: Various fixes, and code quality improvements
icyJoseph Jul 8, 2020
f9b9bb6
refactor: Build a hooks -> render props -> HoC pattern to increase co…
icyJoseph Jul 8, 2020
7d9f154
refactor: Hoist handlers Provider to App
icyJoseph Jul 8, 2020
3dea501
refactor: LegPattern Page does not need params props anymore
icyJoseph Jul 8, 2020
a4c652a
refactor: Breakout HexapodParams into a Provider pattern
icyJoseph Jul 8, 2020
84d7856
chore: Merge master including global state shrinking
icyJoseph Jul 8, 2020
30a110c
fix: Update PoseTable source of props and AlertBox message body
icyJoseph Jul 8, 2020
edeb6e3
refactor: Turn App into a functional component
icyJoseph Jul 8, 2020
a51f61f
refactor: Move away custom hooks
icyJoseph Jul 8, 2020
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
15 changes: 12 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,14 @@
"private": true,
"dependencies": {
"@testing-library/jest-dom": "^4.2.4",
"@testing-library/react": "^9.3.2",
"@testing-library/react": "^10.4.4",
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for bumping this up 👍 💯

"@testing-library/user-event": "^7.1.2",
"jest-canvas-mock": "^2.2.0",
"plotly.js-gl3d-dist-min": "^1.54.2",
"react": "^16.13.1",
"react-dom": "^16.13.1",
"react-ga": "^3.0.0",
"react-icons": "^3.10.0",
"react-markdown": "^4.3.1",
"react-plotly.js": "^2.4.0",
"react-router-dom": "^5.2.0",
"react-scripts": "3.4.1",
Expand All @@ -22,7 +21,8 @@
"analyze": "source-map-explorer 'build/static/js/*.js'",
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"test": "react-scripts test --env=jest-environment-jsdom-sixteen",
"test:coverage": "react-scripts test --env=jest-environment-jsdom-sixteen --collectCoverage --watchAll=false",
"eject": "react-scripts eject"
},
"eslintConfig": {
Expand All @@ -41,6 +41,15 @@
]
},
"devDependencies": {
"jest-environment-jsdom-sixteen": "^1.0.3",
"prettier": "2.0.5"
},
"jest": {
"collectCoverageFrom": [
"src/{components,hexapod}/**/*.{js,jsx,ts,tsx}",
"src/loadables.js",
"src/App.js",
"src/routes.js"
]
}
}
206 changes: 30 additions & 176 deletions src/App.js
Original file line number Diff line number Diff line change
@@ -1,186 +1,40 @@
import React from "react"
import { BrowserRouter as Router, Route, Switch } from "react-router-dom"
import ReactGA from "react-ga"
import { VirtualHexapod, getNewPlotParams } from "./hexapod"
import * as defaults from "./templates"
import { SECTION_NAMES, PATHS } from "./components/vars"
import { Nav, NavDetailed, HexapodPlot, DimensionsWidget } from "./components"
import {
ForwardKinematicsPage,
InverseKinematicsPage,
LandingPage,
LegPatternPage,
WalkingGaitsPage,
} from "./components/pages"
import React, { useEffect } from "react"
import { BrowserRouter as Router, Route, Switch, Redirect } from "react-router-dom"
import { VirtualHexapod } from "./hexapod"
import { PATH_LINKS } from "./components/vars"
import { Nav } from "./components"

ReactGA.initialize("UA-170794768-1", {
//debug: true,
//testMode: process.env.NODE_ENV === 'test',
gaOptions: { siteSpeedSampleRate: 100 },
})
import Routes from "./routes"
import { HandlersProvider } from "./components/providers/Handlers"
import { HexapodParamsProvider } from "./components/providers/HexapodParams"

class App extends React.Component {
state = {
currentPage: SECTION_NAMES.LandingPage,
inHexapodPage: false,
import { useHexapodParams } from "./components/hooks/useHexapodParams"
import { usePlot } from "./components/hooks/usePlot"

hexapodParams: {
dimensions: defaults.DEFAULT_DIMENSIONS,
pose: defaults.DEFAULT_POSE,
},
function App() {
const { hexapodParams, ...handlers } = useHexapodParams()
const { plot, updatePlotWithHexapod, logCameraView } = usePlot()

plot: {
data: defaults.DATA,
layout: defaults.LAYOUT,
latestCameraView: defaults.CAMERA_VIEW,
revisionCounter: 0,
},
}
useEffect(() => {
const hexapod = new VirtualHexapod(hexapodParams.dimensions, hexapodParams.pose)
updatePlotWithHexapod(hexapod)
}, [hexapodParams, updatePlotWithHexapod])

/* * * * * * * * * * * * * *
* Handle page load
* * * * * * * * * * * * * */

onPageLoad = pageName => {
ReactGA.pageview(window.location.pathname + window.location.search)
this.setState({ currentPage: pageName })

if (pageName === SECTION_NAMES.landingPage) {
this.setState({ inHexapodPage: false })
return
}

this.setState({
inHexapodPage: true,
hexapodParams: { ...this.state.hexapodParams, pose: defaults.DEFAULT_POSE },
})

this.updatePlot(this.state.hexapodParams.dimensions, defaults.DEFAULT_POSE)
}

/* * * * * * * * * * * * * *
* Handle plot update
* * * * * * * * * * * * * */

updatePlotWithHexapod = hexapod => {
if (hexapod === null || hexapod === undefined || !hexapod.foundSolution) {
return
}

const [data, layout] = getNewPlotParams(hexapod, this.state.plot.latestCameraView)
this.setState({
plot: {
...this.state.plot,
data,
layout,
revisionCounter: this.state.plot.revisionCounter + 1,
},
hexapodParams: {
dimensions: hexapod.dimensions,
pose: hexapod.pose,
},
})
}

logCameraView = relayoutData => {
const newCameraView = relayoutData["scene.camera"]
const plot = { ...this.state.plot, latestCameraView: newCameraView }
this.setState({ ...this.state, plot: plot })
}

updatePlot = (dimensions, pose) => {
const newHexapodModel = new VirtualHexapod(dimensions, pose)
this.updatePlotWithHexapod(newHexapodModel)
}

updateDimensions = dimensions =>
this.updatePlot(dimensions, this.state.hexapodParams.pose)

updatePose = pose => this.updatePlot(this.state.hexapodParams.dimensions, pose)

/* * * * * * * * * * * * * *
* Control display of widgets
* * * * * * * * * * * * * */

mightShowDetailedNav = () => (this.state.inHexapodPage ? <NavDetailed /> : null)

mightShowDimensions = () => {
if (this.state.inHexapodPage) {
return (
<DimensionsWidget
params={{ dimensions: this.state.hexapodParams.dimensions }}
onUpdate={this.updateDimensions}
/>
)
}
}

mightShowPlot = () => (
<div className={this.state.inHexapodPage ? "plot border" : "no-display"}>
<HexapodPlot
data={this.state.plot.data}
layout={this.state.plot.layout}
onRelayout={this.logCameraView}
revision={this.state.plot.revisionCounter}
/>
</div>
)

/* * * * * * * * * * * * * *
* Pages
* * * * * * * * * * * * * */

showPage = () => (
<Switch>
<Route path="/" exact>
<LandingPage onMount={this.onPageLoad} />
</Route>
<Route path={PATHS.forwardKinematics.path}>
<ForwardKinematicsPage
params={{ pose: this.state.hexapodParams.pose }}
onUpdate={this.updatePose}
onMount={this.onPageLoad}
/>
</Route>
<Route path={PATHS.inverseKinematics.path}>
<InverseKinematicsPage
params={{
dimensions: this.state.hexapodParams.dimensions,
}}
onUpdate={this.updatePlotWithHexapod}
onMount={this.onPageLoad}
/>
</Route>
<Route path={PATHS.legPatterns.path}>
<LegPatternPage onUpdate={this.updatePose} onMount={this.onPageLoad} />
</Route>
<Route path={PATHS.walkingGaits.path}>
<WalkingGaitsPage
params={{
dimensions: this.state.hexapodParams.dimensions,
}}
onUpdate={this.updatePose}
onMount={this.onPageLoad}
/>
</Route>
</Switch>
)

/* * * * * * * * * * * * * *
* Layout
* * * * * * * * * * * * * */

render = () => (
return (
<Router>
<Nav />
<div className="main content">
<div className="sidebar column-container cell">
{this.mightShowDimensions()}
{this.showPage()}
</div>
{this.mightShowPlot()}
</div>
{this.mightShowDetailedNav()}
<Switch>
<Route path={PATH_LINKS.map(({ path }) => path)} exact>
<HexapodParamsProvider {...hexapodParams}>
<HandlersProvider {...handlers}>
<Routes {...plot} onRelayout={logCameraView} />
</HandlersProvider>
</HexapodParamsProvider>
</Route>
<Route>
<Redirect to="/" />
</Route>
</Switch>
</Router>
)
}
Expand Down
4 changes: 3 additions & 1 deletion src/__mocks__/react-plotly.js/factory.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import React from "react"

const createPlotlyComponent = () => ({ style }) => <div style={style}></div>
const createPlotlyComponent = () => ({ style, testId }) => (
<div style={style} data-testid={testId}></div>
)

export default createPlotlyComponent
12 changes: 8 additions & 4 deletions src/components/DimensionsWidget.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@ import NumberInputField from "./generic/NumberInputField"
import { Card, BasicButton, ToggleSwitch } from "./generic/SmallWidgets"
import { DEFAULT_DIMENSIONS } from "../templates"
import { SECTION_NAMES, DIMENSION_NAMES, RESET_LABEL, RANGE_PARAMS } from "./vars"
import { withHandlers } from "./providers/Handlers"
import { withHexapodParams } from "./providers/HexapodParams"

class DimensionsWidget extends Component {
export class DimensionsWidget extends Component {
sectionName = SECTION_NAMES.dimensions
state = { isFine: true, granularity: 1 }

Expand All @@ -14,7 +16,7 @@ class DimensionsWidget extends Component {

reset = () => {
const dimensions = DEFAULT_DIMENSIONS
this.props.onUpdate(dimensions)
this.props.onUpdateDimensions(dimensions)
}

toggleMode = () => {
Expand All @@ -28,7 +30,7 @@ class DimensionsWidget extends Component {

updateDimensions = (name, value) => {
const dimensions = { ...this.props.params.dimensions, [name]: value }
this.props.onUpdate(dimensions)
this.props.onUpdateDimensions(dimensions)
}

updateFieldState = (name, value) => {
Expand Down Expand Up @@ -76,4 +78,6 @@ class DimensionsWidget extends Component {
)
}

export default DimensionsWidget
export default withHexapodParams(withHandlers(DimensionsWidget), ({ dimensions }) => ({
params: { dimensions },
}))
70 changes: 61 additions & 9 deletions src/components/HexapodPlot.js
Original file line number Diff line number Diff line change
@@ -1,20 +1,72 @@
import React from "react"
import Plotly from "plotly.js-gl3d-dist-min"
import React, { useCallback, useLayoutEffect, useState, useRef } from "react"
import createPlotlyComponent from "react-plotly.js/factory"
const Plot = createPlotlyComponent(Plotly)

const HexapodPlot = props => {
import { sleep } from "../utils"

const PlotlyPromise = () =>
import("plotly.js-gl3d-dist-min").then(Plotly => Plotly.default)

export const HexapodPlot = ({ data, layout, onRelayout, revision, promise }) => {
const ref = useRef()
const loadingRef = useRef()
const promiseRef = useRef(promise)
const [ready, setReady] = useState(false)
const [error, setError] = useState(false)

const loader = useCallback(() => {
setError(false)

promiseRef
.current()
.then(Plotly => {
ref.current = createPlotlyComponent(Plotly)
})
.then(sleep(250))
.then(() => {
if (loadingRef.current) {
setReady(true)
}
})
.catch(() => setError(true))
}, [])

useLayoutEffect(() => {
loader()
}, [loader])

if (error) {
return (
<>
<h2>Error Loading Plotly</h2>
<button className="button" onClick={loader}>
Retry
</button>
</>
)
}

if (!ready) {
return <h2 ref={loadingRef}>Loading...</h2>
}

const Plot = ref.current

return (
<Plot
data={props.data}
layout={props.layout}
data={data}
layout={layout}
useResizeHandler={true}
style={{ height: "100%", width: "100%" }}
config={{ displaylogo: false, responsive: true }}
onRelayout={props.onRelayout}
revision={props.revision}
onRelayout={onRelayout}
revision={revision}
testId="plot"
/>
)
}

export default HexapodPlot
const HexapodPlotWithPlotlyPromise = props => (
<HexapodPlot {...props} promise={PlotlyPromise} />
)

export default HexapodPlotWithPlotlyPromise
Loading