This commit is contained in:
Nathan Bierema 2020-10-23 00:37:37 -04:00
parent cedaa8f184
commit 2cbdf7b13e
11 changed files with 138 additions and 68 deletions

View File

@ -29,6 +29,7 @@ import {
CONNECT_SUCCESS, CONNECT_SUCCESS,
DEAUTHENTICATE, DEAUTHENTICATE,
DISCONNECTED, DISCONNECTED,
EMIT,
RECONNECT, RECONNECT,
SUBSCRIBE_ERROR, SUBSCRIBE_ERROR,
SUBSCRIBE_REQUEST, SUBSCRIBE_REQUEST,
@ -90,14 +91,21 @@ export interface MonitorActionAction {
) => unknown; ) => unknown;
monitorProps: unknown; monitorProps: unknown;
} }
interface LiftedActionAction { interface LiftedActionDispatchAction {
type: typeof LIFTED_ACTION; type: typeof LIFTED_ACTION;
message: 'DISPATCH'; message: 'DISPATCH';
action: Action<unknown>; action: Action<unknown>;
} }
interface LiftedActionImportAction {
type: typeof LIFTED_ACTION;
message: 'IMPORT';
state: string;
preloadedState: unknown | undefined;
}
export type LiftedActionAction = LiftedActionDispatchAction;
export function liftedDispatch( export function liftedDispatch(
action: InitMonitorAction action: InitMonitorAction
): MonitorActionAction | LiftedActionAction { ): MonitorActionAction | LiftedActionDispatchAction {
if (action.type[0] === '@') { if (action.type[0] === '@') {
if (action.type === '@@INIT_MONITOR') { if (action.type === '@@INIT_MONITOR') {
monitorReducer = action.update; monitorReducer = action.update;
@ -146,7 +154,10 @@ export function updateMonitorState(nextState) {
return { type: UPDATE_MONITOR_STATE, nextState }; return { type: UPDATE_MONITOR_STATE, nextState };
} }
export function importState(state, preloadedState) { export function importState(
state: string,
preloadedState?: unknown
): LiftedActionImportAction {
return { type: LIFTED_ACTION, message: 'IMPORT', state, preloadedState }; return { type: LIFTED_ACTION, message: 'IMPORT', state, preloadedState };
} }
@ -247,6 +258,10 @@ export function getReport(report) {
return { type: GET_REPORT_REQUEST, report }; return { type: GET_REPORT_REQUEST, report };
} }
interface UpdateStateAction {
type: typeof UPDATE_STATE;
}
interface RemoveInstanceAction { interface RemoveInstanceAction {
type: typeof REMOVE_INSTANCE; type: typeof REMOVE_INSTANCE;
id: string; id: string;
@ -318,6 +333,12 @@ interface UnsubscribeAction {
channel: string; channel: string;
} }
interface EmitAction {
type: typeof EMIT;
message: string;
id: string;
}
export type StoreAction = export type StoreAction =
| ChangeSectionAction | ChangeSectionAction
| ChangeThemeAction | ChangeThemeAction
@ -334,6 +355,7 @@ export type StoreAction =
| ReconnectAction | ReconnectAction
| ShowNotificationAction | ShowNotificationAction
| ClearNotificationAction | ClearNotificationAction
| UpdateStateAction
| RemoveInstanceAction | RemoveInstanceAction
| ConnectRequestAction | ConnectRequestAction
| ConnectSuccessAction | ConnectSuccessAction
@ -346,4 +368,5 @@ export type StoreAction =
| SubscribeRequestAction | SubscribeRequestAction
| SubscribeSuccessAction | SubscribeSuccessAction
| SubscribeErrorAction | SubscribeErrorAction
| UnsubscribeAction; | UnsubscribeAction
| EmitAction;

View File

@ -9,14 +9,19 @@ import DispatcherButton from './buttons/DispatcherButton';
import SliderButton from './buttons/SliderButton'; import SliderButton from './buttons/SliderButton';
import MonitorSelector from './MonitorSelector'; import MonitorSelector from './MonitorSelector';
export default class BottomButtons extends Component { interface Props {
dispatcherIsOpen: boolean;
sliderIsOpen: boolean;
}
export default class BottomButtons extends Component<Props> {
static propTypes = { static propTypes = {
dispatcherIsOpen: PropTypes.bool, dispatcherIsOpen: PropTypes.bool,
sliderIsOpen: PropTypes.bool, sliderIsOpen: PropTypes.bool,
options: PropTypes.object.isRequired, options: PropTypes.object.isRequired,
}; };
shouldComponentUpdate(nextProps) { shouldComponentUpdate(nextProps: Props) {
return ( return (
nextProps.dispatcherIsOpen !== this.props.dispatcherIsOpen || nextProps.dispatcherIsOpen !== this.props.dispatcherIsOpen ||
nextProps.sliderIsOpen !== this.props.sliderIsOpen || nextProps.sliderIsOpen !== this.props.sliderIsOpen ||

View File

@ -1,5 +1,4 @@
import React, { Component } from 'react'; import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { connect, ResolveThunks } from 'react-redux'; import { connect, ResolveThunks } from 'react-redux';
import { Tabs } from 'devui'; import { Tabs } from 'devui';
import { monitors } from '../utils/getMonitor'; import { monitors } from '../utils/getMonitor';
@ -11,11 +10,6 @@ type DispatchProps = ResolveThunks<typeof actionCreators>;
type Props = StateProps & DispatchProps; type Props = StateProps & DispatchProps;
class MonitorSelector extends Component<Props> { class MonitorSelector extends Component<Props> {
static propTypes = {
selected: PropTypes.string,
selectMonitor: PropTypes.func.isRequired,
};
shouldComponentUpdate(nextProps: Props) { shouldComponentUpdate(nextProps: Props) {
return nextProps.selected !== this.props.selected; return nextProps.selected !== this.props.selected;
} }

View File

@ -1,19 +1,16 @@
import React, { Component } from 'react'; import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { connect, ResolveThunks } from 'react-redux'; import { connect, ResolveThunks } from 'react-redux';
import { Button } from 'devui'; import { Button } from 'devui';
import { FaTerminal } from 'react-icons/fa'; import { FaTerminal } from 'react-icons/fa';
import { toggleDispatcher } from '../../actions'; import { toggleDispatcher } from '../../actions';
type DispatchProps = ResolveThunks<typeof actionCreators>; type DispatchProps = ResolveThunks<typeof actionCreators>;
type Props = DispatchProps; interface OwnProps {
dispatcherIsOpen: boolean;
}
type Props = DispatchProps & OwnProps;
class DispatcherButton extends Component<Props> { class DispatcherButton extends Component<Props> {
static propTypes = {
dispatcherIsOpen: PropTypes.bool,
toggleDispatcher: PropTypes.func.isRequired,
};
shouldComponentUpdate(nextProps: Props) { shouldComponentUpdate(nextProps: Props) {
return nextProps.dispatcherIsOpen !== this.props.dispatcherIsOpen; return nextProps.dispatcherIsOpen !== this.props.dispatcherIsOpen;
} }

View File

@ -1,5 +1,4 @@
import React, { Component } from 'react'; import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { connect, ResolveThunks } from 'react-redux'; import { connect, ResolveThunks } from 'react-redux';
import { Button } from 'devui'; import { Button } from 'devui';
import { TiDownload } from 'react-icons/ti'; import { TiDownload } from 'react-icons/ti';
@ -9,10 +8,6 @@ type DispatchProps = ResolveThunks<typeof actionCreators>;
type Props = DispatchProps; type Props = DispatchProps;
class ExportButton extends Component<Props> { class ExportButton extends Component<Props> {
static propTypes = {
exportState: PropTypes.func.isRequired,
};
shouldComponentUpdate() { shouldComponentUpdate() {
return false; return false;
} }

View File

@ -1,5 +1,4 @@
import React, { Component } from 'react'; import React, { ChangeEventHandler, Component, RefCallback } from 'react';
import PropTypes from 'prop-types';
import { connect, ResolveThunks } from 'react-redux'; import { connect, ResolveThunks } from 'react-redux';
import { Button } from 'devui'; import { Button } from 'devui';
import { TiUpload } from 'react-icons/ti'; import { TiUpload } from 'react-icons/ti';
@ -9,38 +8,29 @@ type DispatchProps = ResolveThunks<typeof actionCreators>;
type Props = DispatchProps; type Props = DispatchProps;
class ImportButton extends Component<Props> { class ImportButton extends Component<Props> {
static propTypes = { fileInput?: HTMLInputElement | null;
importState: PropTypes.func.isRequired,
};
constructor() {
super();
this.handleImport = this.handleImport.bind(this);
this.handleImportFile = this.handleImportFile.bind(this);
this.mapRef = this.mapRef.bind(this);
}
shouldComponentUpdate() { shouldComponentUpdate() {
return false; return false;
} }
mapRef(node) { mapRef: RefCallback<HTMLInputElement> = (node) => {
this.fileInput = node; this.fileInput = node;
} };
handleImport() { handleImport = () => {
this.fileInput.click(); this.fileInput!.click();
} };
handleImportFile(e) { handleImportFile: ChangeEventHandler<HTMLInputElement> = (e) => {
const file = e.target.files[0]; const file = e.target.files![0];
const reader = new FileReader(); const reader = new FileReader();
reader.onload = () => { reader.onload = () => {
this.props.importState(reader.result); this.props.importState(reader.result as string);
}; };
reader.readAsText(file); reader.readAsText(file);
e.target.value = ''; // eslint-disable-line no-param-reassign e.target.value = '';
} };
render() { render() {
return ( return (

View File

@ -3,7 +3,8 @@ import PropTypes from 'prop-types';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { Button } from 'devui'; import { Button } from 'devui';
import { IoIosLock } from 'react-icons/io'; import { IoIosLock } from 'react-icons/io';
import { lockChanges } from '../../actions'; import { lockChanges, StoreAction } from '../../actions';
import { Dispatch } from 'redux';
class LockButton extends Component { class LockButton extends Component {
static propTypes = { static propTypes = {
@ -31,7 +32,10 @@ class LockButton extends Component {
} }
} }
function mapDispatchToProps(dispatch, ownProps) { function mapDispatchToProps(
dispatch: Dispatch<StoreAction>,
ownProps: OwnProps
) {
return { return {
lockChanges: () => dispatch(lockChanges(!ownProps.locked)), lockChanges: () => dispatch(lockChanges(!ownProps.locked)),
}; };

View File

@ -7,8 +7,8 @@ export default class PrintButton extends Component {
return false; return false;
} }
handlePrint() { handlePrint = () => {
const d3svg = document.getElementById('d3svg'); const d3svg = (document.getElementById('d3svg') as unknown) as SVGGElement;
if (!d3svg) { if (!d3svg) {
window.print(); window.print();
return; return;
@ -17,11 +17,11 @@ export default class PrintButton extends Component {
const initHeight = d3svg.style.height; const initHeight = d3svg.style.height;
const initWidth = d3svg.style.width; const initWidth = d3svg.style.width;
const box = d3svg.getBBox(); const box = d3svg.getBBox();
d3svg.style.height = box.height; d3svg.style.height = `${box.height}`;
d3svg.style.width = box.width; d3svg.style.width = `${box.width}`;
const g = d3svg.firstChild; const g = d3svg.firstChild! as SVGGElement;
const initTransform = g.getAttribute('transform'); const initTransform = g.getAttribute('transform')!;
g.setAttribute( g.setAttribute(
'transform', 'transform',
initTransform.replace(/.+scale\(/, 'translate(57, 10) scale(') initTransform.replace(/.+scale\(/, 'translate(57, 10) scale(')
@ -32,7 +32,7 @@ export default class PrintButton extends Component {
d3svg.style.height = initHeight; d3svg.style.height = initHeight;
d3svg.style.width = initWidth; d3svg.style.width = initWidth;
g.setAttribute('transform', initTransform); g.setAttribute('transform', initTransform);
} };
render() { render() {
return ( return (

View File

@ -1,19 +1,16 @@
import React, { Component } from 'react'; import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { connect, ResolveThunks } from 'react-redux'; import { connect, ResolveThunks } from 'react-redux';
import { Button } from 'devui'; import { Button } from 'devui';
import { MdAvTimer } from 'react-icons/md'; import { MdAvTimer } from 'react-icons/md';
import { toggleSlider } from '../../actions'; import { toggleSlider } from '../../actions';
type DispatchProps = ResolveThunks<typeof actionCreators>; type DispatchProps = ResolveThunks<typeof actionCreators>;
type Props = DispatchProps; interface OwnProps {
isOpen: boolean;
}
type Props = DispatchProps & OwnProps;
class SliderButton extends Component<Props> { class SliderButton extends Component<Props> {
static propTypes = {
isOpen: PropTypes.bool,
toggleSlider: PropTypes.func.isRequired,
};
shouldComponentUpdate(nextProps: Props) { shouldComponentUpdate(nextProps: Props) {
return nextProps.isOpen !== this.props.isOpen; return nextProps.isOpen !== this.props.isOpen;
} }

View File

@ -50,7 +50,21 @@ function dispatchRemoteAction({ message, action, state, toAll }) {
}); });
} }
function monitoring(request) { interface DisconnectedAction {
type: 'DISCONNECTED';
id: string;
}
interface StartAction {
type: 'START';
id: string;
}
interface ErrorAction {
type: 'ERROR';
payload: string;
}
type Request = DisconnectedAction | StartAction | ErrorAction;
function monitoring(request: Request) {
if (request.type === 'DISCONNECTED') { if (request.type === 'DISCONNECTED') {
store.dispatch({ store.dispatch({
type: REMOVE_INSTANCE, type: REMOVE_INSTANCE,
@ -89,7 +103,10 @@ function monitoring(request) {
} }
} }
function subscribe(channelName, subscription) { function subscribe(
channelName: string,
subscription: typeof UPDATE_STATE | typeof UPDATE_REPORTS
) {
const channel = socket.subscribe(channelName); const channel = socket.subscribe(channelName);
if (subscription === UPDATE_STATE) channel.watch(monitoring); if (subscription === UPDATE_STATE) channel.watch(monitoring);
else { else {
@ -152,7 +169,7 @@ function connect() {
socket = socketCluster.create( socket = socketCluster.create(
connection.type === 'remotedev' ? socketOptions : connection.options connection.type === 'remotedev' ? socketOptions : connection.options
); );
handleConnection(store); handleConnection();
} catch (error) { } catch (error) {
store.dispatch({ type: actions.CONNECT_ERROR, error }); store.dispatch({ type: actions.CONNECT_ERROR, error });
store.dispatch(showNotification(error.message || error)); store.dispatch(showNotification(error.message || error));

View File

@ -1,3 +1,5 @@
import { PerformAction } from 'redux-devtools-instrument';
import { Action } from 'redux';
import { import {
UPDATE_STATE, UPDATE_STATE,
SET_STATE, SET_STATE,
@ -10,10 +12,48 @@ import {
import { DISCONNECTED } from '../constants/socketActionTypes'; import { DISCONNECTED } from '../constants/socketActionTypes';
import parseJSON from '../utils/parseJSON'; import parseJSON from '../utils/parseJSON';
import { recompute } from '../utils/updateState'; import { recompute } from '../utils/updateState';
import { StoreAction } from '../actions'; import { LiftedActionAction, StoreAction } from '../actions';
interface Features {
lock?: boolean;
export?: boolean;
import?: string;
persist?: boolean;
pause?: boolean;
reorder?: boolean;
jump?: boolean;
skip?: boolean;
dispatch?: boolean;
sync?: boolean;
test?: boolean;
}
interface Options {
name?: string;
connectionId?: string;
explicitLib?: string;
lib?: string;
actionCreators?: string;
features: Features;
serialize?: boolean;
}
interface State {
actionsById: { [actionId: number]: PerformAction<Action<unknown>> };
computedStates: { state: unknown; error?: string }[];
currentStateIndex: number;
nextActionId: number;
skippedActionIds: number[];
stagedActionIds: number[];
}
export interface InstancesState { export interface InstancesState {
selected: string | null;
current: string;
sync: boolean; sync: boolean;
connections: { [id: string]: string[] };
options: { [id: string]: Options };
states: { [id: string]: State };
persisted?: boolean; persisted?: boolean;
} }
@ -35,7 +75,12 @@ export const initialState: InstancesState = {
}, },
}; };
function updateState(state, request, id, serialize) { function updateState(
state: { [id: string]: State },
request,
id: string,
serialize: boolean | undefined
) {
let payload = request.payload; let payload = request.payload;
const actionsById = request.actionsById; const actionsById = request.actionsById;
if (actionsById) { if (actionsById) {
@ -154,7 +199,10 @@ function updateState(state, request, id, serialize) {
return { ...state, [id]: newState }; return { ...state, [id]: newState };
} }
export function dispatchAction(state, { action }) { export function dispatchAction(
state: InstancesState,
{ action }: LiftedActionAction
) {
if (action.type === 'JUMP_TO_STATE' || action.type === 'JUMP_TO_ACTION') { if (action.type === 'JUMP_TO_STATE' || action.type === 'JUMP_TO_ACTION') {
const id = state.selected || state.current; const id = state.selected || state.current;
const liftedState = state.states[id]; const liftedState = state.states[id];