WiP #broken

This commit is contained in:
Thomas Nordquist
2019-01-08 16:39:18 +01:00
parent 25cc7ad277
commit b44f352804
16 changed files with 257 additions and 164 deletions

32
app/package-lock.json generated
View File

@@ -4172,14 +4172,15 @@
} }
}, },
"react": { "react": {
"version": "16.3.2", "version": "16.7.0",
"resolved": "https://registry.npmjs.org/react/-/react-16.3.2.tgz", "resolved": "https://registry.npmjs.org/react/-/react-16.7.0.tgz",
"integrity": "sha512-o5GPdkhciQ3cEph6qgvYB7LTOHw/GB0qRI6ZFNugj49qJCFfgHwVNjZ5u+b7nif4vOeMIOuYj3CeYe2IBD74lg==", "integrity": "sha512-StCz3QY8lxTb5cl2HJxjwLFOXPIFQp+p+hxQfc8WE0QiLfCtIlKj8/+5tjjKm8uSTlAW+fCPaavGFS06V9Ar3A==",
"dev": true,
"requires": { "requires": {
"fbjs": "^0.8.16",
"loose-envify": "^1.1.0", "loose-envify": "^1.1.0",
"object-assign": "^4.1.1", "object-assign": "^4.1.1",
"prop-types": "^15.6.0" "prop-types": "^15.6.2",
"scheduler": "^0.12.0"
} }
}, },
"react-base16-styling": { "react-base16-styling": {
@@ -4194,14 +4195,15 @@
} }
}, },
"react-dom": { "react-dom": {
"version": "16.3.3", "version": "16.7.0",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.3.3.tgz", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.7.0.tgz",
"integrity": "sha512-ALCp7ZbSGkqRDtQoZozKVNgwXMxbxf/IGOUMC2A0yF6JHeZrS8e2cOotPT87Vf4b7PKCuUVKU4/RDEXxToA/yA==", "integrity": "sha512-D0Ufv1ExCAmF38P2Uh1lwpminZFRXEINJe53zRAbm4KPwSyd6DY/uDoS0Blj9jvPpn1+wivKpZYc8aAAN/nAkg==",
"dev": true,
"requires": { "requires": {
"fbjs": "^0.8.16",
"loose-envify": "^1.1.0", "loose-envify": "^1.1.0",
"object-assign": "^4.1.1", "object-assign": "^4.1.1",
"prop-types": "^15.6.0" "prop-types": "^15.6.2",
"scheduler": "^0.12.0"
} }
}, },
"react-event-listener": { "react-event-listener": {
@@ -4438,6 +4440,16 @@
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
}, },
"scheduler": {
"version": "0.12.0",
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.12.0.tgz",
"integrity": "sha512-t7MBR28Akcp4Jm+QoR63XgAi9YgCUmgvDHqf5otgAj4QvdoBE4ImCX0ffehefePPG+aitiYHp0g/mW6s4Tp+dw==",
"dev": true,
"requires": {
"loose-envify": "^1.1.0",
"object-assign": "^4.1.1"
}
},
"schema-utils": { "schema-utils": {
"version": "0.4.7", "version": "0.4.7",
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-0.4.7.tgz", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-0.4.7.tgz",

View File

@@ -15,8 +15,9 @@
"@types/react": "^16.7.18", "@types/react": "^16.7.18",
"@types/react-dom": "^16.0.11", "@types/react-dom": "^16.0.11",
"@types/react-redux": "^6.0.12", "@types/react-redux": "^6.0.12",
"react": "^16.7.0",
"react-dom": "^16.7.0",
"react-redux": "^6.0.0", "react-redux": "^6.0.0",
"react-transition-group": "^2.5.2",
"redux": "^4.0.1", "redux": "^4.0.1",
"webpack-livereload-plugin": "^2.2.0" "webpack-livereload-plugin": "^2.2.0"
}, },
@@ -31,8 +32,6 @@
"jquery": "^3.3.1", "jquery": "^3.3.1",
"lodash.throttle": "^4.1.1", "lodash.throttle": "^4.1.1",
"moving-average": "^1.0.0", "moving-average": "^1.0.0",
"react": "^16.3.2",
"react-dom": "^16.3.3",
"react-json-view": "^1.19.1", "react-json-view": "^1.19.1",
"sha1": "^1.1.1", "sha1": "^1.1.1",
"socket.io-client": "^2.2.0", "socket.io-client": "^2.2.0",

