From 6b0f2085e53452c381c139c2588a6982cf6cc533 Mon Sep 17 00:00:00 2001 From: Thomas Nordquist Date: Wed, 23 Jan 2019 11:54:12 +0100 Subject: [PATCH] Add pane resizing Refactor styles Update autoExpand with filtered result count Add redux batch reducer --- app/index.html | 51 +++++++++++++++ app/package-lock.json | 48 +++++++++++++- app/package.json | 3 + app/src/App.tsx | 36 ++++++----- app/src/actions/Settings.ts | 22 ++++++- app/src/components/Settings.tsx | 25 ++++++-- app/src/components/Tree/TreeNode.tsx | 32 +++++----- app/src/components/Tree/TreeNodeSubnodes.tsx | 35 ++++++---- app/src/components/Tree/TreeNodeTitle.tsx | 67 +++++++++----------- app/src/index.tsx | 2 + 10 files changed, 230 insertions(+), 91 deletions(-) diff --git a/app/index.html b/app/index.html index 23522d9..bc51f71 100644 --- a/app/index.html +++ b/app/index.html @@ -58,6 +58,57 @@ 0% {opacity: 1;} 100% {opacity: 0;} } + + .Resizer { + background: #eee; + opacity: .2; + z-index: 1; + -moz-box-sizing: border-box; + -webkit-box-sizing: border-box; + box-sizing: border-box; + -moz-background-clip: padding; + -webkit-background-clip: padding; + background-clip: padding-box; + } + + .Resizer:hover { + -webkit-transition: all 0.3s ease-out; + transition: all 0.3s ease-out; + } + + .Resizer.horizontal { + height: 11px; + margin: -5px 0; + border-top: 5px solid rgba(255, 255, 255, 0); + border-bottom: 5px solid rgba(255, 255, 255, 0); + cursor: row-resize; + width: 100%; + } + + .Resizer.horizontal:hover { + border-top: 5px solid rgba(0, 0, 0, 0.5); + border-bottom: 5px solid rgba(0, 0, 0, 0.5); + } + + .Resizer.vertical { + width: 11px; + margin: 0 -5px; + border-left: 5px solid rgba(255, 255, 255, 0); + border-right: 5px solid rgba(255, 255, 255, 0); + cursor: col-resize; + } + + .Resizer.vertical:hover { + border-left: 5px solid rgba(255, 255, 255, 0.5); + border-right: 5px solid rgba(255, 255, 255, 0.5); + } + .Resizer.disabled { + cursor: not-allowed; + } + .Resizer.disabled:hover { + border-color: transparent; + } + diff --git a/app/package-lock.json b/app/package-lock.json index 3d808ef..a0c621a 100644 --- a/app/package-lock.json +++ b/app/package-lock.json @@ -266,6 +266,14 @@ "@types/react": "*" } }, + "@types/react-split-pane": { + "version": "0.1.67", + "resolved": "https://registry.npmjs.org/@types/react-split-pane/-/react-split-pane-0.1.67.tgz", + "integrity": "sha512-2vq9mohqYZ7kR+XcedfjyI2M7/E8W5owr0KupGGP+TfFp/O9f6c/S06MCB3FKWiBwazA7+Zyj50OTxvYy1kGLA==", + "requires": { + "react-split-pane": "*" + } + }, "@types/react-transition-group": { "version": "2.0.15", "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-2.0.15.tgz", @@ -5768,7 +5776,6 @@ "version": "16.7.0", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.7.0.tgz", "integrity": "sha512-D0Ufv1ExCAmF38P2Uh1lwpminZFRXEINJe53zRAbm4KPwSyd6DY/uDoS0Blj9jvPpn1+wivKpZYc8aAAN/nAkg==", - "dev": true, "requires": { "loose-envify": "^1.1.0", "object-assign": "^4.1.1", @@ -5851,6 +5858,39 @@ "resize-observer-polyfill": "^1.5.1" } }, + "react-split-pane": { + "version": "0.1.85", + "resolved": "https://registry.npmjs.org/react-split-pane/-/react-split-pane-0.1.85.tgz", + "integrity": "sha512-3GhaYs6+eVNrewgN4eQKJoNMQ4pcegNMTMhR5bO/NFO91K6/98qdD1sCuWPpsefCjzxNTjkvVYWQC0bMaC45mA==", + "requires": { + "prop-types": "^15.5.10", + "react": "^16.6.3", + "react-dom": "^16.6.3", + "react-lifecycles-compat": "^3.0.4", + "react-style-proptype": "^3.0.0" + }, + "dependencies": { + "react": { + "version": "16.7.0", + "resolved": "https://registry.npmjs.org/react/-/react-16.7.0.tgz", + "integrity": "sha512-StCz3QY8lxTb5cl2HJxjwLFOXPIFQp+p+hxQfc8WE0QiLfCtIlKj8/+5tjjKm8uSTlAW+fCPaavGFS06V9Ar3A==", + "requires": { + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1", + "prop-types": "^15.6.2", + "scheduler": "^0.12.0" + } + } + } + }, + "react-style-proptype": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/react-style-proptype/-/react-style-proptype-3.2.2.tgz", + "integrity": "sha512-ywYLSjNkxKHiZOqNlso9PZByNEY+FTyh3C+7uuziK0xFXu9xzdyfHwg4S9iyiRRoPCR4k2LqaBBsWVmSBwCWYQ==", + "requires": { + "prop-types": "^15.5.4" + } + }, "react-textarea-autosize": { "version": "6.1.0", "resolved": "http://registry.npmjs.org/react-textarea-autosize/-/react-textarea-autosize-6.1.0.tgz", @@ -6002,6 +6042,11 @@ "symbol-observable": "^1.2.0" } }, + "redux-batched-actions": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/redux-batched-actions/-/redux-batched-actions-0.4.1.tgz", + "integrity": "sha512-r6tLDyBP3U9cXNLEHs0n1mX5TQfmk6xE0Y9uinYZ5HOyAWDgIJxYqRRkU/bC6XrJ4nS7tasNbxaHJHVmf9UdkA==" + }, "reflect-metadata": { "version": "0.1.13", "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.13.tgz", @@ -6211,7 +6256,6 @@ "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" diff --git a/app/package.json b/app/package.json index 85e16b0..a253925 100644 --- a/app/package.json +++ b/app/package.json @@ -31,6 +31,7 @@ "@material-ui/styles": "^3.0.0-alpha.8", "@types/node": "^10.12.18", "@types/react-resize-detector": "^3.1.0", + "@types/react-split-pane": "^0.1.67", "@types/sha1": "^1.1.1", "@types/socket.io-client": "^1.4.32", "@types/vis": "^4.21.9", @@ -42,6 +43,8 @@ "react-ace": "^6.3.2", "react-json-view": "^1.19.1", "react-resize-detector": "^3.4.0", + "react-split-pane": "^0.1.85", + "redux-batched-actions": "^0.4.1", "sha1": "^1.1.1", "socket.io-client": "^2.2.0", "source-map-loader": "^0.2.4", diff --git a/app/src/App.tsx b/app/src/App.tsx index 92cccee..2992e7b 100644 --- a/app/src/App.tsx +++ b/app/src/App.tsx @@ -13,6 +13,7 @@ import Tree from './components/Tree/Tree' import UpdateNotifier from './UpdateNotifier' import { connect } from 'react-redux' import ErrorBoundary from './ErrorBoundary' +import { default as SplitPane } from 'react-split-pane' interface Props { name: string @@ -29,22 +30,33 @@ class App extends React.Component { public render() { const { settingsVisible } = this.props - const { content, contentShift, centerContent, left, right } = this.props.classes + const { content, contentShift, centerContent, paneDefaults, heightProperty } = this.props.classes return (
-
+
-
+ +
-
+
+
@@ -65,24 +77,16 @@ const mapStateToProps = (state: AppState) => { const styles = (theme: Theme) => { const drawerWidth = 300 return { - left: { + heightProperty: { + height: 'calc(100vh - 64px) !important', + }, + paneDefaults: { 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', diff --git a/app/src/actions/Settings.ts b/app/src/actions/Settings.ts index 163af95..c473e74 100644 --- a/app/src/actions/Settings.ts +++ b/app/src/actions/Settings.ts @@ -4,6 +4,8 @@ import { Dispatch } from 'redux' import { showTree } from './Tree' import { AppState } from '../reducers' import * as q from '../../../backend/src/Model' +import { batchActions, enableBatching, batchDispatchMiddleware } from 'redux-batched-actions'; +import { autoExpandLimitSet } from '../components/Settings'; export const setAutoExpandLimit = (autoExpandLimit: number = 0): Action => { return { @@ -34,7 +36,7 @@ export const filterTopics = (filterStr: string) => (dispatch: Dispatch, get }) if (!filterStr || !tree) { - dispatch(showTree(tree)) + dispatch(batchActions([setAutoExpandLimit(0), showTree(tree)])) return } @@ -67,5 +69,21 @@ export const filterTopics = (filterStr: string) => (dispatch: Dispatch, get nextTree.updateWithConnection(tree.updateSource, tree.connectionId, nodeFilter) } - dispatch(showTree(nextTree)) + dispatch(batchActions([setAutoExpandLimit(autoExpandLimitForTree(nextTree)), showTree(nextTree)])) +} + +function autoExpandLimitForTree(tree: q.Tree) { + if (!tree) { + return 0 + } + function closestExistingLimit(i: number): number { + const sorted = autoExpandLimitSet.sort((a, b) => Math.abs(a.limit - i) - Math.abs(b.limit - i)) + console.log('sorted', i, sorted) + return sorted[0]!.limit + } + + const count = tree.childTopicCount() + const calculatedLimit = Math.max(7 - Math.log(count), 0) * 2 + + return closestExistingLimit(calculatedLimit) } diff --git a/app/src/components/Settings.tsx b/app/src/components/Settings.tsx index 7938e34..81eaa7c 100644 --- a/app/src/components/Settings.tsx +++ b/app/src/components/Settings.tsx @@ -20,6 +20,23 @@ import { settingsActions, treeActions } from '../actions' import { TopicOrder } from '../reducers/Settings' import BrokerStatistics from './BrokerStatistics' +export const autoExpandLimitSet = [{ + limit: 0, + name: 'Collapsed', +}, { + limit: 2, + name: 'Few', +}, { + limit: 3, + name: 'Some', +}, { + limit: 10, + name: 'Most', +}, { + limit: 1E6, + name: 'All', +}] + const styles: StyleRulesCallback = theme => ({ drawer: { backgroundColor: theme.palette.background.default, @@ -87,6 +104,8 @@ class Settings extends React.Component { private renderAutoExpand() { const { classes, autoExpandLimit } = this.props + + const limits = autoExpandLimitSet.map(limit => {limit.name}) return (
Auto Expand @@ -98,11 +117,7 @@ class Settings extends React.Component { className={classes.input} style={{ flex: '1' }} > - Collapsed - Few - Some - Most - All + {limits}
) diff --git a/app/src/components/Tree/TreeNode.tsx b/app/src/components/Tree/TreeNode.tsx index 3b4ef9a..7e5c2f5 100644 --- a/app/src/components/Tree/TreeNode.tsx +++ b/app/src/components/Tree/TreeNode.tsx @@ -26,11 +26,11 @@ const styles = (theme: Theme) => { display: 'block', marginLeft: '10px', }, - hover: { - '&:hover': { - backgroundColor: 'rgba(80, 80, 80, 0.35)', - }, - }, + // hover: { + // '&:hover': { + // backgroundColor: 'rgba(80, 80, 80, 0.35)', + // }, + // }, topicSelect: { float: 'right' as 'right', opacity: 0, @@ -51,7 +51,7 @@ interface Props { performanceCallback?: ((ms: number) => void) | undefined autoExpandLimit: number classes: any - style?: React.CSSProperties + className?: string } interface State { @@ -68,6 +68,7 @@ class TreeNode extends React.Component { private willUpdateTime: number = performance.now() private titleRef?: React.RefObject = React.createRef() + private nodeRef?: React.RefObject = React.createRef() private topicSelectRef?: React.RefObject = React.createRef() private subnodesDidchange = () => { @@ -119,6 +120,7 @@ class TreeNode extends React.Component { this.removeSubscriber(treeNode) this.topicSelectRef = undefined this.titleRef = undefined + this.nodeRef = undefined } private stateHasChanged(newState: State) { @@ -177,11 +179,11 @@ class TreeNode extends React.Component { return (
{ lastUpdate={this.props.treeNode.lastUpdate} /> -
- -
{this.renderNodes()}
) @@ -206,12 +200,18 @@ class TreeNode extends React.Component { private mouseOver = (event: React.MouseEvent) => { event.stopPropagation() + if (this.nodeRef && this.nodeRef.current) { + this.nodeRef.current.style.backgroundColor = 'rgba(100, 100, 100, 0.55)' + } if (this.topicSelectRef && this.topicSelectRef.current) { this.topicSelectRef.current.style.opacity = '1' } } private mouseOut = (event: React.MouseEvent) => { event.stopPropagation() + if (this.nodeRef && this.nodeRef.current) { + this.nodeRef.current.style.backgroundColor = 'inherit' + } if (this.topicSelectRef && this.topicSelectRef.current) { this.topicSelectRef.current.style.opacity = '0' } diff --git a/app/src/components/Tree/TreeNodeSubnodes.tsx b/app/src/components/Tree/TreeNodeSubnodes.tsx index 85240ad..80988c3 100644 --- a/app/src/components/Tree/TreeNodeSubnodes.tsx +++ b/app/src/components/Tree/TreeNodeSubnodes.tsx @@ -6,6 +6,7 @@ import { AppState } from '../../reducers' import TreeNode from './TreeNode' import { connect } from 'react-redux' import { TopicOrder } from '../../reducers/Settings' +import { Theme, withStyles } from '@material-ui/core' export interface Props { lastUpdate: number @@ -16,6 +17,7 @@ export interface Props { filter?: string collapsed?: boolean | undefined didSelectNode?: (node: q.TreeNode) => void + classes: any } interface State { @@ -69,24 +71,19 @@ class TreeNodeSubnodes extends React.Component { this.renderMore() } - const listItemStyle = { - padding: '3px 0px 0px 8px', - } - const nodes = this.sortedNodes().slice(0, this.state.alreadyAdded) const listItems = nodes.map(node => ( -
- -
+ )) return ( - + {listItems} ) @@ -100,4 +97,14 @@ const mapStateToProps = (state: AppState) => { } } -export default connect(mapStateToProps)(TreeNodeSubnodes) +const styles = (theme: Theme) => ({ + list: { + display: 'block' as 'block', + clear: 'both' as 'both', + }, + listItem: { + padding: '3px 0px 0px 8px', + }, +}) + +export default withStyles(styles)(connect(mapStateToProps)(TreeNodeSubnodes)) diff --git a/app/src/components/Tree/TreeNodeTitle.tsx b/app/src/components/Tree/TreeNodeTitle.tsx index af8faf4..2ca5b54 100644 --- a/app/src/components/Tree/TreeNodeTitle.tsx +++ b/app/src/components/Tree/TreeNodeTitle.tsx @@ -3,6 +3,7 @@ import { connect } from 'react-redux' import { bindActionCreators } from 'redux' import { treeActions } from '../../actions' import * as q from '../../../../backend/src/Model' +import { withStyles, Theme } from '@material-ui/core' export interface TreeNodeProps extends React.HTMLAttributes { treeNode: q.TreeNode @@ -10,62 +11,33 @@ export interface TreeNodeProps extends React.HTMLAttributes { name?: string | undefined collapsed?: boolean | undefined lastUpdate: number + classes: any } class TreeNodeTitle extends React.Component { - private getStyles() { - return { - collapsedSubnodes: { - color: 'white', // theme.palette.text.secondary, - }, - container: { - display: 'block', - }, - } - } - - private didSelectNode = (event: React.MouseEvent) => { - event.stopPropagation() + private mouseOver = (event: React.MouseEvent) => { if (this.props.treeNode.message) { this.props.actions.selectTopic(this.props.treeNode) } } public render() { - const style: React.CSSProperties = { - lineHeight: '1em', - whiteSpace: 'nowrap', - } return ( - + {this.renderExpander()} {this.renderSourceEdge()} {this.renderCollapsedSubnodes()} {this.renderValue()} ) } private renderSourceEdge() { - const style: React.CSSProperties = { - fontWeight: 'bold', - overflow: 'hidden', - display: 'inline-block', - } const name = this.props.name || (this.props.treeNode.sourceEdge && this.props.treeNode.sourceEdge.name) - return {name} + return {name} } private renderValue() { - const style: React.CSSProperties = { - whiteSpace: 'nowrap', - overflow: 'hidden', - textOverflow: 'ellipsis', - padding: '0', - marginLeft: '5px', - display: 'inline-block', - } - return this.props.treeNode.message && this.props.treeNode.message.length > 0 - ? = {this.props.treeNode.message.value.toString()} + ? = {this.props.treeNode.message.value.toString().slice(0, 120)} : null } @@ -83,7 +55,7 @@ class TreeNodeTitle extends React.Component { } const messages = this.props.treeNode.leafMessageCount() - return ({this.props.treeNode.childTopicCount()} topics, {messages} messages) + return ({this.props.treeNode.childTopicCount()} topics, {messages} messages) } } @@ -93,4 +65,27 @@ const mapDispatchToProps = (dispatch: any) => { } } -export default connect(null, mapDispatchToProps)(TreeNodeTitle) +const styles = (theme: Theme) => ({ + value: { + whiteSpace: 'nowrap' as 'nowrap', + overflow: 'hidden' as 'hidden', + textOverflow: 'ellipsis' as 'ellipsis', + padding: '0', + marginLeft: '5px', + display: 'inline-block' as 'inline-block', + }, + sourceEdge: { + fontWeight: 'bold' as 'bold', + overflow: 'hidden' as 'hidden', + display: 'inline-block' as 'inline-block', + }, + title: { + lineHeight: '1em', + whiteSpace: 'nowrap' as 'nowrap', + }, + collapsedSubnodes: { + color: theme.palette.text.secondary, + }, +}) + +export default withStyles(styles)(connect(null, mapDispatchToProps)(TreeNodeTitle)) diff --git a/app/src/index.tsx b/app/src/index.tsx index 7bd81b3..a3ae610 100644 --- a/app/src/index.tsx +++ b/app/src/index.tsx @@ -3,6 +3,7 @@ import './tracking' import * as React from 'react' import * as ReactDOM from 'react-dom' import reduxThunk from 'redux-thunk' +import { batchDispatchMiddleware } from 'redux-batched-actions'; import { MuiThemeProvider, createMuiTheme } from '@material-ui/core/styles' import reducers from './reducers' @@ -17,6 +18,7 @@ const store = createStore( composeEnhancers( applyMiddleware( reduxThunk, + batchDispatchMiddleware, ), ), )