mirror of
https://github.com/enso-org/enso.git
synced 2024-12-23 08:53:31 +03:00
Fix opening cloud projects (#10351)
- Fix https://github.com/enso-org/cloud-v2/issues/1324 # Important Notes None
This commit is contained in:
parent
5f2c9b32f0
commit
b425c9d1a8
@ -5,7 +5,7 @@ import * as focusHooks from '#/hooks/focusHooks'
|
|||||||
|
|
||||||
import * as aria from '#/components/aria'
|
import * as aria from '#/components/aria'
|
||||||
import * as ariaComponents from '#/components/AriaComponents'
|
import * as ariaComponents from '#/components/AriaComponents'
|
||||||
import Spinner, * as spinnerModule from '#/components/Spinner'
|
import StatelessSpinner, * as spinnerModule from '#/components/StatelessSpinner'
|
||||||
import SvgMask from '#/components/SvgMask'
|
import SvgMask from '#/components/SvgMask'
|
||||||
|
|
||||||
import * as twv from '#/utilities/tailwindVariants'
|
import * as twv from '#/utilities/tailwindVariants'
|
||||||
@ -62,6 +62,9 @@ export interface BaseButtonProps extends Omit<twv.VariantProps<typeof BUTTON_STY
|
|||||||
readonly testId?: string
|
readonly testId?: string
|
||||||
|
|
||||||
readonly formnovalidate?: boolean
|
readonly formnovalidate?: boolean
|
||||||
|
/** Defaults to `full`. When `full`, the entire button will be replaced with the loader.
|
||||||
|
* When `icon`, only the icon will be replaced with the loader. */
|
||||||
|
readonly loaderPosition?: 'full' | 'icon'
|
||||||
}
|
}
|
||||||
|
|
||||||
export const BUTTON_STYLES = twv.tv({
|
export const BUTTON_STYLES = twv.tv({
|
||||||
@ -286,6 +289,7 @@ export const Button = React.forwardRef(function Button(
|
|||||||
tooltip,
|
tooltip,
|
||||||
tooltipPlacement,
|
tooltipPlacement,
|
||||||
testId,
|
testId,
|
||||||
|
loaderPosition = 'full',
|
||||||
onPress = () => {},
|
onPress = () => {},
|
||||||
...ariaProps
|
...ariaProps
|
||||||
} = props
|
} = props
|
||||||
@ -326,7 +330,10 @@ export const Button = React.forwardRef(function Button(
|
|||||||
[{ opacity: 0 }, { opacity: 0, offset: 1 }, { opacity: 1 }],
|
[{ opacity: 0 }, { opacity: 0, offset: 1 }, { opacity: 1 }],
|
||||||
{ duration: delay, easing: 'linear', delay: 0, fill: 'forwards' }
|
{ duration: delay, easing: 'linear', delay: 0, fill: 'forwards' }
|
||||||
)
|
)
|
||||||
const contentAnimation = contentRef.current?.animate([{ opacity: 1 }, { opacity: 0 }], {
|
const contentAnimation =
|
||||||
|
loaderPosition !== 'full'
|
||||||
|
? null
|
||||||
|
: contentRef.current?.animate([{ opacity: 1 }, { opacity: 0 }], {
|
||||||
duration: 0,
|
duration: 0,
|
||||||
easing: 'linear',
|
easing: 'linear',
|
||||||
delay,
|
delay,
|
||||||
@ -340,7 +347,7 @@ export const Button = React.forwardRef(function Button(
|
|||||||
} else {
|
} else {
|
||||||
return () => {}
|
return () => {}
|
||||||
}
|
}
|
||||||
}, [isLoading])
|
}, [isLoading, loaderPosition])
|
||||||
|
|
||||||
const handlePress = (event: aria.PressEvent): void => {
|
const handlePress = (event: aria.PressEvent): void => {
|
||||||
if (!isLoading) {
|
if (!isLoading) {
|
||||||
@ -381,6 +388,12 @@ export const Button = React.forwardRef(function Button(
|
|||||||
const iconComponent = (() => {
|
const iconComponent = (() => {
|
||||||
if (icon == null) {
|
if (icon == null) {
|
||||||
return null
|
return null
|
||||||
|
} else if (isLoading && loaderPosition === 'icon') {
|
||||||
|
return (
|
||||||
|
<span className={iconClasses()}>
|
||||||
|
<StatelessSpinner state={spinnerModule.SpinnerState.loadingMedium} size={16} />
|
||||||
|
</span>
|
||||||
|
)
|
||||||
} else if (typeof icon === 'string') {
|
} else if (typeof icon === 'string') {
|
||||||
return <SvgMask src={icon} className={iconClasses()} />
|
return <SvgMask src={icon} className={iconClasses()} />
|
||||||
} else {
|
} else {
|
||||||
@ -425,9 +438,9 @@ export const Button = React.forwardRef(function Button(
|
|||||||
{childrenFactory()}
|
{childrenFactory()}
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
{isLoading && (
|
{isLoading && loaderPosition === 'full' && (
|
||||||
<span ref={loaderRef} className={loader()}>
|
<span ref={loaderRef} className={loader()}>
|
||||||
<Spinner state={spinnerModule.SpinnerState.loadingMedium} size={16} />
|
<StatelessSpinner state={spinnerModule.SpinnerState.loadingMedium} size={16} />
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</span>
|
</span>
|
||||||
|
@ -12,8 +12,6 @@ import * as aria from '#/components/aria'
|
|||||||
import * as ariaComponents from '#/components/AriaComponents'
|
import * as ariaComponents from '#/components/AriaComponents'
|
||||||
import Portal from '#/components/Portal'
|
import Portal from '#/components/Portal'
|
||||||
|
|
||||||
import * as mergeRefs from '#/utilities/mergeRefs'
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Props for {@link useVisualTooltip}.
|
* Props for {@link useVisualTooltip}.
|
||||||
*/
|
*/
|
||||||
@ -123,7 +121,7 @@ export function useVisualTooltip(props: VisualTooltipProps) {
|
|||||||
const createTooltipElement = () => (
|
const createTooltipElement = () => (
|
||||||
<Portal onMount={updatePosition}>
|
<Portal onMount={updatePosition}>
|
||||||
<span
|
<span
|
||||||
ref={mergeRefs.mergeRefs(popoverRef, ref => ref?.showPopover())}
|
ref={popoverRef}
|
||||||
{...aria.mergeProps<React.HTMLAttributes<HTMLDivElement>>()(
|
{...aria.mergeProps<React.HTMLAttributes<HTMLDivElement>>()(
|
||||||
overlayProps,
|
overlayProps,
|
||||||
tooltipProps,
|
tooltipProps,
|
||||||
|
@ -113,6 +113,7 @@ export default function ProjectIcon(props: ProjectIconProps) {
|
|||||||
item.projectState.executeAsync ?? false
|
item.projectState.executeAsync ?? false
|
||||||
)
|
)
|
||||||
const [shouldSwitchPage, setShouldSwitchPage] = React.useState(false)
|
const [shouldSwitchPage, setShouldSwitchPage] = React.useState(false)
|
||||||
|
const doAbortOpeningRef = React.useRef(() => {})
|
||||||
const doOpenEditorRef = React.useRef(doOpenEditor)
|
const doOpenEditorRef = React.useRef(doOpenEditor)
|
||||||
doOpenEditorRef.current = doOpenEditor
|
doOpenEditorRef.current = doOpenEditor
|
||||||
const isCloud = backend.type === backendModule.BackendType.remote
|
const isCloud = backend.type === backendModule.BackendType.remote
|
||||||
@ -133,14 +134,22 @@ export default function ProjectIcon(props: ProjectIconProps) {
|
|||||||
mutationKey: ['openEditor', item.id],
|
mutationKey: ['openEditor', item.id],
|
||||||
networkMode: 'always',
|
networkMode: 'always',
|
||||||
mutationFn: async () => {
|
mutationFn: async () => {
|
||||||
const projectPromise = waitUntilProjectIsReadyMutation.mutateAsync([
|
const abortController = new AbortController()
|
||||||
|
doAbortOpeningRef.current = () => {
|
||||||
|
abortController.abort()
|
||||||
|
}
|
||||||
|
const projectPromise = openProjectMutate([
|
||||||
|
item.id,
|
||||||
|
{ executeAsync: false, parentId: item.parentId, cognitoCredentials: session },
|
||||||
|
item.title,
|
||||||
|
]).then(() =>
|
||||||
|
waitUntilProjectIsReadyMutation.mutateAsync([
|
||||||
item.id,
|
item.id,
|
||||||
item.parentId,
|
item.parentId,
|
||||||
item.title,
|
item.title,
|
||||||
|
abortController.signal,
|
||||||
])
|
])
|
||||||
if (shouldOpenWhenReady) {
|
)
|
||||||
doOpenEditor()
|
|
||||||
}
|
|
||||||
setProjectStartupInfo({
|
setProjectStartupInfo({
|
||||||
project: projectPromise,
|
project: projectPromise,
|
||||||
projectAsset: item,
|
projectAsset: item,
|
||||||
@ -149,6 +158,12 @@ export default function ProjectIcon(props: ProjectIconProps) {
|
|||||||
accessToken: session?.accessToken ?? null,
|
accessToken: session?.accessToken ?? null,
|
||||||
})
|
})
|
||||||
await projectPromise
|
await projectPromise
|
||||||
|
if (!abortController.signal.aborted) {
|
||||||
|
setState(backendModule.ProjectState.opened)
|
||||||
|
if (shouldOpenWhenReady) {
|
||||||
|
doOpenEditor()
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
const openEditorMutate = openEditorMutation.mutate
|
const openEditorMutate = openEditorMutation.mutate
|
||||||
@ -156,8 +171,12 @@ export default function ProjectIcon(props: ProjectIconProps) {
|
|||||||
const openProject = React.useCallback(
|
const openProject = React.useCallback(
|
||||||
async (shouldRunInBackground: boolean) => {
|
async (shouldRunInBackground: boolean) => {
|
||||||
if (state !== backendModule.ProjectState.opened) {
|
if (state !== backendModule.ProjectState.opened) {
|
||||||
setState(backendModule.ProjectState.openInProgress)
|
|
||||||
try {
|
try {
|
||||||
|
if (!shouldRunInBackground) {
|
||||||
|
setState(backendModule.ProjectState.openInProgress)
|
||||||
|
openEditorMutate()
|
||||||
|
} else {
|
||||||
|
setState(backendModule.ProjectState.opened)
|
||||||
await openProjectMutate([
|
await openProjectMutate([
|
||||||
item.id,
|
item.id,
|
||||||
{
|
{
|
||||||
@ -167,8 +186,6 @@ export default function ProjectIcon(props: ProjectIconProps) {
|
|||||||
},
|
},
|
||||||
item.title,
|
item.title,
|
||||||
])
|
])
|
||||||
if (!shouldRunInBackground) {
|
|
||||||
openEditorMutate()
|
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const project = await getProjectDetailsMutate([item.id, item.parentId, item.title])
|
const project = await getProjectDetailsMutate([item.id, item.parentId, item.title])
|
||||||
@ -176,7 +193,6 @@ export default function ProjectIcon(props: ProjectIconProps) {
|
|||||||
// not just the state type.
|
// not just the state type.
|
||||||
setItem(object.merger({ projectState: project.state }))
|
setItem(object.merger({ projectState: project.state }))
|
||||||
toastAndLog('openProjectError', error, item.title)
|
toastAndLog('openProjectError', error, item.title)
|
||||||
setState(backendModule.ProjectState.closed)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -211,6 +227,7 @@ export default function ProjectIcon(props: ProjectIconProps) {
|
|||||||
if (!event.runInBackground && !isRunningInBackground) {
|
if (!event.runInBackground && !isRunningInBackground) {
|
||||||
setShouldOpenWhenReady(false)
|
setShouldOpenWhenReady(false)
|
||||||
if (!isOtherUserUsingProject && backendModule.IS_OPENING_OR_OPENED[state]) {
|
if (!isOtherUserUsingProject && backendModule.IS_OPENING_OR_OPENED[state]) {
|
||||||
|
doAbortOpeningRef.current()
|
||||||
void closeProject()
|
void closeProject()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -272,6 +289,7 @@ export default function ProjectIcon(props: ProjectIconProps) {
|
|||||||
setShouldOpenWhenReady(false)
|
setShouldOpenWhenReady(false)
|
||||||
setState(backendModule.ProjectState.closing)
|
setState(backendModule.ProjectState.closing)
|
||||||
await closeProjectMutation.mutateAsync([item.id, item.title])
|
await closeProjectMutation.mutateAsync([item.id, item.title])
|
||||||
|
setState(backendModule.ProjectState.closed)
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (state) {
|
switch (state) {
|
||||||
|
@ -113,9 +113,9 @@ export default function Settings(props: SettingsProps) {
|
|||||||
/>
|
/>
|
||||||
</aria.Popover>
|
</aria.Popover>
|
||||||
</aria.MenuTrigger>
|
</aria.MenuTrigger>
|
||||||
<ariaComponents.Text.Heading className="font-bold">
|
<ariaComponents.Text variant="h1" className="font-bold">
|
||||||
<span>{getText('settingsFor')}</span>
|
<span>{getText('settingsFor')}</span>
|
||||||
</ariaComponents.Text.Heading>
|
</ariaComponents.Text>
|
||||||
|
|
||||||
<ariaComponents.Text
|
<ariaComponents.Text
|
||||||
variant="h1"
|
variant="h1"
|
||||||
|
@ -153,15 +153,34 @@ interface InternalTabProps extends Readonly<React.PropsWithChildren> {
|
|||||||
readonly isActive: boolean
|
readonly isActive: boolean
|
||||||
readonly icon: string
|
readonly icon: string
|
||||||
readonly labelId: text.TextId
|
readonly labelId: text.TextId
|
||||||
|
/** When the promise is in flight, the tab icon will instead be a loading spinner. */
|
||||||
|
readonly loadingPromise?: Promise<unknown>
|
||||||
readonly onPress: () => void
|
readonly onPress: () => void
|
||||||
readonly onClose?: () => void
|
readonly onClose?: () => void
|
||||||
}
|
}
|
||||||
|
|
||||||
/** A tab in a {@link TabBar}. */
|
/** A tab in a {@link TabBar}. */
|
||||||
export function Tab(props: InternalTabProps) {
|
export function Tab(props: InternalTabProps) {
|
||||||
const { isActive, icon, labelId, children, onPress, onClose } = props
|
const { isActive, icon, labelId, loadingPromise, children, onPress, onClose } = props
|
||||||
const { updateClipPath } = useTabBarContext()
|
const { updateClipPath } = useTabBarContext()
|
||||||
const { getText } = textProvider.useText()
|
const { getText } = textProvider.useText()
|
||||||
|
const [isLoading, setIsLoading] = React.useState(loadingPromise != null)
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (loadingPromise) {
|
||||||
|
setIsLoading(true)
|
||||||
|
loadingPromise.then(
|
||||||
|
() => {
|
||||||
|
setIsLoading(false)
|
||||||
|
},
|
||||||
|
() => {
|
||||||
|
setIsLoading(false)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
setIsLoading(false)
|
||||||
|
}
|
||||||
|
}, [loadingPromise])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
@ -174,9 +193,11 @@ export function Tab(props: InternalTabProps) {
|
|||||||
<ariaComponents.Button
|
<ariaComponents.Button
|
||||||
size="custom"
|
size="custom"
|
||||||
variant="custom"
|
variant="custom"
|
||||||
|
loaderPosition="icon"
|
||||||
icon={icon}
|
icon={icon}
|
||||||
isDisabled={isActive}
|
isDisabled={isActive}
|
||||||
isActive={isActive}
|
isActive={isActive}
|
||||||
|
loading={isLoading}
|
||||||
aria-label={getText(labelId)}
|
aria-label={getText(labelId)}
|
||||||
tooltip={false}
|
tooltip={false}
|
||||||
className={tailwindMerge.twMerge(
|
className={tailwindMerge.twMerge(
|
||||||
|
@ -39,7 +39,6 @@ import type * as projectManager from '#/services/ProjectManager'
|
|||||||
|
|
||||||
import * as array from '#/utilities/array'
|
import * as array from '#/utilities/array'
|
||||||
import LocalStorage from '#/utilities/LocalStorage'
|
import LocalStorage from '#/utilities/LocalStorage'
|
||||||
import * as object from '#/utilities/object'
|
|
||||||
import * as sanitizedEventTargets from '#/utilities/sanitizedEventTargets'
|
import * as sanitizedEventTargets from '#/utilities/sanitizedEventTargets'
|
||||||
|
|
||||||
import type * as types from '../../../../types/types'
|
import type * as types from '../../../../types/types'
|
||||||
@ -60,7 +59,7 @@ declare module '#/utilities/LocalStorage' {
|
|||||||
interface LocalStorageData {
|
interface LocalStorageData {
|
||||||
readonly isAssetPanelVisible: boolean
|
readonly isAssetPanelVisible: boolean
|
||||||
readonly page: TabType
|
readonly page: TabType
|
||||||
readonly projectStartupInfo: backendModule.ProjectStartupInfo<backendModule.Project>
|
readonly projectStartupInfo: Omit<backendModule.ProjectStartupInfo, 'project'>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -86,15 +85,13 @@ LocalStorage.registerKey('projectStartupInfo', {
|
|||||||
return null
|
return null
|
||||||
} else if (!('backendType' in value) || !array.includes(BACKEND_TYPES, value.backendType)) {
|
} else if (!('backendType' in value) || !array.includes(BACKEND_TYPES, value.backendType)) {
|
||||||
return null
|
return null
|
||||||
} else if (!('project' in value) || !('projectAsset' in value)) {
|
} else if (!('projectAsset' in value)) {
|
||||||
return null
|
return null
|
||||||
} else {
|
} else {
|
||||||
return {
|
return {
|
||||||
// These type assertions are UNSAFE, however correctly type-checking these
|
// These type assertions are UNSAFE, however correctly type-checking these
|
||||||
// would be very complicated.
|
// would be very complicated.
|
||||||
// eslint-disable-next-line no-restricted-syntax
|
// eslint-disable-next-line no-restricted-syntax
|
||||||
project: value.project as backendModule.Project,
|
|
||||||
// eslint-disable-next-line no-restricted-syntax
|
|
||||||
projectAsset: value.projectAsset as backendModule.ProjectAsset,
|
projectAsset: value.projectAsset as backendModule.ProjectAsset,
|
||||||
backendType: value.backendType,
|
backendType: value.backendType,
|
||||||
accessToken: value.accessToken ?? null,
|
accessToken: value.accessToken ?? null,
|
||||||
@ -143,8 +140,7 @@ export default function Dashboard(props: DashboardProps) {
|
|||||||
)
|
)
|
||||||
const [projectStartupInfo, setProjectStartupInfo] =
|
const [projectStartupInfo, setProjectStartupInfo] =
|
||||||
React.useState<backendModule.ProjectStartupInfo | null>(null)
|
React.useState<backendModule.ProjectStartupInfo | null>(null)
|
||||||
const [openProjectAbortController, setOpenProjectAbortController] =
|
const openProjectAbortControllerRef = React.useRef<AbortController | null>(null)
|
||||||
React.useState<AbortController | null>(null)
|
|
||||||
const [assetListEvents, dispatchAssetListEvent] =
|
const [assetListEvents, dispatchAssetListEvent] =
|
||||||
eventHooks.useEvent<assetListEvent.AssetListEvent>()
|
eventHooks.useEvent<assetListEvent.AssetListEvent>()
|
||||||
const [assetEvents, dispatchAssetEvent] = eventHooks.useEvent<assetEvent.AssetEvent>()
|
const [assetEvents, dispatchAssetEvent] = eventHooks.useEvent<assetEvent.AssetEvent>()
|
||||||
@ -187,7 +183,7 @@ export default function Dashboard(props: DashboardProps) {
|
|||||||
setPage(TabType.drive)
|
setPage(TabType.drive)
|
||||||
void (async () => {
|
void (async () => {
|
||||||
const abortController = new AbortController()
|
const abortController = new AbortController()
|
||||||
setOpenProjectAbortController(abortController)
|
openProjectAbortControllerRef.current = abortController
|
||||||
try {
|
try {
|
||||||
const oldProject = await remoteBackend.getProjectDetails(
|
const oldProject = await remoteBackend.getProjectDetails(
|
||||||
savedProjectStartupInfo.projectAsset.id,
|
savedProjectStartupInfo.projectAsset.id,
|
||||||
@ -199,13 +195,9 @@ export default function Dashboard(props: DashboardProps) {
|
|||||||
savedProjectStartupInfo.projectAsset.id,
|
savedProjectStartupInfo.projectAsset.id,
|
||||||
savedProjectStartupInfo.projectAsset.parentId,
|
savedProjectStartupInfo.projectAsset.parentId,
|
||||||
savedProjectStartupInfo.projectAsset.title,
|
savedProjectStartupInfo.projectAsset.title,
|
||||||
abortController
|
abortController.signal
|
||||||
)
|
|
||||||
setProjectStartupInfo(
|
|
||||||
object.merge<backendModule.ProjectStartupInfo>(savedProjectStartupInfo, {
|
|
||||||
project,
|
|
||||||
})
|
|
||||||
)
|
)
|
||||||
|
setProjectStartupInfo({ ...savedProjectStartupInfo, project })
|
||||||
if (page === TabType.editor) {
|
if (page === TabType.editor) {
|
||||||
setPage(page)
|
setPage(page)
|
||||||
}
|
}
|
||||||
@ -232,9 +224,7 @@ export default function Dashboard(props: DashboardProps) {
|
|||||||
savedProjectStartupInfo.projectAsset.parentId,
|
savedProjectStartupInfo.projectAsset.parentId,
|
||||||
savedProjectStartupInfo.projectAsset.title
|
savedProjectStartupInfo.projectAsset.title
|
||||||
)
|
)
|
||||||
setProjectStartupInfo(
|
setProjectStartupInfo({ ...savedProjectStartupInfo, project })
|
||||||
object.merge<backendModule.ProjectStartupInfo>(savedProjectStartupInfo, { project })
|
|
||||||
)
|
|
||||||
if (page === TabType.editor) {
|
if (page === TabType.editor) {
|
||||||
setPage(page)
|
setPage(page)
|
||||||
}
|
}
|
||||||
@ -249,8 +239,8 @@ export default function Dashboard(props: DashboardProps) {
|
|||||||
eventHooks.useEventHandler(assetEvents, event => {
|
eventHooks.useEventHandler(assetEvents, event => {
|
||||||
switch (event.type) {
|
switch (event.type) {
|
||||||
case AssetEventType.openProject: {
|
case AssetEventType.openProject: {
|
||||||
openProjectAbortController?.abort()
|
openProjectAbortControllerRef.current?.abort()
|
||||||
setOpenProjectAbortController(null)
|
openProjectAbortControllerRef.current = null
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
default: {
|
default: {
|
||||||
@ -263,9 +253,10 @@ export default function Dashboard(props: DashboardProps) {
|
|||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (initializedRef.current) {
|
if (initializedRef.current) {
|
||||||
if (projectStartupInfo != null) {
|
if (projectStartupInfo != null) {
|
||||||
void Promise.resolve(projectStartupInfo.project).then(project => {
|
// This is INTENTIONAL - `project` is intentionally omitted from this object.
|
||||||
localStorage.set('projectStartupInfo', { ...projectStartupInfo, project })
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
})
|
const { project, ...rest } = projectStartupInfo
|
||||||
|
localStorage.set('projectStartupInfo', rest)
|
||||||
} else {
|
} else {
|
||||||
localStorage.delete('projectStartupInfo')
|
localStorage.delete('projectStartupInfo')
|
||||||
}
|
}
|
||||||
@ -376,6 +367,7 @@ export default function Dashboard(props: DashboardProps) {
|
|||||||
isActive={page === TabType.editor}
|
isActive={page === TabType.editor}
|
||||||
icon={WorkspaceIcon}
|
icon={WorkspaceIcon}
|
||||||
labelId="editorPageName"
|
labelId="editorPageName"
|
||||||
|
loadingPromise={projectStartupInfo.project}
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
setPage(TabType.editor)
|
setPage(TabType.editor)
|
||||||
}}
|
}}
|
||||||
@ -384,6 +376,7 @@ export default function Dashboard(props: DashboardProps) {
|
|||||||
type: AssetEventType.closeProject,
|
type: AssetEventType.closeProject,
|
||||||
id: projectStartupInfo.projectAsset.id,
|
id: projectStartupInfo.projectAsset.id,
|
||||||
})
|
})
|
||||||
|
setProjectStartupInfo(null)
|
||||||
setPage(TabType.drive)
|
setPage(TabType.drive)
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
@ -292,10 +292,8 @@ export interface BackendProject extends Project {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** Information required to open a project. */
|
/** Information required to open a project. */
|
||||||
export interface ProjectStartupInfo<
|
export interface ProjectStartupInfo {
|
||||||
ProjectType extends Project | Promise<Project> = Project | Promise<Project>,
|
readonly project: Promise<Project>
|
||||||
> {
|
|
||||||
readonly project: ProjectType
|
|
||||||
readonly projectAsset: ProjectAsset
|
readonly projectAsset: ProjectAsset
|
||||||
// This MUST BE optional because it is lost when `JSON.stringify`ing to put in `localStorage`.
|
// This MUST BE optional because it is lost when `JSON.stringify`ing to put in `localStorage`.
|
||||||
readonly setProjectAsset?: React.Dispatch<React.SetStateAction<ProjectAsset>>
|
readonly setProjectAsset?: React.Dispatch<React.SetStateAction<ProjectAsset>>
|
||||||
@ -1428,6 +1426,6 @@ export default abstract class Backend {
|
|||||||
projectId: ProjectId,
|
projectId: ProjectId,
|
||||||
directory: DirectoryId | null,
|
directory: DirectoryId | null,
|
||||||
title: string,
|
title: string,
|
||||||
abortController?: AbortController
|
abortSignal?: AbortSignal
|
||||||
): Promise<Project>
|
): Promise<Project>
|
||||||
}
|
}
|
||||||
|
@ -1067,11 +1067,12 @@ export default class RemoteBackend extends Backend {
|
|||||||
projectId: backend.ProjectId,
|
projectId: backend.ProjectId,
|
||||||
directory: backend.DirectoryId | null,
|
directory: backend.DirectoryId | null,
|
||||||
title: string,
|
title: string,
|
||||||
abortController: AbortController = new AbortController()
|
abortSignal?: AbortSignal
|
||||||
) {
|
) {
|
||||||
let project = await this.getProjectDetails(projectId, directory, title)
|
let project = await this.getProjectDetails(projectId, directory, title)
|
||||||
while (project.state.type !== backend.ProjectState.opened) {
|
while (project.state.type !== backend.ProjectState.opened) {
|
||||||
if (abortController.signal.aborted) {
|
if (abortSignal?.aborted === true) {
|
||||||
|
// The operation was cancelled, do not return.
|
||||||
// eslint-disable-next-line no-restricted-syntax
|
// eslint-disable-next-line no-restricted-syntax
|
||||||
throw new Error()
|
throw new Error()
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user