chore(core): convert to TypeScript (#655)

* Get started

* stash

* stash

* stash

* stash

* stash

* stash

* stash

* stash

* stash

* stash

* stash

* stash

* stash

* stash

* stash

* stash
This commit is contained in:
Nathan Bierema 2020-10-25 19:32:04 -04:00 committed by GitHub
parent 94c2c28cad
commit ee52c29a8d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
102 changed files with 2352 additions and 1236 deletions

View File

@ -1 +1,2 @@
export { default as tree } from './tree/tree';
export type { InputOptions, NodeWithId } from './tree/tree';

View File

@ -10,7 +10,7 @@ import {
} from './utils';
import d3tooltip from 'd3tooltip';
interface InputOptions {
export interface InputOptions {
// eslint-disable-next-line @typescript-eslint/ban-types
state?: {} | null;
// eslint-disable-next-line @typescript-eslint/ban-types
@ -34,7 +34,7 @@ interface InputOptions {
widthBetweenNodesCoeff: number;
transitionDuration: number;
blinkDuration: number;
onClickText: () => void;
onClickText: (datum: NodeWithId) => void;
tooltipOptions: {
disabled?: boolean;
left?: number | undefined;

View File

@ -1,5 +1,6 @@
import * as charts from './charts';
export { tree } from './charts';
export type { InputOptions, NodeWithId } from './charts';
export default charts;

View File

@ -1 +1,2 @@
export { default } from './Tabs';
export { Tab } from './TabsHeader';

View File

@ -6,7 +6,7 @@ export { default as Editor } from './Editor';
export { default as Form } from './Form';
export { default as Select } from './Select';
export { default as Slider } from './Slider';
export { default as Tabs } from './Tabs';
export { default as Tabs, Tab } from './Tabs';
export { default as SegmentedControl } from './SegmentedControl';
export { default as Notification } from './Notification';
export * from './Toolbar';
@ -14,3 +14,4 @@ export * from './Toolbar';
import color from './utils/color';
export const effects = { color };
export { default as createStyledComponent } from './utils/createStyledComponent';
export { Theme, ThemeFromProvider, Scheme } from './utils/theme';

View File

@ -3,19 +3,22 @@ import { nicinabox as defaultDarkScheme } from 'redux-devtools-themes';
import * as baseSchemes from 'base16';
import * as additionalSchemes from '../colorSchemes';
import invertColors from '../utils/invertColors';
import { Theme } from '../themes/default';
import { Theme as ThemeBase } from '../themes/default';
export const schemes = { ...baseSchemes, ...additionalSchemes };
export const listSchemes = () => Object.keys(schemes).slice(1).sort(); // remove `__esModule`
export const listThemes = () => Object.keys(themes);
export type Theme = keyof typeof themes;
export type Scheme = keyof typeof schemes;
export interface ThemeData {
theme: keyof typeof themes;
scheme: keyof typeof schemes;
light: boolean;
}
export interface ThemeFromProvider extends Theme {
export interface ThemeFromProvider extends ThemeBase {
type: keyof typeof themes;
light: boolean;
}

View File

@ -7,6 +7,7 @@ import * as themes from 'redux-devtools-themes';
import { Base16Theme } from 'react-base16-styling';
import { ChartMonitorState } from './reducers';
import { Primitive } from 'd3';
import { NodeWithId } from 'd3-state-visualizer/lib/charts/tree/tree';
const wrapperStyle = {
width: '100%',
@ -25,6 +26,7 @@ export interface Props<S, A extends Action<unknown>>
isSorted: boolean;
heightBetweenNodesCoeff: number;
widthBetweenNodesCoeff: number;
onClickText: (datum: NodeWithId) => void;
tooltipOptions: {
disabled: boolean;
offset: {

View File

@ -9,6 +9,7 @@ import { Base16Theme } from 'react-base16-styling';
import reducer, { ChartMonitorState } from './reducers';
import Chart, { Props } from './Chart';
import { Primitive } from 'd3';
import { NodeWithId } from 'd3-state-visualizer/lib/charts/tree/tree';
// eslint-disable-next-line @typescript-eslint/unbound-method
const { reset, rollback, commit, sweep, toggleAction } = ActionCreators;
@ -49,6 +50,7 @@ export interface ChartMonitorProps<S, A extends Action<unknown>>
isSorted: boolean;
heightBetweenNodesCoeff: number;
widthBetweenNodesCoeff: number;
onClickText: (datum: NodeWithId) => void;
tooltipOptions: unknown;
style: {
width: number;

View File

@ -1,4 +1,8 @@
{
"presets": ["@babel/preset-env", "@babel/preset-react"],
"presets": [
"@babel/preset-env",
"@babel/preset-react",
"@babel/preset-typescript"
],
"plugins": ["@babel/plugin-proposal-class-properties"]
}

View File

@ -0,0 +1,2 @@
lib
umd

View File

@ -0,0 +1,29 @@
module.exports = {
extends: '../../.eslintrc',
overrides: [
{
files: ['*.ts', '*.tsx'],
extends: '../../eslintrc.ts.react.base.json',
parserOptions: {
tsconfigRootDir: __dirname,
project: ['./tsconfig.json'],
},
},
{
files: ['test/*.ts', 'test/*.tsx'],
extends: '../../eslintrc.ts.react.jest.base.json',
parserOptions: {
tsconfigRootDir: __dirname,
project: ['./test/tsconfig.json'],
},
},
{
files: ['webpack.config.ts', 'webpack.config.umd.ts'],
extends: '../../eslintrc.ts.base.json',
parserOptions: {
tsconfigRootDir: __dirname,
project: ['./tsconfig.webpack.json'],
},
},
],
};

View File

@ -0,0 +1,4 @@
module.exports = {
preset: 'ts-jest',
setupFilesAfterEnv: ['<rootDir>/test/setup.ts'],
};

View File

@ -2,65 +2,39 @@
"name": "redux-devtools-core",
"version": "1.0.0-4",
"description": "Reusable functions of Redux DevTools",
"scripts": {
"start": "webpack-dev-server --hot --inline --env.development --env.platform=web --progress",
"build:web": "rimraf ./build/web && webpack -p --env.platform=web --progress",
"build:umd": "rimraf ./umd && webpack --progress --config webpack.config.umd.js",
"build:umd:min": "webpack --env.production --progress --config webpack.config.umd.js",
"build": "rimraf ./lib && babel ./src/app --out-dir lib",
"clean": "rimraf lib",
"test": "jest --no-cache",
"prepare": "npm run build && npm run build:umd && npm run build:umd:min",
"prepublishOnly": "npm run test && npm run build && npm run build:umd && npm run build:umd:min"
"homepage": "https://github.com/reduxjs/redux-devtools/tree/master/packages/redux-devtools-core",
"bugs": {
"url": "https://github.com/reduxjs/redux-devtools/issues"
},
"main": "lib/index.js",
"license": "MIT",
"author": "Mihail Diordiev <zalmoxisus@gmail.com> (https://github.com/zalmoxisus)",
"files": [
"src",
"lib",
"umd"
],
"jest": {
"setupFilesAfterEnv": [
"<rootDir>/test/setup.js"
],
"moduleNameMapper": {
"\\.(css|scss)$": "<rootDir>/test/__mocks__/styleMock.js"
}
},
"main": "lib/index.js",
"types": "lib/index.d.ts",
"repository": {
"type": "git",
"url": "https://github.com/reduxjs/redux-devtools.git"
},
"author": "Mihail Diordiev <zalmoxisus@gmail.com> (https://github.com/zalmoxisus)",
"license": "MIT",
"bugs": {
"url": "https://github.com/reduxjs/redux-devtools/issues"
},
"homepage": "https://github.com/reduxjs/redux-devtools",
"devDependencies": {
"@babel/cli": "^7.10.5",
"@babel/core": "^7.11.1",
"@babel/plugin-proposal-class-properties": "^7.10.4",
"@babel/preset-env": "^7.11.0",
"@babel/preset-react": "^7.10.4",
"babel-loader": "^8.1.0",
"css-loader": "^4.2.1",
"enzyme": "^3.11.0",
"enzyme-adapter-react-16": "^1.15.3",
"enzyme-to-json": "^3.5.0",
"file-loader": "^6.0.0",
"html-loader": "^1.1.0",
"html-webpack-plugin": "^4.3.0",
"jest": "^26.2.2",
"react": "^16.13.1",
"react-dom": "^16.13.1",
"rimraf": "^3.0.2",
"style-loader": "^1.2.1",
"url-loader": "^4.1.0",
"webpack": "^4.44.1",
"webpack-cli": "^3.3.12",
"webpack-dev-server": "^3.11.0",
"webpack-hot-middleware": "^2.25.0"
"scripts": {
"start": "webpack-dev-server --hot --inline --env.development --env.platform=web --progress",
"build": "npm run build:types && npm run build:js && npm run build:web && npm run build:umd && npm run build:umd:min",
"build:types": "tsc --emitDeclarationOnly",
"build:js": "babel src --out-dir lib --extensions \".ts,.tsx\" --source-maps inline",
"build:web": "rimraf ./build/web && webpack -p --env.platform=web --progress",
"build:umd": "rimraf ./umd && webpack --progress --config webpack.config.umd.ts",
"build:umd:min": "webpack --env.production --progress --config webpack.config.umd.ts",
"clean": "rimraf lib",
"test": "jest",
"lint": "eslint . --ext .ts,.tsx",
"lint:fix": "eslint . --ext .ts,.tsx --fix",
"type-check": "tsc --noEmit",
"type-check:watch": "npm run type-check -- --watch",
"preversion": "npm run type-check && npm run lint && npm run test",
"prepublishOnly": "npm run clean && npm run build"
},
"dependencies": {
"d3-state-visualizer": "^1.3.4",
@ -90,6 +64,34 @@
"socketcluster-client": "^14.3.1",
"styled-components": "^5.1.1"
},
"devDependencies": {
"@babel/cli": "^7.10.5",
"@babel/core": "^7.11.1",
"@babel/plugin-proposal-class-properties": "^7.10.4",
"@babel/preset-env": "^7.11.0",
"@babel/preset-react": "^7.10.4",
"@rjsf/core": "^2.4.0",
"@types/json-schema": "^7.0.6",
"@types/socketcluster-client": "^13.0.3",
"babel-loader": "^8.1.0",
"css-loader": "^4.2.1",
"enzyme": "^3.11.0",
"enzyme-adapter-react-16": "^1.15.3",
"enzyme-to-json": "^3.5.0",
"file-loader": "^6.0.0",
"html-loader": "^1.1.0",
"html-webpack-plugin": "^4.3.0",
"jest": "^26.2.2",
"react": "^16.13.1",
"react-dom": "^16.13.1",
"rimraf": "^3.0.2",
"style-loader": "^1.2.1",
"url-loader": "^4.1.0",
"webpack": "^4.44.1",
"webpack-cli": "^3.3.12",
"webpack-dev-server": "^3.11.0",
"webpack-hot-middleware": "^2.25.0"
},
"peerDependencies": {
"react": "^16.3.0"
}

View File

@ -1,122 +0,0 @@
import {
CHANGE_SECTION,
CHANGE_THEME,
SELECT_INSTANCE,
SELECT_MONITOR,
UPDATE_MONITOR_STATE,
LIFTED_ACTION,
MONITOR_ACTION,
EXPORT,
TOGGLE_SYNC,
TOGGLE_SLIDER,
TOGGLE_DISPATCHER,
TOGGLE_PERSIST,
GET_REPORT_REQUEST,
SHOW_NOTIFICATION,
CLEAR_NOTIFICATION,
} from '../constants/actionTypes';
import { RECONNECT } from '../constants/socketActionTypes';
let monitorReducer;
let monitorProps = {};
export function changeSection(section) {
return { type: CHANGE_SECTION, section };
}
export function changeTheme(data) {
return { type: CHANGE_THEME, ...data.formData };
}
export function liftedDispatch(action) {
if (action.type[0] === '@') {
if (action.type === '@@INIT_MONITOR') {
monitorReducer = action.update;
monitorProps = action.monitorProps;
}
return { type: MONITOR_ACTION, action, monitorReducer, monitorProps };
}
return { type: LIFTED_ACTION, message: 'DISPATCH', action };
}
export function selectInstance(selected) {
return { type: SELECT_INSTANCE, selected };
}
export function selectMonitor(monitor) {
return { type: SELECT_MONITOR, monitor };
}
export function selectMonitorWithState(value, monitorState) {
return { type: SELECT_MONITOR, monitor: value, monitorState };
}
export function selectMonitorTab(subTabName) {
return { type: UPDATE_MONITOR_STATE, nextState: { subTabName } };
}
export function updateMonitorState(nextState) {
return { type: UPDATE_MONITOR_STATE, nextState };
}
export function importState(state, preloadedState) {
return { type: LIFTED_ACTION, message: 'IMPORT', state, preloadedState };
}
export function exportState() {
return { type: EXPORT };
}
export function lockChanges(status) {
return {
type: LIFTED_ACTION,
message: 'DISPATCH',
action: { type: 'LOCK_CHANGES', status },
toAll: true,
};
}
export function pauseRecording(status) {
return {
type: LIFTED_ACTION,
message: 'DISPATCH',
action: { type: 'PAUSE_RECORDING', status },
toAll: true,
};
}
export function dispatchRemotely(action) {
return { type: LIFTED_ACTION, message: 'ACTION', action };
}
export function togglePersist() {
return { type: TOGGLE_PERSIST };
}
export function toggleSync() {
return { type: TOGGLE_SYNC };
}
export function toggleSlider() {
return { type: TOGGLE_SLIDER };
}
export function toggleDispatcher() {
return { type: TOGGLE_DISPATCHER };
}
export function saveSocketSettings(options) {
return { type: RECONNECT, options };
}
export function showNotification(message) {
return { type: SHOW_NOTIFICATION, notification: { type: 'error', message } };
}
export function clearNotification() {
return { type: CLEAR_NOTIFICATION };
}
export function getReport(report) {
return { type: GET_REPORT_REQUEST, report };
}

View File

@ -0,0 +1,573 @@
import { Scheme, Theme } from 'devui';
import { AuthStates, States } from 'socketcluster-client/lib/scclientsocket';
import {
CHANGE_SECTION,
CHANGE_THEME,
SELECT_INSTANCE,
SELECT_MONITOR,
UPDATE_MONITOR_STATE,
LIFTED_ACTION,
MONITOR_ACTION,
EXPORT,
TOGGLE_SYNC,
TOGGLE_SLIDER,
TOGGLE_DISPATCHER,
TOGGLE_PERSIST,
GET_REPORT_REQUEST,
SHOW_NOTIFICATION,
CLEAR_NOTIFICATION,
UPDATE_STATE,
UPDATE_REPORTS,
REMOVE_INSTANCE,
SET_STATE,
GET_REPORT_ERROR,
GET_REPORT_SUCCESS,
ERROR,
} from '../constants/actionTypes';
import {
AUTH_ERROR,
AUTH_REQUEST,
AUTH_SUCCESS,
CONNECT_ERROR,
CONNECT_REQUEST,
CONNECT_SUCCESS,
DEAUTHENTICATE,
DISCONNECTED,
EMIT,
RECONNECT,
SUBSCRIBE_ERROR,
SUBSCRIBE_REQUEST,
SUBSCRIBE_SUCCESS,
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';
import { Data } from '../reducers/reports';
let monitorReducer: (
monitorProps: unknown,
state: unknown | undefined,
action: Action<unknown>
) => unknown;
let monitorProps: unknown = {};
interface ChangeSectionAction {
readonly type: typeof CHANGE_SECTION;
readonly section: string;
}
export function changeSection(section: string): ChangeSectionAction {
return { type: CHANGE_SECTION, section };
}
interface ChangeThemeFormData {
readonly theme: Theme;
readonly scheme: Scheme;
readonly dark: boolean;
}
interface ChangeThemeData {
readonly formData: ChangeThemeFormData;
}
interface ChangeThemeAction {
readonly type: typeof CHANGE_THEME;
readonly theme: Theme;
readonly scheme: Scheme;
readonly dark: boolean;
}
export function changeTheme(data: ChangeThemeData): ChangeThemeAction {
return { type: CHANGE_THEME, ...data.formData };
}
export interface InitMonitorAction {
type: '@@INIT_MONITOR';
newMonitorState: unknown;
update: (
monitorProps: unknown,
state: unknown | undefined,
action: Action<unknown>
) => unknown;
monitorProps: unknown;
}
export interface MonitorActionAction {
type: typeof MONITOR_ACTION;
action: InitMonitorAction;
monitorReducer: (
monitorProps: unknown,
state: unknown | undefined,
action: Action<unknown>
) => unknown;
monitorProps: unknown;
}
export interface JumpToStateAction {
type: 'JUMP_TO_STATE';
index: number;
actionId: number;
}
export interface JumpToActionAction {
type: 'JUMP_TO_ACTION';
index: number;
actionId: number;
}
export interface PauseRecordingAction {
type: 'PAUSE_RECORDING';
status: boolean;
}
export interface LockChangesAction {
type: 'LOCK_CHANGES';
status: boolean;
}
export interface ToggleActionAction {
type: 'TOGGLE_ACTION';
}
export interface RollbackAction {
type: 'ROLLBACK';
}
export interface SweepAction {
type: 'SWEEP';
}
export type DispatchAction =
| JumpToStateAction
| JumpToActionAction
| PauseRecordingAction
| LockChangesAction
| ToggleActionAction
| RollbackAction
| SweepAction;
interface LiftedActionActionBase {
action?: DispatchAction | string | CustomAction;
state?: string;
toAll?: boolean;
}
export interface LiftedActionDispatchAction extends LiftedActionActionBase {
type: typeof LIFTED_ACTION;
message: 'DISPATCH';
action: DispatchAction;
toAll?: boolean;
}
interface LiftedActionImportAction extends LiftedActionActionBase {
type: typeof LIFTED_ACTION;
message: 'IMPORT';
state: string;
preloadedState: unknown | undefined;
}
interface LiftedActionActionAction extends LiftedActionActionBase {
type: typeof LIFTED_ACTION;
message: 'ACTION';
action: string | CustomAction;
}
interface LiftedActionExportAction extends LiftedActionActionBase {
type: typeof LIFTED_ACTION;
message: 'EXPORT';
toExport: boolean;
}
export type LiftedActionAction =
| LiftedActionDispatchAction
| LiftedActionImportAction
| LiftedActionActionAction
| LiftedActionExportAction;
export function liftedDispatch(
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,
} as MonitorActionAction;
}
return {
type: LIFTED_ACTION,
message: 'DISPATCH',
action,
} as LiftedActionDispatchAction;
}
interface SelectInstanceAction {
type: typeof SELECT_INSTANCE;
selected: string;
}
export function selectInstance(selected: string): SelectInstanceAction {
return { type: SELECT_INSTANCE, selected };
}
interface SelectMonitorAction {
type: typeof SELECT_MONITOR;
monitor: string;
monitorState?: MonitorStateMonitorState;
}
export function selectMonitor(monitor: string): SelectMonitorAction {
return { type: SELECT_MONITOR, monitor };
}
export function selectMonitorWithState(
value: string,
monitorState: MonitorStateMonitorState
): SelectMonitorAction {
return { type: SELECT_MONITOR, monitor: value, monitorState };
}
interface NextState {
subTabName: string;
inspectedStatePath?: string[];
}
interface UpdateMonitorStateAction {
type: typeof UPDATE_MONITOR_STATE;
nextState: NextState;
}
export function selectMonitorTab(subTabName: string): UpdateMonitorStateAction {
return { type: UPDATE_MONITOR_STATE, nextState: { subTabName } };
}
export function updateMonitorState(
nextState: NextState
): UpdateMonitorStateAction {
return { type: UPDATE_MONITOR_STATE, nextState };
}
export function importState(
state: string,
preloadedState?: unknown
): LiftedActionImportAction {
return { type: LIFTED_ACTION, message: 'IMPORT', state, preloadedState };
}
interface ExportAction {
type: typeof EXPORT;
}
export function exportState(): ExportAction {
return { type: EXPORT };
}
export function lockChanges(status: boolean): LiftedActionDispatchAction {
return {
type: LIFTED_ACTION,
message: 'DISPATCH',
action: { type: 'LOCK_CHANGES', status },
toAll: true,
};
}
export function pauseRecording(status: boolean): LiftedActionDispatchAction {
return {
type: LIFTED_ACTION,
message: 'DISPATCH',
action: { type: 'PAUSE_RECORDING', status },
toAll: true,
};
}
export interface CustomAction {
name: string;
selected: number;
args: (string | undefined)[];
rest: string;
}
export function dispatchRemotely(
action: string | CustomAction
): LiftedActionActionAction {
return { type: LIFTED_ACTION, message: 'ACTION', action };
}
interface TogglePersistAction {
type: typeof TOGGLE_PERSIST;
}
export function togglePersist(): TogglePersistAction {
return { type: TOGGLE_PERSIST };
}
interface ToggleSyncAction {
type: typeof TOGGLE_SYNC;
}
export function toggleSync(): ToggleSyncAction {
return { type: TOGGLE_SYNC };
}
interface ToggleSliderAction {
type: typeof TOGGLE_SLIDER;
}
export function toggleSlider(): ToggleSliderAction {
return { type: TOGGLE_SLIDER };
}
interface ToggleDispatcherAction {
type: typeof TOGGLE_DISPATCHER;
}
export function toggleDispatcher(): ToggleDispatcherAction {
return { type: TOGGLE_DISPATCHER };
}
export type ConnectionType = 'disabled' | 'remotedev' | 'custom';
export interface ConnectionOptions {
readonly type: ConnectionType;
readonly hostname: string;
readonly port: number;
readonly secure: boolean;
}
interface ReconnectAction {
readonly type: typeof RECONNECT;
readonly options: ConnectionOptions;
}
export function saveSocketSettings(
options: ConnectionOptions
): ReconnectAction {
return { type: RECONNECT, options };
}
interface Notification {
readonly type: 'error';
readonly message: string;
}
interface ShowNotificationAction {
readonly type: typeof SHOW_NOTIFICATION;
readonly notification: Notification;
}
export function showNotification(message: string): ShowNotificationAction {
return { type: SHOW_NOTIFICATION, notification: { type: 'error', message } };
}
interface ClearNotificationAction {
readonly type: typeof CLEAR_NOTIFICATION;
}
export function clearNotification(): ClearNotificationAction {
return { type: CLEAR_NOTIFICATION };
}
interface GetReportRequest {
readonly type: typeof GET_REPORT_REQUEST;
readonly report: unknown;
}
export function getReport(report: unknown): GetReportRequest {
return { type: GET_REPORT_REQUEST, report };
}
export interface ActionCreator {
args: string[];
name: string;
}
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;
liftedState?: Partial<State>;
}
interface InitRequest extends RequestBase {
type: 'INIT';
action: string;
}
interface ActionRequest extends RequestBase {
type: 'ACTION';
isExcess: boolean;
nextActionId: number;
maxAge: number;
batched: boolean;
}
interface StateRequest extends RequestBase {
type: 'STATE';
committedState: unknown;
}
interface PartialStateRequest extends RequestBase {
type: 'PARTIAL_STATE';
committedState: unknown;
maxAge: number;
}
interface LiftedRequest extends RequestBase {
type: 'LIFTED';
}
export interface ExportRequest extends RequestBase {
type: 'EXPORT';
committedState: unknown;
}
export type Request =
| InitRequest
| ActionRequest
| StateRequest
| PartialStateRequest
| LiftedRequest
| ExportRequest;
interface UpdateStateAction {
type: typeof UPDATE_STATE;
request?: Request;
id?: string;
}
interface SetStateAction {
type: typeof SET_STATE;
newState: State;
}
interface RemoveInstanceAction {
type: typeof REMOVE_INSTANCE;
id: string;
}
interface ConnectRequestAction {
type: typeof CONNECT_REQUEST;
options: ConnectionOptions;
}
interface ConnectSuccessPayload {
id: string;
authState: AuthStates;
socketState: States;
}
interface ConnectSuccessAction {
type: typeof CONNECT_SUCCESS;
payload: ConnectSuccessPayload;
error: Error | undefined;
}
interface ConnectErrorAction {
type: typeof CONNECT_ERROR;
error: Error | undefined;
}
interface AuthRequestAction {
type: typeof AUTH_REQUEST;
}
interface AuthSuccessAction {
type: typeof AUTH_SUCCESS;
baseChannel: string;
}
interface AuthErrorAction {
type: typeof AUTH_ERROR;
error: Error;
}
interface DisconnectedAction {
type: typeof DISCONNECTED;
code: number;
}
interface DeauthenticateAction {
type: typeof DEAUTHENTICATE;
}
interface SubscribeRequestAction {
type: typeof SUBSCRIBE_REQUEST;
channel: string;
subscription: typeof UPDATE_STATE | typeof UPDATE_REPORTS;
}
interface SubscribeSuccessAction {
type: typeof SUBSCRIBE_SUCCESS;
channel: string;
}
interface SubscribeErrorAction {
type: typeof SUBSCRIBE_ERROR;
error: Error;
status: string;
}
interface UnsubscribeAction {
type: typeof UNSUBSCRIBE;
channel: string;
}
export interface EmitAction {
type: typeof EMIT;
message: 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;
}
export 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 =
| ChangeSectionAction
| ChangeThemeAction
| MonitorActionAction
| LiftedActionAction
| SelectInstanceAction
| SelectMonitorAction
| UpdateMonitorStateAction
| ExportAction
| TogglePersistAction
| ToggleSyncAction
| ToggleSliderAction
| ToggleDispatcherAction
| ReconnectAction
| ShowNotificationAction
| ClearNotificationAction
| GetReportRequest
| SetStateAction
| UpdateStateAction
| RemoveInstanceAction
| ConnectRequestAction
| ConnectSuccessAction
| ConnectErrorAction
| AuthRequestAction
| AuthSuccessAction
| AuthErrorAction
| DisconnectedAction
| DeauthenticateAction
| SubscribeRequestAction
| SubscribeSuccessAction
| SubscribeErrorAction
| UnsubscribeAction
| EmitAction
| UpdateReportsAction
| GetReportError
| GetReportSuccess
| ErrorAction;

View File

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

View File

@ -1,8 +1,6 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { Tabs, Toolbar, Button, Divider } from 'devui';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import { connect, ResolveThunks } from 'react-redux';
import { GoBook } from 'react-icons/go';
import { IoMdText } from 'react-icons/io';
import { TiSocialTwitter } from 'react-icons/ti';
@ -11,13 +9,14 @@ import { changeSection } from '../actions';
const tabs = [{ name: 'Actions' }, { name: 'Reports' }, { name: 'Settings' }];
class Header extends Component {
static propTypes = {
section: PropTypes.string.isRequired,
changeSection: PropTypes.func.isRequired,
};
type DispatchProps = ResolveThunks<typeof actionCreators>;
interface OwnProps {
readonly section: string;
}
type Props = DispatchProps & OwnProps;
openLink = (url) => () => {
class Header extends Component<Props> {
openLink = (url: string) => () => {
window.open(url);
};
@ -69,10 +68,8 @@ class Header extends Component {
}
}
function mapDispatchToProps(dispatch) {
return {
changeSection: bindActionCreators(changeSection, dispatch),
};
}
const actionCreators = {
changeSection,
};
export default connect(null, mapDispatchToProps)(Header);
export default connect(null, actionCreators)(Header);

View File

@ -1,48 +0,0 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import { Select } from 'devui';
import { selectInstance } from '../actions';
class InstanceSelector extends Component {
static propTypes = {
selected: PropTypes.string,
instances: PropTypes.object.isRequired,
onSelect: PropTypes.func.isRequired,
};
render() {
this.select = [{ value: '', label: 'Autoselect instances' }];
const instances = this.props.instances;
let name;
Object.keys(instances).forEach((key) => {
name = instances[key].name;
if (name !== undefined)
this.select.push({ value: key, label: instances[key].name });
});
return (
<Select
options={this.select}
onChange={this.props.onSelect}
value={this.props.selected || ''}
/>
);
}
}
function mapStateToProps(state) {
return {
selected: state.instances.selected,
instances: state.instances.options,
};
}
function mapDispatchToProps(dispatch) {
return {
onSelect: bindActionCreators(selectInstance, dispatch),
};
}
export default connect(mapStateToProps, mapDispatchToProps)(InstanceSelector);

View File

@ -0,0 +1,43 @@
import React, { Component } from 'react';
import { connect, ResolveThunks } from 'react-redux';
import { Select } from 'devui';
import { selectInstance } from '../actions';
import { StoreState } from '../reducers';
type StateProps = ReturnType<typeof mapStateToProps>;
type DispatchProps = ResolveThunks<typeof actionCreators>;
type Props = StateProps & DispatchProps;
class InstanceSelector extends Component<Props> {
select?: { readonly value: string; readonly label: string }[];
render() {
this.select = [{ value: '', label: 'Autoselect instances' }];
const instances = this.props.instances;
let name;
Object.keys(instances).forEach((key) => {
name = instances[key].name;
if (name !== undefined) this.select!.push({ value: key, label: name });
});
return (
<Select
options={this.select}
// TODO Where's the type-checking?
onChange={this.props.onSelect}
value={this.props.selected || ''}
/>
);
}
}
const mapStateToProps = (state: StoreState) => ({
selected: state.instances.selected,
instances: state.instances.options,
});
const actionCreators = {
onSelect: selectInstance,
};
export default connect(mapStateToProps, actionCreators)(InstanceSelector);

View File

@ -1,45 +0,0 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import { Tabs } from 'devui';
import { monitors } from '../utils/getMonitor';
import { selectMonitor } from '../actions';
class MonitorSelector extends Component {
static propTypes = {
selected: PropTypes.string,
selectMonitor: PropTypes.func.isRequired,
};
shouldComponentUpdate(nextProps) {
return nextProps.selected !== this.props.selected;
}
render() {
return (
<Tabs
main
collapsible
position="center"
tabs={monitors}
onClick={this.props.selectMonitor}
selected={this.props.selected || 'InspectorMonitor'}
/>
);
}
}
function mapStateToProps(state) {
return {
selected: state.monitor.selected,
};
}
function mapDispatchToProps(dispatch) {
return {
selectMonitor: bindActionCreators(selectMonitor, dispatch),
};
}
export default connect(mapStateToProps, mapDispatchToProps)(MonitorSelector);

View File

@ -0,0 +1,39 @@
import React, { Component } from 'react';
import { connect, ResolveThunks } from 'react-redux';
import { Tabs } from 'devui';
import { monitors } from '../utils/getMonitor';
import { selectMonitor } from '../actions';
import { StoreState } from '../reducers';
type StateProps = ReturnType<typeof mapStateToProps>;
type DispatchProps = ResolveThunks<typeof actionCreators>;
type Props = StateProps & DispatchProps;
class MonitorSelector extends Component<Props> {
shouldComponentUpdate(nextProps: Props) {
return nextProps.selected !== this.props.selected;
}
render() {
return (
<Tabs
main
collapsible
position="center"
tabs={monitors}
onClick={this.props.selectMonitor}
selected={this.props.selected || 'InspectorMonitor'}
/>
);
}
}
const mapStateToProps = (state: StoreState) => ({
selected: state.monitor.selected,
});
const actionCreators = {
selectMonitor,
};
export default connect(mapStateToProps, actionCreators)(MonitorSelector);

View File

@ -1,11 +1,32 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import { connect, ResolveThunks } from 'react-redux';
import { Container, Form } from 'devui';
import { saveSocketSettings } from '../../actions';
import {
JSONSchema7,
JSONSchema7Definition,
JSONSchema7Type,
JSONSchema7TypeName,
} from 'json-schema';
import { ConnectionType, saveSocketSettings } from '../../actions';
import { StoreState } from '../../reducers';
import { ConnectionOptions } from '../../reducers/connection';
import { IChangeEvent, ISubmitEvent } from '@rjsf/core';
const defaultSchema = {
declare module 'json-schema' {
export interface JSONSchema7 {
enumNames?: JSONSchema7Type[];
}
}
interface Schema {
type: JSONSchema7TypeName;
required?: string[];
properties: {
[key: string]: JSONSchema7Definition;
};
}
const defaultSchema: Schema = {
type: 'object',
required: [],
properties: {
@ -37,37 +58,24 @@ const uiSchema = {
},
};
class Connection extends Component {
static propTypes = {
saveSettings: PropTypes.func.isRequired,
options: PropTypes.object.isRequired,
type: PropTypes.string,
};
type StateProps = ReturnType<typeof mapStateToProps>;
type DispatchProps = ResolveThunks<typeof actionCreators>;
type Props = StateProps & DispatchProps;
constructor(props) {
super(props);
this.state = this.setFormData(props.type);
}
interface FormData extends ConnectionOptions {
readonly type: ConnectionType;
}
shouldComponentUpdate(nextProps, nextState) {
return this.state !== nextState;
}
interface State {
readonly formData: FormData;
readonly type: ConnectionType;
readonly schema: Schema;
readonly changed: boolean | undefined;
}
UNSAFE_componentWillReceiveProps(nextProps) {
if (this.props.options !== nextProps.options) {
this.setState({
formData: { ...nextProps.options, type: nextProps.type },
});
}
}
handleSave = (data) => {
this.props.saveSettings(data.formData);
this.setState({ changed: false });
};
setFormData = (type, changed) => {
let schema;
export class Connection extends Component<Props, State> {
setFormData = (type: ConnectionType, changed?: boolean) => {
let schema: Schema;
if (type !== 'custom') {
schema = {
type: 'object',
@ -87,7 +95,26 @@ class Connection extends Component {
};
};
handleChange = (data) => {
state: State = this.setFormData(this.props.type);
shouldComponentUpdate(nextProps: Props, nextState: State) {
return this.state !== nextState;
}
UNSAFE_componentWillReceiveProps(nextProps: Props) {
if (this.props.options !== nextProps.options) {
this.setState({
formData: { ...nextProps.options, type: nextProps.type },
});
}
}
handleSave = (data: ISubmitEvent<FormData>) => {
this.props.saveSettings(data.formData);
this.setState({ changed: false });
};
handleChange = (data: IChangeEvent<FormData>) => {
const formData = data.formData;
const type = formData.type;
if (type !== this.state.type) {
@ -119,14 +146,10 @@ class Connection extends Component {
}
}
function mapStateToProps(state) {
return state.connection;
}
const mapStateToProps = (state: StoreState) => state.connection;
function mapDispatchToProps(dispatch) {
return {
saveSettings: bindActionCreators(saveSocketSettings, dispatch),
};
}
const actionCreators = {
saveSettings: saveSocketSettings,
};
export default connect(mapStateToProps, mapDispatchToProps)(Connection);
export default connect(mapStateToProps, actionCreators)(Connection);

View File

@ -1,17 +1,15 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import { connect, ResolveThunks } from 'react-redux';
import { Container, Form } from 'devui';
import { listSchemes, listThemes } from 'devui/lib/utils/theme';
import { changeTheme } from '../../actions';
import { StoreState } from '../../reducers';
class Themes extends Component {
static propTypes = {
changeTheme: PropTypes.func.isRequired,
theme: PropTypes.object.isRequired,
};
type StateProps = ReturnType<typeof mapStateToProps>;
type DispatchProps = ResolveThunks<typeof actionCreators>;
type Props = StateProps & DispatchProps;
export class Themes extends Component<Props> {
render() {
const theme = this.props.theme;
const formData = {
@ -49,16 +47,12 @@ class Themes extends Component {
}
}
function mapStateToProps(state) {
return {
theme: state.theme,
};
}
const mapStateToProps = (state: StoreState) => ({
theme: state.theme,
});
function mapDispatchToProps(dispatch) {
return {
changeTheme: bindActionCreators(changeTheme, dispatch),
};
}
const actionCreators = {
changeTheme,
};
export default connect(mapStateToProps, mapDispatchToProps)(Themes);
export default connect(mapStateToProps, actionCreators)(Themes);

View File

@ -1,32 +0,0 @@
import React, { Component } from 'react';
import { Tabs } from 'devui';
import Connection from './Connection';
import Themes from './Themes';
class Settings extends Component {
constructor(props) {
super(props);
this.tabs = [
{ name: 'Connection', component: Connection },
{ name: 'Themes', component: Themes },
];
this.state = { selected: 'Connection' };
}
handleSelect = (selected) => {
this.setState({ selected });
};
render() {
return (
<Tabs
toRight
tabs={this.tabs}
selected={this.state.selected}
onClick={this.handleSelect}
/>
);
}
}
export default Settings;

View File

@ -0,0 +1,34 @@
import React, { Component } from 'react';
import { Tabs } from 'devui';
import Connection from './Connection';
import Themes from './Themes';
interface State {
selected: string;
}
// eslint-disable-next-line @typescript-eslint/ban-types
class Settings extends Component<{}, State> {
tabs = [
{ name: 'Connection', component: Connection },
{ name: 'Themes', component: Themes },
];
state: State = { selected: 'Connection' };
handleSelect = (selected: string) => {
this.setState({ selected });
};
render() {
return (
// eslint-disable-next-line @typescript-eslint/ban-types
<Tabs<{}>
tabs={this.tabs as any}
selected={this.state.selected}
onClick={this.handleSelect}
/>
);
}
}
export default Settings;

View File

@ -1,16 +1,25 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { ActionCreators } from 'redux-devtools-instrument';
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 { Options, State } from '../reducers/instances';
// eslint-disable-next-line @typescript-eslint/unbound-method
const { reset, rollback, commit, sweep } = ActionCreators;
export default class TopButtons extends Component {
interface Props {
dispatch: (action: LiftedAction<unknown, Action<unknown>, unknown>) => void;
liftedState: State;
options: Options;
}
export default class TopButtons extends Component<Props> {
static propTypes = {
// shouldSync: PropTypes.bool,
liftedState: PropTypes.object.isRequired,
@ -18,7 +27,7 @@ export default class TopButtons extends Component {
options: PropTypes.object.isRequired,
};
shouldComponentUpdate(nextProps) {
shouldComponentUpdate(nextProps: Props) {
return (
nextProps.options !== this.props.options ||
nextProps.liftedState !== this.props.liftedState

View File

@ -1,18 +1,17 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import { connect, ResolveThunks } from 'react-redux';
import { Button } from 'devui';
import { FaTerminal } from 'react-icons/fa';
import { toggleDispatcher } from '../../actions';
class DispatcherButton extends Component {
static propTypes = {
dispatcherIsOpen: PropTypes.bool,
toggleDispatcher: PropTypes.func.isRequired,
};
type DispatchProps = ResolveThunks<typeof actionCreators>;
interface OwnProps {
dispatcherIsOpen: boolean;
}
type Props = DispatchProps & OwnProps;
shouldComponentUpdate(nextProps) {
class DispatcherButton extends Component<Props> {
shouldComponentUpdate(nextProps: Props) {
return nextProps.dispatcherIsOpen !== this.props.dispatcherIsOpen;
}
@ -32,10 +31,8 @@ class DispatcherButton extends Component {
}
}
function mapDispatchToProps(dispatch) {
return {
toggleDispatcher: bindActionCreators(toggleDispatcher, dispatch),
};
}
const actionCreators = {
toggleDispatcher,
};
export default connect(null, mapDispatchToProps)(DispatcherButton);
export default connect(null, actionCreators)(DispatcherButton);

View File

@ -1,33 +0,0 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import { Button } from 'devui';
import { TiDownload } from 'react-icons/ti';
import { exportState } from '../../actions';
class ExportButton extends Component {
static propTypes = {
exportState: PropTypes.func.isRequired,
};
shouldComponentUpdate() {
return false;
}
render() {
return (
<Button title="Export to a file" onClick={this.props.exportState}>
<TiDownload />
</Button>
);
}
}
function mapDispatchToProps(dispatch) {
return {
exportState: bindActionCreators(exportState, dispatch),
};
}
export default connect(null, mapDispatchToProps)(ExportButton);

View File

@ -0,0 +1,28 @@
import React, { Component } from 'react';
import { connect, ResolveThunks } from 'react-redux';
import { Button } from 'devui';
import { TiDownload } from 'react-icons/ti';
import { exportState } from '../../actions';
type DispatchProps = ResolveThunks<typeof actionCreators>;
type Props = DispatchProps;
class ExportButton extends Component<Props> {
shouldComponentUpdate() {
return false;
}
render() {
return (
<Button title="Export to a file" onClick={this.props.exportState}>
<TiDownload />
</Button>
);
}
}
const actionCreators = {
exportState,
};
export default connect(null, actionCreators)(ExportButton);

View File

@ -1,64 +0,0 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import { Button } from 'devui';
import { TiUpload } from 'react-icons/ti';
import { importState } from '../../actions';
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);
}
shouldComponentUpdate() {
return false;
}
mapRef(node) {
this.fileInput = node;
}
handleImport() {
this.fileInput.click();
}
handleImportFile(e) {
const file = e.target.files[0];
const reader = new FileReader();
reader.onload = () => {
this.props.importState(reader.result);
};
reader.readAsText(file);
e.target.value = ''; // eslint-disable-line no-param-reassign
}
render() {
return (
<Button title="Import from a file" onClick={this.handleImport}>
<TiUpload />
<input
type="file"
ref={this.mapRef}
style={{ display: 'none' }}
onChange={this.handleImportFile}
/>
</Button>
);
}
}
function mapDispatchToProps(dispatch) {
return {
importState: bindActionCreators(importState, dispatch),
};
}
export default connect(null, mapDispatchToProps)(ImportButton);

View File

@ -0,0 +1,54 @@
import React, { ChangeEventHandler, Component, RefCallback } from 'react';
import { connect, ResolveThunks } from 'react-redux';
import { Button } from 'devui';
import { TiUpload } from 'react-icons/ti';
import { importState } from '../../actions';
type DispatchProps = ResolveThunks<typeof actionCreators>;
type Props = DispatchProps;
class ImportButton extends Component<Props> {
fileInput?: HTMLInputElement | null;
shouldComponentUpdate() {
return false;
}
mapRef: RefCallback<HTMLInputElement> = (node) => {
this.fileInput = node;
};
handleImport = () => {
this.fileInput!.click();
};
handleImportFile: ChangeEventHandler<HTMLInputElement> = (e) => {
const file = e.target.files![0];
const reader = new FileReader();
reader.onload = () => {
this.props.importState(reader.result as string);
};
reader.readAsText(file);
e.target.value = '';
};
render() {
return (
<Button title="Import from a file" onClick={this.handleImport}>
<TiUpload />
<input
type="file"
ref={this.mapRef}
style={{ display: 'none' }}
onChange={this.handleImportFile}
/>
</Button>
);
}
}
const actionCreators = {
importState,
};
export default connect(null, actionCreators)(ImportButton);

View File

@ -1,18 +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 } from '../../actions';
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;
}
@ -31,7 +32,10 @@ class LockButton extends Component {
}
}
function mapDispatchToProps(dispatch, ownProps) {
function mapDispatchToProps(
dispatch: Dispatch<StoreAction>,
ownProps: OwnProps
) {
return {
lockChanges: () => dispatch(lockChanges(!ownProps.locked)),
};

View File

@ -1,26 +1,26 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import { connect, ResolveThunks } from 'react-redux';
import { Button } from 'devui';
import { FaThumbtack } from 'react-icons/fa';
import { togglePersist } from '../../actions';
import { StoreState } from '../../reducers';
class LockButton extends Component {
static propTypes = {
persisted: PropTypes.bool,
disabled: PropTypes.bool,
onClick: PropTypes.func.isRequired,
};
type StateProps = ReturnType<typeof mapStateToProps>;
type DispatchProps = ResolveThunks<typeof actionCreators>;
interface OwnProps {
disabled?: boolean;
}
type Props = StateProps & DispatchProps & OwnProps;
shouldComponentUpdate(nextProps) {
class LockButton extends Component<Props> {
shouldComponentUpdate(nextProps: Props) {
return nextProps.persisted !== this.props.persisted;
}
render() {
return (
<Button
toolbar
tooltipPosition="bottom"
disabled={this.props.disabled}
mark={this.props.persisted && 'base0D'}
@ -37,16 +37,12 @@ class LockButton extends Component {
}
}
function mapStateToProps(state) {
return {
persisted: state.instances.persisted,
};
}
const mapStateToProps = (state: StoreState) => ({
persisted: state.instances.persisted,
});
function mapDispatchToProps(dispatch) {
return {
onClick: bindActionCreators(togglePersist, dispatch),
};
}
const actionCreators = {
onClick: togglePersist,
};
export default connect(mapStateToProps, mapDispatchToProps)(LockButton);
export default connect(mapStateToProps, actionCreators)(LockButton);

View File

@ -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 (

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,18 +1,17 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import { connect, ResolveThunks } from 'react-redux';
import { Button } from 'devui';
import { MdAvTimer } from 'react-icons/md';
import { toggleSlider } from '../../actions';
class SliderButton extends Component {
static propTypes = {
isOpen: PropTypes.bool,
toggleSlider: PropTypes.func.isRequired,
};
type DispatchProps = ResolveThunks<typeof actionCreators>;
interface OwnProps {
isOpen: boolean;
}
type Props = DispatchProps & OwnProps;
shouldComponentUpdate(nextProps) {
class SliderButton extends Component<Props> {
shouldComponentUpdate(nextProps: Props) {
return nextProps.isOpen !== this.props.isOpen;
}
@ -30,10 +29,8 @@ class SliderButton extends Component {
}
}
function mapDispatchToProps(dispatch) {
return {
toggleSlider: bindActionCreators(toggleSlider, dispatch),
};
}
const actionCreators = {
toggleSlider,
};
export default connect(null, mapDispatchToProps)(SliderButton);
export default connect(null, actionCreators)(SliderButton);

View File

@ -1,45 +0,0 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import { Button } from 'devui';
import { TiArrowSync } from 'react-icons/ti';
import { toggleSync } from '../../actions';
class SyncButton extends Component {
static propTypes = {
sync: PropTypes.bool,
onClick: PropTypes.func.isRequired,
};
shouldComponentUpdate(nextProps) {
return nextProps.sync !== this.props.sync;
}
render() {
return (
<Button
title="Sync actions"
tooltipPosition="bottom-left"
onClick={this.props.onClick}
mark={this.props.sync && 'base0B'}
>
<TiArrowSync />
</Button>
);
}
}
function mapStateToProps(state) {
return {
sync: state.instances.sync,
};
}
function mapDispatchToProps(dispatch) {
return {
onClick: bindActionCreators(toggleSync, dispatch),
};
}
export default connect(mapStateToProps, mapDispatchToProps)(SyncButton);

View File

@ -0,0 +1,39 @@
import React, { Component } from 'react';
import { connect, ResolveThunks } from 'react-redux';
import { Button } from 'devui';
import { TiArrowSync } from 'react-icons/ti';
import { toggleSync } from '../../actions';
import { StoreState } from '../../reducers';
type StateProps = ReturnType<typeof mapStateToProps>;
type DispatchProps = ResolveThunks<typeof actionCreators>;
type Props = StateProps & DispatchProps;
class SyncButton extends Component<Props> {
shouldComponentUpdate(nextProps: Props) {
return nextProps.sync !== this.props.sync;
}
render() {
return (
<Button
title="Sync actions"
tooltipPosition="bottom-left"
onClick={this.props.onClick}
mark={this.props.sync && 'base0B'}
>
<TiArrowSync />
</Button>
);
}
}
const mapStateToProps = (state: StoreState) => ({
sync: state.instances.sync,
});
const actionCreators = {
onClick: toggleSync,
};
export default connect(mapStateToProps, actionCreators)(SyncButton);

View File

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

View File

@ -1,7 +1,5 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import { connect, ResolveThunks } from 'react-redux';
import { Container } from 'devui';
import SliderMonitor from './monitors/Slider';
import { liftedDispatch as liftedDispatchAction, getReport } from '../actions';
@ -10,8 +8,13 @@ import DevTools from '../containers/DevTools';
import Dispatcher from './monitors/Dispatcher';
import TopButtons from '../components/TopButtons';
import BottomButtons from '../components/BottomButtons';
import { StoreState } from '../reducers';
class Actions extends Component {
type StateProps = ReturnType<typeof mapStateToProps>;
type DispatchProps = ResolveThunks<typeof actionCreators>;
type Props = StateProps & DispatchProps;
class Actions extends Component<Props> {
render() {
const {
monitor,
@ -51,17 +54,7 @@ class Actions extends Component {
}
}
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,
};
function mapStateToProps(state) {
const mapStateToProps = (state: StoreState) => {
const instances = state.instances;
const id = getActiveInstance(instances);
return {
@ -73,13 +66,11 @@ function mapStateToProps(state) {
sliderIsOpen: state.monitor.sliderIsOpen,
reports: state.reports.data,
};
}
};
function mapDispatchToProps(dispatch) {
return {
liftedDispatch: bindActionCreators(liftedDispatchAction, dispatch),
getReport: bindActionCreators(getReport, dispatch),
};
}
const actionCreators = {
liftedDispatch: liftedDispatchAction,
getReport,
};
export default connect(mapStateToProps, mapDispatchToProps)(Actions);
export default connect(mapStateToProps, actionCreators)(Actions);

View File

@ -1,14 +1,17 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import { connect, ResolveThunks } from 'react-redux';
import { Container, Notification } from 'devui';
import { clearNotification } from '../actions';
import Header from '../components/Header';
import Actions from '../containers/Actions';
import Settings from '../components/Settings';
import { StoreState } from '../reducers';
class App extends Component {
type StateProps = ReturnType<typeof mapStateToProps>;
type DispatchProps = ResolveThunks<typeof actionCreators>;
type Props = StateProps & DispatchProps;
class App extends Component<Props> {
render() {
const { section, theme, notification } = this.props;
let body;
@ -37,28 +40,14 @@ class App extends Component {
}
}
App.propTypes = {
section: PropTypes.string.isRequired,
theme: PropTypes.object.isRequired,
notification: PropTypes.shape({
message: PropTypes.string,
type: PropTypes.string,
}),
clearNotification: PropTypes.func,
const mapStateToProps = (state: StoreState) => ({
section: state.section,
theme: state.theme,
notification: state.notification,
});
const actionCreators = {
clearNotification,
};
function mapStateToProps(state) {
return {
section: state.section,
theme: state.theme,
notification: state.notification,
};
}
function mapDispatchToProps(dispatch) {
return {
clearNotification: bindActionCreators(clearNotification, dispatch),
};
}
export default connect(mapStateToProps, mapDispatchToProps)(App);
export default connect(mapStateToProps, actionCreators)(App);

View File

@ -1,20 +1,49 @@
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 getMonitor from '../utils/getMonitor';
import { InitMonitorAction } from '../actions';
import { Features, State } from '../reducers/instances';
import { MonitorStateMonitorState } from '../reducers/monitor';
import { ThemeFromProvider } from 'devui';
class DevTools extends Component {
constructor(props) {
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> {
monitorProps: unknown;
Monitor?: React.ComponentType<
LiftedState<unknown, Action<unknown>, unknown>
> & {
update(
monitorProps: unknown,
state: unknown | undefined,
action: Action<unknown>
): unknown;
};
preventRender?: boolean;
constructor(props: Props) {
super(props);
this.getMonitor(props, props.monitorState);
}
getMonitor(props, skipUpdate) {
getMonitor(props: Props, skipUpdate?: unknown) {
const monitorElement = getMonitor(props);
this.monitorProps = monitorElement.props;
this.Monitor = monitorElement.type;
const update = this.Monitor.update;
// eslint-disable-next-line @typescript-eslint/unbound-method
const update = this.Monitor!.update;
if (update) {
let newMonitorState;
const monitorState = props.monitorState;
@ -24,7 +53,11 @@ class DevTools extends Component {
) {
newMonitorState = monitorState;
} else {
newMonitorState = update(this.monitorProps, undefined, {});
newMonitorState = update(
this.monitorProps,
undefined,
{} as Action<unknown>
);
if (newMonitorState !== monitorState) {
this.preventRender = true;
}
@ -38,21 +71,23 @@ class DevTools extends Component {
}
}
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
);
}
dispatch = (action) => {
dispatch = (
action: LiftedAction<unknown, Action<unknown>, unknown> | InitMonitorAction
) => {
this.props.dispatch(action);
};
@ -66,9 +101,10 @@ class DevTools extends Component {
...this.props.liftedState,
monitorState: this.props.monitorState,
};
const MonitorAsAny = this.Monitor as any;
return (
<div className={`monitor monitor-${this.props.monitor}`}>
<this.Monitor
<MonitorAsAny
{...liftedState}
{...this.monitorProps}
features={this.props.features}
@ -80,13 +116,4 @@ class DevTools extends Component {
}
}
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

@ -1,25 +1,27 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import { connect, ResolveThunks } from 'react-redux';
import ChartMonitor from 'redux-devtools-chart-monitor';
import { NodeWithId } from 'd3-state-visualizer';
import { selectMonitorWithState } from '../../actions';
export function getPath(obj, inspectedStatePath) {
export function getPath(obj: NodeWithId, inspectedStatePath: string[]) {
const parent = obj.parent;
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);
}
class ChartMonitorWrapper extends Component {
type DispatchProps = ResolveThunks<typeof actionCreators>;
type Props = DispatchProps;
class ChartMonitorWrapper extends Component<Props> {
static update = ChartMonitor.update;
onClickText = (data) => {
const inspectedStatePath = [];
onClickText = (data: NodeWithId) => {
const inspectedStatePath: string[] = [];
getPath(data, inspectedStatePath);
this.props.selectMonitorWithState('InspectorMonitor', {
inspectedStatePath,
@ -33,6 +35,8 @@ class ChartMonitorWrapper extends Component {
render() {
return (
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
<ChartMonitor
defaultIsVisible
invertTheme
@ -43,17 +47,8 @@ class ChartMonitorWrapper extends Component {
}
}
ChartMonitorWrapper.propTypes = {
selectMonitorWithState: PropTypes.func.isRequired,
const actionCreators = {
selectMonitorWithState: selectMonitorWithState,
};
function mapDispatchToProps(dispatch) {
return {
selectMonitorWithState: bindActionCreators(
selectMonitorWithState,
dispatch
),
};
}
export default connect(null, mapDispatchToProps)(ChartMonitorWrapper);
export default connect(null, actionCreators)(ChartMonitorWrapper);

View File

@ -1,12 +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 { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import { connect, ResolveThunks } from 'react-redux';
import { dispatchRemotely } from '../../actions';
import { Options } from '../../reducers/instances';
export const DispatcherContainer = styled.div`
display: flex;
@ -47,13 +46,22 @@ export const ActionContainer = styled.div`
}
`;
class Dispatcher extends Component {
static propTypes = {
options: PropTypes.object.isRequired,
dispatch: PropTypes.func.isRequired,
};
type DispatchProps = ResolveThunks<typeof actionCreators>;
interface OwnProps {
options: Options;
}
type Props = DispatchProps & OwnProps;
state = {
interface State {
selected: 'default' | number;
customAction: string;
args: (string | undefined)[];
rest: string;
changed: boolean;
}
class Dispatcher extends Component<Props, State> {
state: State = {
selected: 'default',
customAction:
this.props.options.lib === 'redux' ? "{\n type: ''\n}" : 'this.',
@ -62,7 +70,7 @@ class Dispatcher extends Component {
changed: false,
};
UNSAFE_componentWillReceiveProps(nextProps) {
UNSAFE_componentWillReceiveProps(nextProps: Props) {
if (
this.state.selected !== 'default' &&
!nextProps.options.actionCreators
@ -74,14 +82,14 @@ class Dispatcher extends Component {
}
}
shouldComponentUpdate(nextProps, nextState) {
shouldComponentUpdate(nextProps: Props, nextState: State) {
return (
nextState !== this.state ||
nextProps.options.actionCreators !== this.props.options.actionCreators
);
}
selectActionCreator = (selected) => {
selectActionCreator = (selected: 'default' | 'actions-help' | number) => {
if (selected === 'actions-help') {
window.open(
'https://github.com/zalmoxisus/redux-devtools-extension/blob/master/docs/' +
@ -90,14 +98,14 @@ class Dispatcher extends Component {
return;
}
const args = [];
const args: string[] = [];
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 });
};
handleArg = (argIndex) => (value) => {
handleArg = (argIndex: number) => (value: string) => {
const args = [
...this.state.args.slice(0, argIndex),
value || undefined,
@ -106,26 +114,26 @@ class Dispatcher extends Component {
this.setState({ args, changed: true });
};
handleRest = (rest) => {
handleRest = (rest: string) => {
this.setState({ rest, changed: true });
};
handleCustomAction = (customAction) => {
handleCustomAction = (customAction: string) => {
this.setState({ customAction, changed: true });
};
dispatchAction = () => {
const { selected, customAction, args, rest } = this.state;
if (this.state.selected !== 'default') {
if (selected !== 'default') {
// remove trailing `undefined` arguments
let i = args.length - 1;
while (i >= 0 && typeof args[i] === 'undefined') {
args.pop(i);
args.pop();
i--;
}
this.props.dispatch({
name: this.props.options.actionCreators[selected].name,
name: this.props.options.actionCreators![selected].name,
selected,
args,
rest,
@ -174,7 +182,9 @@ class Dispatcher extends Component {
);
}
let options = [{ value: 'default', label: 'Custom action' }];
let options: { value: string | number; label: string }[] = [
{ value: 'default', label: 'Custom action' },
];
if (actionCreators && actionCreators.length > 0) {
options = options.concat(
actionCreators.map(({ name, args }, i) => ({
@ -208,10 +218,8 @@ class Dispatcher extends Component {
}
}
function mapDispatchToProps(dispatch) {
return {
dispatch: bindActionCreators(dispatchRemotely, dispatch),
};
}
const actionCreators = {
dispatch: dispatchRemotely,
};
export default connect(null, mapDispatchToProps)(Dispatcher);
export default connect(null, actionCreators)(Dispatcher);

View File

@ -1,18 +1,28 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import React, { Component, RefCallback } from 'react';
import { connect, ResolveThunks } from 'react-redux';
import { withTheme } from 'styled-components';
import { tree } from 'd3-state-visualizer';
import { InputOptions, NodeWithId, tree } from 'd3-state-visualizer';
import { getPath } from '../ChartMonitorWrapper';
import { updateMonitorState } from '../../../actions';
import { ThemeFromProvider } from 'devui';
const style = {
width: '100%',
height: '100%',
};
class ChartTab extends Component {
type DispatchProps = ResolveThunks<typeof actionCreators>;
interface OwnProps {
data: unknown;
theme: ThemeFromProvider;
}
type Props = DispatchProps & OwnProps;
class ChartTab extends Component<Props> {
node?: HTMLDivElement | null;
// eslint-disable-next-line @typescript-eslint/ban-types
renderChart?: (nextState?: {} | null | undefined) => void;
shouldComponentUpdate() {
return false;
}
@ -21,28 +31,30 @@ class ChartTab extends Component {
this.createChart(this.props);
}
UNSAFE_componentWillReceiveProps(nextProps) {
UNSAFE_componentWillReceiveProps(nextProps: Props) {
if (
this.props.theme.scheme !== nextProps.theme.scheme ||
nextProps.theme.light !== this.props.theme.light
) {
this.node.innerHTML = '';
this.node!.innerHTML = '';
this.createChart(nextProps);
} else if (nextProps.data !== this.props.data) {
this.renderChart(nextProps.data);
// eslint-disable-next-line @typescript-eslint/ban-types
this.renderChart!(nextProps.data as {} | null | undefined);
}
}
getRef = (node) => {
getRef: RefCallback<HTMLDivElement> = (node) => {
this.node = node;
};
createChart(props) {
this.renderChart = tree(this.node, this.getChartTheme(props.theme));
this.renderChart(props.data);
createChart(props: Props) {
this.renderChart = tree(this.node!, this.getChartTheme(props.theme));
// eslint-disable-next-line @typescript-eslint/ban-types
this.renderChart(props.data as {} | null | undefined);
}
getChartTheme(theme) {
getChartTheme(theme: ThemeFromProvider): Partial<InputOptions> {
return {
heightBetweenNodesCoeff: 1,
widthBetweenNodesCoeff: 1.3,
@ -60,27 +72,27 @@ class ChartTab extends Component {
style: {
width: '100%',
height: '100%',
node: {
node: ({
colors: {
default: theme.base0B,
collapsed: theme.base0B,
parent: theme.base0E,
},
radius: 7,
},
text: {
} as unknown) as string,
text: ({
colors: {
default: theme.base0D,
hover: theme.base06,
},
},
} as unknown) as string,
},
onClickText: this.onClickText,
};
}
onClickText = (data) => {
const inspectedStatePath = [];
onClickText = (data: NodeWithId) => {
const inspectedStatePath: string[] = [];
getPath(data, inspectedStatePath);
this.props.updateMonitorState({
inspectedStatePath,
@ -93,17 +105,9 @@ class ChartTab extends Component {
}
}
ChartTab.propTypes = {
data: PropTypes.object,
updateMonitorState: PropTypes.func.isRequired,
theme: PropTypes.object.isRequired,
const actionCreators = {
updateMonitorState,
};
function mapDispatchToProps(dispatch) {
return {
updateMonitorState: bindActionCreators(updateMonitorState, dispatch),
};
}
const ConnectedChartTab = connect(null, mapDispatchToProps)(ChartTab);
const ConnectedChartTab = connect(null, actionCreators)(ChartTab);
export default withTheme(ConnectedChartTab);

View File

@ -2,21 +2,27 @@ import React, { Component } from 'react';
import { Editor } from 'devui';
import { stringify } from 'javascript-stringify';
export default class RawTab extends Component {
constructor(props) {
interface Props {
data: unknown;
}
export default class RawTab extends Component<Props> {
value?: string | undefined;
constructor(props: Props) {
super(props);
this.stringifyData(props);
}
shouldComponentUpdate(nextProps) {
shouldComponentUpdate(nextProps: Props) {
return nextProps.data !== this.value;
}
UNSAFE_componentWillUpdate(nextProps) {
UNSAFE_componentWillUpdate(nextProps: Props) {
this.stringifyData(nextProps);
}
stringifyData(props) {
stringifyData(props: Props) {
this.value = stringify(props.data, null, 2);
}

View File

@ -1,8 +1,8 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import { Tabs } from 'devui';
import { connect, ResolveThunks } from 'react-redux';
import { Tab, Tabs } from 'devui';
import { TabComponentProps } from 'redux-devtools-inspector-monitor';
import { Action } from 'redux';
import StateTree from 'redux-devtools-inspector-monitor/lib/tabs/StateTab';
import ActionTree from 'redux-devtools-inspector-monitor/lib/tabs/ActionTab';
import DiffTree from 'redux-devtools-inspector-monitor/lib/tabs/DiffTab';
@ -10,14 +10,24 @@ import { selectMonitorTab } from '../../../actions';
import RawTab from './RawTab';
import ChartTab from './ChartTab';
import VisualDiffTab from './VisualDiffTab';
import { StoreState } from '../../../reducers';
import { Delta } from 'jsondiffpatch';
class SubTabs extends Component {
constructor(props) {
type StateProps = ReturnType<typeof mapStateToProps>;
type DispatchProps = ResolveThunks<typeof actionCreators>;
type Props = StateProps &
DispatchProps &
TabComponentProps<unknown, Action<unknown>>;
class SubTabs extends Component<Props> {
tabs?: (Tab<Props> | Tab<{ data: unknown }> | Tab<{ data?: Delta }>)[];
constructor(props: Props) {
super(props);
this.updateTabs(props);
}
UNSAFE_componentWillReceiveProps(nextProps) {
UNSAFE_componentWillReceiveProps(nextProps: Props) {
if (nextProps.parentTab !== this.props.parentTab) {
this.updateTabs(nextProps);
}
@ -34,7 +44,7 @@ class SubTabs extends Component {
}
};
updateTabs(props) {
updateTabs(props: Props) {
const parentTab = props.parentTab;
if (parentTab === 'Diff') {
@ -47,7 +57,7 @@ class SubTabs extends Component {
{
name: 'Raw',
component: VisualDiffTab,
selector: this.selector,
selector: this.selector as () => { data?: Delta },
},
];
return;
@ -79,7 +89,7 @@ class SubTabs extends Component {
return (
<Tabs
tabs={this.tabs}
tabs={this.tabs! as any}
selected={selected || 'Tree'}
onClick={this.props.selectMonitorTab}
/>
@ -87,26 +97,13 @@ class SubTabs extends Component {
}
}
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,
});
const actionCreators = {
selectMonitorTab,
};
function mapStateToProps(state) {
return {
parentTab: state.monitor.monitorState.tabName,
selected: state.monitor.monitorState.subTabName,
};
}
function mapDispatchToProps(dispatch) {
return {
selectMonitorTab: bindActionCreators(selectMonitorTab, dispatch),
};
}
export default connect(mapStateToProps, mapDispatchToProps)(SubTabs);
export default connect(mapStateToProps, actionCreators)(SubTabs);

View File

@ -1,6 +1,5 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { formatters } from 'jsondiffpatch';
import { Delta, formatters } from 'jsondiffpatch';
import styled from 'styled-components';
import { effects } from 'devui';
@ -218,22 +217,22 @@ export const StyledContainer = styled.div`
}
`;
export default class VisualDiffTab extends Component {
shouldComponentUpdate(nextProps) {
interface Props {
data?: Delta;
}
export default class VisualDiffTab extends Component<Props> {
shouldComponentUpdate(nextProps: Props) {
return this.props.data !== nextProps.data;
}
render() {
let __html;
let __html: string | undefined;
const data = this.props.data;
if (data) {
__html = formatters.html.format(data);
__html = formatters.html.format(data, undefined);
}
return <StyledContainer dangerouslySetInnerHTML={{ __html }} />;
return <StyledContainer dangerouslySetInnerHTML={{ __html: __html! }} />;
}
}
VisualDiffTab.propTypes = {
data: PropTypes.object,
};

View File

@ -1,10 +1,10 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import InspectorMonitor from 'redux-devtools-inspector-monitor';
import InspectorMonitor, { Tab } from 'redux-devtools-inspector-monitor';
import TraceTab from 'redux-devtools-inspector-monitor-trace-tab';
import TestTab from 'redux-devtools-inspector-monitor-test-tab';
import { DATA_TYPE_KEY } from '../../../constants/dataTypes';
import SubTabs from './SubTabs';
import { Action } from 'redux';
const DEFAULT_TABS = [
{
@ -25,25 +25,38 @@ const DEFAULT_TABS = [
},
];
class InspectorWrapper extends Component {
interface Features {
test?: boolean;
skip?: boolean;
}
interface Props {
features?: Features;
}
class InspectorWrapper extends Component<Props> {
static update = InspectorMonitor.update;
render() {
const { features, ...rest } = this.props;
let tabs;
let tabs: () => Tab<unknown, Action<unknown>>[];
if (features && features.test) {
tabs = () => [...DEFAULT_TABS, { name: 'Test', component: TestTab }];
tabs = () => [
...(DEFAULT_TABS as Tab<unknown, Action<unknown>>[]),
({ name: 'Test', component: TestTab } as unknown) as Tab<
unknown,
Action<unknown>
>,
];
} else {
tabs = () => DEFAULT_TABS;
tabs = () => DEFAULT_TABS as Tab<unknown, Action<unknown>>[];
}
return (
<InspectorMonitor
dataTypeKey={DATA_TYPE_KEY}
shouldPersistState={false}
invertTheme={false}
tabs={tabs}
hideActionButtons={!features.skip}
hideActionButtons={!features!.skip}
hideMainButtons
{...rest}
/>
@ -51,8 +64,4 @@ class InspectorWrapper extends Component {
}
}
InspectorWrapper.propTypes = {
features: PropTypes.object,
};
export default InspectorWrapper;

View File

@ -1,7 +1,10 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import styled, { withTheme } from 'styled-components';
import SliderMonitor from 'redux-devtools-slider-monitor';
import { LiftedAction } from 'redux-devtools-instrument';
import { Action } from 'redux';
import { ThemeFromProvider } from 'devui';
import { State } from '../../reducers/instances';
const SliderWrapper = styled.div`
border-color: ${(props) => props.theme.base02};
@ -9,8 +12,14 @@ const SliderWrapper = styled.div`
border-width: 1px 0;
`;
class Slider extends Component {
shouldComponentUpdate(nextProps) {
interface Props {
liftedState: State;
dispatch: (action: LiftedAction<unknown, Action<unknown>, unknown>) => void;
theme: ThemeFromProvider;
}
class Slider extends Component<Props> {
shouldComponentUpdate(nextProps: Props) {
return (
nextProps.liftedState !== this.props.liftedState ||
nextProps.theme.scheme !== this.props.theme.scheme
@ -21,6 +30,8 @@ class Slider extends Component {
<SliderWrapper>
<SliderMonitor
{...this.props.liftedState}
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
dispatch={this.props.dispatch}
theme={this.props.theme}
hideResetButton
@ -30,10 +41,4 @@ class Slider extends Component {
}
}
Slider.propTypes = {
liftedState: PropTypes.object,
dispatch: PropTypes.func.isRequired,
theme: PropTypes.object.isRequired,
};
export default withTheme(Slider);

View File

@ -1,18 +1,27 @@
import 'devui/lib/presets';
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { Provider } from 'react-redux';
import { Store } from 'redux';
import configureStore from './store/configureStore';
import { CONNECT_REQUEST } from './constants/socketActionTypes';
import App from './containers/App';
import { StoreState } from './reducers';
import { ConnectionOptions, StoreAction } from './actions';
interface Props {
socketOptions?: ConnectionOptions;
}
class Root extends Component<Props> {
store?: Store<StoreState, StoreAction>;
class Root extends Component {
UNSAFE_componentWillMount() {
configureStore((store, preloadedState) => {
this.store = store;
store.dispatch({
type: CONNECT_REQUEST,
options: preloadedState.connection || this.props.socketOptions,
options: (preloadedState!.connection ||
this.props.socketOptions) as ConnectionOptions,
});
this.forceUpdate();
});
@ -20,21 +29,13 @@ class Root extends Component {
render() {
if (!this.store) return null;
const AppAsAny = App as any;
return (
<Provider store={this.store}>
<App {...this.props} />
<AppAsAny {...this.props} />
</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;

View File

@ -1,5 +1,6 @@
import socketCluster from 'socketcluster-client';
import socketCluster, { SCClientSocket } from 'socketcluster-client';
import { stringify } from 'jsan';
import { Dispatch, MiddlewareAPI } from 'redux';
import socketOptions from '../constants/socketOptions';
import * as actions from '../constants/socketActionTypes';
import { getActiveInstance } from '../reducers/instances';
@ -12,22 +13,37 @@ import {
GET_REPORT_ERROR,
GET_REPORT_SUCCESS,
} from '../constants/actionTypes';
import { showNotification, importState } from '../actions';
import {
showNotification,
importState,
StoreAction,
EmitAction,
LiftedActionAction,
Request,
DispatchAction,
UpdateReportsRequest,
} from '../actions';
import { nonReduxDispatch } from '../utils/monitorActions';
import { StoreState } from '../reducers';
let socket;
let store;
let socket: SCClientSocket;
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 });
}
function startMonitoring(channel) {
function startMonitoring(channel: string) {
if (channel !== store.getState().socket.baseChannel) return;
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 instanceId = getActiveInstance(instances);
const id = !toAll && instances.options[instanceId].connectionId;
@ -39,7 +55,7 @@ function dispatchRemoteAction({ message, action, state, toAll }) {
store,
message,
instanceId,
action,
action as DispatchAction,
state,
instances
),
@ -48,7 +64,32 @@ function dispatchRemoteAction({ message, action, state, toAll }) {
});
}
function monitoring(request) {
interface RequestBase {
id?: string;
instanceId?: string;
}
interface DisconnectedAction extends RequestBase {
type: 'DISCONNECTED';
id: string;
}
interface StartAction extends RequestBase {
type: 'START';
id: string;
}
interface ErrorAction extends RequestBase {
type: 'ERROR';
payload: string;
}
interface RequestWithData extends RequestBase {
data: Request;
}
type MonitoringRequest =
| DisconnectedAction
| StartAction
| ErrorAction
| Request;
function monitoring(request: MonitoringRequest) {
if (request.type === 'DISCONNECTED') {
store.dispatch({
type: REMOVE_INSTANCE,
@ -68,7 +109,9 @@ function monitoring(request) {
store.dispatch({
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;
@ -87,11 +130,14 @@ 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 {
const watcher = (request) => {
const watcher = (request: UpdateReportsRequest) => {
store.dispatch({ type: subscription, request });
};
channel.watch(watcher);
@ -150,7 +196,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));
@ -163,7 +209,7 @@ function disconnect() {
}
function login() {
socket.emit('login', {}, (error, baseChannel) => {
socket.emit('login', {}, (error: Error, baseChannel: string) => {
if (error) {
store.dispatch({ type: actions.AUTH_ERROR, error });
return;
@ -182,24 +228,28 @@ function login() {
});
}
function getReport(reportId) {
socket.emit('getReport', reportId, (error, data) => {
if (error) {
store.dispatch({ type: GET_REPORT_ERROR, error });
return;
function getReport(reportId: unknown) {
socket.emit(
'getReport',
reportId,
(error: Error, data: { payload: string }) => {
if (error) {
store.dispatch({ type: GET_REPORT_ERROR, error });
return;
}
store.dispatch({ type: GET_REPORT_SUCCESS, data });
store.dispatch(importState(data.payload));
}
store.dispatch({ type: GET_REPORT_SUCCESS, data });
store.dispatch(importState(data.payload));
});
);
}
export default function api(inStore) {
export default function api(
inStore: MiddlewareAPI<Dispatch<StoreAction>, StoreState>
) {
store = inStore;
return (next) => (action) => {
return (next: Dispatch<StoreAction>) => (action: StoreAction) => {
const result = next(action);
switch (
action.type // eslint-disable-line default-case
) {
switch (action.type) {
case actions.CONNECT_REQUEST:
connect();
break;

View File

@ -1,14 +1,17 @@
import stringifyJSON from '../utils/stringifyJSON';
import { UPDATE_STATE, LIFTED_ACTION, EXPORT } from '../constants/actionTypes';
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 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);
@ -19,15 +22,17 @@ function download(state) {
}, 0);
}
const exportState = (store) => (next) => (action) => {
const exportState = (
store: MiddlewareAPI<Dispatch<StoreAction>, StoreState>
) => (next: Dispatch<StoreAction>) => (action: StoreAction) => {
const result = next(action);
if (
toExport &&
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;
if (id === toExport) {
toExport = undefined;
@ -35,7 +40,7 @@ const exportState = (store) => (next) => (action) => {
JSON.stringify(
{
payload: request.payload,
preloadedState: request.committedState,
preloadedState: (request as ExportRequest).committedState,
},
null,
'\t'

View File

@ -1,11 +1,22 @@
import { RECONNECT } from '../constants/socketActionTypes';
import { ConnectionType, StoreAction } from '../actions';
export interface ConnectionOptions {
readonly hostname: string;
readonly port: number;
readonly secure: boolean;
}
export interface ConnectionState {
readonly options: ConnectionOptions;
readonly type: ConnectionType;
}
export default function connection(
state = {
state: ConnectionState = {
options: { hostname: 'localhost', port: 8000, secure: false },
type: 'remotedev',
},
action
action: StoreAction
) {
if (action.type === RECONNECT) {
const { type, ...options } = action.options;

View File

@ -1,22 +0,0 @@
import { combineReducers } from 'redux';
import section from './section';
import connection from './connection';
import socket from './socket';
import monitor from './monitor';
import notification from './notification';
import instances from './instances';
import reports from './reports';
import theme from './theme';
const rootReducer = combineReducers({
section,
theme,
connection,
socket,
monitor,
instances,
reports,
notification,
});
export default rootReducer;

View File

@ -0,0 +1,34 @@
import { combineReducers } from 'redux';
import section, { SectionState } from './section';
import connection, { ConnectionState } from './connection';
import socket, { SocketState } from './socket';
import monitor, { MonitorState } from './monitor';
import notification, { NotificationState } from './notification';
import instances, { InstancesState } from './instances';
import reports, { ReportsState } from './reports';
import theme, { ThemeState } from './theme';
import { StoreAction } from '../actions';
export interface StoreState {
readonly section: SectionState;
readonly theme: ThemeState;
readonly connection: ConnectionState;
readonly socket: SocketState;
readonly monitor: MonitorState;
readonly instances: InstancesState;
readonly reports: ReportsState;
readonly notification: NotificationState;
}
const rootReducer = combineReducers<StoreState, StoreAction>({
section,
theme,
connection,
socket,
monitor,
instances,
reports,
notification,
});
export default rootReducer;

View File

@ -1,3 +1,5 @@
import { PerformAction } from 'redux-devtools-instrument';
import { Action } from 'redux';
import {
UPDATE_STATE,
SET_STATE,
@ -10,8 +12,60 @@ import {
import { DISCONNECTED } from '../constants/socketActionTypes';
import parseJSON from '../utils/parseJSON';
import { recompute } from '../utils/updateState';
import {
ActionCreator,
LiftedActionDispatchAction,
Request,
StoreAction,
} from '../actions';
export const initialState = {
export interface Features {
lock?: boolean;
export?: string | boolean;
import?: string | boolean;
persist?: boolean;
pause?: boolean;
reorder?: boolean;
jump?: boolean;
skip?: boolean;
dispatch?: boolean;
sync?: boolean;
test?: boolean;
}
export interface Options {
name?: string;
connectionId?: string;
explicitLib?: string;
lib?: string;
actionCreators?: ActionCreator[];
features: Features;
serialize?: boolean;
}
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 {
selected: string | null;
current: string;
sync: boolean;
connections: { [id: string]: string[] };
options: { [id: string]: Options };
states: { [id: string]: State };
persisted?: boolean;
}
export const initialState: InstancesState = {
selected: null,
current: 'default',
sync: false,
@ -29,35 +83,42 @@ export const initialState = {
},
};
function updateState(state, request, id, serialize) {
let payload = request.payload;
function updateState(
state: { [id: string]: State },
request: Request,
id: string,
serialize: boolean | undefined
) {
let payload: State = request.payload as State;
const actionsById = request.actionsById;
if (actionsById) {
payload = {
// eslint-disable-next-line @typescript-eslint/ban-types
...payload,
actionsById: parseJSON(actionsById, serialize),
computedStates: parseJSON(request.computedStates, serialize),
};
} as State;
if (request.type === 'STATE' && request.committedState) {
payload.committedState = payload.computedStates[0].state;
}
} else {
payload = parseJSON(payload, serialize);
payload = parseJSON((payload as unknown) as string, serialize) as State;
}
let newState;
const liftedState = state[id] || state.default;
const action = (request.action && parseJSON(request.action, serialize)) || {};
const action = ((request.action && parseJSON(request.action, serialize)) ||
{}) as PerformAction<Action<unknown>>;
switch (request.type) {
case 'INIT':
newState = recompute(state.default, payload, {
action: { type: '@@INIT' },
timestamp: action.timestamp || Date.now(),
timestamp: (action as { timestamp?: number }).timestamp || Date.now(),
});
break;
case 'ACTION': {
let isExcess = request.isExcess;
const isExcess = request.isExcess;
const nextActionId = request.nextActionId || liftedState.nextActionId + 1;
const maxAge = request.maxAge;
if (Array.isArray(action)) {
@ -66,7 +127,7 @@ function updateState(state, request, id, serialize) {
for (let i = 0; i < action.length; i++) {
newState = recompute(
newState,
request.batched ? payload : payload[i],
request.batched ? payload : ((payload as unknown) as State[])[i],
action[i],
newState.nextActionId + 1,
maxAge,
@ -148,7 +209,10 @@ function updateState(state, request, id, serialize) {
return { ...state, [id]: newState };
}
export function dispatchAction(state, { action }) {
export function dispatchAction(
state: InstancesState,
{ action }: LiftedActionDispatchAction
) {
if (action.type === 'JUMP_TO_STATE' || action.type === 'JUMP_TO_ACTION') {
const id = state.selected || state.current;
const liftedState = state.states[id];
@ -167,7 +231,7 @@ export function dispatchAction(state, { action }) {
return state;
}
function removeState(state, connectionId) {
function removeState(state: InstancesState, connectionId: string) {
const instanceIds = state.connections[connectionId];
if (!instanceIds) return state;
@ -202,7 +266,11 @@ function removeState(state, connectionId) {
};
}
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;
@ -234,7 +302,10 @@ function init({ type, action, name, libConfig = {} }, connectionId, current) {
};
}
export default function instances(state = initialState, action) {
export default function instances(
state = initialState,
action: StoreAction
): InstancesState {
switch (action.type) {
case UPDATE_STATE: {
const { request } = action;
@ -293,7 +364,7 @@ export default function instances(state = initialState, action) {
...state,
states: {
...state.states,
[id]: parseJSON(action.state),
[id]: parseJSON(action.state) as State,
},
};
}
@ -307,7 +378,5 @@ export default function instances(state = initialState, action) {
}
}
/* eslint-disable no-shadow */
export const getActiveInstance = (instances) =>
export const getActiveInstance = (instances: InstancesState) =>
instances.selected || instances.current;
/* eslint-enable */

View File

@ -5,28 +5,50 @@ import {
TOGGLE_SLIDER,
TOGGLE_DISPATCHER,
} from '../constants/actionTypes';
import { MonitorActionAction, StoreAction } from '../actions';
const initialState = {
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: MonitorStateMonitorState | undefined;
sliderIsOpen: boolean;
dispatcherIsOpen: boolean;
}
const initialState: MonitorState = {
selected: 'InspectorMonitor',
monitorState: undefined,
sliderIsOpen: true,
dispatcherIsOpen: false,
};
export function dispatchMonitorAction(state, action) {
export function dispatchMonitorAction(
state: MonitorState,
action: MonitorActionAction
): MonitorState {
return {
...state,
monitorState:
action.action.newMonitorState ||
monitorState: (action.action.newMonitorState ||
action.monitorReducer(
action.monitorProps,
state.monitorState,
action.action
),
)) as MonitorStateMonitorState,
};
}
export default function monitor(state = initialState, action) {
export default function monitor(
state = initialState,
action: StoreAction
): MonitorState {
switch (action.type) {
case MONITOR_ACTION:
return dispatchMonitorAction(state, action);
@ -45,7 +67,7 @@ export default function monitor(state = initialState, action) {
};
}
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

@ -4,8 +4,18 @@ import {
LIFTED_ACTION,
ERROR,
} from '../constants/actionTypes';
import { StoreAction } from '../actions';
export default function notification(state = null, action) {
interface Notification {
readonly type: 'error';
readonly message: string;
}
export type NotificationState = Notification | null;
export default function notification(
state: NotificationState = null,
action: StoreAction
): NotificationState {
switch (action.type) {
case SHOW_NOTIFICATION:
return action.notification;

View File

@ -1,12 +1,24 @@
import {
UPDATE_REPORTS /* , GET_REPORT_SUCCESS */,
} from '../constants/actionTypes';
import { StoreAction } from '../actions';
const initialState = {
export interface Data {
id: unknown;
}
export interface ReportsState {
data: Data[];
}
const initialState: ReportsState = {
data: [],
};
export default function reports(state = initialState, action) {
export default function reports(
state = initialState,
action: StoreAction
): ReportsState {
/* if (action.type === GET_REPORT_SUCCESS) {
const id = action.data.id;
return {
@ -19,17 +31,16 @@ export default function reports(state = initialState, action) {
return state;
const request = action.request;
const data = request.data;
switch (request.type) {
case 'list':
return {
...state,
data,
data: request.data,
};
case 'add':
return {
...state,
data: [...state.data, data],
data: [...state.data, request.data],
};
case 'remove':
return {

View File

@ -1,8 +0,0 @@
import { CHANGE_SECTION } from '../constants/actionTypes';
export default function section(state = 'Actions', action) {
if (action.type === CHANGE_SECTION) {
return action.section;
}
return state;
}

View File

@ -0,0 +1,11 @@
import { CHANGE_SECTION } from '../constants/actionTypes';
import { StoreAction } from '../actions';
export type SectionState = string;
export default function section(state = 'Actions', action: StoreAction) {
if (action.type === CHANGE_SECTION) {
return action.section;
}
return state;
}

View File

@ -1,15 +1,29 @@
import { AuthStates, States } from 'socketcluster-client/lib/scclientsocket';
import * as actions from '../constants/socketActionTypes';
import { StoreAction } from '../actions';
const initialState = {
export interface SocketState {
id: string | null;
channels: string[];
socketState: States;
authState: AuthStates | 'pending';
error: Error | undefined;
baseChannel?: string;
authToken?: null;
}
const initialState: SocketState = {
id: null,
channels: [],
socketState: actions.CLOSED,
authState: actions.PENDING,
authToken: null,
error: undefined,
};
export default function socket(state = initialState, action) {
export default function socket(
state = initialState,
action: StoreAction
): SocketState {
switch (action.type) {
case actions.CONNECT_REQUEST: {
return {
@ -39,7 +53,6 @@ export default function socket(state = initialState, action) {
return {
...state,
authState: actions.AUTHENTICATED,
authToken: action.authToken,
baseChannel: action.baseChannel,
};
case actions.AUTH_ERROR:
@ -57,13 +70,13 @@ export default function socket(state = initialState, action) {
case actions.SUBSCRIBE_SUCCESS:
return {
...state,
channels: [...state.channels, action.channelName],
channels: [...state.channels, action.channel],
};
case actions.UNSUBSCRIBE:
return {
...state,
channels: state.channels.filter(
(channel) => channel !== action.channelName
(channel) => channel !== action.channel
),
};
case actions.DISCONNECTED:

View File

@ -1,15 +0,0 @@
import { CHANGE_THEME } from '../constants/actionTypes';
export default function theme(
state = { theme: 'default', scheme: 'default', light: true },
action
) {
if (action.type === CHANGE_THEME) {
return {
theme: action.theme,
scheme: action.scheme,
light: !action.dark,
};
}
return state;
}

View File

@ -0,0 +1,27 @@
import { Scheme, Theme } from 'devui';
import { CHANGE_THEME } from '../constants/actionTypes';
import { StoreAction } from '../actions';
export interface ThemeState {
readonly theme: Theme;
readonly scheme: Scheme;
readonly light: boolean;
}
export default function theme(
state: ThemeState = {
theme: 'default' as const,
scheme: 'default' as const,
light: true,
},
action: StoreAction
) {
if (action.type === CHANGE_THEME) {
return {
theme: action.theme,
scheme: action.scheme,
light: !action.dark,
};
}
return state;
}

View File

@ -1,41 +0,0 @@
import { createStore, compose, applyMiddleware } from 'redux';
import localForage from 'localforage';
import { getStoredState, createPersistor } from 'redux-persist';
import api from '../middlewares/api';
import exportState from '../middlewares/exportState';
import rootReducer from '../reducers';
export default function configureStore(callback, key) {
const persistConfig = {
keyPrefix: `redux-devtools${key || ''}:`,
blacklist: ['instances', 'socket'],
storage: localForage,
serialize: (data) => data,
deserialize: (data) => data,
};
getStoredState(persistConfig, (err, restoredState) => {
let composeEnhancers = compose;
if (process.env.NODE_ENV !== 'production') {
if (window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__) {
composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__;
}
if (module.hot) {
// Enable Webpack hot module replacement for reducers
module.hot.accept('../reducers', () => {
const nextReducer = require('../reducers'); // eslint-disable-line global-require
store.replaceReducer(nextReducer);
});
}
}
const store = createStore(
rootReducer,
restoredState,
composeEnhancers(applyMiddleware(exportState, api))
);
const persistor = createPersistor(store, persistConfig);
callback(store, restoredState);
if (err) persistor.purge();
});
}

View File

@ -0,0 +1,60 @@
import { createStore, compose, applyMiddleware, Store } from 'redux';
import localForage from 'localforage';
import {
getStoredState,
createPersistor,
PersistorConfig,
} from 'redux-persist';
import api from '../middlewares/api';
import exportState from '../middlewares/exportState';
import rootReducer, { StoreState } from '../reducers';
import { StoreAction } from '../actions';
export default function configureStore(
callback: (
store: Store<StoreState, StoreAction>,
restoredState: Partial<StoreState> | undefined
) => void,
key?: string
) {
const persistConfig: PersistorConfig = ({
keyPrefix: `redux-devtools${key || ''}:`,
blacklist: ['instances', 'socket'],
storage: localForage,
serialize: (data: unknown) => data,
deserialize: (data: unknown) => data,
} as unknown) as PersistorConfig;
// eslint-disable-next-line @typescript-eslint/no-floating-promises
getStoredState<StoreState>(persistConfig, (err, restoredState) => {
let composeEnhancers = compose;
if (process.env.NODE_ENV !== 'production') {
if (
((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) {
// Enable Webpack hot module replacement for reducers
module.hot.accept('../reducers', () => {
// eslint-disable-next-line @typescript-eslint/no-var-requires
const nextReducer = require('../reducers'); // eslint-disable-line global-require
store.replaceReducer(nextReducer);
});
}
}
const store = createStore(
rootReducer,
restoredState,
composeEnhancers(applyMiddleware(exportState, api))
);
const persistor = createPersistor(store, persistConfig);
callback(store, restoredState);
if (err) persistor.purge();
});
}

View File

@ -1,7 +1,9 @@
// Based on https://github.com/gaearon/redux-devtools/pull/241
/* 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.
let excess = n;
let idsToDelete = liftedState.stagedActionIds.slice(1, excess + 1);

View File

@ -9,7 +9,7 @@ export const monitors = [
{ value: 'ChartMonitor', name: 'Chart' },
];
export default function getMonitor({ monitor }) {
export default function getMonitor({ monitor }: { monitor: string }) {
switch (monitor) {
case 'LogMonitor':
return (

View File

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

View File

@ -1,17 +1,17 @@
import jsan from 'jsan';
import { DATA_TYPE_KEY, DATA_REF_KEY } from '../constants/dataTypes';
export function reviver(key, value) {
export function reviver(key: string, value: unknown) {
if (
typeof value === 'object' &&
value !== null &&
'__serializedType__' in value &&
typeof value.data === 'object'
typeof (value as any).data === 'object'
) {
const data = value.data;
data[DATA_TYPE_KEY] = value.__serializedType__;
const data = (value as any).data;
data[DATA_TYPE_KEY] = (value as any).__serializedType__;
if ('__serializedRef__' in value)
data[DATA_REF_KEY] = value.__serializedRef__;
data[DATA_REF_KEY] = (value as any).__serializedRef__;
/*
if (Array.isArray(data)) {
data.__serializedType__ = value.__serializedType__;
@ -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,20 +0,0 @@
import jsan from 'jsan';
import { DATA_TYPE_KEY, DATA_REF_KEY } from '../constants/dataTypes';
function replacer(key, value) {
if (typeof value === 'object' && value !== null && DATA_TYPE_KEY in value) {
const __serializedType__ = value[DATA_TYPE_KEY];
const clone = { ...value };
delete clone[DATA_TYPE_KEY]; // eslint-disable-line no-param-reassign
const r = { data: clone, __serializedType__ };
if (DATA_REF_KEY in value) r.__serializedRef__ = clone[DATA_REF_KEY];
return r;
}
return value;
}
export default function stringifyJSON(data, serialize) {
return serialize
? jsan.stringify(data, replacer, null, true)
: jsan.stringify(data);
}

View File

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

View File

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

View File

@ -1,8 +0,0 @@
function injectedScript() {
/* eslint-disable-next-line no-console */
console.error(
"Not implemented yet. WIP. If you're looking for utils, import `redux-devtools-core/lib/utils`."
);
}
export default injectedScript;

View File

@ -1,6 +1,6 @@
import React from 'react';
import { render } from 'react-dom';
import App from './src/app';
import App from './app';
render(<App />, document.getElementById('root'));

View File

@ -1,42 +0,0 @@
const ERROR = '@@redux-devtools/ERROR';
export default function catchErrors(sendError) {
if (typeof window === 'object' && typeof window.onerror === 'object') {
window.onerror = function (message, url, lineNo, columnNo, error) {
const errorAction = { type: ERROR, message, url, lineNo, columnNo };
if (error && error.stack) errorAction.stack = error.stack;
sendError(errorAction);
return false;
};
} else if (typeof global !== 'undefined' && global.ErrorUtils) {
global.ErrorUtils.setGlobalHandler((error, isFatal) => {
sendError({ type: ERROR, error, isFatal });
});
}
/* eslint-disable no-console */
if (
typeof console === 'object' &&
typeof console.error === 'function' &&
!console.beforeRemotedev
) {
console.beforeRemotedev = console.error.bind(console);
console.error = function () {
let errorAction = { type: ERROR };
const error = arguments[0];
errorAction.message = error.message ? error.message : error;
if (error.sourceURL) {
errorAction = {
...errorAction,
sourceURL: error.sourceURL,
line: error.line,
column: error.column,
};
}
if (error.stack) errorAction.stack = error.stack;
sendError(errorAction);
console.beforeRemotedev.apply(null, arguments);
};
}
/* eslint-enable no-console */
}

View File

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

View File

@ -1,4 +1,7 @@
import mapValues from 'lodash/mapValues';
import { PerformAction } from 'redux-devtools-instrument';
import { Action } from 'redux';
import { State } from '../app/reducers/instances';
export const FilterState = {
DO_NOT_FILTER: 'DO_NOT_FILTER',
@ -6,19 +9,25 @@ export const FilterState = {
WHITELIST_SPECIFIC: 'WHITELIST_SPECIFIC',
};
export function arrToRegex(v) {
export function arrToRegex(v: string | string[]) {
return typeof v === 'string' ? v : v.join('|');
}
function filterActions(actionsById, actionsFilter) {
function filterActions(
actionsById: { [actionId: number]: PerformAction<Action<unknown>> },
actionsFilter: (action: Action<unknown>, id: number) => Action
) {
if (!actionsFilter) return actionsById;
return mapValues(actionsById, (action, id) => ({
return mapValues(actionsById, (action, id: number) => ({
...action,
action: actionsFilter(action.action, id),
}));
}
function filterStates(computedStates, statesFilter) {
function filterStates(
computedStates: { state: unknown; error?: string | undefined }[],
statesFilter: (state: unknown, actionId: number) => unknown
) {
if (!statesFilter) return computedStates;
return computedStates.map((state, idx) => ({
...state,
@ -26,7 +35,17 @@ function filterStates(computedStates, statesFilter) {
}));
}
export function getLocalFilter(config) {
interface Config {
actionsBlacklist?: string[];
actionsWhitelist?: string[];
}
interface LocalFilter {
whitelist?: string;
blacklist?: string;
}
export function getLocalFilter(config: Config): LocalFilter | undefined {
if (config.actionsBlacklist || config.actionsWhitelist) {
return {
whitelist: config.actionsWhitelist && config.actionsWhitelist.join('|'),
@ -36,33 +55,53 @@ export function getLocalFilter(config) {
return undefined;
}
interface DevToolsOptions {
filter?:
| typeof FilterState.DO_NOT_FILTER
| typeof FilterState.BLACKLIST_SPECIFIC
| typeof FilterState.WHITELIST_SPECIFIC;
whitelist?: string;
blacklist?: string;
}
function getDevToolsOptions() {
return (typeof window !== 'undefined' && window.devToolsOptions) || {};
return (
(typeof window !== 'undefined' &&
(window as { devToolsOptions?: DevToolsOptions }).devToolsOptions) ||
{}
);
}
export function isFiltered(action, localFilter) {
const { type } = action.action || action;
export function isFiltered(
action: PerformAction<Action<unknown>> | Action<unknown>,
localFilter?: LocalFilter
) {
const { type } = (action as PerformAction<Action<unknown>>).action || action;
const opts = getDevToolsOptions();
if (
(!localFilter &&
opts.filter &&
opts.filter === FilterState.DO_NOT_FILTER) ||
(type && typeof type.match !== 'function')
(type && typeof (type as string).match !== 'function')
)
return false;
const { whitelist, blacklist } = localFilter || opts;
return (
(whitelist && !type.match(whitelist)) ||
(blacklist && type.match(blacklist))
// eslint-disable-next-line @typescript-eslint/prefer-regexp-exec
(whitelist && !(type as string).match(whitelist)) ||
// eslint-disable-next-line @typescript-eslint/prefer-regexp-exec
(blacklist && (type as string).match(blacklist))
);
}
export function filterStagedActions(state, filters) {
export function filterStagedActions(state: State, filters: LocalFilter) {
if (!filters) return state;
const filteredStagedActionIds = [];
const filteredComputedStates = [];
const filteredStagedActionIds: number[] = [];
const filteredComputedStates: {
state: unknown;
error?: string | undefined;
}[] = [];
state.stagedActionIds.forEach((id, idx) => {
if (!isFiltered(state.actionsById[id], filters)) {
@ -79,13 +118,13 @@ export function filterStagedActions(state, filters) {
}
export function filterState(
state,
type,
localFilter,
stateSanitizer,
actionSanitizer,
nextActionId,
predicate
state: State,
type: string,
localFilter: LocalFilter,
stateSanitizer: (state: unknown, actionId: number) => unknown,
actionSanitizer: (action: Action<unknown>, id: number) => Action,
nextActionId: number,
predicate: (currState: unknown, currAction: Action<unknown>) => boolean
) {
if (type === 'ACTION')
return !stateSanitizer ? state : stateSanitizer(state, nextActionId - 1);
@ -97,9 +136,14 @@ export function filterState(
localFilter ||
(filter && filter !== FilterState.DO_NOT_FILTER)
) {
const filteredStagedActionIds = [];
const filteredComputedStates = [];
const sanitizedActionsById = actionSanitizer && {};
const filteredStagedActionIds: number[] = [];
const filteredComputedStates: {
state: unknown;
error?: string | undefined;
}[] = [];
const sanitizedActionsById: {
[id: number]: PerformAction<Action<unknown>>;
} = actionSanitizer && {};
const { actionsById } = state;
const { computedStates } = state;

View File

@ -0,0 +1,4 @@
declare module 'get-params' {
function getParams(func: (...args: any[]) => unknown): string[];
export default getParams;
}

View File

@ -1,69 +0,0 @@
import mapValues from 'lodash/mapValues';
import jsan from 'jsan';
import seralizeImmutable from 'remotedev-serialize/immutable/serialize';
function deprecate(param) {
// eslint-disable-next-line no-console
console.warn(
`\`${param}\` parameter for Redux DevTools Extension is deprecated. Use \`serialize\` parameter instead:` +
' https://github.com/zalmoxisus/redux-devtools-extension/releases/tag/v2.12.1'
);
}
export default function importState(
state,
{ deserializeState, deserializeAction, serialize }
) {
if (!state) return undefined;
let parse = jsan.parse;
if (serialize) {
if (serialize.immutable) {
parse = (v) =>
jsan.parse(
v,
seralizeImmutable(serialize.immutable, serialize.refs).reviver
);
} else if (serialize.reviver) {
parse = (v) => jsan.parse(v, serialize.reviver);
}
}
let preloadedState;
let nextLiftedState = parse(state);
if (nextLiftedState.payload) {
if (nextLiftedState.preloadedState)
preloadedState = parse(nextLiftedState.preloadedState);
nextLiftedState = parse(nextLiftedState.payload);
}
if (deserializeState) {
deprecate('deserializeState');
if (typeof nextLiftedState.computedStates !== 'undefined') {
nextLiftedState.computedStates = nextLiftedState.computedStates.map(
(computedState) => ({
...computedState,
state: deserializeState(computedState.state),
})
);
}
if (typeof nextLiftedState.committedState !== 'undefined') {
nextLiftedState.committedState = deserializeState(
nextLiftedState.committedState
);
}
if (typeof preloadedState !== 'undefined') {
preloadedState = deserializeState(preloadedState);
}
}
if (deserializeAction) {
deprecate('deserializeAction');
nextLiftedState.actionsById = mapValues(
nextLiftedState.actionsById,
(liftedAction) => ({
...liftedAction,
action: deserializeAction(liftedAction.action),
})
);
}
return { nextLiftedState, preloadedState };
}

View File

@ -0,0 +1,105 @@
import mapValues from 'lodash/mapValues';
import jsan from 'jsan';
import { immutableSerialize } from 'redux-devtools-serialize';
import { Action } from 'redux';
import Immutable from 'immutable';
import { State } from '../app/reducers/instances';
function deprecate(param: string) {
// eslint-disable-next-line no-console
console.warn(
`\`${param}\` parameter for Redux DevTools Extension is deprecated. Use \`serialize\` parameter instead:` +
' https://github.com/zalmoxisus/redux-devtools-extension/releases/tag/v2.12.1'
);
}
export default function importState(
state: string,
{
deserializeState,
deserializeAction,
serialize,
}: {
deserializeState?: (state: string) => unknown;
deserializeAction?: (action: string) => Action<unknown>;
serialize?: {
immutable?: typeof Immutable;
refs?: (new (data: any) => unknown)[] | null;
reviver?: (key: string, value: unknown) => unknown;
};
}
) {
if (!state) return undefined;
let parse = jsan.parse;
if (serialize) {
if (serialize.immutable) {
parse = (v) =>
jsan.parse(
v,
immutableSerialize(serialize.immutable!, serialize.refs).reviver
);
} else if (serialize.reviver) {
parse = (v) => jsan.parse(v, serialize.reviver);
}
}
let preloadedState: State | undefined;
let nextLiftedState: State = parse(state) as State;
if (
((nextLiftedState as unknown) as {
payload?: string;
preloadedState?: string;
}).payload
) {
if (
((nextLiftedState as unknown) as {
payload: string;
preloadedState?: string;
}).preloadedState
)
preloadedState = parse(
((nextLiftedState as unknown) as {
payload: string;
preloadedState: string;
}).preloadedState
) as State;
nextLiftedState = parse(
((nextLiftedState as unknown) as {
payload: string;
}).payload
) as State;
}
if (deserializeState) {
deprecate('deserializeState');
if (typeof nextLiftedState.computedStates !== 'undefined') {
nextLiftedState.computedStates = nextLiftedState.computedStates.map(
(computedState) => ({
...computedState,
state: deserializeState(computedState.state as string),
})
);
}
if (typeof nextLiftedState.committedState !== 'undefined') {
nextLiftedState.committedState = deserializeState(
nextLiftedState.committedState as string
);
}
if (typeof preloadedState !== 'undefined') {
preloadedState = deserializeState(
(preloadedState as unknown) as string
) as State;
}
}
if (deserializeAction) {
deprecate('deserializeAction');
nextLiftedState.actionsById = mapValues(
nextLiftedState.actionsById,
(liftedAction) => ({
...liftedAction,
action: deserializeAction((liftedAction.action as unknown) as string),
})
);
}
return { nextLiftedState, preloadedState };
}

View File

@ -1,14 +1,24 @@
import getParams from 'get-params';
import jsan from 'jsan';
import { nanoid } from 'nanoid/non-secure';
import seralizeImmutable from 'remotedev-serialize/immutable/serialize';
import { immutableSerialize } from 'redux-devtools-serialize';
import Immutable from 'immutable';
import { Action } from 'redux';
export function generateId(id) {
export function generateId(id: string | undefined) {
return id || nanoid(7);
}
function flatTree(obj, namespace = '') {
let functions = [];
// eslint-disable-next-line @typescript-eslint/ban-types
function flatTree(
obj: { [key: string]: (...args: any[]) => unknown },
namespace = ''
) {
let functions: {
name: string;
func: (...args: any[]) => unknown;
args: string[];
}[] = [];
Object.keys(obj).forEach((key) => {
const prop = obj[key];
if (typeof prop === 'function') {
@ -24,18 +34,23 @@ function flatTree(obj, namespace = '') {
return functions;
}
export function getMethods(obj) {
export function getMethods(obj: unknown) {
if (typeof obj !== 'object') return undefined;
let functions;
let m;
if (obj.__proto__) m = obj.__proto__.__proto__;
if (!m) m = obj;
let functions:
| {
name: string;
args: string[];
}[]
| undefined;
let m: { [key: string]: (...args: any[]) => unknown } | undefined;
if ((obj as any).__proto__) m = (obj as any).__proto__.__proto__;
if (!m) m = obj as any;
Object.getOwnPropertyNames(m).forEach((key) => {
const propDescriptor = Object.getOwnPropertyDescriptor(m, key);
if (!propDescriptor || 'get' in propDescriptor || 'set' in propDescriptor)
return;
const prop = m[key];
const prop = m![key];
if (typeof prop === 'function' && key !== 'constructor') {
if (!functions) functions = [];
functions.push({
@ -47,15 +62,17 @@ export function getMethods(obj) {
return functions;
}
export function getActionsArray(actionCreators) {
export function getActionsArray(actionCreators: {
[key: string]: (...args: any[]) => unknown;
}) {
if (Array.isArray(actionCreators)) return actionCreators;
return flatTree(actionCreators);
}
/* eslint-disable no-new-func */
const interpretArg = (arg) => new Function('return ' + arg)();
// eslint-disable-next-line @typescript-eslint/no-implied-eval
const interpretArg = (arg: string) => new Function('return ' + arg)();
function evalArgs(inArgs, restArgs) {
function evalArgs(inArgs: string[], restArgs: string) {
const args = inArgs.map(interpretArg);
if (!restArgs) return args;
const rest = interpretArg(restArgs);
@ -63,8 +80,14 @@ function evalArgs(inArgs, restArgs) {
throw new Error('rest must be an array');
}
export function evalAction(action, actionCreators) {
export function evalAction(
action: string | { args: string[]; rest: string; selected: string },
actionCreators: {
[selected: string]: { func: (...args: any[]) => Action<unknown> };
}
) {
if (typeof action === 'string') {
// eslint-disable-next-line @typescript-eslint/no-implied-eval
return new Function('return ' + action)();
}
@ -73,12 +96,17 @@ export function evalAction(action, actionCreators) {
return actionCreator(...args);
}
export function evalMethod(action, obj) {
export function evalMethod(
action: string | { args: string[]; rest: string; name: string },
obj: unknown
) {
if (typeof action === 'string') {
// eslint-disable-next-line @typescript-eslint/no-implied-eval
return new Function('return ' + action).call(obj);
}
const args = evalArgs(action.args, action.rest);
// eslint-disable-next-line @typescript-eslint/no-implied-eval
return new Function('args', `return this.${action.name}(args)`).apply(
obj,
args
@ -86,7 +114,7 @@ export function evalMethod(action, obj) {
}
/* eslint-enable */
function tryCatchStringify(obj) {
function tryCatchStringify(obj: unknown) {
try {
return JSON.stringify(obj);
} catch (err) {
@ -94,11 +122,26 @@ function tryCatchStringify(obj) {
if (process.env.NODE_ENV !== 'production')
console.log('Failed to stringify', err);
/* eslint-enable no-console */
return jsan.stringify(obj, null, null, { circular: '[CIRCULAR]' });
return jsan.stringify(
obj,
(null as unknown) as undefined,
(null as unknown) as undefined,
({
circular: '[CIRCULAR]',
} as unknown) as boolean
);
}
}
export function stringify(obj, serialize) {
export function stringify(
obj: unknown,
serialize?:
| {
replacer?: (key: string, value: unknown) => unknown;
options?: unknown | boolean;
}
| true
) {
if (typeof serialize === 'undefined') {
return tryCatchStringify(obj);
}
@ -106,23 +149,44 @@ export function stringify(obj, serialize) {
return jsan.stringify(
obj,
function (key, value) {
if (value && typeof value.toJS === 'function') return value.toJS();
if (value && typeof (value as any).toJS === 'function')
return (value as any).toJS();
return value;
},
null,
(null as unknown) as undefined,
true
);
}
return jsan.stringify(obj, serialize.replacer, null, serialize.options);
return jsan.stringify(
obj,
serialize.replacer,
(null as unknown) as undefined,
serialize.options as boolean
);
}
export function getSeralizeParameter(config, param) {
export function getSeralizeParameter(
config: {
serialize?: {
immutable?: typeof Immutable;
refs?: (new (data: any) => unknown)[] | null;
replacer?: (key: string, value: unknown) => unknown;
options?: unknown | boolean;
};
},
param: string
):
| {
replacer?: (key: string, value: unknown) => unknown;
options: unknown | boolean;
}
| undefined {
const serialize = config.serialize;
if (serialize) {
if (serialize === true) return { options: true };
if (serialize.immutable) {
return {
replacer: seralizeImmutable(serialize.immutable, serialize.refs)
replacer: immutableSerialize(serialize.immutable, serialize.refs)
.replacer,
options: serialize.options || true,
};
@ -131,7 +195,12 @@ export function getSeralizeParameter(config, param) {
return { replacer: serialize.replacer, options: serialize.options || true };
}
const value = config[param];
const value = (config as {
[param: string]: {
replacer?: (key: string, value: unknown) => unknown;
options: unknown | boolean;
};
})[param];
if (typeof value === 'undefined') return undefined;
// eslint-disable-next-line no-console
console.warn(
@ -139,12 +208,15 @@ export function getSeralizeParameter(config, param) {
' https://github.com/zalmoxisus/redux-devtools-extension/releases/tag/v2.12.1'
);
if (typeof serializeState === 'boolean') return { options: value };
if (typeof serializeState === 'function') return { replacer: value };
return value;
}
export function getStackTrace(config, toExcludeFromTrace) {
export function getStackTrace(
// eslint-disable-next-line @typescript-eslint/ban-types
config: { trace?: () => {}; traceLimit: number },
// eslint-disable-next-line @typescript-eslint/ban-types
toExcludeFromTrace?: Function | undefined
) {
if (!config.trace) return undefined;
if (typeof config.trace === 'function') return config.trace();
@ -169,7 +241,7 @@ export function getStackTrace(config, toExcludeFromTrace) {
typeof Error.stackTraceLimit !== 'number' ||
Error.stackTraceLimit > traceLimit
) {
const frames = stack.split('\n');
const frames = stack!.split('\n');
if (frames.length > traceLimit) {
stack = frames
.slice(0, traceLimit + extraFrames + (frames[0] === 'Error' ? 1 : 0))

View File

@ -1 +0,0 @@
module.exports = {};

View File

@ -1,7 +1,7 @@
import React from 'react';
import React, { Component } from 'react';
import { Provider } from 'react-redux';
import { createStore, applyMiddleware } from 'redux';
import { mount } from 'enzyme';
import { mount, ReactWrapper } from 'enzyme';
// import { mountToJson } from 'enzyme-to-json';
import App from '../src/app/containers/App';
import api from '../src/app/middlewares/api';
@ -10,7 +10,7 @@ import rootReducer from '../src/app/reducers';
import { DATA_TYPE_KEY } from '../src/app/constants/dataTypes';
import stringifyJSON from '../src/app/utils/stringifyJSON';
let wrapper;
let wrapper: ReactWrapper<unknown, unknown, Component>;
const store = createStore(rootReducer, applyMiddleware(exportState, api));

View File

@ -0,0 +1,4 @@
{
"extends": "../../../tsconfig.react.base.json",
"include": ["../src", "."]
}

View File

@ -0,0 +1,7 @@
{
"extends": "../../tsconfig.react.base.json",
"compilerOptions": {
"outDir": "lib"
},
"include": ["src"]
}

View File

@ -0,0 +1,4 @@
{
"extends": "../../tsconfig.base.json",
"include": ["webpack.config.ts", "webpack.config.umd.ts"]
}

View File

@ -1,14 +1,15 @@
const path = require('path');
const webpack = require('webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin');
import * as path from 'path';
import * as webpack from 'webpack';
import HtmlWebpackPlugin from 'html-webpack-plugin';
import ForkTsCheckerWebpackPlugin from 'fork-ts-checker-webpack-plugin';
module.exports = (env = {}) => ({
module.exports = (env: { development?: boolean; platform?: string } = {}) => ({
mode: env.development ? 'development' : 'production',
entry: {
app: './index.js',
app: './src/index',
},
output: {
path: path.resolve(__dirname, 'build/' + env.platform),
path: path.resolve(__dirname, `build/${env.platform as string}`),
publicPath: '',
filename: 'js/[name].js',
sourceMapFilename: 'js/[name].map',
@ -16,7 +17,7 @@ module.exports = (env = {}) => ({
module: {
rules: [
{
test: /\.js$/,
test: /\.(js|ts)x?$/,
loader: 'babel-loader',
exclude: /node_modules/,
},
@ -44,6 +45,9 @@ module.exports = (env = {}) => ({
},
],
},
resolve: {
extensions: ['.js', '.jsx', '.ts', '.tsx'],
},
plugins: [
new webpack.DefinePlugin({
'process.env': {
@ -56,6 +60,11 @@ module.exports = (env = {}) => ({
new HtmlWebpackPlugin({
template: 'assets/index.html',
}),
new ForkTsCheckerWebpackPlugin({
typescript: {
configFile: 'tsconfig.json',
},
}),
],
optimization: {
minimize: false,

View File

@ -1,10 +1,11 @@
const path = require('path');
const webpack = require('webpack');
import * as path from 'path';
import * as webpack from 'webpack';
import ForkTsCheckerWebpackPlugin from 'fork-ts-checker-webpack-plugin';
module.exports = (env = {}) => ({
module.exports = (env: { production?: boolean } = {}) => ({
mode: env.production ? 'production' : 'development',
entry: {
app: ['./src/app/index.js'],
app: ['./src/app/index'],
},
output: {
library: 'ReduxDevTools',
@ -18,7 +19,7 @@ module.exports = (env = {}) => ({
module: {
rules: [
{
test: /\.js$/,
test: /\.(js|ts)x?$/,
loader: 'babel-loader',
exclude: /node_modules/,
},
@ -41,6 +42,9 @@ module.exports = (env = {}) => ({
},
],
},
resolve: {
extensions: ['.js', '.jsx', '.ts', '.tsx'],
},
plugins: [
new webpack.DefinePlugin({
'process.env': {
@ -48,6 +52,11 @@ module.exports = (env = {}) => ({
PLATFORM: JSON.stringify('web'),
},
}),
new ForkTsCheckerWebpackPlugin({
typescript: {
configFile: 'tsconfig.json',
},
}),
],
externals: {
react: {

View File

@ -25,7 +25,7 @@ export interface TabComponentProps<S, A extends Action<unknown>> {
base16Theme: Base16Theme;
invertTheme: boolean;
isWideLayout: boolean;
dataTypeKey: string | undefined;
dataTypeKey: string | symbol | undefined;
delta: Delta | null | undefined | false;
action: A;
nextState: S;
@ -67,7 +67,7 @@ interface Props<S, A extends Action<unknown>> {
actions: { [actionId: number]: PerformAction<A> };
selectedActionId: number | null;
startActionId: number | null;
dataTypeKey: string | undefined;
dataTypeKey: string | symbol | undefined;
monitorState: DevtoolsInspectorState;
updateMonitorState: (monitorState: Partial<DevtoolsInspectorState>) => void;
styling: StylingFunction;

View File

@ -128,7 +128,7 @@ function createThemeState<S, A extends Action<unknown>>(
return { base16Theme, styling };
}
interface ExternalProps<S, A extends Action<unknown>> {
export interface ExternalProps<S, A extends Action<unknown>> {
dispatch: Dispatch<
DevtoolsInspectorAction | LiftedAction<S, A, DevtoolsInspectorState>
>;
@ -142,7 +142,7 @@ interface ExternalProps<S, A extends Action<unknown>> {
hideMainButtons?: boolean;
hideActionButtons?: boolean;
invertTheme: boolean;
dataTypeKey?: string;
dataTypeKey?: string | symbol;
tabs: Tab<S, A>[] | ((tabs: Tab<S, A>[]) => Tab<S, A>[]);
}
@ -169,7 +169,7 @@ export interface DevtoolsInspectorProps<S, A extends Action<unknown>>
hideMainButtons?: boolean;
hideActionButtons?: boolean;
invertTheme: boolean;
dataTypeKey?: string;
dataTypeKey?: string | symbol;
tabs: Tab<S, A>[] | ((tabs: Tab<S, A>[]) => Tab<S, A>[]);
}

View File

@ -4,7 +4,7 @@ import { DevtoolsInspectorProps } from './DevtoolsInspector';
const UPDATE_MONITOR_STATE =
'@@redux-devtools-inspector-monitor/UPDATE_MONITOR_STATE';
interface UpdateMonitorStateAction {
export interface UpdateMonitorStateAction {
type: typeof UPDATE_MONITOR_STATE;
monitorState: Partial<DevtoolsInspectorState>;
}

View File

@ -60,7 +60,7 @@ interface Props {
expandable: boolean
) => React.ReactNode;
isWideLayout: boolean;
dataTypeKey: string | undefined;
dataTypeKey: string | symbol | undefined;
}
interface State {

View File

@ -73,7 +73,7 @@ const getItemString = (
styling: StylingFunction,
type: string,
data: any,
dataTypeKey: string | undefined,
dataTypeKey: string | symbol | undefined,
isWideLayout: boolean,
isDiff?: boolean
) => (

View File

@ -35,3 +35,4 @@ export default function (
serialize: serialize,
};
}
export { default as serialize } from './serialize';

Some files were not shown because too many files have changed in this diff Show More