From d8a3817c06a05ef5c2c50078e2f30e3c5c0834d2 Mon Sep 17 00:00:00 2001 From: Philipp Otto Date: Mon, 9 Mar 2020 17:51:42 +0100 Subject: [PATCH] memoize comment tab data computation and only update component when diff of skeleton produces relevant actions --- .../comment_tab/comment_tab_view.js | 230 +++++++++++------- 1 file changed, 141 insertions(+), 89 deletions(-) diff --git a/frontend/javascripts/oxalis/view/right-menu/comment_tab/comment_tab_view.js b/frontend/javascripts/oxalis/view/right-menu/comment_tab/comment_tab_view.js index ddab5679783..abd483e4764 100644 --- a/frontend/javascripts/oxalis/view/right-menu/comment_tab/comment_tab_view.js +++ b/frontend/javascripts/oxalis/view/right-menu/comment_tab/comment_tab_view.js @@ -7,12 +7,14 @@ import Enum from "Enumjs"; import Maybe from "data.maybe"; import * as React from "react"; import _ from "lodash"; +import memoizeOne from "memoize-one"; import update from "immutability-helper"; import { Comment } from "oxalis/view/right-menu/comment_tab/comment"; import { type Comparator, compareBy, localeCompareBy, zipMaybe } from "libs/utils"; import { InputKeyboard } from "libs/input"; import { MarkdownModal } from "oxalis/view/components/markdown_modal"; +import { cachedDiffTrees } from "oxalis/model/sagas/skeletontracing_saga"; import { getActiveTree, getActiveNode } from "oxalis/model/accessors/skeletontracing_accessor"; import { listenToStoreProperty } from "oxalis/model/helpers/listener_helpers"; import { makeSkeletonTracingGuard } from "oxalis/view/guards"; @@ -22,6 +24,7 @@ import { deleteCommentAction, } from "oxalis/model/actions/skeletontracing_actions"; import ButtonComponent from "oxalis/view/components/button_component"; +import DomVisibilityObserver from "oxalis/view/components/dom_visibility_observer"; import InputComponent from "oxalis/view/components/input_component"; import Store, { type CommentType, @@ -94,7 +97,33 @@ type CommentTabState = { isMarkdownModalVisible: boolean, }; -class CommentTabView extends React.PureComponent { +const RELEVANT_ACTIONS_FOR_COMMENTS = [ + "updateTree", + "deleteTree", + "mergeTree", + "moveTreeComponent", + "deleteEdge", + "revertToVersion", +]; + +const memoizedDeriveData = memoizeOne( + (trees, state: CommentTabState): CommentTabState => { + const sortedTrees = _.values(trees) + .filter(tree => tree.comments.length > 0) + .sort(getTreeSorter(state)); + + const commentSorter = getCommentSorter(state); + const data = sortedTrees.reduce((result, tree) => { + result.push(tree); + const isCollapsed = state.collapsedTreeIds[tree.treeId]; + return isCollapsed ? result : result.concat(tree.comments.slice().sort(commentSorter)); + }, []); + + return data; + }, +); + +class CommentTabView extends React.Component { listRef: ?List; storePropertyUnsubscribers: Array<() => void> = []; keyboard = new InputKeyboard( @@ -108,28 +137,30 @@ class CommentTabView extends React.PureComponent { - const sortedTrees = _.values(props.skeletonTracing.trees) - .filter(tree => tree.comments.length > 0) - .sort(getTreeSorter(state)); + shouldComponentUpdate(nextProps, nextState) { + if (nextState != this.state) { + return true; + } - const data = sortedTrees.reduce((result, tree) => { - result.push(tree); - const isCollapsed = state.collapsedTreeIds[tree.treeId]; - return isCollapsed - ? result - : result.concat(tree.comments.slice().sort(getCommentSorter(state))); - }, []); + if (this.props.skeletonTracing.activeNodeId != nextProps.skeletonTracing.activeNodeId) { + return true; + } + if (this.props.skeletonTracing.activeTreeId != nextProps.skeletonTracing.activeTreeId) { + return true; + } - return { data }; + const updateActions = Array.from( + cachedDiffTrees(this.props.skeletonTracing, nextProps.skeletonTracing), + ); + + const relevantUpdateActions = updateActions.filter(ua => + RELEVANT_ACTIONS_FOR_COMMENTS.includes(ua.type), + ); + return relevantUpdateActions.length > 0; } componentDidMount() { @@ -302,9 +333,13 @@ class CommentTabView extends React.PureComponent { - if (this.state.data[index].treeId != null) { - const tree = this.state.data[index]; + if (this.getData()[index].treeId != null) { + const tree = this.getData()[index]; return ( ); } else { - const comment = this.state.data[index]; + const comment = this.getData()[index]; const isActive = comment.nodeId === this.props.skeletonTracing.activeNodeId; return ; } @@ -342,78 +377,95 @@ class CommentTabView extends React.PureComponent - {this.renderMarkdownModal()} - - this.props.setActiveNode(comment.nodeId)} - data={_.flatMap(this.props.skeletonTracing.trees, tree => tree.comments)} - searchKey="content" - targetId={commentListId} - > - - - - this.handleChangeInput(evt, true)} - onPressEnter={evt => evt.target.blur()} - placeholder="Add comment" - style={{ width: "50%" }} - /> - this.setMarkdownModalVisibility(true)} - disabled={activeNodeMaybe.isNothing} - type={isMultilineComment ? "primary" : "button"} - icon="edit" - title="Open dialog to edit comment in multi-line mode" - /> - - - - {this.renderSortIcon()} - - - - -
- - {({ height, width }) => ( -
- -1 ? scrollIndex : undefined} - tabIndex={null} - ref={listEl => { - this.listRef = listEl; - }} - /> -
- )} -
-
+
+ + {isVisibleInDom => { + if (!isVisibleInDom) { + return null; + } + return ( + + {this.renderMarkdownModal()} + + this.props.setActiveNode(comment.nodeId)} + data={_.flatMap(this.props.skeletonTracing.trees, tree => tree.comments)} + searchKey="content" + targetId={commentListId} + > + + + + this.handleChangeInput(evt, true)} + onPressEnter={evt => evt.target.blur()} + placeholder="Add comment" + style={{ width: "50%" }} + /> + this.setMarkdownModalVisibility(true)} + disabled={activeNodeMaybe.isNothing} + type={isMultilineComment ? "primary" : "button"} + icon="edit" + title="Open dialog to edit comment in multi-line mode" + /> + + + + {this.renderSortIcon()} + + + + +
+ + {({ height, width }) => ( +
+ -1 ? scrollIndex : undefined} + tabIndex={null} + ref={listEl => { + this.listRef = listEl; + }} + /> +
+ )} +
+
+
+ ); + }} +
); }