From 3cfc5bc16638cd878fe2ca1765b91a64e227ab64 Mon Sep 17 00:00:00 2001 From: EnixCoda Date: Sun, 9 May 2021 16:33:10 +0800 Subject: [PATCH] refactor: catch network errors --- src/components/ErrorContext.tsx | 12 ++++ src/components/Gitako.tsx | 25 ++++---- src/components/RepoContext.tsx | 5 +- src/components/SideBar.tsx | 21 ++++--- src/driver/core/FileExplorer.ts | 79 +++++++++++++----------- src/driver/core/SideBar.ts | 31 ---------- src/utils/hooks/useCatchNetworkError.tsx | 41 ++++++++++++ 7 files changed, 127 insertions(+), 87 deletions(-) create mode 100644 src/components/ErrorContext.tsx create mode 100644 src/utils/hooks/useCatchNetworkError.tsx diff --git a/src/components/ErrorContext.tsx b/src/components/ErrorContext.tsx new file mode 100644 index 0000000..53d9455 --- /dev/null +++ b/src/components/ErrorContext.tsx @@ -0,0 +1,12 @@ +import * as React from 'react' +import { useStateIO } from 'utils/hooks/useStateIO' + +export type SideBarErrorContextShape = IO + +export const SideBarErrorContext = React.createContext(null) + +export function StateBarErrorContextWrapper({ children }: React.PropsWithChildren<{}>) { + const $error = useStateIO(null) + + return {children} +} diff --git a/src/components/Gitako.tsx b/src/components/Gitako.tsx index 9b386fa..82470b9 100644 --- a/src/components/Gitako.tsx +++ b/src/components/Gitako.tsx @@ -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() { - - - {() => ( - - )} - - + + + + {() => ( + + )} + + + diff --git a/src/components/RepoContext.tsx b/src/components/RepoContext.tsx index 3b88e4b..2c97118 100644 --- a/src/components/RepoContext.tsx +++ b/src/components/RepoContext.tsx @@ -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(null) @@ -55,8 +55,9 @@ function useBranchName(): MetaData['branchName'] | null { function useDefaultBranch(partialMetaData: PartialMetaData | null) { const { accessToken } = useConfigs().value const $defaultBranch = useStateIO(null) + const catchNetworkError = useCatchNetworkError() React.useEffect(() => { - run(async () => { + catchNetworkError(async () => { if (!partialMetaData) return const defaultBranch = await platform.getDefaultBranchName(partialMetaData, accessToken) diff --git a/src/components/SideBar.tsx b/src/components/SideBar.tsx index 2876dec..3104d60 100644 --- a/src/components/SideBar.tsx +++ b/src/components/SideBar.tsx @@ -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 = function RawGitako(props) {
- + + {() => ( + + )} + ) : null } diff --git a/src/driver/core/FileExplorer.ts b/src/driver/core/FileExplorer.ts index 9024e94..a3ddfd2 100644 --- a/src/driver/core/FileExplorer.ts +++ b/src/driver/core/FileExplorer.ts @@ -11,6 +11,7 @@ export type Props = { accessToken: string | undefined config: Config loadWithPJAX(url: string): void + catchNetworkErrors: (fn: () => T) => T | undefined } export type ConnectorState = { @@ -47,48 +48,54 @@ export const setUpTree: BoundMethodCreator< config: Pick }, ] -> = 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 => { diff --git a/src/driver/core/SideBar.ts b/src/driver/core/SideBar.ts index 2e6879d..287c4a6 100644 --- a/src/driver/core/SideBar.ts +++ b/src/driver/core/SideBar.ts @@ -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 = MethodCreator -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 }, diff --git a/src/utils/hooks/useCatchNetworkError.tsx b/src/utils/hooks/useCatchNetworkError.tsx new file mode 100644 index 0000000..a33b5c8 --- /dev/null +++ b/src/utils/hooks/useCatchNetworkError.tsx @@ -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 (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 + } + } + } +}