From 8e49d19fbe5ebd308773e77d837d70085d379855 Mon Sep 17 00:00:00 2001 From: Thomas Nordquist Date: Thu, 11 Jul 2019 15:33:42 +0200 Subject: [PATCH] Refactor --- app/package.json | 1 + app/src/actions/Tree.ts | 5 +- .../Tree/TreeNode/TreeNodeSubnodes.tsx | 2 +- .../useAnimationToIndicateTopicUpdate.tsx | 5 ++ .../effects/useIsAllowedToAutoExpandState.tsx | 1 + .../effects/useViewModelSubscriptions.tsx | 54 +++++++++---------- app/src/components/Tree/TreeNode/index.tsx | 12 ++--- app/src/components/Tree/index.tsx | 12 ++--- app/yarn.lock | 5 ++ backend/src/Model/Tree.ts | 35 ++++++++++-- 10 files changed, 84 insertions(+), 48 deletions(-) diff --git a/app/package.json b/app/package.json index c00ee49..f5dee20 100644 --- a/app/package.json +++ b/app/package.json @@ -28,6 +28,7 @@ "file-loader": "^4.0.0", "get-value": "^3.0.1", "immutable": "^4.0.0-rc.12", + "in-viewport": "^3.6.0", "js-base64": "^2.5.1", "json-to-ast": "^2.1.0", "lodash.debounce": "^4.0.8", diff --git a/app/src/actions/Tree.ts b/app/src/actions/Tree.ts index b78f1e1..c6cd19a 100644 --- a/app/src/actions/Tree.ts +++ b/app/src/actions/Tree.ts @@ -99,13 +99,16 @@ export const togglePause = (tree?: q.Tree) => (dispatch: Dispatc const tree = getState().tree.get('tree') const changes = tree ? tree.unmergedChanges().length : 0 + if (tree) { + paused ? tree.resume() : tree.pause() + } + if (paused && changes > 0) { dispatch(globalActions.showNotification('Applying recorded changes.')) } // Allow for notification to be displayed setTimeout(() => { - tree && tree.applyUnmergedChanges() if (paused && changes > 0) { dispatch(globalActions.showNotification(`Successfully applied ${changes} changes.`)) } diff --git a/app/src/components/Tree/TreeNode/TreeNodeSubnodes.tsx b/app/src/components/Tree/TreeNode/TreeNodeSubnodes.tsx index aa81c81..a2e04e7 100644 --- a/app/src/components/Tree/TreeNode/TreeNodeSubnodes.tsx +++ b/app/src/components/Tree/TreeNode/TreeNodeSubnodes.tsx @@ -29,7 +29,7 @@ function useStagedRendering(treeNode: q.TreeNode) { if (alreadyAdded < edges.length) { renderMoreAnimationFrame = (window as any).requestIdleCallback( () => { - setAlreadyAdded(alreadyAdded * 1.5) + setAlreadyAdded(Math.max(25, alreadyAdded * 1.5)) }, { timeout: 500 } ) diff --git a/app/src/components/Tree/TreeNode/effects/useAnimationToIndicateTopicUpdate.tsx b/app/src/components/Tree/TreeNode/effects/useAnimationToIndicateTopicUpdate.tsx index aba1811..9f62f7f 100644 --- a/app/src/components/Tree/TreeNode/effects/useAnimationToIndicateTopicUpdate.tsx +++ b/app/src/components/Tree/TreeNode/effects/useAnimationToIndicateTopicUpdate.tsx @@ -1,4 +1,5 @@ import React, { useEffect } from 'react' +var inViewport = require('in-viewport') export function useAnimationToIndicateTopicUpdate( ref: React.MutableRefObject, @@ -9,6 +10,10 @@ export function useAnimationToIndicateTopicUpdate( ) { useEffect(() => { if (ref.current && shouldAnimate && Date.now() - lastUpdate < 3000 && !selected) { + if (!inViewport(ref.current)) { + return + } + let timeout: any let animationFrame = requestAnimationFrame(() => { ref.current && ref.current.classList.add(className) diff --git a/app/src/components/Tree/TreeNode/effects/useIsAllowedToAutoExpandState.tsx b/app/src/components/Tree/TreeNode/effects/useIsAllowedToAutoExpandState.tsx index 5556ae2..0881ad8 100644 --- a/app/src/components/Tree/TreeNode/effects/useIsAllowedToAutoExpandState.tsx +++ b/app/src/components/Tree/TreeNode/effects/useIsAllowedToAutoExpandState.tsx @@ -4,6 +4,7 @@ import { useEffect, useState } from 'react' export function useIsAllowedToAutoExpandState(props: Props): boolean { const { settings, treeNode, isRoot } = props const [isAllowedToAutoExpand, setAllowAutoExpand] = useState(false) + useEffect(() => { const newIsAllowedToAutoExpand = isRoot || treeNode.edgeCount() <= settings.get('autoExpandLimit') if (newIsAllowedToAutoExpand !== isAllowedToAutoExpand) { diff --git a/app/src/components/Tree/TreeNode/effects/useViewModelSubscriptions.tsx b/app/src/components/Tree/TreeNode/effects/useViewModelSubscriptions.tsx index 2dc94cb..d051eaf 100644 --- a/app/src/components/Tree/TreeNode/effects/useViewModelSubscriptions.tsx +++ b/app/src/components/Tree/TreeNode/effects/useViewModelSubscriptions.tsx @@ -8,37 +8,37 @@ export function useViewModelSubscriptions( setSelected: (value: boolean) => void, setCollapsedOverride: (value: boolean) => void ) { - const selectionDidChange = () => { - const selected = treeNode.viewModel && treeNode.viewModel.isSelected() - treeNode.viewModel && setSelected(Boolean(selected)) - - if (selected && nodeRef && nodeRef.current) { - nodeRef.current.focus({ preventScroll: false }) - } - } - - const expandedDidChange = () => { - treeNode.viewModel && setCollapsedOverride(!treeNode.viewModel.isExpanded()) - } - useEffect(() => { + const selectionDidChange = () => { + const selected = treeNode.viewModel && treeNode.viewModel.isSelected() + treeNode.viewModel && setSelected(Boolean(selected)) + + if (selected && nodeRef && nodeRef.current) { + nodeRef.current.focus({ preventScroll: false }) + } + } + + const expandedDidChange = () => { + treeNode.viewModel && setCollapsedOverride(!treeNode.viewModel.isExpanded()) + } + + function addSubscriber() { + treeNode.viewModel = new TopicViewModel() + treeNode.viewModel.selectionChange.subscribe(selectionDidChange) + treeNode.viewModel.expandedChange.subscribe(expandedDidChange) + } + + function removeSubscriber() { + if (treeNode.viewModel) { + treeNode.viewModel.selectionChange.unsubscribe(selectionDidChange) + treeNode.viewModel.expandedChange.unsubscribe(expandedDidChange) + treeNode.viewModel = undefined + } + } + addSubscriber() return function cleanup() { removeSubscriber() } }, [treeNode]) - - function addSubscriber() { - treeNode.viewModel = new TopicViewModel() - treeNode.viewModel.selectionChange.subscribe(selectionDidChange) - treeNode.viewModel.expandedChange.subscribe(expandedDidChange) - } - - function removeSubscriber() { - if (treeNode.viewModel) { - treeNode.viewModel.selectionChange.unsubscribe(selectionDidChange) - treeNode.viewModel.expandedChange.unsubscribe(expandedDidChange) - treeNode.viewModel = undefined - } - } } diff --git a/app/src/components/Tree/TreeNode/index.tsx b/app/src/components/Tree/TreeNode/index.tsx index 29c69d6..8ad4edc 100644 --- a/app/src/components/Tree/TreeNode/index.tsx +++ b/app/src/components/Tree/TreeNode/index.tsx @@ -48,10 +48,6 @@ function TreeNodeComponent(props: Props) { const isCollapsed = Boolean(collapsedOverride) === collapsedOverride ? Boolean(collapsedOverride) : !isAllowedToAutoExpand - const toggle = useCallback(() => { - setCollapsedOverride(!isCollapsed) - }, [isCollapsed]) - const didSelectTopic = useCallback( (event?: React.MouseEvent) => { event && event.stopPropagation() @@ -64,17 +60,17 @@ function TreeNodeComponent(props: Props) { (event: React.MouseEvent) => { event.stopPropagation() didSelectTopic() - toggle() + setCollapsedOverride(!isCollapsed) }, - [toggle, didSelectTopic] + [isCollapsed, didSelectTopic] ) const toggleCollapsed = useCallback( (event: React.MouseEvent) => { event.stopPropagation() - toggle() + setCollapsedOverride(!isCollapsed) }, - [toggle] + [isCollapsed] ) const didObtainFocus = useCallback(() => { diff --git a/app/src/components/Tree/index.tsx b/app/src/components/Tree/index.tsx index 42ba1d0..59afdb5 100644 --- a/app/src/components/Tree/index.tsx +++ b/app/src/components/Tree/index.tsx @@ -4,11 +4,10 @@ import TreeNode from './TreeNode' import { AppState } from '../../reducers' import { bindActionCreators } from 'redux' import { connect } from 'react-redux' +import { KeyCodes } from '../../utils/KeyCodes' import { SettingsState } from '../../reducers/Settings' import { TopicViewModel } from '../../model/TopicViewModel' import { treeActions } from '../../actions' -import { KeyCodes } from '../../utils/KeyCodes' - const MovingAverage = require('moving-average') const averagingTimeInterval = 10 * 1000 @@ -70,17 +69,17 @@ class TreeComponent extends React.PureComponent { public componentWillReceiveProps(nextProps: Props) { if (this.props.tree !== nextProps.tree) { if (this.props.tree) { - this.props.tree.didReceive.unsubscribe(this.throttledTreeUpdate) + this.props.tree.didUpdate.unsubscribe(this.throttledTreeUpdate) } if (nextProps.tree) { - nextProps.tree.didReceive.subscribe(this.throttledTreeUpdate) + nextProps.tree.didUpdate.subscribe(this.throttledTreeUpdate) } this.setState(this.state) } } public componentWillUnmount() { - this.props.tree && this.props.tree.didReceive.unsubscribe(this.throttledTreeUpdate) + this.props.tree && this.props.tree.didUpdate.unsubscribe(this.throttledTreeUpdate) } public throttledTreeUpdate = () => { @@ -99,9 +98,6 @@ class TreeComponent extends React.PureComponent { this.updateTimer = undefined this.renderTime = performance.now() - if (!this.props.paused && this.props.tree) { - this.props.tree.applyUnmergedChanges() - } window.requestIdleCallback( () => { this.setState({ lastUpdate: this.renderTime }) diff --git a/app/yarn.lock b/app/yarn.lock index 77e0bf1..9dfc06e 100644 --- a/app/yarn.lock +++ b/app/yarn.lock @@ -3038,6 +3038,11 @@ imurmurhash@^0.1.4: resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" integrity sha1-khi5srkoojixPcT7a21XbyMUU+o= +in-viewport@^3.6.0: + version "3.6.0" + resolved "https://registry.yarnpkg.com/in-viewport/-/in-viewport-3.6.0.tgz#c59b4cdcaa41adb5bf5b8fe390c7d34259891f4a" + integrity sha512-MhaJ7Pr3NhUyAfpULysTZZBUAYfJAX1O8PccW2gvXlbQduMrJz7qQQ5yzC7SAr/0g5LbeRk432yNjsLMCnYzJg== + indexes-of@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/indexes-of/-/indexes-of-1.0.1.tgz#f30f716c8e2bd346c7b67d3df3915566a7c05607" diff --git a/backend/src/Model/Tree.ts b/backend/src/Model/Tree.ts index ca8514f..15e6bb0 100644 --- a/backend/src/Model/Tree.ts +++ b/backend/src/Model/Tree.ts @@ -12,7 +12,11 @@ export class Tree extends TreeNode { public isTree = true private cachedHash = `${Math.random()}` private unmergedMessages: ChangeBuffer = new ChangeBuffer() - public didReceive = new EventDispatcher() + public didUpdate = new EventDispatcher() + + public updateInterval: any + private paused: boolean = false + private applyChangesHasCompleted = true constructor() { super(undefined, undefined) @@ -20,14 +24,27 @@ export class Tree extends TreeNode { private handleNewData = (msg: MqttMessage) => { this.unmergedMessages.push(msg) - this.didReceive.dispatch() + } + + private runUpdates() { + this.updateInterval = setInterval(() => { + if (!this.paused && this.applyChangesHasCompleted) { + this.applyChangesHasCompleted = false + if ((window as any).requestIdleCallback) { + ;(window as any).requestIdleCallback(() => this.applyUnmergedChanges(), { timeout: 500 }) + } else { + this.applyUnmergedChanges() + } + } + }, 300) } public destroy() { super.destroy() + this.updateInterval && clearInterval(this.updateInterval) this.updateSource && this.updateSource.unsubscribe(this.subscriptionEvent, this.handleNewData) this.updateSource = undefined - this.didReceive.removeAllListeners() + this.didUpdate.removeAllListeners() } public updateWithConnection( @@ -41,12 +58,21 @@ export class Tree extends TreeNode { this.subscriptionEvent = makeConnectionMessageEvent(connectionId) this.updateSource.subscribe(this.subscriptionEvent, this.handleNewData) + this.runUpdates() } public hash() { return this.cachedHash } + public pause() { + this.paused = true + } + + public resume() { + this.paused = false + } + public applyUnmergedChanges() { this.unmergedMessages.popAll().forEach(bufferedItem => { const edges = bufferedItem.message.topic.split('/') @@ -61,6 +87,9 @@ export class Tree extends TreeNode { this.updateWithNode(node.firstNode()) } }) + + this.didUpdate.dispatch() + this.applyChangesHasCompleted = true } public unmergedChanges(): ChangeBuffer {