This commit is contained in:
Nathan Bierema 2020-08-26 00:03:56 -04:00
parent 5f64276609
commit f7107df278
10 changed files with 169 additions and 43 deletions

View File

@ -1,4 +1,4 @@
import React, { Component } from 'react'; import React, { Component, ReactNode } from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import debounce from 'lodash.debounce'; import debounce from 'lodash.debounce';
import autoprefix from './autoprefix'; import autoprefix from './autoprefix';
@ -227,12 +227,14 @@ interface Props {
dockStyle?: React.CSSProperties | null; dockStyle?: React.CSSProperties | null;
dockHiddenStyle?: React.CSSProperties | null; dockHiddenStyle?: React.CSSProperties | null;
duration: number; duration: number;
children?: React.FunctionComponent<{ children?:
| React.FunctionComponent<{
position: 'left' | 'right' | 'top' | 'bottom'; position: 'left' | 'right' | 'top' | 'bottom';
isResizing: boolean | undefined; isResizing: boolean | undefined;
size: number; size: number;
isVisible: boolean | undefined; isVisible: boolean | undefined;
}>; }>
| ReactNode;
} }
interface State { interface State {
@ -376,7 +378,12 @@ export default class Dock extends Component<Props, State> {
/> />
<div style={styles.dockContent}> <div style={styles.dockContent}>
{typeof children === 'function' {typeof children === 'function'
? children({ ? (children as React.FunctionComponent<{
position: 'left' | 'right' | 'top' | 'bottom';
isResizing: boolean | undefined;
size: number;
isVisible: boolean | undefined;
}>)({
position, position,
isResizing, isResizing,
size, size,

View File

@ -4,7 +4,5 @@
"@babel/preset-react", "@babel/preset-react",
"@babel/preset-typescript" "@babel/preset-typescript"
], ],
"plugins": [ "plugins": ["@babel/plugin-proposal-class-properties"]
"@babel/plugin-proposal-class-properties"
]
} }

View File

@ -45,9 +45,16 @@
"prop-types": "^15.7.2", "prop-types": "^15.7.2",
"react-dock": "^0.2.4" "react-dock": "^0.2.4"
}, },
"devDependencies": {
"@types/react": "^16.9.46",
"react": "^16.13.1",
"redux": "^4.0.5",
"redux-devtools": "^3.6.1"
},
"peerDependencies": { "peerDependencies": {
"@types/react": "^16.3.18", "@types/react": "^16.3.18",
"react": "^16.3.0", "react": "^16.3.0",
"redux": "^3.4.0 || ^4.0.0",
"redux-devtools": "^3.4.0" "redux-devtools": "^3.4.0"
} }
} }

View File

@ -1,17 +1,55 @@
import React, { cloneElement, Children, Component } from 'react'; import React, {
cloneElement,
Children,
Component,
ReactNode,
ReactElement,
} from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import Dock from 'react-dock'; import Dock from 'react-dock';
import { Action, Dispatch } from 'redux';
import { LiftedState, Monitor } from 'redux-devtools';
import { POSITIONS } from './constants'; import { POSITIONS } from './constants';
import { import {
toggleVisibility, toggleVisibility,
changeMonitor, changeMonitor,
changePosition, changePosition,
changeSize, changeSize,
DockMonitorAction,
} from './actions'; } from './actions';
import reducer from './reducers'; import reducer, { DockMonitorState } from './reducers';
import parseKey from 'parse-key'; import parseKey from 'parse-key';
export default class DockMonitor extends Component { interface KeyObject {
name: string;
ctrl: boolean;
meta: boolean;
shift: boolean;
alt: boolean;
sequence: string;
}
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, unknown, unknown, Action<unknown>>
| Monitor<S, A, unknown, unknown, Action<unknown>>[];
}
export default class DockMonitor<
S,
A extends Action<unknown>
> extends Component<DockMonitorProps<S, A>> {
static update = reducer; static update = reducer;
static propTypes = { static propTypes = {
@ -39,10 +77,8 @@ export default class DockMonitor extends Component {
fluid: true, fluid: true,
}; };
constructor(props) { constructor(props: DockMonitorProps<S, A>) {
super(props); super(props);
this.handleKeyDown = this.handleKeyDown.bind(this);
this.handleSizeChange = this.handleSizeChange.bind(this);
const childrenCount = Children.count(props.children); const childrenCount = Children.count(props.children);
if (childrenCount === 0) { if (childrenCount === 0) {
@ -71,7 +107,7 @@ export default class DockMonitor extends Component {
window.removeEventListener('keydown', this.handleKeyDown); window.removeEventListener('keydown', this.handleKeyDown);
} }
matchesKey(key, event) { matchesKey(key: KeyObject | undefined, event: KeyboardEvent) {
if (!key) { if (!key) {
return false; return false;
} }
@ -87,17 +123,17 @@ export default class DockMonitor extends Component {
); );
} }
handleKeyDown(e) { handleKeyDown = (e: KeyboardEvent) => {
// Ignore regular keys when focused on a field // Ignore regular keys when focused on a field
// and no modifiers are active. // and no modifiers are active.
if ( if (
!e.ctrlKey && !e.ctrlKey &&
!e.metaKey && !e.metaKey &&
!e.altKey && !e.altKey &&
(e.target.tagName === 'INPUT' || ((e.target! as { tagName?: string }).tagName === 'INPUT' ||
e.target.tagName === 'SELECT' || (e.target! as { tagName?: string }).tagName === 'SELECT' ||
e.target.tagName === 'TEXTAREA' || (e.target! as { tagName?: string }).tagName === 'TEXTAREA' ||
e.target.isContentEditable) (e.target! as { isContentEditable?: boolean }).isContentEditable)
) { ) {
return; return;
} }
@ -120,13 +156,20 @@ export default class DockMonitor extends Component {
e.preventDefault(); e.preventDefault();
this.props.dispatch(changeMonitor()); this.props.dispatch(changeMonitor());
} }
} };
handleSizeChange(requestedSize) { handleSizeChange = (requestedSize: number) => {
this.props.dispatch(changeSize(requestedSize)); this.props.dispatch(changeSize(requestedSize));
} };
renderChild(child, index, otherProps) { renderChild(
child: ReactElement,
index: number,
otherProps: Omit<
DockMonitorProps<S, A>,
'monitorState' | 'children' | 'fluid'
>
) {
const { monitorState } = this.props; const { monitorState } = this.props;
const { childMonitorIndex, childMonitorStates } = monitorState; const { childMonitorIndex, childMonitorStates } = monitorState;
@ -153,7 +196,7 @@ export default class DockMonitor extends Component {
onSizeChange={this.handleSizeChange} onSizeChange={this.handleSizeChange}
dimMode="none" dimMode="none"
> >
{Children.map(children, (child, index) => {Children.map<ReactNode, ReactElement>(children, (child, index) =>
this.renderChild(child, index, rest) this.renderChild(child, index, rest)
)} )}
</Dock> </Dock>

View File

@ -1,20 +1,39 @@
export const TOGGLE_VISIBILITY = export const TOGGLE_VISIBILITY =
'@@redux-devtools-log-monitor/TOGGLE_VISIBILITY'; '@@redux-devtools-log-monitor/TOGGLE_VISIBILITY';
export function toggleVisibility() { interface ToggleVisibilityAction {
type: typeof TOGGLE_VISIBILITY;
}
export function toggleVisibility(): ToggleVisibilityAction {
return { type: TOGGLE_VISIBILITY }; return { type: TOGGLE_VISIBILITY };
} }
export const CHANGE_POSITION = '@@redux-devtools-log-monitor/CHANGE_POSITION'; export const CHANGE_POSITION = '@@redux-devtools-log-monitor/CHANGE_POSITION';
export function changePosition() { interface ChangePositionAction {
type: typeof CHANGE_POSITION;
}
export function changePosition(): ChangePositionAction {
return { type: CHANGE_POSITION }; return { type: CHANGE_POSITION };
} }
export const CHANGE_SIZE = '@@redux-devtools-log-monitor/CHANGE_SIZE'; export const CHANGE_SIZE = '@@redux-devtools-log-monitor/CHANGE_SIZE';
export function changeSize(size) { interface ChangeSizeAction {
type: typeof CHANGE_SIZE;
size: number;
}
export function changeSize(size: number): ChangeSizeAction {
return { type: CHANGE_SIZE, size: size }; return { type: CHANGE_SIZE, size: size };
} }
export const CHANGE_MONITOR = '@@redux-devtools-log-monitor/CHANGE_MONITOR'; export const CHANGE_MONITOR = '@@redux-devtools-log-monitor/CHANGE_MONITOR';
export function changeMonitor() { interface ChangeMonitorAction {
type: typeof CHANGE_MONITOR;
}
export function changeMonitor(): ChangeMonitorAction {
return { type: CHANGE_MONITOR }; return { type: CHANGE_MONITOR };
} }
export type DockMonitorAction =
| ToggleVisibilityAction
| ChangePositionAction
| ChangeSizeAction
| ChangeMonitorAction;

View File

@ -1 +1 @@
export const POSITIONS = ['left', 'top', 'right', 'bottom']; export const POSITIONS = ['left', 'top', 'right', 'bottom'] as const;

View File

@ -0,0 +1,12 @@
declare module 'parse-key' {
interface KeyObject {
name: string;
ctrl: boolean;
meta: boolean;
shift: boolean;
alt: boolean;
sequence: string;
}
export default function parse(s: string): KeyObject;
}

View File

@ -1,33 +1,64 @@
import { Children } from 'react';
import { Action } from 'redux';
import { import {
CHANGE_MONITOR, CHANGE_MONITOR,
CHANGE_POSITION, CHANGE_POSITION,
CHANGE_SIZE, CHANGE_SIZE,
DockMonitorAction,
TOGGLE_VISIBILITY, TOGGLE_VISIBILITY,
} from './actions'; } from './actions';
import { POSITIONS } from './constants'; import { POSITIONS } from './constants';
import { Children } from 'react'; import { DockMonitorProps } from './DockMonitor';
function position(props, state = props.defaultPosition, action) { export interface DockMonitorState {
position: 'left' | 'top' | 'right' | 'bottom';
size: number;
isVisible: boolean;
childMonitorStates: unknown[];
childMonitorIndex: number;
}
function position<S, A extends Action<unknown>>(
props: DockMonitorProps<S, A>,
state = props.defaultPosition,
action: DockMonitorAction
) {
return action.type === CHANGE_POSITION return action.type === CHANGE_POSITION
? POSITIONS[(POSITIONS.indexOf(state) + 1) % POSITIONS.length] ? POSITIONS[(POSITIONS.indexOf(state) + 1) % POSITIONS.length]
: state; : state;
} }
function size(props, state = props.defaultSize, action) { function size<S, A extends Action<unknown>>(
props: DockMonitorProps<S, A>,
state = props.defaultSize,
action: DockMonitorAction
) {
return action.type === CHANGE_SIZE ? action.size : state; return action.type === CHANGE_SIZE ? action.size : state;
} }
function isVisible(props, state = props.defaultIsVisible, action) { function isVisible<S, A extends Action<unknown>>(
props: DockMonitorProps<S, A>,
state = props.defaultIsVisible,
action: DockMonitorAction
) {
return action.type === TOGGLE_VISIBILITY ? !state : state; return action.type === TOGGLE_VISIBILITY ? !state : state;
} }
function childMonitorStates(props, state = [], action) { function childMonitorStates<S, A extends Action<unknown>>(
props: DockMonitorProps<S, A>,
state: unknown[] = [],
action: DockMonitorAction
) {
return Children.map(props.children, (child, index) => return Children.map(props.children, (child, index) =>
child.type.update(child.props, state[index], action) child.type.update(child.props, state[index], action)
); );
} }
function childMonitorIndex(props, state = 0, action) { function childMonitorIndex<S, A extends Action<unknown>>(
props: DockMonitorProps<S, A>,
state = 0,
action: DockMonitorAction
) {
switch (action.type) { switch (action.type) {
case CHANGE_MONITOR: case CHANGE_MONITOR:
return (state + 1) % Children.count(props.children); return (state + 1) % Children.count(props.children);
@ -36,14 +67,22 @@ function childMonitorIndex(props, state = 0, action) {
} }
} }
export default function reducer(props, state = {}, action) { export default function reducer<S, A extends Action<unknown>>(
props: DockMonitorProps<S, A>,
state: Partial<DockMonitorState> = {},
action: DockMonitorAction
): DockMonitorState {
if (!state.childMonitorStates) { if (!state.childMonitorStates) {
Children.forEach(props.children, (child, index) => { Children.forEach(props.children, (child, index) => {
if (typeof child.type.update !== 'function') { if (typeof child.type.update !== 'function') {
// eslint-disable-next-line no-console // eslint-disable-next-line no-console
console.error( console.error(
`Child of <DockMonitor> with the index ${index} ` + `Child of <DockMonitor> with the index ${index} ` +
`(${child.type.displayName || child.type.name || child.type}) ` + `(${
child.type.displayName ||
child.type.name ||
((child.type as unknown) as string)
}) ` +
'does not appear to be a valid Redux DevTools monitor.' 'does not appear to be a valid Redux DevTools monitor.'
); );
} }

View File

@ -34,7 +34,7 @@ interface Props<
store?: EnhancedStore<S, A, MonitorState>; store?: EnhancedStore<S, A, MonitorState>;
} }
type Monitor< export type Monitor<
S, S,
A extends Action<unknown>, A extends Action<unknown>,
MonitorProps, MonitorProps,

View File

@ -2,6 +2,7 @@ export {
default as instrument, default as instrument,
ActionCreators, ActionCreators,
ActionTypes, ActionTypes,
LiftedState,
} from 'redux-devtools-instrument'; } from 'redux-devtools-instrument';
export { default as persistState } from './persistState'; export { default as persistState } from './persistState';
export { default as createDevTools } from './createDevTools'; export { default as createDevTools, Monitor } from './createDevTools';