refactor: catch network errors

This commit is contained in:
EnixCoda 2021-05-09 16:33:10 +08:00
parent 72f825db2e
commit 3cfc5bc166
No known key found for this signature in database
GPG key ID: 0C1A07377913A1DD
7 changed files with 127 additions and 87 deletions

View file

@ -0,0 +1,12 @@
import * as React from 'react'
import { useStateIO } from 'utils/hooks/useStateIO'
export type SideBarErrorContextShape = IO<string | null>
export const SideBarErrorContext = React.createContext<SideBarErrorContextShape | null>(null)
export function StateBarErrorContextWrapper({ children }: React.PropsWithChildren<{}>) {
const $error = useStateIO<string | null>(null)
return <SideBarErrorContext.Provider value={$error}>{children}</SideBarErrorContext.Provider>
}

View file

@ -4,6 +4,7 @@ import { ConfigsContextWrapper, useConfigs } from 'containers/ConfigsContext'
import * as React from 'react'
import { useLoadedContext } from 'utils/hooks/useLoadedContext'
import { ErrorBoundary } from './ErrorBoundary'
import { StateBarErrorContextWrapper } from './ErrorContext'
import { RepoContext, RepoContextWrapper } from './RepoContext'
import { SideBarStateContext, StateBarStateContextWrapper } from './SideBarState'
@ -12,17 +13,19 @@ export function Gitako() {
<ErrorBoundary>
<ConfigsContextWrapper>
<StateBarStateContextWrapper>
<RepoContextWrapper>
<IIFC>
{() => (
<SideBar
configContext={useConfigs()}
stateContext={useLoadedContext(SideBarStateContext)}
metaData={React.useContext(RepoContext)}
/>
)}
</IIFC>
</RepoContextWrapper>
<StateBarErrorContextWrapper>
<RepoContextWrapper>
<IIFC>
{() => (
<SideBar
configContext={useConfigs()}
stateContext={useLoadedContext(SideBarStateContext)}
metaData={React.useContext(RepoContext)}
/>
)}
</IIFC>
</RepoContextWrapper>
</StateBarErrorContextWrapper>
</StateBarStateContextWrapper>
</ConfigsContextWrapper>
</ErrorBoundary>

View file

@ -1,11 +1,11 @@
import { useConfigs } from 'containers/ConfigsContext'
import { platform } from 'platforms'
import * as React from 'react'
import { run } from 'utils/general'
import { useEffectOnSerializableUpdates } from 'utils/hooks/useEffectOnSerializableUpdates'
import { useLoadedContext } from 'utils/hooks/useLoadedContext'
import { useOnPJAXDone } from 'utils/hooks/usePJAX'
import { useStateIO } from 'utils/hooks/useStateIO'
import { useCatchNetworkError } from '../utils/hooks/useCatchNetworkError'
import { SideBarStateContext } from './SideBarState'
export const RepoContext = React.createContext<MetaData | null>(null)
@ -55,8 +55,9 @@ function useBranchName(): MetaData['branchName'] | null {
function useDefaultBranch(partialMetaData: PartialMetaData | null) {
const { accessToken } = useConfigs().value
const $defaultBranch = useStateIO<string | null>(null)
const catchNetworkError = useCatchNetworkError()
React.useEffect(() => {
run(async () => {
catchNetworkError(async () => {
if (!partialMetaData) return
const defaultBranch = await platform.getDefaultBranchName(partialMetaData, accessToken)

View file

@ -15,12 +15,14 @@ import * as React from 'react'
import { cx } from 'utils/cx'
import * as DOMHelper from 'utils/DOMHelper'
import { parseURLSearch, run } from 'utils/general'
import { useCatchNetworkError } from 'utils/hooks/useCatchNetworkError'
import { useLoadedContext } from 'utils/hooks/useLoadedContext'
import { loadWithPJAX, useOnPJAXDone, usePJAX } from 'utils/hooks/usePJAX'
import { useProgressBar } from 'utils/hooks/useProgressBar'
import { useStateIO } from 'utils/hooks/useStateIO'
import * as keyHelper from 'utils/keyHelper'
import { Icon } from './Icon'
import { IIFC } from './IIFC'
import { LoadingIndicator } from './LoadingIndicator'
import { SideBarStateContext } from './SideBarState'
import { Theme } from './Theme'
@ -142,13 +144,18 @@ const RawSideBar: React.FC<Props & ConnectorState> = function RawGitako(props) {
<div className={'header'}>
<MetaBar metaData={metaData} />
</div>
<FileExplorer
metaData={metaData}
freeze={showSettings}
accessToken={accessToken}
loadWithPJAX={loadWithPJAX}
config={configContext.value}
/>
<IIFC>
{() => (
<FileExplorer
metaData={metaData}
freeze={showSettings}
accessToken={accessToken}
loadWithPJAX={loadWithPJAX}
config={configContext.value}
catchNetworkErrors={useCatchNetworkError()}
/>
)}
</IIFC>
</>
) : null
}

View file

@ -11,6 +11,7 @@ export type Props = {
accessToken: string | undefined
config: Config
loadWithPJAX(url: string): void
catchNetworkErrors: <T>(fn: () => T) => T | undefined
}
export type ConnectorState = {
@ -47,48 +48,54 @@ export const setUpTree: BoundMethodCreator<
config: Pick<Config, 'compressSingletonFolder' | 'accessToken'>
},
]
> = dispatch => async ({ stateContext, metaData, config }) => {
const { userName, repoName, branchName } = metaData
> = dispatch => ({ stateContext, metaData, config }) => {
const {
props: { catchNetworkErrors },
} = dispatch.get()
stateContext.onChange('tree-loading')
const { root: treeRoot, defer = false } = await platform.getTreeData(
{
branchName: branchName,
userName,
repoName,
},
'/',
true,
config.accessToken,
)
catchNetworkErrors(async () => {
const { userName, repoName, branchName } = metaData
stateContext.onChange('tree-rendering')
dispatch.set({ defer })
stateContext.onChange('tree-loading')
const { root: treeRoot, defer = false } = await platform.getTreeData(
{
branchName: branchName,
userName,
repoName,
},
'/',
true,
config.accessToken,
)
const visibleNodesGenerator = new VisibleNodesGenerator({
root: treeRoot,
compress: config.compressSingletonFolder,
async getTreeData(path) {
const { root } = await platform.getTreeData(metaData, path, false, config.accessToken)
return root
},
})
dispatch.set({ visibleNodesGenerator })
visibleNodesGenerator.onUpdate(visibleNodes => dispatch.set({ visibleNodes }))
stateContext.onChange('tree-rendering')
dispatch.set({ defer })
if (platform.shouldExpandAll?.()) {
const unsubscribe = visibleNodesGenerator.onUpdate(visibleNodes => {
unsubscribe()
visibleNodes.nodes.forEach(node =>
dispatch.call(toggleNodeExpansion, node, { recursive: true }),
)
const visibleNodesGenerator = new VisibleNodesGenerator({
root: treeRoot,
compress: config.compressSingletonFolder,
async getTreeData(path) {
const { root } = await platform.getTreeData(metaData, path, false, config.accessToken)
return root
},
})
} else {
const targetPath = platform.getCurrentPath(metaData.branchName)
if (targetPath) dispatch.call(goTo, targetPath)
}
dispatch.set({ visibleNodesGenerator })
visibleNodesGenerator.onUpdate(visibleNodes => dispatch.set({ visibleNodes }))
stateContext.onChange('tree-rendered')
if (platform.shouldExpandAll?.()) {
const unsubscribe = visibleNodesGenerator.onUpdate(visibleNodes => {
unsubscribe()
visibleNodes.nodes.forEach(node =>
dispatch.call(toggleNodeExpansion, node, { recursive: true }),
)
})
} else {
const targetPath = platform.getCurrentPath(metaData.branchName)
if (targetPath) dispatch.call(goTo, targetPath)
}
stateContext.onChange('tree-rendered')
})
}
export const handleKeyDown: BoundMethodCreator<[React.KeyboardEvent]> = dispatch => event => {

View file

@ -1,7 +1,6 @@
import { SideBarStateContextShape } from 'components/SideBarState'
import { ConfigsContextShape } from 'containers/ConfigsContext'
import { GetCreatedMethod, MethodCreator } from 'driver/connect'
import { errors, platformName } from 'platforms'
import * as DOMHelper from 'utils/DOMHelper'
export type Props = {
@ -23,36 +22,6 @@ export type ConnectorState = {
type BoundMethodCreator<Args extends any[] = []> = MethodCreator<Props, ConnectorState, Args>
export const handleError: BoundMethodCreator<[Error]> = dispatch => async err => {
const {
props: { stateContext },
} = dispatch.get()
if (err.message === errors.EMPTY_PROJECT) {
dispatch.call(setError, 'This project seems to be empty.')
} else if (err.message === errors.BLOCKED_PROJECT) {
dispatch.call(setError, 'Access to the project is blocked.')
} else if (
err.message === errors.NOT_FOUND ||
err.message === errors.BAD_CREDENTIALS ||
err.message === errors.API_RATE_LIMIT
) {
stateContext.onChange('error-due-to-auth')
} else if (err.message === errors.CONNECTION_BLOCKED) {
const { props } = dispatch.get()
if (props.configContext.value.accessToken) {
dispatch.call(setError, `Cannot connect to ${platformName}.`)
} else {
stateContext.onChange('error-due-to-auth')
}
} else if (err.message === errors.SERVER_FAULT) {
dispatch.call(setError, `${platformName} server went down.`)
} else {
DOMHelper.markGitakoReadyState(false)
dispatch.call(setError, 'Some thing went wrong.')
throw err
}
}
export const toggleShowSideBar: BoundMethodCreator = dispatch => () => {
const {
state: { shouldShow },

View file

@ -0,0 +1,41 @@
import { useConfigs } from 'containers/ConfigsContext'
import { errors, platformName } from 'platforms'
import { useLoadedContext } from 'utils/hooks/useLoadedContext'
import { SideBarErrorContext } from '../../components/ErrorContext'
import { SideBarStateContext } from '../../components/SideBarState'
export function useCatchNetworkError() {
const { accessToken } = useConfigs().value
const stateContext = useLoadedContext(SideBarStateContext)
const errorContext = useLoadedContext(SideBarErrorContext)
return function <T>(fn: () => T) {
try {
return fn()
} catch (err) {
if (err.message === errors.EMPTY_PROJECT) {
errorContext.onChange('This project seems to be empty.')
} else if (err.message === errors.BLOCKED_PROJECT) {
errorContext.onChange('Access to the project is blocked.')
} else if (
err.message === errors.NOT_FOUND ||
err.message === errors.BAD_CREDENTIALS ||
err.message === errors.API_RATE_LIMIT
) {
stateContext.onChange('error-due-to-auth')
} else if (err.message === errors.CONNECTION_BLOCKED) {
if (accessToken) {
errorContext.onChange(`Cannot connect to ${platformName}.`)
} else {
stateContext.onChange('error-due-to-auth')
}
} else if (err.message === errors.SERVER_FAULT) {
errorContext.onChange(`${platformName} server went down.`)
} else {
stateContext.onChange('disabled')
errorContext.onChange('Some thing went wrong.')
throw err
}
}
}
}