From 258a252a18ab5ca51ed9f0ed282c3796492584c6 Mon Sep 17 00:00:00 2001 From: epiqueras Date: Tue, 2 Jun 2020 16:04:40 -0700 Subject: [PATCH 1/3] Edit Site: Refactor business logic into store. --- lib/edit-site-page.php | 20 +- package-lock.json | 3 +- .../src/components/provider/use-block-sync.js | 12 + .../data/src/plugins/persistence/index.js | 2 +- packages/edit-site/package.json | 3 +- .../src/components/block-editor/index.js | 69 +--- .../edit-site/src/components/editor/index.js | 315 +++++++++--------- .../edit-site/src/components/header/index.js | 150 +++------ .../src/components/navigate-to-link/index.js | 90 ++--- .../src/components/page-switcher/index.js | 16 +- .../src/components/save-button/index.js | 38 +-- packages/edit-site/src/index.js | 13 +- packages/edit-site/src/store/actions.js | 81 +++++ packages/edit-site/src/store/controls.js | 25 ++ packages/edit-site/src/store/index.js | 19 +- packages/edit-site/src/store/reducer.js | 155 +++++++++ packages/edit-site/src/store/selectors.js | 141 ++++++++ packages/edit-site/src/store/test/actions.js | 70 +++- packages/edit-site/src/store/test/controls.js | 44 +++ packages/edit-site/src/store/test/reducer.js | 232 ++++++++++++- .../edit-site/src/store/test/selectors.js | 112 ++++++- packages/edit-site/src/utils/find-template.js | 35 ++ packages/edit-site/src/utils/index.js | 1 + .../edit-site/src/utils/test/find-template.js | 49 +++ packages/format-library/src/link/utils.js | 2 +- packages/url/README.md | 19 ++ packages/url/src/get-path-and-query-string.js | 26 ++ packages/url/src/index.js | 1 + packages/url/src/test/index.test.js | 37 ++ 29 files changed, 1332 insertions(+), 448 deletions(-) create mode 100644 packages/edit-site/src/store/test/controls.js create mode 100644 packages/edit-site/src/utils/find-template.js create mode 100644 packages/edit-site/src/utils/index.js create mode 100644 packages/edit-site/src/utils/test/find-template.js create mode 100644 packages/url/src/get-path-and-query-string.js diff --git a/lib/edit-site-page.php b/lib/edit-site-page.php index 502d15ab25a762..817c3ef9cb809f 100644 --- a/lib/edit-site-page.php +++ b/lib/edit-site-page.php @@ -142,6 +142,7 @@ function gutenberg_edit_site_init( $hook ) { if ( false !== $font_sizes ) { $settings['fontSizes'] = $font_sizes; } + $settings['styles'] = gutenberg_get_editor_styles(); $template_ids = array(); $template_part_ids = array(); @@ -161,17 +162,18 @@ function gutenberg_edit_site_init( $hook ) { $current_template_id = $template_ids['front-page']; - $settings['templateId'] = $current_template_id; - $settings['homeTemplateId'] = $current_template_id; - $settings['templateType'] = 'wp_template'; - $settings['templateIds'] = array_values( $template_ids ); - $settings['templatePartIds'] = array_values( $template_part_ids ); - $settings['styles'] = gutenberg_get_editor_styles(); + $settings['editSiteInitialState'] = array(); - $settings['showOnFront'] = get_option( 'show_on_front' ); - $settings['page'] = array( + $settings['editSiteInitialState']['homeTemplateId'] = $current_template_id; + $settings['editSiteInitialState']['templateId'] = $current_template_id; + $settings['editSiteInitialState']['templateType'] = 'wp_template'; + $settings['editSiteInitialState']['templateIds'] = array_values( $template_ids ); + $settings['editSiteInitialState']['templatePartIds'] = array_values( $template_part_ids ); + + $settings['editSiteInitialState']['showOnFront'] = get_option( 'show_on_front' ); + $settings['editSiteInitialState']['page'] = array( 'path' => '/', - 'context' => 'page' === $settings['showOnFront'] ? array( + 'context' => 'page' === $settings['editSiteInitialState']['showOnFront'] ? array( 'postType' => 'page', 'postId' => get_option( 'page_on_front' ), ) : array(), diff --git a/package-lock.json b/package-lock.json index de73f17229c637..b2ce42ff3e5d1d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10278,7 +10278,8 @@ "@wordpress/url": "file:packages/url", "file-saver": "^2.0.2", "jszip": "^3.2.2", - "lodash": "^4.17.15" + "lodash": "^4.17.15", + "rememo": "^3.0.0" } }, "@wordpress/edit-widgets": { diff --git a/packages/block-editor/src/components/provider/use-block-sync.js b/packages/block-editor/src/components/provider/use-block-sync.js index bc585a524394b6..c561a2c652060a 100644 --- a/packages/block-editor/src/components/provider/use-block-sync.js +++ b/packages/block-editor/src/components/provider/use-block-sync.js @@ -106,6 +106,8 @@ export default function useBlockSync( { // have been made. This lets us inform the data source of changes. This // is an effect so that the subscriber can run synchronously without // waiting for React renders for changes. + const onInputRef = useRef( onInput ); + const onChangeRef = useRef( onChange ); useEffect( () => { const { getSelectionStart, @@ -118,7 +120,17 @@ export default function useBlockSync( { let isPersistent = isLastBlockChangePersistent(); let previousAreBlocksDifferent = false; + onInputRef.current = onInput; + onChangeRef.current = onChange; const unsubscribe = registry.subscribe( () => { + // Sometimes, subscriptions might trigger with stale callbacks + // before they are cleaned up. Avoid running them. + if ( + onInputRef.current !== onInput || + onChangeRef.current !== onChange + ) + return; + // Sometimes, when changing block lists, lingering subscriptions // might trigger before they are cleaned up. If the block for which // the subscription runs is no longer in the store, this would clear diff --git a/packages/data/src/plugins/persistence/index.js b/packages/data/src/plugins/persistence/index.js index 73eb722733892f..fc9ae014d4628f 100644 --- a/packages/data/src/plugins/persistence/index.js +++ b/packages/data/src/plugins/persistence/index.js @@ -181,7 +181,7 @@ function persistencePlugin( registry, pluginOptions ) { // Load from persistence to use as initial state. const persistedState = persistence.get()[ reducerKey ]; if ( persistedState !== undefined ) { - let initialState = options.reducer( undefined, { + let initialState = options.reducer( options.initialState, { type: '@@WP/PERSISTENCE_RESTORE', } ); diff --git a/packages/edit-site/package.json b/packages/edit-site/package.json index 6ec4960f3004fd..4a83496a196435 100644 --- a/packages/edit-site/package.json +++ b/packages/edit-site/package.json @@ -48,7 +48,8 @@ "@wordpress/url": "file:../url", "file-saver": "^2.0.2", "jszip": "^3.2.2", - "lodash": "^4.17.15" + "lodash": "^4.17.15", + "rememo": "^3.0.0" }, "publishConfig": { "access": "public" diff --git a/packages/edit-site/src/components/block-editor/index.js b/packages/edit-site/src/components/block-editor/index.js index 827f5c85bd81aa..6ca418a8067a13 100644 --- a/packages/edit-site/src/components/block-editor/index.js +++ b/packages/edit-site/src/components/block-editor/index.js @@ -1,9 +1,8 @@ /** * WordPress dependencies */ -import { useSelect } from '@wordpress/data'; -import { useMemo, useCallback } from '@wordpress/element'; -import { uploadMedia } from '@wordpress/media-utils'; +import { useSelect, useDispatch } from '@wordpress/data'; +import { useCallback } from '@wordpress/element'; import { useEntityBlockEditor } from '@wordpress/core-data'; import { BlockEditorProvider, @@ -19,62 +18,26 @@ import { /** * Internal dependencies */ -import { useEditorContext } from '../editor'; import NavigateToLink from '../navigate-to-link'; import { SidebarInspectorFill } from '../sidebar'; export default function BlockEditor() { - const { settings: _settings, setSettings } = useEditorContext(); - const { canUserCreateMedia, focusMode, hasFixedToolbar } = useSelect( - ( select ) => { - const { isFeatureActive } = select( 'core/edit-site' ); - const _canUserCreateMedia = select( 'core' ).canUser( - 'create', - 'media' - ); - return { - canUserCreateMedia: - _canUserCreateMedia || _canUserCreateMedia !== false, - focusMode: isFeatureActive( 'focusMode' ), - hasFixedToolbar: isFeatureActive( 'fixedToolbar' ), - }; - }, - [] - ); - - const settings = useMemo( () => { - if ( ! canUserCreateMedia ) { - return _settings; - } + const { settings, templateType, page } = useSelect( ( select ) => { + const { getSettings, getTemplateType, getPage } = select( + 'core/edit-site' + ); return { - ..._settings, - focusMode, - hasFixedToolbar, - mediaUpload( { onError, ...rest } ) { - uploadMedia( { - wpAllowedMimeTypes: _settings.allowedMimeTypes, - onError: ( { message } ) => onError( message ), - ...rest, - } ); - }, + settings: getSettings(), + templateType: getTemplateType(), + page: getPage(), }; - }, [ canUserCreateMedia, _settings, focusMode, hasFixedToolbar ] ); - + }, [] ); const [ blocks, onInput, onChange ] = useEntityBlockEditor( 'postType', - settings.templateType - ); - const setActivePageAndTemplateId = useCallback( - ( { page, templateId } ) => - setSettings( ( prevSettings ) => ( { - ...prevSettings, - page, - templateId, - templateType: 'wp_template', - } ) ), - [] + templateType ); + const { setPage } = useDispatch( 'core/edit-site' ); return ( ( ), - [ settings.page, setActivePageAndTemplateId ] + [ page ] ) } diff --git a/packages/edit-site/src/components/editor/index.js b/packages/edit-site/src/components/editor/index.js index 8f577c24f9d9ef..f031ab540cc3c3 100644 --- a/packages/edit-site/src/components/editor/index.js +++ b/packages/edit-site/src/components/editor/index.js @@ -1,14 +1,8 @@ /** * WordPress dependencies */ -import { - createContext, - useContext, - useState, - useMemo, - useCallback, -} from '@wordpress/element'; -import { useSelect } from '@wordpress/data'; +import { useState, useMemo, useCallback } from '@wordpress/element'; +import { useSelect, useDispatch } from '@wordpress/data'; import { SlotFillProvider, DropZoneProvider, @@ -45,52 +39,56 @@ import { SidebarComplementaryAreaFills } from '../sidebar'; import BlockEditor from '../block-editor'; import KeyboardShortcuts from '../keyboard-shortcuts'; -const Context = createContext(); -export function useEditorContext() { - return useContext( Context ); -} - const interfaceLabels = { leftSidebar: __( 'Block Library' ), }; -function Editor( { settings: _settings } ) { +function Editor() { const [ isInserterOpen, setIsInserterOpen ] = useState( false ); const isMobile = useViewportMatch( 'medium', '<' ); - const [ settings, setSettings ] = useState( _settings ); - const template = useSelect( - ( select ) => - select( 'core' ).getEntityRecord( - 'postType', - settings.templateType, - settings.templateType === 'wp_template' - ? settings.templateId - : settings.templatePartId - ), - [ settings.templateType, settings.templateId, settings.templatePartId ] - ); - const context = useMemo( () => ( { settings, setSettings } ), [ + const { + isFullscreenActive, + deviceType, + sidebarIsOpened, settings, - setSettings, - ] ); - - const { isFullscreenActive, deviceType, sidebarIsOpened } = useSelect( - ( select ) => { - const { - isFeatureActive, - __experimentalGetPreviewDeviceType, - } = select( 'core/edit-site' ); - return { - isFullscreenActive: isFeatureActive( 'fullscreenMode' ), - deviceType: __experimentalGetPreviewDeviceType(), - sidebarIsOpened: !! select( - 'core/interface' - ).getActiveComplementaryArea( 'core/edit-site' ), - }; - }, - [] - ); + templateId, + templatePartId, + templateType, + page, + template, + } = useSelect( ( select ) => { + const { + isFeatureActive, + __experimentalGetPreviewDeviceType, + getSettings, + getTemplateId, + getTemplatePartId, + getTemplateType, + getPage, + } = select( 'core/edit-site' ); + const _templateId = getTemplateId(); + const _templatePartId = getTemplatePartId(); + const _templateType = getTemplateType(); + return { + isFullscreenActive: isFeatureActive( 'fullscreenMode' ), + deviceType: __experimentalGetPreviewDeviceType(), + sidebarIsOpened: !! select( + 'core/interface' + ).getActiveComplementaryArea( 'core/edit-site' ), + settings: getSettings(), + templateId: _templateId, + templatePartId: _templatePartId, + templateType: _templateType, + page: getPage(), + template: select( 'core' ).getEntityRecord( + 'postType', + _templateType, + _templateType === 'wp_template' ? _templateId : _templatePartId + ), + }; + }, [] ); + const { setPage } = useDispatch( 'core/edit-site' ); const inlineStyles = useResizeCanvas( deviceType ); @@ -112,27 +110,24 @@ function Editor( { settings: _settings } ) { // and Query Pagination blocks. const blockContext = useMemo( () => ( { - ...settings.page.context, - query: settings.page.context.query || { categoryIds: [] }, + ...page.context, + query: page.context.query || { categoryIds: [] }, queryContext: [ - settings.page.context.queryContext || { page: 1 }, + page.context.queryContext || { page: 1 }, ( newQueryContext ) => - setSettings( ( prevSettings ) => ( { - ...prevSettings, - page: { - ...prevSettings.page, - context: { - ...prevSettings.page.context, - queryContext: { - ...prevSettings.page.context.queryContext, - ...newQueryContext, - }, + setPage( { + ...page, + context: { + ...page.context, + queryContext: { + ...page.context.queryContext, + ...newQueryContext, }, }, - } ) ), + } ), ], } ), - [ settings.page.context ] + [ page.context ] ); return template ? ( <> @@ -143,117 +138,115 @@ function Editor( { settings: _settings } ) { - - - - - -
-
+
+ { + if ( + isMobile + ) { setIsInserterOpen( false - ) + ); } - /> -
-
- { - if ( - isMobile - ) { - setIsInserterOpen( - false - ); - } - } } - /> -
+ } } + /> - ) - } - sidebar={ - sidebarIsOpened && ( - - ) - } - header={ -
+ ) + } + sidebar={ + sidebarIsOpened && ( + + ) + } + header={ +
+ setIsInserterOpen( + ! isInserterOpen + ) + } + /> + } + content={ + + + + + + + } + actions={ + <> + - setIsInserterOpen( - ! isInserterOpen - ) + close={ + closeEntitiesSavedStates } /> - } - content={ - - - - - - - } - actions={ - <> - - { ! isEntitiesSavedStatesOpen && ( -
- -
- ) } - - } - footer={ } - /> - - - - + { ! isEntitiesSavedStatesOpen && ( +
+ +
+ ) } + + } + footer={ } + /> + + + diff --git a/packages/edit-site/src/components/header/index.js b/packages/edit-site/src/components/header/index.js index 90da7d050ea9de..8dc46f53808659 100644 --- a/packages/edit-site/src/components/header/index.js +++ b/packages/edit-site/src/components/header/index.js @@ -2,19 +2,13 @@ * WordPress dependencies */ import { useViewportMatch } from '@wordpress/compose'; -import { useCallback } from '@wordpress/element'; -import { addQueryArgs } from '@wordpress/url'; import { BlockNavigationDropdown, ToolSelector, BlockToolbar, __experimentalPreviewOptions as PreviewOptions, } from '@wordpress/block-editor'; -import { - __experimentalResolveSelect as resolveSelect, - useSelect, - useDispatch, -} from '@wordpress/data'; +import { useSelect, useDispatch } from '@wordpress/data'; import { PinnedItems, __experimentalMainDashboardButton as MainDashboardButton, @@ -26,7 +20,6 @@ import { Button } from '@wordpress/components'; /** * Internal dependencies */ -import { useEditorContext } from '../editor'; import MoreMenu from './more-menu'; import PageSwitcher from '../page-switcher'; import TemplateSwitcher from '../template-switcher'; @@ -35,98 +28,53 @@ import UndoButton from './undo-redo/undo'; import RedoButton from './undo-redo/redo'; import FullscreenModeClose from './fullscreen-mode-close'; -/** - * Browser dependencies - */ -const { fetch } = window; - export default function Header( { openEntitiesSavedStates, isInserterOpen, onToggleInserter, } ) { - const { settings, setSettings } = useEditorContext(); - const setActiveTemplateId = useCallback( - ( newTemplateId ) => - setSettings( ( prevSettings ) => ( { - ...prevSettings, - templateId: newTemplateId, - templateType: 'wp_template', - } ) ), - [] - ); - const setActiveTemplatePartId = useCallback( - ( newTemplatePartId ) => - setSettings( ( prevSettings ) => ( { - ...prevSettings, - templatePartId: newTemplatePartId, - templateType: 'wp_template_part', - } ) ), - [] - ); - const setActivePage = useCallback( async ( newPage ) => { - try { - const { success, data } = await fetch( - addQueryArgs( newPage.path, { '_wp-find-template': true } ) - ).then( ( res ) => res.json() ); - if ( success ) { - let newTemplateId = data.ID; - if ( newTemplateId === null ) { - newTemplateId = ( - await resolveSelect( 'core' ).getEntityRecords( - 'postType', - 'wp_template', - { - resolved: true, - slug: data.post_name, - } - ) - )[ 0 ].id; - } - setSettings( ( prevSettings ) => ( { - ...prevSettings, - page: newPage, - templateId: newTemplateId, - templateType: 'wp_template', - } ) ); - } - } catch ( err ) {} - }, [] ); - const addTemplateId = useCallback( - ( newTemplateId ) => - setSettings( ( prevSettings ) => ( { - ...prevSettings, - templateId: newTemplateId, - templateType: 'wp_template', - templateIds: [ ...prevSettings.templateIds, newTemplateId ], - } ) ), - [] - ); - const removeTemplateId = useCallback( - ( oldTemplateId ) => { - setSettings( ( prevSettings ) => ( { - ...prevSettings, - templateIds: prevSettings.templateIds.filter( - ( templateId ) => templateId !== oldTemplateId - ), - } ) ); - setActivePage( settings.page ); - }, - [ settings.page ] - ); - - const { deviceType, hasFixedToolbar } = useSelect( ( select ) => { - const { __experimentalGetPreviewDeviceType, isFeatureActive } = select( - 'core/edit-site' - ); + const { + deviceType, + hasFixedToolbar, + homeTemplateId, + templateId, + templatePartId, + templateType, + templatePartIds, + page, + showOnFront, + } = useSelect( ( select ) => { + const { + __experimentalGetPreviewDeviceType, + isFeatureActive, + getHomeTemplateId, + getTemplateId, + getTemplatePartId, + getTemplateType, + getTemplatePartIds, + getPage, + getShowOnFront, + } = select( 'core/edit-site' ); return { deviceType: __experimentalGetPreviewDeviceType(), hasFixedToolbar: isFeatureActive( 'fixedToolbar' ), + homeTemplateId: getHomeTemplateId(), + templateId: getTemplateId(), + templatePartId: getTemplatePartId(), + templateType: getTemplateType(), + templatePartIds: getTemplatePartIds(), + page: getPage(), + showOnFront: getShowOnFront(), }; }, [] ); const { __experimentalSetPreviewDeviceType: setPreviewDeviceType, + setTemplate, + addTemplate, + removeTemplate, + setTemplatePart, + setPage, } = useDispatch( 'core/edit-site' ); const isLargeViewport = useViewportMatch( 'medium' ); @@ -160,26 +108,24 @@ export default function Header( { ) }
/
diff --git a/packages/edit-site/src/components/navigate-to-link/index.js b/packages/edit-site/src/components/navigate-to-link/index.js index d4ce684792f99b..8aabdb3a38b212 100644 --- a/packages/edit-site/src/components/navigate-to-link/index.js +++ b/packages/edit-site/src/components/navigate-to-link/index.js @@ -1,34 +1,22 @@ /** * WordPress dependencies */ -import { getPath, getQueryString, addQueryArgs } from '@wordpress/url'; +import { getPathAndQueryString } from '@wordpress/url'; import { useState, useEffect, useMemo } from '@wordpress/element'; -import { - useSelect, - __experimentalResolveSelect as resolveSelect, -} from '@wordpress/data'; +import { useSelect, useRegistry } from '@wordpress/data'; import { Button } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; /** - * Browser dependencies + * Internal dependencies */ -const { fetch } = window; +import { findTemplate } from '../../utils'; -function getPathFromLink( link ) { - // TODO: Explore abstracting this into `@wordpress/url`. - const path = getPath( link ); - const queryString = getQueryString( link ); - let value = '/'; - if ( path ) value += path; - if ( queryString ) value += `?${ queryString }`; - return value; -} export default function NavigateToLink( { type, id, activePage, - onActivePageAndTemplateIdChange, + onActivePageChange, } ) { const pageEntity = useSelect( ( select ) => @@ -39,64 +27,34 @@ export default function NavigateToLink( { [ type, id ] ); + const registry = useRegistry(); const [ templateId, setTemplateId ] = useState(); useEffect( () => { - const effect = async () => { - try { - const { success, data } = await fetch( - addQueryArgs( pageEntity.link, { - '_wp-find-template': true, - } ) - ).then( ( res ) => res.json() ); - if ( success ) { - let newTemplateId = data.ID; - if ( newTemplateId === null ) { - newTemplateId = ( - await resolveSelect( 'core' ).getEntityRecords( - 'postType', - 'wp_template', - { - resolved: true, - slug: data.post_name, - } - ) - )[ 0 ].id; - } - setTemplateId( newTemplateId ); - } else { - throw new Error(); - } - } catch ( err ) { - setTemplateId( null ); - } - }; - effect(); - }, [ pageEntity?.link ] ); + if ( pageEntity ) + findTemplate( + pageEntity.link, + registry.__experimentalResolveSelect( 'core' ).getEntityRecords + ).then( + ( newTemplateId ) => setTemplateId( newTemplateId ), + () => setTemplateId( null ) + ); + }, [ pageEntity?.link, registry ] ); const onClick = useMemo( () => { if ( ! pageEntity || ! templateId ) return null; - const path = getPathFromLink( pageEntity.link ); + const path = getPathAndQueryString( pageEntity.link ); if ( path === activePage.path ) return null; return () => - onActivePageAndTemplateIdChange( { - page: { - type, - slug: pageEntity.slug, - path, - context: { - postType: pageEntity.type, - postId: pageEntity.id, - }, + onActivePageChange( { + type, + slug: pageEntity.slug, + path, + context: { + postType: pageEntity.type, + postId: pageEntity.id, }, - templateId, } ); - }, [ - pageEntity, - templateId, - getPathFromLink, - activePage.path, - onActivePageAndTemplateIdChange, - ] ); + }, [ pageEntity, templateId, activePage.path, onActivePageChange ] ); return ( onClick && (