mirror of
https://github.com/quine-global/hyper.git
synced 2026-01-13 04:28:41 -09:00
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
This commit is contained in:
parent
27c9cc3892
commit
537c746c75
14 changed files with 479 additions and 280 deletions
|
|
@ -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<any>) => {
|
||||
dispatch({
|
||||
type: INIT,
|
||||
effect: () => {
|
||||
rpc.emit('init');
|
||||
rpc.emit('init', null);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
|
@ -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,
|
||||
|
|
@ -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<any>, 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<any>, 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<any>) => {
|
||||
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<any>, 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<any>) => {
|
||||
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<any>, 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<any>, 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<any>, 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<any>, getState: () => HyperState) => {
|
||||
dispatch({
|
||||
type: SESSION_USER_DATA,
|
||||
data,
|
||||
|
|
@ -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<any>, 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<any>, 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<any>, 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<ITermState>, group: Immutable<ITermGroup>): 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 = <T>(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<ITermState>, group: Immutable<ITermGroup>) => {
|
||||
// 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<any>, 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<any>, 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<any>, 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!));
|
||||
}
|
||||
});
|
||||
};
|
||||
|
|
@ -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<any>, 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<any>, 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<any>, getState: () => HyperState) => {
|
||||
dispatch({
|
||||
type: UI_FONT_SIZE_DECR,
|
||||
effect() {
|
||||
|
|
@ -89,7 +92,7 @@ export function resetFontSize() {
|
|||
}
|
||||
|
||||
export function setFontSmoothing() {
|
||||
return dispatch => {
|
||||
return (dispatch: Dispatch<any>) => {
|
||||
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<any>, 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<any>, getState: () => HyperState) => {
|
||||
dispatch({
|
||||
type: UI_MOVE_LEFT,
|
||||
effect() {
|
||||
|
|
@ -180,7 +186,7 @@ export function moveLeft() {
|
|||
}
|
||||
|
||||
export function moveRight() {
|
||||
return (dispatch, getState) => {
|
||||
return (dispatch: Dispatch<any>, 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<any>, 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<any>) => {
|
||||
dispatch({
|
||||
type: UI_WINDOW_MOVE,
|
||||
window,
|
||||
|
|
@ -244,7 +250,7 @@ export function windowMove(window) {
|
|||
}
|
||||
|
||||
export function windowGeometryChange() {
|
||||
return dispatch => {
|
||||
return (dispatch: Dispatch<any>) => {
|
||||
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<any>) => {
|
||||
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<any>) => {
|
||||
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<any>) =>
|
||||
dispatch({
|
||||
type: UI_COMMAND_EXEC,
|
||||
command,
|
||||
131
lib/hyper.d.ts
vendored
131
lib/hyper.d.ts
vendored
|
|
@ -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<string, string>;
|
||||
activeRootGroup: string | null;
|
||||
};
|
||||
|
||||
export type ITermGroupReducer = Reducer<Immutable<ITermState>, 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<string, number>;
|
||||
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<Immutable<uiState>>;
|
||||
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<string, Partial<session>>;
|
||||
activeUid: string | null;
|
||||
};
|
||||
|
||||
export type ISessionReducer = Reducer<Immutable<sessionState>>;
|
||||
|
||||
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<typeof rootReducer>;
|
||||
|
|
|
|||
8
lib/index.d.ts
vendored
Normal file
8
lib/index.d.ts
vendored
Normal file
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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<sessionState> = Immutable({
|
||||
sessions: {},
|
||||
activeUid: null
|
||||
});
|
||||
|
||||
function Session(obj) {
|
||||
function Session(obj: Immutable.DeepPartial<sessionState['sessions']>) {
|
||||
return Immutable({
|
||||
uid: '',
|
||||
title: '',
|
||||
|
|
@ -33,7 +34,15 @@ function Session(obj) {
|
|||
}).merge(obj);
|
||||
}
|
||||
|
||||
const reducer = (state = initialState, action) => {
|
||||
function deleteSession(state: ImmutableType<sessionState>, uid: string) {
|
||||
return state.updateIn(['sessions'], (sessions: ImmutableType<any>) => {
|
||||
const sessions_ = sessions.asMutable();
|
||||
delete sessions_[uid];
|
||||
return sessions_;
|
||||
});
|
||||
}
|
||||
|
||||
const reducer = (state: ImmutableType<sessionState> = 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_;
|
||||
});
|
||||
}
|
||||
|
|
@ -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<ITermState>({
|
||||
termGroups: {},
|
||||
activeSessions: {},
|
||||
activeRootGroup: null
|
||||
});
|
||||
|
||||
function TermGroup(obj) {
|
||||
function TermGroup(obj: Immutable.DeepPartial<ITermGroup>) {
|
||||
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<ITermGroups>, uid: string): ImmutableType<ITermGroup> => {
|
||||
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<ITermState>, 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<number[]>, 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<number[]>, 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<ITermState>, 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<ITermState>, parent: ImmutableType<ITermGroup>, 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<ITermState>, 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<ITermState>, 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) {
|
||||
|
|
@ -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<uiState> = 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<uiState> = {};
|
||||
|
||||
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<any>) => {
|
||||
const times_ = times.asMutable();
|
||||
delete times_[action.uid];
|
||||
return times_;
|
||||
})
|
||||
.updateIn(['activityMarkers'], markers => {
|
||||
.updateIn(['activityMarkers'], (markers: ImmutableType<any>) => {
|
||||
const markers_ = markers.asMutable();
|
||||
delete markers_[action.uid];
|
||||
return markers_;
|
||||
|
|
@ -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<string, any> = {};
|
||||
|
||||
// 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<any, any> {
|
||||
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<any, {hasError: boolean}> {
|
||||
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<string, any> = 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;
|
||||
} = <T extends keyof typeof reducersDecorators>(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);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
Loading…
Reference in a new issue