View File

@@ -1,4 +1,4 @@
import { ActionTypes } from '../reducers' import { ActionTypes, NodeOrder } from '../reducers'
export const setAutoExpandLimit = (autoExpandLimit: number = 0) => { export const setAutoExpandLimit = (autoExpandLimit: number = 0) => {
return { return {
@@ -12,3 +12,10 @@ export const toggleSettingsVisibility = () => {
type: ActionTypes.toggleSettingsVisibility, type: ActionTypes.toggleSettingsVisibility,
} }
} }
export const setNodeOrder = (nodeOrder: NodeOrder = NodeOrder.none) => {
return {
nodeOrder,
type: ActionTypes.setNodeOrder,
}
}

View File

@@ -14,7 +14,7 @@ import { Typography, InputBase, Input, InputLabel } from '@material-ui/core'
import { withStyles, StyleRulesCallback } from '@material-ui/core/styles' import { withStyles, StyleRulesCallback } from '@material-ui/core/styles'
import { settingsActions } from '../actions' import { settingsActions } from '../actions'
import { AppState } from '../reducers' import { AppState, NodeOrder } from '../reducers'
import { connect } from 'react-redux' import { connect } from 'react-redux'
import { bindActionCreators } from 'redux' import { bindActionCreators } from 'redux'
@@ -40,16 +40,14 @@ const styles: StyleRulesCallback = theme => ({
interface Props { interface Props {
actions?: any actions?: any
autoExpandLimit: number, autoExpandLimit: number
visible: boolean, visible: boolean
store?: any, store?: any
classes: any classes: any
nodeOrder: NodeOrder
} }
interface State { class Settings extends React.Component<Props, {}> {
}
class Settings extends React.Component<Props, State> {
constructor(props: any) { constructor(props: any) {
super(props) super(props)
this.state = {} this.state = {}
@@ -57,7 +55,8 @@ class Settings extends React.Component<Props, State> {
public render() { public render() {
const { classes, actions, visible } = this.props const { classes, actions, visible } = this.props
return <Drawer return (
<Drawer
anchor="left" anchor="left"
className={classes.drawer} className={classes.drawer}
open={visible} open={visible}
@@ -80,20 +79,23 @@ class Settings extends React.Component<Props, State> {
<Divider /> <Divider />
{this.renderAutoExpand()} {this.renderAutoExpand()}
{this.renderNodeOrder()}
</div> </div>
</Drawer> </Drawer>
)
} }
private renderAutoExpand() { private renderAutoExpand() {
const { classes, actions, autoExpandLimit } = this.props const { classes, actions, autoExpandLimit } = this.props
return <div style={{ padding: '8px' }}> return (
<div style={{ padding: '8px' }}>
<InputLabel htmlFor="auto-expand">Auto Expand</InputLabel> <InputLabel htmlFor="auto-expand">Auto Expand</InputLabel>
<Select <Select
value={autoExpandLimit} value={autoExpandLimit}
onChange={ (e: React.ChangeEvent<HTMLSelectElement>) => actions.setAutoExpandLimit(e.target.value) } onChange={ (e: React.ChangeEvent<HTMLSelectElement>) => actions.setAutoExpandLimit(e.target.value) }
input={<Input name="auto-expand" id="auto-expand-label-placeholder" />} input={<Input name="auto-expand" id="auto-expand-label-placeholder" />}
displayEmpty displayEmpty={true}
name="auto-expand" name="auto-expand"
className={classes.input} className={classes.input}
> >
@@ -104,12 +106,40 @@ class Settings extends React.Component<Props, State> {
<MenuItem value={1E6}>All</MenuItem> <MenuItem value={1E6}>All</MenuItem>
</Select> </Select>
</div> </div>
)
}
private renderNodeOrder() {
const { classes, actions, nodeOrder } = this.props
return (
<div style={{ padding: '8px' }}>
<InputLabel htmlFor="auto-expand">Topic order</InputLabel>
<Select
value={nodeOrder}
onChange={ (e: React.ChangeEvent<HTMLSelectElement>) => {
window.requestAnimationFrame(() => {
actions.setNodeOrder(e.target.value)
})
}}
input={<Input name="node-order" id="node-order-label-placeholder" />}
displayEmpty={true}
name="node-order"
className={classes.input}
>
<MenuItem value={NodeOrder.none}><em>default</em></MenuItem>
<MenuItem value={NodeOrder.abc}>a-z</MenuItem>
<MenuItem value={NodeOrder.messages}>{NodeOrder.messages}</MenuItem>
<MenuItem value={NodeOrder.topics}>{NodeOrder.topics}</MenuItem>
</Select>
</div>)
} }
} }
const mapStateToProps = (state: AppState) => { const mapStateToProps = (state: AppState) => {
return { return {
autoExpandLimit: state.settings.autoExpandLimit, autoExpandLimit: state.settings.autoExpandLimit,
nodeOrder: state.settings.nodeOrder,
visible: state.settings.visible, visible: state.settings.visible,
} }
} }

View File

@@ -9,6 +9,7 @@ import { AppState } from '../../reducers'
const MovingAverage = require('moving-average') const MovingAverage = require('moving-average')
declare const performance: any declare const performance: any
declare const window: any
const timeInterval = 10 * 1000 const timeInterval = 10 * 1000
const average = MovingAverage(timeInterval) const average = MovingAverage(timeInterval)
@@ -48,14 +49,17 @@ class Tree extends React.Component<Props, TreeState> {
} }
const expectedRenderTime = average.forecast() const expectedRenderTime = average.forecast()
const updateInterval = Math.max(expectedRenderTime * 5, 300) const updateInterval = Math.max(expectedRenderTime * 20, 300)
const timeUntilNextUpdate = updateInterval - (performance.now() - this.lastUpdate) const timeUntilNextUpdate = updateInterval - (performance.now() - this.lastUpdate)
this.updateTimer = setTimeout(() => { this.updateTimer = setTimeout(() => {
window.requestAnimationFrame(() => {
console.log('doRender')
this.lastUpdate = performance.now() this.lastUpdate = performance.now()
this.updateTimer && clearTimeout(this.updateTimer) this.updateTimer && clearTimeout(this.updateTimer)
this.updateTimer = undefined this.updateTimer = undefined
this.setState(state) this.setState(state)
})
}, Math.max(0, timeUntilNextUpdate)) }, Math.max(0, timeUntilNextUpdate))
} }

View File

@@ -136,38 +136,41 @@ class TreeNode extends React.Component<Props, State> {
const { classes } = this.props const { classes } = this.props
this.dirtyEdges = this.dirtyMessage = this.dirtySubnodes = false this.dirtyEdges = this.dirtyMessage = this.dirtySubnodes = false
return <div return (
<div
key={this.props.treeNode.hash()} key={this.props.treeNode.hash()}
className={`${classes.node} ${!this.props.isRoot ? classes.hover : ''}`} className={`${classes.node} ${!this.props.isRoot ? classes.hover : ''}`}
onClick={(event) => { onClick={this.didClickNode}
event.stopPropagation()
this.toggle()
this.props.didSelectNode && this.props.didSelectNode(this.props.treeNode)
}}
> >
<span ref={this.titleRef} style={animationStyle}> <span ref={this.titleRef} style={animationStyle}>
<TreeNodeTitle <TreeNodeTitle
onClick={() => this.toggle()}
collapsed={this.collapsed()} collapsed={this.collapsed()}
treeNode={this.props.treeNode} treeNode={this.props.treeNode}
name={this.props.name} name={this.props.name}
didSelectNode={this.props.didSelectNode} didSelectNode={this.props.didSelectNode}
toggleCollapsed={() => this.toggle()}
/> />
</span> </span>
{this.renderNodes()} {this.renderNodes()}
</div> </div>
)
}
private didClickNode = (event: React.MouseEvent) => {
event.stopPropagation()
this.toggle()
this.props.didSelectNode && this.props.didSelectNode(this.props.treeNode)
} }
private renderNodes() { private renderNodes() {
return <TreeNodeSubnodes return (
<TreeNodeSubnodes
animateChanges={this.props.animateChages} animateChanges={this.props.animateChages}
collapsed={this.collapsed()} collapsed={this.collapsed()}
autoExpandLimit={this.props.autoExpandLimit} autoExpandLimit={this.props.autoExpandLimit}
didSelectNode={this.props.didSelectNode} didSelectNode={this.props.didSelectNode}
toggleCollapsed={() => this.toggle()}
treeNode={this.props.treeNode} treeNode={this.props.treeNode}
/> />
)
} }
private indicatingChangeAnimationStyle(): React.CSSProperties { private indicatingChangeAnimationStyle(): React.CSSProperties {

View File

@@ -1,34 +1,54 @@
import * as React from 'react' import * as React from 'react'
import { connect } from 'react-redux'
import * as q from '../../../../backend/src/Model' import * as q from '../../../../backend/src/Model'
import { withTheme, Theme } from '@material-ui/core/styles' import { withTheme, Theme } from '@material-ui/core/styles'
import { AppState, NodeOrder } from '../../reducers'
import TreeNode from './TreeNode' import TreeNode from './TreeNode'
export interface Props { export interface Props {
nodeOrder?: NodeOrder
animateChanges: boolean animateChanges: boolean
treeNode: q.TreeNode treeNode: q.TreeNode
autoExpandLimit: number autoExpandLimit: number
collapsed?: boolean | undefined collapsed?: boolean | undefined
didSelectNode?: (node: q.TreeNode) => void didSelectNode?: (node: q.TreeNode) => void
toggleCollapsed: () => void
theme: Theme theme: Theme
} }
class TreeNodeSubnodes extends React.Component<Props, {}> { class TreeNodeSubnodes extends React.Component<Props, {}> {
private sortedNodes(): q.TreeNode[] {
const { nodeOrder, treeNode } = this.props
let edges = Object.values(treeNode.edges)
if (nodeOrder === NodeOrder.abc) {
edges = edges.sort((a, b) => a.name.localeCompare(b.name))
}
let nodes = edges.map(edge => edge.target)
if (nodeOrder === NodeOrder.messages) {
nodes = nodes.sort((a, b) => b.leafMessageCount() - a.leafMessageCount())
}
if (nodeOrder === NodeOrder.topics) {
nodes = nodes.sort((a, b) => b.leafCount() - a.leafCount())
}
return nodes
}
public render() { public render() {
const edges = Object.values(this.props.treeNode.edges) const edges = Object.values(this.props.treeNode.edges)
if (edges.length === 0 || this.props.collapsed) {
return null
}
const listItemStyle = { const listItemStyle = {
padding: '3px 8px 0px 8px', padding: '3px 8px 0px 8px',
} }
if (edges.length > 0 && !this.props.collapsed) { const nodes = this.sortedNodes()
const listItems = edges const listItems = nodes.map(node => (
.map(edge => edge.target) <div key={node.hash()} style={listItemStyle}>
.map(node => (
<div
key={node.hash()}
style={listItemStyle}
>
<TreeNode <TreeNode
animateChages={this.props.animateChanges} animateChages={this.props.animateChanges}
treeNode={node} treeNode={node}
@@ -38,15 +58,18 @@ class TreeNodeSubnodes extends React.Component<Props, {}> {
</div> </div>
)) ))
return <span return (
style={{ display: 'block', clear: 'both' }} <span style={{ display: 'block', clear: 'both' }} >
>
{this.props.collapsed ? null : listItems} {this.props.collapsed ? null : listItems}
</span> </span>
} )
return null
} }
} }
export default withTheme()(TreeNodeSubnodes) const mapStateToProps = (state: AppState) => {
return {
nodeOrder: state.settings.nodeOrder,
}
}
export default withTheme()(connect(mapStateToProps)(TreeNodeSubnodes))

View File

@@ -6,7 +6,6 @@ export interface TreeNodeProps extends React.HTMLAttributes<HTMLElement> {
treeNode: q.TreeNode treeNode: q.TreeNode
name?: string | undefined name?: string | undefined
collapsed?: boolean | undefined collapsed?: boolean | undefined
toggleCollapsed: () => void
didSelectNode?: (node: q.TreeNode) => void didSelectNode?: (node: q.TreeNode) => void
theme: Theme theme: Theme
} }
@@ -24,25 +23,22 @@ class TreeNodeTitle extends React.Component<TreeNodeProps, {}> {
} }
} }
private didSelectNode = () => {
if (this.props.treeNode.message) {
this.props.didSelectNode && this.props.didSelectNode(this.props.treeNode)
}
}
public render() { public render() {
const style: React.CSSProperties = { const style: React.CSSProperties = {
lineHeight: '1em', lineHeight: '1em',
whiteSpace: 'nowrap', whiteSpace: 'nowrap',
} }
return <span return (
style={style} <span style={style} onMouseOver={this.didSelectNode}>
onClick={() => {
this.toggle()
this.props.didSelectNode && this.props.didSelectNode(this.props.treeNode)
}}
onMouseOver={() => {
if (this.props.treeNode.message) {
this.props.didSelectNode && this.props.didSelectNode(this.props.treeNode)
}
}}
>
{this.renderExpander()} {this.renderSourceEdge()} {this.renderCollapsedSubnodes()} {this.renderValue()} {this.renderExpander()} {this.renderSourceEdge()} {this.renderCollapsedSubnodes()} {this.renderValue()}
</span> </span>
)
} }
private renderSourceEdge() { private renderSourceEdge() {
@@ -67,16 +63,10 @@ class TreeNodeTitle extends React.Component<TreeNodeProps, {}> {
display: 'inline-block', display: 'inline-block',
} }
return this.props.treeNode.message return this.props.treeNode.message
? <span ? <span style={style}> = {this.props.treeNode.message.value.toString()}</span>
style={style}
> = {this.props.treeNode.message.value.toString()}</span>
: null : null
} }
private toggle() {
this.props.toggleCollapsed()
}
private renderExpander() { private renderExpander() {
if (this.props.treeNode.edgeCount() === 0) { if (this.props.treeNode.edgeCount() === 0) {
return null return null

View File

@@ -4,7 +4,7 @@ import { Provider } from 'react-redux'
import { createStore } from 'redux' import { createStore } from 'redux'
import { MuiThemeProvider, createMuiTheme } from '@material-ui/core/styles' import { MuiThemeProvider, createMuiTheme } from '@material-ui/core/styles'
import reducers from './reducers' import reducers, { NodeOrder } from './reducers'
import App from './App' import App from './App'
declare var document: any declare var document: any
@@ -12,6 +12,7 @@ declare var document: any
const initialAppState = { const initialAppState = {
settings: { settings: {
autoExpandLimit: 0, autoExpandLimit: 0,
nodeOrder: NodeOrder.none,
visible: false, visible: false,
}, },
} }

View File

@@ -3,20 +3,30 @@ import { Reducer, Action } from 'redux'
export enum ActionTypes { export enum ActionTypes {
setAutoExpandLimit = 'SET_AUTO_EXPAND_LIMIT', setAutoExpandLimit = 'SET_AUTO_EXPAND_LIMIT',
toggleSettingsVisibility = 'TOGGLE_SETTINGS_VISIBILITY', toggleSettingsVisibility = 'TOGGLE_SETTINGS_VISIBILITY',
setNodeOrder = 'SET_NODE_ORDER',
} }
interface SettingsAction extends Action { interface SettingsAction extends Action {
type: ActionTypes, type: ActionTypes,
autoExpandLimit: number autoExpandLimit?: number
nodeOrder?: NodeOrder
} }
export interface AppState { export interface AppState {
settings: SettingsModel settings: SettingsModel
} }
export enum NodeOrder {
none = 'none',
messages = '#messages',
abc = 'abc',
topics = '#topics',
}
export interface SettingsModel { export interface SettingsModel {
autoExpandLimit: number autoExpandLimit: number
visible: boolean visible: boolean
nodeOrder: NodeOrder
} }
const reducer: Reducer<AppState | undefined, SettingsAction> = (state, action) => { const reducer: Reducer<AppState | undefined, SettingsAction> = (state, action) => {
@@ -27,11 +37,15 @@ const reducer: Reducer<AppState | undefined, SettingsAction> = (state, action) =
switch (action.type) { switch (action.type) {
case ActionTypes.setAutoExpandLimit: case ActionTypes.setAutoExpandLimit:
if (!action.autoExpandLimit) {
return state
}
return { return {
...state, ...state,
settings: { settings: {
visible: state.settings.visible, visible: state.settings.visible,
autoExpandLimit: action.autoExpandLimit, autoExpandLimit: action.autoExpandLimit,
nodeOrder: state.settings.nodeOrder,
}, },
} }
case ActionTypes.toggleSettingsVisibility: case ActionTypes.toggleSettingsVisibility:
@@ -40,6 +54,19 @@ const reducer: Reducer<AppState | undefined, SettingsAction> = (state, action) =
settings: { settings: {
visible: !state.settings.visible, visible: !state.settings.visible,
autoExpandLimit: state.settings.autoExpandLimit, autoExpandLimit: state.settings.autoExpandLimit,
nodeOrder: state.settings.nodeOrder,
},
}
case ActionTypes.setNodeOrder:
if (!action.nodeOrder) {
return state
}
return {
...state,
settings: {
visible: state.settings.visible,
autoExpandLimit: state.settings.autoExpandLimit,
nodeOrder: action.nodeOrder,
}, },
} }
default: default:

View File

@@ -27,13 +27,13 @@ const mac: builder.CliOptions = {
arm64: false, arm64: false,
mac: ['dmg'], mac: ['dmg'],
projectDir: './build/clean', projectDir: './build/clean',
publish: 'onTag', publish: 'always',
} }
async function buildAll() { async function buildAll() {
await builder.build(linux) // await builder.build(linux)
// await builder.build(win)
await builder.build(mac) await builder.build(mac)
await builder.build(win)
} }
buildAll() buildAll()

View File

@@ -18,11 +18,7 @@
"appId": "mqtt-explorer", "appId": "mqtt-explorer",
"mac": { "mac": {
"category": "de.t7n.apps.mq-explorer", "category": "de.t7n.apps.mq-explorer",
"files": [ "publish": ["github"]
"**/*",
"!**/*.ts",
"!app/node_modules"
]
}, },
"linux": { "linux": {
"category": "Development", "category": "Development",

View File

@@ -19,6 +19,7 @@ exit 0
docker run --rm -ti \ docker run --rm -ti \
--env ELECTRON_CACHE="/root/.cache/electron" \ --env ELECTRON_CACHE="/root/.cache/electron" \
--env ELECTRON_BUILDER_CACHE="/root/.cache/electron-builder" \ --env ELECTRON_BUILDER_CACHE="/root/.cache/electron-builder" \
--env GH_TOKEN="$GH_TOKEN" \
-v ${PWD}:/project \ -v ${PWD}:/project \
-v ~/.cache/electron:/root/.cache/electron \ -v ~/.cache/electron:/root/.cache/electron \
-v ~/.cache/electron-builder:/root/.cache/electron-builder \ -v ~/.cache/electron-builder:/root/.cache/electron-builder \

View File

@@ -1,5 +1,5 @@
{ {
"extends": "tslint-config-airbnb", "extends": ["tslint-config-airbnb", "tslint-react"],
"rules": { "rules": {
"semicolon": [true, "never"], "semicolon": [true, "never"],
"max-line-length": [true, 200], "max-line-length": [true, 200],