diff --git a/packages/redux-devtools-core/src/app/actions/index.ts b/packages/redux-devtools-core/src/app/actions/index.ts index 0a9e9bd0..973905f3 100644 --- a/packages/redux-devtools-core/src/app/actions/index.ts +++ b/packages/redux-devtools-core/src/app/actions/index.ts @@ -29,6 +29,7 @@ import { CONNECT_SUCCESS, DEAUTHENTICATE, DISCONNECTED, + EMIT, RECONNECT, SUBSCRIBE_ERROR, SUBSCRIBE_REQUEST, @@ -90,14 +91,21 @@ export interface MonitorActionAction { ) => unknown; monitorProps: unknown; } -interface LiftedActionAction { +interface LiftedActionDispatchAction { type: typeof LIFTED_ACTION; message: 'DISPATCH'; action: Action; } +interface LiftedActionImportAction { + type: typeof LIFTED_ACTION; + message: 'IMPORT'; + state: string; + preloadedState: unknown | undefined; +} +export type LiftedActionAction = LiftedActionDispatchAction; export function liftedDispatch( action: InitMonitorAction -): MonitorActionAction | LiftedActionAction { +): MonitorActionAction | LiftedActionDispatchAction { if (action.type[0] === '@') { if (action.type === '@@INIT_MONITOR') { monitorReducer = action.update; @@ -146,7 +154,10 @@ export function updateMonitorState(nextState) { return { type: UPDATE_MONITOR_STATE, nextState }; } -export function importState(state, preloadedState) { +export function importState( + state: string, + preloadedState?: unknown +): LiftedActionImportAction { return { type: LIFTED_ACTION, message: 'IMPORT', state, preloadedState }; } @@ -247,6 +258,10 @@ export function getReport(report) { return { type: GET_REPORT_REQUEST, report }; } +interface UpdateStateAction { + type: typeof UPDATE_STATE; +} + interface RemoveInstanceAction { type: typeof REMOVE_INSTANCE; id: string; @@ -318,6 +333,12 @@ interface UnsubscribeAction { channel: string; } +interface EmitAction { + type: typeof EMIT; + message: string; + id: string; +} + export type StoreAction = | ChangeSectionAction | ChangeThemeAction @@ -334,6 +355,7 @@ export type StoreAction = | ReconnectAction | ShowNotificationAction | ClearNotificationAction + | UpdateStateAction | RemoveInstanceAction | ConnectRequestAction | ConnectSuccessAction @@ -346,4 +368,5 @@ export type StoreAction = | SubscribeRequestAction | SubscribeSuccessAction | SubscribeErrorAction - | UnsubscribeAction; + | UnsubscribeAction + | EmitAction; diff --git a/packages/redux-devtools-core/src/app/components/BottomButtons.tsx b/packages/redux-devtools-core/src/app/components/BottomButtons.tsx index d7e0f33b..96dd6c2a 100644 --- a/packages/redux-devtools-core/src/app/components/BottomButtons.tsx +++ b/packages/redux-devtools-core/src/app/components/BottomButtons.tsx @@ -9,14 +9,19 @@ import DispatcherButton from './buttons/DispatcherButton'; import SliderButton from './buttons/SliderButton'; import MonitorSelector from './MonitorSelector'; -export default class BottomButtons extends Component { +interface Props { + dispatcherIsOpen: boolean; + sliderIsOpen: boolean; +} + +export default class BottomButtons extends Component { static propTypes = { dispatcherIsOpen: PropTypes.bool, sliderIsOpen: PropTypes.bool, options: PropTypes.object.isRequired, }; - shouldComponentUpdate(nextProps) { + shouldComponentUpdate(nextProps: Props) { return ( nextProps.dispatcherIsOpen !== this.props.dispatcherIsOpen || nextProps.sliderIsOpen !== this.props.sliderIsOpen || diff --git a/packages/redux-devtools-core/src/app/components/MonitorSelector.tsx b/packages/redux-devtools-core/src/app/components/MonitorSelector.tsx index df826bfa..e606008b 100644 --- a/packages/redux-devtools-core/src/app/components/MonitorSelector.tsx +++ b/packages/redux-devtools-core/src/app/components/MonitorSelector.tsx @@ -1,5 +1,4 @@ import React, { Component } from 'react'; -import PropTypes from 'prop-types'; import { connect, ResolveThunks } from 'react-redux'; import { Tabs } from 'devui'; import { monitors } from '../utils/getMonitor'; @@ -11,11 +10,6 @@ type DispatchProps = ResolveThunks; type Props = StateProps & DispatchProps; class MonitorSelector extends Component { - static propTypes = { - selected: PropTypes.string, - selectMonitor: PropTypes.func.isRequired, - }; - shouldComponentUpdate(nextProps: Props) { return nextProps.selected !== this.props.selected; } diff --git a/packages/redux-devtools-core/src/app/components/buttons/DispatcherButton.tsx b/packages/redux-devtools-core/src/app/components/buttons/DispatcherButton.tsx index ed298ccf..f148f0c6 100644 --- a/packages/redux-devtools-core/src/app/components/buttons/DispatcherButton.tsx +++ b/packages/redux-devtools-core/src/app/components/buttons/DispatcherButton.tsx @@ -1,19 +1,16 @@ import React, { Component } from 'react'; -import PropTypes from 'prop-types'; import { connect, ResolveThunks } from 'react-redux'; import { Button } from 'devui'; import { FaTerminal } from 'react-icons/fa'; import { toggleDispatcher } from '../../actions'; type DispatchProps = ResolveThunks; -type Props = DispatchProps; +interface OwnProps { + dispatcherIsOpen: boolean; +} +type Props = DispatchProps & OwnProps; class DispatcherButton extends Component { - static propTypes = { - dispatcherIsOpen: PropTypes.bool, - toggleDispatcher: PropTypes.func.isRequired, - }; - shouldComponentUpdate(nextProps: Props) { return nextProps.dispatcherIsOpen !== this.props.dispatcherIsOpen; } diff --git a/packages/redux-devtools-core/src/app/components/buttons/ExportButton.tsx b/packages/redux-devtools-core/src/app/components/buttons/ExportButton.tsx index 959f3c4c..0276d94a 100644 --- a/packages/redux-devtools-core/src/app/components/buttons/ExportButton.tsx +++ b/packages/redux-devtools-core/src/app/components/buttons/ExportButton.tsx @@ -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 { TiDownload } from 'react-icons/ti'; @@ -9,10 +8,6 @@ type DispatchProps = ResolveThunks; type Props = DispatchProps; class ExportButton extends Component { - static propTypes = { - exportState: PropTypes.func.isRequired, - }; - shouldComponentUpdate() { return false; } diff --git a/packages/redux-devtools-core/src/app/components/buttons/ImportButton.tsx b/packages/redux-devtools-core/src/app/components/buttons/ImportButton.tsx index c4674d6f..0b33a042 100644 --- a/packages/redux-devtools-core/src/app/components/buttons/ImportButton.tsx +++ b/packages/redux-devtools-core/src/app/components/buttons/ImportButton.tsx @@ -1,5 +1,4 @@ -import React, { Component } from 'react'; -import PropTypes from 'prop-types'; +import React, { ChangeEventHandler, Component, RefCallback } from 'react'; import { connect, ResolveThunks } from 'react-redux'; import { Button } from 'devui'; import { TiUpload } from 'react-icons/ti'; @@ -9,38 +8,29 @@ type DispatchProps = ResolveThunks; type Props = DispatchProps; class ImportButton extends Component { - static propTypes = { - importState: PropTypes.func.isRequired, - }; - - constructor() { - super(); - this.handleImport = this.handleImport.bind(this); - this.handleImportFile = this.handleImportFile.bind(this); - this.mapRef = this.mapRef.bind(this); - } + fileInput?: HTMLInputElement | null; shouldComponentUpdate() { return false; } - mapRef(node) { + mapRef: RefCallback = (node) => { this.fileInput = node; - } + }; - handleImport() { - this.fileInput.click(); - } + handleImport = () => { + this.fileInput!.click(); + }; - handleImportFile(e) { - const file = e.target.files[0]; + handleImportFile: ChangeEventHandler = (e) => { + const file = e.target.files![0]; const reader = new FileReader(); reader.onload = () => { - this.props.importState(reader.result); + this.props.importState(reader.result as string); }; reader.readAsText(file); - e.target.value = ''; // eslint-disable-line no-param-reassign - } + e.target.value = ''; + }; render() { return ( diff --git a/packages/redux-devtools-core/src/app/components/buttons/LockButton.tsx b/packages/redux-devtools-core/src/app/components/buttons/LockButton.tsx index 0bd642f7..c75e5f27 100644 --- a/packages/redux-devtools-core/src/app/components/buttons/LockButton.tsx +++ b/packages/redux-devtools-core/src/app/components/buttons/LockButton.tsx @@ -3,7 +3,8 @@ import PropTypes from 'prop-types'; import { connect } from 'react-redux'; import { Button } from 'devui'; import { IoIosLock } from 'react-icons/io'; -import { lockChanges } from '../../actions'; +import { lockChanges, StoreAction } from '../../actions'; +import { Dispatch } from 'redux'; class LockButton extends Component { static propTypes = { @@ -31,7 +32,10 @@ class LockButton extends Component { } } -function mapDispatchToProps(dispatch, ownProps) { +function mapDispatchToProps( + dispatch: Dispatch, + ownProps: OwnProps +) { return { lockChanges: () => dispatch(lockChanges(!ownProps.locked)), }; diff --git a/packages/redux-devtools-core/src/app/components/buttons/PrintButton.tsx b/packages/redux-devtools-core/src/app/components/buttons/PrintButton.tsx index dc55408c..ee272b69 100644 --- a/packages/redux-devtools-core/src/app/components/buttons/PrintButton.tsx +++ b/packages/redux-devtools-core/src/app/components/buttons/PrintButton.tsx @@ -7,8 +7,8 @@ export default class PrintButton extends Component { return false; } - handlePrint() { - const d3svg = document.getElementById('d3svg'); + handlePrint = () => { + const d3svg = (document.getElementById('d3svg') as unknown) as SVGGElement; if (!d3svg) { window.print(); return; @@ -17,11 +17,11 @@ export default class PrintButton extends Component { const initHeight = d3svg.style.height; const initWidth = d3svg.style.width; const box = d3svg.getBBox(); - d3svg.style.height = box.height; - d3svg.style.width = box.width; + d3svg.style.height = `${box.height}`; + d3svg.style.width = `${box.width}`; - const g = d3svg.firstChild; - const initTransform = g.getAttribute('transform'); + const g = d3svg.firstChild! as SVGGElement; + const initTransform = g.getAttribute('transform')!; g.setAttribute( 'transform', initTransform.replace(/.+scale\(/, 'translate(57, 10) scale(') @@ -32,7 +32,7 @@ export default class PrintButton extends Component { d3svg.style.height = initHeight; d3svg.style.width = initWidth; g.setAttribute('transform', initTransform); - } + }; render() { return ( diff --git a/packages/redux-devtools-core/src/app/components/buttons/SliderButton.tsx b/packages/redux-devtools-core/src/app/components/buttons/SliderButton.tsx index 1988edf5..2d86df26 100644 --- a/packages/redux-devtools-core/src/app/components/buttons/SliderButton.tsx +++ b/packages/redux-devtools-core/src/app/components/buttons/SliderButton.tsx @@ -1,19 +1,16 @@ import React, { Component } from 'react'; -import PropTypes from 'prop-types'; import { connect, ResolveThunks } from 'react-redux'; import { Button } from 'devui'; import { MdAvTimer } from 'react-icons/md'; import { toggleSlider } from '../../actions'; type DispatchProps = ResolveThunks; -type Props = DispatchProps; +interface OwnProps { + isOpen: boolean; +} +type Props = DispatchProps & OwnProps; class SliderButton extends Component { - static propTypes = { - isOpen: PropTypes.bool, - toggleSlider: PropTypes.func.isRequired, - }; - shouldComponentUpdate(nextProps: Props) { return nextProps.isOpen !== this.props.isOpen; } diff --git a/packages/redux-devtools-core/src/app/middlewares/api.ts b/packages/redux-devtools-core/src/app/middlewares/api.ts index 9656efdd..204cd41a 100644 --- a/packages/redux-devtools-core/src/app/middlewares/api.ts +++ b/packages/redux-devtools-core/src/app/middlewares/api.ts @@ -50,7 +50,21 @@ function dispatchRemoteAction({ message, action, state, toAll }) { }); } -function monitoring(request) { +interface DisconnectedAction { + type: 'DISCONNECTED'; + id: string; +} +interface StartAction { + type: 'START'; + id: string; +} +interface ErrorAction { + type: 'ERROR'; + payload: string; +} +type Request = DisconnectedAction | StartAction | ErrorAction; + +function monitoring(request: Request) { if (request.type === 'DISCONNECTED') { store.dispatch({ type: REMOVE_INSTANCE, @@ -89,7 +103,10 @@ function monitoring(request) { } } -function subscribe(channelName, subscription) { +function subscribe( + channelName: string, + subscription: typeof UPDATE_STATE | typeof UPDATE_REPORTS +) { const channel = socket.subscribe(channelName); if (subscription === UPDATE_STATE) channel.watch(monitoring); else { @@ -152,7 +169,7 @@ function connect() { socket = socketCluster.create( connection.type === 'remotedev' ? socketOptions : connection.options ); - handleConnection(store); + handleConnection(); } catch (error) { store.dispatch({ type: actions.CONNECT_ERROR, error }); store.dispatch(showNotification(error.message || error)); diff --git a/packages/redux-devtools-core/src/app/reducers/instances.ts b/packages/redux-devtools-core/src/app/reducers/instances.ts index ed209859..3a9347f9 100644 --- a/packages/redux-devtools-core/src/app/reducers/instances.ts +++ b/packages/redux-devtools-core/src/app/reducers/instances.ts @@ -1,3 +1,5 @@ +import { PerformAction } from 'redux-devtools-instrument'; +import { Action } from 'redux'; import { UPDATE_STATE, SET_STATE, @@ -10,10 +12,48 @@ import { import { DISCONNECTED } from '../constants/socketActionTypes'; import parseJSON from '../utils/parseJSON'; import { recompute } from '../utils/updateState'; -import { StoreAction } from '../actions'; +import { LiftedActionAction, StoreAction } from '../actions'; + +interface Features { + lock?: boolean; + export?: boolean; + import?: string; + persist?: boolean; + pause?: boolean; + reorder?: boolean; + jump?: boolean; + skip?: boolean; + dispatch?: boolean; + sync?: boolean; + test?: boolean; +} + +interface Options { + name?: string; + connectionId?: string; + explicitLib?: string; + lib?: string; + actionCreators?: string; + features: Features; + serialize?: boolean; +} + +interface State { + actionsById: { [actionId: number]: PerformAction> }; + computedStates: { state: unknown; error?: string }[]; + currentStateIndex: number; + nextActionId: number; + skippedActionIds: number[]; + stagedActionIds: number[]; +} export interface InstancesState { + selected: string | null; + current: string; sync: boolean; + connections: { [id: string]: string[] }; + options: { [id: string]: Options }; + states: { [id: string]: State }; persisted?: boolean; } @@ -35,7 +75,12 @@ export const initialState: InstancesState = { }, }; -function updateState(state, request, id, serialize) { +function updateState( + state: { [id: string]: State }, + request, + id: string, + serialize: boolean | undefined +) { let payload = request.payload; const actionsById = request.actionsById; if (actionsById) { @@ -154,7 +199,10 @@ function updateState(state, request, id, serialize) { return { ...state, [id]: newState }; } -export function dispatchAction(state, { action }) { +export function dispatchAction( + state: InstancesState, + { action }: LiftedActionAction +) { if (action.type === 'JUMP_TO_STATE' || action.type === 'JUMP_TO_ACTION') { const id = state.selected || state.current; const liftedState = state.states[id];