2020-08-06 23:25:12 +03:00
|
|
|
|
import React, { cloneElement, Children, Component } from 'react';
|
|
|
|
|
import PropTypes from 'prop-types';
|
|
|
|
|
import Dock from 'react-dock';
|
2020-08-26 16:22:53 +03:00
|
|
|
|
import { Action, Dispatch } from 'redux';
|
2020-12-21 17:08:08 +03:00
|
|
|
|
import { LiftedState, Monitor } from '@redux-devtools/core';
|
2020-08-06 23:25:12 +03:00
|
|
|
|
import { POSITIONS } from './constants';
|
|
|
|
|
import {
|
|
|
|
|
toggleVisibility,
|
|
|
|
|
changeMonitor,
|
|
|
|
|
changePosition,
|
2020-08-08 23:26:39 +03:00
|
|
|
|
changeSize,
|
2020-08-26 16:22:53 +03:00
|
|
|
|
DockMonitorAction,
|
2020-08-06 23:25:12 +03:00
|
|
|
|
} from './actions';
|
2020-08-26 16:22:53 +03:00
|
|
|
|
import reducer, { DockMonitorState } from './reducers';
|
2020-08-06 23:25:12 +03:00
|
|
|
|
import parseKey from 'parse-key';
|
|
|
|
|
|
2020-08-26 16:22:53 +03:00
|
|
|
|
interface KeyObject {
|
|
|
|
|
name: string;
|
|
|
|
|
ctrl: boolean;
|
|
|
|
|
meta: boolean;
|
|
|
|
|
shift: boolean;
|
|
|
|
|
alt: boolean;
|
|
|
|
|
sequence: string;
|
|
|
|
|
}
|
|
|
|
|
|
2020-08-28 01:44:02 +03:00
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2020-08-26 16:22:53 +03:00
|
|
|
|
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>>[];
|
|
|
|
|
}
|
|
|
|
|
|
2020-08-28 01:44:02 +03:00
|
|
|
|
class DockMonitor<S, A extends Action<unknown>> extends Component<
|
|
|
|
|
DockMonitorProps<S, A>
|
|
|
|
|
> {
|
2020-08-06 23:25:12 +03:00
|
|
|
|
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,
|
2020-08-08 23:26:39 +03:00
|
|
|
|
childMonitorState: PropTypes.any,
|
|
|
|
|
}),
|
2020-08-06 23:25:12 +03:00
|
|
|
|
};
|
|
|
|
|
|
2020-08-28 01:44:02 +03:00
|
|
|
|
static defaultProps: DefaultProps = {
|
2020-08-06 23:25:12 +03:00
|
|
|
|
defaultIsVisible: true,
|
|
|
|
|
defaultPosition: 'right',
|
|
|
|
|
defaultSize: 0.3,
|
2020-08-08 23:26:39 +03:00
|
|
|
|
fluid: true,
|
2020-08-06 23:25:12 +03:00
|
|
|
|
};
|
|
|
|
|
|
2020-08-26 16:22:53 +03:00
|
|
|
|
constructor(props: DockMonitorProps<S, A>) {
|
2020-08-06 23:25:12 +03:00
|
|
|
|
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/gaearon/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);
|
|
|
|
|
}
|
|
|
|
|
|
2020-08-26 16:22:53 +03:00
|
|
|
|
matchesKey(key: KeyObject | undefined, event: KeyboardEvent) {
|
2020-08-06 23:25:12 +03:00
|
|
|
|
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
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2020-08-26 16:22:53 +03:00
|
|
|
|
handleKeyDown = (e: KeyboardEvent) => {
|
2020-08-06 23:25:12 +03:00
|
|
|
|
// Ignore regular keys when focused on a field
|
|
|
|
|
// and no modifiers are active.
|
|
|
|
|
if (
|
|
|
|
|
!e.ctrlKey &&
|
|
|
|
|
!e.metaKey &&
|
|
|
|
|
!e.altKey &&
|
2020-08-26 16:22:53 +03:00
|
|
|
|
((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)
|
2020-08-06 23:25:12 +03:00
|
|
|
|
) {
|
|
|
|
|
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());
|
|
|
|
|
}
|
2020-08-26 16:22:53 +03:00
|
|
|
|
};
|
2020-08-06 23:25:12 +03:00
|
|
|
|
|
2020-08-26 16:22:53 +03:00
|
|
|
|
handleSizeChange = (requestedSize: number) => {
|
2020-08-06 23:25:12 +03:00
|
|
|
|
this.props.dispatch(changeSize(requestedSize));
|
2020-08-26 16:22:53 +03:00
|
|
|
|
};
|
2020-08-06 23:25:12 +03:00
|
|
|
|
|
2020-08-26 16:22:53 +03:00
|
|
|
|
renderChild(
|
|
|
|
|
child: Monitor<S, A, LiftedState<S, A, unknown>, unknown, Action<unknown>>,
|
|
|
|
|
index: number,
|
|
|
|
|
otherProps: Omit<
|
|
|
|
|
DockMonitorProps<S, A>,
|
|
|
|
|
'monitorState' | 'children' | 'fluid'
|
|
|
|
|
>
|
|
|
|
|
) {
|
2020-08-06 23:25:12 +03:00
|
|
|
|
const { monitorState } = this.props;
|
|
|
|
|
const { childMonitorIndex, childMonitorStates } = monitorState;
|
|
|
|
|
|
|
|
|
|
if (index !== childMonitorIndex) {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return cloneElement(child, {
|
|
|
|
|
monitorState: childMonitorStates[index],
|
2020-08-08 23:26:39 +03:00
|
|
|
|
...otherProps,
|
2020-08-06 23:25:12 +03:00
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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"
|
|
|
|
|
>
|
2020-08-26 16:22:53 +03:00
|
|
|
|
{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)
|
2020-08-06 23:25:12 +03:00
|
|
|
|
)}
|
|
|
|
|
</Dock>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
2020-08-28 01:44:02 +03:00
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
};
|