diff --git a/packages/redux-devtools-inspector/.babelrc b/packages/redux-devtools-inspector/.babelrc index 530ac9c9..e073a7e7 100644 --- a/packages/redux-devtools-inspector/.babelrc +++ b/packages/redux-devtools-inspector/.babelrc @@ -1,9 +1,10 @@ { - "presets": ["@babel/preset-env", "@babel/preset-react"], + "presets": [ + "@babel/env", + "@babel/react", + "@babel/typescript" + ], "plugins": [ - "@babel/plugin-transform-runtime", - "@babel/plugin-proposal-class-properties", - "@babel/plugin-proposal-export-default-from", - "@babel/plugin-proposal-do-expressions" + "@babel/proposal-class-properties" ] } diff --git a/packages/redux-devtools-inspector/.eslintignore b/packages/redux-devtools-inspector/.eslintignore new file mode 100644 index 00000000..b1e0862f --- /dev/null +++ b/packages/redux-devtools-inspector/.eslintignore @@ -0,0 +1,2 @@ +lib +demo diff --git a/packages/redux-devtools-inspector/.eslintrc.js b/packages/redux-devtools-inspector/.eslintrc.js new file mode 100644 index 00000000..1eb24d2a --- /dev/null +++ b/packages/redux-devtools-inspector/.eslintrc.js @@ -0,0 +1,13 @@ +module.exports = { + extends: '../../.eslintrc', + overrides: [ + { + files: ['*.ts', '*.tsx'], + extends: '../../eslintrc.ts.react.base.json', + parserOptions: { + tsconfigRootDir: __dirname, + project: ['./tsconfig.json'] + } + } + ] +}; diff --git a/packages/redux-devtools-inspector/.npmignore b/packages/redux-devtools-inspector/.npmignore deleted file mode 100644 index 7e7ce1e8..00000000 --- a/packages/redux-devtools-inspector/.npmignore +++ /dev/null @@ -1,8 +0,0 @@ -static -src -demo -.* -webpack.config.js -index.html -*.gif -*.png diff --git a/packages/redux-devtools-inspector/.prettierignore b/packages/redux-devtools-inspector/.prettierignore new file mode 100644 index 00000000..b1e0862f --- /dev/null +++ b/packages/redux-devtools-inspector/.prettierignore @@ -0,0 +1,2 @@ +lib +demo diff --git a/packages/redux-devtools-inspector/package.json b/packages/redux-devtools-inspector/package.json index f785a2e4..339beac5 100644 --- a/packages/redux-devtools-inspector/package.json +++ b/packages/redux-devtools-inspector/package.json @@ -3,19 +3,25 @@ "version": "0.11.0", "description": "Redux DevTools Diff Monitor", "scripts": { - "build": "npm run build:lib", - "build:lib": "cross-env NODE_ENV=production babel src --out-dir lib", + "type-check": "tsc --noEmit", + "type-check:watch": "npm run type-check -- --watch", + "build": "npm run build:types && npm run build:js", + "build:types": "tsc --emitDeclarationOnly", + "build:js": "babel src --out-dir lib --extensions \".ts,.tsx\" --source-maps inline", + "lint": "eslint . --ext .js,.jsx,.ts,.tsx", + "lint:fix": "eslint . --ext .js,.jsx,.ts,.tsx --fix", "build:demo": "cross-env NODE_ENV=production webpack -p", "stats": "webpack --profile --json > stats.json", "start": "webpack-dev-server", "preversion": "npm run lint", "version": "npm run build:demo && git add -A .", "postversion": "git push", - "prepare": "npm run build:lib", - "prepublishOnly": "npm run build:lib", + "prepare": "npm run build", + "prepublishOnly": "npm run build", "gh": "git subtree push --prefix demo/dist origin gh-pages" }, "main": "lib/index.js", + "types": "lib/index.d.ts", "repository": { "url": "https://github.com/reduxjs/redux-devtools" }, @@ -28,11 +34,17 @@ "@babel/plugin-transform-runtime": "^7.2.0", "@babel/preset-env": "^7.3.1", "@babel/preset-react": "^7.0.0", + "@types/dateformat": "^3.0.1", + "@types/dragula": "^3.7.0", + "@types/hex-rgba": "^1.0.0", + "@typescript-eslint/eslint-plugin": "^2.31.0", + "@typescript-eslint/parser": "^2.31.0", "babel-loader": "^8.0.5", "base16": "^1.0.0", "chokidar": "^1.6.1", "clean-webpack-plugin": "^1.0.0", "cross-env": "^5.2.0", + "csstype": "^2.6.10", "export-files-webpack-plugin": "0.0.1", "html-webpack-plugin": "^3.2.0", "lodash.shuffle": "^4.2.0", @@ -50,13 +62,14 @@ "redux-devtools": "^3.1.0", "redux-devtools-dock-monitor": "^1.0.1", "redux-logger": "^2.5.2", + "typescript": "^3.8.3", "webpack": "^4.27.1", "webpack-cli": "^3.2.0", "webpack-dev-server": "^3.1.14" }, "peerDependencies": { - "react": ">=15.0.0", - "react-dom": ">=15.0.0" + "@types/react": "^16.3.0", + "react": "^16.3.0" }, "author": "Alexander (http://kuzya.org/)", "contributors": [ @@ -64,6 +77,7 @@ ], "license": "MIT", "dependencies": { + "@types/prop-types": "^15.6.2", "babel-runtime": "^6.3.19", "dateformat": "^1.0.12", "hex-rgba": "^1.0.0", diff --git a/packages/redux-devtools-inspector/src/.noderequirer.json b/packages/redux-devtools-inspector/src/.noderequirer.json deleted file mode 100644 index 20e76308..00000000 --- a/packages/redux-devtools-inspector/src/.noderequirer.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "import": true -} diff --git a/packages/redux-devtools-inspector/src/ActionList.jsx b/packages/redux-devtools-inspector/src/ActionList.tsx similarity index 61% rename from packages/redux-devtools-inspector/src/ActionList.jsx rename to packages/redux-devtools-inspector/src/ActionList.tsx index 66ce8a3b..e6317078 100644 --- a/packages/redux-devtools-inspector/src/ActionList.jsx +++ b/packages/redux-devtools-inspector/src/ActionList.tsx @@ -1,10 +1,18 @@ -import React, { Component } from 'react'; +import React, { Component, RefCallback } from 'react'; import dragula from 'react-dragula'; import ActionListRow from './ActionListRow'; import ActionListHeader from './ActionListHeader'; import shouldPureComponentUpdate from 'react-pure-render/function'; +import { Action } from 'redux'; +import { PerformAction } from 'redux-devtools'; +import { StylingFunction } from 'react-base16-styling'; +import { Drake } from 'dragula'; -function getTimestamps(actions, actionIds, actionId) { +function getTimestamps>( + actions: { [actionId: number]: PerformAction }, + actionIds: number[], + actionId: number +) { const idx = actionIds.indexOf(actionId); const prevActionId = actionIds[idx - 1]; @@ -14,10 +22,40 @@ function getTimestamps(actions, actionIds, actionId) { }; } -export default class ActionList extends Component { +interface Props> { + actions: { [actionId: number]: PerformAction }; + actionIds: number[]; + isWideLayout: boolean; + searchValue: string | undefined; + selectedActionId: number | null; + startActionId: number | null; + skippedActionIds: number[]; + draggableActions: boolean; + hideMainButtons: boolean | undefined; + hideActionButtons: boolean | undefined; + styling: StylingFunction; + + onSelect: (event: React.MouseEvent, actionId: number) => void; + onSearch: (value: string) => void; + onToggleAction: (actionId: number) => void; + onJumpToState: (actionId: number) => void; + onCommit: () => void; + onSweep: () => void; + onReorderAction: (actionId: number, beforeActionId: number) => void; + currentActionId: number; + lastActionId: number; +} + +export default class ActionList> extends Component< + Props +> { shouldComponentUpdate = shouldPureComponentUpdate; - componentWillReceiveProps(nextProps) { + node?: HTMLDivElement | null; + scrollDown?: boolean; + drake?: Drake; + + componentWillReceiveProps(nextProps: Props) { const node = this.node; if (!node) { this.scrollDown = true; @@ -35,22 +73,22 @@ export default class ActionList extends Component { this.scrollToBottom(); if (!this.props.draggableActions) return; - const container = this.node; + const container = this.node!; this.drake = dragula([container], { copy: false, copySortSource: false, mirrorContainer: container, accepts: (el, target, source, sibling) => - !sibling || parseInt(sibling.getAttribute('data-id')), + !sibling || !!parseInt(sibling.getAttribute('data-id')!), moves: (el, source, handle) => - parseInt(el.getAttribute('data-id')) && - handle.className.indexOf('selectorButton') !== 0 + !!parseInt(el!.getAttribute('data-id')!) && + !handle!.className.startsWith('selectorButton') }).on('drop', (el, target, source, sibling) => { let beforeActionId = this.props.actionIds.length; - if (sibling && sibling.className.indexOf('gu-mirror') === -1) { - beforeActionId = parseInt(sibling.getAttribute('data-id')); + if (sibling && !sibling.className.includes('gu-mirror')) { + beforeActionId = parseInt(sibling.getAttribute('data-id')!); } - const actionId = parseInt(el.getAttribute('data-id')); + const actionId = parseInt(el.getAttribute('data-id')!); this.props.onReorderAction(actionId, beforeActionId); }); } @@ -69,7 +107,7 @@ export default class ActionList extends Component { } } - getRef = node => { + getRef: RefCallback = node => { this.node = node; }; @@ -95,10 +133,10 @@ export default class ActionList extends Component { } = this.props; const lowerSearchValue = searchValue && searchValue.toLowerCase(); const filteredActionIds = searchValue - ? actionIds.filter( - id => - actions[id].action.type.toLowerCase().indexOf(lowerSearchValue) !== - -1 + ? actionIds.filter(id => + (actions[id].action.type as string) + .toLowerCase() + .includes(lowerSearchValue as string) ) : actionIds; @@ -129,20 +167,22 @@ export default class ActionList extends Component { isSelected={ (startActionId !== null && actionId >= startActionId && - actionId <= selectedActionId) || + actionId <= (selectedActionId as number)) || actionId === selectedActionId } isInFuture={ actionIds.indexOf(actionId) > actionIds.indexOf(currentActionId) } - onSelect={e => onSelect(e, actionId)} + onSelect={(e: React.MouseEvent) => + onSelect(e, actionId) + } timestamps={getTimestamps(actions, actionIds, actionId)} action={actions[actionId].action} onToggleClick={() => onToggleAction(actionId)} onJumpClick={() => onJumpToState(actionId)} - onCommitClick={() => onCommit(actionId)} + onCommitClick={() => onCommit()} hideActionButtons={hideActionButtons} - isSkipped={skippedActionIds.indexOf(actionId) !== -1} + isSkipped={skippedActionIds.includes(actionId)} /> ))} diff --git a/packages/redux-devtools-inspector/src/ActionListHeader.jsx b/packages/redux-devtools-inspector/src/ActionListHeader.tsx similarity index 57% rename from packages/redux-devtools-inspector/src/ActionListHeader.jsx rename to packages/redux-devtools-inspector/src/ActionListHeader.tsx index eb086f37..8ac97037 100644 --- a/packages/redux-devtools-inspector/src/ActionListHeader.jsx +++ b/packages/redux-devtools-inspector/src/ActionListHeader.tsx @@ -1,10 +1,25 @@ import React from 'react'; +import PropTypes from 'prop-types'; import RightSlider from './RightSlider'; +import { StylingFunction } from 'react-base16-styling'; -const getActiveButtons = hasSkippedActions => - [hasSkippedActions && 'Sweep', 'Commit'].filter(a => a); +const getActiveButtons = (hasSkippedActions: boolean): ('Sweep' | 'Commit')[] => + [hasSkippedActions && 'Sweep', 'Commit'].filter(a => a) as ( + | 'Sweep' + | 'Commit' + )[]; -const ActionListHeader = ({ +interface Props { + styling: StylingFunction; + onSearch: (value: string) => void; + hasSkippedActions: boolean; + hasStagedActions: boolean; + onCommit: () => void; + onSweep: () => void; + hideMainButtons: boolean | undefined; +} + +const ActionListHeader: React.FunctionComponent = ({ styling, onSearch, hasSkippedActions, @@ -48,4 +63,14 @@ const ActionListHeader = ({ ); +ActionListHeader.propTypes = { + styling: PropTypes.func.isRequired, + onSearch: PropTypes.func.isRequired, + hasSkippedActions: PropTypes.bool.isRequired, + hasStagedActions: PropTypes.bool.isRequired, + onCommit: PropTypes.func.isRequired, + onSweep: PropTypes.func.isRequired, + hideMainButtons: PropTypes.bool +}; + export default ActionListHeader; diff --git a/packages/redux-devtools-inspector/src/ActionListRow.jsx b/packages/redux-devtools-inspector/src/ActionListRow.tsx similarity index 71% rename from packages/redux-devtools-inspector/src/ActionListRow.jsx rename to packages/redux-devtools-inspector/src/ActionListRow.tsx index 838ca567..0e6639b0 100644 --- a/packages/redux-devtools-inspector/src/ActionListRow.jsx +++ b/packages/redux-devtools-inspector/src/ActionListRow.tsx @@ -1,14 +1,41 @@ import React, { Component } from 'react'; -import { PropTypes } from 'prop-types'; +import PropTypes from 'prop-types'; import shouldPureComponentUpdate from 'react-pure-render/function'; import dateformat from 'dateformat'; import debounce from 'lodash.debounce'; import RightSlider from './RightSlider'; +import { StylingFunction } from 'react-base16-styling'; +import { Action } from 'redux'; const BUTTON_SKIP = 'Skip'; const BUTTON_JUMP = 'Jump'; -export default class ActionListRow extends Component { +type Button = typeof BUTTON_SKIP | typeof BUTTON_JUMP; + +interface Props> { + styling: StylingFunction; + isSelected: boolean; + action: A; + actionId: number; + isInitAction: boolean; + onSelect: React.MouseEventHandler | undefined; + timestamps: { current: number; previous: number }; + isSkipped: boolean; + isInFuture: boolean; + hideActionButtons: boolean | undefined; + onToggleClick: () => void; + onJumpClick: () => void; + onCommitClick: () => void; +} + +interface State { + hover: boolean; +} + +export default class ActionListRow> extends Component< + Props, + State +> { state = { hover: false }; static propTypes = { @@ -44,18 +71,24 @@ export default class ActionListRow extends Component { const timeDelta = timestamps.current - timestamps.previous; const showButtons = (hover && !isInitAction) || isSkipped; - const isButtonSelected = btn => btn === BUTTON_SKIP && isSkipped; + const isButtonSelected = (btn: Button) => btn === BUTTON_SKIP && isSkipped; let actionType = action.type; if (typeof actionType === 'undefined') actionType = ''; else if (actionType === null) actionType = ''; - else actionType = actionType.toString() || ''; + else actionType = (actionType as string).toString() || ''; return (
+ } + onMouseLeave={ + (!hideActionButtons && + this.handleMouseLeave) as React.MouseEventHandler + } onMouseDown={this.handleMouseDown} onMouseUp={this.handleMouseEnter} data-id={actionId} @@ -76,7 +109,7 @@ export default class ActionListRow extends Component { isSkipped && 'actionListItemNameSkipped' ])} > - {actionType} + {actionType as string}
{hideActionButtons ? ( @@ -103,12 +136,12 @@ export default class ActionListRow extends Component {
- {[BUTTON_JUMP, BUTTON_SKIP].map( + {([BUTTON_JUMP, BUTTON_SKIP] as const).map( btn => (!isInitAction || btn !== BUTTON_SKIP) && (
this.handleButtonClick(btn, e)} {...styling( [ 'selectorButton', @@ -131,7 +164,7 @@ export default class ActionListRow extends Component { ); } - handleButtonClick(btn, e) { + handleButtonClick = (btn: Button, e: React.MouseEvent) => { e.stopPropagation(); switch (btn) { @@ -142,10 +175,10 @@ export default class ActionListRow extends Component { this.props.onJumpClick(); break; } - } + }; - handleMouseEnter = e => { - if (this.hover) return; + handleMouseEnter = (e: React.MouseEvent) => { + if (this.state.hover) return; this.handleMouseLeave.cancel(); this.handleMouseEnterDebounced(e.buttons); }; @@ -160,8 +193,13 @@ export default class ActionListRow extends Component { if (this.state.hover) this.setState({ hover: false }); }, 100); - handleMouseDown = e => { - if (e.target.className.indexOf('selectorButton') === 0) return; + handleMouseDown = (e: React.MouseEvent) => { + if ( + ((e.target as unknown) as { className: string[] }).className.indexOf( + 'selectorButton' + ) === 0 + ) + return; this.handleMouseLeave(); }; } diff --git a/packages/redux-devtools-inspector/src/ActionPreview.jsx b/packages/redux-devtools-inspector/src/ActionPreview.tsx similarity index 52% rename from packages/redux-devtools-inspector/src/ActionPreview.jsx rename to packages/redux-devtools-inspector/src/ActionPreview.tsx index 7a4e25f7..8d054f77 100644 --- a/packages/redux-devtools-inspector/src/ActionPreview.jsx +++ b/packages/redux-devtools-inspector/src/ActionPreview.tsx @@ -1,9 +1,41 @@ import React, { Component } from 'react'; -import { DEFAULT_STATE } from './redux'; +import { DEFAULT_STATE, MonitorState } from './redux'; import ActionPreviewHeader from './ActionPreviewHeader'; import DiffTab from './tabs/DiffTab'; import StateTab from './tabs/StateTab'; import ActionTab from './tabs/ActionTab'; +import { Base16Theme, StylingFunction } from 'react-base16-styling'; +import { Delta } from 'jsondiffpatch'; +import { Action } from 'redux'; +import { PerformAction } from 'redux-devtools'; + +export interface TabComponentProps> { + labelRenderer: ( + keyPath: (string | number)[], + nodeType: string, + expanded: boolean, + expandable: boolean + ) => React.ReactNode; + styling: StylingFunction; + computedStates: { state: S; error?: string }[]; + actions: { [actionId: number]: PerformAction }; + selectedActionId: number | null; + startActionId: number | null; + base16Theme: Base16Theme; + invertTheme: boolean; + isWideLayout: boolean; + dataTypeKey: string | undefined; + delta: Delta | null | undefined | false; + action: A; + nextState: S; + monitorState: MonitorState; + updateMonitorState: (monitorState: Partial) => void; +} + +export interface Tab> { + name: string; + component: React.ComponentType>; +} const DEFAULT_TABS = [ { @@ -20,7 +52,32 @@ const DEFAULT_TABS = [ } ]; -class ActionPreview extends Component { +interface Props> { + styling: StylingFunction; + delta: Delta | null | undefined | false; + error: string | undefined; + nextState: S; + onInspectPath: (path: (string | number)[]) => void; + inspectedPath: (string | number)[]; + tabName: string; + isWideLayout: boolean; + onSelectTab: (tabName: string) => void; + action: A; + actions: { [actionId: number]: PerformAction }; + selectedActionId: number | null; + startActionId: number | null; + computedStates: { state: S; error?: string }[]; + base16Theme: Base16Theme; + invertTheme: boolean; + tabs: Tab[] | ((tabs: Tab[]) => Tab[]); + dataTypeKey: string | undefined; + monitorState: MonitorState; + updateMonitorState: (monitorState: Partial) => void; +} + +class ActionPreview> extends Component< + Props +> { static defaultProps = { tabName: DEFAULT_STATE.tabName }; @@ -49,21 +106,21 @@ class ActionPreview extends Component { updateMonitorState } = this.props; - const renderedTabs = + const renderedTabs: Tab[] = typeof tabs === 'function' - ? tabs(DEFAULT_TABS) + ? tabs(DEFAULT_TABS as Tab[]) : tabs ? tabs - : DEFAULT_TABS; + : (DEFAULT_TABS as Tab[]); const { component: TabComponent } = - renderedTabs.find(tab => tab.name === tabName) || - renderedTabs.find(tab => tab.name === DEFAULT_STATE.tabName); + renderedTabs.find(tab => tab.name === tabName)! || + renderedTabs.find(tab => tab.name === DEFAULT_STATE.tabName)!; return (
>[]} {...{ styling, inspectedPath, onInspectPath, tabName, onSelectTab }} /> {!error && ( @@ -94,7 +151,11 @@ class ActionPreview extends Component { ); } - labelRenderer = ([key, ...rest], nodeType, expanded) => { + labelRenderer = ( + [key, ...rest]: (string | number)[], + nodeType: string, + expanded: boolean + ) => { const { styling, onInspectPath, inspectedPath } = this.props; return ( diff --git a/packages/redux-devtools-inspector/src/ActionPreviewHeader.jsx b/packages/redux-devtools-inspector/src/ActionPreviewHeader.tsx similarity index 58% rename from packages/redux-devtools-inspector/src/ActionPreviewHeader.jsx rename to packages/redux-devtools-inspector/src/ActionPreviewHeader.tsx index d6a87a4e..56fe8c46 100644 --- a/packages/redux-devtools-inspector/src/ActionPreviewHeader.jsx +++ b/packages/redux-devtools-inspector/src/ActionPreviewHeader.tsx @@ -1,16 +1,32 @@ import React from 'react'; +import PropTypes from 'prop-types'; +import { StylingFunction } from 'react-base16-styling'; +import { Action } from 'redux'; +import { Tab } from './ActionPreview'; -const ActionPreviewHeader = ({ +interface Props> { + styling: StylingFunction; + inspectedPath: (string | number)[]; + onInspectPath: (path: (string | number)[]) => void; + tabName: string; + onSelectTab: (tabName: string) => void; + tabs: Tab[]; +} + +const ActionPreviewHeader: React.FunctionComponent +>> = >({ styling, inspectedPath, onInspectPath, tabName, onSelectTab, tabs -}) => ( +}: Props) => (
- {tabs.map(tab => ( + {tabs.map((tab: Tab) => (
onSelectTab(tab.name)} key={tab.name} @@ -39,7 +55,7 @@ const ActionPreviewHeader = ({ ) : ( tabName )} - {inspectedPath.map((key, idx) => + {inspectedPath.map((key: string | number, idx: number) => idx === inspectedPath.length - 1 ? ( {key} ) : ( @@ -57,4 +73,13 @@ const ActionPreviewHeader = ({
); +ActionPreviewHeader.propTypes = { + styling: PropTypes.func.isRequired, + inspectedPath: PropTypes.array.isRequired, + onInspectPath: PropTypes.func.isRequired, + tabName: PropTypes.string.isRequired, + onSelectTab: PropTypes.func.isRequired, + tabs: PropTypes.array.isRequired +}; + export default ActionPreviewHeader; diff --git a/packages/redux-devtools-inspector/src/DevtoolsInspector.js b/packages/redux-devtools-inspector/src/DevtoolsInspector.tsx similarity index 74% rename from packages/redux-devtools-inspector/src/DevtoolsInspector.js rename to packages/redux-devtools-inspector/src/DevtoolsInspector.tsx index 6f30a5ad..0c79d075 100644 --- a/packages/redux-devtools-inspector/src/DevtoolsInspector.js +++ b/packages/redux-devtools-inspector/src/DevtoolsInspector.tsx @@ -1,17 +1,29 @@ import React, { Component } from 'react'; -import { PropTypes } from 'prop-types'; +import PropTypes from 'prop-types'; import { createStylingFromTheme, base16Themes } from './utils/createStylingFromTheme'; import shouldPureComponentUpdate from 'react-pure-render/function'; import ActionList from './ActionList'; -import ActionPreview from './ActionPreview'; +import ActionPreview, { Tab } from './ActionPreview'; import getInspectedState from './utils/getInspectedState'; import createDiffPatcher from './createDiffPatcher'; -import { getBase16Theme } from 'react-base16-styling'; -import { reducer, updateMonitorState } from './redux'; -import { ActionCreators } from 'redux-devtools'; +import { + Base16Theme, + getBase16Theme, + StylingFunction, + Theme +} from 'react-base16-styling'; +import { + MonitorAction, + MonitorState, + reducer, + updateMonitorState +} from './redux'; +import { ActionCreators, LiftedAction, LiftedState } from 'redux-devtools'; +import { Action, Dispatch } from 'redux'; +import { Delta, DiffContext } from 'jsondiffpatch'; const { commit, @@ -22,21 +34,43 @@ const { reorderAction } = ActionCreators; -function getLastActionId(props) { +export interface Props> + extends LiftedState { + dispatch: Dispatch< + MonitorAction | LiftedAction + >; + + select: (state: S) => unknown; + supportImmutable: boolean; + draggableActions: boolean; + tabs: Tab[] | ((tabs: Tab[]) => Tab[]); + theme: Theme; + invertTheme: boolean; + diffObjectHash?: (item: any, index: number) => string; + diffPropertyFilter?: (name: string, context: DiffContext) => boolean; + dataTypeKey?: string; + hideMainButtons?: boolean; + hideActionButtons?: boolean; +} + +function getLastActionId>(props: Props) { return props.stagedActionIds[props.stagedActionIds.length - 1]; } -function getCurrentActionId(props, monitorState) { +function getCurrentActionId>( + props: Props, + monitorState: MonitorState +) { return monitorState.selectedActionId === null ? props.stagedActionIds[props.currentStateIndex] : monitorState.selectedActionId; } -function getFromState( - actionIndex, - stagedActionIds, - computedStates, - monitorState +function getFromState( + actionIndex: number, + stagedActionIds: number[], + computedStates: { state: S; error?: string }[], + monitorState: MonitorState ) { const { startActionId } = monitorState; if (startActionId === null) { @@ -47,7 +81,10 @@ function getFromState( return computedStates[fromStateIdx]; } -function createIntermediateState(props, monitorState) { +function createIntermediateState>( + props: Props, + monitorState: MonitorState +) { const { supportImmutable, computedStates, @@ -97,15 +134,27 @@ function createIntermediateState(props, monitorState) { }; } -function createThemeState(props) { - const base16Theme = getBase16Theme(props.theme, base16Themes); +function createThemeState>(props: Props) { + const base16Theme = getBase16Theme(props.theme, base16Themes)!; const styling = createStylingFromTheme(props.theme, props.invertTheme); return { base16Theme, styling }; } -export default class DevtoolsInspector extends Component { - constructor(props) { +interface State> { + isWideLayout: boolean; + themeState: { base16Theme: Base16Theme; styling: StylingFunction }; + delta: Delta | null | undefined | false; + nextState: S; + action: A; + error: string | undefined; +} + +export default class DevtoolsInspector< + S, + A extends Action +> extends Component, State> { + constructor(props: Props) { super(props); this.state = { ...createIntermediateState(props, props.monitorState), @@ -142,7 +191,7 @@ export default class DevtoolsInspector extends Component { static update = reducer; static defaultProps = { - select: state => state, + select: (state: unknown) => state, supportImmutable: false, draggableActions: true, theme: 'inspector', @@ -151,29 +200,35 @@ export default class DevtoolsInspector extends Component { shouldComponentUpdate = shouldPureComponentUpdate; + updateSizeTimeout?: number; + inspectorRef?: HTMLDivElement | null; + componentDidMount() { this.updateSizeMode(); - this.updateSizeTimeout = setInterval(this.updateSizeMode.bind(this), 150); + this.updateSizeTimeout = window.setInterval( + this.updateSizeMode.bind(this), + 150 + ); } componentWillUnmount() { clearTimeout(this.updateSizeTimeout); } - updateMonitorState = monitorState => { + updateMonitorState = (monitorState: Partial) => { this.props.dispatch(updateMonitorState(monitorState)); }; updateSizeMode() { - const isWideLayout = this.inspectorRef.offsetWidth > 500; + const isWideLayout = this.inspectorRef!.offsetWidth > 500; if (isWideLayout !== this.state.isWideLayout) { this.setState({ isWideLayout }); } } - componentWillReceiveProps(nextProps) { - let nextMonitorState = nextProps.monitorState; + componentWillReceiveProps(nextProps: Props) { + const nextMonitorState = nextProps.monitorState; const monitorState = this.props.monitorState; if ( @@ -197,7 +252,7 @@ export default class DevtoolsInspector extends Component { } } - inspectorCreateRef = node => { + inspectorCreateRef: React.RefCallback = node => { this.inspectorRef = node; }; @@ -287,7 +342,9 @@ export default class DevtoolsInspector extends Component { monitorState={this.props.monitorState} updateMonitorState={this.updateMonitorState} styling={styling} - onInspectPath={this.handleInspectPath.bind(this, inspectedPathType)} + onInspectPath={keyPath => + this.handleInspectPath(inspectedPathType, keyPath) + } inspectedPath={monitorState[inspectedPathType]} onSelectTab={this.handleSelectTab} /> @@ -295,11 +352,11 @@ export default class DevtoolsInspector extends Component { ); } - handleToggleAction = actionId => { + handleToggleAction = (actionId: number) => { this.props.dispatch(toggleAction(actionId)); }; - handleJumpToState = actionId => { + handleJumpToState = (actionId: number) => { if (jumpToAction) { this.props.dispatch(jumpToAction(actionId)); } else { @@ -309,7 +366,7 @@ export default class DevtoolsInspector extends Component { } }; - handleReorderAction = (actionId, beforeActionId) => { + handleReorderAction = (actionId: number, beforeActionId: number) => { if (reorderAction) this.props.dispatch(reorderAction(actionId, beforeActionId)); }; @@ -322,11 +379,14 @@ export default class DevtoolsInspector extends Component { this.props.dispatch(sweep()); }; - handleSearch = val => { + handleSearch = (val: string) => { this.updateMonitorState({ searchValue: val }); }; - handleSelectAction = (e, actionId) => { + handleSelectAction = ( + e: React.MouseEvent, + actionId: number + ) => { const { monitorState } = this.props; let startActionId; let selectedActionId; @@ -365,11 +425,14 @@ export default class DevtoolsInspector extends Component { this.updateMonitorState({ startActionId, selectedActionId }); }; - handleInspectPath = (pathType, path) => { + handleInspectPath = ( + pathType: 'inspectedActionPath' | 'inspectedStatePath', + path: (string | number)[] + ) => { this.updateMonitorState({ [pathType]: path }); }; - handleSelectTab = tabName => { + handleSelectTab = (tabName: string) => { this.updateMonitorState({ tabName }); }; } diff --git a/packages/redux-devtools-inspector/src/RightSlider.jsx b/packages/redux-devtools-inspector/src/RightSlider.jsx deleted file mode 100644 index 2b1ebd45..00000000 --- a/packages/redux-devtools-inspector/src/RightSlider.jsx +++ /dev/null @@ -1,21 +0,0 @@ -import React from 'react'; -import { PropTypes } from 'prop-types'; - -const RightSlider = ({ styling, shown, children, rotate }) => ( -
- {children} -
-); - -RightSlider.propTypes = { - shown: PropTypes.bool -}; - -export default RightSlider; diff --git a/packages/redux-devtools-inspector/src/RightSlider.tsx b/packages/redux-devtools-inspector/src/RightSlider.tsx new file mode 100644 index 00000000..476fc4bb --- /dev/null +++ b/packages/redux-devtools-inspector/src/RightSlider.tsx @@ -0,0 +1,37 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { StylingFunction } from 'react-base16-styling'; + +interface Props { + styling: StylingFunction; + shown?: boolean; + children: React.ReactNode; + rotate?: boolean; +} + +const RightSlider: React.FunctionComponent = ({ + styling, + shown, + children, + rotate +}) => ( +
+ {children} +
+); + +RightSlider.propTypes = { + styling: PropTypes.func.isRequired, + shown: PropTypes.bool, + children: PropTypes.any.isRequired, + rotate: PropTypes.bool +}; + +export default RightSlider; diff --git a/packages/redux-devtools-inspector/src/base16.ts b/packages/redux-devtools-inspector/src/base16.ts new file mode 100644 index 00000000..2434e11c --- /dev/null +++ b/packages/redux-devtools-inspector/src/base16.ts @@ -0,0 +1,61 @@ +declare module 'base16' { + export interface Base16Theme { + scheme?: string; + author?: string; + base00: string; + base01: string; + base02: string; + base03: string; + base04: string; + base05: string; + base06: string; + base07: string; + base08: string; + base09: string; + base0A: string; + base0B: string; + base0C: string; + base0D: string; + base0E: string; + base0F: string; + } + + export const threezerotwofour: Base16Theme; + export const apathy: Base16Theme; + export const ashes: Base16Theme; + export const atelierDune: Base16Theme; + export const atelierForest: Base16Theme; + export const atelierHeath: Base16Theme; + export const atelierLakeside: Base16Theme; + export const atelierSeaside: Base16Theme; + export const bespin: Base16Theme; + export const brewer: Base16Theme; + export const bright: Base16Theme; + export const chalk: Base16Theme; + export const codeschool: Base16Theme; + export const colors: Base16Theme; + const _default: Base16Theme; + export default _default; + export const eighties: Base16Theme; + export const embers: Base16Theme; + export const flat: Base16Theme; + export const google: Base16Theme; + export const grayscale: Base16Theme; + export const greenscreen: Base16Theme; + export const harmonic: Base16Theme; + export const hopscotch: Base16Theme; + export const isotope: Base16Theme; + export const marrakesh: Base16Theme; + export const mocha: Base16Theme; + export const monokai: Base16Theme; + export const ocean: Base16Theme; + export const paraiso: Base16Theme; + export const pop: Base16Theme; + export const railscasts: Base16Theme; + export const shapeshifter: Base16Theme; + export const solarized: Base16Theme; + export const summerfruit: Base16Theme; + export const tomorrow: Base16Theme; + export const tube: Base16Theme; + export const twilight: Base16Theme; +} diff --git a/packages/redux-devtools-inspector/src/createDiffPatcher.js b/packages/redux-devtools-inspector/src/createDiffPatcher.ts similarity index 51% rename from packages/redux-devtools-inspector/src/createDiffPatcher.js rename to packages/redux-devtools-inspector/src/createDiffPatcher.ts index 34d056cd..dc931719 100644 --- a/packages/redux-devtools-inspector/src/createDiffPatcher.js +++ b/packages/redux-devtools-inspector/src/createDiffPatcher.ts @@ -1,28 +1,37 @@ -import { DiffPatcher } from 'jsondiffpatch/src/diffpatcher'; +import { DiffContext, DiffPatcher } from 'jsondiffpatch'; -const defaultObjectHash = (o, idx) => +const defaultObjectHash = (o: any, idx: number) => (o === null && '$$null') || (o && (o.id || o.id === 0) && `$$id:${JSON.stringify(o.id)}`) || (o && (o._id || o._id === 0) && `$$_id:${JSON.stringify(o._id)}`) || '$$index:' + idx; -const defaultPropertyFilter = (name, context) => +const defaultPropertyFilter = (name: string, context: DiffContext) => typeof context.left[name] !== 'function' && typeof context.right[name] !== 'function'; const defaultDiffPatcher = new DiffPatcher({ - arrays: { detectMove: false }, + arrays: { detectMove: false } as { + detectMove: boolean; + includeValueOnMove: boolean; + }, objectHash: defaultObjectHash, propertyFilter: defaultPropertyFilter }); -export default function createDiffPatcher(objectHash, propertyFilter) { +export default function createDiffPatcher( + objectHash: ((item: any, index: number) => string) | undefined, + propertyFilter: ((name: string, context: DiffContext) => boolean) | undefined +) { if (!objectHash && !propertyFilter) { return defaultDiffPatcher; } return new DiffPatcher({ - arrays: { detectMove: false }, + arrays: { detectMove: false } as { + detectMove: boolean; + includeValueOnMove: boolean; + }, objectHash: objectHash || defaultObjectHash, propertyFilter: propertyFilter || defaultPropertyFilter }); diff --git a/packages/redux-devtools-inspector/src/index.js b/packages/redux-devtools-inspector/src/index.js deleted file mode 100644 index 2040c41e..00000000 --- a/packages/redux-devtools-inspector/src/index.js +++ /dev/null @@ -1 +0,0 @@ -export default from './DevtoolsInspector'; diff --git a/packages/redux-devtools-inspector/src/index.ts b/packages/redux-devtools-inspector/src/index.ts new file mode 100644 index 00000000..d6ea9d8f --- /dev/null +++ b/packages/redux-devtools-inspector/src/index.ts @@ -0,0 +1,2 @@ +import DevtoolsInspector from './DevtoolsInspector'; +export default DevtoolsInspector; diff --git a/packages/redux-devtools-inspector/src/jss-nested.ts b/packages/redux-devtools-inspector/src/jss-nested.ts new file mode 100644 index 00000000..b71cf34b --- /dev/null +++ b/packages/redux-devtools-inspector/src/jss-nested.ts @@ -0,0 +1,6 @@ +declare module 'jss-nested' { + import { Plugin } from 'jss'; + + const jssNested: () => Plugin; + export default jssNested; +} diff --git a/packages/redux-devtools-inspector/src/jss-vendor-prefixer.ts b/packages/redux-devtools-inspector/src/jss-vendor-prefixer.ts new file mode 100644 index 00000000..d75a9547 --- /dev/null +++ b/packages/redux-devtools-inspector/src/jss-vendor-prefixer.ts @@ -0,0 +1,6 @@ +declare module 'jss-vendor-prefixer' { + import { Plugin } from 'jss'; + + const jssVendorPrefixer: () => Plugin; + export default jssVendorPrefixer; +} diff --git a/packages/redux-devtools-inspector/src/jss.ts b/packages/redux-devtools-inspector/src/jss.ts new file mode 100644 index 00000000..f63eaa9c --- /dev/null +++ b/packages/redux-devtools-inspector/src/jss.ts @@ -0,0 +1,57 @@ +declare module 'jss' { + import * as css from 'csstype'; + + // TODO: Type data better, currently typed as any for allowing to override it + type FnValue = R | ((data: any) => R); + + type NormalCssProperties = css.Properties; + type CssProperties = { + [K in keyof NormalCssProperties]: FnValue; + }; + + // Jss Style definitions + type JssStyleP = CssProperties & { + [key: string]: FnValue; + }; + + export type JssStyle = JssStyleP< + JssStyleP>>>>> + >; + + export type Styles = Record< + Name, + JssStyle | string + >; + export type Classes = Record< + Name, + string + >; + + export type JssValue = + | string + | number + | Array | '!important'> + | null + | false; + + // eslint-disable-next-line @typescript-eslint/no-empty-interface + export interface Plugin {} + + export interface StyleSheet< + RuleName extends string | number | symbol = string | number | symbol + > { + classes: Classes; + attach(): this; + detach(): this; + } + + export interface Jss { + createStyleSheet( + styles: Partial> + ): StyleSheet; + use(...plugins: Plugin[]): this; + } + + const jss: Jss; + export default jss; +} diff --git a/packages/redux-devtools-inspector/src/react-base16-styling.ts b/packages/redux-devtools-inspector/src/react-base16-styling.ts new file mode 100644 index 00000000..6967440c --- /dev/null +++ b/packages/redux-devtools-inspector/src/react-base16-styling.ts @@ -0,0 +1,6 @@ +declare module 'react-base16-styling' { + export function getBase16Theme( + theme: Theme, + base16Themes?: Base16Theme[] | null + ): Base16Theme | undefined | null; +} diff --git a/packages/redux-devtools-inspector/src/react-dragula.ts b/packages/redux-devtools-inspector/src/react-dragula.ts new file mode 100644 index 00000000..0b6ba9f0 --- /dev/null +++ b/packages/redux-devtools-inspector/src/react-dragula.ts @@ -0,0 +1,8 @@ +declare module 'react-dragula' { + import { DragulaOptions, Drake } from 'dragula'; + + export default function( + containers: Array, + options: DragulaOptions + ): Drake; +} diff --git a/packages/redux-devtools-inspector/src/react-pure-render.ts b/packages/redux-devtools-inspector/src/react-pure-render.ts new file mode 100644 index 00000000..794702c7 --- /dev/null +++ b/packages/redux-devtools-inspector/src/react-pure-render.ts @@ -0,0 +1,6 @@ +declare module 'react-pure-render/function' { + export default function shouldPureComponentUpdate( + nextProps: unknown, + nextState: unknown + ): boolean; +} diff --git a/packages/redux-devtools-inspector/src/redux-devtools-themes.ts b/packages/redux-devtools-inspector/src/redux-devtools-themes.ts new file mode 100644 index 00000000..895ce79d --- /dev/null +++ b/packages/redux-devtools-inspector/src/redux-devtools-themes.ts @@ -0,0 +1,5 @@ +declare module 'redux-devtools-themes' { + import { Base16Theme } from 'base16'; + export * from 'base16'; + export const nicinabox: Base16Theme; +} diff --git a/packages/redux-devtools-inspector/src/redux.js b/packages/redux-devtools-inspector/src/redux.js deleted file mode 100644 index 92033375..00000000 --- a/packages/redux-devtools-inspector/src/redux.js +++ /dev/null @@ -1,28 +0,0 @@ -const UPDATE_MONITOR_STATE = '@@redux-devtools-inspector/UPDATE_MONITOR_STATE'; - -export const DEFAULT_STATE = { - selectedActionId: null, - startActionId: null, - inspectedActionPath: [], - inspectedStatePath: [], - tabName: 'Diff' -}; - -export function updateMonitorState(monitorState) { - return { type: UPDATE_MONITOR_STATE, monitorState }; -} - -function reduceUpdateState(state, action) { - return action.type === UPDATE_MONITOR_STATE - ? { - ...state, - ...action.monitorState - } - : state; -} - -export function reducer(props, state = DEFAULT_STATE, action) { - return { - ...reduceUpdateState(state, action) - }; -} diff --git a/packages/redux-devtools-inspector/src/redux.ts b/packages/redux-devtools-inspector/src/redux.ts new file mode 100644 index 00000000..8fd7f2d2 --- /dev/null +++ b/packages/redux-devtools-inspector/src/redux.ts @@ -0,0 +1,52 @@ +import { Action } from 'redux'; +import { Props } from './DevtoolsInspector'; + +const UPDATE_MONITOR_STATE = '@@redux-devtools-inspector/UPDATE_MONITOR_STATE'; +interface UpdateMonitorStateAction { + type: typeof UPDATE_MONITOR_STATE; + monitorState: Partial; +} + +export type MonitorAction = UpdateMonitorStateAction; + +export interface MonitorState { + readonly selectedActionId: number | null; + readonly startActionId: number | null; + readonly inspectedActionPath: (string | number)[]; + readonly inspectedStatePath: (string | number)[]; + readonly tabName: string; + readonly searchValue?: string; +} + +export const DEFAULT_STATE: MonitorState = { + selectedActionId: null, + startActionId: null, + inspectedActionPath: [], + inspectedStatePath: [], + tabName: 'Diff' +}; + +export function updateMonitorState( + monitorState: Partial +): UpdateMonitorStateAction { + return { type: UPDATE_MONITOR_STATE, monitorState }; +} + +function reduceUpdateState(state: MonitorState, action: MonitorAction) { + return action.type === UPDATE_MONITOR_STATE + ? { + ...state, + ...action.monitorState + } + : state; +} + +export function reducer>( + props: Props, + state = DEFAULT_STATE, + action: MonitorAction +) { + return { + ...reduceUpdateState(state, action) + }; +} diff --git a/packages/redux-devtools-inspector/src/tabs/ActionTab.jsx b/packages/redux-devtools-inspector/src/tabs/ActionTab.tsx similarity index 51% rename from packages/redux-devtools-inspector/src/tabs/ActionTab.jsx rename to packages/redux-devtools-inspector/src/tabs/ActionTab.tsx index e2a91800..320e3544 100644 --- a/packages/redux-devtools-inspector/src/tabs/ActionTab.jsx +++ b/packages/redux-devtools-inspector/src/tabs/ActionTab.tsx @@ -1,9 +1,15 @@ import React from 'react'; +import PropTypes from 'prop-types'; import JSONTree from 'react-json-tree'; import getItemString from './getItemString'; import getJsonTreeTheme from './getJsonTreeTheme'; +import { TabComponentProps } from '../ActionPreview'; +import { Action } from 'redux'; -const ActionTab = ({ +const ActionTab: React.FunctionComponent +>> = ({ action, styling, base16Theme, @@ -24,4 +30,14 @@ const ActionTab = ({ /> ); +ActionTab.propTypes = { + action: PropTypes.any.isRequired, + styling: PropTypes.func.isRequired, + base16Theme: PropTypes.any.isRequired, + invertTheme: PropTypes.bool.isRequired, + labelRenderer: PropTypes.func.isRequired, + dataTypeKey: PropTypes.string, + isWideLayout: PropTypes.bool.isRequired +}; + export default ActionTab; diff --git a/packages/redux-devtools-inspector/src/tabs/DiffTab.jsx b/packages/redux-devtools-inspector/src/tabs/DiffTab.jsx deleted file mode 100644 index 8934c785..00000000 --- a/packages/redux-devtools-inspector/src/tabs/DiffTab.jsx +++ /dev/null @@ -1,24 +0,0 @@ -import React from 'react'; -import JSONDiff from './JSONDiff'; - -const DiffTab = ({ - delta, - styling, - base16Theme, - invertTheme, - labelRenderer, - isWideLayout -}) => ( - -); - -export default DiffTab; diff --git a/packages/redux-devtools-inspector/src/tabs/DiffTab.tsx b/packages/redux-devtools-inspector/src/tabs/DiffTab.tsx new file mode 100644 index 00000000..62605335 --- /dev/null +++ b/packages/redux-devtools-inspector/src/tabs/DiffTab.tsx @@ -0,0 +1,42 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import JSONDiff from './JSONDiff'; +import { TabComponentProps } from '../ActionPreview'; +import { Action } from 'redux'; + +const DiffTab: React.FunctionComponent +>> = ({ + delta, + styling, + base16Theme, + invertTheme, + labelRenderer, + isWideLayout, + dataTypeKey +}) => ( + +); + +DiffTab.propTypes = { + delta: PropTypes.any, + styling: PropTypes.func.isRequired, + base16Theme: PropTypes.any.isRequired, + invertTheme: PropTypes.bool.isRequired, + labelRenderer: PropTypes.func.isRequired, + isWideLayout: PropTypes.bool.isRequired, + dataTypeKey: PropTypes.string +}; + +export default DiffTab; diff --git a/packages/redux-devtools-inspector/src/tabs/JSONDiff.jsx b/packages/redux-devtools-inspector/src/tabs/JSONDiff.tsx similarity index 73% rename from packages/redux-devtools-inspector/src/tabs/JSONDiff.jsx rename to packages/redux-devtools-inspector/src/tabs/JSONDiff.tsx index 3afa43b0..c919284b 100644 --- a/packages/redux-devtools-inspector/src/tabs/JSONDiff.jsx +++ b/packages/redux-devtools-inspector/src/tabs/JSONDiff.tsx @@ -3,8 +3,10 @@ import JSONTree from 'react-json-tree'; import stringify from 'javascript-stringify'; import getItemString from './getItemString'; import getJsonTreeTheme from './getJsonTreeTheme'; +import { Delta } from 'jsondiffpatch'; +import { Base16Theme, StylingFunction } from 'react-base16-styling'; -function stringifyAndShrink(val, isWideLayout) { +function stringifyAndShrink(val: any, isWideLayout?: boolean) { if (val === null) { return 'null'; } @@ -19,18 +21,22 @@ function stringifyAndShrink(val, isWideLayout) { return str.length > 22 ? `${str.substr(0, 15)}…${str.substr(-5)}` : str; } -const expandFirstLevel = (keyName, data, level) => level <= 1; +const expandFirstLevel = ( + keyName: (string | number)[], + data: any, + level: number +) => level <= 1; -function prepareDelta(value) { +function prepareDelta(value: any) { if (value && value._t === 'a') { - const res = {}; - for (let key in value) { + const res: { [key: string]: any } = {}; + for (const key in value) { if (key !== '_t') { - if (key[0] === '_' && !value[key.substr(1)]) { + if (key.startsWith('_') && !value[key.substr(1)]) { res[key.substr(1)] = value[key]; } else if (value['_' + key]) { res[key] = [value['_' + key][0], value[key][0]]; - } else if (!value['_' + key] && key[0] !== '_') { + } else if (!value['_' + key] && !key.startsWith('_')) { res[key] = value[key]; } } @@ -41,14 +47,33 @@ function prepareDelta(value) { return value; } -export default class JSONDiff extends Component { +interface Props { + delta: Delta | null | undefined | false; + styling: StylingFunction; + base16Theme: Base16Theme; + invertTheme: boolean; + labelRenderer: ( + keyPath: (string | number)[], + nodeType: string, + expanded: boolean, + expandable: boolean + ) => React.ReactNode; + isWideLayout: boolean; + dataTypeKey: string | undefined; +} + +interface State { + data: any; +} + +export default class JSONDiff extends Component { state = { data: {} }; componentDidMount() { this.updateData(); } - componentDidUpdate(prevProps) { + componentDidUpdate(prevProps: Props) { if (prevProps.delta !== this.props.delta) { this.updateData(); } @@ -84,7 +109,7 @@ export default class JSONDiff extends Component { ); } - getItemString = (type, data) => + getItemString = (type: string, data: any) => getItemString( this.props.styling, type, @@ -94,10 +119,10 @@ export default class JSONDiff extends Component { true ); - valueRenderer = (raw, value) => { + valueRenderer = (raw: any, value: any) => { const { styling, isWideLayout } = this.props; - function renderSpan(name, body) { + function renderSpan(name: string, body: string) { return ( {body} diff --git a/packages/redux-devtools-inspector/src/tabs/StateTab.jsx b/packages/redux-devtools-inspector/src/tabs/StateTab.tsx similarity index 52% rename from packages/redux-devtools-inspector/src/tabs/StateTab.jsx rename to packages/redux-devtools-inspector/src/tabs/StateTab.tsx index 59064bad..9e46fe1d 100644 --- a/packages/redux-devtools-inspector/src/tabs/StateTab.jsx +++ b/packages/redux-devtools-inspector/src/tabs/StateTab.tsx @@ -1,9 +1,15 @@ import React from 'react'; +import PropTypes from 'prop-types'; import JSONTree from 'react-json-tree'; import getItemString from './getItemString'; import getJsonTreeTheme from './getJsonTreeTheme'; +import { TabComponentProps } from '../ActionPreview'; +import { Action } from 'redux'; -const StateTab = ({ +const StateTab: React.FunctionComponent +>> = ({ nextState, styling, base16Theme, @@ -24,4 +30,14 @@ const StateTab = ({ /> ); +StateTab.propTypes = { + nextState: PropTypes.any.isRequired, + styling: PropTypes.func.isRequired, + base16Theme: PropTypes.any.isRequired, + invertTheme: PropTypes.bool.isRequired, + labelRenderer: PropTypes.func.isRequired, + dataTypeKey: PropTypes.string, + isWideLayout: PropTypes.bool.isRequired +}; + export default StateTab; diff --git a/packages/redux-devtools-inspector/src/tabs/getItemString.js b/packages/redux-devtools-inspector/src/tabs/getItemString.tsx similarity index 79% rename from packages/redux-devtools-inspector/src/tabs/getItemString.js rename to packages/redux-devtools-inspector/src/tabs/getItemString.tsx index 71e8d303..ce18766b 100644 --- a/packages/redux-devtools-inspector/src/tabs/getItemString.js +++ b/packages/redux-devtools-inspector/src/tabs/getItemString.tsx @@ -1,10 +1,11 @@ import React from 'react'; import { Iterable } from 'immutable'; import isIterable from '../utils/isIterable'; +import { StylingFunction } from 'react-base16-styling'; const IS_IMMUTABLE_KEY = '@@__IS_IMMUTABLE__@@'; -function isImmutable(value) { +function isImmutable(value: any) { return ( Iterable.isKeyed(value) || Iterable.isIndexed(value) || @@ -12,7 +13,7 @@ function isImmutable(value) { ); } -function getShortTypeString(val, diff) { +function getShortTypeString(val: any, diff: boolean | undefined) { if (diff && Array.isArray(val)) { val = val[val.length === 2 ? 1 : 0]; } @@ -38,7 +39,12 @@ function getShortTypeString(val, diff) { } } -function getText(type, data, isWideLayout, isDiff) { +function getText( + type: string, + data: any, + isWideLayout: boolean, + isDiff: boolean | undefined +) { if (type === 'Object') { const keys = Object.keys(data); if (!isWideLayout) return keys.length ? '{…}' : '{}'; @@ -55,7 +61,7 @@ function getText(type, data, isWideLayout, isDiff) { const str = data .slice(0, 4) - .map(val => getShortTypeString(val, isDiff)) + .map((val: any) => getShortTypeString(val, isDiff)) .concat(data.length > 4 ? ['…'] : []) .join(', '); @@ -66,12 +72,12 @@ function getText(type, data, isWideLayout, isDiff) { } const getItemString = ( - styling, - type, - data, - dataTypeKey, - isWideLayout, - isDiff + styling: StylingFunction, + type: string, + data: any, + dataTypeKey: string | undefined, + isWideLayout: boolean, + isDiff?: boolean ) => ( {data[IS_IMMUTABLE_KEY] ? 'Immutable' : ''} diff --git a/packages/redux-devtools-inspector/src/tabs/getJsonTreeTheme.js b/packages/redux-devtools-inspector/src/tabs/getJsonTreeTheme.ts similarity index 62% rename from packages/redux-devtools-inspector/src/tabs/getJsonTreeTheme.js rename to packages/redux-devtools-inspector/src/tabs/getJsonTreeTheme.ts index 7bb2942a..0b382542 100644 --- a/packages/redux-devtools-inspector/src/tabs/getJsonTreeTheme.js +++ b/packages/redux-devtools-inspector/src/tabs/getJsonTreeTheme.ts @@ -1,6 +1,10 @@ -export default function getJsonTreeTheme(base16Theme) { +import { Base16Theme, StylingConfig, StylingValue } from 'react-base16-styling'; + +export default function getJsonTreeTheme( + base16Theme: Base16Theme +): StylingConfig { return { - extend: base16Theme, + extend: base16Theme as StylingValue, nestedNode: ({ style }, keyPath, nodeType, expanded) => ({ style: { ...style, diff --git a/packages/redux-devtools-inspector/src/themes/index.js b/packages/redux-devtools-inspector/src/themes/index.ts similarity index 100% rename from packages/redux-devtools-inspector/src/themes/index.js rename to packages/redux-devtools-inspector/src/themes/index.ts diff --git a/packages/redux-devtools-inspector/src/themes/inspector.js b/packages/redux-devtools-inspector/src/themes/inspector.ts similarity index 100% rename from packages/redux-devtools-inspector/src/themes/inspector.js rename to packages/redux-devtools-inspector/src/themes/inspector.ts diff --git a/packages/redux-devtools-inspector/src/utils/createStylingFromTheme.js b/packages/redux-devtools-inspector/src/utils/createStylingFromTheme.ts similarity index 94% rename from packages/redux-devtools-inspector/src/utils/createStylingFromTheme.js rename to packages/redux-devtools-inspector/src/utils/createStylingFromTheme.ts index a4603412..355651c1 100644 --- a/packages/redux-devtools-inspector/src/utils/createStylingFromTheme.js +++ b/packages/redux-devtools-inspector/src/utils/createStylingFromTheme.ts @@ -1,7 +1,7 @@ -import jss from 'jss'; +import jss, { Styles, StyleSheet } from 'jss'; import jssVendorPrefixer from 'jss-vendor-prefixer'; import jssNested from 'jss-nested'; -import { createStyling } from 'react-base16-styling'; +import { Base16Theme, createStyling } from 'react-base16-styling'; import rgba from 'hex-rgba'; import inspector from '../themes/inspector'; import * as reduxThemes from 'redux-devtools-themes'; @@ -10,7 +10,7 @@ import * as inspectorThemes from '../themes'; jss.use(jssVendorPrefixer()); jss.use(jssNested()); -const colorMap = theme => ({ +const colorMap = (theme: Base16Theme) => ({ TEXT_COLOR: theme.base06, TEXT_PLACEHOLDER_COLOR: rgba(theme.base06, 60), BACKGROUND_COLOR: theme.base00, @@ -36,7 +36,12 @@ const colorMap = theme => ({ ERROR_COLOR: theme.base08 }); -const getSheetFromColorMap = map => ({ +type Color = keyof ReturnType; +type ColorMap = { + [color in Color]: string; +}; + +const getSheetFromColorMap = (map: ColorMap): Partial => ({ inspector: { display: 'flex', 'flex-direction': 'column', @@ -386,9 +391,9 @@ const getSheetFromColorMap = map => ({ } }); -let themeSheet; +let themeSheet: StyleSheet; -const getDefaultThemeStyling = theme => { +const getDefaultThemeStyling = (theme: Base16Theme) => { if (themeSheet) { themeSheet.detach(); } @@ -400,7 +405,10 @@ const getDefaultThemeStyling = theme => { return themeSheet.classes; }; -export const base16Themes = { ...reduxThemes, ...inspectorThemes }; +export const base16Themes = ({ + ...reduxThemes, + ...inspectorThemes +} as unknown) as Base16Theme[]; export const createStylingFromTheme = createStyling(getDefaultThemeStyling, { defaultBase16: inspector, diff --git a/packages/redux-devtools-inspector/src/utils/deepMap.js b/packages/redux-devtools-inspector/src/utils/deepMap.js deleted file mode 100644 index 88d3dca0..00000000 --- a/packages/redux-devtools-inspector/src/utils/deepMap.js +++ /dev/null @@ -1,30 +0,0 @@ -function deepMapCached(obj, f, ctx, cache) { - cache.push(obj); - if (Array.isArray(obj)) { - return obj.map(function(val, key) { - val = f.call(ctx, val, key); - return typeof val === 'object' && cache.indexOf(val) === -1 - ? deepMapCached(val, f, ctx, cache) - : val; - }); - } else if (typeof obj === 'object') { - const res = {}; - for (const key in obj) { - let val = obj[key]; - if (val && typeof val === 'object') { - val = f.call(ctx, val, key); - res[key] = - cache.indexOf(val) === -1 ? deepMapCached(val, f, ctx, cache) : val; - } else { - res[key] = f.call(ctx, val, key); - } - } - return res; - } else { - return obj; - } -} - -export default function deepMap(obj, f, ctx) { - return deepMapCached(obj, f, ctx, []); -} diff --git a/packages/redux-devtools-inspector/src/utils/getInspectedState.js b/packages/redux-devtools-inspector/src/utils/getInspectedState.ts similarity index 72% rename from packages/redux-devtools-inspector/src/utils/getInspectedState.js rename to packages/redux-devtools-inspector/src/utils/getInspectedState.ts index 321233fe..4966895c 100644 --- a/packages/redux-devtools-inspector/src/utils/getInspectedState.js +++ b/packages/redux-devtools-inspector/src/utils/getInspectedState.ts @@ -1,10 +1,10 @@ import { Iterable, fromJS } from 'immutable'; import isIterable from './isIterable'; -function iterateToKey(obj, key) { +function iterateToKey(obj: any, key: string | number) { // maybe there's a better way, dunno let idx = 0; - for (let entry of obj) { + for (const entry of obj) { if (Array.isArray(entry)) { if (entry[0] === key) return entry[1]; } else { @@ -15,11 +15,15 @@ function iterateToKey(obj, key) { } } -export default function getInspectedState(state, path, convertImmutable) { +export default function getInspectedState( + state: S, + path: (string | number)[], + convertImmutable: boolean +): S { state = path && path.length - ? { - [path[path.length - 1]]: path.reduce((s, key) => { + ? ({ + [path[path.length - 1]]: path.reduce((s: any, key) => { if (!s) { return s; } @@ -32,7 +36,7 @@ export default function getInspectedState(state, path, convertImmutable) { return s[key]; }, state) - } + } as S) : state; if (convertImmutable) { diff --git a/packages/redux-devtools-inspector/src/utils/isIterable.js b/packages/redux-devtools-inspector/src/utils/isIterable.ts similarity index 76% rename from packages/redux-devtools-inspector/src/utils/isIterable.js rename to packages/redux-devtools-inspector/src/utils/isIterable.ts index 45f33b64..a7226ad0 100644 --- a/packages/redux-devtools-inspector/src/utils/isIterable.js +++ b/packages/redux-devtools-inspector/src/utils/isIterable.ts @@ -1,4 +1,4 @@ -export default function isIterable(obj) { +export default function isIterable(obj: any) { return ( obj !== null && typeof obj === 'object' && diff --git a/packages/redux-devtools-inspector/tsconfig.json b/packages/redux-devtools-inspector/tsconfig.json new file mode 100644 index 00000000..7b7d1492 --- /dev/null +++ b/packages/redux-devtools-inspector/tsconfig.json @@ -0,0 +1,7 @@ +{ + "extends": "../../tsconfig.react.base.json", + "compilerOptions": { + "outDir": "lib" + }, + "include": ["src"] +}