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={