diff --git a/editor/src/components/canvas/canvas-loading-screen.tsx b/editor/src/components/canvas/canvas-loading-screen.tsx
index 54e4c74042e4..854dec875c93 100644
--- a/editor/src/components/canvas/canvas-loading-screen.tsx
+++ b/editor/src/components/canvas/canvas-loading-screen.tsx
@@ -3,6 +3,8 @@ import { Global, css } from '@emotion/react'
import { useColorTheme } from '../../uuiui'
import { useEditorState } from '../editor/store/store-hook'
import { Substores } from '../editor/store/store-hook'
+import { getTotalImportStatusAndResult } from '../../core/shared/import/import-operation-service'
+import type { TotalImportResult } from '../../core/shared/import/import-operation-types'
export const CanvasLoadingScreen = React.memo(() => {
const colorTheme = useColorTheme()
@@ -17,17 +19,26 @@ export const CanvasLoadingScreen = React.memo(() => {
'CanvasLoadingScreen importWizardOpen',
)
+ const totalImportResult: TotalImportResult = React.useMemo(
+ () => getTotalImportStatusAndResult(importState),
+ [importState],
+ )
+
const importingStoppedStyleOverride = React.useMemo(
() =>
// if the importing was stopped, we want to pause the shimmer animation
- (importWizardOpen && importState.importStatus.status === 'done') ||
- importState.importStatus.status === 'paused'
+ (importWizardOpen && totalImportResult.importStatus.status === 'done') ||
+ totalImportResult.importStatus.status === 'paused'
? {
background: colorTheme.codeEditorShimmerPrimary.value,
animation: 'none',
}
: {},
- [importWizardOpen, importState.importStatus.status, colorTheme.codeEditorShimmerPrimary.value],
+ [
+ importWizardOpen,
+ totalImportResult.importStatus.status,
+ colorTheme.codeEditorShimmerPrimary.value,
+ ],
)
return (
diff --git a/editor/src/components/editor/actions/actions.tsx b/editor/src/components/editor/actions/actions.tsx
index de08aba96446..1f910fb5b9b0 100644
--- a/editor/src/components/editor/actions/actions.tsx
+++ b/editor/src/components/editor/actions/actions.tsx
@@ -520,6 +520,7 @@ import {
import { addToastToState, includeToast, removeToastFromState } from './toast-helpers'
import { AspectRatioLockedProp } from '../../aspect-ratio'
import {
+ getDependenciesStatus,
refreshDependencies,
removeModulesFromNodeModules,
} from '../../../core/shared/dependencies'
@@ -629,6 +630,8 @@ import { getParseCacheOptions } from '../../../core/shared/parse-cache-utils'
import { styleP } from '../../inspector/inspector-common'
import {
getUpdateOperationResult,
+ notifyOperationFinished,
+ notifyOperationStarted,
notifyImportStatusToDiscord,
} from '../../../core/shared/import/import-operation-service'
import { updateRequirements } from '../../../core/shared/import/project-health-check/utopia-requirements-service'
@@ -6319,6 +6322,8 @@ export async function load(
// this action is now async!
const migratedModel = applyMigrations(model)
const npmDependencies = dependenciesWithEditorRequirements(migratedModel.projectContents)
+ // side effect ☢️
+ notifyOperationStarted(dispatch, { type: 'refreshDependencies' })
const fetchNodeModulesResult = await fetchNodeModules(
dispatch,
npmDependencies,
@@ -6333,6 +6338,13 @@ export async function load(
fetchNodeModulesResult.dependenciesNotFound,
)
+ // side effect ☢️
+ notifyOperationFinished(
+ dispatch,
+ { type: 'refreshDependencies' },
+ getDependenciesStatus(packageResult),
+ )
+
const codeResultCache: CodeResultCache = generateCodeResultCache(
// TODO is this sufficient here?
migratedModel.projectContents,
diff --git a/editor/src/components/editor/import-wizard/import-wizard.tsx b/editor/src/components/editor/import-wizard/import-wizard.tsx
index 446e414fba93..8c5c54b76c86 100644
--- a/editor/src/components/editor/import-wizard/import-wizard.tsx
+++ b/editor/src/components/editor/import-wizard/import-wizard.tsx
@@ -188,6 +188,14 @@ function ActionButtons() {
fontSize: 14,
cursor: 'pointer',
}
+ React.useEffect(() => {
+ if (
+ importResult.importStatus.status == 'done' &&
+ importResult.result == ImportOperationResult.Success
+ ) {
+ hideWizard()
+ }
+ }, [importResult, hideWizard])
if (
importResult.importStatus.status === 'in-progress' ||
importResult.importStatus.status === 'not-started'
diff --git a/editor/src/components/editor/loading-screen.tsx b/editor/src/components/editor/loading-screen.tsx
new file mode 100644
index 000000000000..c3287334c007
--- /dev/null
+++ b/editor/src/components/editor/loading-screen.tsx
@@ -0,0 +1,174 @@
+import React from 'react'
+import { Substores, useEditorState } from './store/store-hook'
+import { getImportOperationTextAsJsx } from './import-wizard/import-wizard-helpers'
+import { getTotalImportStatusAndResult } from '../../core/shared/import/import-operation-service'
+import type { TotalImportResult } from '../../core/shared/import/import-operation-types'
+import type { Theme } from '../../uuiui'
+import { useColorTheme } from '../../uuiui'
+import { getCurrentTheme } from './store/editor-state'
+import ReactDOM from 'react-dom'
+
+export function LoadingEditorComponent() {
+ const colorTheme = useColorTheme()
+
+ const currentTheme: Theme = useEditorState(
+ Substores.theme,
+ (store) => getCurrentTheme(store.userState),
+ 'currentTheme',
+ )
+
+ const importState = useEditorState(
+ Substores.restOfEditor,
+ (store) => store.editor.importState,
+ 'LoadingEditorComponent importState',
+ )
+
+ const githubRepo = useEditorState(
+ Substores.userState,
+ (store) => store.userState.githubState.gitRepoToLoad,
+ 'LoadingEditorComponent githubRepoToLoad',
+ )
+
+ const totalImportResult: TotalImportResult = React.useMemo(
+ () => getTotalImportStatusAndResult(importState),
+ [importState],
+ )
+
+ const projectId = useEditorState(
+ Substores.restOfEditor,
+ (store) => store.editor.id,
+ 'LoadingEditorComponent projectId',
+ )
+
+ const cleared = React.useRef(false)
+
+ const currentOperationToShow: {
+ text: React.ReactNode
+ id: string
+ timeDone: number | null | undefined
+ timeStarted: number | null | undefined
+ } | null = React.useMemo(() => {
+ if (totalImportResult.importStatus.status == 'not-started') {
+ if (projectId == null) {
+ return {
+ text: 'Loading Editor...',
+ id: 'loading-editor',
+ timeDone: null,
+ timeStarted: null,
+ }
+ } else {
+ return {
+ text: `Parsing files`,
+ id: 'parseFiles',
+ timeDone: null,
+ timeStarted: null,
+ }
+ }
+ }
+ for (const op of importState.importOperations) {
+ if (op?.children?.length == 0 || op.type == 'refreshDependencies') {
+ if (op.timeStarted != null && op.timeDone == null) {
+ return {
+ text: getImportOperationTextAsJsx(op),
+ id: op.id ?? op.type,
+ timeDone: op.timeDone,
+ timeStarted: op.timeStarted,
+ }
+ }
+ }
+ if (op.type !== 'refreshDependencies') {
+ for (const child of op.children ?? []) {
+ if (child.timeStarted != null && child.timeDone == null) {
+ return {
+ text: getImportOperationTextAsJsx(child),
+ id: child.id ?? child.type,
+ timeDone: child.timeDone,
+ timeStarted: child.timeStarted,
+ }
+ }
+ }
+ }
+ }
+ return {
+ text: 'Loading Editor...',
+ id: 'loading-editor',
+ timeDone: null,
+ timeStarted: null,
+ }
+ }, [totalImportResult, importState.importOperations, projectId])
+
+ const shouldBeCleared = React.useMemo(() => {
+ return (
+ cleared.current ||
+ (totalImportResult.importStatus.status == 'done' &&
+ (githubRepo == null || totalImportResult.result == 'criticalError')) ||
+ totalImportResult.importStatus.status == 'paused'
+ )
+ }, [totalImportResult, githubRepo])
+
+ React.useEffect(() => {
+ if (shouldBeCleared) {
+ const loadingScreenWrapper = document.getElementById('loading-screen-wrapper')
+ if (loadingScreenWrapper != null) {
+ loadingScreenWrapper.remove()
+ }
+ }
+ }, [shouldBeCleared])
+
+ const portal = React.useRef(document.getElementById('loading-screen-progress-bar-portal')).current
+ const hasMounted = React.useRef(false)
+ if (portal == null) {
+ return null
+ }
+
+ if (shouldBeCleared) {
+ cleared.current = true
+ return null
+ }
+
+ if (!hasMounted.current) {
+ portal.innerHTML = ''
+ hasMounted.current = true
+ }
+
+ const themeStyle =
+ currentTheme === 'dark'
+ ? `
+ .editor-loading-screen { background-color: ${colorTheme.bg6.value} }
+ .utopia-logo-pyramid.light { display: none; }
+ .utopia-logo-pyramid.dark { display: block; }
+ `
+ : ''
+
+ return ReactDOM.createPortal(
+
+ {currentOperationToShow != null ? (
+
+