This commit is contained in:
Nathan Bierema 2020-10-23 11:15:48 -04:00
parent 2cbdf7b13e
commit 8486fe058f
18 changed files with 232 additions and 103 deletions

View File

@ -19,6 +19,7 @@ import {
UPDATE_STATE,
UPDATE_REPORTS,
REMOVE_INSTANCE,
SET_STATE,
} from '../constants/actionTypes';
import {
AUTH_ERROR,
@ -37,6 +38,9 @@ import {
UNSUBSCRIBE,
} from '../constants/socketActionTypes';
import { Action } from 'redux';
import { Features, State } from '../reducers/instances';
import { MonitorStateMonitorState } from '../reducers/monitor';
import { LiftedAction } from 'redux-devtools-instrument';
let monitorReducer: (
monitorProps: unknown,
@ -91,10 +95,33 @@ export interface MonitorActionAction {
) => unknown;
monitorProps: unknown;
}
interface LiftedActionDispatchAction {
interface JumpToStateAction {
type: 'JUMP_TO_STATE';
index: number;
actionId: number;
}
interface JumpToActionAction {
type: 'JUMP_TO_ACTION';
index: number;
actionId: number;
}
interface PauseRecordingAction {
type: 'PAUSE_RECORDING';
status: boolean;
}
interface LockChangesAction {
type: 'LOCK_CHANGES';
status: boolean;
}
export interface LiftedActionDispatchAction {
type: typeof LIFTED_ACTION;
message: 'DISPATCH';
action: Action<unknown>;
action:
| JumpToStateAction
| JumpToActionAction
| PauseRecordingAction
| LockChangesAction;
toAll?: boolean;
}
interface LiftedActionImportAction {
type: typeof LIFTED_ACTION;
@ -102,18 +129,39 @@ interface LiftedActionImportAction {
state: string;
preloadedState: unknown | undefined;
}
export type LiftedActionAction = LiftedActionDispatchAction;
interface LiftedActionActionAction {
type: typeof LIFTED_ACTION;
message: 'ACTION';
action: Action<unknown>;
}
export type LiftedActionAction =
| LiftedActionDispatchAction
| LiftedActionImportAction
| LiftedActionActionAction;
export function liftedDispatch(
action: InitMonitorAction
action:
| InitMonitorAction
| JumpToStateAction
| JumpToActionAction
| LiftedAction<unknown, Action<unknown>, unknown>
): MonitorActionAction | LiftedActionDispatchAction {
if (action.type[0] === '@') {
if (action.type === '@@INIT_MONITOR') {
monitorReducer = action.update;
monitorProps = action.monitorProps;
}
return { type: MONITOR_ACTION, action, monitorReducer, monitorProps };
return {
type: MONITOR_ACTION,
action,
monitorReducer,
monitorProps,
} as MonitorActionAction;
}
return { type: LIFTED_ACTION, message: 'DISPATCH', action };
return {
type: LIFTED_ACTION,
message: 'DISPATCH',
action,
} as LiftedActionDispatchAction;
}
interface SelectInstanceAction {
@ -127,20 +175,21 @@ export function selectInstance(selected: string): SelectInstanceAction {
interface SelectMonitorAction {
type: typeof SELECT_MONITOR;
monitor: string;
monitorState?: unknown;
monitorState?: MonitorStateMonitorState;
}
export function selectMonitor(monitor: string): SelectMonitorAction {
return { type: SELECT_MONITOR, monitor };
}
export function selectMonitorWithState(
value: string,
monitorState: unknown
monitorState: MonitorStateMonitorState
): SelectMonitorAction {
return { type: SELECT_MONITOR, monitor: value, monitorState };
}
interface NextState {
subTabName: string;
inspectedStatePath?: string[];
}
interface UpdateMonitorStateAction {
type: typeof UPDATE_MONITOR_STATE;
@ -150,7 +199,9 @@ export function selectMonitorTab(subTabName: string): UpdateMonitorStateAction {
return { type: UPDATE_MONITOR_STATE, nextState: { subTabName } };
}
export function updateMonitorState(nextState) {
export function updateMonitorState(
nextState: NextState
): UpdateMonitorStateAction {
return { type: UPDATE_MONITOR_STATE, nextState };
}
@ -168,7 +219,7 @@ export function exportState(): ExportAction {
return { type: EXPORT };
}
export function lockChanges(status) {
export function lockChanges(status: boolean): LiftedActionDispatchAction {
return {
type: LIFTED_ACTION,
message: 'DISPATCH',
@ -177,7 +228,7 @@ export function lockChanges(status) {
};
}
export function pauseRecording(status) {
export function pauseRecording(status: boolean): LiftedActionDispatchAction {
return {
type: LIFTED_ACTION,
message: 'DISPATCH',
@ -186,7 +237,9 @@ export function pauseRecording(status) {
};
}
export function dispatchRemotely(action) {
export function dispatchRemotely(
action: Action<unknown>
): LiftedActionActionAction {
return { type: LIFTED_ACTION, message: 'ACTION', action };
}
@ -254,12 +307,67 @@ export function clearNotification(): ClearNotificationAction {
return { type: CLEAR_NOTIFICATION };
}
export function getReport(report) {
interface GetReportRequest {
readonly type: typeof GET_REPORT_REQUEST;
readonly report: unknown;
}
export function getReport(report: unknown): GetReportRequest {
return { type: GET_REPORT_REQUEST, report };
}
interface LibConfig {
actionCreators?: string;
name?: string;
type?: string;
features?: Features;
serialize?: boolean;
}
export interface RequestBase {
id: string;
instanceId?: string;
action?: string;
name?: string;
libConfig?: LibConfig;
actionsById?: string;
computedStates?: string;
// eslint-disable-next-line @typescript-eslint/ban-types
payload?: {} | string;
}
interface InitRequest extends RequestBase {
type: 'INIT';
action: string;
}
interface ActionRequest extends RequestBase {
type: 'ACTION';
}
interface StateRequest extends RequestBase {
type: 'STATE';
committedState: unknown;
}
interface PartialStateRequest extends RequestBase {
type: 'PARTIAL_STATE';
committedState: unknown;
}
interface LiftedRequest extends RequestBase {
type: 'LIFTED';
}
export type Request =
| InitRequest
| ActionRequest
| StateRequest
| PartialStateRequest
| LiftedRequest;
interface UpdateStateAction {
type: typeof UPDATE_STATE;
request?: Request;
id?: string;
}
interface SetStateAction {
type: typeof SET_STATE;
newState: State;
}
interface RemoveInstanceAction {
@ -355,6 +463,8 @@ export type StoreAction =
| ReconnectAction
| ShowNotificationAction
| ClearNotificationAction
| GetReportRequest
| SetStateAction
| UpdateStateAction
| RemoveInstanceAction
| ConnectRequestAction

View File

@ -8,10 +8,12 @@ import PrintButton from './buttons/PrintButton';
import DispatcherButton from './buttons/DispatcherButton';
import SliderButton from './buttons/SliderButton';
import MonitorSelector from './MonitorSelector';
import { Options } from '../reducers/instances';
interface Props {
dispatcherIsOpen: boolean;
sliderIsOpen: boolean;
options: Options;
}
export default class BottomButtons extends Component<Props> {

View File

@ -2,18 +2,21 @@ import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { ActionCreators, LiftedAction } from 'redux-devtools-instrument';
import { Button, Toolbar, Divider } from 'devui';
import { Action } from 'redux';
import RecordButton from './buttons/RecordButton';
import PersistButton from './buttons/PersistButton';
import LockButton from './buttons/LockButton';
import InstanceSelector from './InstanceSelector';
import SyncButton from './buttons/SyncButton';
import { Action } from 'redux';
import { Options, State } from '../reducers/instances';
// eslint-disable-next-line @typescript-eslint/unbound-method
const { reset, rollback, commit, sweep } = ActionCreators;
interface Props {
dispatch: (action: LiftedAction<unknown, Action<unknown>, unknown>) => void;
liftedState: State;
options: Options;
}
export default class TopButtons extends Component<Props> {

View File

@ -1,19 +1,19 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { Button } from 'devui';
import { IoIosLock } from 'react-icons/io';
import { lockChanges, StoreAction } from '../../actions';
import { Dispatch } from 'redux';
class LockButton extends Component {
static propTypes = {
locked: PropTypes.bool,
disabled: PropTypes.bool,
lockChanges: PropTypes.func.isRequired,
};
type DispatchProps = ReturnType<typeof mapDispatchToProps>;
interface OwnProps {
locked: boolean | undefined;
disabled: boolean;
}
type Props = DispatchProps & OwnProps;
shouldComponentUpdate(nextProps) {
class LockButton extends Component<Props> {
shouldComponentUpdate(nextProps: Props) {
return nextProps.locked !== this.props.locked;
}

View File

@ -8,15 +8,12 @@ import { StoreState } from '../../reducers';
type StateProps = ReturnType<typeof mapStateToProps>;
type DispatchProps = ResolveThunks<typeof actionCreators>;
type Props = StateProps & DispatchProps;
interface OwnProps {
disabled?: boolean;
}
type Props = StateProps & DispatchProps & OwnProps;
class LockButton extends Component<Props> {
static propTypes = {
persisted: PropTypes.bool,
disabled: PropTypes.bool,
onClick: PropTypes.func.isRequired,
};
shouldComponentUpdate(nextProps: Props) {
return nextProps.persisted !== this.props.persisted;
}

View File

@ -1,17 +1,18 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { Button } from 'devui';
import { MdFiberManualRecord } from 'react-icons/md';
import { pauseRecording } from '../../actions';
import { pauseRecording, StoreAction } from '../../actions';
import { Dispatch } from 'redux';
class RecordButton extends Component {
static propTypes = {
paused: PropTypes.bool,
pauseRecording: PropTypes.func.isRequired,
};
type DispatchProps = ReturnType<typeof mapDispatchToProps>;
interface OwnProps {
paused: boolean | undefined;
}
type Props = DispatchProps & OwnProps;
shouldComponentUpdate(nextProps) {
class RecordButton extends Component<Props> {
shouldComponentUpdate(nextProps: Props) {
return nextProps.paused !== this.props.paused;
}
@ -29,7 +30,10 @@ class RecordButton extends Component {
}
}
function mapDispatchToProps(dispatch, ownProps) {
function mapDispatchToProps(
dispatch: Dispatch<StoreAction>,
ownProps: OwnProps
) {
return {
pauseRecording: () => dispatch(pauseRecording(!ownProps.paused)),
};

View File

@ -1,5 +1,4 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { connect, ResolveThunks } from 'react-redux';
import { Button } from 'devui';
import { TiArrowSync } from 'react-icons/ti';
@ -11,11 +10,6 @@ type DispatchProps = ResolveThunks<typeof actionCreators>;
type Props = StateProps & DispatchProps;
class SyncButton extends Component<Props> {
static propTypes = {
sync: PropTypes.bool,
onClick: PropTypes.func.isRequired,
};
shouldComponentUpdate(nextProps: Props) {
return nextProps.sync !== this.props.sync;
}

View File

@ -1,17 +1,23 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { withTheme } from 'styled-components';
import { LiftedAction, LiftedState } from 'redux-devtools-instrument';
import { Action } from 'redux';
import { Monitor } from 'redux-devtools';
import getMonitor from '../utils/getMonitor';
import { InitMonitorAction } from '../actions';
import { Features, State } from '../reducers/instances';
import { MonitorStateMonitorState } from '../reducers/monitor';
import { ThemeFromProvider } from 'devui';
interface Props {
monitor: string;
liftedState: State;
monitorState: MonitorStateMonitorState | undefined;
dispatch: (
action: LiftedAction<unknown, Action<unknown>, unknown> | InitMonitorAction
) => void;
features: Features | undefined;
theme: ThemeFromProvider;
}
class DevTools extends Component<Props> {
@ -30,7 +36,7 @@ class DevTools extends Component<Props> {
this.getMonitor(props, props.monitorState);
}
getMonitor(props: Props, skipUpdate: unknown) {
getMonitor(props: Props, skipUpdate?: unknown) {
const monitorElement = getMonitor(props);
this.monitorProps = monitorElement.props;
this.Monitor = monitorElement.type;
@ -59,15 +65,15 @@ class DevTools extends Component<Props> {
}
}
UNSAFE_componentWillUpdate(nextProps) {
UNSAFE_componentWillUpdate(nextProps: Props) {
if (nextProps.monitor !== this.props.monitor) this.getMonitor(nextProps);
}
shouldComponentUpdate(nextProps) {
shouldComponentUpdate(nextProps: Props) {
return (
nextProps.monitor !== this.props.monitor ||
nextProps.liftedState !== this.props.liftedState ||
nextProps.monitorState !== this.props.liftedState ||
nextProps.monitorState !== this.props.monitorState ||
nextProps.features !== this.props.features ||
nextProps.theme.scheme !== this.props.theme.scheme
);
@ -103,13 +109,4 @@ class DevTools extends Component<Props> {
}
}
DevTools.propTypes = {
liftedState: PropTypes.object,
monitorState: PropTypes.object,
dispatch: PropTypes.func.isRequired,
monitor: PropTypes.string,
features: PropTypes.object.isRequired,
theme: PropTypes.object.isRequired,
};
export default withTheme(DevTools);

View File

@ -9,7 +9,7 @@ export function getPath(obj: NodeWithId, inspectedStatePath: string[]) {
if (!parent) return;
getPath(parent, inspectedStatePath);
let name = obj.name;
const item = name.match(/.+\[(\d+)]/);
const item = /.+\[(\d+)]/.exec(name);
if (item) name = item[1];
inspectedStatePath.push(name);
}

View File

@ -1,11 +1,11 @@
// Based on https://github.com/YoruNoHikage/redux-devtools-dispatch
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import styled from 'styled-components';
import { Button, Select, Editor, Toolbar } from 'devui';
import { connect, ResolveThunks } from 'react-redux';
import { dispatchRemotely } from '../../actions';
import { Options } from '../../reducers/instances';
export const DispatcherContainer = styled.div`
display: flex;
@ -47,14 +47,20 @@ export const ActionContainer = styled.div`
`;
type DispatchProps = ResolveThunks<typeof actionCreators>;
type Props = DispatchProps;
interface OwnProps {
options: Options;
}
type Props = DispatchProps & OwnProps;
class Dispatcher extends Component<Props> {
static propTypes = {
options: PropTypes.object.isRequired,
dispatch: PropTypes.func.isRequired,
};
interface State {
selected: string;
customAction: string;
args: unknown[];
rest: string;
changed: boolean;
}
class Dispatcher extends Component<Props, State> {
state = {
selected: 'default',
customAction:
@ -64,7 +70,7 @@ class Dispatcher extends Component<Props> {
changed: false,
};
UNSAFE_componentWillReceiveProps(nextProps) {
UNSAFE_componentWillReceiveProps(nextProps: Props) {
if (
this.state.selected !== 'default' &&
!nextProps.options.actionCreators
@ -76,7 +82,7 @@ class Dispatcher extends Component<Props> {
}
}
shouldComponentUpdate(nextProps, nextState) {
shouldComponentUpdate(nextProps: Props, nextState: State) {
return (
nextState !== this.state ||
nextProps.options.actionCreators !== this.props.options.actionCreators
@ -94,7 +100,7 @@ class Dispatcher extends Component<Props> {
const args = [];
if (selected !== 'default') {
args.length = this.props.options.actionCreators[selected].args.length;
args.length = this.props.options.actionCreators![selected].args.length;
}
this.setState({ selected, args, rest: '[]', changed: false });
};

View File

@ -1,5 +1,4 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { connect, ResolveThunks } from 'react-redux';
import { Tab, Tabs } from 'devui';
import { TabComponentProps } from 'redux-devtools-inspector-monitor';
@ -97,18 +96,9 @@ class SubTabs extends Component<Props> {
}
}
SubTabs.propTypes = {
selected: PropTypes.string,
parentTab: PropTypes.string,
selectMonitorTab: PropTypes.func.isRequired,
action: PropTypes.object,
delta: PropTypes.object,
nextState: PropTypes.object,
};
const mapStateToProps = (state: StoreState) => ({
parentTab: state.monitor.monitorState.tabName,
selected: state.monitor.monitorState.subTabName,
parentTab: state.monitor.monitorState!.tabName,
selected: state.monitor.monitorState!.subTabName,
});
const actionCreators = {

View File

@ -8,7 +8,7 @@ function download(state) {
const blob = new Blob([state], { type: 'octet/stream' });
const href = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.style = 'display: none';
a.style.display = 'none';
a.download = 'state.json';
a.href = href;
document.body.appendChild(a);

View File

@ -12,12 +12,12 @@ import {
import { DISCONNECTED } from '../constants/socketActionTypes';
import parseJSON from '../utils/parseJSON';
import { recompute } from '../utils/updateState';
import { LiftedActionAction, StoreAction } from '../actions';
import { LiftedActionDispatchAction, Request, StoreAction } from '../actions';
interface Features {
export interface Features {
lock?: boolean;
export?: boolean;
import?: string;
export?: string | boolean;
import?: string | boolean;
persist?: boolean;
pause?: boolean;
reorder?: boolean;
@ -28,7 +28,7 @@ interface Features {
test?: boolean;
}
interface Options {
export interface Options {
name?: string;
connectionId?: string;
explicitLib?: string;
@ -38,13 +38,16 @@ interface Options {
serialize?: boolean;
}
interface State {
export interface State {
actionsById: { [actionId: number]: PerformAction<Action<unknown>> };
computedStates: { state: unknown; error?: string }[];
currentStateIndex: number;
nextActionId: number;
skippedActionIds: number[];
stagedActionIds: number[];
committedState?: unknown;
isLocked?: boolean;
isPaused?: boolean;
}
export interface InstancesState {
@ -77,7 +80,7 @@ export const initialState: InstancesState = {
function updateState(
state: { [id: string]: State },
request,
request: Request,
id: string,
serialize: boolean | undefined
) {
@ -201,7 +204,7 @@ function updateState(
export function dispatchAction(
state: InstancesState,
{ action }: LiftedActionAction
{ action }: LiftedActionDispatchAction
) {
if (action.type === 'JUMP_TO_STATE' || action.type === 'JUMP_TO_ACTION') {
const id = state.selected || state.current;
@ -256,7 +259,11 @@ function removeState(state: InstancesState, connectionId: string) {
};
}
function init({ type, action, name, libConfig = {} }, connectionId, current) {
function init(
{ type, action, name, libConfig = {} }: Request,
connectionId: string,
current: string
): Options {
let lib;
let actionCreators;
let creators = libConfig.actionCreators || action;

View File

@ -7,14 +7,23 @@ import {
} from '../constants/actionTypes';
import { MonitorActionAction, StoreAction } from '../actions';
export interface MonitorStateMonitorState {
inspectedStatePath?: string[];
tabName?: string;
subTabName?: string;
selectedActionId?: number | null;
startActionId?: number | null;
inspectedActionPath?: string[];
__overwritten__?: string;
}
export interface MonitorState {
selected: string;
monitorState: unknown | undefined;
monitorState: MonitorStateMonitorState | undefined;
sliderIsOpen: boolean;
dispatcherIsOpen: boolean;
}
const initialState = {
const initialState: MonitorState = {
selected: 'InspectorMonitor',
monitorState: undefined,
sliderIsOpen: true,
@ -24,7 +33,7 @@ const initialState = {
export function dispatchMonitorAction(
state: MonitorState,
action: MonitorActionAction
) {
): MonitorState {
return {
...state,
monitorState:
@ -37,7 +46,10 @@ export function dispatchMonitorAction(
};
}
export default function monitor(state = initialState, action: StoreAction) {
export default function monitor(
state = initialState,
action: StoreAction
): MonitorState {
switch (action.type) {
case MONITOR_ACTION:
return dispatchMonitorAction(state, action);
@ -56,7 +68,7 @@ export default function monitor(state = initialState, action: StoreAction) {
};
}
case UPDATE_MONITOR_STATE: {
let inspectedStatePath = state.monitorState.inspectedStatePath;
let inspectedStatePath = state.monitorState!.inspectedStatePath!;
if (action.nextState.inspectedStatePath) {
inspectedStatePath = [
...inspectedStatePath.slice(0, -1),

View File

@ -3,9 +3,11 @@ import {
} from '../constants/actionTypes';
import { StoreAction } from '../actions';
export interface ReportsState {}
export interface ReportsState {
data: unknown[];
}
const initialState = {
const initialState: ReportsState = {
data: [],
};

View File

@ -2,8 +2,9 @@ import difference from 'lodash/difference';
import omit from 'lodash/omit';
import stringifyJSON from './stringifyJSON';
import { SET_STATE } from '../constants/actionTypes';
import { State } from '../reducers/instances';
export function sweep(state) {
export function sweep(state: State): State {
return {
...state,
actionsById: omit(state.actionsById, state.skippedActionIds),

View File

@ -26,7 +26,10 @@ export function reviver(key, value) {
return value;
}
export default function parseJSON(data, serialize) {
export default function parseJSON(
data: string | undefined,
serialize?: boolean
) {
if (typeof data !== 'string') return data;
try {
return serialize ? jsan.parse(data, reviver) : jsan.parse(data);

View File

@ -1,12 +1,13 @@
import commitExcessActions from './commitExcessActions';
import { State } from '../reducers/instances';
export function recompute(
previousLiftedState,
previousLiftedState: State,
storeState,
action,
nextActionId = 1,
maxAge,
isExcess
maxAge?: number,
isExcess?: boolean
) {
const actionId = nextActionId - 1;
const liftedState = { ...previousLiftedState };