Move the logic from @redux-devtools/app into @redux-devtools/app-core (#1655)

This change splits out the main logic from the Redux Devtools App into a new
core package but keeps the socket connection management in @redux-devtools/app.
The aim is to allow for easier reuse of the rest of the app in other envioronments
with their own transport methods, such as React Native or Electron.
This commit is contained in:
Matt Oakes 2024-06-12 14:18:46 +01:00 committed by GitHub
parent a4382ecb9c
commit 96ac1f291a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
70 changed files with 13775 additions and 10408 deletions

View File

@ -0,0 +1,6 @@
---
'@redux-devtools/app-core': major
'@redux-devtools/app': minor
---
Move the logic from @redux-devtools/app into @redux-devtools/app-core

View File

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

View File

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

View File

@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2024 Mihail Diordiev
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -0,0 +1,27 @@
# Redux DevTools monitor app core
The core React component and Redux store for the Redux DevTools monitor app. It is split out to allow you to use it directly for transports other than the standard WebSocket one.
### Usage
```js
import { Provider } from 'react-redux';
import { Persistor } from 'redux-persist';
import { PersistGate } from 'redux-persist/integration/react';
import { App } from '@redux-devtools/app-core';
import { store, persistor } from "./yourStore";
export function Root() {
return (
<Provider store={store}>
<PersistGate loading={null} persistor={persistor!}>
<App />
</PersistGate>
</Provider>
);
}
```
### License
MIT

View File

@ -0,0 +1,8 @@
{
"presets": [
["@babel/preset-env", { "targets": "defaults", "modules": false }],
"@babel/preset-react",
"@babel/preset-typescript"
],
"plugins": ["@babel/plugin-transform-runtime"]
}

View File

@ -0,0 +1,8 @@
{
"presets": [
["@babel/preset-env", { "targets": "defaults" }],
"@babel/preset-react",
"@babel/preset-typescript"
],
"plugins": ["@babel/plugin-transform-runtime"]
}

View File

@ -0,0 +1,108 @@
{
"name": "@redux-devtools/app-core",
"version": "1.0.0",
"description": "Redux DevTools app core",
"homepage": "https://github.com/reduxjs/redux-devtools/tree/master/packages/redux-devtools-app-core",
"bugs": {
"url": "https://github.com/reduxjs/redux-devtools/issues"
},
"license": "MIT",
"author": "Mihail Diordiev <zalmoxisus@gmail.com> (https://github.com/zalmoxisus)",
"files": [
"build",
"lib",
"src",
"umd"
],
"main": "lib/cjs/index.js",
"module": "lib/esm/index.js",
"types": "lib/types/index.d.ts",
"sideEffects": false,
"repository": {
"type": "git",
"url": "https://github.com/reduxjs/redux-devtools.git"
},
"scripts": {
"build": "pnpm run build:cjs && pnpm run build:esm && pnpm run build:types",
"build:cjs": "babel src --extensions \".ts,.tsx\" --out-dir lib/cjs",
"build:esm": "babel src --config-file ./babel.config.esm.json --extensions \".ts,.tsx\" --out-dir lib/esm",
"build:types": "tsc --emitDeclarationOnly",
"clean": "rimraf lib",
"test": "jest",
"lint": "eslint . --ext .ts,.tsx",
"type-check": "tsc --noEmit",
"prepack": "pnpm run clean && pnpm run build",
"prepublish": "pnpm run type-check && pnpm run lint && pnpm run test"
},
"dependencies": {
"@babel/runtime": "^7.24.7",
"@redux-devtools/chart-monitor": "^5.0.1",
"@redux-devtools/core": "^4.0.0",
"@redux-devtools/inspector-monitor": "^6.0.0",
"@redux-devtools/inspector-monitor-test-tab": "^4.0.0",
"@redux-devtools/inspector-monitor-trace-tab": "^4.0.0",
"@redux-devtools/log-monitor": "^5.0.0",
"@redux-devtools/rtk-query-monitor": "^5.0.0",
"@redux-devtools/slider-monitor": "^5.0.0",
"@redux-devtools/ui": "^1.3.1",
"d3-state-visualizer": "^3.0.0",
"javascript-stringify": "^2.1.0",
"jsan": "^3.1.14",
"jsondiffpatch": "^0.6.0",
"react-icons": "^5.2.1",
"react-is": "^18.3.1"
},
"devDependencies": {
"@babel/cli": "^7.24.1",
"@babel/core": "^7.24.3",
"@babel/eslint-parser": "^7.24.1",
"@babel/plugin-transform-runtime": "^7.24.3",
"@babel/preset-env": "^7.24.3",
"@babel/preset-react": "^7.24.1",
"@babel/preset-typescript": "^7.24.1",
"@emotion/react": "^11.11.4",
"@rjsf/core": "^4.2.3",
"@testing-library/dom": "^10.1.0",
"@testing-library/jest-dom": "^6.4.5",
"@testing-library/react": "^16.0.0",
"@types/jest": "^29.5.12",
"@types/jsan": "^3.1.5",
"@types/json-schema": "^7.0.15",
"@types/node": "^20.11.30",
"@types/react": "^18.2.72",
"@types/react-dom": "^18.2.22",
"@types/styled-components": "^5.1.34",
"@typescript-eslint/eslint-plugin": "^7.4.0",
"@typescript-eslint/parser": "^7.4.0",
"cross-env": "^7.0.3",
"esbuild": "^0.20.2",
"eslint": "^8.57.0",
"eslint-plugin-jest": "^28.6.0",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-react": "^7.34.2",
"eslint-plugin-react-hooks": "^4.6.2",
"jest": "^29.7.0",
"jest-environment-jsdom": "^29.7.0",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-redux": "^8.1.3",
"redux": "^4.2.1",
"redux-persist": "^6.0.0",
"rimraf": "^5.0.7",
"styled-components": "^5.3.11",
"ts-jest": "^29.1.2",
"ts-node": "^10.9.2",
"typescript": "~5.3.3"
},
"peerDependencies": {
"@emotion/react": "^11.11.4",
"@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0",
"@types/styled-components": "^5.1.34",
"react": "^16.8.4 || ^17.0.0 || ^18.0.0",
"react-dom": "^16.8.4 || ^17.0.0 || ^18.0.0",
"react-redux": "^8.0.0 || ^9.0.0",
"redux": "^4.0.0 || ^5.0.0",
"redux-persist": "^6.0.0",
"styled-components": "^5.3.11"
}
}

View File

@ -0,0 +1,523 @@
import { SchemeName, ThemeName } from '@redux-devtools/ui';
import { REHYDRATE } from 'redux-persist';
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,
SET_PERSIST,
CHANGE_STATE_TREE_SETTINGS,
CLEAR_INSTANCES,
} from '../constants/actionTypes';
import { Action } from 'redux';
import { Features, State } from '../reducers/instances';
import { MonitorStateMonitorState } from '../reducers/monitor';
import { LiftedAction } from '@redux-devtools/core';
import { Data } from '../reducers/reports';
import { LiftedState } from '@redux-devtools/core';
let monitorReducer: (
monitorProps: unknown,
state: unknown | undefined,
action: Action<string>,
) => unknown;
let monitorProps: unknown = {};
export 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: ThemeName;
readonly scheme: SchemeName;
readonly colorPreference: 'auto' | 'light' | 'dark';
}
interface ChangeThemeData {
readonly formData: ChangeThemeFormData;
}
export interface ChangeThemeAction {
readonly type: typeof CHANGE_THEME;
readonly theme: ThemeName;
readonly scheme: SchemeName;
readonly colorPreference: 'auto' | 'light' | 'dark';
}
export function changeTheme(data: ChangeThemeData): ChangeThemeAction {
return { type: CHANGE_THEME, ...data.formData };
}
interface ChangeStateTreeSettingsFormData {
readonly sortAlphabetically: boolean;
readonly disableCollection: boolean;
}
interface ChangeStateTreeSettingsData {
readonly formData: ChangeStateTreeSettingsFormData;
}
export interface ChangeStateTreeSettingsAction {
readonly type: typeof CHANGE_STATE_TREE_SETTINGS;
readonly sortAlphabetically: boolean;
readonly disableCollection: boolean;
}
export function changeStateTreeSettings(
data: ChangeStateTreeSettingsData,
): ChangeStateTreeSettingsAction {
return { type: CHANGE_STATE_TREE_SETTINGS, ...data.formData };
}
export interface InitMonitorAction {
type: '@@INIT_MONITOR';
newMonitorState: unknown;
update: (
monitorProps: unknown,
state: unknown | undefined,
action: Action<string>,
) => unknown;
monitorProps: unknown;
}
export interface MonitorActionAction {
type: typeof MONITOR_ACTION;
action: InitMonitorAction;
monitorReducer: (
monitorProps: unknown,
state: unknown | undefined,
action: Action<string>,
) => unknown;
monitorProps: unknown;
}
export interface JumpToStateAction {
type: 'JUMP_TO_STATE';
index: number;
}
export interface JumpToActionAction {
type: 'JUMP_TO_ACTION';
actionId: number;
}
export interface PauseRecordingAction {
type: 'PAUSE_RECORDING';
status: boolean;
}
export interface LockChangesAction {
type: 'LOCK_CHANGES';
status: boolean;
}
export interface ToggleActionAction {
type: 'TOGGLE_ACTION';
id: number;
}
export interface RollbackAction {
type: 'ROLLBACK';
timestamp: number;
}
export interface SweepAction {
type: 'SWEEP';
}
interface ReorderActionAction {
type: 'REORDER_ACTION';
actionId: number;
beforeActionId: number;
}
interface ImportStateAction {
type: 'IMPORT_STATE';
nextLiftedState:
| LiftedState<unknown, Action<string>, unknown>
| readonly Action<string>[];
preloadedState?: unknown;
noRecompute?: boolean | undefined;
}
export type DispatchAction =
| JumpToStateAction
| JumpToActionAction
| PauseRecordingAction
| LockChangesAction
| ToggleActionAction
| RollbackAction
| SweepAction
| ReorderActionAction
| ImportStateAction;
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;
}
export interface LiftedActionImportAction extends LiftedActionActionBase {
type: typeof LIFTED_ACTION;
message: 'IMPORT';
state: string;
preloadedState: unknown | undefined;
}
export interface LiftedActionActionAction extends LiftedActionActionBase {
type: typeof LIFTED_ACTION;
message: 'ACTION';
action: string | CustomAction;
}
export 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<string>, 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;
}
export interface SelectInstanceAction {
type: typeof SELECT_INSTANCE;
selected: string | number;
}
export function selectInstance(selected: string): SelectInstanceAction {
return { type: SELECT_INSTANCE, selected };
}
export 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[];
}
export 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 };
}
export 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[];
rest: string;
}
export function dispatchRemotely(
action: string | CustomAction,
): LiftedActionActionAction {
return { type: LIFTED_ACTION, message: 'ACTION', action };
}
export interface TogglePersistAction {
type: typeof TOGGLE_PERSIST;
}
export function togglePersist(): TogglePersistAction {
return { type: TOGGLE_PERSIST };
}
export interface SetPersistAction {
type: typeof SET_PERSIST;
payload: boolean;
}
export function setPersist(persist: boolean): SetPersistAction {
return { type: SET_PERSIST, payload: persist };
}
export interface ToggleSyncAction {
type: typeof TOGGLE_SYNC;
}
export function toggleSync(): ToggleSyncAction {
return { type: TOGGLE_SYNC };
}
export interface ToggleSliderAction {
type: typeof TOGGLE_SLIDER;
}
export function toggleSlider(): ToggleSliderAction {
return { type: TOGGLE_SLIDER };
}
export interface ToggleDispatcherAction {
type: typeof TOGGLE_DISPATCHER;
}
export function toggleDispatcher(): ToggleDispatcherAction {
return { type: TOGGLE_DISPATCHER };
}
interface Notification {
readonly type: 'error';
readonly message: string;
}
export interface ShowNotificationAction {
readonly type: typeof SHOW_NOTIFICATION;
readonly notification: Notification;
}
export function showNotification(message: string): ShowNotificationAction {
return { type: SHOW_NOTIFICATION, notification: { type: 'error', message } };
}
export interface ClearNotificationAction {
readonly type: typeof CLEAR_NOTIFICATION;
}
export function clearNotification(): ClearNotificationAction {
return { type: CLEAR_NOTIFICATION };
}
export 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;
}
export interface LibConfig {
actionCreators?: string;
name?: string;
type?: string;
features?: Features;
serialize?: boolean;
}
export interface RequestBase {
id?: string;
instanceId?: string | number;
action?: string;
name?: string | undefined;
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;
payload?: 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;
export interface UpdateStateAction {
type: typeof UPDATE_STATE;
request?: Request;
id?: string | number;
}
export interface SetStateAction {
type: typeof SET_STATE;
newState: State;
}
export interface RemoveInstanceAction {
type: typeof REMOVE_INSTANCE;
id: string | number;
}
export interface ClearInstancesAction {
type: typeof CLEAR_INSTANCES;
}
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;
export interface UpdateReportsAction {
type: typeof UPDATE_REPORTS;
request: UpdateReportsRequest;
}
export interface GetReportError {
type: typeof GET_REPORT_ERROR;
error: Error;
}
export interface GetReportSuccess {
type: typeof GET_REPORT_SUCCESS;
data: { payload: string };
}
export interface ErrorAction {
type: typeof ERROR;
payload: string;
}
export interface ReduxPersistRehydrateAction {
type: typeof REHYDRATE;
payload: unknown;
}
export type CoreStoreActionWithoutUpdateStateOrLiftedAction =
| ChangeSectionAction
| ChangeThemeAction
| ChangeStateTreeSettingsAction
| MonitorActionAction
| SelectInstanceAction
| SelectMonitorAction
| UpdateMonitorStateAction
| ExportAction
| TogglePersistAction
| SetPersistAction
| ToggleSyncAction
| ToggleSliderAction
| ToggleDispatcherAction
| ShowNotificationAction
| ClearNotificationAction
| GetReportRequest
| SetStateAction
| RemoveInstanceAction
| ClearInstancesAction
| UpdateReportsAction
| GetReportError
| GetReportSuccess
| ErrorAction
| ReduxPersistRehydrateAction;
export type CoreStoreActionWithoutUpdateState =
| CoreStoreActionWithoutUpdateStateOrLiftedAction
| LiftedActionAction;
export type CoreStoreActionWithoutLiftedAction =
| CoreStoreActionWithoutUpdateStateOrLiftedAction
| UpdateStateAction;
export type CoreStoreAction =
| CoreStoreActionWithoutUpdateState
| UpdateStateAction;

View File

@ -2,7 +2,7 @@ import React, { Component } from 'react';
import { connect, ResolveThunks } from 'react-redux';
import { Select } from '@redux-devtools/ui';
import { selectInstance } from '../actions';
import { StoreState } from '../reducers';
import { CoreStoreState } from '../reducers';
type StateProps = ReturnType<typeof mapStateToProps>;
type DispatchProps = ResolveThunks<typeof actionCreators>;
@ -32,7 +32,7 @@ class InstanceSelector extends Component<Props> {
}
}
const mapStateToProps = (state: StoreState) => ({
const mapStateToProps = (state: CoreStoreState) => ({
selected: state.instances.selected,
instances: state.instances.options,
});

View File

@ -3,7 +3,7 @@ import { connect, ResolveThunks } from 'react-redux';
import { Tabs } from '@redux-devtools/ui';
import { monitors } from '../utils/getMonitor';
import { selectMonitor } from '../actions';
import { StoreState } from '../reducers';
import { CoreStoreState } from '../reducers';
type StateProps = ReturnType<typeof mapStateToProps>;
type DispatchProps = ResolveThunks<typeof actionCreators>;
@ -28,7 +28,7 @@ class MonitorSelector extends Component<Props> {
}
}
const mapStateToProps = (state: StoreState) => ({
const mapStateToProps = (state: CoreStoreState) => ({
selected: state.monitor.selected,
});

View File

@ -2,7 +2,7 @@ import React, { Component } from 'react';
import { connect, ResolveThunks } from 'react-redux';
import { Container, Form } from '@redux-devtools/ui';
import { changeStateTreeSettings } from '../../actions';
import { StoreState } from '../../reducers';
import { CoreStoreState } from '../../reducers';
type StateProps = ReturnType<typeof mapStateToProps>;
type DispatchProps = ResolveThunks<typeof actionCreators>;
@ -41,7 +41,7 @@ export class StateTree extends Component<Props> {
}
}
const mapStateToProps = (state: StoreState) => ({
const mapStateToProps = (state: CoreStoreState) => ({
theme: state.stateTreeSettings,
});

View File

@ -3,7 +3,7 @@ import { connect, ResolveThunks } from 'react-redux';
import { Container, Form } from '@redux-devtools/ui';
import { listSchemes, listThemes } from '@redux-devtools/ui';
import { changeTheme } from '../../actions';
import { StoreState } from '../../reducers';
import { CoreStoreState } from '../../reducers';
type StateProps = ReturnType<typeof mapStateToProps>;
type DispatchProps = ResolveThunks<typeof actionCreators>;
@ -49,7 +49,7 @@ export class Themes extends Component<Props> {
}
}
const mapStateToProps = (state: StoreState) => ({
const mapStateToProps = (state: CoreStoreState) => ({
theme: state.theme,
});

View File

@ -0,0 +1,38 @@
import React, { Component } from 'react';
import { Tabs } from '@redux-devtools/ui';
import Themes from './Themes';
import StateTree from './StateTree';
interface Props {
extraTabs?: { name: string; component: React.ComponentType }[];
}
interface State {
selected: string | undefined;
}
// eslint-disable-next-line @typescript-eslint/ban-types
export default class Settings extends Component<Props, State> {
state: State = { selected: undefined };
handleSelect = (selected: string) => {
this.setState({ selected });
};
render() {
const { extraTabs = [] } = this.props;
const tabs = [
...extraTabs,
{ name: 'Themes', component: Themes },
{ name: 'State Tree', component: StateTree },
];
return (
// eslint-disable-next-line @typescript-eslint/ban-types
<Tabs<{}>
tabs={tabs as any}
selected={this.state.selected || tabs[0].name}
onClick={this.handleSelect}
/>
);
}
}

View File

@ -2,7 +2,7 @@ import React, { Component } from 'react';
import { connect } from 'react-redux';
import { Button } from '@redux-devtools/ui';
import { IoIosLock } from 'react-icons/io';
import { lockChanges, StoreAction } from '../../actions';
import { lockChanges, CoreStoreAction } from '../../actions';
import { Dispatch } from 'redux';
type DispatchProps = ReturnType<typeof mapDispatchToProps>;
@ -33,7 +33,7 @@ class LockButton extends Component<Props> {
}
function mapDispatchToProps(
dispatch: Dispatch<StoreAction>,
dispatch: Dispatch<CoreStoreAction>,
ownProps: OwnProps,
) {
return {

View File

@ -3,7 +3,7 @@ import { connect, ResolveThunks } from 'react-redux';
import { Button } from '@redux-devtools/ui';
import { FaThumbtack } from 'react-icons/fa';
import { togglePersist } from '../../actions';
import { StoreState } from '../../reducers';
import { CoreStoreState } from '../../reducers';
type StateProps = ReturnType<typeof mapStateToProps>;
type DispatchProps = ResolveThunks<typeof actionCreators>;
@ -36,7 +36,7 @@ class LockButton extends Component<Props> {
}
}
const mapStateToProps = (state: StoreState) => ({
const mapStateToProps = (state: CoreStoreState) => ({
persisted: state.instances.persisted,
});

View File

@ -2,7 +2,7 @@ import React, { Component } from 'react';
import { connect } from 'react-redux';
import { Button } from '@redux-devtools/ui';
import { MdFiberManualRecord } from 'react-icons/md';
import { pauseRecording, StoreAction } from '../../actions';
import { pauseRecording, CoreStoreAction } from '../../actions';
import { Dispatch } from 'redux';
type DispatchProps = ReturnType<typeof mapDispatchToProps>;
@ -31,7 +31,7 @@ class RecordButton extends Component<Props> {
}
function mapDispatchToProps(
dispatch: Dispatch<StoreAction>,
dispatch: Dispatch<CoreStoreAction>,
ownProps: OwnProps,
) {
return {

View File

@ -3,7 +3,7 @@ import { connect, ResolveThunks } from 'react-redux';
import { Button } from '@redux-devtools/ui';
import { TiArrowSync } from 'react-icons/ti';
import { toggleSync } from '../../actions';
import { StoreState } from '../../reducers';
import { CoreStoreState } from '../../reducers';
type StateProps = ReturnType<typeof mapStateToProps>;
type DispatchProps = ResolveThunks<typeof actionCreators>;
@ -28,7 +28,7 @@ class SyncButton extends Component<Props> {
}
}
const mapStateToProps = (state: StoreState) => ({
const mapStateToProps = (state: CoreStoreState) => ({
sync: state.instances.sync,
});

View File

@ -6,6 +6,7 @@ export const UPDATE_STATE = 'devTools/UPDATE_STATE';
export const SET_STATE = 'devTools/SET_STATE';
export const SELECT_INSTANCE = 'devTools/SELECT_INSTANCE';
export const REMOVE_INSTANCE = 'devTools/REMOVE_INSTANCE';
export const CLEAR_INSTANCES = 'devTools/CLEAR_INSTANCES';
export const LIFTED_ACTION = 'devTools/LIFTED_ACTION';
export const MONITOR_ACTION = 'devTools/MONITOR_ACTION';
export const TOGGLE_SYNC = 'devTools/TOGGLE_SYNC';

View File

@ -4,11 +4,11 @@ import { Container } from '@redux-devtools/ui';
import SliderMonitor from './monitors/Slider';
import { liftedDispatch, getReport } from '../actions';
import { getActiveInstance } from '../reducers/instances';
import DevTools from '../containers/DevTools';
import DevTools from './DevTools';
import Dispatcher from './monitors/Dispatcher';
import TopButtons from '../components/TopButtons';
import BottomButtons from '../components/BottomButtons';
import { StoreState } from '../reducers';
import { CoreStoreState } from '../reducers';
type StateProps = ReturnType<typeof mapStateToProps>;
type DispatchProps = ResolveThunks<typeof actionCreators>;
@ -56,7 +56,7 @@ class Actions extends Component<Props> {
}
}
const mapStateToProps = (state: StoreState) => {
const mapStateToProps = (state: CoreStoreState) => {
const instances = state.instances;
const id = getActiveInstance(instances);
return {

View File

@ -3,21 +3,24 @@ import { connect, ResolveThunks } from 'react-redux';
import { Container, Notification } from '@redux-devtools/ui';
import { clearNotification } from '../actions';
import Header from '../components/Header';
import Actions from '../containers/Actions';
import Actions from './Actions';
import Settings from '../components/Settings';
import { StoreState } from '../reducers';
import { CoreStoreState } from '../reducers';
type StateProps = ReturnType<typeof mapStateToProps>;
type DispatchProps = ResolveThunks<typeof actionCreators>;
type Props = StateProps & DispatchProps;
type OwnProps = {
extraSettingsTabs?: { name: string; component: React.ComponentType }[];
};
type Props = StateProps & DispatchProps & OwnProps;
class App extends Component<Props> {
render() {
const { section, theme, notification } = this.props;
const { extraSettingsTabs, section, theme, notification } = this.props;
let body;
switch (section) {
case 'Settings':
body = <Settings />;
body = <Settings extraTabs={extraSettingsTabs} />;
break;
default:
body = <Actions />;
@ -40,7 +43,7 @@ class App extends Component<Props> {
}
}
const mapStateToProps = (state: StoreState) => ({
const mapStateToProps = (state: CoreStoreState) => ({
section: state.section,
theme: state.theme,
notification: state.notification,

View File

@ -12,7 +12,7 @@ import { selectMonitorTab } from '../../../actions';
import RawTab from './RawTab';
import ChartTab from './ChartTab';
import VisualDiffTab from './VisualDiffTab';
import { StoreState } from '../../../reducers';
import { CoreStoreState } from '../../../reducers';
import type { Delta } from 'jsondiffpatch';
type StateProps = ReturnType<typeof mapStateToProps>;
@ -99,7 +99,7 @@ class SubTabs extends Component<Props> {
}
}
const mapStateToProps = (state: StoreState) => ({
const mapStateToProps = (state: CoreStoreState) => ({
parentTab: state.monitor.monitorState!.tabName,
selected: state.monitor.monitorState!.subTabName,
});

View File

@ -0,0 +1,26 @@
export * from './actions';
export { default as DispatcherButton } from './components/buttons/DispatcherButton';
export { default as ExportButton } from './components/buttons/ExportButton';
export { default as ImportButton } from './components/buttons/ImportButton';
export { default as PrintButton } from './components/buttons/PrintButton';
export { default as SliderButton } from './components/buttons/SliderButton';
export { default as Header } from './components/Header';
export { default as MonitorSelector } from './components/MonitorSelector';
export { default as Settings } from './components/Settings';
export { default as TopButtons } from './components/TopButtons';
export { default as App } from './containers/App';
export { default as DevTools } from './containers/DevTools';
export { default as Dispatcher } from './containers/monitors/Dispatcher';
export { default as SliderMonitor } from './containers/monitors/Slider';
export * from './constants/actionTypes';
export { default as middlewares } from './middlewares';
export * from './middlewares/exportState';
export * from './reducers';
export * from './reducers/instances';
export * from './reducers/monitor';
export * from './reducers/notification';
export * from './reducers/reports';
export * from './reducers/section';
export * from './reducers/theme';
export * from './reducers/stateTreeSettings';
export * from './utils/stringifyJSON';

View File

@ -2,8 +2,8 @@ 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';
import { CoreStoreAction } from '../actions';
import { CoreStoreState } from '../reducers';
let toExport: string | number | undefined;
@ -23,9 +23,9 @@ function download(state: string) {
}
export const exportStateMiddleware =
(store: MiddlewareAPI<Dispatch<StoreAction>, StoreState>) =>
(next: Dispatch<StoreAction>) =>
(action: StoreAction) => {
(store: MiddlewareAPI<Dispatch<CoreStoreAction>, CoreStoreState>) =>
(next: Dispatch<CoreStoreAction>) =>
(action: CoreStoreAction) => {
const result = next(action);
if (

View File

@ -0,0 +1,5 @@
import { exportStateMiddleware } from './exportState';
const middlewares = [exportStateMiddleware];
export default middlewares;

View File

@ -0,0 +1,27 @@
import { section, SectionState } from './section';
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 { stateTreeSettings, StateTreeSettings } from './stateTreeSettings';
export interface CoreStoreState {
readonly section: SectionState;
readonly theme: ThemeState;
readonly stateTreeSettings: StateTreeSettings;
readonly monitor: MonitorState;
readonly instances: InstancesState;
readonly reports: ReportsState;
readonly notification: NotificationState;
}
export const coreReducers = {
section,
theme,
stateTreeSettings,
monitor,
instances,
reports,
notification,
};

View File

@ -9,15 +9,15 @@ import {
TOGGLE_PERSIST,
TOGGLE_SYNC,
SET_PERSIST,
CLEAR_INSTANCES,
} from '../constants/actionTypes';
import { DISCONNECTED } from '../constants/socketActionTypes';
import parseJSON from '../utils/parseJSON';
import { recompute } from '../utils/updateState';
import {
ActionCreator,
LiftedActionDispatchAction,
Request,
StoreAction,
CoreStoreAction,
} from '../actions';
export interface Features {
@ -305,7 +305,7 @@ function init(
export function instances(
state = instancesInitialState,
action: StoreAction,
action: CoreStoreAction,
): InstancesState {
switch (action.type) {
case UPDATE_STATE: {
@ -374,7 +374,7 @@ export function instances(
}
return state;
}
case DISCONNECTED:
case CLEAR_INSTANCES:
return instancesInitialState;
default:
return state;

View File

@ -6,7 +6,7 @@ import {
TOGGLE_SLIDER,
TOGGLE_DISPATCHER,
} from '../constants/actionTypes';
import { MonitorActionAction, StoreAction } from '../actions';
import { MonitorActionAction, CoreStoreAction } from '../actions';
export interface MonitorStateMonitorState {
inspectedStatePath?: string[];
@ -48,7 +48,7 @@ export function dispatchMonitorAction(
export function monitor(
state = initialState,
action: StoreAction,
action: CoreStoreAction,
): MonitorState {
switch (action.type) {
case MONITOR_ACTION:

View File

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

View File

@ -1,7 +1,7 @@
import {
UPDATE_REPORTS /* , GET_REPORT_SUCCESS */,
} from '../constants/actionTypes';
import { StoreAction } from '../actions';
import { CoreStoreAction } from '../actions';
export interface Data {
id: unknown;
@ -17,7 +17,7 @@ const initialState: ReportsState = {
export function reports(
state = initialState,
action: StoreAction,
action: CoreStoreAction,
): ReportsState {
/* if (action.type === GET_REPORT_SUCCESS) {
const id = action.data.id;

View File

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

View File

@ -1,5 +1,5 @@
import { CHANGE_STATE_TREE_SETTINGS } from '../constants/actionTypes';
import { StoreAction } from '../actions';
import { CoreStoreAction } from '../actions';
export interface StateTreeSettings {
readonly sortAlphabetically: boolean;
@ -11,7 +11,7 @@ export function stateTreeSettings(
sortAlphabetically: false,
disableCollection: false,
},
action: StoreAction,
action: CoreStoreAction,
) {
if (action.type === CHANGE_STATE_TREE_SETTINGS) {
return {

View File

@ -1,6 +1,6 @@
import { SchemeName, ThemeName } from '@redux-devtools/ui';
import { CHANGE_THEME } from '../constants/actionTypes';
import { StoreAction } from '../actions';
import { CoreStoreAction } from '../actions';
export interface ThemeState {
readonly theme: ThemeName;
@ -14,7 +14,7 @@ export function theme(
scheme: 'default',
colorPreference: 'auto',
},
action: StoreAction,
action: CoreStoreAction,
) {
if (action.type === CHANGE_THEME) {
return {

View File

@ -3,11 +3,11 @@ import { Provider } from 'react-redux';
import { createStore, applyMiddleware } from 'redux';
import { render, screen, within } from '@testing-library/react';
import App from '../src/containers/App';
import { api } from '../src/middlewares/api';
import { exportStateMiddleware } from '../src/middlewares/exportState';
import { rootReducer } from '../src/reducers';
import { coreReducers } from '../src/reducers';
import { DATA_TYPE_KEY } from '../src/constants/dataTypes';
import { stringifyJSON } from '../src/utils/stringifyJSON';
import { combineReducers } from 'redux';
Object.defineProperty(window, 'matchMedia', {
writable: true,
@ -24,8 +24,8 @@ Object.defineProperty(window, 'matchMedia', {
});
const store = createStore(
rootReducer,
applyMiddleware(exportStateMiddleware, api),
combineReducers(coreReducers),
applyMiddleware(exportStateMiddleware),
);
describe('App container', () => {

View File

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

View File

@ -1,7 +1,7 @@
{
"extends": "../../tsconfig.react.base.json",
"compilerOptions": {
"types": ["jest"]
"types": ["node", "jest"]
},
"include": ["src", "test"]
}

View File

@ -33,32 +33,17 @@
"build:umd": "node buildUmd.mjs --dev",
"build:umd:min": "node buildUmd.mjs",
"clean": "rimraf build lib umd",
"test": "jest",
"lint": "eslint . --ext .ts,.tsx",
"type-check": "tsc --noEmit",
"prepack": "pnpm run clean && pnpm run build",
"prepublish": "pnpm run type-check && pnpm run lint && pnpm run test"
},
"dependencies": {
"@babel/runtime": "^7.24.7",
"@redux-devtools/chart-monitor": "^5.0.1",
"@redux-devtools/core": "^4.0.0",
"@redux-devtools/inspector-monitor": "^6.0.0",
"@redux-devtools/inspector-monitor-test-tab": "^4.0.0",
"@redux-devtools/inspector-monitor-trace-tab": "^4.0.0",
"@redux-devtools/log-monitor": "^5.0.0",
"@redux-devtools/rtk-query-monitor": "^5.0.0",
"@redux-devtools/slider-monitor": "^5.0.0",
"@redux-devtools/ui": "^1.3.1",
"@reduxjs/toolkit": "^1.9.7",
"d3-state-visualizer": "^3.0.0",
"javascript-stringify": "^2.1.0",
"jsan": "^3.1.14",
"jsondiffpatch": "^0.6.0",
"@redux-devtools/app-core": "^1.0.0",
"@redux-devtools/ui": "^1.3.2",
"localforage": "^1.10.0",
"jsan": "^3.1.14",
"lodash": "^4.17.21",
"react-icons": "^5.2.1",
"react-is": "^18.3.1",
"react-redux": "^8.1.3",
"redux": "^4.2.1",
"redux-persist": "^6.0.0",
@ -74,10 +59,6 @@
"@babel/preset-typescript": "^7.24.7",
"@emotion/react": "^11.11.4",
"@rjsf/core": "^4.2.3",
"@testing-library/dom": "^10.1.0",
"@testing-library/jest-dom": "^6.4.5",
"@testing-library/react": "^16.0.0",
"@types/jest": "^29.5.12",
"@types/jsan": "^3.1.5",
"@types/json-schema": "^7.0.15",
"@types/lodash": "^4.17.4",
@ -95,14 +76,11 @@
"esbuild": "^0.21.4",
"eslint": "^8.57.0",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-jest": "^28.6.0",
"eslint-plugin-react": "^7.34.2",
"eslint-plugin-react-hooks": "^4.6.2",
"fork-ts-checker-webpack-plugin": "^9.0.2",
"html-loader": "^5.0.0",
"html-webpack-plugin": "^5.6.0",
"jest": "^29.7.0",
"jest-environment-jsdom": "^29.7.0",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"rimraf": "^5.0.7",

View File

@ -1,32 +1,11 @@
import { SchemeName, ThemeName } from '@redux-devtools/ui';
import { AuthStates, States } from 'socketcluster-client/lib/clientsocket';
import { REHYDRATE } from 'redux-persist';
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,
CoreStoreActionWithoutUpdateStateOrLiftedAction,
LiftedActionAction,
UPDATE_REPORTS,
REMOVE_INSTANCE,
SET_STATE,
GET_REPORT_ERROR,
GET_REPORT_SUCCESS,
ERROR,
SET_PERSIST,
CHANGE_STATE_TREE_SETTINGS,
} from '../constants/actionTypes';
UPDATE_STATE,
UpdateStateAction,
} from '@redux-devtools/app-core';
import {
AUTH_ERROR,
AUTH_REQUEST,
@ -43,315 +22,6 @@ import {
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/core';
import { Data } from '../reducers/reports';
import { LiftedState } from '@redux-devtools/core';
let monitorReducer: (
monitorProps: unknown,
state: unknown | undefined,
action: Action<string>,
) => unknown;
let monitorProps: unknown = {};
export 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: ThemeName;
readonly scheme: SchemeName;
readonly colorPreference: 'auto' | 'light' | 'dark';
}
interface ChangeThemeData {
readonly formData: ChangeThemeFormData;
}
export interface ChangeThemeAction {
readonly type: typeof CHANGE_THEME;
readonly theme: ThemeName;
readonly scheme: SchemeName;
readonly colorPreference: 'auto' | 'light' | 'dark';
}
export function changeTheme(data: ChangeThemeData): ChangeThemeAction {
return { type: CHANGE_THEME, ...data.formData };
}
interface ChangeStateTreeSettingsFormData {
readonly sortAlphabetically: boolean;
readonly disableCollection: boolean;
}
interface ChangeStateTreeSettingsData {
readonly formData: ChangeStateTreeSettingsFormData;
}
export interface ChangeStateTreeSettingsAction {
readonly type: typeof CHANGE_STATE_TREE_SETTINGS;
readonly sortAlphabetically: boolean;
readonly disableCollection: boolean;
}
export function changeStateTreeSettings(
data: ChangeStateTreeSettingsData,
): ChangeStateTreeSettingsAction {
return { type: CHANGE_STATE_TREE_SETTINGS, ...data.formData };
}
export interface InitMonitorAction {
type: '@@INIT_MONITOR';
newMonitorState: unknown;
update: (
monitorProps: unknown,
state: unknown | undefined,
action: Action<string>,
) => unknown;
monitorProps: unknown;
}
export interface MonitorActionAction {
type: typeof MONITOR_ACTION;
action: InitMonitorAction;
monitorReducer: (
monitorProps: unknown,
state: unknown | undefined,
action: Action<string>,
) => unknown;
monitorProps: unknown;
}
export interface JumpToStateAction {
type: 'JUMP_TO_STATE';
index: number;
}
export interface JumpToActionAction {
type: 'JUMP_TO_ACTION';
actionId: number;
}
export interface PauseRecordingAction {
type: 'PAUSE_RECORDING';
status: boolean;
}
export interface LockChangesAction {
type: 'LOCK_CHANGES';
status: boolean;
}
export interface ToggleActionAction {
type: 'TOGGLE_ACTION';
id: number;
}
export interface RollbackAction {
type: 'ROLLBACK';
timestamp: number;
}
export interface SweepAction {
type: 'SWEEP';
}
interface ReorderActionAction {
type: 'REORDER_ACTION';
actionId: number;
beforeActionId: number;
}
interface ImportStateAction {
type: 'IMPORT_STATE';
nextLiftedState:
| LiftedState<unknown, Action<string>, unknown>
| readonly Action<string>[];
preloadedState?: unknown;
noRecompute?: boolean | undefined;
}
export type DispatchAction =
| JumpToStateAction
| JumpToActionAction
| PauseRecordingAction
| LockChangesAction
| ToggleActionAction
| RollbackAction
| SweepAction
| ReorderActionAction
| ImportStateAction;
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;
}
export interface LiftedActionImportAction extends LiftedActionActionBase {
type: typeof LIFTED_ACTION;
message: 'IMPORT';
state: string;
preloadedState: unknown | undefined;
}
export interface LiftedActionActionAction extends LiftedActionActionBase {
type: typeof LIFTED_ACTION;
message: 'ACTION';
action: string | CustomAction;
}
export 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<string>, 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;
}
export interface SelectInstanceAction {
type: typeof SELECT_INSTANCE;
selected: string | number;
}
export function selectInstance(selected: string): SelectInstanceAction {
return { type: SELECT_INSTANCE, selected };
}
export 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[];
}
export 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 };
}
export 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[];
rest: string;
}
export function dispatchRemotely(
action: string | CustomAction,
): LiftedActionActionAction {
return { type: LIFTED_ACTION, message: 'ACTION', action };
}
export interface TogglePersistAction {
type: typeof TOGGLE_PERSIST;
}
export function togglePersist(): TogglePersistAction {
return { type: TOGGLE_PERSIST };
}
export interface SetPersistAction {
type: typeof SET_PERSIST;
payload: boolean;
}
export function setPersist(persist: boolean): SetPersistAction {
return { type: SET_PERSIST, payload: persist };
}
export interface ToggleSyncAction {
type: typeof TOGGLE_SYNC;
}
export function toggleSync(): ToggleSyncAction {
return { type: TOGGLE_SYNC };
}
export interface ToggleSliderAction {
type: typeof TOGGLE_SLIDER;
}
export function toggleSlider(): ToggleSliderAction {
return { type: TOGGLE_SLIDER };
}
export interface ToggleDispatcherAction {
type: typeof TOGGLE_DISPATCHER;
}
export function toggleDispatcher(): ToggleDispatcherAction {
return { type: TOGGLE_DISPATCHER };
}
export type ConnectionType = 'disabled' | 'custom';
export interface ConnectionOptions {
@ -370,110 +40,6 @@ export function saveSocketSettings(
return { type: RECONNECT, options };
}
interface Notification {
readonly type: 'error';
readonly message: string;
}
export interface ShowNotificationAction {
readonly type: typeof SHOW_NOTIFICATION;
readonly notification: Notification;
}
export function showNotification(message: string): ShowNotificationAction {
return { type: SHOW_NOTIFICATION, notification: { type: 'error', message } };
}
export interface ClearNotificationAction {
readonly type: typeof CLEAR_NOTIFICATION;
}
export function clearNotification(): ClearNotificationAction {
return { type: CLEAR_NOTIFICATION };
}
export 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;
}
export interface LibConfig {
actionCreators?: string;
name?: string;
type?: string;
features?: Features;
serialize?: boolean;
}
export interface RequestBase {
id?: string;
instanceId?: string | number;
action?: string;
name?: string | undefined;
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;
payload?: 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;
export interface UpdateStateAction {
type: typeof UPDATE_STATE;
request?: Request;
id?: string | number;
}
export interface SetStateAction {
type: typeof SET_STATE;
newState: State;
}
export interface RemoveInstanceAction {
type: typeof REMOVE_INSTANCE;
id: string | number;
}
export interface ConnectRequestAction {
type: typeof CONNECT_REQUEST;
}
@ -548,65 +114,9 @@ export interface EmitAction {
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;
export interface UpdateReportsAction {
type: typeof UPDATE_REPORTS;
request: UpdateReportsRequest;
}
export interface GetReportError {
type: typeof GET_REPORT_ERROR;
error: Error;
}
export interface GetReportSuccess {
type: typeof GET_REPORT_SUCCESS;
data: { payload: string };
}
export interface ErrorAction {
type: typeof ERROR;
payload: string;
}
interface ReduxPersistRehydrateAction {
type: typeof REHYDRATE;
payload: unknown;
}
export type StoreActionWithoutUpdateStateOrLiftedAction =
| ChangeSectionAction
| ChangeThemeAction
| ChangeStateTreeSettingsAction
| MonitorActionAction
| SelectInstanceAction
| SelectMonitorAction
| UpdateMonitorStateAction
| ExportAction
| TogglePersistAction
| SetPersistAction
| ToggleSyncAction
| ToggleSliderAction
| ToggleDispatcherAction
| CoreStoreActionWithoutUpdateStateOrLiftedAction
| ReconnectAction
| ShowNotificationAction
| ClearNotificationAction
| GetReportRequest
| SetStateAction
| RemoveInstanceAction
| ConnectRequestAction
| ConnectSuccessAction
| ConnectErrorAction
@ -619,12 +129,7 @@ export type StoreActionWithoutUpdateStateOrLiftedAction =
| SubscribeSuccessAction
| SubscribeErrorAction
| UnsubscribeAction
| EmitAction
| UpdateReportsAction
| GetReportError
| GetReportSuccess
| ErrorAction
| ReduxPersistRehydrateAction;
| EmitAction;
export type StoreActionWithoutUpdateState =
| StoreActionWithoutUpdateStateOrLiftedAction

View File

@ -1,34 +0,0 @@
import React, { Component } from 'react';
import { Tabs } from '@redux-devtools/ui';
import Connection from './Connection';
import Themes from './Themes';
import StateTree from './StateTree';
interface State {
selected: string;
}
// eslint-disable-next-line @typescript-eslint/ban-types
export default class Settings extends Component<{}, State> {
tabs = [
{ name: 'Connection', component: Connection },
{ name: 'Themes', component: Themes },
{ name: 'State Tree', component: StateTree },
];
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}
/>
);
}
}

View File

@ -3,11 +3,12 @@ import { Provider } from 'react-redux';
import { Store } from 'redux';
import { Persistor } from 'redux-persist';
import { PersistGate } from 'redux-persist/integration/react';
import configureStore from './store/configureStore';
import { CONNECT_REQUEST } from './constants/socketActionTypes';
import App from './containers/App';
import { App } from '@redux-devtools/app-core';
import { StoreState } from './reducers';
import { StoreAction } from './actions';
import { CONNECT_REQUEST } from './constants/socketActionTypes';
import Connection from './components/Settings/Connection';
import configureStore from './store/configureStore';
export class Root extends Component {
store?: Store<StoreState, StoreAction>;
@ -32,39 +33,20 @@ export class Root extends Component {
return (
<Provider store={this.store}>
<PersistGate loading={null} persistor={this.persistor!}>
<App />
<App
extraSettingsTabs={[{ name: 'Connection', component: Connection }]}
/>
</PersistGate>
</Provider>
);
}
}
export * from '@redux-devtools/app-core';
export * from './actions';
export { default as DispatcherButton } from './components/buttons/DispatcherButton';
export { default as ExportButton } from './components/buttons/ExportButton';
export { default as ImportButton } from './components/buttons/ImportButton';
export { default as PrintButton } from './components/buttons/PrintButton';
export { default as SliderButton } from './components/buttons/SliderButton';
export { default as Header } from './components/Header';
export { default as MonitorSelector } from './components/MonitorSelector';
export { default as Settings } from './components/Settings';
export { default as TopButtons } from './components/TopButtons';
export { default as DevTools } from './containers/DevTools';
export { default as Dispatcher } from './containers/monitors/Dispatcher';
export { default as SliderMonitor } from './containers/monitors/Slider';
export * from './constants/actionTypes';
export * from './constants/socketActionTypes';
export * from './middlewares/api';
export * from './middlewares/exportState';
export * from './reducers';
export * from './reducers/connection';
export * from './reducers/instances';
export * from './reducers/monitor';
export * from './reducers/notification';
export * from './reducers/reports';
export * from './reducers/section';
export * from './reducers/socket';
export * from './reducers/theme';
export * from './reducers/stateTreeSettings';
export * from './utils/monitorActions';
export * from './utils/stringifyJSON';

View File

@ -1,28 +1,26 @@
import {
DispatchAction,
GET_REPORT_ERROR,
GET_REPORT_REQUEST,
GET_REPORT_SUCCESS,
CLEAR_INSTANCES,
getActiveInstance,
importState,
LIFTED_ACTION,
LiftedActionAction,
REMOVE_INSTANCE,
Request,
showNotification,
UPDATE_REPORTS,
UPDATE_STATE,
UpdateReportsRequest,
} from '@redux-devtools/app-core';
import socketClusterClient, { AGClientSocket } from 'socketcluster-client';
import { stringify } from 'jsan';
import { Dispatch, MiddlewareAPI } from 'redux';
import * as actions from '../constants/socketActionTypes';
import { getActiveInstance } from '../reducers/instances';
import {
UPDATE_STATE,
REMOVE_INSTANCE,
LIFTED_ACTION,
UPDATE_REPORTS,
GET_REPORT_REQUEST,
GET_REPORT_ERROR,
GET_REPORT_SUCCESS,
} from '../constants/actionTypes';
import {
showNotification,
importState,
StoreAction,
EmitAction,
LiftedActionAction,
Request,
DispatchAction,
UpdateReportsRequest,
} from '../actions';
import { nonReduxDispatch } from '../utils/monitorActions';
import { EmitAction, StoreAction } from '../actions';
import { StoreState } from '../reducers';
let socket: AGClientSocket;
@ -178,6 +176,7 @@ function handleConnection() {
void (async () => {
for await (const data of socket.listener('disconnect')) {
store.dispatch({ type: actions.DISCONNECTED, code: data.code });
store.dispatch({ type: CLEAR_INSTANCES });
}
})();

View File

@ -1,35 +1,17 @@
import { CoreStoreState, coreReducers } from '@redux-devtools/app-core';
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';
import { stateTreeSettings, StateTreeSettings } from './stateTreeSettings';
export interface StoreState {
readonly section: SectionState;
readonly theme: ThemeState;
readonly stateTreeSettings: StateTreeSettings;
export interface StoreState extends CoreStoreState {
readonly connection: ConnectionState;
readonly socket: SocketState;
readonly monitor: MonitorState;
readonly instances: InstancesState;
readonly reports: ReportsState;
readonly notification: NotificationState;
}
/// @ts-expect-error An error happens due to TypeScript not being able to reconcile a clash between CoreStoreAction and StoreAction in the core reducers, but this is correct as they're a superset
export const rootReducer = combineReducers<StoreState, StoreAction>({
section,
theme,
stateTreeSettings,
...coreReducers,
connection,
socket,
monitor,
instances,
reports,
notification,
});

View File

@ -1,9 +1,9 @@
import { middlewares } from '@redux-devtools/app-core';
import { createStore, compose, applyMiddleware, Reducer, Store } from 'redux';
import localForage from 'localforage';
import { persistReducer, persistStore } from 'redux-persist';
import { api } from '../middlewares/api';
import { exportStateMiddleware } from '../middlewares/exportState';
import { rootReducer, StoreState } from '../reducers';
import { StoreState, rootReducer } from '../reducers';
import { StoreAction } from '../actions';
const persistConfig = {
@ -39,7 +39,7 @@ export default function configureStore(
const store = createStore(
persistedReducer,
composeEnhancers(applyMiddleware(exportStateMiddleware, api)),
composeEnhancers(applyMiddleware(...middlewares, api)),
);
const persistor = persistStore(store, null, () => {
callback(store);

View File

@ -1,10 +1,14 @@
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 {
DispatchAction,
InstancesState,
SET_STATE,
State,
stringifyJSON,
} from '@redux-devtools/app-core';
import { Dispatch, MiddlewareAPI } from 'redux';
import { DispatchAction, StoreActionWithoutLiftedAction } from '../actions';
import { StoreActionWithoutLiftedAction } from '../actions';
export function sweep(state: State): State {
return {

File diff suppressed because it is too large Load Diff