Update state and reducer typings

This commit is contained in:
Labhansh Agrawal 2021-03-05 21:33:35 +05:30
parent 6199f1b025
commit aa9a50e8f9
8 changed files with 75 additions and 100 deletions

View file

@ -11,7 +11,6 @@ import findBySession from '../utils/term-groups';
import {getRootGroups} from '../selectors'; import {getRootGroups} from '../selectors';
import {setActiveSession, ptyExitSession, userExitSession} from './sessions'; import {setActiveSession, ptyExitSession, userExitSession} from './sessions';
import {ITermState, ITermGroup, HyperState, HyperDispatch, HyperActions} from '../hyper'; import {ITermState, ITermGroup, HyperState, HyperDispatch, HyperActions} from '../hyper';
import {Immutable} from 'seamless-immutable';
function requestSplit(direction: 'VERTICAL' | 'HORIZONTAL') { function requestSplit(direction: 'VERTICAL' | 'HORIZONTAL') {
return (activeUid: string) => (dispatch: HyperDispatch, getState: () => HyperState): void => { return (activeUid: string) => (dispatch: HyperDispatch, getState: () => HyperState): void => {
@ -67,7 +66,7 @@ export function setActiveGroup(uid: string) {
// When we've found the next group which we want to // When we've found the next group which we want to
// set as active (after closing something), we also need // set as active (after closing something), we also need
// to find the first child group which has a sessionUid. // to find the first child group which has a sessionUid.
const findFirstSession = (state: Immutable<ITermState>, group: Immutable<ITermGroup>): string | undefined => { const findFirstSession = (state: ITermState, group: ITermGroup): string | undefined => {
if (group.sessionUid) { if (group.sessionUid) {
return group.sessionUid; return group.sessionUid;
} }
@ -90,7 +89,7 @@ const findPrevious = <T>(list: T[], old: T) => {
return index ? list[index - 1] : list[1]; return index ? list[index - 1] : list[1];
}; };
const findNextSessionUid = (state: Immutable<ITermState>, group: Immutable<ITermGroup>) => { const findNextSessionUid = (state: ITermState, group: ITermGroup) => {
// If we're closing a root group (i.e. a whole tab), // If we're closing a root group (i.e. a whole tab),
// the next group needs to be a root group as well: // the next group needs to be a root group as well:
if (state.activeRootGroup === group.uid) { if (state.activeRootGroup === group.uid) {

View file

@ -28,7 +28,7 @@ import {
import {setActiveGroup} from './term-groups'; import {setActiveGroup} from './term-groups';
import parseUrl from 'parse-url'; import parseUrl from 'parse-url';
import {HyperState, HyperDispatch, HyperActions} from '../hyper'; import {HyperState, HyperDispatch, HyperActions, ITermGroups} from '../hyper';
import {stat, Stats} from 'fs'; import {stat, Stats} from 'fs';
export function openContextMenu(uid: string, selection: any) { export function openContextMenu(uid: string, selection: any) {
@ -110,7 +110,7 @@ export function windowGeometryUpdated(): HyperActions {
// Find all sessions that are below the given // Find all sessions that are below the given
// termGroup uid in the hierarchy: // termGroup uid in the hierarchy:
const findChildSessions = (termGroups: HyperState['termGroups']['termGroups'], uid: string): string[] => { const findChildSessions = (termGroups: ITermGroups, uid: string): string[] => {
const group = termGroups[uid]; const group = termGroups[uid];
if (group.sessionUid) { if (group.sessionUid) {
return [uid]; return [uid];

20
lib/config.d.ts vendored
View file

@ -1,13 +1,6 @@
import {FontWeight} from 'xterm'; import {FontWeight} from 'xterm';
export type configOptions = { export type ColorMap = {
autoUpdatePlugins: boolean | string;
backgroundColor: string;
bell: string;
bellSound: string | null;
bellSoundURL: string | null;
borderColor: string;
colors: {
black: string; black: string;
blue: string; blue: string;
cyan: string; cyan: string;
@ -24,7 +17,16 @@ export type configOptions = {
red: string; red: string;
white: string; white: string;
yellow: string; yellow: string;
}; };
export type configOptions = {
autoUpdatePlugins: boolean | string;
backgroundColor: string;
bell: string;
bellSound: string | null;
bellSoundURL: string | null;
borderColor: string;
colors: ColorMap;
copyOnSelect: boolean; copyOnSelect: boolean;
css: string; css: string;
cursorAccentColor: string; cursorAccentColor: string;

69
lib/hyper.d.ts vendored
View file

@ -9,27 +9,28 @@ declare global {
} }
} }
export type ITermGroup = { export type ITermGroup = Immutable<{
uid: string; uid: string;
sessionUid: string | null; sessionUid: string | null;
parentUid: string | null; parentUid: string | null;
direction: 'HORIZONTAL' | 'VERTICAL' | null; direction: 'HORIZONTAL' | 'VERTICAL' | null;
sizes: number[] | null; sizes: number[] | null;
children: string[]; children: string[];
}; }>;
export type ITermGroups = Record<string, ITermGroup>; export type ITermGroups = Immutable<Record<string, ITermGroup>>;
export type ITermState = { export type ITermState = Immutable<{
termGroups: ITermGroups; termGroups: Mutable<ITermGroups>;
activeSessions: Record<string, string>; activeSessions: Record<string, string>;
activeRootGroup: string | null; activeRootGroup: string | null;
}; }>;
export type cursorShapes = 'BEAM' | 'UNDERLINE' | 'BLOCK'; export type cursorShapes = 'BEAM' | 'UNDERLINE' | 'BLOCK';
import {FontWeight, Terminal} from 'xterm'; import {FontWeight, Terminal} from 'xterm';
import {ColorMap} from './config';
export type uiState = { export type uiState = Immutable<{
_lastUpdate: number | null; _lastUpdate: number | null;
activeUid: string | null; activeUid: string | null;
activityMarkers: Record<string, boolean>; activityMarkers: Record<string, boolean>;
@ -38,24 +39,7 @@ export type uiState = {
bellSoundURL: string | null; bellSoundURL: string | null;
bellSound: string | null; bellSound: string | null;
borderColor: string; borderColor: string;
colors: { colors: ColorMap;
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; cols: number | null;
copyOnSelect: boolean; copyOnSelect: boolean;
css: string; css: string;
@ -107,7 +91,7 @@ export type uiState = {
updateVersion: string | null; updateVersion: string | null;
webGLRenderer: boolean; webGLRenderer: boolean;
webLinksActivationKey: string; webLinksActivationKey: string;
}; }>;
export type session = { export type session = {
cleared: boolean; cleared: boolean;
@ -123,22 +107,19 @@ export type session = {
splitDirection?: 'HORIZONTAL' | 'VERTICAL'; splitDirection?: 'HORIZONTAL' | 'VERTICAL';
activeUid?: string; activeUid?: string;
}; };
export type sessionState = { export type sessionState = Immutable<{
sessions: Record<string, session>; sessions: Record<string, session>;
activeUid: string | null; activeUid: string | null;
write?: any; write?: any;
}; }>;
export {ITermGroupReducer} from './reducers/term-groups'; export type ITermGroupReducer = Reducer<ITermState, HyperActions>;
import {ITermGroupReducer} from './reducers/term-groups';
export {IUiReducer} from './reducers/ui'; export type IUiReducer = Reducer<uiState, HyperActions>;
import {IUiReducer} from './reducers/ui';
export {ISessionReducer} from './reducers/sessions'; export type ISessionReducer = Reducer<sessionState, HyperActions>;
import {ISessionReducer} from './reducers/sessions';
import {Middleware} from 'redux'; import {Middleware, Reducer} from 'redux';
export type hyperPlugin = { export type hyperPlugin = {
getTabProps: any; getTabProps: any;
getTabsProps: any; getTabsProps: any;
@ -163,9 +144,9 @@ export type hyperPlugin = {
}; };
export type HyperState = { export type HyperState = {
ui: Immutable<uiState>; ui: uiState;
sessions: Immutable<sessionState>; sessions: sessionState;
termGroups: Immutable<ITermState>; termGroups: ITermState;
}; };
import {UIActions} from './constants/ui'; import {UIActions} from './constants/ui';
@ -188,8 +169,6 @@ export type HyperActions = (
| TabActions | TabActions
) & {effect?: () => void}; ) & {effect?: () => void};
type immutableRecord<T> = {[k in keyof T]: Immutable<T[k]>};
import configureStore from './store/configure-store'; import configureStore from './store/configure-store';
export type HyperDispatch = ReturnType<typeof configureStore>['dispatch']; export type HyperDispatch = ReturnType<typeof configureStore>['dispatch'];
@ -276,7 +255,7 @@ export type TermGroupOwnProps = {
fontSmoothing?: string; fontSmoothing?: string;
parentProps: TermsProps; parentProps: TermsProps;
ref_: (uid: string, term: Term | null) => void; ref_: (uid: string, term: Term | null) => void;
termGroup: Immutable<ITermGroup>; termGroup: ITermGroup;
terms: Record<string, Term | null>; terms: Record<string, Term | null>;
} & Pick< } & Pick<
TermsProps, TermsProps,
@ -336,7 +315,7 @@ export type TermProps = {
bellSoundURL: string | null; bellSoundURL: string | null;
borderColor: string; borderColor: string;
cleared: boolean; cleared: boolean;
colors: uiState['colors']; colors: ColorMap;
cols: number | null; cols: number | null;
copyOnSelect: boolean; copyOnSelect: boolean;
cursorAccentColor?: string; cursorAccentColor?: string;
@ -379,4 +358,10 @@ export type TermProps = {
ref_: (uid: string, term: Term | null) => void; ref_: (uid: string, term: Term | null) => void;
} & extensionProps; } & extensionProps;
// Utility types
export type Mutable<T> = T extends Immutable<infer U> ? (Exclude<U, T> extends never ? U : Exclude<U, T>) : T;
export type immutableRecord<T> = {[k in keyof T]: Immutable<T[k]>};
export type Assignable<T, U> = {[k in keyof U]: k extends keyof T ? T[k] : U[k]} & Partial<T>; export type Assignable<T, U> = {[k in keyof U]: k extends keyof T ? T[k] : U[k]} & Partial<T>;

View file

@ -1,4 +1,4 @@
import Immutable, {Immutable as ImmutableType} from 'seamless-immutable'; import Immutable from 'seamless-immutable';
import {decorateSessionsReducer} from '../utils/plugins'; import {decorateSessionsReducer} from '../utils/plugins';
import { import {
SESSION_ADD, SESSION_ADD,
@ -13,10 +13,10 @@ import {
SESSION_SEARCH, SESSION_SEARCH,
SESSION_SEARCH_CLOSE SESSION_SEARCH_CLOSE
} from '../constants/sessions'; } from '../constants/sessions';
import {sessionState, session, HyperActions} from '../hyper'; import {sessionState, session, Mutable, ISessionReducer} from '../hyper';
const initialState: ImmutableType<sessionState> = Immutable({ const initialState: sessionState = Immutable<Mutable<sessionState>>({
sessions: {} as Record<string, session>, sessions: {},
activeUid: null activeUid: null
}); });
@ -35,7 +35,7 @@ function Session(obj: Immutable.DeepPartial<session>) {
return Immutable(x).merge(obj); return Immutable(x).merge(obj);
} }
function deleteSession(state: ImmutableType<sessionState>, uid: string) { function deleteSession(state: sessionState, uid: string) {
return state.updateIn(['sessions'], (sessions: typeof state['sessions']) => { return state.updateIn(['sessions'], (sessions: typeof state['sessions']) => {
const sessions_ = sessions.asMutable(); const sessions_ = sessions.asMutable();
delete sessions_[uid]; delete sessions_[uid];
@ -43,7 +43,7 @@ function deleteSession(state: ImmutableType<sessionState>, uid: string) {
}); });
} }
const reducer = (state: ImmutableType<sessionState> = initialState, action: HyperActions) => { const reducer: ISessionReducer = (state = initialState, action) => {
switch (action.type) { switch (action.type) {
case SESSION_ADD: case SESSION_ADD:
return state.set('activeUid', action.uid).setIn( return state.set('activeUid', action.uid).setIn(
@ -134,6 +134,4 @@ const reducer = (state: ImmutableType<sessionState> = initialState, action: Hype
} }
}; };
export type ISessionReducer = typeof reducer;
export default decorateSessionsReducer(reducer); export default decorateSessionsReducer(reducer);

View file

@ -4,17 +4,17 @@ import {TERM_GROUP_EXIT, TERM_GROUP_RESIZE} from '../constants/term-groups';
import {SESSION_ADD, SESSION_SET_ACTIVE, SessionAddAction} from '../constants/sessions'; import {SESSION_ADD, SESSION_SET_ACTIVE, SessionAddAction} from '../constants/sessions';
import findBySession from '../utils/term-groups'; import findBySession from '../utils/term-groups';
import {decorateTermGroupsReducer} from '../utils/plugins'; import {decorateTermGroupsReducer} from '../utils/plugins';
import {ITermGroup, ITermState, ITermGroups, HyperActions} from '../hyper'; import {ITermGroup, ITermState, ITermGroups, ITermGroupReducer, Mutable} from '../hyper';
const MIN_SIZE = 0.05; const MIN_SIZE = 0.05;
const initialState = Immutable<ITermState>({ const initialState: ITermState = Immutable<Mutable<ITermState>>({
termGroups: {}, termGroups: {},
activeSessions: {}, activeSessions: {},
activeRootGroup: null activeRootGroup: null
}); });
function TermGroup(obj: Immutable.DeepPartial<ITermGroup>) { function TermGroup(obj: Immutable.DeepPartial<Mutable<ITermGroup>>) {
const x: ITermGroup = { const x: Mutable<ITermGroup> = {
uid: '', uid: '',
sessionUid: null, sessionUid: null,
parentUid: null, parentUid: null,
@ -26,7 +26,7 @@ function TermGroup(obj: Immutable.DeepPartial<ITermGroup>) {
} }
// Recurse upwards until we find a root term group (no parent). // Recurse upwards until we find a root term group (no parent).
const findRootGroup = (termGroups: ImmutableType<ITermGroups>, uid: string): ImmutableType<ITermGroup> => { const findRootGroup = (termGroups: ITermGroups, uid: string): ITermGroup => {
const current = termGroups[uid]; const current = termGroups[uid];
if (!current.parentUid) { if (!current.parentUid) {
return current; return current;
@ -35,7 +35,7 @@ const findRootGroup = (termGroups: ImmutableType<ITermGroups>, uid: string): Imm
return findRootGroup(termGroups, current.parentUid); return findRootGroup(termGroups, current.parentUid);
}; };
const setActiveGroup = (state: ImmutableType<ITermState>, action: {uid: string}) => { const setActiveGroup = (state: ITermState, action: {uid: string}) => {
if (!action.uid) { if (!action.uid) {
return state.set('activeRootGroup', null); return state.set('activeRootGroup', null);
} }
@ -66,7 +66,7 @@ const removalRebalance = (oldSizes: ImmutableType<number[]>, index: number) => {
); );
}; };
const splitGroup = (state: ImmutableType<ITermState>, action: SessionAddAction) => { const splitGroup = (state: ITermState, action: SessionAddAction) => {
const {splitDirection, uid, activeUid} = action; 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 // If we're splitting in the same direction as the current active
@ -132,11 +132,7 @@ const splitGroup = (state: ImmutableType<ITermState>, action: SessionAddAction)
// Replace the parent by the given child in the tree, // Replace the parent by the given child in the tree,
// used when we remove another child and we're left // used when we remove another child and we're left
// with a one-to-one mapping between parent and child. // with a one-to-one mapping between parent and child.
const replaceParent = ( const replaceParent = (state: ITermState, parent: ITermGroup, child: ITermGroup) => {
state: ImmutableType<ITermState>,
parent: ImmutableType<ITermGroup>,
child: ImmutableType<ITermGroup>
) => {
if (parent.parentUid) { if (parent.parentUid) {
const parentParent = state.termGroups[parent.parentUid]; const parentParent = state.termGroups[parent.parentUid];
// If the parent we're replacing has a parent, // If the parent we're replacing has a parent,
@ -161,7 +157,7 @@ const replaceParent = (
.setIn(['termGroups', child.uid, 'parentUid'], parent.parentUid); .setIn(['termGroups', child.uid, 'parentUid'], parent.parentUid);
}; };
const removeGroup = (state: ImmutableType<ITermState>, uid: string) => { const removeGroup = (state: ITermState, uid: string) => {
const group = state.termGroups[uid]; 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. // 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. // it's safe to remove the group.
@ -188,7 +184,7 @@ const removeGroup = (state: ImmutableType<ITermState>, uid: string) => {
.set('activeSessions', state.activeSessions.without(uid)); .set('activeSessions', state.activeSessions.without(uid));
}; };
const resizeGroup = (state: ImmutableType<ITermState>, uid: string, sizes: number[]) => { const resizeGroup = (state: ITermState, uid: string, sizes: number[]) => {
// Make sure none of the sizes fall below MIN_SIZE: // Make sure none of the sizes fall below MIN_SIZE:
if (sizes.find((size) => size < MIN_SIZE)) { if (sizes.find((size) => size < MIN_SIZE)) {
return state; return state;
@ -197,7 +193,7 @@ const resizeGroup = (state: ImmutableType<ITermState>, uid: string, sizes: numbe
return state.setIn(['termGroups', uid, 'sizes'], sizes); return state.setIn(['termGroups', uid, 'sizes'], sizes);
}; };
const reducer = (state = initialState, action: HyperActions) => { const reducer: ITermGroupReducer = (state = initialState, action) => {
switch (action.type) { switch (action.type) {
case SESSION_ADD: { case SESSION_ADD: {
if (action.splitDirection) { if (action.splitDirection) {
@ -227,6 +223,4 @@ const reducer = (state = initialState, action: HyperActions) => {
} }
}; };
export type ITermGroupReducer = typeof reducer;
export default decorateTermGroupsReducer(reducer); export default decorateTermGroupsReducer(reducer);

View file

@ -24,7 +24,7 @@ import {
SESSION_SET_CWD SESSION_SET_CWD
} from '../constants/sessions'; } from '../constants/sessions';
import {UPDATE_AVAILABLE} from '../constants/updater'; import {UPDATE_AVAILABLE} from '../constants/updater';
import {uiState, HyperActions} from '../hyper'; import {uiState, Mutable, IUiReducer} from '../hyper';
const allowedCursorShapes = new Set(['BEAM', 'BLOCK', 'UNDERLINE']); const allowedCursorShapes = new Set(['BEAM', 'BLOCK', 'UNDERLINE']);
const allowedCursorBlinkValues = new Set([true, false]); const allowedCursorBlinkValues = new Set([true, false]);
@ -33,7 +33,7 @@ const allowedHamburgerMenuValues = new Set([true, false]);
const allowedWindowControlsValues = new Set([true, false, 'left']); const allowedWindowControlsValues = new Set([true, false, 'left']);
// Populate `config-default.js` from this :) // Populate `config-default.js` from this :)
const initial: ImmutableType<uiState> = Immutable({ const initial: uiState = Immutable<Mutable<uiState>>({
cols: null, cols: null,
rows: null, rows: null,
scrollback: 1000, scrollback: 1000,
@ -57,7 +57,7 @@ const initial: ImmutableType<uiState> = Immutable({
letterSpacing: 0, letterSpacing: 0,
css: '', css: '',
termCSS: '', termCSS: '',
openAt: {} as Record<string, number>, openAt: {},
resizeAt: 0, resizeAt: 0,
colors: { colors: {
black: '#000000', black: '#000000',
@ -77,7 +77,7 @@ const initial: ImmutableType<uiState> = Immutable({
lightCyan: '#68FDFE', lightCyan: '#68FDFE',
lightWhite: '#FFFFFF' lightWhite: '#FFFFFF'
}, },
activityMarkers: {} as Record<string, boolean>, activityMarkers: {},
notifications: { notifications: {
font: false, font: false,
resize: false, resize: false,
@ -115,7 +115,7 @@ const initial: ImmutableType<uiState> = Immutable({
const currentWindow = remote.getCurrentWindow(); const currentWindow = remote.getCurrentWindow();
const reducer = (state = initial, action: HyperActions) => { const reducer: IUiReducer = (state = initial, action) => {
let state_ = state; let state_ = state;
let isMax; let isMax;
switch (action.type) { switch (action.type) {
@ -127,7 +127,7 @@ const reducer = (state = initial, action: HyperActions) => {
// font size changed from the config // font size changed from the config
.merge( .merge(
(() => { (() => {
const ret: Immutable.DeepPartial<uiState> = {}; const ret: Immutable.DeepPartial<Mutable<uiState>> = {};
if (config.scrollback) { if (config.scrollback) {
ret.scrollback = config.scrollback; ret.scrollback = config.scrollback;
@ -452,6 +452,4 @@ const reducer = (state = initial, action: HyperActions) => {
return state_; return state_;
}; };
export type IUiReducer = typeof reducer;
export default decorateUIReducer(reducer); export default decorateUIReducer(reducer);

View file

@ -1,7 +1,6 @@
import {ITermState} from '../hyper'; import {ITermState} from '../hyper';
import {Immutable} from 'seamless-immutable';
export default function findBySession(termGroupState: Immutable<ITermState>, sessionUid: string) { export default function findBySession(termGroupState: ITermState, sessionUid: string) {
const {termGroups} = termGroupState; const {termGroups} = termGroupState;
return Object.keys(termGroups) return Object.keys(termGroups)
.map((uid) => termGroups[uid]) .map((uid) => termGroups[uid])