From d4ce58a8ec776d520d59d83a3231f5b3e5f5a4b2 Mon Sep 17 00:00:00 2001 From: Thomas Nordquist Date: Tue, 22 Jan 2019 12:17:59 +0100 Subject: [PATCH] Fix emitter leaks, style, tree swaps --- app/package-lock.json | 187 +++++++++++++++--- app/package.json | 6 +- app/src/App.tsx | 109 +++++----- app/src/UpdateNotifier.tsx | 2 +- app/src/actions/Settings.ts | 33 ++-- app/src/actions/Tree.ts | 14 +- .../components/ConnectionSetup/Connection.tsx | 3 +- app/src/components/Settings.tsx | 1 - app/src/components/Sidebar/MessageHistory.tsx | 8 +- app/src/components/Sidebar/PlotHistory.tsx | 5 +- app/src/components/Sidebar/Sidebar.tsx | 39 ++-- app/src/components/Tree/Tree.tsx | 6 +- app/src/components/Tree/TreeNode.tsx | 44 ++++- app/src/components/Tree/TreeNodeSubnodes.tsx | 24 +-- app/src/components/Tree/TreeNodeTitle.tsx | 7 +- .../components/{ => helper}/DateFormatter.tsx | 0 app/src/reducers/Theme.ts | 11 ++ app/src/reducers/index.ts | 2 + backend/src/Model/Edge.ts | 9 +- backend/src/Model/Tree.ts | 30 ++- backend/src/Model/TreeNode.ts | 1 + events/EventBus.ts | 34 +++- 22 files changed, 411 insertions(+), 164 deletions(-) rename app/src/components/{ => helper}/DateFormatter.tsx (100%) create mode 100644 app/src/reducers/Theme.ts diff --git a/app/package-lock.json b/app/package-lock.json index 2491dfc..7ee63c1 100644 --- a/app/package-lock.json +++ b/app/package-lock.json @@ -12,6 +12,11 @@ "regenerator-runtime": "^0.12.0" } }, + "@emotion/hash": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.7.1.tgz", + "integrity": "sha512-OYpa/Sg+2GDX+jibUfpZVn1YqSVRpYmTLF2eyAfrFTIJSbwyIrc+YscayoykvaOME/wV4BV0Sa0yqdMrgse6mA==" + }, "@fimbul/bifrost": { "version": "0.17.0", "resolved": "https://registry.npmjs.org/@fimbul/bifrost/-/bifrost-0.17.0.tgz", @@ -53,12 +58,13 @@ } }, "@material-ui/core": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/@material-ui/core/-/core-3.7.1.tgz", - "integrity": "sha512-CjIGwvzn84BgzXWzC9M/Tz2gDI7AfUe3G1JXkZQAVy+ddPikh+iZwn5snnElfcjuC+ahXxaIyK49ARt3NM49vQ==", + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/@material-ui/core/-/core-3.9.0.tgz", + "integrity": "sha512-9fxu0B9zx/TfiU0OJmwRFk+I+2J7fcXzTfQl+ZPxqlVRrPDqTHnMZlqWRJjlHCe3AVXlgpJ6/a4QCvGEohEbIQ==", "requires": { - "@babel/runtime": "7.2.0", - "@material-ui/utils": "^3.0.0-alpha.1", + "@babel/runtime": "^7.2.0", + "@material-ui/system": "^3.0.0-alpha.0", + "@material-ui/utils": "^3.0.0-alpha.2", "@types/jss": "^9.5.6", "@types/react-transition-group": "^2.0.8", "brcast": "^3.0.1", @@ -127,17 +133,66 @@ } } }, - "@material-ui/lab": { - "version": "3.0.0-alpha.27", - "resolved": "https://registry.npmjs.org/@material-ui/lab/-/lab-3.0.0-alpha.27.tgz", - "integrity": "sha512-VwUCXEJEo1QkSTDoAg5g8K51f6Re7Fh5d7m5IO9W95B9iEYVJhSGy+WY1/ZQdpJl1F1gPzL9u2asCg6LIazBLA==", - "dev": true, + "@material-ui/styles": { + "version": "3.0.0-alpha.8", + "resolved": "https://registry.npmjs.org/@material-ui/styles/-/styles-3.0.0-alpha.8.tgz", + "integrity": "sha512-66X0fN9AmXexpnk8wWin2L0p4PzRvIP8Cy2pzXANrbfpHyTgDsURiGECRgv+cgtIBoAjqb/x+NxEE92RYFYFWQ==", "requires": { - "@babel/runtime": "7.2.0", + "@babel/runtime": "^7.2.0", + "@emotion/hash": "^0.7.1", "@material-ui/utils": "^3.0.0-alpha.2", "classnames": "^2.2.5", - "keycode": "^2.1.9", - "prop-types": "^15.6.0" + "deepmerge": "^3.0.0", + "hoist-non-react-statics": "^3.2.1", + "jss": "^10.0.0-alpha.7", + "jss-plugin-camel-case": "^10.0.0-alpha.7", + "jss-plugin-default-unit": "^10.0.0-alpha.7", + "jss-plugin-global": "^10.0.0-alpha.7", + "jss-plugin-nested": "^10.0.0-alpha.7", + "jss-plugin-props-sort": "^10.0.0-alpha.7", + "jss-plugin-rule-value-function": "^10.0.0-alpha.7", + "jss-plugin-vendor-prefixer": "^10.0.0-alpha.7", + "prop-types": "^15.6.0", + "warning": "^4.0.1" + }, + "dependencies": { + "jss": { + "version": "10.0.0-alpha.8", + "resolved": "https://registry.npmjs.org/jss/-/jss-10.0.0-alpha.8.tgz", + "integrity": "sha512-7cNQXMbJ5KQOgTJSaDPfLInBIaU5AD+FYg00PCl9i170NXj3M/CsoIWUMnTGbsWjcIQ7DmlqzrSFdsxSwzHjuA==", + "requires": { + "@babel/runtime": "^7.0.0", + "is-in-browser": "^1.1.3", + "symbol-observable": "^1.2.0", + "tiny-warning": "^1.0.2" + } + } + } + }, + "@material-ui/system": { + "version": "3.0.0-alpha.1", + "resolved": "https://registry.npmjs.org/@material-ui/system/-/system-3.0.0-alpha.1.tgz", + "integrity": "sha512-5EihYa6Ct5mA/shfFSjWO8e/whV+otbXAduYfiL34GH+Mh4vZs+wjcy0P80XA/cDIwSgkQ7vTvvY1x04AgIz4w==", + "requires": { + "@babel/runtime": "7.1.2", + "deepmerge": "^2.0.1", + "prop-types": "^15.6.0", + "warning": "^4.0.1" + }, + "dependencies": { + "@babel/runtime": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.1.2.tgz", + "integrity": "sha512-Y3SCjmhSupzFB6wcv1KmmFucH6gDVnI30WjOcicV10ju0cZjak3Jcs67YLIXBrmZYw1xCrVeJPbycFwrqNyxpg==", + "requires": { + "regenerator-runtime": "^0.12.0" + } + }, + "deepmerge": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-2.2.1.tgz", + "integrity": "sha512-R9hc1Xa/NOBi9WRVUWg19rl1UB7Tt4kuPd+thNJgFZoxXsTz7ncaPaeIm+40oSGuP33DfMb4sZt1QIGiJzC4EA==" + } } }, "@material-ui/utils": { @@ -1802,9 +1857,9 @@ "dev": true }, "deepmerge": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-3.0.0.tgz", - "integrity": "sha512-a8z8bkgHsAML+uHLqmMS83HHlpy3PvZOOuiTQqaa3wu8ZVg3h0hqHk6aCsGdOnZV2XMM/FRimNGjUh0KCcmHBw==" + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-3.1.0.tgz", + "integrity": "sha512-/TnecbwXEdycfbsM2++O3eGiatEFHjjNciHEwJclM+T5Kd94qD1AP+2elP/Mq0L5b9VZJao5znR01Mz6eX8Seg==" }, "default-gateway": { "version": "2.7.2", @@ -4206,6 +4261,75 @@ } } }, + "jss-plugin-camel-case": { + "version": "10.0.0-alpha.7", + "resolved": "https://registry.npmjs.org/jss-plugin-camel-case/-/jss-plugin-camel-case-10.0.0-alpha.7.tgz", + "integrity": "sha512-Bwrav1ZB0XywdJW6TaEuFhKe1ZpZvUlESh3jsFOvebA9aFTYNCkmHMEqjA5+u9VMxksl3u77nnZHtukpxkzrBA==", + "requires": { + "@babel/runtime": "^7.0.0", + "hyphenate-style-name": "^1.0.2" + } + }, + "jss-plugin-default-unit": { + "version": "10.0.0-alpha.7", + "resolved": "https://registry.npmjs.org/jss-plugin-default-unit/-/jss-plugin-default-unit-10.0.0-alpha.7.tgz", + "integrity": "sha512-auuJUbQaWMxoHOVFPrfZNZpZm9ab8PZeDyvey8nMt2lbokkmZ53UyAnM/1kNsg5BdAXTItcLDxDB3I4gwNU84g==", + "requires": { + "@babel/runtime": "^7.0.0" + } + }, + "jss-plugin-global": { + "version": "10.0.0-alpha.7", + "resolved": "https://registry.npmjs.org/jss-plugin-global/-/jss-plugin-global-10.0.0-alpha.7.tgz", + "integrity": "sha512-OWeoW4szLDgRUKviST+xfilqa8O5uXJCW+O3YonheCRTRJg6rRzlE/b5pfYPoU9UtwvY9n7JvwBX5r3c1lMsEQ==", + "requires": { + "@babel/runtime": "^7.0.0" + } + }, + "jss-plugin-nested": { + "version": "10.0.0-alpha.7", + "resolved": "https://registry.npmjs.org/jss-plugin-nested/-/jss-plugin-nested-10.0.0-alpha.7.tgz", + "integrity": "sha512-wsRzuIZXAc6WMjc61mREW9cUrDxgSI7dK/fx5c7a06IDUfSn+83NJ30J/RB4oBnbQW9SijV/muujz7IJqpn9Gw==", + "requires": { + "@babel/runtime": "^7.0.0", + "tiny-warning": "^1.0.2" + } + }, + "jss-plugin-props-sort": { + "version": "10.0.0-alpha.7", + "resolved": "https://registry.npmjs.org/jss-plugin-props-sort/-/jss-plugin-props-sort-10.0.0-alpha.7.tgz", + "integrity": "sha512-KXOCaHUk1+KXqE0z3q66/w1fDoy+VsZvI77gLxOqTsTrvIKFLX0jarwXogW3CDlaPQQFTZ6JykJJXtPRTBlstA==", + "requires": { + "@babel/runtime": "^7.0.0" + } + }, + "jss-plugin-rule-value-function": { + "version": "10.0.0-alpha.7", + "resolved": "https://registry.npmjs.org/jss-plugin-rule-value-function/-/jss-plugin-rule-value-function-10.0.0-alpha.7.tgz", + "integrity": "sha512-ett83hvIM69/LknmrWndrrdiDlfLfP+rneU5qP7gTOWJ7g1P9GuEL1Tc4CWdZUWBX+T58tgIBP0V1pzWCkP0QA==", + "requires": { + "@babel/runtime": "^7.0.0" + } + }, + "jss-plugin-vendor-prefixer": { + "version": "10.0.0-alpha.7", + "resolved": "https://registry.npmjs.org/jss-plugin-vendor-prefixer/-/jss-plugin-vendor-prefixer-10.0.0-alpha.7.tgz", + "integrity": "sha512-YbIVgqq+dLimOBOEYggho1Iuc0roz4PJSZYyaok9n8JnXVIqPnxYJbr8+bMbvzJ5CL3eeJij/e7L2IPCceRKrA==", + "requires": { + "@babel/runtime": "^7.0.0", + "css-vendor": "^1.1.0" + }, + "dependencies": { + "css-vendor": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/css-vendor/-/css-vendor-1.1.0.tgz", + "integrity": "sha512-pePyROdjdMRjHhGN+xZjBomaR9GXueNQ2ZLkEi0i2A/eVx0m4oCLglltdRsaYCSOpun0GRJWuqmWLIYj6Y/RVQ==", + "requires": { + "is-in-browser": "^1.0.2" + } + } + } + }, "jss-props-sort": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/jss-props-sort/-/jss-props-sort-6.0.0.tgz", @@ -5588,15 +5712,27 @@ } }, "react": { - "version": "16.7.0", - "resolved": "https://registry.npmjs.org/react/-/react-16.7.0.tgz", - "integrity": "sha512-StCz3QY8lxTb5cl2HJxjwLFOXPIFQp+p+hxQfc8WE0QiLfCtIlKj8/+5tjjKm8uSTlAW+fCPaavGFS06V9Ar3A==", + "version": "16.8.0-alpha.1", + "resolved": "https://registry.npmjs.org/react/-/react-16.8.0-alpha.1.tgz", + "integrity": "sha512-vLwwnhM2dXrCsiQmcSxF2UdZVV5xsiXjK5Yetmy8dVqngJhQ3aw3YJhZN/YmyonxwdimH40wVqFQfsl4gSu2RA==", "dev": true, "requires": { "loose-envify": "^1.1.0", "object-assign": "^4.1.1", "prop-types": "^15.6.2", - "scheduler": "^0.12.0" + "scheduler": "^0.13.0-alpha.1" + }, + "dependencies": { + "scheduler": { + "version": "0.13.0-alpha.1", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.13.0-alpha.1.tgz", + "integrity": "sha512-W0sH0848sVuPKg+I18vTYQyzVtA4X1lrVgSeXK6KnOPUltFdJcY5nkbTkjGUeS/E0x+eBsNYfSdhJtGjT95njw==", + "dev": true, + "requires": { + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1" + } + } } }, "react-ace": { @@ -5718,9 +5854,9 @@ } }, "react-transition-group": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-2.5.2.tgz", - "integrity": "sha512-vwHP++S+f6KL7rg8V1mfs62+MBKtbMeZDR8KiNmD7v98Gs3UPGsDZDahPJH2PVprFW5YHJfh6cbNim3zPndaSQ==", + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-2.5.3.tgz", + "integrity": "sha512-2DGFck6h99kLNr8pOFk+z4Soq3iISydwOFeeEVPjTN6+Y01CmvbWmnN02VuTWyFdnRtIDPe+wy2q6Ui8snBPZg==", "requires": { "dom-helpers": "^3.3.1", "loose-envify": "^1.4.0", @@ -6949,6 +7085,11 @@ } } }, + "tiny-warning": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.2.tgz", + "integrity": "sha512-rru86D9CpQRLvsFG5XFdy0KdLAvjdQDyZCsRcuu60WtzFylDM3eAWSxEVz5kzL2Gp544XiUvPbVKtOA/txLi9Q==" + }, "to-array": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/to-array/-/to-array-0.1.4.tgz", diff --git a/app/package.json b/app/package.json index 8c99c7e..b99b020 100644 --- a/app/package.json +++ b/app/package.json @@ -10,14 +10,13 @@ "license": "ISC", "devDependencies": { "@material-ui/icons": "^3.0.1", - "@material-ui/lab": "^3.0.0-alpha.27", "@types/react": "^16.7.18", "@types/react-dom": "^16.0.11", "@types/react-redux": "^6.0.12", "css-loader": "^2.1.0", "electron-nucleus": "^1.11.0", "electron-telemetry": "git+https://github.com/thomasnordquist/electron-telemetry.git", - "react": "^16.7.0", + "react": "^16.8.0-alpha.1", "react-dom": "^16.7.0", "react-redux": "^6.0.0", "react-vis": "^1.11.6", @@ -27,7 +26,8 @@ "webpack-livereload-plugin": "^2.2.0" }, "dependencies": { - "@material-ui/core": "^3.7.1", + "@material-ui/core": "^3.9.0", + "@material-ui/styles": "^3.0.0-alpha.8", "@types/node": "^10.12.18", "@types/react-resize-detector": "^3.1.0", "@types/sha1": "^1.1.1", diff --git a/app/src/App.tsx b/app/src/App.tsx index ea24837..92cccee 100644 --- a/app/src/App.tsx +++ b/app/src/App.tsx @@ -17,7 +17,7 @@ import ErrorBoundary from './ErrorBoundary' interface Props { name: string connectionId: string - theme: Theme + classes: any settingsVisible: boolean } @@ -27,69 +27,22 @@ class App extends React.Component { this.state = { } } - private getStyles(): {[s: string]: React.CSSProperties} { - const { theme } = this.props - const drawerWidth = 300 - return { - left: { - backgroundColor: theme.palette.background.default, - color: theme.palette.text.primary, - height: 'calc(100vh - 64px)', - float: 'left', - overflowY: 'scroll', - overflowX: 'hidden', - display: 'block', - width: '60vw', - }, - right: { - height: 'calc(100vh - 64px)', - color: theme.palette.text.primary, - float: 'left', - width: '40vw', - overflowY: 'scroll', - overflowX: 'hidden', - display: 'block', - }, - centerContent: { - width: '100vw', - overflow: 'hidden', - }, - content: { - backgroundColor: theme.palette.background.default, - transition: theme.transitions.create('margin', { - easing: theme.transitions.easing.sharp, - duration: theme.transitions.duration.leavingScreen, - }), - marginLeft: 0, - }, - contentShift: { - padding: 0, - backgroundColor: theme.palette.background.default, - transition: theme.transitions.create('margin', { - easing: theme.transitions.easing.easeOut, - duration: theme.transitions.duration.enteringScreen, - }), - marginLeft: drawerWidth, - }, - } - } - public render() { const { settingsVisible } = this.props - const { content, contentShift, centerContent } = this.getStyles() + const { content, contentShift, centerContent, left, right } = this.props.classes return ( -
+
-
+
-
-
+
+
-
+
@@ -109,4 +62,50 @@ const mapStateToProps = (state: AppState) => { } } -export default withStyles({}, { withTheme: true })(connect(mapStateToProps)(App)) +const styles = (theme: Theme) => { + const drawerWidth = 300 + return { + left: { + backgroundColor: theme.palette.background.default, + color: theme.palette.text.primary, + height: 'calc(100vh - 64px)', + float: 'left' as 'left', + overflowY: 'scroll' as 'scroll', + overflowX: 'hidden' as 'hidden', + display: 'block' as 'block', + width: '60vw', + }, + right: { + height: 'calc(100vh - 64px)', + color: theme.palette.text.primary, + float: 'left' as 'left', + width: '40vw', + overflowY: 'scroll' as 'scroll', + overflowX: 'hidden' as 'hidden', + display: 'block' as 'block', + }, + centerContent: { + width: '100vw', + overflow: 'hidden' as 'hidden', + }, + content: { + backgroundColor: theme.palette.background.default, + transition: theme.transitions.create('margin', { + easing: theme.transitions.easing.sharp, + duration: theme.transitions.duration.leavingScreen, + }), + marginLeft: 0, + }, + contentShift: { + padding: 0, + backgroundColor: theme.palette.background.default, + transition: theme.transitions.create('margin', { + easing: theme.transitions.easing.easeOut, + duration: theme.transitions.duration.enteringScreen, + }), + marginLeft: drawerWidth, + }, + } +} + +export default withStyles(styles)(connect(mapStateToProps)(App)) diff --git a/app/src/UpdateNotifier.tsx b/app/src/UpdateNotifier.tsx index 89e6c98..7633865 100644 --- a/app/src/UpdateNotifier.tsx +++ b/app/src/UpdateNotifier.tsx @@ -224,4 +224,4 @@ const mapDispatchToProps = (dispatch: any) => { } } -export default withStyles(styles, { withTheme: true })(connect(mapStateToProps, mapDispatchToProps)(UpdateNotifier)) +export default withStyles(styles)(connect(mapStateToProps, mapDispatchToProps)(UpdateNotifier)) diff --git a/app/src/actions/Settings.ts b/app/src/actions/Settings.ts index 9077e1a..3216dba 100644 --- a/app/src/actions/Settings.ts +++ b/app/src/actions/Settings.ts @@ -1,6 +1,7 @@ import { Action, ActionTypes, TopicOrder } from '../reducers/Settings' -import { ActionTypes as TreeActionTypes, Action as TreeAction } from '../reducers/Tree' +import { ActionTypes as TreeActionTypes } from '../reducers/Tree' import { Dispatch } from 'redux' +import { showTree } from './Tree' import { AppState } from '../reducers' import * as q from '../../../backend/src/Model' @@ -38,17 +39,22 @@ export const filterTopics = (filterStr: string) => (dispatch: Dispatch, get } if (!topicFilter) { - dispatch({ - tree, - filter: '', - type: TreeActionTypes.TREE_SHOW_TREE, - }) - + dispatch(showTree(tree)) return } + const nodeFilter = (node: q.TreeNode): boolean => { + const topicMatches = node.path().toLowerCase().indexOf(topicFilter) !== -1 + if (topicMatches) { + return true + } + + const messageMatches = (node.message && typeof node.message.value === 'string' && node.message.value.toLowerCase().indexOf(filterStr) !== -1) + return Boolean(messageMatches) + } + const resultTree = tree.leafes() - .filter(leaf => leaf.path().toLowerCase().indexOf(topicFilter) !== -1) + .filter(nodeFilter) .map((node) => { const clone = node.unconnectedClone() q.TreeNodeFactory.insertNodeAtPosition(node.path().split('/'), clone) @@ -59,9 +65,10 @@ export const filterTopics = (filterStr: string) => (dispatch: Dispatch, get return a }, new q.Tree()) - dispatch({ - tree: resultTree, - filter: topicFilter, - type: TreeActionTypes.TREE_SHOW_TREE, - }) + const nextTree: q.Tree = resultTree as q.Tree + if (tree.updateSource && tree.connectionId) { + nextTree.updateWithConnection(tree.updateSource, tree.connectionId, nodeFilter) + } + + dispatch(showTree(nextTree)) } diff --git a/app/src/actions/Tree.ts b/app/src/actions/Tree.ts index 80144fb..8ac45fc 100644 --- a/app/src/actions/Tree.ts +++ b/app/src/actions/Tree.ts @@ -18,9 +18,17 @@ export const selectTopic = (topic: q.TreeNode) => (dispatch: Dispatch, getS }) } -export const showTree = (tree?: q.Tree) => { - return { +export const showTree = (tree?: q.Tree) => (dispatch: Dispatch, getState: () => AppState) => { + const visibleTree = getState().tree.tree + const connectionTree = getState().connection.tree + + // Stop updates of old tree + if (visibleTree !== connectionTree && visibleTree) { + visibleTree.stopUpdating() + } + + dispatch({ tree, type: ActionTypes.TREE_SHOW_TREE, - } + }) } diff --git a/app/src/components/ConnectionSetup/Connection.tsx b/app/src/components/ConnectionSetup/Connection.tsx index a795a8c..b671dce 100644 --- a/app/src/components/ConnectionSetup/Connection.tsx +++ b/app/src/components/ConnectionSetup/Connection.tsx @@ -33,7 +33,6 @@ import { connectionActions } from '../../actions' interface Props { classes: {[s: string]: string} - theme: Theme actions: typeof connectionActions, visible: boolean connected: boolean @@ -393,4 +392,4 @@ const mapDispatchToProps = (dispatch: any) => { } } -export default connect(mapStateToProps, mapDispatchToProps)(withStyles(Connection.styles, { withTheme: true })(Connection)) +export default connect(mapStateToProps, mapDispatchToProps)(withStyles(Connection.styles)(Connection)) diff --git a/app/src/components/Settings.tsx b/app/src/components/Settings.tsx index ac9677a..0104abf 100644 --- a/app/src/components/Settings.tsx +++ b/app/src/components/Settings.tsx @@ -18,7 +18,6 @@ import { bindActionCreators } from 'redux' import { connect } from 'react-redux' import { settingsActions } from '../actions' import { TopicOrder } from '../reducers/Settings' -import Topic from './Sidebar/Topic'; const styles: StyleRulesCallback = theme => ({ drawer: { diff --git a/app/src/components/Sidebar/MessageHistory.tsx b/app/src/components/Sidebar/MessageHistory.tsx index 8e2ef6f..ab170be 100644 --- a/app/src/components/Sidebar/MessageHistory.tsx +++ b/app/src/components/Sidebar/MessageHistory.tsx @@ -1,9 +1,8 @@ import * as React from 'react' import * as q from '../../../../backend/src/Model' -import { Theme, withTheme } from '@material-ui/core/styles' import BarChart from '@material-ui/icons/BarChart' -import DateFormatter from '../DateFormatter' +import DateFormatter from '../helper/DateFormatter' import History from './History' import PlotHistory from './PlotHistory' @@ -11,7 +10,6 @@ const throttle = require('lodash.throttle') interface Props { node?: q.TreeNode - theme: Theme onSelect: (message: q.Message) => void } @@ -35,7 +33,7 @@ class MessageHistory extends React.Component { nextProps.node && nextProps.node.onMessage.subscribe(this.updateNode) } - public componentWillMount() { + public componentDidMount() { this.props.node && this.props.node.onMessage.subscribe(this.updateNode) } @@ -81,4 +79,4 @@ class MessageHistory extends React.Component { } } -export default withTheme()(MessageHistory) +export default MessageHistory diff --git a/app/src/components/Sidebar/PlotHistory.tsx b/app/src/components/Sidebar/PlotHistory.tsx index 2d8898e..8e7c0c7 100644 --- a/app/src/components/Sidebar/PlotHistory.tsx +++ b/app/src/components/Sidebar/PlotHistory.tsx @@ -1,10 +1,9 @@ const { XYPlot, XAxis, LineMarkSeries, Hint, YAxis, HorizontalGridLines, LineSeries } = require('react-vis') import { default as ReactResizeDetector } from 'react-resize-detector' -import DateFormatter from '../DateFormatter' +import DateFormatter from '../helper/DateFormatter' import * as React from 'react' import * as q from '../../../../backend/src/Model' -import { withStyles, Theme, StyleRulesCallback } from '@material-ui/core/styles' import 'react-vis/dist/style.css' interface Props { @@ -71,4 +70,4 @@ class PlotHistory extends React.Component { } } -export default withStyles({}, { withTheme: true })(PlotHistory) +export default PlotHistory diff --git a/app/src/components/Sidebar/Sidebar.tsx b/app/src/components/Sidebar/Sidebar.tsx index 8649407..7b99efa 100644 --- a/app/src/components/Sidebar/Sidebar.tsx +++ b/app/src/components/Sidebar/Sidebar.tsx @@ -7,11 +7,9 @@ import { ExpansionPanelDetails, ExpansionPanelSummary, Fade, - Fab, Paper, Popper, Typography, - IconButton, Tooltip, } from '@material-ui/core' import { StyleRulesCallback, Theme, withStyles } from '@material-ui/core/styles' @@ -20,7 +18,7 @@ import { sidebarActons } from '../../actions' import { AppState } from '../../reducers' import Copy from '../Copy' -import DateFormatter from '../DateFormatter' +import DateFormatter from '../helper/DateFormatter' import ExpandMore from '@material-ui/icons/ExpandMore' import Clear from '@material-ui/icons/Clear' import MessageHistory from './MessageHistory' @@ -37,7 +35,6 @@ interface Props { node?: q.TreeNode, actions: typeof sidebarActons, classes: any, - theme: Theme, connectionId?: string, } @@ -58,22 +55,6 @@ class Sidebar extends React.Component { this.state = { node: new q.Tree() } } - public static styles: StyleRulesCallback = (theme: Theme) => { - return { - drawer: { - display: 'block', - height: '100%', - }, - valuePaper: { - margin: `${theme.spacing.unit}px ${theme.spacing.unit}px ${theme.spacing.unit}px ${theme.spacing.unit}px`, - }, - heading: { - fontSize: theme.typography.pxToRem(15), - fontWeight: theme.typography.fontWeightRegular, - }, - } - } - public componentWillReceiveProps(nextProps: Props) { this.props.node && this.removeUpdateListener(this.props.node) nextProps.node && this.registerUpdateListener(nextProps.node) @@ -229,4 +210,20 @@ const mapDispatchToProps = (dispatch: any) => { } } -export default withStyles(Sidebar.styles, { withTheme: true })(connect(mapStateToProps, mapDispatchToProps)(Sidebar)) +const styles: StyleRulesCallback = (theme: Theme) => { + return { + drawer: { + display: 'block', + height: '100%', + }, + valuePaper: { + margin: `${theme.spacing.unit}px ${theme.spacing.unit}px ${theme.spacing.unit}px ${theme.spacing.unit}px`, + }, + heading: { + fontSize: theme.typography.pxToRem(15), + fontWeight: theme.typography.fontWeightRegular, + }, + } +} + +export default withStyles(styles)(connect(mapStateToProps, mapDispatchToProps)(Sidebar)) diff --git a/app/src/components/Tree/Tree.tsx b/app/src/components/Tree/Tree.tsx index 884abfb..e51b81f 100644 --- a/app/src/components/Tree/Tree.tsx +++ b/app/src/components/Tree/Tree.tsx @@ -1,8 +1,6 @@ import * as React from 'react' import * as q from '../../../../backend/src/Model' -import { makeConnectionMessageEvent, rendererEvents, MqttMessage } from '../../../../events' - import { AppState } from '../../reducers' import TreeNode from './TreeNode' import { connect } from 'react-redux' @@ -84,11 +82,11 @@ class Tree extends React.Component { lineHeight: '1.1', cursor: 'default', } - const key = `rootNode-${filter}` + return (
{ private cssAnimationWasSetAt?: number private willUpdateTime: number = performance.now() - private titleRef = React.createRef() - private topicSelectRef = React.createRef() + private titleRef?: React.RefObject = React.createRef() + private topicSelectRef?: React.RefObject = React.createRef() private subnodesDidchange = () => { this.dirtySubnodes = true @@ -90,18 +90,52 @@ class TreeNode extends React.Component { } } + private writeStats(what: string) { + const w: any = window + if (!w.stats) { + w.stats = {} + } + if (!w.stats[what]) { + w.stats[what] = 0 + } + w.stats[what] += 1 + } + public componentDidMount() { const { treeNode } = this.props + this.addSubscriber(treeNode) + } + + private addSubscriber(treeNode: q.TreeNode) { + this.writeStats('subscribe') treeNode.onMerge.subscribe(this.subnodesDidchange) treeNode.onEdgesChange.subscribe(this.edgesDidChange) treeNode.onMessage.subscribe(this.messageDidChange) } + private removeSubscriber(treeNode: q.TreeNode) { + this.writeStats('unsubscribe') + treeNode.onMerge.unsubscribe(this.subnodesDidchange) + treeNode.onEdgesChange.unsubscribe(this.edgesDidChange) + treeNode.onMessage.unsubscribe(this.messageDidChange) + } + + public componentWillReceiveProps(nextProps: Props) { + if (nextProps.treeNode !== this.props.treeNode) { + this.removeSubscriber(this.props.treeNode) + this.addSubscriber(nextProps.treeNode) + } + } + public componentWillUnmount() { + this.writeStats('unsubscribe') + const { treeNode } = this.props treeNode.onMerge.unsubscribe(this.subnodesDidchange) treeNode.onEdgesChange.unsubscribe(this.edgesDidChange) treeNode.onMessage.unsubscribe(this.messageDidChange) + this.topicSelectRef = undefined + this.titleRef = undefined } private stateHasChanged(newState: State) { @@ -189,20 +223,20 @@ class TreeNode extends React.Component { private mouseOver = (event: React.MouseEvent) => { event.stopPropagation() - if (this.topicSelectRef.current) { + if (this.topicSelectRef && this.topicSelectRef.current) { this.topicSelectRef.current.style.opacity = '1' } } private mouseOut = (event: React.MouseEvent) => { event.stopPropagation() - if (this.topicSelectRef.current) { + if (this.topicSelectRef && this.topicSelectRef.current) { this.topicSelectRef.current.style.opacity = '0' } } private didSelectNode = (event: React.MouseEvent) => { event.stopPropagation() - if (this.topicSelectRef.current) { + if (this.topicSelectRef && this.topicSelectRef.current) { this.topicSelectRef.current.style.opacity = '1' } this.props.actions.selectTopic(this.props.treeNode) diff --git a/app/src/components/Tree/TreeNodeSubnodes.tsx b/app/src/components/Tree/TreeNodeSubnodes.tsx index cb947ce..946f512 100644 --- a/app/src/components/Tree/TreeNodeSubnodes.tsx +++ b/app/src/components/Tree/TreeNodeSubnodes.tsx @@ -2,7 +2,6 @@ import * as React from 'react' import * as q from '../../../../backend/src/Model' import { AppState } from '../../reducers' -import { Theme, withTheme } from '@material-ui/core/styles' import TreeNode from './TreeNode' import { connect } from 'react-redux' @@ -14,9 +13,9 @@ export interface Props { animateChanges: boolean treeNode: q.TreeNode autoExpandLimit: number + filter?: string collapsed?: boolean | undefined didSelectNode?: (node: q.TreeNode) => void - theme: Theme } interface State { @@ -76,15 +75,15 @@ class TreeNodeSubnodes extends React.Component { const nodes = this.sortedNodes().slice(0, this.state.alreadyAdded) const listItems = nodes.map(node => ( -
- -
- )) +
+ +
+ )) return ( @@ -97,7 +96,8 @@ class TreeNodeSubnodes extends React.Component { const mapStateToProps = (state: AppState) => { return { topicOrder: state.settings.topicOrder, + filter: state.tree.filter, } } -export default withTheme()(connect(mapStateToProps)(TreeNodeSubnodes)) +export default connect(mapStateToProps)(TreeNodeSubnodes) diff --git a/app/src/components/Tree/TreeNodeTitle.tsx b/app/src/components/Tree/TreeNodeTitle.tsx index 46576c2..89b68b9 100644 --- a/app/src/components/Tree/TreeNodeTitle.tsx +++ b/app/src/components/Tree/TreeNodeTitle.tsx @@ -3,23 +3,20 @@ import { connect } from 'react-redux' import { bindActionCreators } from 'redux' import { treeActions } from '../../actions' import * as q from '../../../../backend/src/Model' -import { withTheme, Theme } from '@material-ui/core/styles' export interface TreeNodeProps extends React.HTMLAttributes { treeNode: q.TreeNode actions: any name?: string | undefined collapsed?: boolean | undefined - theme: Theme lastUpdate: number } class TreeNodeTitle extends React.Component { private getStyles() { - const { theme } = this.props return { collapsedSubnodes: { - color: theme.palette.text.secondary, + color: 'white', // theme.palette.text.secondary, }, container: { display: 'block', @@ -97,4 +94,4 @@ const mapDispatchToProps = (dispatch: any) => { } } -export default withTheme()(connect(null, mapDispatchToProps)(TreeNodeTitle)) +export default connect(null, mapDispatchToProps)(TreeNodeTitle) diff --git a/app/src/components/DateFormatter.tsx b/app/src/components/helper/DateFormatter.tsx similarity index 100% rename from app/src/components/DateFormatter.tsx rename to app/src/components/helper/DateFormatter.tsx diff --git a/app/src/reducers/Theme.ts b/app/src/reducers/Theme.ts new file mode 100644 index 0000000..6fa40d4 --- /dev/null +++ b/app/src/reducers/Theme.ts @@ -0,0 +1,11 @@ +import { createReducer } from './lib' +import { createMuiTheme, Theme } from '@material-ui/core' + +const initialState: Theme = createMuiTheme({ + palette: { + type: 'dark', + }, + typography: { useNextVariants: true }, +}) + +export const theme = createReducer(initialState, {}) diff --git a/app/src/reducers/index.ts b/app/src/reducers/index.ts index a525f04..a9fa813 100644 --- a/app/src/reducers/index.ts +++ b/app/src/reducers/index.ts @@ -7,6 +7,7 @@ import { PublishState, publishReducer } from './Publish' import { ConnectionState, connectionReducer } from './Connection' import { SettingsState, settingsReducer } from './Settings' import { TreeState, treeReducer } from './Tree' +import { theme } from './theme' export enum ActionTypes { showUpdateNotification = 'SHOW_UPDATE_NOTIFICATION', @@ -64,6 +65,7 @@ const tooBigReducer: Reducer = (state = } const reducer = combineReducers({ + theme, tooBigReducer, publish: publishReducer, connection: connectionReducer, diff --git a/backend/src/Model/Edge.ts b/backend/src/Model/Edge.ts index 701ade9..0d61326 100644 --- a/backend/src/Model/Edge.ts +++ b/backend/src/Model/Edge.ts @@ -18,7 +18,14 @@ export class Edge implements Hashable { public hash(): string { if (!this.cachedHash) { - const previousHash = (this.source && this.source.sourceEdge) ? this.source.sourceEdge.hash() : '' + let previousHash + if (this.source && this.source.sourceEdge) { + previousHash = this.source.sourceEdge.hash() + } else { + // Use the tree hash to distinguish between different trees + previousHash = this.source && this.source.isTree ? (this.source as any).hash() : '' + } + this.cachedHash = `H${sha1(previousHash + this.name)}` } diff --git a/backend/src/Model/Tree.ts b/backend/src/Model/Tree.ts index 7fc6c4b..e123df5 100644 --- a/backend/src/Model/Tree.ts +++ b/backend/src/Model/Tree.ts @@ -3,27 +3,45 @@ import { EventBusInterface, makeConnectionMessageEvent, MqttMessage } from '../. import { TreeNodeFactory } from './TreeNodeFactory' export class Tree extends TreeNode { - private connectionId?: string - private updateSource?: EventBusInterface + public connectionId?: string + public updateSource?: EventBusInterface + public nodeFilter?: (node: TreeNode) => boolean + private subscriptionEvent?: any + public isTree = true + private cachedHash = `${Math.random()}` + constructor() { super(undefined, undefined) } - public updateWithConnection(emitter: EventBusInterface, connectionId: string) { + public updateWithConnection(emitter: EventBusInterface, connectionId: string, nodeFilter?:(node: TreeNode) => boolean) { this.updateSource = emitter - this.updateSource.subscribe(makeConnectionMessageEvent(connectionId), this.handleNewData) + this.connectionId = connectionId + this.nodeFilter = nodeFilter + + this.subscriptionEvent = makeConnectionMessageEvent(connectionId) + this.updateSource.subscribe(this.subscriptionEvent, this.handleNewData) + } + + public hash() { + return this.cachedHash } private handleNewData = (msg: MqttMessage) => { const edges = msg.topic.split('/') const node = TreeNodeFactory.fromEdgesAndValue(edges, msg.payload) node.mqttMessage = msg + + if (this.nodeFilter && !this.nodeFilter(node)) { + return + } this.updateWithNode(node.firstNode()) } public stopUpdating() { - if (this.updateSource && this.connectionId) { - this.updateSource.unsubscribeAll(makeConnectionMessageEvent(this.connectionId)) + if (this.subscriptionEvent && this.updateSource) { + console.log(this.updateSource.ipc) + this.updateSource.unsubscribe(this.subscriptionEvent, this.handleNewData) } } } diff --git a/backend/src/Model/TreeNode.ts b/backend/src/Model/TreeNode.ts index a3bb506..bde538e 100644 --- a/backend/src/Model/TreeNode.ts +++ b/backend/src/Model/TreeNode.ts @@ -13,6 +13,7 @@ export class TreeNode { public onMerge = new EventDispatcher(this) public onEdgesChange = new EventDispatcher(this) public onMessage = new EventDispatcher(this) + public isTree = false private cachedLeafes?: TreeNode[] private cachedLeafMessageCount?: number diff --git a/events/EventBus.ts b/events/EventBus.ts index 8059d56..f2a31f3 100644 --- a/events/EventBus.ts +++ b/events/EventBus.ts @@ -6,6 +6,12 @@ export interface EventBusInterface { subscribe(event: Event, callback:(msg: MessageType) => void): void unsubscribeAll(event: Event): void emit(event: Event, msg: MessageType): void + unsubscribe(event: Event, callback: any): void +} + +interface CallbackStore { + wrappedCallback: any + callback: any } class IpcMainEventBus implements EventBusInterface { @@ -27,6 +33,10 @@ class IpcMainEventBus implements EventBusInterface { this.ipc.removeAllListeners(event.topic) } + public unsubscribe(event: Event, callback: any) { + throw new Error('Not implemented') // Todo: implement + } + public emit(event: Event, msg: MessageType) { if (!this.client.isDestroyed()) { this.client.send(event.topic, msg) @@ -36,18 +46,40 @@ class IpcMainEventBus implements EventBusInterface { class IpcRendererEventBus implements EventBusInterface { private ipc: IpcRenderer + private callbacks: CallbackStore[] = [] + constructor(ipc: IpcRenderer) { this.ipc = ipc } public subscribe(event: Event, callback:(msg: MessageType) => void) { - this.ipc.on(event.topic, (_event: any, arg: any) => callback(arg)) + const wrappedCallback = (_event: any, arg: any) => { + callback(arg) + } + + this.ipc.on(event.topic, wrappedCallback) + + this.callbacks.push({ + callback, + wrappedCallback, + }) } public unsubscribeAll(event: Event) { this.ipc.removeAllListeners(event.topic) } + public unsubscribe(event: Event, callback: any) { + debugger + const item = this.callbacks.find(store => store.callback === callback) + if (!item) { + return + } + + this.ipc.removeListener(event.topic, item.wrappedCallback) + this.callbacks = this.callbacks.filter(a => a !== item) + } + public emit(event: Event, msg: MessageType) { this.ipc.send(event.topic, msg) }