mirror of
https://github.com/EnixCoda/Gitako.git
synced 2026-03-11 08:54:44 +00:00
refactor: stricter types
This commit is contained in:
parent
b01a4c418c
commit
f39ba9c96f
5 changed files with 115 additions and 56 deletions
|
|
@ -1,16 +1,9 @@
|
|||
import * as React from 'react'
|
||||
|
||||
export type ParametersOfReturnedFunction<Func> = Func extends (<Args extends []>(
|
||||
...args1: any[] // it's ok
|
||||
) => (...args2: Args) => any) // it's ok
|
||||
? Args
|
||||
: never
|
||||
|
||||
export type Method = (...args: Args) => void | Promise<void>
|
||||
type Args = any[] // it's ok
|
||||
export type Method<Args = any[]> = (...args: Args extends any[] ? Args : []) => void | Promise<void>
|
||||
export type Middleware = <M extends Method, MM extends Method>(
|
||||
method: M,
|
||||
args: Parameters<M>
|
||||
args: Parameters<M>,
|
||||
) => [MM | M, Parameters<MM | M>]
|
||||
|
||||
const middlewares: Middleware[] = []
|
||||
|
|
@ -40,11 +33,11 @@ function run<M extends Method>([method, args]: [M, Parameters<M>]) {
|
|||
export type DispatchState<Props, State> = React.Component<Props, State>['setState']
|
||||
export type PreDispatch<Props, State> = (
|
||||
dispatchCallback: (state: State, props: Props) => Props | Promise<void> | void,
|
||||
callback?: () => void
|
||||
callback?: () => void,
|
||||
) => void
|
||||
export type TriggerOtherMethod = <MC extends MethodCreator<any, any>>(
|
||||
export type TriggerOtherMethod = <MC extends MethodCreator<any, any, any[]>>(
|
||||
methodCreator: MC,
|
||||
...args: ParametersOfReturnedFunction<MC>
|
||||
...args: Parameters<ReturnType<MC>>
|
||||
) => void
|
||||
|
||||
export type Dispatch<Props, State> = {
|
||||
|
|
@ -53,20 +46,25 @@ export type Dispatch<Props, State> = {
|
|||
call: TriggerOtherMethod
|
||||
}
|
||||
|
||||
export type MethodCreator<Props, State> = (dispatch: Dispatch<Props, State>) => Method
|
||||
export type MethodCreator<Props, State, Args = []> = (
|
||||
dispatch: Dispatch<Props, State>,
|
||||
) => Method<Args>
|
||||
|
||||
type Sources = {
|
||||
[key: string]: MethodCreator<any, any>
|
||||
type Sources<P, S> = {
|
||||
[key: string]: MethodCreator<P, S, any[]>
|
||||
}
|
||||
type WrappedMethods = {
|
||||
[key: string]: Method
|
||||
}
|
||||
|
||||
function link<P, S>(instance: React.Component<P, S>, sources: Sources): WrappedMethods {
|
||||
function link<P, S>(instance: React.Component<P, S>, sources: Sources<P, S>): WrappedMethods {
|
||||
const wrappedMethods: WrappedMethods = {
|
||||
/* [keyof sources] -> wrappedMethods.method */
|
||||
}
|
||||
const map = new Map<MethodCreator<P, S>, Method>(/* sources.creator -> wrappedMethods.method */)
|
||||
const map = new Map<
|
||||
MethodCreator<P, S, any[]>,
|
||||
Method
|
||||
>(/* sources.creator -> wrappedMethods.method */)
|
||||
|
||||
const dispatchCall: TriggerOtherMethod = (createMethod, ...otherArgs) => {
|
||||
const isFromSource = sourcesValues.includes(createMethod)
|
||||
|
|
@ -99,9 +97,9 @@ function link<P, S>(instance: React.Component<P, S>, sources: Sources): WrappedM
|
|||
return wrappedMethods
|
||||
}
|
||||
|
||||
export default function connect<BaseP, ExtraP>(mapping: Sources) {
|
||||
export default function connect<BaseP, ExtraP>(mapping: Sources<BaseP, ExtraP>) {
|
||||
return function linkComponent<S>(
|
||||
ComponentClass: React.ComponentClass<BaseP & ExtraP, S>
|
||||
ComponentClass: React.ComponentClass<BaseP & ExtraP, S>,
|
||||
): React.ComponentClass<BaseP, ExtraP> {
|
||||
return class AwesomeApp extends React.PureComponent<BaseP, ExtraP> {
|
||||
static displayName = `Connected(${ComponentClass.displayName || ComponentClass.name})`
|
||||
|
|
|
|||
|
|
@ -39,7 +39,8 @@ type Task = () => void
|
|||
const tasksAfterRender: (Task)[] = []
|
||||
const visibleNodesGenerator = new VisibleNodesGenerator()
|
||||
|
||||
const init: MethodCreator<Props, ConnectorState> = dispatch => () => dispatch.call(setStateText, 'Fetching File List...')
|
||||
const init: MethodCreator<Props, ConnectorState> = dispatch => () =>
|
||||
dispatch.call(setStateText, 'Fetching File List...')
|
||||
|
||||
function resolveGitModules(root: TreeNode, blobData: BlobData) {
|
||||
if (blobData) {
|
||||
|
|
@ -79,7 +80,7 @@ const setUpTree: MethodCreator<Props, ConnectorState> = dispatch => () =>
|
|||
await visibleNodesGenerator.plantTree(root as TreeNode)
|
||||
|
||||
tasksAfterRender.push(DOMHelper.focusSearchInput)
|
||||
dispatch.call(setStateText, null)
|
||||
dispatch.call(setStateText, '')
|
||||
const currentPath = URLHelper.getCurrentPath(metaData.branchName)
|
||||
if (currentPath.length) {
|
||||
const nodeExpandedTo = visibleNodesGenerator.expandTo(currentPath.join('/'))
|
||||
|
|
@ -99,12 +100,20 @@ const execAfterRender: MethodCreator<Props, ConnectorState> = dispatch => () =>
|
|||
tasksAfterRender.length = 0
|
||||
}
|
||||
|
||||
const setStateText: MethodCreator<Props, ConnectorState> = dispatch => (text: string) =>
|
||||
const setStateText: MethodCreator<
|
||||
Props,
|
||||
ConnectorState,
|
||||
[ConnectorState['stateText']]
|
||||
> = dispatch => (text: string) =>
|
||||
dispatch.set({
|
||||
stateText: text,
|
||||
})
|
||||
|
||||
const handleKeyDown: MethodCreator<Props, ConnectorState> = dispatch => event =>
|
||||
const handleKeyDown: MethodCreator<
|
||||
Props,
|
||||
ConnectorState,
|
||||
[React.KeyboardEvent]
|
||||
> = dispatch => event =>
|
||||
dispatch.get(({ visibleNodes: { nodes, focusedNode, expandedNodes, depths } }) => {
|
||||
function handleVerticalMove(index: number) {
|
||||
if (0 <= index && index < nodes.length) {
|
||||
|
|
@ -201,12 +210,17 @@ const handleKeyDown: MethodCreator<Props, ConnectorState> = dispatch => event =>
|
|||
}
|
||||
})
|
||||
|
||||
const onFocusSearchBar: MethodCreator<Props, ConnectorState> = dispatch => () => dispatch.call(focusNode, null)
|
||||
const onFocusSearchBar: MethodCreator<Props, ConnectorState> = dispatch => () =>
|
||||
dispatch.call(focusNode, null)
|
||||
|
||||
const handleSearchKeyChange: MethodCreator<Props, ConnectorState> = dispatch => {
|
||||
const handleSearchKeyChange: MethodCreator<
|
||||
Props,
|
||||
ConnectorState,
|
||||
[React.FormEvent<HTMLInputElement>]
|
||||
> = dispatch => {
|
||||
let i = 0
|
||||
return async event => {
|
||||
const searchKey = event.target.value
|
||||
const searchKey = event.currentTarget.value
|
||||
const j = (i += 1)
|
||||
await visibleNodesGenerator.search(searchKey)
|
||||
if (i === j) dispatch.call(updateVisibleNodes)
|
||||
|
|
@ -221,7 +235,10 @@ function shouldDelayExpand(node: TreeNode) {
|
|||
)
|
||||
}
|
||||
|
||||
const setExpand: MethodCreator<Props, ConnectorState> = dispatch => (node, expand) => {
|
||||
const setExpand: MethodCreator<Props, ConnectorState, [TreeNode, boolean]> = dispatch => (
|
||||
node,
|
||||
expand = false
|
||||
) => {
|
||||
visibleNodesGenerator.setExpand(node, expand)
|
||||
const applyChanges = () => dispatch.call(focusNode, node)
|
||||
if (shouldDelayExpand(node)) {
|
||||
|
|
@ -232,7 +249,10 @@ const setExpand: MethodCreator<Props, ConnectorState> = dispatch => (node, expan
|
|||
}
|
||||
}
|
||||
|
||||
const toggleNodeExpansion: MethodCreator<Props, ConnectorState> = dispatch => (node, skipScrollToNode) => {
|
||||
const toggleNodeExpansion: MethodCreator<Props, ConnectorState, [TreeNode, boolean]> = dispatch => (
|
||||
node,
|
||||
skipScrollToNode
|
||||
) => {
|
||||
visibleNodesGenerator.toggleExpand(node)
|
||||
const applyChanges = () => {
|
||||
dispatch.call(focusNode, node, skipScrollToNode)
|
||||
|
|
@ -246,7 +266,11 @@ const toggleNodeExpansion: MethodCreator<Props, ConnectorState> = dispatch => (n
|
|||
}
|
||||
}
|
||||
|
||||
const focusNode: MethodCreator<Props, ConnectorState> = dispatch => (node, skipScroll) =>
|
||||
const focusNode: MethodCreator<
|
||||
Props,
|
||||
ConnectorState,
|
||||
[TreeNode] | [TreeNode, boolean]
|
||||
> = dispatch => (node: TreeNode, skipScroll = false) =>
|
||||
dispatch.get(({ visibleNodes: { nodes } }) => {
|
||||
visibleNodesGenerator.focusNode(node)
|
||||
if (node && !skipScroll) {
|
||||
|
|
@ -257,7 +281,7 @@ const focusNode: MethodCreator<Props, ConnectorState> = dispatch => (node, skipS
|
|||
dispatch.call(updateVisibleNodes)
|
||||
})
|
||||
|
||||
const onNodeClick: MethodCreator<Props, ConnectorState> = dispatch => node => {
|
||||
const onNodeClick: MethodCreator<Props, ConnectorState, [TreeNode]> = dispatch => node => {
|
||||
if (node.type === 'tree') {
|
||||
dispatch.call(toggleNodeExpansion, node, true)
|
||||
} else if (node.type === 'blob') {
|
||||
|
|
@ -268,7 +292,11 @@ const onNodeClick: MethodCreator<Props, ConnectorState> = dispatch => node => {
|
|||
}
|
||||
}
|
||||
|
||||
const mountExpandingIndicator: MethodCreator<Props, ConnectorState> = dispatch => node =>
|
||||
const mountExpandingIndicator: MethodCreator<
|
||||
Props,
|
||||
ConnectorState,
|
||||
[TreeNode]
|
||||
> = dispatch => node =>
|
||||
dispatch.get(({ visibleNodes }) => {
|
||||
const dummyVisibleNodes = {
|
||||
...visibleNodes,
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@ export type ConnectorState = {
|
|||
toggleShowSideBar: () => void
|
||||
toggleShowSettings: () => void
|
||||
onAccessTokenChange: SettingsBar['props']['onAccessTokenChange']
|
||||
onKeyDown: EventListener
|
||||
onKeyDown: (e: KeyboardEvent) => string
|
||||
onShortcutChange: SettingsBar['props']['onShortcutChange']
|
||||
setCopyFile: SettingsBar['props']['setCopyFile']
|
||||
setCopySnippet: SettingsBar['props']['setCopySnippet']
|
||||
|
|
@ -134,7 +134,7 @@ const init: MethodCreator<Props, ConnectorState> = dispatch => async () => {
|
|||
}
|
||||
}
|
||||
|
||||
const handleError: MethodCreator<Props, ConnectorState> = dispatch => async err => {
|
||||
const handleError: MethodCreator<Props, ConnectorState, [Error]> = dispatch => async err => {
|
||||
if (err.message === EMPTY_PROJECT) {
|
||||
dispatch.call(setError, 'This project seems to be empty.')
|
||||
} else if (
|
||||
|
|
@ -160,7 +160,7 @@ const onPJAXEnd: MethodCreator<Props, ConnectorState> = dispatch => () => {
|
|||
})
|
||||
}
|
||||
|
||||
const onKeyDown: MethodCreator<Props, ConnectorState> = dispatch => e => {
|
||||
const onKeyDown: MethodCreator<Props, ConnectorState, [KeyboardEvent]> = dispatch => e => {
|
||||
dispatch.get(({ toggleShowSideBarShortcut }) => {
|
||||
if (toggleShowSideBarShortcut) {
|
||||
const keys = keyHelper.parseEvent(e)
|
||||
|
|
@ -172,16 +172,22 @@ const onKeyDown: MethodCreator<Props, ConnectorState> = dispatch => e => {
|
|||
}
|
||||
|
||||
const toggleShowSideBar: MethodCreator<Props, ConnectorState> = dispatch => () =>
|
||||
dispatch.get(({ shouldShow }) =>
|
||||
dispatch.call(setShouldShow, !shouldShow)
|
||||
)
|
||||
dispatch.get(({ shouldShow }) => dispatch.call(setShouldShow, !shouldShow))
|
||||
|
||||
const setShouldShow: MethodCreator<Props, ConnectorState> = dispatch => shouldShow => {
|
||||
const setShouldShow: MethodCreator<
|
||||
Props,
|
||||
ConnectorState,
|
||||
[ConnectorState['shouldShow']]
|
||||
> = dispatch => shouldShow => {
|
||||
dispatch.set({ shouldShow }, shouldShow ? DOMHelper.focusFileExplorer : null)
|
||||
DOMHelper.setBodyIndent(shouldShow)
|
||||
}
|
||||
|
||||
const setError: MethodCreator<Props, ConnectorState> = dispatch => error => {
|
||||
const setError: MethodCreator<
|
||||
Props,
|
||||
ConnectorState,
|
||||
[ConnectorState['error']]
|
||||
> = dispatch => error => {
|
||||
dispatch.set({ error })
|
||||
dispatch.call(setShouldShow, false)
|
||||
}
|
||||
|
|
@ -191,22 +197,47 @@ const toggleShowSettings: MethodCreator<Props, ConnectorState> = dispatch => ()
|
|||
showSettings: !showSettings,
|
||||
}))
|
||||
|
||||
const setShowSettings: MethodCreator<Props, ConnectorState> = dispatch => showSettings => dispatch.set({ showSettings })
|
||||
const setShowSettings: MethodCreator<
|
||||
Props,
|
||||
ConnectorState,
|
||||
[ConnectorState['showSettings']]
|
||||
> = dispatch => showSettings => dispatch.set({ showSettings })
|
||||
|
||||
const onAccessTokenChange: MethodCreator<Props, ConnectorState> = dispatch => accessToken => dispatch.set({ accessToken })
|
||||
const onAccessTokenChange: MethodCreator<
|
||||
Props,
|
||||
ConnectorState,
|
||||
[ConnectorState['accessToken']]
|
||||
> = dispatch => accessToken => dispatch.set({ accessToken })
|
||||
|
||||
const onShortcutChange: MethodCreator<Props, ConnectorState> = dispatch => shortcut =>
|
||||
dispatch.set({ toggleShowSideBarShortcut: shortcut })
|
||||
const onShortcutChange: MethodCreator<
|
||||
Props,
|
||||
ConnectorState,
|
||||
[ConnectorState['toggleShowSideBarShortcut']]
|
||||
> = dispatch => shortcut => dispatch.set({ toggleShowSideBarShortcut: shortcut })
|
||||
|
||||
const setMetaData: MethodCreator<Props, ConnectorState> = dispatch => metaData => dispatch.set({ metaData })
|
||||
const setMetaData: MethodCreator<
|
||||
Props,
|
||||
ConnectorState,
|
||||
[ConnectorState['metaData']]
|
||||
> = dispatch => metaData => dispatch.set({ metaData })
|
||||
|
||||
const setCompressSingleton: MethodCreator<Props, ConnectorState> = dispatch => compressSingletonFolder =>
|
||||
dispatch.set({ compressSingletonFolder })
|
||||
const setCompressSingleton: MethodCreator<
|
||||
Props,
|
||||
ConnectorState,
|
||||
[ConnectorState['compressSingletonFolder']]
|
||||
> = dispatch => compressSingletonFolder => dispatch.set({ compressSingletonFolder })
|
||||
|
||||
const setCopyFile: MethodCreator<Props, ConnectorState> = dispatch => copyFileButton => dispatch.set({ copyFileButton })
|
||||
const setCopyFile: MethodCreator<
|
||||
Props,
|
||||
ConnectorState,
|
||||
[ConnectorState['copyFileButton']]
|
||||
> = dispatch => copyFileButton => dispatch.set({ copyFileButton })
|
||||
|
||||
const setCopySnippet: MethodCreator<Props, ConnectorState> = dispatch => copySnippetButton =>
|
||||
dispatch.set({ copySnippetButton })
|
||||
const setCopySnippet: MethodCreator<
|
||||
Props,
|
||||
ConnectorState,
|
||||
[ConnectorState['copySnippetButton']]
|
||||
> = dispatch => copySnippetButton => dispatch.set({ copySnippetButton })
|
||||
|
||||
export default {
|
||||
init,
|
||||
|
|
|
|||
|
|
@ -28,7 +28,11 @@ function setBodyIndent(shouldShowGitako: boolean) {
|
|||
}
|
||||
}
|
||||
|
||||
function $(selector: string, existCallback?: (element: Element) => any, otherwise?: () => any) {
|
||||
function $<R1 = never, R2 = never>(
|
||||
selector: string,
|
||||
existCallback?: (element: Element) => R1,
|
||||
otherwise?: () => R2,
|
||||
): R1 | R2 | Element {
|
||||
const element = document.querySelector(selector)
|
||||
if (element) {
|
||||
return existCallback ? existCallback(element) : element
|
||||
|
|
@ -52,14 +56,14 @@ function getCurrentBranch() {
|
|||
'.repository-content > .file-navigation > .branch-select-menu > button'
|
||||
const branchNameFromButtonElement = $(
|
||||
selectedBranchButtonSelector,
|
||||
(element: HTMLButtonElement) => element.title.trim()
|
||||
(element: HTMLButtonElement) => element.title.trim(),
|
||||
)
|
||||
if (branchNameFromButtonElement) return branchNameFromButtonElement
|
||||
|
||||
const selectedBranchSelector =
|
||||
'.select-menu.branch-select-menu .select-menu-modal .select-menu-list .select-menu-item.selected svg.select-menu-item-icon + span'
|
||||
const branchNameFromSelectElement = $(selectedBranchSelector, element =>
|
||||
element.textContent.trim()
|
||||
element.textContent.trim(),
|
||||
)
|
||||
if (branchNameFromSelectElement) return branchNameFromSelectElement
|
||||
}
|
||||
|
|
@ -299,7 +303,7 @@ function attachCopySnippet() {
|
|||
target.parentNode.insertBefore(clippy, target)
|
||||
}
|
||||
}
|
||||
})
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
import { KeyboardEvent } from 'react'
|
||||
|
||||
const keyCodeArray = [
|
||||
...'1234567890abcdefghijklmnopqrstuvwxyz'.split(''),
|
||||
..."`[]\\;',./".split(''),
|
||||
|
|
@ -38,7 +36,7 @@ function parseKeyCode(code: string) {
|
|||
return code.toLowerCase().replace(/^control$/, 'ctrl')
|
||||
}
|
||||
|
||||
function parseEvent(e: KeyboardEvent<HTMLInputElement>) {
|
||||
function parseEvent(e: KeyboardEvent | React.KeyboardEvent) {
|
||||
const { altKey: alt, shiftKey: shift, metaKey: meta, ctrlKey: ctrl } = e
|
||||
const code = parseKeyCode(e.key)
|
||||
const keys = { meta, ctrl, shift, alt, [code]: true }
|
||||
|
|
|
|||
Loading…
Reference in a new issue