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

View File

@ -9,14 +9,19 @@ import DispatcherButton from './buttons/DispatcherButton';
import SliderButton from './buttons/SliderButton';
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 = {
dispatcherIsOpen: PropTypes.bool,
sliderIsOpen: PropTypes.bool,
options: PropTypes.object.isRequired,
};
shouldComponentUpdate(nextProps) {
shouldComponentUpdate(nextProps: Props) {
return (
nextProps.dispatcherIsOpen !== this.props.dispatcherIsOpen ||
nextProps.sliderIsOpen !== this.props.sliderIsOpen ||

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,19 +1,16 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { connect, ResolveThunks } from 'react-redux';
import { Button } from 'devui';
import { MdAvTimer } from 'react-icons/md';
import { toggleSlider } from '../../actions';
type DispatchProps = ResolveThunks<typeof actionCreators>;
type Props = DispatchProps;
interface OwnProps {
isOpen: boolean;
}
type Props = DispatchProps & OwnProps;
class SliderButton extends Component<Props> {
static propTypes = {
isOpen: PropTypes.bool,
toggleSlider: PropTypes.func.isRequired,
};
shouldComponentUpdate(nextProps: Props) {
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') {
store.dispatch({
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);
if (subscription === UPDATE_STATE) channel.watch(monitoring);
else {
@ -152,7 +169,7 @@ function connect() {
socket = socketCluster.create(
connection.type === 'remotedev' ? socketOptions : connection.options
);
handleConnection(store);
handleConnection();
} catch (error) {
store.dispatch({ type: actions.CONNECT_ERROR, error });
store.dispatch(showNotification(error.message || error));

View File

@ -1,3 +1,5 @@
import { PerformAction } from 'redux-devtools-instrument';
import { Action } from 'redux';
import {
UPDATE_STATE,
SET_STATE,
@ -10,10 +12,48 @@ import {
import { DISCONNECTED } from '../constants/socketActionTypes';
import parseJSON from '../utils/parseJSON';
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 {
selected: string | null;
current: string;
sync: boolean;
connections: { [id: string]: string[] };
options: { [id: string]: Options };
states: { [id: string]: State };
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;
const actionsById = request.actionsById;
if (actionsById) {
@ -154,7 +199,10 @@ function updateState(state, request, id, serialize) {
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') {
const id = state.selected || state.current;
const liftedState = state.states[id];