diff --git a/packages/block-editor/src/components/index.js b/packages/block-editor/src/components/index.js
index f113ad3b05f630..77e0fbb7f48ccb 100644
--- a/packages/block-editor/src/components/index.js
+++ b/packages/block-editor/src/components/index.js
@@ -71,6 +71,7 @@ export { default as __experimentalLinkControlSearchResults } from './link-contro
export { default as __experimentalLinkControlSearchItem } from './link-control/search-item';
export { default as LineHeightControl } from './line-height-control';
export { default as __experimentalListView } from './list-view';
+export { default as __experimentalLoadingScreen } from './loading-screen';
export { default as MediaReplaceFlow } from './media-replace-flow';
export { default as MediaPlaceholder } from './media-placeholder';
export { default as MediaUpload } from './media-upload';
diff --git a/packages/block-editor/src/components/loading-screen/index.js b/packages/block-editor/src/components/loading-screen/index.js
new file mode 100644
index 00000000000000..ab614df128b528
--- /dev/null
+++ b/packages/block-editor/src/components/loading-screen/index.js
@@ -0,0 +1,145 @@
+/**
+ * External dependencies
+ */
+import classNames from 'classnames';
+
+/**
+ * WordPress dependencies
+ */
+import {
+ createContext,
+ Suspense,
+ useEffect,
+ useState,
+} from '@wordpress/element';
+import { Modal, Spinner } from '@wordpress/components';
+import { useSuspenseSelect } from '@wordpress/data';
+
+const LoadingScreenContext = createContext( false );
+
+/**
+ * Component that declares a data dependency.
+ * Will suspend if data has not resolved.
+ *
+ * @param {Object} props Component props
+ * @param {import('@wordpress/data').StoreDescriptor} props.store Data store descriptor
+ * @param {string} props.selector Selector name
+ * @param {Array} props.args Optional arguments to pass to the selector
+ */
+const SuspenseDataDependency = ( { store, selector, args = [] } ) => {
+ useSuspenseSelect(
+ ( select ) => select( store )[ selector ]( ...args ),
+ []
+ );
+
+ return null;
+};
+
+/**
+ * Component that will render a loading screen if dependencies have not resolved,
+ * or its children if all dependencies have resolved.
+ *
+ * @param {Object} props Component props
+ * @param {Array} props.dataDependencies Array of dependencies
+ * @param {string} props.children Component children
+ * @param {string?} props.overlayClassName Additional overlay classname
+ */
+const SuspenseWithLoadingScreen = ( {
+ dataDependencies,
+ children,
+ overlayClassName,
+} ) => {
+ const [ loaded, setLoaded ] = useState( false );
+
+ const finishedLoading = () => {
+ if ( ! loaded ) {
+ setLoaded( true );
+ }
+ };
+
+ return (
+
+ { loaded ? (
+ <>
+
+ { children }
+ >
+ ) : (
+
+ }
+ >
+ { dataDependencies.map(
+ ( { store, selector, args }, depindex ) => (
+
+ )
+ ) }
+ { children }
+
+ ) }
+
+ );
+};
+
+/**
+ * Renders a loading screen.
+ * Supports automatic closing with the `autoClose` prop.
+ *
+ * @param {Object} props Component props
+ * @param {Function?} props.onUnmount Optional callback to call on unmount.
+ * @param {boolean} props.autoClose Whether to automatically close.
+ * @param {string?} props.overlayClassName Additional overlay classname
+ */
+const LoadingScreen = ( { onUnmount, autoClose, overlayClassName } ) => {
+ const [ visible, setVisible ] = useState( true );
+
+ useEffect( () => {
+ if ( autoClose ) {
+ setTimeout( () => {
+ setVisible( false );
+ }, 2000 );
+ }
+
+ return () => {
+ if ( onUnmount ) {
+ onUnmount();
+ }
+ };
+ } );
+
+ if ( ! visible ) {
+ return null;
+ }
+
+ return (
+ {} }
+ __experimentalHideHeader
+ className="block-editor-loading-screen-modal"
+ overlayClassName={ classNames(
+ 'block-editor-loading-screen-modal-overlay',
+ overlayClassName
+ ) }
+ >
+
+
+
+
+ );
+};
+
+export default SuspenseWithLoadingScreen;
diff --git a/packages/block-editor/src/components/loading-screen/style.scss b/packages/block-editor/src/components/loading-screen/style.scss
new file mode 100644
index 00000000000000..5764c01fd01f28
--- /dev/null
+++ b/packages/block-editor/src/components/loading-screen/style.scss
@@ -0,0 +1,24 @@
+.block-editor-loading-screen-modal-overlay {
+ backdrop-filter: none;
+ background-color: transparent;
+ padding-top: $header-height;
+}
+
+.block-editor-loading-screen-modal-overlay.is-canvas-view {
+ padding-top: 0;
+}
+
+.block-editor-loading-screen-modal.is-full-screen {
+ box-shadow: 0 0 0 transparent;
+ width: 100%;
+ max-width: 100%;
+ max-height: 100%;
+ min-height: 100%;
+}
+
+.block-editor-loading-screen-wrapper {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ height: 100%;
+}
diff --git a/packages/block-editor/src/style.scss b/packages/block-editor/src/style.scss
index d5ec18cb4c69ff..9b57a5445d5837 100644
--- a/packages/block-editor/src/style.scss
+++ b/packages/block-editor/src/style.scss
@@ -35,6 +35,7 @@
@import "./components/justify-content-control/style.scss";
@import "./components/link-control/style.scss";
@import "./components/list-view/style.scss";
+@import "./components/loading-screen/style.scss";
@import "./components/media-replace-flow/style.scss";
@import "./components/multi-selection-inspector/style.scss";
@import "./components/responsive-block-control/style.scss";
diff --git a/packages/edit-site/src/components/editor/index.js b/packages/edit-site/src/components/editor/index.js
index 434554bbda7e82..44706ec97df198 100644
--- a/packages/edit-site/src/components/editor/index.js
+++ b/packages/edit-site/src/components/editor/index.js
@@ -4,11 +4,12 @@
import { useMemo } from '@wordpress/element';
import { useSelect, useDispatch } from '@wordpress/data';
import { Notice } from '@wordpress/components';
-import { EntityProvider } from '@wordpress/core-data';
+import { EntityProvider, store as coreStore } from '@wordpress/core-data';
import { store as preferencesStore } from '@wordpress/preferences';
import {
BlockContextProvider,
BlockBreadcrumb,
+ __experimentalLoadingScreen as LoadingScreen,
store as blockEditorStore,
} from '@wordpress/block-editor';
import {
@@ -68,6 +69,7 @@ export default function Editor() {
isListViewOpen,
showIconLabels,
showBlockBreadcrumbs,
+ globalStylesId,
} = useSelect( ( select ) => {
const {
getEditedPostContext,
@@ -76,8 +78,16 @@ export default function Editor() {
isInserterOpened,
isListViewOpened,
} = unlock( select( editSiteStore ) );
+ const {
+ __experimentalGetCurrentGlobalStylesId,
+ getEditedEntityRecord,
+ } = select( coreStore );
const { __unstableGetEditorMode } = select( blockEditorStore );
const { getActiveComplementaryArea } = select( interfaceStore );
+ const _globalStylesId = __experimentalGetCurrentGlobalStylesId();
+ const globalStylesRecord = _globalStylesId
+ ? getEditedEntityRecord( 'root', 'globalStyles', _globalStylesId )
+ : undefined;
// The currently selected entity to display.
// Typically template or template part in the site editor.
@@ -99,6 +109,8 @@ export default function Editor() {
'core/edit-site',
'showBlockBreadcrumbs'
),
+ globalStylesId: _globalStylesId,
+ globalStylesRecord,
};
}, [] );
const { setEditedPostContext } = useDispatch( editSiteStore );
@@ -152,6 +164,48 @@ export default function Editor() {
// action in from double-announcing.
useTitle( hasLoadedPost && title );
+ const contentDependencies = [
+ // Current post entity,
+ {
+ store: coreStore,
+ selector: 'getEntityRecord',
+ args: [ 'postType', 'postType', editedPostId ],
+ },
+ // Global styles entity ID
+ {
+ store: coreStore,
+ selector: '__experimentalGetCurrentGlobalStylesId',
+ },
+ // Global styles entity
+ globalStylesId && {
+ store: coreStore,
+ selector: 'getEditedEntityRecord',
+ args: [ 'root', 'globalStyles', globalStylesId ],
+ },
+ // Menus
+ {
+ store: coreStore,
+ selector: 'getEntityRecords',
+ args: [ 'root', 'menu', { per_page: -1, context: 'edit' } ],
+ },
+ // Pages
+ {
+ store: coreStore,
+ selector: 'getEntityRecords',
+ args: [
+ 'postType',
+ 'page',
+ {
+ parent: 0,
+ order: 'asc',
+ orderby: 'id',
+ per_page: -1,
+ context: 'view',
+ },
+ ],
+ },
+ ].filter( Boolean );
+
if ( ! hasLoadedPost ) {
return ;
}
@@ -174,27 +228,36 @@ export default function Editor() {
notices={ isEditMode && }
content={
<>
-
- { isEditMode && }
- { showVisualEditor && editedPost && (
-
- ) }
- { editorMode === 'text' &&
- editedPost &&
- isEditMode && }
- { hasLoadedPost && ! editedPost && (
-
- { __(
- "You attempted to edit an item that doesn't exist. Perhaps it was deleted?"
- ) }
-
- ) }
- { isEditMode && (
-
- ) }
+
+
+ { isEditMode && }
+ { showVisualEditor && editedPost && (
+
+ ) }
+ { editorMode === 'text' &&
+ editedPost &&
+ isEditMode && }
+ { hasLoadedPost && ! editedPost && (
+
+ { __(
+ "You attempted to edit an item that doesn't exist. Perhaps it was deleted?"
+ ) }
+
+ ) }
+ { isEditMode && (
+
+ ) }
+
>
}
secondarySidebar={