mirror of
https://github.com/enso-org/enso.git
synced 2024-11-22 03:32:23 +03:00
Fix lint
This commit is contained in:
parent
83add15830
commit
d4d2f9348f
@ -7,6 +7,9 @@
|
||||
|
||||
import * as React from 'react'
|
||||
|
||||
import * as reactQuery from '@tanstack/react-query'
|
||||
|
||||
import * as debounceValue from '#/hooks/debounceValueHooks'
|
||||
import * as offlineHooks from '#/hooks/offlineHooks'
|
||||
|
||||
import * as textProvider from '#/providers/TextProvider'
|
||||
@ -24,6 +27,8 @@ export interface SuspenseProps extends React.SuspenseProps {
|
||||
readonly offlineFallbackProps?: result.ResultProps
|
||||
}
|
||||
|
||||
const OFFLINE_FETCHING_TOGGLE_DELAY_MS = 250
|
||||
|
||||
/**
|
||||
* Suspense is a component that allows you to wrap a part of your application that might suspend,
|
||||
* showing a fallback to the user while waiting for the data to load.
|
||||
@ -32,22 +37,52 @@ export interface SuspenseProps extends React.SuspenseProps {
|
||||
* And handles offline scenarios.
|
||||
*/
|
||||
export function Suspense(props: SuspenseProps) {
|
||||
const { children, loaderProps, fallback, offlineFallbackProps, offlineFallback } = props
|
||||
const { children } = props
|
||||
|
||||
return <React.Suspense fallback={<FallbackElement {...props} />}>{children}</React.Suspense>
|
||||
}
|
||||
|
||||
/**
|
||||
* Fallback Element
|
||||
* Checks if ongoing network requests are happening
|
||||
* And shows either fallback(loader) or offline message
|
||||
*
|
||||
* Some request do not require active internet connection, e.g. requests in local backend
|
||||
* So we don't want to show misleading information
|
||||
*
|
||||
* We check the fetching status in fallback component because
|
||||
* we want to know if there are ongiong requests once React renders the fallback in suspense
|
||||
*/
|
||||
function FallbackElement(props: SuspenseProps) {
|
||||
const { loaderProps, fallback, offlineFallbackProps, offlineFallback } = props
|
||||
|
||||
const { getText } = textProvider.useText()
|
||||
|
||||
const { isOffline } = offlineHooks.useOffline()
|
||||
|
||||
const getFallbackElement = () => {
|
||||
if (isOffline) {
|
||||
return (
|
||||
offlineFallback ?? (
|
||||
<result.Result status="info" title={getText('offlineTitle')} {...offlineFallbackProps} />
|
||||
)
|
||||
)
|
||||
} else {
|
||||
return fallback ?? <loader.Loader minHeight="h24" size="medium" {...loaderProps} />
|
||||
}
|
||||
}
|
||||
const paused = reactQuery.useIsFetching({ fetchStatus: 'paused' })
|
||||
|
||||
return <React.Suspense fallback={getFallbackElement()}>{children}</React.Suspense>
|
||||
const fetching = reactQuery.useIsFetching({
|
||||
predicate: query =>
|
||||
query.state.fetchStatus === 'fetching' ||
|
||||
query.state.status === 'pending' ||
|
||||
query.state.status === 'success',
|
||||
})
|
||||
|
||||
// we use small debounce to avoid flickering when query is resolved,
|
||||
// but fallback is still showing
|
||||
const shouldDisplayOfflineMessage = debounceValue.useDebounceValue(
|
||||
isOffline && paused >= 0 && fetching === 0,
|
||||
OFFLINE_FETCHING_TOGGLE_DELAY_MS
|
||||
)
|
||||
|
||||
if (shouldDisplayOfflineMessage) {
|
||||
return (
|
||||
offlineFallback ?? (
|
||||
<result.Result status="info" title={getText('offlineTitle')} {...offlineFallbackProps} />
|
||||
)
|
||||
)
|
||||
} else {
|
||||
return fallback ?? <loader.Loader minHeight="h24" size="medium" {...loaderProps} />
|
||||
}
|
||||
}
|
||||
|
@ -27,7 +27,7 @@ export default function SharedWithColumnHeading(props: column.AssetColumnHeading
|
||||
const isUnderPaywall = isFeatureUnderPaywall('share')
|
||||
|
||||
return (
|
||||
<div className="h-table-row flex w-full items-center gap-icon-with-text">
|
||||
<div className="flex h-table-row w-full items-center gap-icon-with-text">
|
||||
<ariaComponents.Button
|
||||
variant="icon"
|
||||
size="xsmall"
|
||||
|
@ -0,0 +1,93 @@
|
||||
/**
|
||||
* @file
|
||||
*
|
||||
* This file contains the `useDebouncedCallback` hook which is used to debounce a callback function.
|
||||
*/
|
||||
import * as React from 'react'
|
||||
|
||||
import * as callbackHooks from './eventCallbackHooks'
|
||||
import * as unmountEffect from './unmountEffectHooks'
|
||||
|
||||
/**
|
||||
* Wrap a callback into debounce function
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
export function useDebouncedCallback<Fn extends (...args: any[]) => any>(
|
||||
callback: Fn,
|
||||
deps: React.DependencyList,
|
||||
delay: number,
|
||||
maxWait = 0
|
||||
): DebouncedFunction<Fn> {
|
||||
const callbackEvent = callbackHooks.useEventCallback(callback)
|
||||
const timeout = React.useRef<ReturnType<typeof setTimeout>>()
|
||||
const waitTimeout = React.useRef<ReturnType<typeof setTimeout>>()
|
||||
const lastCall = React.useRef<{ args: Parameters<Fn>; this: ThisParameterType<Fn> }>()
|
||||
|
||||
const clear = () => {
|
||||
if (timeout.current) {
|
||||
clearTimeout(timeout.current)
|
||||
timeout.current = undefined
|
||||
}
|
||||
|
||||
if (waitTimeout.current) {
|
||||
clearTimeout(waitTimeout.current)
|
||||
waitTimeout.current = undefined
|
||||
}
|
||||
}
|
||||
|
||||
// cancel scheduled execution on unmount
|
||||
unmountEffect.useUnmountEffect(clear)
|
||||
|
||||
return React.useMemo(() => {
|
||||
const execute = () => {
|
||||
if (!lastCall.current) {
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
return
|
||||
}
|
||||
|
||||
const context = lastCall.current
|
||||
lastCall.current = undefined
|
||||
|
||||
callbackEvent.apply(context.this, context.args)
|
||||
|
||||
clear()
|
||||
}
|
||||
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
const wrapped = function (this, ...args) {
|
||||
if (timeout.current) {
|
||||
clearTimeout(timeout.current)
|
||||
}
|
||||
|
||||
lastCall.current = { args, this: this }
|
||||
|
||||
if (delay === 0) {
|
||||
execute()
|
||||
} else {
|
||||
// plan regular execution
|
||||
timeout.current = setTimeout(execute, delay)
|
||||
|
||||
// plan maxWait execution if required
|
||||
if (maxWait > 0 && !waitTimeout.current) {
|
||||
waitTimeout.current = setTimeout(execute, maxWait)
|
||||
}
|
||||
}
|
||||
} as DebouncedFunction<Fn>
|
||||
|
||||
Object.defineProperties(wrapped, {
|
||||
length: { value: callbackEvent.length },
|
||||
name: { value: `${callbackEvent.name || 'anonymous'}__debounced__${delay}` },
|
||||
})
|
||||
|
||||
return wrapped
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [callbackEvent, delay, maxWait, ...deps])
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
export interface DebouncedFunction<Fn extends (...args: any[]) => any> {
|
||||
(this: ThisParameterType<Fn>, ...args: Parameters<Fn>): void
|
||||
}
|
@ -0,0 +1,41 @@
|
||||
/**
|
||||
* @file
|
||||
*
|
||||
* This file contains the `useDebounceState` hook,
|
||||
* which is a custom hook that returns a stateful value and a function to update it that will debounce updates.
|
||||
*/
|
||||
import * as React from 'react'
|
||||
|
||||
import * as debouncedCallback from './debounceCallbackHooks'
|
||||
import * as eventCallbackHooks from './eventCallbackHooks'
|
||||
|
||||
/**
|
||||
* A hook that returns a stateful value, and a function to update it that will debounce updates.
|
||||
*/
|
||||
export function useDebounceState<S>(
|
||||
initialState: S | (() => S),
|
||||
delay: number,
|
||||
maxWait = 0
|
||||
): [S, React.Dispatch<React.SetStateAction<S>>] {
|
||||
const [state, setState] = React.useState(initialState)
|
||||
const currentValueRef = React.useRef(state)
|
||||
const [, startTransition] = React.useTransition()
|
||||
|
||||
const dSetState = debouncedCallback.useDebouncedCallback<React.Dispatch<React.SetStateAction<S>>>(
|
||||
value => {
|
||||
startTransition(() => {
|
||||
setState(value)
|
||||
})
|
||||
},
|
||||
[],
|
||||
delay,
|
||||
maxWait
|
||||
)
|
||||
const setValue = eventCallbackHooks.useEventCallback((next: S | ((currentValue: S) => S)) => {
|
||||
currentValueRef.current = next instanceof Function ? next(currentValueRef.current) : next
|
||||
|
||||
dSetState(currentValueRef.current)
|
||||
})
|
||||
|
||||
return [state, setValue]
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
/**
|
||||
* @file
|
||||
*
|
||||
* This file contains the `useDebounceValue` hook.
|
||||
*/
|
||||
import * as debounceState from './debounceStateHooks'
|
||||
|
||||
/**
|
||||
* Debounce a value.
|
||||
*/
|
||||
export function useDebounceValue<T>(value: T, delay: number, maxWait?: number) {
|
||||
const [debouncedValue, setDebouncedValue] = debounceState.useDebounceState(value, delay, maxWait)
|
||||
|
||||
if (value !== debouncedValue) {
|
||||
setDebouncedValue(value)
|
||||
}
|
||||
|
||||
return debouncedValue
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
/**
|
||||
* @file
|
||||
*/
|
||||
import * as React from 'react'
|
||||
|
||||
import * as eventCallback from './eventCallbackHooks'
|
||||
|
||||
/**
|
||||
* Calls callback when component is unmounted.
|
||||
*/
|
||||
export function useUnmountEffect(callback: () => void) {
|
||||
// by using `useEventCallback` we can ensure that the callback is stable
|
||||
const callbackEvent = eventCallback.useEventCallback(callback)
|
||||
|
||||
React.useEffect(() => callbackEvent, [callbackEvent])
|
||||
}
|
@ -95,24 +95,26 @@ function run(props: Omit<app.AppProps, 'httpClient' | 'portalRoot'>) {
|
||||
const httpClient = new HttpClient()
|
||||
const queryClient = reactQueryClientModule.createReactQueryClient()
|
||||
|
||||
reactDOM.createRoot(root).render(
|
||||
<React.StrictMode>
|
||||
<reactQuery.QueryClientProvider client={queryClient}>
|
||||
<errorBoundary.ErrorBoundary>
|
||||
<suspense.Suspense fallback={<LoadingScreen />}>
|
||||
<App
|
||||
{...props}
|
||||
supportsDeepLinks={actuallySupportsDeepLinks}
|
||||
portalRoot={portalRoot}
|
||||
httpClient={httpClient}
|
||||
/>
|
||||
</suspense.Suspense>
|
||||
</errorBoundary.ErrorBoundary>
|
||||
React.startTransition(() => {
|
||||
reactDOM.createRoot(root).render(
|
||||
<React.StrictMode>
|
||||
<reactQuery.QueryClientProvider client={queryClient}>
|
||||
<errorBoundary.ErrorBoundary>
|
||||
<suspense.Suspense fallback={<LoadingScreen />}>
|
||||
<App
|
||||
{...props}
|
||||
supportsDeepLinks={actuallySupportsDeepLinks}
|
||||
portalRoot={portalRoot}
|
||||
httpClient={httpClient}
|
||||
/>
|
||||
</suspense.Suspense>
|
||||
</errorBoundary.ErrorBoundary>
|
||||
|
||||
<reactQueryDevtools.ReactQueryDevtools />
|
||||
</reactQuery.QueryClientProvider>
|
||||
</React.StrictMode>
|
||||
)
|
||||
<reactQueryDevtools.ReactQueryDevtools />
|
||||
</reactQuery.QueryClientProvider>
|
||||
</React.StrictMode>
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
/** Global configuration for the {@link App} component. */
|
||||
|
@ -76,7 +76,6 @@ export function TermsOfServiceModal() {
|
||||
// and refetch in the background to check for updates.
|
||||
...(localVersionHash != null && {
|
||||
initialData: { hash: localVersionHash },
|
||||
initialDataUpdatedAt: 0,
|
||||
}),
|
||||
select: data => data.hash,
|
||||
})
|
||||
|
Loading…
Reference in New Issue
Block a user