mirror of
https://github.com/EnixCoda/Gitako.git
synced 2026-03-11 08:54:44 +00:00
fix: unify meta resolution
This commit is contained in:
parent
3f7463d5b7
commit
05b89e111a
4 changed files with 113 additions and 110 deletions
|
|
@ -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
6
src/global.d.ts
vendored
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
)
|
||||
}
|
||||
52
src/utils/hooks/useHandleNetworkError.ts
Normal file
52
src/utils/hooks/useHandleNetworkError.ts
Normal 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],
|
||||
)
|
||||
}
|
||||
Loading…
Reference in a new issue