fix: unify meta resolution

This commit is contained in:
EnixCoda 2022-12-26 13:12:54 +08:00
parent 3f7463d5b7
commit 05b89e111a
4 changed files with 113 additions and 110 deletions

View file

@ -2,11 +2,13 @@ import { PropsWithChildren } from 'common'
import { useConfigs } from 'containers/ConfigsContext'
import { platform } from 'platforms'
import * as React from 'react'
import { run } from 'utils/general'
import { useAbortableEffect } from 'utils/hooks/useAbortableEffect'
import { useEffectOnSerializableUpdates } from 'utils/hooks/useEffectOnSerializableUpdates'
import { useAfterRedirect } from 'utils/hooks/useFastRedirect'
import { useHandleNetworkError } from 'utils/hooks/useHandleNetworkError'
import { useLoadedContext } from 'utils/hooks/useLoadedContext'
import { useStateIO } from 'utils/hooks/useStateIO'
import { useCatchNetworkError } from '../utils/hooks/useCatchNetworkError'
import { SideBarStateContext } from './SideBarState'
import { useInspector } from './StateInspector'
@ -14,17 +16,15 @@ export const RepoContext = React.createContext<MetaData | null>(null)
export function RepoContextWrapper({ children }: PropsWithChildren) {
const partialMetaData = usePartialMetaData()
const defaultBranch = useDefaultBranch(partialMetaData)
const metaData = useMetaData(partialMetaData, defaultBranch)
const metaData = useMetaData(partialMetaData)
useInspector(
'RepoContext',
React.useMemo(
() => ({
partialMetaData,
defaultBranch,
metaData,
}),
[partialMetaData, defaultBranch, metaData],
[partialMetaData, metaData],
),
)
const state = useLoadedContext(SideBarStateContext).value
@ -33,7 +33,7 @@ export function RepoContextWrapper({ children }: PropsWithChildren) {
return <RepoContext.Provider value={metaData}>{children}</RepoContext.Provider>
}
function resolvePartialMetaData() {
function resolvePartialMetaData(): PartialMetaData | null {
const partialMetaData = platform.resolvePartialMetaData()
if (partialMetaData) {
const { userName, repoName, type } = partialMetaData
@ -49,7 +49,8 @@ function resolvePartialMetaData() {
function usePartialMetaData(): PartialMetaData | null {
const $state = useLoadedContext(SideBarStateContext)
const isGettingAccessToken = $state.value === 'getting-access-token' // will be false after getting access token and trigger meta-resolve progress
// will be false after getting access token and trigger meta-resolve progress
const isGettingAccessToken = $state.value === 'getting-access-token'
// sync along URL and DOM
const $partialMetaData = useStateIO(isGettingAccessToken ? null : resolvePartialMetaData)
const $committedPartialMetaData = useStateIO($partialMetaData.value)
@ -74,58 +75,60 @@ function usePartialMetaData(): PartialMetaData | null {
return $committedPartialMetaData.value
}
function useBranchName(): MetaData['branchName'] | null {
// sync along URL and DOM
const $branchName = useStateIO(() => platform.resolvePartialMetaData()?.branchName || null)
useAfterRedirect(() =>
$branchName.onChange(platform.resolvePartialMetaData()?.branchName || null),
)
return $branchName.value
}
function useDefaultBranch(partialMetaData: PartialMetaData | null) {
const { accessToken } = useConfigs().value
const $state = useLoadedContext(SideBarStateContext)
const $defaultBranch = useStateIO<string | null>(null)
const catchNetworkError = useCatchNetworkError()
React.useEffect(() => {
catchNetworkError(async () => {
if (!partialMetaData) return
$state.onChange('meta-loading')
const { userName, repoName } = partialMetaData
if (!userName || !repoName) return
const defaultBranch = await platform.getDefaultBranchName({ userName, repoName }, accessToken)
$defaultBranch.onChange(defaultBranch)
})
}, [partialMetaData, accessToken]) // eslint-disable-line react-hooks/exhaustive-deps
return $defaultBranch.value
}
function useMetaData(
partialMetaData: PartialMetaData | null,
defaultBranchName: MetaData['defaultBranchName'] | null,
) {
function useMetaData(partialMetaData: PartialMetaData | null) {
const $state = useLoadedContext(SideBarStateContext)
const $metaData = useStateIO<MetaData | null>(null)
const branchName = useBranchName()
const theBranch = branchName && branchName !== defaultBranchName ? branchName : defaultBranchName
React.useEffect(() => {
if (partialMetaData && defaultBranchName && theBranch) {
const { userName, repoName } = partialMetaData
if (!userName || !repoName) return
const safeMetaData: MetaData = {
userName,
repoName,
branchName: theBranch,
defaultBranchName,
}
$metaData.onChange(safeMetaData)
$state.onChange('meta-loaded')
} else {
$metaData.onChange(null)
}
}, [partialMetaData, defaultBranchName, theBranch]) // eslint-disable-line react-hooks/exhaustive-deps
const { accessToken } = useConfigs().value
const handleNetworkError = useHandleNetworkError()
useAbortableEffect(
React.useCallback(
signal => {
// get default branch
run(async () => {
if (!partialMetaData) return
const { userName, repoName } = partialMetaData
if (!userName || !repoName) return
$state.onChange('meta-loading')
let { branchName } = partialMetaData
if (!branchName) {
try {
const defaultBranchName = await platform.getDefaultBranchName(
{ userName, repoName },
accessToken,
)
if (signal.aborted) return
branchName = defaultBranchName
} catch (err) {
// state will be updated in the network error handler
if (err instanceof Error) {
handleNetworkError(err)
return
}
throw err
}
}
$metaData.onChange({
userName,
repoName,
branchName,
})
$state.onChange('meta-loaded')
})
return () => {
$state.onChange('disabled')
$metaData.onChange(null)
}
},
// eslint-disable-next-line react-hooks/exhaustive-deps
[partialMetaData, accessToken],
),
)
return $metaData.value
}

6
src/global.d.ts vendored
View file

@ -3,15 +3,11 @@ type AnyArray = any[] // eslint-disable-line @typescript-eslint/no-explicit-any
type MetaData = {
userName: string
repoName: string
defaultBranchName: string
branchName: string
type?: EnumString<'tree' | 'blob' | 'pull' | 'commit'>
}
type PartialMetaData = Omit<
MakeOptional<MetaData, 'repoName' | 'userName' | 'branchName'>,
'defaultBranchName'
>
type PartialMetaData = MakeOptional<MetaData, 'repoName' | 'userName' | 'branchName'>
type TreeNode = {
name: string

View file

@ -1,48 +0,0 @@
import { useConfigs } from 'containers/ConfigsContext'
import { errors, platformName } from 'platforms'
import { useCallback } from 'react'
import { assert } from 'utils/assert'
import { useLoadedContext } from 'utils/hooks/useLoadedContext'
import { SideBarErrorContext } from '../../containers/ErrorContext'
import { SideBarStateContext } from '../../containers/SideBarState'
export function useCatchNetworkError() {
const { accessToken } = useConfigs().value
const stateContext = useLoadedContext(SideBarStateContext)
const errorContext = useLoadedContext(SideBarErrorContext)
return useCallback(
async function <T>(fn: () => T) {
try {
return await fn() // keep the await so that catch block can catch async errors
} catch (err) {
assert(err instanceof Error)
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('Something unexpected happened.')
throw err
}
}
},
[accessToken /* , stateContext.value, errorContext.value */], // eslint-disable-line react-hooks/exhaustive-deps
)
}

View file

@ -0,0 +1,52 @@
import { useConfigs } from 'containers/ConfigsContext'
import { errors, platformName } from 'platforms'
import { useCallback } from 'react'
import { useLoadedContext } from 'utils/hooks/useLoadedContext'
import { SideBarErrorContext } from '../../containers/ErrorContext'
import { SideBarStateContext } from '../../containers/SideBarState'
export function useHandleNetworkError() {
const { accessToken } = useConfigs().value
const changeErrorContext = useLoadedContext(SideBarErrorContext).onChange
const changeStateContext = useLoadedContext(SideBarStateContext).onChange
return useCallback(
function handleNetworkError(err: Error) {
if (err.message === errors.EMPTY_PROJECT) {
changeErrorContext('This project seems to be empty.')
return
}
if (err.message === errors.BLOCKED_PROJECT) {
changeErrorContext('Access to the project is blocked.')
return
}
if (
err.message === errors.NOT_FOUND ||
err.message === errors.BAD_CREDENTIALS ||
err.message === errors.API_RATE_LIMIT
) {
changeStateContext('error-due-to-auth')
return
}
if (err.message === errors.CONNECTION_BLOCKED) {
if (accessToken) changeErrorContext(`Cannot connect to ${platformName}.`)
else changeStateContext('error-due-to-auth')
return
}
if (err.message === errors.SERVER_FAULT) {
changeErrorContext(`${platformName} server went down.`)
return
}
changeStateContext('disabled')
changeErrorContext('Something unexpected happened.')
throw err
},
[accessToken, changeErrorContext, changeStateContext],
)
}