This commit is contained in:
Nathan Bierema 2020-10-23 20:06:37 -04:00
parent 6266d6bd5a
commit 2bfeb49b43
24 changed files with 335 additions and 154 deletions

View File

@ -20,6 +20,9 @@ import {
UPDATE_REPORTS, UPDATE_REPORTS,
REMOVE_INSTANCE, REMOVE_INSTANCE,
SET_STATE, SET_STATE,
GET_REPORT_ERROR,
GET_REPORT_SUCCESS,
ERROR,
} from '../constants/actionTypes'; } from '../constants/actionTypes';
import { import {
AUTH_ERROR, AUTH_ERROR,
@ -41,6 +44,7 @@ import { Action } from 'redux';
import { Features, State } from '../reducers/instances'; import { Features, State } from '../reducers/instances';
import { MonitorStateMonitorState } from '../reducers/monitor'; import { MonitorStateMonitorState } from '../reducers/monitor';
import { LiftedAction } from 'redux-devtools-instrument'; import { LiftedAction } from 'redux-devtools-instrument';
import { Data } from '../reducers/reports';
let monitorReducer: ( let monitorReducer: (
monitorProps: unknown, monitorProps: unknown,
@ -95,49 +99,73 @@ export interface MonitorActionAction {
) => unknown; ) => unknown;
monitorProps: unknown; monitorProps: unknown;
} }
interface JumpToStateAction { export interface JumpToStateAction {
type: 'JUMP_TO_STATE'; type: 'JUMP_TO_STATE';
index: number; index: number;
actionId: number; actionId: number;
} }
interface JumpToActionAction { export interface JumpToActionAction {
type: 'JUMP_TO_ACTION'; type: 'JUMP_TO_ACTION';
index: number; index: number;
actionId: number; actionId: number;
} }
interface PauseRecordingAction { export interface PauseRecordingAction {
type: 'PAUSE_RECORDING'; type: 'PAUSE_RECORDING';
status: boolean; status: boolean;
} }
interface LockChangesAction { export interface LockChangesAction {
type: 'LOCK_CHANGES'; type: 'LOCK_CHANGES';
status: boolean; status: boolean;
} }
export interface LiftedActionDispatchAction { export interface ToggleActionAction {
type: typeof LIFTED_ACTION; type: 'TOGGLE_ACTION';
message: 'DISPATCH'; }
action: export interface RollbackAction {
type: 'ROLLBACK';
}
export interface SweepAction {
type: 'SWEEP';
}
export type DispatchAction =
| JumpToStateAction | JumpToStateAction
| JumpToActionAction | JumpToActionAction
| PauseRecordingAction | PauseRecordingAction
| LockChangesAction; | LockChangesAction
| ToggleActionAction
| RollbackAction
| SweepAction;
interface LiftedActionActionBase {
action?: DispatchAction | string | CustomAction;
state?: string;
toAll?: boolean; toAll?: boolean;
} }
interface LiftedActionImportAction { export interface LiftedActionDispatchAction extends LiftedActionActionBase {
type: typeof LIFTED_ACTION;
message: 'DISPATCH';
action: DispatchAction;
toAll?: boolean;
}
interface LiftedActionImportAction extends LiftedActionActionBase {
type: typeof LIFTED_ACTION; type: typeof LIFTED_ACTION;
message: 'IMPORT'; message: 'IMPORT';
state: string; state: string;
preloadedState: unknown | undefined; preloadedState: unknown | undefined;
} }
interface LiftedActionActionAction { interface LiftedActionActionAction extends LiftedActionActionBase {
type: typeof LIFTED_ACTION; type: typeof LIFTED_ACTION;
message: 'ACTION'; message: 'ACTION';
action: Action<unknown>; action: string | CustomAction;
}
interface LiftedActionExportAction extends LiftedActionActionBase {
type: typeof LIFTED_ACTION;
message: 'EXPORT';
toExport: boolean;
} }
export type LiftedActionAction = export type LiftedActionAction =
| LiftedActionDispatchAction | LiftedActionDispatchAction
| LiftedActionImportAction | LiftedActionImportAction
| LiftedActionActionAction; | LiftedActionActionAction
| LiftedActionExportAction;
export function liftedDispatch( export function liftedDispatch(
action: action:
| InitMonitorAction | InitMonitorAction
@ -237,8 +265,14 @@ export function pauseRecording(status: boolean): LiftedActionDispatchAction {
}; };
} }
export interface CustomAction {
name: string;
selected: number;
args: (string | undefined)[];
rest: string;
}
export function dispatchRemotely( export function dispatchRemotely(
action: Action<unknown> action: string | CustomAction
): LiftedActionActionAction { ): LiftedActionActionAction {
return { type: LIFTED_ACTION, message: 'ACTION', action }; return { type: LIFTED_ACTION, message: 'ACTION', action };
} }
@ -317,6 +351,7 @@ export function getReport(report: unknown): GetReportRequest {
export interface ActionCreator { export interface ActionCreator {
args: string[]; args: string[];
name: string;
} }
interface LibConfig { interface LibConfig {
@ -337,6 +372,7 @@ export interface RequestBase {
computedStates?: string; computedStates?: string;
// eslint-disable-next-line @typescript-eslint/ban-types // eslint-disable-next-line @typescript-eslint/ban-types
payload?: {} | string; payload?: {} | string;
liftedState?: Partial<State>;
} }
interface InitRequest extends RequestBase { interface InitRequest extends RequestBase {
type: 'INIT'; type: 'INIT';
@ -344,6 +380,10 @@ interface InitRequest extends RequestBase {
} }
interface ActionRequest extends RequestBase { interface ActionRequest extends RequestBase {
type: 'ACTION'; type: 'ACTION';
isExcess: boolean;
nextActionId: number;
maxAge: number;
batched: boolean;
} }
interface StateRequest extends RequestBase { interface StateRequest extends RequestBase {
type: 'STATE'; type: 'STATE';
@ -352,16 +392,22 @@ interface StateRequest extends RequestBase {
interface PartialStateRequest extends RequestBase { interface PartialStateRequest extends RequestBase {
type: 'PARTIAL_STATE'; type: 'PARTIAL_STATE';
committedState: unknown; committedState: unknown;
maxAge: number;
} }
interface LiftedRequest extends RequestBase { interface LiftedRequest extends RequestBase {
type: 'LIFTED'; type: 'LIFTED';
} }
export interface ExportRequest extends RequestBase {
type: 'EXPORT';
committedState: unknown;
}
export type Request = export type Request =
| InitRequest | InitRequest
| ActionRequest | ActionRequest
| StateRequest | StateRequest
| PartialStateRequest | PartialStateRequest
| LiftedRequest; | LiftedRequest
| ExportRequest;
interface UpdateStateAction { interface UpdateStateAction {
type: typeof UPDATE_STATE; type: typeof UPDATE_STATE;
@ -445,10 +491,47 @@ interface UnsubscribeAction {
channel: string; channel: string;
} }
interface EmitAction { export interface EmitAction {
type: typeof EMIT; type: typeof EMIT;
message: string; message: string;
id: string; id?: string | false;
instanceId?: string;
action?: unknown;
state?: unknown;
}
interface ListRequest {
type: 'list';
data: Data;
}
interface AddRequest {
type: 'add';
data: Data;
}
interface RemoveRequest {
type: 'remove';
data: Data;
id: unknown;
}
type UpdateReportsRequest = ListRequest | AddRequest | RemoveRequest;
interface UpdateReportsAction {
type: typeof UPDATE_REPORTS;
request: UpdateReportsRequest;
}
interface GetReportError {
type: typeof GET_REPORT_ERROR;
error: Error;
}
interface GetReportSuccess {
type: typeof GET_REPORT_SUCCESS;
data: { payload: string };
}
interface ErrorAction {
type: typeof ERROR;
payload: string;
} }
export type StoreAction = export type StoreAction =
@ -483,4 +566,8 @@ export type StoreAction =
| SubscribeSuccessAction | SubscribeSuccessAction
| SubscribeErrorAction | SubscribeErrorAction
| UnsubscribeAction | UnsubscribeAction
| EmitAction; | EmitAction
| UpdateReportsAction
| GetReportError
| GetReportSuccess
| ErrorAction;

View File

@ -21,7 +21,8 @@ class Settings extends Component<{}, State> {
render() { render() {
return ( return (
<Tabs // eslint-disable-next-line @typescript-eslint/ban-types
<Tabs<{}>
toRight toRight
tabs={this.tabs} tabs={this.tabs}
selected={this.state.selected} selected={this.state.selected}

View File

@ -1,5 +1,14 @@
import socketCluster from 'socketcluster-client'; import socketCluster from 'socketcluster-client';
interface States {
CLOSED: 'closed';
CONNECTING: 'connecting';
OPEN: 'open';
AUTHENTICATED: 'authenticated';
PENDING: 'pending';
UNAUTHENTICATED: 'unauthenticated';
}
export const { export const {
CLOSED, CLOSED,
CONNECTING, CONNECTING,
@ -7,7 +16,7 @@ export const {
AUTHENTICATED, AUTHENTICATED,
PENDING, PENDING,
UNAUTHENTICATED, UNAUTHENTICATED,
} = socketCluster.SCClientSocket; } = (socketCluster.SCClientSocket as unknown) as States;
export const CONNECT_REQUEST = 'socket/CONNECT_REQUEST'; export const CONNECT_REQUEST = 'socket/CONNECT_REQUEST';
export const CONNECT_SUCCESS = 'socket/CONNECT_SUCCESS'; export const CONNECT_SUCCESS = 'socket/CONNECT_SUCCESS';
export const CONNECT_ERROR = 'socket/CONNECT_ERROR'; export const CONNECT_ERROR = 'socket/CONNECT_ERROR';

View File

@ -1,5 +1,4 @@
import React, { Component } from 'react'; import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { connect, ResolveThunks } from 'react-redux'; import { connect, ResolveThunks } from 'react-redux';
import { Container } from 'devui'; import { Container } from 'devui';
import SliderMonitor from './monitors/Slider'; import SliderMonitor from './monitors/Slider';
@ -55,16 +54,6 @@ class Actions extends Component<Props> {
} }
} }
Actions.propTypes = {
liftedDispatch: PropTypes.func.isRequired,
liftedState: PropTypes.object.isRequired,
monitorState: PropTypes.object,
options: PropTypes.object.isRequired,
monitor: PropTypes.string,
dispatcherIsOpen: PropTypes.bool,
sliderIsOpen: PropTypes.bool,
};
const mapStateToProps = (state: StoreState) => { const mapStateToProps = (state: StoreState) => {
const instances = state.instances; const instances = state.instances;
const id = getActiveInstance(instances); const id = getActiveInstance(instances);

View File

@ -2,7 +2,6 @@ import React, { Component } from 'react';
import { withTheme } from 'styled-components'; import { withTheme } from 'styled-components';
import { LiftedAction, LiftedState } from 'redux-devtools-instrument'; import { LiftedAction, LiftedState } from 'redux-devtools-instrument';
import { Action } from 'redux'; import { Action } from 'redux';
import { Monitor } from 'redux-devtools';
import getMonitor from '../utils/getMonitor'; import getMonitor from '../utils/getMonitor';
import { InitMonitorAction } from '../actions'; import { InitMonitorAction } from '../actions';
import { Features, State } from '../reducers/instances'; import { Features, State } from '../reducers/instances';
@ -22,13 +21,15 @@ interface Props {
class DevTools extends Component<Props> { class DevTools extends Component<Props> {
monitorProps: unknown; monitorProps: unknown;
Monitor?: Monitor< Monitor?: React.ComponentType<
unknown, LiftedState<unknown, Action<unknown>, unknown>
Action<unknown>, > & {
LiftedState<unknown, Action<unknown>, unknown>, update(
unknown, monitorProps: unknown,
Action<unknown> state: unknown | undefined,
>; action: Action<unknown>
): unknown;
};
preventRender?: boolean; preventRender?: boolean;
constructor(props: Props) { constructor(props: Props) {
@ -41,6 +42,7 @@ class DevTools extends Component<Props> {
this.monitorProps = monitorElement.props; this.monitorProps = monitorElement.props;
this.Monitor = monitorElement.type; this.Monitor = monitorElement.type;
// eslint-disable-next-line @typescript-eslint/unbound-method
const update = this.Monitor!.update; const update = this.Monitor!.update;
if (update) { if (update) {
let newMonitorState; let newMonitorState;
@ -51,7 +53,11 @@ class DevTools extends Component<Props> {
) { ) {
newMonitorState = monitorState; newMonitorState = monitorState;
} else { } else {
newMonitorState = update(this.monitorProps, undefined, {}); newMonitorState = update(
this.monitorProps,
undefined,
{} as Action<unknown>
);
if (newMonitorState !== monitorState) { if (newMonitorState !== monitorState) {
this.preventRender = true; this.preventRender = true;
} }
@ -95,9 +101,10 @@ class DevTools extends Component<Props> {
...this.props.liftedState, ...this.props.liftedState,
monitorState: this.props.monitorState, monitorState: this.props.monitorState,
}; };
const MonitorAsAny = this.Monitor as any;
return ( return (
<div className={`monitor monitor-${this.props.monitor}`}> <div className={`monitor monitor-${this.props.monitor}`}>
<this.Monitor <MonitorAsAny
{...liftedState} {...liftedState}
{...this.monitorProps} {...this.monitorProps}
features={this.props.features} features={this.props.features}

View File

@ -35,6 +35,8 @@ class ChartMonitorWrapper extends Component<Props> {
render() { render() {
return ( return (
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
<ChartMonitor <ChartMonitor
defaultIsVisible defaultIsVisible
invertTheme invertTheme

View File

@ -53,15 +53,15 @@ interface OwnProps {
type Props = DispatchProps & OwnProps; type Props = DispatchProps & OwnProps;
interface State { interface State {
selected: string; selected: 'default' | number;
customAction: string; customAction: string;
args: unknown[]; args: (string | undefined)[];
rest: string; rest: string;
changed: boolean; changed: boolean;
} }
class Dispatcher extends Component<Props, State> { class Dispatcher extends Component<Props, State> {
state = { state: State = {
selected: 'default', selected: 'default',
customAction: customAction:
this.props.options.lib === 'redux' ? "{\n type: ''\n}" : 'this.', this.props.options.lib === 'redux' ? "{\n type: ''\n}" : 'this.',
@ -89,7 +89,7 @@ class Dispatcher extends Component<Props, State> {
); );
} }
selectActionCreator = (selected) => { selectActionCreator = (selected: 'default' | 'actions-help' | number) => {
if (selected === 'actions-help') { if (selected === 'actions-help') {
window.open( window.open(
'https://github.com/zalmoxisus/redux-devtools-extension/blob/master/docs/' + 'https://github.com/zalmoxisus/redux-devtools-extension/blob/master/docs/' +
@ -98,14 +98,14 @@ class Dispatcher extends Component<Props, State> {
return; return;
} }
const args = []; const args: string[] = [];
if (selected !== 'default') { 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 }); this.setState({ selected, args, rest: '[]', changed: false });
}; };
handleArg = (argIndex) => (value) => { handleArg = (argIndex: number) => (value: string) => {
const args = [ const args = [
...this.state.args.slice(0, argIndex), ...this.state.args.slice(0, argIndex),
value || undefined, value || undefined,
@ -114,26 +114,26 @@ class Dispatcher extends Component<Props, State> {
this.setState({ args, changed: true }); this.setState({ args, changed: true });
}; };
handleRest = (rest) => { handleRest = (rest: string) => {
this.setState({ rest, changed: true }); this.setState({ rest, changed: true });
}; };
handleCustomAction = (customAction) => { handleCustomAction = (customAction: string) => {
this.setState({ customAction, changed: true }); this.setState({ customAction, changed: true });
}; };
dispatchAction = () => { dispatchAction = () => {
const { selected, customAction, args, rest } = this.state; const { selected, customAction, args, rest } = this.state;
if (this.state.selected !== 'default') { if (selected !== 'default') {
// remove trailing `undefined` arguments // remove trailing `undefined` arguments
let i = args.length - 1; let i = args.length - 1;
while (i >= 0 && typeof args[i] === 'undefined') { while (i >= 0 && typeof args[i] === 'undefined') {
args.pop(i); args.pop();
i--; i--;
} }
this.props.dispatch({ this.props.dispatch({
name: this.props.options.actionCreators[selected].name, name: this.props.options.actionCreators![selected].name,
selected, selected,
args, args,
rest, rest,
@ -182,7 +182,9 @@ class Dispatcher extends Component<Props, State> {
); );
} }
let options = [{ value: 'default', label: 'Custom action' }]; let options: { value: string | number; label: string }[] = [
{ value: 'default', label: 'Custom action' },
];
if (actionCreators && actionCreators.length > 0) { if (actionCreators && actionCreators.length > 0) {
options = options.concat( options = options.concat(
actionCreators.map(({ name, args }, i) => ({ actionCreators.map(({ name, args }, i) => ({

View File

@ -72,20 +72,20 @@ class ChartTab extends Component<Props> {
style: { style: {
width: '100%', width: '100%',
height: '100%', height: '100%',
node: { node: ({
colors: { colors: {
default: theme.base0B, default: theme.base0B,
collapsed: theme.base0B, collapsed: theme.base0B,
parent: theme.base0E, parent: theme.base0E,
}, },
radius: 7, radius: 7,
}, } as unknown) as string,
text: { text: ({
colors: { colors: {
default: theme.base0D, default: theme.base0D,
hover: theme.base06, hover: theme.base06,
}, },
}, } as unknown) as string,
}, },
onClickText: this.onClickText, onClickText: this.onClickText,
}; };

View File

@ -11,6 +11,7 @@ import RawTab from './RawTab';
import ChartTab from './ChartTab'; import ChartTab from './ChartTab';
import VisualDiffTab from './VisualDiffTab'; import VisualDiffTab from './VisualDiffTab';
import { StoreState } from '../../../reducers'; import { StoreState } from '../../../reducers';
import { Delta } from 'jsondiffpatch';
type StateProps = ReturnType<typeof mapStateToProps>; type StateProps = ReturnType<typeof mapStateToProps>;
type DispatchProps = ResolveThunks<typeof actionCreators>; type DispatchProps = ResolveThunks<typeof actionCreators>;
@ -19,7 +20,7 @@ type Props = StateProps &
TabComponentProps<unknown, Action<unknown>>; TabComponentProps<unknown, Action<unknown>>;
class SubTabs extends Component<Props> { class SubTabs extends Component<Props> {
tabs?: Tab<unknown>[]; tabs?: (Tab<Props> | Tab<{ data: unknown }> | Tab<{ data?: Delta }>)[];
constructor(props: Props) { constructor(props: Props) {
super(props); super(props);
@ -56,7 +57,7 @@ class SubTabs extends Component<Props> {
{ {
name: 'Raw', name: 'Raw',
component: VisualDiffTab, component: VisualDiffTab,
selector: this.selector, selector: this.selector as () => { data?: Delta },
}, },
]; ];
return; return;
@ -88,7 +89,7 @@ class SubTabs extends Component<Props> {
return ( return (
<Tabs <Tabs
tabs={this.tabs!} tabs={this.tabs! as any}
selected={selected || 'Tree'} selected={selected || 'Tree'}
onClick={this.props.selectMonitorTab} onClick={this.props.selectMonitorTab}
/> />

View File

@ -1,9 +1,10 @@
import React, { Component } from 'react'; import React, { Component } from 'react';
import styled, { withTheme } from 'styled-components'; import styled, { withTheme } from 'styled-components';
import SliderMonitor from 'redux-devtools-slider-monitor'; import SliderMonitor from 'redux-devtools-slider-monitor';
import { LiftedAction, LiftedState } from 'redux-devtools-instrument'; import { LiftedAction } from 'redux-devtools-instrument';
import { Action, Dispatch } from 'redux'; import { Action } from 'redux';
import { ThemeFromProvider } from 'devui'; import { ThemeFromProvider } from 'devui';
import { State } from '../../reducers/instances';
const SliderWrapper = styled.div` const SliderWrapper = styled.div`
border-color: ${(props) => props.theme.base02}; border-color: ${(props) => props.theme.base02};
@ -12,8 +13,8 @@ const SliderWrapper = styled.div`
`; `;
interface Props { interface Props {
liftedState: LiftedState<unknown, Action<unknown>, unknown>; liftedState: State;
dispatch: Dispatch<LiftedAction<unknown, Action<unknown>, unknown>>; dispatch: (action: LiftedAction<unknown, Action<unknown>, unknown>) => void;
theme: ThemeFromProvider; theme: ThemeFromProvider;
} }
@ -29,6 +30,8 @@ class Slider extends Component<Props> {
<SliderWrapper> <SliderWrapper>
<SliderMonitor <SliderMonitor
{...this.props.liftedState} {...this.props.liftedState}
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
dispatch={this.props.dispatch} dispatch={this.props.dispatch}
theme={this.props.theme} theme={this.props.theme}
hideResetButton hideResetButton

View File

@ -1,6 +1,5 @@
import 'devui/lib/presets'; import 'devui/lib/presets';
import React, { Component } from 'react'; import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { Provider } from 'react-redux'; import { Provider } from 'react-redux';
import { Store } from 'redux'; import { Store } from 'redux';
import configureStore from './store/configureStore'; import configureStore from './store/configureStore';
@ -21,7 +20,8 @@ class Root extends Component<Props> {
this.store = store; this.store = store;
store.dispatch({ store.dispatch({
type: CONNECT_REQUEST, type: CONNECT_REQUEST,
options: preloadedState!.connection || this.props.socketOptions, options: (preloadedState!.connection ||
this.props.socketOptions) as ConnectionOptions,
}); });
this.forceUpdate(); this.forceUpdate();
}); });
@ -29,21 +29,13 @@ class Root extends Component<Props> {
render() { render() {
if (!this.store) return null; if (!this.store) return null;
const AppAsAny = App as any;
return ( return (
<Provider store={this.store}> <Provider store={this.store}>
<App {...this.props} /> <AppAsAny {...this.props} />
</Provider> </Provider>
); );
} }
} }
Root.propTypes = {
socketOptions: PropTypes.shape({
hostname: PropTypes.string,
port: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
autoReconnect: PropTypes.bool,
secure: PropTypes.bool,
}),
};
export default Root; export default Root;

View File

@ -13,23 +13,36 @@ import {
GET_REPORT_ERROR, GET_REPORT_ERROR,
GET_REPORT_SUCCESS, GET_REPORT_SUCCESS,
} from '../constants/actionTypes'; } from '../constants/actionTypes';
import { showNotification, importState, StoreAction } from '../actions'; import {
showNotification,
importState,
StoreAction,
EmitAction,
LiftedActionAction,
Request,
DispatchAction,
} from '../actions';
import { nonReduxDispatch } from '../utils/monitorActions'; import { nonReduxDispatch } from '../utils/monitorActions';
import { StoreState } from '../reducers'; import { StoreState } from '../reducers';
let socket: SCClientSocket; let socket: SCClientSocket;
let store: MiddlewareAPI<Dispatch<StoreAction>, StoreState>; let store: MiddlewareAPI<Dispatch<StoreAction>, StoreState>;
function emit({ message: type, id, instanceId, action, state }) { function emit({ message: type, id, instanceId, action, state }: EmitAction) {
socket.emit(id ? 'sc-' + id : 'respond', { type, action, state, instanceId }); socket.emit(id ? 'sc-' + id : 'respond', { type, action, state, instanceId });
} }
function startMonitoring(channel) { function startMonitoring(channel: string) {
if (channel !== store.getState().socket.baseChannel) return; if (channel !== store.getState().socket.baseChannel) return;
store.dispatch({ type: actions.EMIT, message: 'START' }); store.dispatch({ type: actions.EMIT, message: 'START' });
} }
function dispatchRemoteAction({ message, action, state, toAll }) { function dispatchRemoteAction({
message,
action,
state,
toAll,
}: LiftedActionAction) {
const instances = store.getState().instances; const instances = store.getState().instances;
const instanceId = getActiveInstance(instances); const instanceId = getActiveInstance(instances);
const id = !toAll && instances.options[instanceId].connectionId; const id = !toAll && instances.options[instanceId].connectionId;
@ -41,7 +54,7 @@ function dispatchRemoteAction({ message, action, state, toAll }) {
store, store,
message, message,
instanceId, instanceId,
action, action as DispatchAction,
state, state,
instances instances
), ),
@ -50,21 +63,32 @@ function dispatchRemoteAction({ message, action, state, toAll }) {
}); });
} }
interface DisconnectedAction { interface RequestBase {
id?: string;
instanceId?: string;
}
interface DisconnectedAction extends RequestBase {
type: 'DISCONNECTED'; type: 'DISCONNECTED';
id: string; id: string;
} }
interface StartAction { interface StartAction extends RequestBase {
type: 'START'; type: 'START';
id: string; id: string;
} }
interface ErrorAction { interface ErrorAction extends RequestBase {
type: 'ERROR'; type: 'ERROR';
payload: string; payload: string;
} }
type Request = DisconnectedAction | StartAction | ErrorAction; interface RequestWithData extends RequestBase {
data: Request;
}
type MonitoringRequest =
| DisconnectedAction
| StartAction
| ErrorAction
| Request;
function monitoring(request: Request) { function monitoring(request: MonitoringRequest) {
if (request.type === 'DISCONNECTED') { if (request.type === 'DISCONNECTED') {
store.dispatch({ store.dispatch({
type: REMOVE_INSTANCE, type: REMOVE_INSTANCE,
@ -84,7 +108,9 @@ function monitoring(request: Request) {
store.dispatch({ store.dispatch({
type: UPDATE_STATE, type: UPDATE_STATE,
request: request.data ? { ...request.data, id: request.id } : request, request: ((request as unknown) as RequestWithData).data
? { ...((request as unknown) as RequestWithData).data, id: request.id }
: request,
}); });
const instances = store.getState().instances; const instances = store.getState().instances;
@ -110,7 +136,7 @@ function subscribe(
const channel = socket.subscribe(channelName); const channel = socket.subscribe(channelName);
if (subscription === UPDATE_STATE) channel.watch(monitoring); if (subscription === UPDATE_STATE) channel.watch(monitoring);
else { else {
const watcher = (request) => { const watcher = (request: Request) => {
store.dispatch({ type: subscription, request }); store.dispatch({ type: subscription, request });
}; };
channel.watch(watcher); channel.watch(watcher);
@ -201,15 +227,19 @@ function login() {
}); });
} }
function getReport(reportId) { function getReport(reportId: unknown) {
socket.emit('getReport', reportId, (error, data) => { socket.emit(
'getReport',
reportId,
(error: Error, data: { payload: string }) => {
if (error) { if (error) {
store.dispatch({ type: GET_REPORT_ERROR, error }); store.dispatch({ type: GET_REPORT_ERROR, error });
return; return;
} }
store.dispatch({ type: GET_REPORT_SUCCESS, data }); store.dispatch({ type: GET_REPORT_SUCCESS, data });
store.dispatch(importState(data.payload)); store.dispatch(importState(data.payload));
}); }
);
} }
export default function api( export default function api(

View File

@ -1,10 +1,13 @@
import stringifyJSON from '../utils/stringifyJSON'; import stringifyJSON from '../utils/stringifyJSON';
import { UPDATE_STATE, LIFTED_ACTION, EXPORT } from '../constants/actionTypes'; import { UPDATE_STATE, LIFTED_ACTION, EXPORT } from '../constants/actionTypes';
import { getActiveInstance } from '../reducers/instances'; import { getActiveInstance } from '../reducers/instances';
import { Dispatch, MiddlewareAPI } from 'redux';
import { ExportRequest, StoreAction } from '../actions';
import { StoreState } from '../reducers';
let toExport; let toExport: string | undefined;
function download(state) { function download(state: string) {
const blob = new Blob([state], { type: 'octet/stream' }); const blob = new Blob([state], { type: 'octet/stream' });
const href = window.URL.createObjectURL(blob); const href = window.URL.createObjectURL(blob);
const a = document.createElement('a'); const a = document.createElement('a');
@ -19,15 +22,17 @@ function download(state) {
}, 0); }, 0);
} }
const exportState = (store) => (next) => (action) => { const exportState = (
store: MiddlewareAPI<Dispatch<StoreAction>, StoreState>
) => (next: Dispatch<StoreAction>) => (action: StoreAction) => {
const result = next(action); const result = next(action);
if ( if (
toExport && toExport &&
action.type === UPDATE_STATE && action.type === UPDATE_STATE &&
action.request.type === 'EXPORT' action.request!.type === 'EXPORT'
) { ) {
const request = action.request; const request = action.request!;
const id = request.instanceId || request.id; const id = request.instanceId || request.id;
if (id === toExport) { if (id === toExport) {
toExport = undefined; toExport = undefined;
@ -35,7 +40,7 @@ const exportState = (store) => (next) => (action) => {
JSON.stringify( JSON.stringify(
{ {
payload: request.payload, payload: request.payload,
preloadedState: request.committedState, preloadedState: (request as ExportRequest).committedState,
}, },
null, null,
'\t' '\t'

View File

@ -89,19 +89,20 @@ function updateState(
id: string, id: string,
serialize: boolean | undefined serialize: boolean | undefined
) { ) {
let payload = request.payload; let payload: State = request.payload as State;
const actionsById = request.actionsById; const actionsById = request.actionsById;
if (actionsById) { if (actionsById) {
payload = { payload = {
// eslint-disable-next-line @typescript-eslint/ban-types
...payload, ...payload,
actionsById: parseJSON(actionsById, serialize), actionsById: parseJSON(actionsById, serialize),
computedStates: parseJSON(request.computedStates, serialize), computedStates: parseJSON(request.computedStates, serialize),
}; } as State;
if (request.type === 'STATE' && request.committedState) { if (request.type === 'STATE' && request.committedState) {
payload.committedState = payload.computedStates[0].state; payload.committedState = payload.computedStates[0].state;
} }
} else { } else {
payload = parseJSON(payload, serialize); payload = parseJSON((payload as unknown) as string, serialize) as State;
} }
let newState; let newState;
@ -112,11 +113,11 @@ function updateState(
case 'INIT': case 'INIT':
newState = recompute(state.default, payload, { newState = recompute(state.default, payload, {
action: { type: '@@INIT' }, action: { type: '@@INIT' },
timestamp: action.timestamp || Date.now(), timestamp: (action as { timestamp?: unknown }).timestamp || Date.now(),
}); });
break; break;
case 'ACTION': { case 'ACTION': {
let isExcess = request.isExcess; const isExcess = request.isExcess;
const nextActionId = request.nextActionId || liftedState.nextActionId + 1; const nextActionId = request.nextActionId || liftedState.nextActionId + 1;
const maxAge = request.maxAge; const maxAge = request.maxAge;
if (Array.isArray(action)) { if (Array.isArray(action)) {
@ -125,7 +126,7 @@ function updateState(
for (let i = 0; i < action.length; i++) { for (let i = 0; i < action.length; i++) {
newState = recompute( newState = recompute(
newState, newState,
request.batched ? payload : payload[i], request.batched ? payload : ((payload as unknown) as State[])[i],
action[i], action[i],
newState.nextActionId + 1, newState.nextActionId + 1,
maxAge, maxAge,

View File

@ -36,13 +36,12 @@ export function dispatchMonitorAction(
): MonitorState { ): MonitorState {
return { return {
...state, ...state,
monitorState: monitorState: (action.action.newMonitorState ||
action.action.newMonitorState ||
action.monitorReducer( action.monitorReducer(
action.monitorProps, action.monitorProps,
state.monitorState, state.monitorState,
action.action action.action
), )) as MonitorStateMonitorState,
}; };
} }

View File

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

View File

@ -6,8 +6,10 @@ export interface SocketState {
id: string | null; id: string | null;
channels: string[]; channels: string[];
socketState: States; socketState: States;
authState: AuthStates; authState: AuthStates | 'pending';
error: Error | undefined; error: Error | undefined;
baseChannel?: string;
authToken?: null;
} }
const initialState: SocketState = { const initialState: SocketState = {
@ -18,7 +20,10 @@ const initialState: SocketState = {
error: undefined, error: undefined,
}; };
export default function socket(state = initialState, action: StoreAction) { export default function socket(
state = initialState,
action: StoreAction
): SocketState {
switch (action.type) { switch (action.type) {
case actions.CONNECT_REQUEST: { case actions.CONNECT_REQUEST: {
return { return {

View File

@ -25,15 +25,23 @@ export default function configureStore(
deserialize: (data: unknown) => data, deserialize: (data: unknown) => data,
} as unknown) as PersistorConfig; } as unknown) as PersistorConfig;
// eslint-disable-next-line @typescript-eslint/no-floating-promises
getStoredState<StoreState>(persistConfig, (err, restoredState) => { getStoredState<StoreState>(persistConfig, (err, restoredState) => {
let composeEnhancers = compose; let composeEnhancers = compose;
if (process.env.NODE_ENV !== 'production') { if (process.env.NODE_ENV !== 'production') {
if (window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__) { if (
composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__; ((window as unknown) as {
__REDUX_DEVTOOLS_EXTENSION_COMPOSE__?: typeof compose;
}).__REDUX_DEVTOOLS_EXTENSION_COMPOSE__
) {
composeEnhancers = ((window as unknown) as {
__REDUX_DEVTOOLS_EXTENSION_COMPOSE__: typeof compose;
}).__REDUX_DEVTOOLS_EXTENSION_COMPOSE__;
} }
if (module.hot) { if (module.hot) {
// Enable Webpack hot module replacement for reducers // Enable Webpack hot module replacement for reducers
module.hot.accept('../reducers', () => { module.hot.accept('../reducers', () => {
// eslint-disable-next-line @typescript-eslint/no-var-requires
const nextReducer = require('../reducers'); // eslint-disable-line global-require const nextReducer = require('../reducers'); // eslint-disable-line global-require
store.replaceReducer(nextReducer); store.replaceReducer(nextReducer);
}); });

View File

@ -1,7 +1,9 @@
// Based on https://github.com/gaearon/redux-devtools/pull/241 // Based on https://github.com/gaearon/redux-devtools/pull/241
/* eslint-disable no-param-reassign */ /* eslint-disable no-param-reassign */
export default function commitExcessActions(liftedState, n = 1) { import { State } from '../reducers/instances';
export default function commitExcessActions(liftedState: State, n = 1) {
// Auto-commits n-number of excess actions. // Auto-commits n-number of excess actions.
let excess = n; let excess = n;
let idsToDelete = liftedState.stagedActionIds.slice(1, excess + 1); let idsToDelete = liftedState.stagedActionIds.slice(1, excess + 1);

View File

@ -2,7 +2,10 @@ import difference from 'lodash/difference';
import omit from 'lodash/omit'; import omit from 'lodash/omit';
import stringifyJSON from './stringifyJSON'; import stringifyJSON from './stringifyJSON';
import { SET_STATE } from '../constants/actionTypes'; import { SET_STATE } from '../constants/actionTypes';
import { State } from '../reducers/instances'; import { InstancesState, State } from '../reducers/instances';
import { Dispatch, MiddlewareAPI } from 'redux';
import { DispatchAction, StoreAction } from '../actions';
import { StoreState } from '../reducers';
export function sweep(state: State): State { export function sweep(state: State): State {
return { return {
@ -18,12 +21,12 @@ export function sweep(state: State): State {
} }
export function nonReduxDispatch( export function nonReduxDispatch(
store, store: MiddlewareAPI<Dispatch<StoreAction>, StoreState>,
message, message: string,
instanceId, instanceId: string,
action, action: DispatchAction,
initialState, initialState: string | undefined,
preInstances preInstances: InstancesState
) { ) {
const instances = preInstances || store.getState().instances; const instances = preInstances || store.getState().instances;
const state = instances.states[instanceId]; const state = instances.states[instanceId];

View File

@ -1,17 +1,17 @@
import jsan from 'jsan'; import jsan from 'jsan';
import { DATA_TYPE_KEY, DATA_REF_KEY } from '../constants/dataTypes'; import { DATA_TYPE_KEY, DATA_REF_KEY } from '../constants/dataTypes';
export function reviver(key, value) { export function reviver(key: string, value: unknown) {
if ( if (
typeof value === 'object' && typeof value === 'object' &&
value !== null && value !== null &&
'__serializedType__' in value && '__serializedType__' in value &&
typeof value.data === 'object' typeof (value as any).data === 'object'
) { ) {
const data = value.data; const data = (value as any).data;
data[DATA_TYPE_KEY] = value.__serializedType__; data[DATA_TYPE_KEY] = (value as any).__serializedType__;
if ('__serializedRef__' in value) if ('__serializedRef__' in value)
data[DATA_REF_KEY] = value.__serializedRef__; data[DATA_REF_KEY] = (value as any).__serializedRef__;
/* /*
if (Array.isArray(data)) { if (Array.isArray(data)) {
data.__serializedType__ = value.__serializedType__; data.__serializedType__ = value.__serializedType__;

View File

@ -1,20 +1,21 @@
import jsan from 'jsan'; import jsan from 'jsan';
import { DATA_TYPE_KEY, DATA_REF_KEY } from '../constants/dataTypes'; import { DATA_TYPE_KEY, DATA_REF_KEY } from '../constants/dataTypes';
function replacer(key, value) { function replacer(key: string, value: unknown) {
if (typeof value === 'object' && value !== null && DATA_TYPE_KEY in value) { if (typeof value === 'object' && value !== null && DATA_TYPE_KEY in value) {
const __serializedType__ = value[DATA_TYPE_KEY]; const __serializedType__ = (value as any)[DATA_TYPE_KEY];
const clone = { ...value }; const clone = { ...value };
delete clone[DATA_TYPE_KEY]; // eslint-disable-line no-param-reassign delete (clone as any)[DATA_TYPE_KEY]; // eslint-disable-line no-param-reassign
const r = { data: clone, __serializedType__ }; const r = { data: clone, __serializedType__ };
if (DATA_REF_KEY in value) r.__serializedRef__ = clone[DATA_REF_KEY]; if (DATA_REF_KEY in value)
(r as any).__serializedRef__ = (clone as any)[DATA_REF_KEY];
return r; return r;
} }
return value; return value;
} }
export default function stringifyJSON(data, serialize) { export default function stringifyJSON(data: unknown, serialize: boolean) {
return serialize return serialize
? jsan.stringify(data, replacer, null, true) ? jsan.stringify(data, replacer, (null as unknown) as undefined, true)
: jsan.stringify(data); : jsan.stringify(data);
} }

View File

@ -1,10 +1,12 @@
import commitExcessActions from './commitExcessActions'; import commitExcessActions from './commitExcessActions';
import { State } from '../reducers/instances'; import { State } from '../reducers/instances';
import { Action } from 'redux';
import { PerformAction } from 'redux-devtools-instrument';
export function recompute( export function recompute(
previousLiftedState: State, previousLiftedState: State,
storeState, storeState: State,
action, action: Action<unknown>,
nextActionId = 1, nextActionId = 1,
maxAge?: number, maxAge?: number,
isExcess?: boolean isExcess?: boolean
@ -21,12 +23,14 @@ export function recompute(
liftedState.stagedActionIds = [...liftedState.stagedActionIds, actionId]; liftedState.stagedActionIds = [...liftedState.stagedActionIds, actionId];
liftedState.actionsById = { ...liftedState.actionsById }; liftedState.actionsById = { ...liftedState.actionsById };
if (action.type === 'PERFORM_ACTION') { if (action.type === 'PERFORM_ACTION') {
liftedState.actionsById[actionId] = action; liftedState.actionsById[actionId] = action as PerformAction<
Action<unknown>
>;
} else { } else {
liftedState.actionsById[actionId] = { liftedState.actionsById[actionId] = {
action: action.action || action, action: (action as any).action || action,
timestamp: action.timestamp || Date.now(), timestamp: (action as any).timestamp || Date.now(),
stack: action.stack, stack: (action as any).stack,
type: 'PERFORM_ACTION', type: 'PERFORM_ACTION',
}; };
} }

View File

@ -1,28 +1,53 @@
const ERROR = '@@redux-devtools/ERROR'; const ERROR = '@@redux-devtools/ERROR';
export default function catchErrors(sendError) { interface ErrorAction {
type: typeof ERROR;
message?: Event | string;
url?: string | undefined;
lineNo?: number | undefined;
columnNo?: number | undefined;
stack?: string;
error?: Error;
isFatal?: boolean;
sourceURL?: string;
line?: number;
column?: number;
}
export default function catchErrors(
sendError: (errorAction: ErrorAction) => void
) {
if (typeof window === 'object' && typeof window.onerror === 'object') { if (typeof window === 'object' && typeof window.onerror === 'object') {
window.onerror = function (message, url, lineNo, columnNo, error) { window.onerror = function (message, url, lineNo, columnNo, error) {
const errorAction = { type: ERROR, message, url, lineNo, columnNo }; const errorAction: ErrorAction = {
type: ERROR,
message,
url,
lineNo,
columnNo,
};
if (error && error.stack) errorAction.stack = error.stack; if (error && error.stack) errorAction.stack = error.stack;
sendError(errorAction); sendError(errorAction);
return false; return false;
}; };
} else if (typeof global !== 'undefined' && global.ErrorUtils) { } else if (typeof global !== 'undefined' && (global as any).ErrorUtils) {
global.ErrorUtils.setGlobalHandler((error, isFatal) => { (global as any).ErrorUtils.setGlobalHandler(
(error: Error, isFatal: boolean) => {
sendError({ type: ERROR, error, isFatal }); sendError({ type: ERROR, error, isFatal });
}); }
);
} }
/* eslint-disable no-console */ /* eslint-disable no-console */
if ( if (
typeof console === 'object' && typeof console === 'object' &&
typeof console.error === 'function' && typeof console.error === 'function' &&
!console.beforeRemotedev !(console as any).beforeRemotedev
) { ) {
console.beforeRemotedev = console.error.bind(console); (console as any).beforeRemotedev = console.error.bind(console);
console.error = function () { console.error = function () {
let errorAction = { type: ERROR }; let errorAction: ErrorAction = { type: ERROR };
// eslint-disable-next-line prefer-rest-params
const error = arguments[0]; const error = arguments[0];
errorAction.message = error.message ? error.message : error; errorAction.message = error.message ? error.message : error;
if (error.sourceURL) { if (error.sourceURL) {
@ -35,7 +60,8 @@ export default function catchErrors(sendError) {
} }
if (error.stack) errorAction.stack = error.stack; if (error.stack) errorAction.stack = error.stack;
sendError(errorAction); sendError(errorAction);
console.beforeRemotedev.apply(null, arguments); // eslint-disable-next-line prefer-rest-params
(console as any).beforeRemotedev.apply(null, arguments);
}; };
} }
/* eslint-enable no-console */ /* eslint-enable no-console */