mirror of
https://github.com/reduxjs/redux-devtools.git
synced 2025-04-28 04:03:42 +03:00
248 lines
6.9 KiB
TypeScript
248 lines
6.9 KiB
TypeScript
import React, { cloneElement, Children, Component } from 'react';
|
||
import PropTypes from 'prop-types';
|
||
import Dock from 'react-dock';
|
||
import { Action, Dispatch } from 'redux';
|
||
import { LiftedState, Monitor } from '@redux-devtools/core';
|
||
import { POSITIONS } from './constants';
|
||
import {
|
||
toggleVisibility,
|
||
changeMonitor,
|
||
changePosition,
|
||
changeSize,
|
||
DockMonitorAction,
|
||
} from './actions';
|
||
import reducer, { DockMonitorState } from './reducers';
|
||
import parseKey from 'parse-key';
|
||
|
||
interface KeyObject {
|
||
name: string;
|
||
ctrl: boolean;
|
||
meta: boolean;
|
||
shift: boolean;
|
||
alt: boolean;
|
||
sequence: string;
|
||
}
|
||
|
||
interface ExternalProps<S, A extends Action<unknown>> {
|
||
defaultPosition: 'left' | 'top' | 'right' | 'bottom';
|
||
defaultIsVisible: boolean;
|
||
defaultSize: number;
|
||
toggleVisibilityKey: string;
|
||
changePositionKey: string;
|
||
changeMonitorKey?: string;
|
||
fluid: boolean;
|
||
|
||
dispatch: Dispatch<DockMonitorAction>;
|
||
|
||
children:
|
||
| Monitor<S, A, LiftedState<S, A, unknown>, unknown, Action<unknown>>
|
||
| Monitor<S, A, LiftedState<S, A, unknown>, unknown, Action<unknown>>[];
|
||
}
|
||
|
||
interface DefaultProps {
|
||
defaultIsVisible: boolean;
|
||
defaultPosition: 'left' | 'top' | 'right' | 'bottom';
|
||
defaultSize: number;
|
||
fluid: boolean;
|
||
}
|
||
|
||
export interface DockMonitorProps<S, A extends Action<unknown>>
|
||
extends LiftedState<S, A, DockMonitorState> {
|
||
defaultPosition: 'left' | 'top' | 'right' | 'bottom';
|
||
defaultIsVisible: boolean;
|
||
defaultSize: number;
|
||
toggleVisibilityKey: string;
|
||
changePositionKey: string;
|
||
changeMonitorKey?: string;
|
||
fluid: boolean;
|
||
|
||
dispatch: Dispatch<DockMonitorAction>;
|
||
|
||
children:
|
||
| Monitor<S, A, LiftedState<S, A, unknown>, unknown, Action<unknown>>
|
||
| Monitor<S, A, LiftedState<S, A, unknown>, unknown, Action<unknown>>[];
|
||
}
|
||
|
||
class DockMonitor<S, A extends Action<unknown>> extends Component<
|
||
DockMonitorProps<S, A>
|
||
> {
|
||
static update = reducer;
|
||
|
||
static propTypes = {
|
||
defaultPosition: PropTypes.oneOf(POSITIONS),
|
||
defaultIsVisible: PropTypes.bool.isRequired,
|
||
defaultSize: PropTypes.number.isRequired,
|
||
toggleVisibilityKey: PropTypes.string.isRequired,
|
||
changePositionKey: PropTypes.string.isRequired,
|
||
changeMonitorKey: PropTypes.string,
|
||
fluid: PropTypes.bool,
|
||
|
||
dispatch: PropTypes.func,
|
||
monitorState: PropTypes.shape({
|
||
position: PropTypes.oneOf(POSITIONS).isRequired,
|
||
size: PropTypes.number.isRequired,
|
||
isVisible: PropTypes.bool.isRequired,
|
||
childMonitorState: PropTypes.any,
|
||
}),
|
||
};
|
||
|
||
static defaultProps: DefaultProps = {
|
||
defaultIsVisible: true,
|
||
defaultPosition: 'right',
|
||
defaultSize: 0.3,
|
||
fluid: true,
|
||
};
|
||
|
||
constructor(props: DockMonitorProps<S, A>) {
|
||
super(props);
|
||
|
||
const childrenCount = Children.count(props.children);
|
||
if (childrenCount === 0) {
|
||
// eslint-disable-next-line no-console
|
||
console.error(
|
||
'<DockMonitor> requires at least one monitor inside. ' +
|
||
'Why don’t you try <LogMonitor>? You can get it at ' +
|
||
'https://github.com/reduxjs/redux-devtools/tree/master/packages/redux-devtools-log-monitor.'
|
||
);
|
||
} else if (childrenCount > 1 && !props.changeMonitorKey) {
|
||
// eslint-disable-next-line no-console
|
||
console.error(
|
||
'You specified multiple monitors inside <DockMonitor> ' +
|
||
'but did not provide `changeMonitorKey` prop to change them. ' +
|
||
'Try specifying <DockMonitor changeMonitorKey="ctrl-m" /> ' +
|
||
'and then press Ctrl-M.'
|
||
);
|
||
}
|
||
}
|
||
|
||
componentDidMount() {
|
||
window.addEventListener('keydown', this.handleKeyDown);
|
||
}
|
||
|
||
componentWillUnmount() {
|
||
window.removeEventListener('keydown', this.handleKeyDown);
|
||
}
|
||
|
||
matchesKey(key: KeyObject | undefined, event: KeyboardEvent) {
|
||
if (!key) {
|
||
return false;
|
||
}
|
||
|
||
const charCode = event.keyCode || event.which;
|
||
const char = String.fromCharCode(charCode);
|
||
return (
|
||
key.name.toUpperCase() === char.toUpperCase() &&
|
||
key.alt === event.altKey &&
|
||
key.ctrl === event.ctrlKey &&
|
||
key.meta === event.metaKey &&
|
||
key.shift === event.shiftKey
|
||
);
|
||
}
|
||
|
||
handleKeyDown = (e: KeyboardEvent) => {
|
||
// Ignore regular keys when focused on a field
|
||
// and no modifiers are active.
|
||
if (
|
||
!e.ctrlKey &&
|
||
!e.metaKey &&
|
||
!e.altKey &&
|
||
((e.target! as { tagName?: string }).tagName === 'INPUT' ||
|
||
(e.target! as { tagName?: string }).tagName === 'SELECT' ||
|
||
(e.target! as { tagName?: string }).tagName === 'TEXTAREA' ||
|
||
(e.target! as { isContentEditable?: boolean }).isContentEditable)
|
||
) {
|
||
return;
|
||
}
|
||
|
||
const visibilityKey = parseKey(this.props.toggleVisibilityKey);
|
||
const positionKey = parseKey(this.props.changePositionKey);
|
||
|
||
let monitorKey;
|
||
if (this.props.changeMonitorKey) {
|
||
monitorKey = parseKey(this.props.changeMonitorKey);
|
||
}
|
||
|
||
if (this.matchesKey(visibilityKey, e)) {
|
||
e.preventDefault();
|
||
this.props.dispatch(toggleVisibility());
|
||
} else if (this.matchesKey(positionKey, e)) {
|
||
e.preventDefault();
|
||
this.props.dispatch(changePosition());
|
||
} else if (this.matchesKey(monitorKey, e)) {
|
||
e.preventDefault();
|
||
this.props.dispatch(changeMonitor());
|
||
}
|
||
};
|
||
|
||
handleSizeChange = (requestedSize: number) => {
|
||
this.props.dispatch(changeSize(requestedSize));
|
||
};
|
||
|
||
renderChild(
|
||
child: Monitor<S, A, LiftedState<S, A, unknown>, unknown, Action<unknown>>,
|
||
index: number,
|
||
otherProps: Omit<
|
||
DockMonitorProps<S, A>,
|
||
'monitorState' | 'children' | 'fluid'
|
||
>
|
||
) {
|
||
const { monitorState } = this.props;
|
||
const { childMonitorIndex, childMonitorStates } = monitorState;
|
||
|
||
if (index !== childMonitorIndex) {
|
||
return null;
|
||
}
|
||
|
||
return cloneElement(child, {
|
||
monitorState: childMonitorStates[index],
|
||
...otherProps,
|
||
});
|
||
}
|
||
|
||
render() {
|
||
const { monitorState, children, fluid, ...rest } = this.props;
|
||
const { position, isVisible, size } = monitorState;
|
||
|
||
return (
|
||
<Dock
|
||
position={position}
|
||
isVisible={isVisible}
|
||
size={size}
|
||
fluid={fluid}
|
||
onSizeChange={this.handleSizeChange}
|
||
dimMode="none"
|
||
>
|
||
{Children.map(
|
||
children as
|
||
| Monitor<
|
||
S,
|
||
A,
|
||
LiftedState<S, A, unknown>,
|
||
unknown,
|
||
Action<unknown>
|
||
>
|
||
| Monitor<
|
||
S,
|
||
A,
|
||
LiftedState<S, A, unknown>,
|
||
unknown,
|
||
Action<unknown>
|
||
>[],
|
||
(child, index) => this.renderChild(child, index, rest)
|
||
)}
|
||
</Dock>
|
||
);
|
||
}
|
||
}
|
||
|
||
export default DockMonitor as unknown as React.ComponentType<
|
||
ExternalProps<unknown, Action<unknown>>
|
||
> & {
|
||
update(
|
||
monitorProps: ExternalProps<unknown, Action<unknown>>,
|
||
state: DockMonitorState | undefined,
|
||
action: DockMonitorAction
|
||
): DockMonitorState;
|
||
defaultProps: DefaultProps;
|
||
};
|