redux-devtools/packages/redux-devtools-inspector-monitor/src/ActionList.tsx

196 lines
5.8 KiB
TypeScript
Raw Normal View History

import React, { PureComponent, RefCallback } from 'react';
import { Drake } from 'dragula';
import dragula from 'react-dragula';
import { Action } from 'redux';
import { PerformAction } from '@redux-devtools/core';
import { StylingFunction } from 'react-base16-styling';
import ActionListRow from './ActionListRow';
import ActionListHeader from './ActionListHeader';
function getTimestamps<A extends Action<unknown>>(
actions: { [actionId: number]: PerformAction<A> },
actionIds: number[],
actionId: number
) {
const idx = actionIds.indexOf(actionId);
const prevActionId = actionIds[idx - 1];
return {
current: actions[actionId].timestamp,
previous: idx ? actions[prevActionId].timestamp : 0,
};
}
interface Props<A extends Action<unknown>> {
actions: { [actionId: number]: PerformAction<A> };
actionIds: number[];
isWideLayout: boolean;
searchValue: string | undefined;
selectedActionId: number | null;
startActionId: number | null;
skippedActionIds: number[];
draggableActions: boolean;
hideMainButtons: boolean | undefined;
hideActionButtons: boolean | undefined;
styling: StylingFunction;
onSearch: (value: string) => void;
onSelect: (e: React.MouseEvent<HTMLDivElement>, actionId: number) => void;
onToggleAction: (actionId: number) => void;
onJumpToState: (actionId: number) => void;
onCommit: () => void;
onSweep: () => void;
onReorderAction: (actionId: number, beforeActionId: number) => void;
currentActionId: number;
lastActionId: number;
}
export default class ActionList<
A extends Action<unknown>
> extends PureComponent<Props<A>> {
node?: HTMLDivElement | null;
scrollDown?: boolean;
drake?: Drake;
UNSAFE_componentWillReceiveProps(nextProps: Props<A>) {
const node = this.node;
if (!node) {
this.scrollDown = true;
} else if (this.props.lastActionId !== nextProps.lastActionId) {
const { scrollTop, offsetHeight, scrollHeight } = node;
2019-01-10 21:51:14 +03:00
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,
2019-01-10 21:51:14 +03:00
accepts: (el, target, source, sibling) =>
!sibling || !!parseInt(sibling.getAttribute('data-id')!),
2019-01-10 21:51:14 +03:00
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')!);
2019-01-10 21:51:14 +03:00
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<HTMLDivElement> = (node) => {
this.node = node;
2019-01-10 21:51:14 +03:00
};
render() {
2019-01-10 21:51:14 +03:00
const {
styling,
actions,
actionIds,
isWideLayout,
onToggleAction,
skippedActionIds,
selectedActionId,
startActionId,
onSelect,
onSearch,
searchValue,
currentActionId,
hideMainButtons,
hideActionButtons,
onCommit,
onSweep,
onJumpToState,
2019-01-10 21:51:14 +03:00
} = this.props;
const lowerSearchValue = searchValue && searchValue.toLowerCase();
2019-01-10 21:51:14 +03:00
const filteredActionIds = searchValue
? actionIds.filter(
(id) =>
(actions[id].action.type as string)
.toLowerCase()
.indexOf(lowerSearchValue as string) !== -1
2019-01-10 21:51:14 +03:00
)
: actionIds;
return (
2019-01-10 21:51:14 +03:00
<div
key="actionList"
data-testid="actionList"
2019-01-10 21:51:14 +03:00
{...styling(
['actionList', isWideLayout && 'actionListWide'],
isWideLayout
)}
>
<ActionListHeader
styling={styling}
onSearch={onSearch}
onCommit={onCommit}
onSweep={onSweep}
hideMainButtons={hideMainButtons}
hasSkippedActions={skippedActionIds.length > 0}
2019-01-10 21:51:14 +03:00
hasStagedActions={actionIds.length > 1}
searchValue={searchValue}
2019-01-10 21:51:14 +03:00
/>
<div
data-testid="actionListRows"
{...styling('actionListRows')}
ref={this.getRef}
>
{filteredActionIds.map((actionId) => (
2019-01-10 21:51:14 +03:00
<ActionListRow
key={actionId}
styling={styling}
actionId={actionId}
isInitAction={!actionId}
isSelected={
2019-01-10 21:51:14 +03:00
(startActionId !== null &&
actionId >= startActionId &&
actionId <= (selectedActionId as number)) ||
2019-01-10 21:51:14 +03:00
actionId === selectedActionId
}
isInFuture={
actionIds.indexOf(actionId) > actionIds.indexOf(currentActionId)
}
onSelect={(e: React.MouseEvent<HTMLDivElement>) =>
onSelect(e, actionId)
}
timestamps={getTimestamps(actions, actionIds, actionId)}
action={actions[actionId].action}
onToggleClick={() => onToggleAction(actionId)}
onJumpClick={() => onJumpToState(actionId)}
onCommitClick={() => onCommit()}
hideActionButtons={hideActionButtons}
2019-01-10 21:51:14 +03:00
isSkipped={skippedActionIds.indexOf(actionId) !== -1}
/>
))}
</div>
</div>
);
}
}