From 537c746c75a6d512641a1e44432c1231642dc279 Mon Sep 17 00:00:00 2001 From: Labhansh Agrawal Date: Sat, 19 Oct 2019 22:29:56 +0530 Subject: [PATCH] porting files in lib/ actions, reducers and utils/plugins to ts (#3887) * add type definitions * rename files in lib/actions * rename files from lib/reducers to ts * renamed plugins.js to ts * Add hyper types * Fix ts errors in lib/reducers * Fix ts errors in lib/actions * Fix ts errors in plugins.ts --- lib/actions/{index.js => index.ts} | 5 +- .../{notifications.js => notifications.ts} | 4 +- lib/actions/{sessions.js => sessions.ts} | 40 +-- .../{term-groups.js => term-groups.ts} | 55 ++-- lib/actions/{ui.js => ui.ts} | 76 +++-- lib/hyper.d.ts | 131 ++++++++ lib/index.d.ts | 8 + lib/reducers/{index.js => index.ts} | 0 lib/reducers/{sessions.js => sessions.ts} | 27 +- .../{term-groups.js => term-groups.ts} | 81 +++-- lib/reducers/{ui.js => ui.ts} | 16 +- lib/utils/{plugins.js => plugins.ts} | 308 ++++++++++-------- package.json | 1 + yarn.lock | 7 + 14 files changed, 479 insertions(+), 280 deletions(-) rename lib/actions/{index.js => index.ts} (62%) rename lib/actions/{notifications.js => notifications.ts} (60%) rename lib/actions/{sessions.js => sessions.ts} (71%) rename lib/actions/{term-groups.js => term-groups.ts} (70%) rename lib/actions/{ui.js => ui.ts} (78%) create mode 100644 lib/index.d.ts rename lib/reducers/{index.js => index.ts} (100%) rename lib/reducers/{sessions.js => sessions.ts} (85%) rename lib/reducers/{term-groups.js => term-groups.ts} (68%) rename lib/reducers/{ui.js => ui.ts} (96%) rename lib/utils/{plugins.js => plugins.ts} (76%) diff --git a/lib/actions/index.js b/lib/actions/index.ts similarity index 62% rename from lib/actions/index.js rename to lib/actions/index.ts index ea8e9e65..efa5aed8 100644 --- a/lib/actions/index.js +++ b/lib/actions/index.ts @@ -1,12 +1,13 @@ import rpc from '../rpc'; import INIT from '../constants'; +import {Dispatch} from 'redux'; export default function init() { - return dispatch => { + return (dispatch: Dispatch) => { dispatch({ type: INIT, effect: () => { - rpc.emit('init'); + rpc.emit('init', null); } }); }; diff --git a/lib/actions/notifications.js b/lib/actions/notifications.ts similarity index 60% rename from lib/actions/notifications.js rename to lib/actions/notifications.ts index df928375..01077550 100644 --- a/lib/actions/notifications.js +++ b/lib/actions/notifications.ts @@ -1,13 +1,13 @@ import {NOTIFICATION_MESSAGE, NOTIFICATION_DISMISS} from '../constants/notifications'; -export function dismissNotification(id) { +export function dismissNotification(id: string) { return { type: NOTIFICATION_DISMISS, id }; } -export function addNotificationMessage(text, url = null, dismissable = true) { +export function addNotificationMessage(text: string, url: string | null = null, dismissable = true) { return { type: NOTIFICATION_MESSAGE, text, diff --git a/lib/actions/sessions.js b/lib/actions/sessions.ts similarity index 71% rename from lib/actions/sessions.js rename to lib/actions/sessions.ts index 49d249be..1e78a04b 100644 --- a/lib/actions/sessions.js +++ b/lib/actions/sessions.ts @@ -16,9 +16,11 @@ import { SESSION_SEARCH, SESSION_SEARCH_CLOSE } from '../constants/sessions'; +import {HyperState, session} from '../hyper'; +import {Dispatch} from 'redux'; -export function addSession({uid, shell, pid, cols, rows, splitDirection, activeUid}) { - return (dispatch, getState) => { +export function addSession({uid, shell, pid, cols, rows, splitDirection, activeUid}: session) { + return (dispatch: Dispatch, getState: () => HyperState) => { const {sessions} = getState(); const now = Date.now(); dispatch({ @@ -36,7 +38,7 @@ export function addSession({uid, shell, pid, cols, rows, splitDirection, activeU } export function requestSession() { - return (dispatch, getState) => { + return (dispatch: Dispatch, getState: () => HyperState) => { dispatch({ type: SESSION_REQUEST, effect: () => { @@ -49,8 +51,8 @@ export function requestSession() { }; } -export function addSessionData(uid, data) { - return dispatch => { +export function addSessionData(uid: string, data: any) { + return (dispatch: Dispatch) => { dispatch({ type: SESSION_ADD_DATA, data, @@ -67,8 +69,8 @@ export function addSessionData(uid, data) { }; } -function createExitAction(type) { - return uid => (dispatch, getState) => { +function createExitAction(type: string) { + return (uid: string) => (dispatch: Dispatch, getState: () => HyperState) => { return dispatch({ type, uid, @@ -91,8 +93,8 @@ function createExitAction(type) { export const userExitSession = createExitAction(SESSION_USER_EXIT); export const ptyExitSession = createExitAction(SESSION_PTY_EXIT); -export function setActiveSession(uid) { - return dispatch => { +export function setActiveSession(uid: string) { + return (dispatch: Dispatch) => { dispatch({ type: SESSION_SET_ACTIVE, uid @@ -106,7 +108,7 @@ export function clearActiveSession() { }; } -export function setSessionXtermTitle(uid, title) { +export function setSessionXtermTitle(uid: string, title: string) { return { type: SESSION_SET_XTERM_TITLE, uid, @@ -114,10 +116,10 @@ export function setSessionXtermTitle(uid, title) { }; } -export function resizeSession(uid, cols, rows) { - return (dispatch, getState) => { +export function resizeSession(uid: string, cols: number, rows: number) { + return (dispatch: Dispatch, getState: () => HyperState) => { const {termGroups} = getState(); - const group = findBySession(termGroups, uid); + const group = findBySession(termGroups, uid)!; const isStandaloneTerm = !group.parentUid && !group.children.length; const now = Date.now(); dispatch({ @@ -134,8 +136,8 @@ export function resizeSession(uid, cols, rows) { }; } -export function onSearch(uid) { - return (dispatch, getState) => { +export function onSearch(uid: string) { + return (dispatch: Dispatch, getState: () => HyperState) => { const targetUid = uid || getState().sessions.activeUid; dispatch({ type: SESSION_SEARCH, @@ -144,8 +146,8 @@ export function onSearch(uid) { }; } -export function closeSearch(uid) { - return (dispatch, getState) => { +export function closeSearch(uid: string) { + return (dispatch: Dispatch, getState: () => HyperState) => { const targetUid = uid || getState().sessions.activeUid; dispatch({ type: SESSION_SEARCH_CLOSE, @@ -154,8 +156,8 @@ export function closeSearch(uid) { }; } -export function sendSessionData(uid, data, escaped) { - return (dispatch, getState) => { +export function sendSessionData(uid: string, data: any, escaped: any) { + return (dispatch: Dispatch, getState: () => HyperState) => { dispatch({ type: SESSION_USER_DATA, data, diff --git a/lib/actions/term-groups.js b/lib/actions/term-groups.ts similarity index 70% rename from lib/actions/term-groups.js rename to lib/actions/term-groups.ts index 582ecaa1..5241d894 100644 --- a/lib/actions/term-groups.js +++ b/lib/actions/term-groups.ts @@ -10,9 +10,12 @@ import {SESSION_REQUEST} from '../constants/sessions'; import findBySession from '../utils/term-groups'; import getRootGroups from '../selectors'; import {setActiveSession, ptyExitSession, userExitSession} from './sessions'; +import {Dispatch} from 'redux'; +import {ITermState, ITermGroup, HyperState} from '../hyper'; +import {Immutable} from 'seamless-immutable'; -function requestSplit(direction) { - return activeUid => (dispatch, getState) => { +function requestSplit(direction: string) { + return (activeUid: string) => (dispatch: Dispatch, getState: () => HyperState): void => { dispatch({ type: SESSION_REQUEST, effect: () => { @@ -30,7 +33,7 @@ function requestSplit(direction) { export const requestVerticalSplit = requestSplit(DIRECTION.VERTICAL); export const requestHorizontalSplit = requestSplit(DIRECTION.HORIZONTAL); -export function resizeTermGroup(uid, sizes) { +export function resizeTermGroup(uid: string, sizes: number[]) { return { uid, type: TERM_GROUP_RESIZE, @@ -38,8 +41,8 @@ export function resizeTermGroup(uid, sizes) { }; } -export function requestTermGroup(activeUid) { - return (dispatch, getState) => { +export function requestTermGroup(activeUid: string) { + return (dispatch: Dispatch, getState: () => HyperState) => { dispatch({ type: TERM_GROUP_REQUEST, effect: () => { @@ -55,8 +58,8 @@ export function requestTermGroup(activeUid) { }; } -export function setActiveGroup(uid) { - return (dispatch, getState) => { +export function setActiveGroup(uid: string) { + return (dispatch: Dispatch, getState: () => HyperState) => { const {termGroups} = getState(); dispatch(setActiveSession(termGroups.activeSessions[uid])); }; @@ -65,12 +68,12 @@ export function setActiveGroup(uid) { // When we've found the next group which we want to // set as active (after closing something), we also need // to find the first child group which has a sessionUid. -const findFirstSession = (state, group) => { +const findFirstSession = (state: Immutable, group: Immutable): string | undefined => { if (group.sessionUid) { return group.sessionUid; } - for (const childUid of group.children) { + for (const childUid of group.children.asMutable()) { const child = state.termGroups[childUid]; // We want to find the *leftmost* session, // even if it's nested deep down: @@ -81,14 +84,14 @@ const findFirstSession = (state, group) => { } }; -const findPrevious = (list, old) => { +const findPrevious = (list: T[], old: T) => { const index = list.indexOf(old); // If `old` was the first item in the list, // choose the other item available: return index ? list[index - 1] : list[1]; }; -const findNextSessionUid = (state, group) => { +const findNextSessionUid = (state: Immutable, group: Immutable) => { // If we're closing a root group (i.e. a whole tab), // the next group needs to be a root group as well: if (state.activeRootGroup === group.uid) { @@ -97,13 +100,13 @@ const findNextSessionUid = (state, group) => { return findFirstSession(state, nextGroup); } - const {children} = state.termGroups[group.parentUid]; - const nextUid = findPrevious(children, group.uid); - return findFirstSession(state, state.termGroups[nextUid]); + const {children} = state.termGroups[group.parentUid!]; + const nextUid = findPrevious(children.asMutable(), group.uid); + return findFirstSession(state, state.termGroups[nextUid!]); }; -export function ptyExitTermGroup(sessionUid) { - return (dispatch, getState) => { +export function ptyExitTermGroup(sessionUid: string) { + return (dispatch: Dispatch, getState: () => HyperState) => { const {termGroups} = getState(); const group = findBySession(termGroups, sessionUid); // This might have already been closed: @@ -115,10 +118,10 @@ export function ptyExitTermGroup(sessionUid) { type: TERM_GROUP_EXIT, uid: group.uid, effect: () => { - const activeSessionUid = termGroups.activeSessions[termGroups.activeRootGroup]; + const activeSessionUid = termGroups.activeSessions[termGroups.activeRootGroup!]; if (Object.keys(termGroups.termGroups).length > 1 && activeSessionUid === sessionUid) { const nextSessionUid = findNextSessionUid(termGroups, group); - dispatch(setActiveSession(nextSessionUid)); + dispatch(setActiveSession(nextSessionUid!)); } dispatch(ptyExitSession(sessionUid)); @@ -127,8 +130,8 @@ export function ptyExitTermGroup(sessionUid) { }; } -export function userExitTermGroup(uid) { - return (dispatch, getState) => { +export function userExitTermGroup(uid: string) { + return (dispatch: Dispatch, getState: () => HyperState) => { const {termGroups} = getState(); dispatch({ type: TERM_GROUP_EXIT, @@ -138,13 +141,13 @@ export function userExitTermGroup(uid) { if (Object.keys(termGroups.termGroups).length <= 1) { // No need to attempt finding a new active session // if this is the last one we've got: - return dispatch(userExitSession(group.sessionUid)); + return dispatch(userExitSession(group.sessionUid!)); } - const activeSessionUid = termGroups.activeSessions[termGroups.activeRootGroup]; + const activeSessionUid = termGroups.activeSessions[termGroups.activeRootGroup!]; if (termGroups.activeRootGroup === uid || activeSessionUid === group.sessionUid) { const nextSessionUid = findNextSessionUid(termGroups, group); - dispatch(setActiveSession(nextSessionUid)); + dispatch(setActiveSession(nextSessionUid!)); } if (group.sessionUid) { @@ -160,13 +163,13 @@ export function userExitTermGroup(uid) { } export function exitActiveTermGroup() { - return (dispatch, getState) => { + return (dispatch: Dispatch, getState: () => HyperState) => { dispatch({ type: TERM_GROUP_EXIT_ACTIVE, effect() { const {sessions, termGroups} = getState(); - const {uid} = findBySession(termGroups, sessions.activeUid); - dispatch(userExitTermGroup(uid)); + const {uid} = findBySession(termGroups, sessions.activeUid!)!; + dispatch(userExitTermGroup(uid!)); } }); }; diff --git a/lib/actions/ui.js b/lib/actions/ui.ts similarity index 78% rename from lib/actions/ui.js rename to lib/actions/ui.ts index bda6b3e0..3002082c 100644 --- a/lib/actions/ui.js +++ b/lib/actions/ui.ts @@ -28,11 +28,14 @@ import { import {setActiveGroup} from './term-groups'; import parseUrl from 'parse-url'; +import {Dispatch} from 'redux'; +import {HyperState} from '../hyper'; +import {Stats} from 'fs'; const {stat} = window.require('fs'); -export function openContextMenu(uid, selection) { - return (dispatch, getState) => { +export function openContextMenu(uid: string, selection: any) { + return (dispatch: Dispatch, getState: () => HyperState) => { dispatch({ type: UI_CONTEXTMENU_OPEN, uid, @@ -48,7 +51,7 @@ export function openContextMenu(uid, selection) { } export function increaseFontSize() { - return (dispatch, getState) => { + return (dispatch: Dispatch, getState: () => HyperState) => { dispatch({ type: UI_FONT_SIZE_INCR, effect() { @@ -65,7 +68,7 @@ export function increaseFontSize() { } export function decreaseFontSize() { - return (dispatch, getState) => { + return (dispatch: Dispatch, getState: () => HyperState) => { dispatch({ type: UI_FONT_SIZE_DECR, effect() { @@ -89,7 +92,7 @@ export function resetFontSize() { } export function setFontSmoothing() { - return dispatch => { + return (dispatch: Dispatch) => { setTimeout(() => { const devicePixelRatio = window.devicePixelRatio; const fontSmoothing = devicePixelRatio < 2 ? 'subpixel-antialiased' : 'antialiased'; @@ -110,18 +113,21 @@ export function windowGeometryUpdated() { // Find all sessions that are below the given // termGroup uid in the hierarchy: -const findChildSessions = (termGroups, uid) => { +const findChildSessions = (termGroups: any, uid: string): string[] => { const group = termGroups[uid]; if (group.sessionUid) { return [uid]; } - return group.children.reduce((total, childUid) => total.concat(findChildSessions(termGroups, childUid)), []); + return group.children.reduce( + (total: string[], childUid: string) => total.concat(findChildSessions(termGroups, childUid)), + [] + ); }; // Get the index of the next or previous group, // depending on the movement direction: -const getNeighborIndex = (groups, uid, type) => { +const getNeighborIndex = (groups: string[], uid: string, type: string) => { if (type === UI_MOVE_NEXT_PANE) { return (groups.indexOf(uid) + 1) % groups.length; } @@ -129,21 +135,21 @@ const getNeighborIndex = (groups, uid, type) => { return (groups.indexOf(uid) + groups.length - 1) % groups.length; }; -function moveToNeighborPane(type) { - return () => (dispatch, getState) => { +function moveToNeighborPane(type: string) { + return () => (dispatch: Dispatch, getState: () => HyperState) => { dispatch({ type, effect() { const {sessions, termGroups} = getState(); - const {uid} = findBySession(termGroups, sessions.activeUid); - const childGroups = findChildSessions(termGroups.termGroups, termGroups.activeRootGroup); + const {uid} = findBySession(termGroups, sessions.activeUid!)!; + const childGroups = findChildSessions(termGroups.termGroups, termGroups.activeRootGroup!); if (childGroups.length === 1) { //eslint-disable-next-line no-console console.log('ignoring move for single group'); } else { - const index = getNeighborIndex(childGroups, uid, type); + const index = getNeighborIndex(childGroups, uid!, type); const {sessionUid} = termGroups.termGroups[childGroups[index]]; - dispatch(setActiveSession(sessionUid)); + dispatch(setActiveSession(sessionUid!)); } } }); @@ -153,13 +159,13 @@ function moveToNeighborPane(type) { export const moveToNextPane = moveToNeighborPane(UI_MOVE_NEXT_PANE); export const moveToPreviousPane = moveToNeighborPane(UI_MOVE_PREV_PANE); -const getGroupUids = state => { +const getGroupUids = (state: HyperState) => { const rootGroups = getRootGroups(state); return rootGroups.map(({uid}) => uid); }; export function moveLeft() { - return (dispatch, getState) => { + return (dispatch: Dispatch, getState: () => HyperState) => { dispatch({ type: UI_MOVE_LEFT, effect() { @@ -180,7 +186,7 @@ export function moveLeft() { } export function moveRight() { - return (dispatch, getState) => { + return (dispatch: Dispatch, getState: () => HyperState) => { dispatch({ type: UI_MOVE_RIGHT, effect() { @@ -200,8 +206,8 @@ export function moveRight() { }; } -export function moveTo(i) { - return (dispatch, getState) => { +export function moveTo(i: number | 'last') { + return (dispatch: Dispatch, getState: () => HyperState) => { if (i === 'last') { // Finding last tab index const {termGroups} = getState().termGroups; @@ -217,11 +223,11 @@ export function moveTo(i) { const state = getState(); const groupUids = getGroupUids(state); const uid = state.termGroups.activeRootGroup; - if (uid === groupUids[i]) { + if (uid === groupUids[i as number]) { //eslint-disable-next-line no-console console.log('ignoring same uid'); - } else if (groupUids[i]) { - dispatch(setActiveGroup(groupUids[i])); + } else if (groupUids[i as number]) { + dispatch(setActiveGroup(groupUids[i as number])); } else { //eslint-disable-next-line no-console console.log('ignoring inexistent index', i); @@ -231,8 +237,8 @@ export function moveTo(i) { }; } -export function windowMove(window) { - return dispatch => { +export function windowMove(window: any) { + return (dispatch: Dispatch) => { dispatch({ type: UI_WINDOW_MOVE, window, @@ -244,7 +250,7 @@ export function windowMove(window) { } export function windowGeometryChange() { - return dispatch => { + return (dispatch: Dispatch) => { dispatch({ type: UI_WINDOW_MOVE, effect() { @@ -254,12 +260,12 @@ export function windowGeometryChange() { }; } -export function openFile(path) { - return dispatch => { +export function openFile(path: string) { + return (dispatch: Dispatch) => { dispatch({ type: UI_OPEN_FILE, effect() { - stat(path, (err, stats) => { + stat(path, (err: any, stats: Stats) => { if (err) { notify('Unable to open path', `"${path}" doesn't exist.`, {error: err}); } else { @@ -271,7 +277,7 @@ export function openFile(path) { } rpc.once('session add', ({uid}) => { rpc.once('session data', () => { - dispatch(sendSessionData(uid, command)); + dispatch(sendSessionData(uid, command, null)); }); }); } @@ -294,12 +300,12 @@ export function leaveFullScreen() { }; } -export function openSSH(url) { - return dispatch => { +export function openSSH(url: string) { + return (dispatch: Dispatch) => { dispatch({ type: UI_OPEN_SSH_URL, effect() { - let parsedUrl = parseUrl(url, true); + const parsedUrl = parseUrl(url, true); let command = parsedUrl.protocol + ' ' + (parsedUrl.user ? `${parsedUrl.user}@` : '') + parsedUrl.resource; if (parsedUrl.port) command += ' -p ' + parsedUrl.port; @@ -308,7 +314,7 @@ export function openSSH(url) { rpc.once('session add', ({uid}) => { rpc.once('session data', () => { - dispatch(sendSessionData(uid, command)); + dispatch(sendSessionData(uid, command, null)); }); }); @@ -318,8 +324,8 @@ export function openSSH(url) { }; } -export function execCommand(command, fn, e) { - return dispatch => +export function execCommand(command: any, fn: any, e: any) { + return (dispatch: Dispatch) => dispatch({ type: UI_COMMAND_EXEC, command, diff --git a/lib/hyper.d.ts b/lib/hyper.d.ts index 31d332b5..10cf2c1f 100644 --- a/lib/hyper.d.ts +++ b/lib/hyper.d.ts @@ -1,3 +1,6 @@ +import {Reducer} from 'redux'; +import {Immutable} from 'seamless-immutable'; + declare global { interface Window { __rpcId: string; @@ -20,3 +23,131 @@ export type ITermState = { activeSessions: Record; activeRootGroup: string | null; }; + +export type ITermGroupReducer = Reducer, any>; + +export type uiState = { + _lastUpdate: null; + activeUid: string | null; + activityMarkers: {}; + backgroundColor: string; + bell: string; + bellSoundURL: string | null; + bellSound: string | null; + borderColor: string; + colors: { + black: string; + blue: string; + cyan: string; + green: string; + lightBlack: string; + lightBlue: string; + lightCyan: string; + lightGreen: string; + lightMagenta: string; + lightRed: string; + lightWhite: string; + lightYellow: string; + magenta: string; + red: string; + white: string; + yellow: string; + }; + cols: number | null; + copyOnSelect: boolean; + css: string; + cursorAccentColor: string; + cursorBlink: boolean; + cursorColor: string; + cursorShape: string; + cwd?: string; + disableLigatures: boolean; + fontFamily: string; + fontSize: number; + fontSizeOverride: null | number; + fontSmoothingOverride: string; + fontWeight: string; + fontWeightBold: string; + foregroundColor: string; + fullScreen: boolean; + letterSpacing: number; + lineHeight: number; + macOptionSelectionMode: string; + maximized: boolean; + messageDismissable: null | boolean; + messageText: null; + messageURL: null; + modifierKeys: { + altIsMeta: boolean; + cmdIsMeta: boolean; + }; + notifications: { + font: boolean; + message: boolean; + resize: boolean; + updates: boolean; + }; + openAt: Record; + padding: string; + quickEdit: boolean; + resizeAt: number; + rows: number | null; + scrollback: number; + selectionColor: string; + showHamburgerMenu: string; + showWindowControls: string; + termCSS: string; + uiFontFamily: string; + updateCanInstall: null | boolean; + updateNotes: null; + updateReleaseUrl: null; + updateVersion: null; + webGLRenderer: boolean; +}; + +export type IUiReducer = Reducer>; +export type session = { + cleared: boolean; + cols: null; + pid: null; + resizeAt: number; + rows: null; + search: boolean; + shell: string; + title: string; + uid: string; + url: null; + splitDirection: string; + activeUid: string; +}; +export type sessionState = { + sessions: Record>; + activeUid: string | null; +}; + +export type ISessionReducer = Reducer>; + +export type hyperPlugin = { + getTabProps: any; + getTabsProps: any; + getTermGroupProps: any; + getTermProps: any; + mapHeaderDispatch: any; + mapHyperDispatch: any; + mapHyperTermDispatch: any; + mapNotificationsDispatch: any; + mapTermsDispatch: any; + mapHeaderState: any; + mapHyperState: any; + mapHyperTermState: any; + mapNotificationsState: any; + mapTermsState: any; + middleware: any; + onRendererWindow: any; + reduceSessions: any; + reduceTermGroups: any; + reduceUI: any; +}; + +import rootReducer from './reducers/index'; +export type HyperState = ReturnType; diff --git a/lib/index.d.ts b/lib/index.d.ts new file mode 100644 index 00000000..e45b7ede --- /dev/null +++ b/lib/index.d.ts @@ -0,0 +1,8 @@ +declare module 'php-escape-shell' { + // eslint-disable-next-line @typescript-eslint/camelcase + export function php_escapeshellcmd(path: string): string; +} + +declare module 'parse-url' { + export default function(...args: any[]): any; +} diff --git a/lib/reducers/index.js b/lib/reducers/index.ts similarity index 100% rename from lib/reducers/index.js rename to lib/reducers/index.ts diff --git a/lib/reducers/sessions.js b/lib/reducers/sessions.ts similarity index 85% rename from lib/reducers/sessions.js rename to lib/reducers/sessions.ts index c3e25f6a..dfc58c03 100644 --- a/lib/reducers/sessions.js +++ b/lib/reducers/sessions.ts @@ -1,4 +1,4 @@ -import Immutable from 'seamless-immutable'; +import Immutable, {Immutable as ImmutableType} from 'seamless-immutable'; import {decorateSessionsReducer} from '../utils/plugins'; import { SESSION_ADD, @@ -13,13 +13,14 @@ import { SESSION_SEARCH, SESSION_SEARCH_CLOSE } from '../constants/sessions'; +import {sessionState} from '../hyper'; -const initialState = Immutable({ +const initialState: ImmutableType = Immutable({ sessions: {}, activeUid: null }); -function Session(obj) { +function Session(obj: Immutable.DeepPartial) { return Immutable({ uid: '', title: '', @@ -33,7 +34,15 @@ function Session(obj) { }).merge(obj); } -const reducer = (state = initialState, action) => { +function deleteSession(state: ImmutableType, uid: string) { + return state.updateIn(['sessions'], (sessions: ImmutableType) => { + const sessions_ = sessions.asMutable(); + delete sessions_[uid]; + return sessions_; + }); +} + +const reducer = (state: ImmutableType = initialState, action: any) => { switch (action.type) { case SESSION_ADD: return state.set('activeUid', action.uid).setIn( @@ -60,7 +69,7 @@ const reducer = (state = initialState, action) => { return state.merge( { sessions: { - [state.activeUid]: { + [state.activeUid!]: { cleared: true } } @@ -126,11 +135,3 @@ const reducer = (state = initialState, action) => { }; export default decorateSessionsReducer(reducer); - -function deleteSession(state, uid) { - return state.updateIn(['sessions'], sessions => { - const sessions_ = sessions.asMutable(); - delete sessions_[uid]; - return sessions_; - }); -} diff --git a/lib/reducers/term-groups.js b/lib/reducers/term-groups.ts similarity index 68% rename from lib/reducers/term-groups.js rename to lib/reducers/term-groups.ts index d5e44b65..aa9c68ac 100644 --- a/lib/reducers/term-groups.js +++ b/lib/reducers/term-groups.ts @@ -1,18 +1,19 @@ import uuid from 'uuid'; -import Immutable from 'seamless-immutable'; +import Immutable, {Immutable as ImmutableType} from 'seamless-immutable'; import {TERM_GROUP_EXIT, TERM_GROUP_RESIZE} from '../constants/term-groups'; import {SESSION_ADD, SESSION_SET_ACTIVE} from '../constants/sessions'; import findBySession from '../utils/term-groups'; import {decorateTermGroupsReducer} from '../utils/plugins'; +import {ITermGroup, ITermState, ITermGroups} from '../hyper'; const MIN_SIZE = 0.05; -const initialState = Immutable({ +const initialState = Immutable({ termGroups: {}, activeSessions: {}, activeRootGroup: null }); -function TermGroup(obj) { +function TermGroup(obj: Immutable.DeepPartial) { return Immutable({ uid: null, sessionUid: null, @@ -20,11 +21,11 @@ function TermGroup(obj) { direction: null, sizes: null, children: [] - }).merge(obj); + } as ITermGroup).merge(obj); } // Recurse upwards until we find a root term group (no parent). -const findRootGroup = (termGroups, uid) => { +const findRootGroup = (termGroups: ImmutableType, uid: string): ImmutableType => { const current = termGroups[uid]; if (!current.parentUid) { return current; @@ -33,35 +34,40 @@ const findRootGroup = (termGroups, uid) => { return findRootGroup(termGroups, current.parentUid); }; -const setActiveGroup = (state, action) => { +const setActiveGroup = (state: ImmutableType, action: {uid: string}) => { if (!action.uid) { return state.set('activeRootGroup', null); } - const childGroup = findBySession(state, action.uid); - const rootGroup = findRootGroup(state.termGroups, childGroup.uid); - return state.set('activeRootGroup', rootGroup.uid).setIn(['activeSessions', rootGroup.uid], action.uid); + const childGroup = findBySession(state, action.uid)!; + const rootGroup = findRootGroup(state.termGroups, childGroup.uid!); + return state.set('activeRootGroup', rootGroup.uid).setIn(['activeSessions', rootGroup.uid as string], action.uid); }; // Reduce existing sizes to fit a new split: -const insertRebalance = (oldSizes, index) => { +const insertRebalance = (oldSizes: ImmutableType, index: any) => { const newSize = 1 / (oldSizes.length + 1); // We spread out how much each pane should be reduced // with based on their existing size: const balanced = oldSizes.map(size => size - newSize * size); - return [...balanced.slice(0, index), newSize, ...balanced.slice(index)]; + return [...balanced.slice(0, index).asMutable(), newSize, ...balanced.slice(index).asMutable()]; }; // Spread out the removed size to all the existing sizes: -const removalRebalance = (oldSizes, index) => { +const removalRebalance = (oldSizes: ImmutableType, index: number) => { const removedSize = oldSizes[index]; const increase = removedSize / (oldSizes.length - 1); - return oldSizes.filter((_size, i) => i !== index).map(size => size + increase); + return Immutable( + oldSizes + .asMutable() + .filter((_size: number, i: number) => i !== index) + .map((size: number) => size + increase) + ); }; -const splitGroup = (state, action) => { +const splitGroup = (state: ImmutableType, action: {splitDirection: any; uid: any; activeUid: any}) => { const {splitDirection, uid, activeUid} = action; - const activeGroup = findBySession(state, activeUid); + const activeGroup = findBySession(state, activeUid)!; // If we're splitting in the same direction as the current active // group's parent - or if it's the first split for that group - // we want the parent to get another child: @@ -84,7 +90,7 @@ const splitGroup = (state, action) => { parentUid: parentGroup.uid }); - state = state.setIn(['termGroups', newSession.uid], newSession); + state = state.setIn(['termGroups', newSession.uid as string], newSession); if (parentGroup.sessionUid) { const existingSession = TermGroup({ uid: uuid.v4(), @@ -92,22 +98,22 @@ const splitGroup = (state, action) => { parentUid: parentGroup.uid }); - return state.setIn(['termGroups', existingSession.uid], existingSession).setIn( - ['termGroups', parentGroup.uid], + return state.setIn(['termGroups', existingSession.uid as string], existingSession).setIn( + ['termGroups', parentGroup.uid!], parentGroup.merge({ - sessionUid: null, + sessionUid: '', direction: splitDirection, - children: [existingSession.uid, newSession.uid] + children: [existingSession.uid!, newSession.uid!] }) ); } const {children} = parentGroup; // Insert the new child pane right after the active one: - const index = children.indexOf(activeGroup.uid) + 1; - const newChildren = [...children.slice(0, index), newSession.uid, ...children.slice(index)]; + const index = children.indexOf(activeGroup.uid!) + 1; + const newChildren = [...children.slice(0, index).asMutable(), newSession.uid!, ...children.slice(index).asMutable()]; state = state.setIn( - ['termGroups', parentGroup.uid], + ['termGroups', parentGroup.uid!], parentGroup.merge({ direction: splitDirection, children: newChildren @@ -116,7 +122,7 @@ const splitGroup = (state, action) => { if (parentGroup.sizes) { const newSizes = insertRebalance(parentGroup.sizes, index); - state = state.setIn(['termGroups', parentGroup.uid, 'sizes'], newSizes); + state = state.setIn(['termGroups', parentGroup.uid!, 'sizes'], newSizes); } return state; @@ -125,19 +131,19 @@ const splitGroup = (state, action) => { // Replace the parent by the given child in the tree, // used when we remove another child and we're left // with a one-to-one mapping between parent and child. -const replaceParent = (state, parent, child) => { +const replaceParent = (state: ImmutableType, parent: ImmutableType, child: {uid: any}) => { if (parent.parentUid) { const parentParent = state.termGroups[parent.parentUid]; // If the parent we're replacing has a parent, // we need to change the uid in its children array // with `child`: - const newChildren = parentParent.children.map(uid => (uid === parent.uid ? child.uid : uid)); + const newChildren = parentParent.children.map((uid: any) => (uid === parent.uid ? child.uid : uid)); - state = state.setIn(['termGroups', parentParent.uid, 'children'], newChildren); + state = state.setIn(['termGroups', parentParent.uid!, 'children'], newChildren); } else { // This means the given child will be // a root group, so we need to set it up as such: - const newSessions = state.activeSessions.without(parent.uid).set(child.uid, state.activeSessions[parent.uid]); + const newSessions = state.activeSessions.without(parent.uid!).set(child.uid, state.activeSessions[parent.uid!]); state = state .set('activeTermGroup', child.uid) @@ -146,17 +152,17 @@ const replaceParent = (state, parent, child) => { } return state - .set('termGroups', state.termGroups.without(parent.uid)) + .set('termGroups', state.termGroups.without(parent.uid!)) .setIn(['termGroups', child.uid, 'parentUid'], parent.parentUid); }; -const removeGroup = (state, uid) => { +const removeGroup = (state: ImmutableType, uid: string) => { const group = state.termGroups[uid]; // when close tab with multiple panes, it remove group from parent to child. so maybe the parentUid exists but parent group have removed. // it's safe to remove the group. if (group.parentUid && state.termGroups[group.parentUid]) { const parent = state.termGroups[group.parentUid]; - const newChildren = parent.children.filter(childUid => childUid !== uid); + const newChildren = parent.children.filter((childUid: any) => childUid !== uid); if (newChildren.length === 1) { // Since we only have one child left, // we can merge the parent and child into one group: @@ -177,7 +183,7 @@ const removeGroup = (state, uid) => { .set('activeSessions', state.activeSessions.without(uid)); }; -const resizeGroup = (state, uid, sizes) => { +const resizeGroup = (state: ImmutableType, uid: any, sizes: number[]) => { // Make sure none of the sizes fall below MIN_SIZE: if (sizes.find(size => size < MIN_SIZE)) { return state; @@ -186,7 +192,16 @@ const resizeGroup = (state, uid, sizes) => { return state.setIn(['termGroups', uid, 'sizes'], sizes); }; -const reducer = (state = initialState, action) => { +const reducer = ( + state = initialState, + action: { + splitDirection: any; + uid: any; + activeUid: any; + type: any; + sizes: any; + } +) => { switch (action.type) { case SESSION_ADD: { if (action.splitDirection) { diff --git a/lib/reducers/ui.js b/lib/reducers/ui.ts similarity index 96% rename from lib/reducers/ui.js rename to lib/reducers/ui.ts index 8a711fc0..00ab2f65 100644 --- a/lib/reducers/ui.js +++ b/lib/reducers/ui.ts @@ -1,5 +1,5 @@ import {remote} from 'electron'; -import Immutable from 'seamless-immutable'; +import Immutable, {Immutable as ImmutableType} from 'seamless-immutable'; import {decorateUIReducer} from '../utils/plugins'; import {CONFIG_LOAD, CONFIG_RELOAD} from '../constants/config'; import { @@ -22,6 +22,7 @@ import { SESSION_SET_CWD } from '../constants/sessions'; import {UPDATE_AVAILABLE} from '../constants/updater'; +import {uiState} from '../hyper'; const allowedCursorShapes = new Set(['BEAM', 'BLOCK', 'UNDERLINE']); const allowedCursorBlinkValues = new Set([true, false]); @@ -30,7 +31,7 @@ const allowedHamburgerMenuValues = new Set([true, false]); const allowedWindowControlsValues = new Set([true, false, 'left']); // Populate `config-default.js` from this :) -const initial = Immutable({ +const initial: ImmutableType = Immutable({ cols: null, rows: null, scrollback: 1000, @@ -87,6 +88,9 @@ const initial = Immutable({ maximized: false, updateVersion: null, updateNotes: null, + updateReleaseUrl: null, + updateCanInstall: null, + _lastUpdate: null, messageText: null, messageURL: null, messageDismissable: null, @@ -108,7 +112,7 @@ const initial = Immutable({ const currentWindow = remote.getCurrentWindow(); -const reducer = (state = initial, action) => { +const reducer = (state = initial, action: any) => { let state_ = state; let isMax; //eslint-disable-next-line default-case @@ -122,7 +126,7 @@ const reducer = (state = initial, action) => { // font size changed from the config .merge( (() => { - const ret = {}; + const ret: Immutable.DeepPartial = {}; if (config.scrollback) { ret.scrollback = config.scrollback; @@ -295,12 +299,12 @@ const reducer = (state = initial, action) => { case SESSION_PTY_EXIT: state_ = state - .updateIn(['openAt'], times => { + .updateIn(['openAt'], (times: ImmutableType) => { const times_ = times.asMutable(); delete times_[action.uid]; return times_; }) - .updateIn(['activityMarkers'], markers => { + .updateIn(['activityMarkers'], (markers: ImmutableType) => { const markers_ = markers.asMutable(); delete markers_[action.uid]; return markers_; diff --git a/lib/utils/plugins.js b/lib/utils/plugins.ts similarity index 76% rename from lib/utils/plugins.js rename to lib/utils/plugins.ts index 38d92f9d..ea080aa2 100644 --- a/lib/utils/plugins.js +++ b/lib/utils/plugins.ts @@ -9,10 +9,135 @@ import React, {PureComponent} from 'react'; import ReactDOM from 'react-dom'; import Notification from '../components/notification'; import notify from './notify'; +import {hyperPlugin, IUiReducer, ISessionReducer, ITermGroupReducer} from '../hyper'; -const Module = require('module'); +// remote interface to `../plugins` +const plugins = remote.require('./plugins') as typeof import('../../app/plugins'); + +// `require`d modules +let modules: any; + +// cache of decorated components +let decorated: Record = {}; + +// various caches extracted of the plugin methods +let connectors: { + Terms: {state: any[]; dispatch: any[]}; + Header: {state: any[]; dispatch: any[]}; + Hyper: {state: any[]; dispatch: any[]}; + Notifications: {state: any[]; dispatch: any[]}; +}; +let middlewares: any[]; +let uiReducers: IUiReducer[]; +let sessionsReducers: ISessionReducer[]; +let termGroupsReducers: ITermGroupReducer[]; +let tabPropsDecorators: any[]; +let tabsPropsDecorators: any[]; +let termPropsDecorators: any[]; +let termGroupPropsDecorators: any[]; +let propsDecorators: { + getTermProps: any[]; + getTabProps: any[]; + getTabsProps: any[]; + getTermGroupProps: any[]; +}; +let reducersDecorators: { + reduceUI: IUiReducer[]; + reduceSessions: ISessionReducer[]; + reduceTermGroups: ITermGroupReducer[]; +}; + +// expose decorated component instance to the higher-order components +function exposeDecorated(Component_: any) { + return class DecoratedComponent extends React.Component { + constructor(props: any, context: any) { + super(props, context); + this.onRef = this.onRef.bind(this); + } + onRef(decorated_: any) { + if (this.props.onDecorated) { + try { + this.props.onDecorated(decorated_); + } catch (e) { + notify('Plugin error', `Error occurred. Check Developer Tools for details`, {error: e}); + } + } + } + render() { + return React.createElement(Component_, Object.assign({}, this.props, {ref: this.onRef})); + } + }; +} + +function getDecorated(parent: any, name: string) { + if (!decorated[name]) { + let class_ = exposeDecorated(parent); + (class_ as any).displayName = `_exposeDecorated(${name})`; + + modules.forEach((mod: any) => { + const method = 'decorate' + name; + const fn = mod[method]; + + if (fn) { + let class__; + + try { + class__ = fn(class_, {React, PureComponent, Notification, notify}); + class__.displayName = `${fn._pluginName}(${name})`; + } catch (err) { + notify( + 'Plugin error', + `${fn._pluginName}: Error occurred in \`${method}\`. Check Developer Tools for details`, + {error: err} + ); + return; + } + + if (!class__ || typeof class__.prototype.render !== 'function') { + notify( + 'Plugin error', + `${fn._pluginName}: Invalid return value of \`${method}\`. No \`render\` method found. Please return a \`React.Component\`.` + ); + return; + } + + class_ = class__; + } + }); + + decorated[name] = class_; + } + + return decorated[name]; +} + +// for each component, we return a higher-order component +// that wraps with the higher-order components +// exposed by plugins +export function decorate(Component_: any, name: string) { + return class DecoratedComponent extends React.Component { + constructor(props: any) { + super(props); + this.state = {hasError: false}; + } + componentDidCatch() { + this.setState({hasError: true}); + // No need to detail this error because React print these information. + notify( + 'Plugin error', + `Plugins decorating ${name} has been disabled because of a plugin crash. Check Developer Tools for details.` + ); + } + render() { + const Sub = this.state.hasError ? Component_ : getDecorated(Component_, name); + return React.createElement(Sub, this.props); + } + }; +} + +const Module = require('module') as typeof import('module') & {_load: Function}; const originalLoad = Module._load; -Module._load = function _load(path) { +Module._load = function _load(path: string) { // PLEASE NOTE: Code changes here, also need to be changed in // app/plugins.js switch (path) { @@ -37,38 +162,17 @@ Module._load = function _load(path) { case 'hyper/decorate': return decorate; default: + // eslint-disable-next-line prefer-rest-params return originalLoad.apply(this, arguments); } }; -// remote interface to `../plugins` -const plugins = remote.require('./plugins'); - -// `require`d modules -let modules; - -// cache of decorated components -let decorated = {}; - -// various caches extracted of the plugin methods -let connectors; -let middlewares; -let uiReducers; -let sessionsReducers; -let termGroupsReducers; -let tabPropsDecorators; -let tabsPropsDecorators; -let termPropsDecorators; -let termGroupPropsDecorators; -let propsDecorators; -let reducersDecorators; - const clearModulesCache = () => { // the fs locations where user plugins are stored const {path, localPath} = plugins.getBasePaths(); // trigger unload hooks - modules.forEach(mod => { + modules.forEach((mod: any) => { if (mod.onRendererUnload) { mod.onRendererUnload(window); } @@ -83,14 +187,14 @@ const clearModulesCache = () => { } }; -const pathModule = window.require('path'); +const pathModule = window.require('path') as typeof import('path'); -const getPluginName = path => pathModule.basename(path); +const getPluginName = (path: string) => pathModule.basename(path); -const getPluginVersion = path => { +const getPluginVersion = (path: string): string | null => { let version = null; try { - version = window.require(pathModule.resolve(path, 'package.json')).version; + version = (window.require(pathModule.resolve(path, 'package.json')) as any).version as string; } catch (err) { //eslint-disable-next-line no-console console.warn(`No package.json found in ${path}`); @@ -132,19 +236,19 @@ const loadModules = () => { reduceTermGroups: termGroupsReducers }; - const loadedPlugins = plugins.getLoadedPluginVersions().map(plugin => plugin.name); + const loadedPlugins = plugins.getLoadedPluginVersions().map((plugin: any) => plugin.name); modules = paths.plugins .concat(paths.localPlugins) - .filter(plugin => loadedPlugins.indexOf(basename(plugin)) !== -1) - .map(path => { - let mod; + .filter((plugin: any) => loadedPlugins.indexOf(basename(plugin)) !== -1) + .map((path: any) => { + let mod: hyperPlugin; const pluginName = getPluginName(path); const pluginVersion = getPluginVersion(path); // window.require allows us to ensure this doesn't get // in the way of our build try { - mod = window.require(path); + mod = window.require(path) as any; } catch (err) { notify( 'Plugin load error', @@ -154,12 +258,12 @@ const loadModules = () => { return undefined; } - for (const i in mod) { - if ({}.hasOwnProperty.call(mod, i)) { + (Object.keys(mod) as (keyof typeof mod)[]).forEach(i => { + if (Object.hasOwnProperty.call(mod, i)) { mod[i]._pluginName = pluginName; mod[i]._pluginVersion = pluginVersion; } - } + }); // mapHyperTermState mapping for backwards compatibility with hyperterm if (mod.mapHyperTermState) { @@ -247,9 +351,9 @@ const loadModules = () => { return mod; }) - .filter(mod => Boolean(mod)); + .filter((mod: any) => Boolean(mod)); - const deprecatedPlugins = plugins.getDeprecatedConfig(); + const deprecatedPlugins: Record = plugins.getDeprecatedConfig(); Object.keys(deprecatedPlugins).forEach(name => { const {css} = deprecatedPlugins[name]; if (css) { @@ -270,9 +374,9 @@ export function reload() { decorated = {}; } -function getProps(name, props, ...fnArgs) { +function getProps(name: keyof typeof propsDecorators, props: any, ...fnArgs: any[]) { const decorators = propsDecorators[name]; - let props_; + let props_: typeof props; decorators.forEach(fn => { let ret_; @@ -301,27 +405,27 @@ function getProps(name, props, ...fnArgs) { return props_ || props; } -export function getTermGroupProps(uid, parentProps, props) { +export function getTermGroupProps(uid: string, parentProps: any, props: any) { return getProps('getTermGroupProps', props, uid, parentProps); } -export function getTermProps(uid, parentProps, props) { +export function getTermProps(uid: string, parentProps: any, props: any) { return getProps('getTermProps', props, uid, parentProps); } -export function getTabsProps(parentProps, props) { +export function getTabsProps(parentProps: any, props: any) { return getProps('getTabsProps', props, parentProps); } -export function getTabProps(tab, parentProps, props) { +export function getTabProps(tab: any, parentProps: any, props: any) { return getProps('getTabProps', props, tab, parentProps); } // connects + decorates a class // plugins can override mapToState, dispatchToProps // and the class gets decorated (proxied) -export function connect(stateFn, dispatchFn, c, d = {}) { - return (Class, name) => { +export function connect(stateFn: Function, dispatchFn: Function, c: any, d = {}) { + return (Class: any, name: keyof typeof connectors) => { return reduxConnect( state => { let ret = stateFn(state); @@ -382,12 +486,16 @@ export function connect(stateFn, dispatchFn, c, d = {}) { }; } -function decorateReducer(name, fn) { +const decorateReducer: { + (name: 'reduceUI', fn: IUiReducer): IUiReducer; + (name: 'reduceSessions', fn: ISessionReducer): ISessionReducer; + (name: 'reduceTermGroups', fn: ITermGroupReducer): ITermGroupReducer; +} = (name: T, fn: any) => { const reducers = reducersDecorators[name]; - return (state, action) => { + return (state: any, action: any) => { let state_ = fn(state, action); - reducers.forEach(pluginReducer => { + reducers.forEach((pluginReducer: any) => { let state__; try { @@ -409,111 +517,23 @@ function decorateReducer(name, fn) { return state_; }; -} +}; -export function decorateTermGroupsReducer(fn) { +export function decorateTermGroupsReducer(fn: ITermGroupReducer) { return decorateReducer('reduceTermGroups', fn); } -export function decorateUIReducer(fn) { +export function decorateUIReducer(fn: IUiReducer) { return decorateReducer('reduceUI', fn); } -export function decorateSessionsReducer(fn) { +export function decorateSessionsReducer(fn: ISessionReducer) { return decorateReducer('reduceSessions', fn); } // redux middleware generator -export const middleware = store => next => action => { - const nextMiddleware = remaining => action_ => +export const middleware = (store: any) => (next: any) => (action: any) => { + const nextMiddleware = (remaining: any[]) => (action_: any) => remaining.length ? remaining[0](store)(nextMiddleware(remaining.slice(1)))(action_) : next(action_); nextMiddleware(middlewares)(action); }; - -// expose decorated component instance to the higher-order components -function exposeDecorated(Component_) { - return class DecoratedComponent extends React.Component { - constructor(props, context) { - super(props, context); - this.onRef = this.onRef.bind(this); - } - onRef(decorated_) { - if (this.props.onDecorated) { - try { - this.props.onDecorated(decorated_); - } catch (e) { - notify('Plugin error', `Error occurred. Check Developer Tools for details`, {error: e}); - } - } - } - render() { - return React.createElement(Component_, Object.assign({}, this.props, {ref: this.onRef})); - } - }; -} - -function getDecorated(parent, name) { - if (!decorated[name]) { - let class_ = exposeDecorated(parent); - class_.displayName = `_exposeDecorated(${name})`; - - modules.forEach(mod => { - const method = 'decorate' + name; - const fn = mod[method]; - - if (fn) { - let class__; - - try { - class__ = fn(class_, {React, PureComponent, Notification, notify}); - class__.displayName = `${fn._pluginName}(${name})`; - } catch (err) { - notify( - 'Plugin error', - `${fn._pluginName}: Error occurred in \`${method}\`. Check Developer Tools for details`, - {error: err} - ); - return; - } - - if (!class__ || typeof class__.prototype.render !== 'function') { - notify( - 'Plugin error', - `${fn._pluginName}: Invalid return value of \`${method}\`. No \`render\` method found. Please return a \`React.Component\`.` - ); - return; - } - - class_ = class__; - } - }); - - decorated[name] = class_; - } - - return decorated[name]; -} - -// for each component, we return a higher-order component -// that wraps with the higher-order components -// exposed by plugins -export function decorate(Component_, name) { - return class DecoratedComponent extends React.Component { - constructor(props) { - super(props); - this.state = {hasError: false}; - } - componentDidCatch() { - this.setState({hasError: true}); - // No need to detail this error because React print these information. - notify( - 'Plugin error', - `Plugins decorating ${name} has been disabled because of a plugin crash. Check Developer Tools for details.` - ); - } - render() { - const Sub = this.state.hasError ? Component_ : getDecorated(Component_, name); - return React.createElement(Sub, this.props); - } - }; -} diff --git a/package.json b/package.json index a24824e9..ab15738d 100644 --- a/package.json +++ b/package.json @@ -286,6 +286,7 @@ "@types/react-dom": "^16.9.1", "@types/react-redux": "^7.1.4", "@types/seamless-immutable": "7.1.11", + "@types/uuid": "3.4.5", "@typescript-eslint/eslint-plugin": "2.4.0", "@typescript-eslint/parser": "2.3.3", "ava": "0.25.0", diff --git a/yarn.lock b/yarn.lock index 4b30f511..ee97f99b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -513,6 +513,13 @@ resolved "https://registry.yarnpkg.com/@types/seamless-immutable/-/seamless-immutable-7.1.11.tgz#89250c3e2587a44c2a051f5798e6f29f0e91bbc9" integrity sha512-U8Mp+Q6P5ZxG6KDRwWduQl822MnsnBtOq/Lb4HQB9Tzi8t1nr7PLqq88I4kTwEDHO95hcH8y8KhGvxWPIS6QoQ== +"@types/uuid@3.4.5": + version "3.4.5" + resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-3.4.5.tgz#d4dc10785b497a1474eae0ba7f0cb09c0ddfd6eb" + integrity sha512-MNL15wC3EKyw1VLF+RoVO4hJJdk9t/Hlv3rt1OL65Qvuadm4BYo6g9ZJQqoq7X8NBFSsQXgAujWciovh2lpVjA== + dependencies: + "@types/node" "*" + "@typescript-eslint/eslint-plugin@2.4.0": version "2.4.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-2.4.0.tgz#aaf6b542ff75b78f4191a8bf1c519184817caa24"