diff --git a/packages/redux-devtools-inspector-monitor/src/ActionList.tsx b/packages/redux-devtools-inspector-monitor/src/ActionList.tsx index 4b312e8e..df6f0ef4 100644 --- a/packages/redux-devtools-inspector-monitor/src/ActionList.tsx +++ b/packages/redux-devtools-inspector-monitor/src/ActionList.tsx @@ -1,6 +1,4 @@ -import React, { PureComponent, RefCallback } from 'react'; -import { Drake } from 'dragula'; -import dragula from 'react-dragula'; +import React, { useCallback, useLayoutEffect, useRef } from 'react'; import { Action } from 'redux'; import { PerformAction } from '@redux-devtools/core'; import { StylingFunction } from 'react-base16-styling'; @@ -44,152 +42,114 @@ interface Props> { lastActionId: number; } -export default class ActionList< - A extends Action, -> extends PureComponent> { - node?: HTMLDivElement | null; - scrollDown?: boolean; - drake?: Drake; - - UNSAFE_componentWillReceiveProps(nextProps: Props) { - const node = this.node; - if (!node) { - this.scrollDown = true; - } else if (this.props.lastActionId !== nextProps.lastActionId) { - const { scrollTop, offsetHeight, scrollHeight } = node; - this.scrollDown = - Math.abs(scrollHeight - (scrollTop + offsetHeight)) < 50; - } else { - this.scrollDown = false; - } - } - - componentDidMount() { - this.scrollDown = true; - this.scrollToBottom(); - - if (!this.props.draggableActions) return; - const container = this.node!; - this.drake = dragula([container], { - copy: false, - copySortSource: false, - mirrorContainer: container, - accepts: (el, target, source, sibling) => - !sibling || !!parseInt(sibling.getAttribute('data-id')!), - moves: (el, source, handle) => - !!parseInt(el!.getAttribute('data-id')!) && - handle!.className.indexOf('selectorButton') !== 0, - }).on('drop', (el, target, source, sibling) => { - let beforeActionId = this.props.actionIds.length; - if (sibling && sibling.className.indexOf('gu-mirror') === -1) { - beforeActionId = parseInt(sibling.getAttribute('data-id')!); - } - const actionId = parseInt(el.getAttribute('data-id')!); - this.props.onReorderAction(actionId, beforeActionId); - }); - } - - componentWillUnmount() { - if (this.drake) this.drake.destroy(); - } - - componentDidUpdate() { - this.scrollToBottom(); - } - - scrollToBottom() { - if (this.scrollDown && this.node) { - this.node.scrollTop = this.node.scrollHeight; - } - } - - getRef: RefCallback = (node) => { - this.node = node; - }; - - render() { - const { - styling, - actions, - actionIds, - isWideLayout, - onToggleAction, - skippedActionIds, - selectedActionId, - startActionId, - onSelect, - onSearch, - searchValue, - currentActionId, - hideMainButtons, - hideActionButtons, - onCommit, - onSweep, - onJumpToState, - } = this.props; - const lowerSearchValue = searchValue && searchValue.toLowerCase(); - const filteredActionIds = searchValue - ? actionIds.filter( - (id) => - (actions[id].action.type as string) - .toLowerCase() - .indexOf(lowerSearchValue as string) !== -1, - ) - : actionIds; - - return ( -
- 0} - hasStagedActions={actionIds.length > 1} - searchValue={searchValue} - /> -
- {filteredActionIds.map((actionId) => ( - = startActionId && - actionId <= (selectedActionId as number)) || - actionId === selectedActionId - } - isInFuture={ - actionIds.indexOf(actionId) > actionIds.indexOf(currentActionId) - } - onSelect={(e: React.MouseEvent) => - onSelect(e, actionId) - } - timestamps={getTimestamps(actions, actionIds, actionId)} - action={actions[actionId].action} - onToggleClick={() => onToggleAction(actionId)} - onJumpClick={() => onJumpToState(actionId)} - onCommitClick={() => onCommit()} - hideActionButtons={hideActionButtons} - isSkipped={skippedActionIds.indexOf(actionId) !== -1} - /> - ))} -
-
- ); - } +function scrollToBottom(node: HTMLDivElement) { + node.scrollTop = node.scrollHeight; +} + +export default function ActionListFunction
>({ + styling, + actions, + actionIds, + isWideLayout, + onToggleAction, + skippedActionIds, + selectedActionId, + startActionId, + onSelect, + onSearch, + searchValue, + currentActionId, + hideMainButtons, + hideActionButtons, + onCommit, + onSweep, + onJumpToState, + lastActionId, +}: Props) { + const nodeRef = useRef(null); + const prevLastActionId = useRef(); + + useLayoutEffect(() => { + if (nodeRef.current && prevLastActionId.current !== lastActionId) { + const { scrollTop, offsetHeight, scrollHeight } = nodeRef.current; + if (Math.abs(scrollHeight - (scrollTop + offsetHeight)) < 50) { + scrollToBottom(nodeRef.current); + } + } + + prevLastActionId.current = lastActionId; + }, [lastActionId]); + + const setNodeRef = useCallback((node: HTMLDivElement | null) => { + if (node && !nodeRef.current) { + scrollToBottom(node); + } + + nodeRef.current = node; + }, []); + + const lowerSearchValue = searchValue && searchValue.toLowerCase(); + const filteredActionIds = searchValue + ? actionIds.filter( + (id) => + (actions[id].action.type as string) + .toLowerCase() + .indexOf(lowerSearchValue as string) !== -1, + ) + : actionIds; + + return ( +
+ 0} + hasStagedActions={actionIds.length > 1} + searchValue={searchValue} + /> +
+ {filteredActionIds.map((actionId) => ( + = startActionId && + actionId <= (selectedActionId as number)) || + actionId === selectedActionId + } + isInFuture={ + actionIds.indexOf(actionId) > actionIds.indexOf(currentActionId) + } + onSelect={(e: React.MouseEvent) => + onSelect(e, actionId) + } + timestamps={getTimestamps(actions, actionIds, actionId)} + action={actions[actionId].action} + onToggleClick={() => onToggleAction(actionId)} + onJumpClick={() => onJumpToState(actionId)} + onCommitClick={() => onCommit()} + hideActionButtons={hideActionButtons} + isSkipped={skippedActionIds.indexOf(actionId) !== -1} + /> + ))} +
+
+ ); }