mirror of
https://github.com/jlfwong/speedscope.git
synced 2024-12-26 20:04:37 +03:00
Hot reloading WIP
This commit is contained in:
parent
c9f0cf83c1
commit
a51382c525
101
application.tsx
Normal file
101
application.tsx
Normal file
@ -0,0 +1,101 @@
|
||||
import {h, Component} from 'preact'
|
||||
import {StyleSheet, css} from 'aphrodite'
|
||||
import {ReloadableComponent} from './reloadable'
|
||||
|
||||
import {importFromBGFlameGraph} from './import/bg-flamegraph'
|
||||
import {importFromStackprof} from './import/stackprof'
|
||||
|
||||
import {Profile} from './profile'
|
||||
import {Flamechart, FlamechartView} from './flamechart'
|
||||
|
||||
const enum SortOrder {
|
||||
CHRONO,
|
||||
ALPHA
|
||||
}
|
||||
|
||||
interface ApplicationState {
|
||||
profile: Profile | null
|
||||
flamechart: Flamechart | null
|
||||
sortedFlamechart: Flamechart | null
|
||||
sortOrder: SortOrder
|
||||
}
|
||||
|
||||
export class Application extends ReloadableComponent<{}, ApplicationState, {}> {
|
||||
constructor() {
|
||||
super()
|
||||
this.state = {
|
||||
profile: null,
|
||||
flamechart: null,
|
||||
sortedFlamechart: null,
|
||||
sortOrder: SortOrder.CHRONO
|
||||
}
|
||||
}
|
||||
|
||||
onDrop = (ev: DragEvent) => {
|
||||
const file = ev.dataTransfer.files.item(0)
|
||||
const reader = new FileReader
|
||||
reader.addEventListener('loadend', () => {
|
||||
const profile = file.name.endsWith('json') ? importFromStackprof(reader.result) : importFromBGFlameGraph(reader.result)
|
||||
const flamechart = new Flamechart(profile)
|
||||
const sortedFlamechart = new Flamechart(profile.sortedAlphabetically())
|
||||
this.setState({profile, flamechart, sortedFlamechart})
|
||||
})
|
||||
reader.readAsText(file)
|
||||
ev.preventDefault()
|
||||
}
|
||||
|
||||
onDragOver = (ev: DragEvent) => {
|
||||
ev.preventDefault()
|
||||
}
|
||||
|
||||
onWindowKeyPress = (ev: KeyboardEvent) => {
|
||||
if (ev.key == 'a') {
|
||||
this.setState({
|
||||
sortOrder: this.state.sortOrder === SortOrder.CHRONO ? SortOrder.ALPHA : SortOrder.CHRONO
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
onWindowResize = () => {
|
||||
this.forceUpdate()
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
window.addEventListener('resize', this.onWindowResize)
|
||||
// TODO(jlfwong): for this to be safely embeddable, there'll need to be some
|
||||
// way of specify event focus.
|
||||
window.addEventListener('keypress', this.onWindowKeyPress)
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
window.removeEventListener('resize', this.onWindowResize)
|
||||
window.removeEventListener('keypress', this.onWindowKeyPress)
|
||||
}
|
||||
|
||||
flamechartView: FlamechartView | null
|
||||
flamechartRef = (view: FlamechartView | null) => this.flamechartView = view
|
||||
subcomponents() {
|
||||
return {
|
||||
flamechart: this.flamechartView
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const {flamechart, sortedFlamechart, sortOrder} = this.state
|
||||
const flamechartToView = sortOrder == SortOrder.CHRONO ? flamechart : sortedFlamechart
|
||||
|
||||
return <div onDrop={this.onDrop} onDragOver={this.onDragOver} className={css(style.root)}>
|
||||
{flamechartToView &&
|
||||
<FlamechartView ref={this.flamechartRef} flamechart={flamechartToView} />}
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
||||
const style = StyleSheet.create({
|
||||
root: {
|
||||
width: '100vw',
|
||||
height: '100vh',
|
||||
overflow: 'hidden'
|
||||
}
|
||||
})
|
||||
|
@ -1,5 +1,6 @@
|
||||
import {h, Component} from 'preact'
|
||||
import {StyleSheet, css} from 'aphrodite'
|
||||
import {ReloadableComponent} from './reloadable'
|
||||
|
||||
import {Profile, Frame, CallTreeNode} from './profile'
|
||||
import * as regl from 'regl'
|
||||
@ -647,7 +648,7 @@ interface FlamechartViewState {
|
||||
logicalSpaceMouse: Vec2
|
||||
}
|
||||
|
||||
export class FlamechartView extends Component<FlamechartViewProps, FlamechartViewState> {
|
||||
export class FlamechartView extends ReloadableComponent<FlamechartViewProps, FlamechartViewState> {
|
||||
container: HTMLDivElement | null = null
|
||||
|
||||
constructor() {
|
||||
|
@ -5,7 +5,7 @@
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"serve": "parcel -o dev.html -d dist/dev",
|
||||
"release": "parcel build speedscope.tsx"
|
||||
"release": "tsc --noEmit && parcel build speedscope.tsx"
|
||||
},
|
||||
"author": "",
|
||||
"license": "MIT",
|
||||
|
51
reloadable.tsx
Normal file
51
reloadable.tsx
Normal file
@ -0,0 +1,51 @@
|
||||
import {Component} from 'preact'
|
||||
|
||||
interface SerializedComponent<S, I> {
|
||||
state: S
|
||||
internal: I | null
|
||||
serializedSubcomponents: {[key: string]: any}
|
||||
}
|
||||
|
||||
export abstract class ReloadableComponent<P, S, I> extends Component<P, S> {
|
||||
serialize(): SerializedComponent<S, I> {
|
||||
const serializedSubcomponents: {[key: string]: any} = Object.create(null)
|
||||
|
||||
const subcomponents = this.subcomponents()
|
||||
for (const key in subcomponents) {
|
||||
const val = subcomponents[key]
|
||||
if (val && val instanceof ReloadableComponent) {
|
||||
serializedSubcomponents[key] = val.serialize()
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
state: this.state,
|
||||
internal: this.serializeInternal(),
|
||||
serializedSubcomponents,
|
||||
}
|
||||
}
|
||||
rehydrate(serialized: SerializedComponent<S, I>) {
|
||||
this.setState(serialized.state)
|
||||
if (serialized.internal) {
|
||||
this.rehydrateInternal(serialized.internal)
|
||||
}
|
||||
|
||||
const subcomponents = this.subcomponents()
|
||||
for (const key in subcomponents) {
|
||||
const val = subcomponents[key]
|
||||
const data = serialized.serializedSubcomponents[key]
|
||||
if (data && val && val instanceof ReloadableComponent) {
|
||||
subcomponents.serialize(data)
|
||||
}
|
||||
}
|
||||
}
|
||||
serializeInternal(): I | null {
|
||||
return null
|
||||
}
|
||||
rehydrateInternal(internal: I) {}
|
||||
subcomponents(): {[key: string]: any} {
|
||||
return Object.create(null)
|
||||
}
|
||||
}
|
||||
|
||||
|
105
speedscope.tsx
105
speedscope.tsx
@ -1,93 +1,24 @@
|
||||
import {h, render, Component} from 'preact'
|
||||
import {StyleSheet, css} from 'aphrodite'
|
||||
import {h, render} from 'preact'
|
||||
import {Application} from'./application'
|
||||
|
||||
import {importFromBGFlameGraph} from './import/bg-flamegraph'
|
||||
import {importFromStackprof} from './import/stackprof'
|
||||
|
||||
import {Profile} from './profile'
|
||||
import {Flamechart, FlamechartView} from './flamechart'
|
||||
|
||||
const enum SortOrder {
|
||||
CHRONO,
|
||||
ALPHA
|
||||
}
|
||||
|
||||
interface ApplicationState {
|
||||
profile: Profile | null
|
||||
flamechart: Flamechart | null
|
||||
sortedFlamechart: Flamechart | null
|
||||
sortOrder: SortOrder
|
||||
}
|
||||
|
||||
class Application extends Component<{}, ApplicationState> {
|
||||
constructor() {
|
||||
super()
|
||||
this.state = {
|
||||
profile: null,
|
||||
flamechart: null,
|
||||
sortedFlamechart: null,
|
||||
sortOrder: SortOrder.CHRONO
|
||||
let app: Application | null = null
|
||||
const retained = (window as any)['__retained__'] as any
|
||||
declare const module: any
|
||||
if (module.hot) {
|
||||
module.hot.dispose(() => {
|
||||
if (app) {
|
||||
(window as any)['__retained__'] = app.serialize()
|
||||
}
|
||||
}
|
||||
})
|
||||
module.hot.accept()
|
||||
}
|
||||
|
||||
onDrop = (ev: DragEvent) => {
|
||||
const file = ev.dataTransfer.files.item(0)
|
||||
const reader = new FileReader
|
||||
reader.addEventListener('loadend', () => {
|
||||
const profile = file.name.endsWith('json') ? importFromStackprof(reader.result) : importFromBGFlameGraph(reader.result)
|
||||
const flamechart = new Flamechart(profile)
|
||||
const sortedFlamechart = new Flamechart(profile.sortedAlphabetically())
|
||||
this.setState({profile, flamechart, sortedFlamechart})
|
||||
})
|
||||
reader.readAsText(file)
|
||||
ev.preventDefault()
|
||||
}
|
||||
|
||||
onDragOver = (ev: DragEvent) => {
|
||||
ev.preventDefault()
|
||||
}
|
||||
|
||||
onWindowKeyPress = (ev: KeyboardEvent) => {
|
||||
if (ev.key == 'a') {
|
||||
this.setState({
|
||||
sortOrder: this.state.sortOrder === SortOrder.CHRONO ? SortOrder.ALPHA : SortOrder.CHRONO
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
onWindowResize = () => {
|
||||
this.forceUpdate()
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
window.addEventListener('resize', this.onWindowResize)
|
||||
// TODO(jlfwong): for this to be safely embeddable, there'll need to be some
|
||||
// way of specify event focus.
|
||||
window.addEventListener('keypress', this.onWindowKeyPress)
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
window.removeEventListener('resize', this.onWindowResize)
|
||||
window.removeEventListener('keypress', this.onWindowKeyPress)
|
||||
}
|
||||
|
||||
render() {
|
||||
const {flamechart, sortedFlamechart, sortOrder} = this.state
|
||||
const flamechartToView = sortOrder == SortOrder.CHRONO ? flamechart : sortedFlamechart
|
||||
|
||||
return <div onDrop={this.onDrop} onDragOver={this.onDragOver} className={css(style.root)}>
|
||||
{flamechartToView &&
|
||||
<FlamechartView flamechart={flamechartToView} />}
|
||||
</div>
|
||||
function ref(instance: Application | null) {
|
||||
app = instance
|
||||
if (instance && retained) {
|
||||
console.log('rehydrating: ', retained)
|
||||
instance.rehydrate(retained)
|
||||
}
|
||||
}
|
||||
|
||||
const style = StyleSheet.create({
|
||||
root: {
|
||||
width: '100vw',
|
||||
height: '100vh',
|
||||
overflow: 'hidden'
|
||||
}
|
||||
})
|
||||
|
||||
render(<Application />, document.body)
|
||||
render(<Application ref={ref}/>, document.body, document.body.lastElementChild || undefined)
|
Loading…
Reference in New Issue
Block a user