Merge pull request #15056 from overleaf/td-ide-connection-load

IDE page: connection manager and loading screen

GitOrigin-RevId: 2cbc8c09aeb36a14eae66da78b267c7a830fb71a
This commit is contained in:
Tim Down
2023-10-17 08:03:13 +00:00
committed by Copybot
parent 22531969f6
commit 719da5fbd8
19 changed files with 672 additions and 37 deletions
@@ -1,7 +1,9 @@
import useWaitForI18n from '@/shared/hooks/use-wait-for-i18n'
import { GenericErrorBoundaryFallback } from '@/shared/components/generic-error-boundary-fallback'
import withErrorBoundary from '@/infrastructure/error-boundary'
import IdePage from '@/features/ide-react/components/layout/ide-page'
import { ReactContextRoot } from '@/features/ide-react/context/react-context-root'
import { Loading } from '@/features/ide-react/components/loading'
import getMeta from '@/utils/meta'
function IdeRoot() {
// Check that we haven't inadvertently loaded Angular
@@ -10,13 +12,15 @@ function IdeRoot() {
throw new Error('Angular detected. This page must not load Angular.')
}
const { isReady } = useWaitForI18n()
const loadingText = getMeta('ol-loadingText')
if (!isReady) {
return null
}
return <IdePage />
return (
<ReactContextRoot>
<Loading loadingText={loadingText}>
<IdePage />
</Loading>
</ReactContextRoot>
)
}
export default withErrorBoundary(IdeRoot, GenericErrorBoundaryFallback)
@@ -1,11 +1,30 @@
import LayoutWithPlaceholders from '@/features/ide-react/components/layout/layout-with-placeholders'
import { useConnectionContext } from '@/features/ide-react/context/connection-context'
import useEventListener from '@/shared/hooks/use-event-listener'
import { useCallback, useEffect } from 'react'
// This is filled with placeholder content while the real content is migrated
// away from Angular
export default function IdePage() {
const { registerUserActivity } = useConnectionContext()
// Inform the connection manager when the user is active
const listener = useCallback(
() => registerUserActivity(),
[registerUserActivity]
)
useEventListener('cursor:editor:update', listener)
useEffect(() => {
document.body.addEventListener('click', listener)
return () => document.body.removeEventListener('click', listener)
}, [listener])
return (
<div id="ide-react-page">
<>
{/* TODO: Alerts and left menu will go here */}
<LayoutWithPlaceholders shouldPersistLayout />
</div>
</>
)
}
@@ -0,0 +1,69 @@
import { FC, useEffect, useState } from 'react'
import LoadingBranded from '../../../shared/components/loading-branded'
import i18n from '../../../i18n'
import { useConnectionContext } from '../context/connection-context'
import getMeta from '@/utils/meta'
type LoadStatus = 'initial' | 'rendered' | 'connected' | 'loaded'
const loadProgressPercentage: Record<LoadStatus, number> = {
initial: 20,
rendered: 40,
connected: 70,
loaded: 100,
}
// Pass in loading text from the server because i18n will not be ready initially
export const Loading: FC<{ loadingText: string }> = ({
loadingText,
children,
}) => {
const [loadStatus, setLoadStatus] = useState<LoadStatus>('initial')
const { connectionState, isConnected } = useConnectionContext()
const loadProgress = loadProgressPercentage[loadStatus]
const editorLoaded = loadStatus === 'loaded'
const [i18nLoaded, setI18nLoaded] = useState(false)
const [translationLoadError, setTranslationLoadError] = useState(false)
// Advance to 40% once this component is rendered
useEffect(() => {
// Force a reflow now so that the animation from 20% to 40% occurs
// eslint-disable-next-line no-void
void document.body.offsetHeight
setLoadStatus('rendered')
}, [])
useEffect(() => {
i18n
.then(() => setI18nLoaded(true))
.catch(() => {
setTranslationLoadError(true)
})
}, [])
useEffect(() => {
if (editorLoaded) {
return
}
if (isConnected) {
setLoadStatus(i18nLoaded ? 'loaded' : 'connected')
}
}, [i18nLoaded, editorLoaded, setLoadStatus, isConnected])
const translationLoadErrorMessage = translationLoadError
? getMeta('ol-translationLoadErrorMessage')
: ''
return editorLoaded ? (
<>{children}</>
) : (
<div className="loading-screen">
<LoadingBranded
loadProgress={loadProgress}
label={loadingText}
error={connectionState.error || translationLoadErrorMessage}
/>
</div>
)
}