diff --git a/packages/edit-site/src/components/page-patterns/duplicate-menu-item.js b/packages/edit-site/src/components/page-patterns/duplicate-menu-item.js deleted file mode 100644 index e82666902ed16..0000000000000 --- a/packages/edit-site/src/components/page-patterns/duplicate-menu-item.js +++ /dev/null @@ -1,105 +0,0 @@ -/** - * WordPress dependencies - */ -import { MenuItem } from '@wordpress/components'; -import { useDispatch } from '@wordpress/data'; -import { useState } from '@wordpress/element'; -import { __, sprintf } from '@wordpress/i18n'; -import { store as noticesStore } from '@wordpress/notices'; -import { privateApis as patternsPrivateApis } from '@wordpress/patterns'; -import { privateApis as routerPrivateApis } from '@wordpress/router'; - -/** - * Internal dependencies - */ -import { TEMPLATE_PART_POST_TYPE, PATTERN_TYPES } from '../../utils/constants'; -import { unlock } from '../../lock-unlock'; -import CreateTemplatePartModal from '../create-template-part-modal'; - -const { DuplicatePatternModal } = unlock( patternsPrivateApis ); -const { useHistory } = unlock( routerPrivateApis ); - -export default function DuplicateMenuItem( { - categoryId, - item, - label = __( 'Duplicate' ), - onClose, -} ) { - const { createSuccessNotice } = useDispatch( noticesStore ); - const [ isModalOpen, setIsModalOpen ] = useState( false ); - const history = useHistory(); - - const closeModal = () => setIsModalOpen( false ); - - const isTemplatePart = item.type === TEMPLATE_PART_POST_TYPE; - const isThemePattern = item.type === PATTERN_TYPES.theme; - - async function onTemplatePartSuccess( templatePart ) { - createSuccessNotice( - sprintf( - // translators: %s: The new template part's title e.g. 'Call to action (copy)'. - __( '"%s" duplicated.' ), - item.title - ), - { - type: 'snackbar', - id: 'edit-site-patterns-success', - } - ); - - history.push( { - postType: TEMPLATE_PART_POST_TYPE, - postId: templatePart?.id, - categoryType: TEMPLATE_PART_POST_TYPE, - categoryId, - } ); - - onClose(); - } - - function onPatternSuccess( { pattern } ) { - history.push( { - categoryType: PATTERN_TYPES.theme, - categoryId, - postType: PATTERN_TYPES.user, - postId: pattern.id, - } ); - - onClose(); - } - - return ( - <> - setIsModalOpen( true ) } - aria-expanded={ isModalOpen } - aria-haspopup="dialog" - > - { label } - - { isModalOpen && ! isTemplatePart && ( - - ) } - { isModalOpen && isTemplatePart && ( - - ) } - - ); -} diff --git a/packages/edit-site/src/components/page-patterns/grid-item.js b/packages/edit-site/src/components/page-patterns/grid-item.js deleted file mode 100644 index 0c1b162dac99d..0000000000000 --- a/packages/edit-site/src/components/page-patterns/grid-item.js +++ /dev/null @@ -1,331 +0,0 @@ -/** - * External dependencies - */ -import classnames from 'classnames'; -import { paramCase as kebabCase } from 'change-case'; - -/** - * WordPress dependencies - */ -import { - BlockPreview, - privateApis as blockEditorPrivateApis, -} from '@wordpress/block-editor'; -import { - Button, - __experimentalConfirmDialog as ConfirmDialog, - DropdownMenu, - MenuGroup, - MenuItem, - __experimentalHeading as Heading, - __experimentalHStack as HStack, - Tooltip, - Flex, -} from '@wordpress/components'; -import { useDispatch } from '@wordpress/data'; -import { useState, useId, memo } from '@wordpress/element'; -import { __, sprintf } from '@wordpress/i18n'; -import { - Icon, - header, - footer, - symbolFilled as uncategorized, - symbol, - moreVertical, - lockSmall, -} from '@wordpress/icons'; -import { store as noticesStore } from '@wordpress/notices'; -import { store as reusableBlocksStore } from '@wordpress/reusable-blocks'; -import { downloadBlob } from '@wordpress/blob'; - -/** - * Internal dependencies - */ -import RenameMenuItem from './rename-menu-item'; -import DuplicateMenuItem from './duplicate-menu-item'; -import { - PATTERN_TYPES, - TEMPLATE_PART_POST_TYPE, - PATTERN_SYNC_TYPES, -} from '../../utils/constants'; -import { store as editSiteStore } from '../../store'; -import { useLink } from '../routes/link'; -import { unlock } from '../../lock-unlock'; - -const { useGlobalStyle } = unlock( blockEditorPrivateApis ); - -const templatePartIcons = { header, footer, uncategorized }; - -function GridItem( { categoryId, item, ...props } ) { - const descriptionId = useId(); - const [ isDeleteDialogOpen, setIsDeleteDialogOpen ] = useState( false ); - const [ backgroundColor ] = useGlobalStyle( 'color.background' ); - - const { removeTemplate } = useDispatch( editSiteStore ); - const { __experimentalDeleteReusableBlock } = - useDispatch( reusableBlocksStore ); - const { createErrorNotice, createSuccessNotice } = - useDispatch( noticesStore ); - - const isUserPattern = item.type === PATTERN_TYPES.user; - const isNonUserPattern = item.type === PATTERN_TYPES.theme; - const isTemplatePart = item.type === TEMPLATE_PART_POST_TYPE; - - const { onClick } = useLink( { - postType: item.type, - postId: isUserPattern ? item.id : item.name, - categoryId, - categoryType: isTemplatePart ? item.type : PATTERN_TYPES.theme, - } ); - - const isEmpty = ! item.blocks?.length; - const patternClassNames = classnames( 'edit-site-patterns__pattern', { - 'is-placeholder': isEmpty, - } ); - const previewClassNames = classnames( 'edit-site-patterns__preview', { - 'is-inactive': isNonUserPattern, - } ); - - const deletePattern = async () => { - try { - await __experimentalDeleteReusableBlock( item.id ); - createSuccessNotice( - sprintf( - // translators: %s: The pattern's title e.g. 'Call to action'. - __( '"%s" deleted.' ), - item.title - ), - { type: 'snackbar', id: 'edit-site-patterns-success' } - ); - } catch ( error ) { - const errorMessage = - error.message && error.code !== 'unknown_error' - ? error.message - : __( 'An error occurred while deleting the pattern.' ); - createErrorNotice( errorMessage, { - type: 'snackbar', - id: 'edit-site-patterns-error', - } ); - } - }; - const deleteItem = () => - isTemplatePart ? removeTemplate( item ) : deletePattern(); - const exportAsJSON = () => { - const json = { - __file: item.type, - title: item.title || item.name, - content: item.patternPost.content.raw, - syncStatus: item.patternPost.wp_pattern_sync_status, - }; - - return downloadBlob( - `${ kebabCase( item.title || item.name ) }.json`, - JSON.stringify( json, null, 2 ), - 'application/json' - ); - }; - - // Only custom patterns or custom template parts can be renamed or deleted. - const isCustomPattern = - isUserPattern || ( isTemplatePart && item.isCustom ); - const hasThemeFile = isTemplatePart && item.templatePart.has_theme_file; - const ariaDescriptions = []; - - if ( isCustomPattern ) { - // User patterns don't have descriptions, but can be edited and deleted, so include some help text. - ariaDescriptions.push( - __( 'Press Enter to edit, or Delete to delete the pattern.' ) - ); - } else if ( item.description ) { - ariaDescriptions.push( item.description ); - } - - if ( isNonUserPattern ) { - ariaDescriptions.push( - __( 'Theme & plugin patterns cannot be edited.' ) - ); - } - - let itemIcon; - if ( ! isUserPattern && templatePartIcons[ categoryId ] ) { - itemIcon = templatePartIcons[ categoryId ]; - } else { - itemIcon = - item.syncStatus === PATTERN_SYNC_TYPES.full ? symbol : undefined; - } - - const confirmButtonText = hasThemeFile ? __( 'Clear' ) : __( 'Delete' ); - const confirmPrompt = hasThemeFile - ? __( 'Are you sure you want to clear these customizations?' ) - : sprintf( - // translators: %s: The pattern or template part's title e.g. 'Call to action'. - __( 'Are you sure you want to delete "%s"?' ), - item.title || item.name - ); - - const additionalStyles = ! backgroundColor - ? [ { css: 'body { background: #fff; }' } ] - : undefined; - - return ( -
  • - - { ariaDescriptions.map( ( ariaDescription, index ) => ( - - ) ) } - - - { itemIcon && ! isNonUserPattern && ( - - - - ) } - - { item.type === PATTERN_TYPES.theme ? ( - item.title - ) : ( - - - - ) } - { item.type === PATTERN_TYPES.theme && ( - - - - ) } - - - - { ( { onClose } ) => ( - - { isCustomPattern && ! hasThemeFile && ( - - ) } - - { item.type === PATTERN_TYPES.user && ( - exportAsJSON() }> - { __( 'Export as JSON' ) } - - ) } - - { isCustomPattern && ( - - setIsDeleteDialogOpen( true ) - } - > - { hasThemeFile - ? __( 'Clear customizations' ) - : __( 'Delete' ) } - - ) } - - ) } - - - - { isDeleteDialogOpen && ( - setIsDeleteDialogOpen( false ) } - > - { confirmPrompt } - - ) } -
  • - ); -} - -export default memo( GridItem ); diff --git a/packages/edit-site/src/components/page-patterns/grid.js b/packages/edit-site/src/components/page-patterns/grid.js deleted file mode 100644 index 59a8cc431567f..0000000000000 --- a/packages/edit-site/src/components/page-patterns/grid.js +++ /dev/null @@ -1,22 +0,0 @@ -/** - * Internal dependencies - */ -import GridItem from './grid-item'; - -export default function Grid( { categoryId, items, ...props } ) { - if ( ! items?.length ) { - return null; - } - - return ( - - ); -} diff --git a/packages/edit-site/src/components/page-patterns/index.js b/packages/edit-site/src/components/page-patterns/index.js index 5564ae0514618..9cf02dbd3e2ab 100644 --- a/packages/edit-site/src/components/page-patterns/index.js +++ b/packages/edit-site/src/components/page-patterns/index.js @@ -391,7 +391,6 @@ export default function DataviewsPatterns() { // Wrap everything in a block editor provider. // This ensures 'styles' that are needed for the previews are synced // from the site editor store to the block editor store. - // TODO: check if I add the provider in every preview like in templates... return ( - { __( 'No patterns found.' ) } - - ); -} diff --git a/packages/edit-site/src/components/page-patterns/patterns-list.js b/packages/edit-site/src/components/page-patterns/patterns-list.js deleted file mode 100644 index 91ca083607674..0000000000000 --- a/packages/edit-site/src/components/page-patterns/patterns-list.js +++ /dev/null @@ -1,229 +0,0 @@ -/** - * WordPress dependencies - */ -import { useState, useDeferredValue, useId, useMemo } from '@wordpress/element'; -import { - SearchControl, - __experimentalVStack as VStack, - Flex, - FlexBlock, - __experimentalToggleGroupControl as ToggleGroupControl, - __experimentalToggleGroupControlOption as ToggleGroupControlOption, - __experimentalHeading as Heading, - __experimentalText as Text, -} from '@wordpress/components'; -import { __, _x, isRTL } from '@wordpress/i18n'; -import { chevronLeft, chevronRight } from '@wordpress/icons'; -import { privateApis as routerPrivateApis } from '@wordpress/router'; -import { - useAsyncList, - useViewportMatch, - useDebouncedInput, -} from '@wordpress/compose'; - -/** - * Internal dependencies - */ -import PatternsHeader from './header'; -import Grid from './grid'; -import NoPatterns from './no-patterns'; -import usePatterns from './use-patterns'; -import SidebarButton from '../sidebar-button'; -import { unlock } from '../../lock-unlock'; -import { PATTERN_SYNC_TYPES, PATTERN_TYPES } from '../../utils/constants'; -import Pagination from '../pagination'; - -const { useLocation, useHistory } = unlock( routerPrivateApis ); - -const SYNC_FILTERS = { - all: _x( 'All', 'Option that shows all patterns' ), - [ PATTERN_SYNC_TYPES.full ]: _x( - 'Synced', - 'Option that shows all synchronized patterns' - ), - [ PATTERN_SYNC_TYPES.unsynced ]: _x( - 'Not synced', - 'Option that shows all patterns that are not synchronized' - ), -}; - -const SYNC_DESCRIPTIONS = { - all: '', - [ PATTERN_SYNC_TYPES.full ]: __( - 'Patterns that are kept in sync across the site.' - ), - [ PATTERN_SYNC_TYPES.unsynced ]: __( - 'Patterns that can be changed freely without affecting the site.' - ), -}; - -const PAGE_SIZE = 20; - -export default function PatternsList( { categoryId, type } ) { - const location = useLocation(); - const history = useHistory(); - const isMobileViewport = useViewportMatch( 'medium', '<' ); - const [ filterValue, setFilterValue, delayedFilterValue ] = - useDebouncedInput( '' ); - const deferredFilterValue = useDeferredValue( delayedFilterValue ); - - const [ syncFilter, setSyncFilter ] = useState( 'all' ); - const [ currentPage, setCurrentPage ] = useState( 1 ); - - const deferredSyncedFilter = useDeferredValue( syncFilter ); - - const isUncategorizedThemePatterns = - type === PATTERN_TYPES.theme && categoryId === 'uncategorized'; - - const { patterns, isResolving } = usePatterns( - type, - isUncategorizedThemePatterns ? '' : categoryId, - { - search: deferredFilterValue, - syncStatus: - deferredSyncedFilter === 'all' - ? undefined - : deferredSyncedFilter, - } - ); - - const updateSearchFilter = ( value ) => { - setCurrentPage( 1 ); - setFilterValue( value ); - }; - - const updateSyncFilter = ( value ) => { - setCurrentPage( 1 ); - setSyncFilter( value ); - }; - - const id = useId(); - const titleId = `${ id }-title`; - const descriptionId = `${ id }-description`; - - const hasPatterns = patterns.length; - const title = SYNC_FILTERS[ syncFilter ]; - const description = SYNC_DESCRIPTIONS[ syncFilter ]; - - const totalItems = patterns.length; - const pageIndex = currentPage - 1; - const numPages = Math.ceil( patterns.length / PAGE_SIZE ); - - const list = useMemo( () => { - return patterns.slice( - pageIndex * PAGE_SIZE, - pageIndex * PAGE_SIZE + PAGE_SIZE - ); - }, [ pageIndex, patterns ] ); - - const asyncList = useAsyncList( list, { step: 10 } ); - - const changePage = ( page ) => { - const scrollContainer = document.querySelector( '.edit-site-patterns' ); - scrollContainer?.scrollTo( 0, 0 ); - - setCurrentPage( page ); - }; - - return ( - <> - - - - { isMobileViewport && ( - { - // Go back in history if we came from the Patterns page. - // Otherwise push a stack onto the history. - if ( - location.state?.backPath === '/patterns' - ) { - history.back(); - } else { - history.push( { path: '/patterns' } ); - } - } } - /> - ) } - - - updateSearchFilter( value ) - } - placeholder={ __( 'Search patterns' ) } - label={ __( 'Search patterns' ) } - value={ filterValue } - __nextHasNoMarginBottom - /> - - { type === PATTERN_TYPES.theme && ( - updateSyncFilter( value ) } - __nextHasNoMarginBottom - > - { Object.entries( SYNC_FILTERS ).map( - ( [ key, label ] ) => ( - - ) - ) } - - ) } - - - - { syncFilter !== 'all' && ( - - - { title } - - { description ? ( - - { description } - - ) : null } - - ) } - { hasPatterns && ( - - ) } - { ! isResolving && ! hasPatterns && } - - { numPages > 1 && ( - - ) } - - ); -} diff --git a/packages/edit-site/src/components/page-patterns/rename-menu-item.js b/packages/edit-site/src/components/page-patterns/rename-menu-item.js deleted file mode 100644 index c2b3b960fb667..0000000000000 --- a/packages/edit-site/src/components/page-patterns/rename-menu-item.js +++ /dev/null @@ -1,132 +0,0 @@ -/** - * WordPress dependencies - */ -import { - Button, - MenuItem, - Modal, - TextControl, - __experimentalHStack as HStack, - __experimentalVStack as VStack, -} from '@wordpress/components'; -import { store as coreStore } from '@wordpress/core-data'; -import { useDispatch } from '@wordpress/data'; -import { useState } from '@wordpress/element'; -import { __ } from '@wordpress/i18n'; -import { store as noticesStore } from '@wordpress/notices'; - -/** - * Internal dependencies - */ -import { TEMPLATE_PART_POST_TYPE } from '../../utils/constants'; - -export default function RenameMenuItem( { item, onClose } ) { - const [ title, setTitle ] = useState( () => item.title ); - const [ isModalOpen, setIsModalOpen ] = useState( false ); - - const { editEntityRecord, saveEditedEntityRecord } = - useDispatch( coreStore ); - const { createSuccessNotice, createErrorNotice } = - useDispatch( noticesStore ); - - if ( item.type === TEMPLATE_PART_POST_TYPE && ! item.isCustom ) { - return null; - } - - async function onRename( event ) { - event.preventDefault(); - - try { - await editEntityRecord( 'postType', item.type, item.id, { title } ); - - // Update state before saving rerenders the list. - setTitle( '' ); - setIsModalOpen( false ); - onClose(); - - // Persist edited entity. - await saveEditedEntityRecord( 'postType', item.type, item.id, { - throwOnError: true, - } ); - - createSuccessNotice( - item.type === TEMPLATE_PART_POST_TYPE - ? __( 'Template part renamed.' ) - : __( 'Pattern renamed.' ), - { - type: 'snackbar', - } - ); - } catch ( error ) { - const fallbackErrorMessage = - item.type === TEMPLATE_PART_POST_TYPE - ? __( - 'An error occurred while renaming the template part.' - ) - : __( 'An error occurred while renaming the pattern.' ); - const errorMessage = - error.message && error.code !== 'unknown_error' - ? error.message - : fallbackErrorMessage; - - createErrorNotice( errorMessage, { type: 'snackbar' } ); - } - } - - return ( - <> - { - setIsModalOpen( true ); - setTitle( item.title ); - } } - > - { __( 'Rename' ) } - - { isModalOpen && ( - { - setIsModalOpen( false ); - onClose(); - } } - overlayClassName="edit-site-list__rename-modal" - > -
    - - - - - - - - - -
    -
    - ) } - - ); -} diff --git a/packages/edit-site/src/components/page-patterns/style.scss b/packages/edit-site/src/components/page-patterns/style.scss index e5bd44956b262..89f38e7d4c9a6 100644 --- a/packages/edit-site/src/components/page-patterns/style.scss +++ b/packages/edit-site/src/components/page-patterns/style.scss @@ -1,70 +1,3 @@ -.edit-site-patterns { - background: #1e1e1e; - border-left: 1px solid $gray-800; - margin: $header-height 0 0; - border-radius: 0; - padding: 0; - overflow-x: auto; - min-height: 100%; - - .components-base-control { - width: 100%; - @include break-medium { - width: auto; - } - } - - .components-text { - color: $gray-600; - } - - .components-heading { - color: $gray-200; - } - - @include break-medium { - margin: 0; - } - - .edit-site-patterns__search-block { - min-width: fit-content; - flex-grow: 1; - } - - // TODO: Consider using the Theme component to automatically adapt to a dark background. - .edit-site-patterns__search { - --wp-components-color-foreground: #{$gray-200}; - - .components-input-control__container { - background: $gray-800; - } - - svg { - fill: $gray-600; - } - } - - .edit-site-patterns__sync-status-filter { - background: $gray-800; - border: none; - height: $button-size-next-default-40px; - min-width: max-content; - width: 100%; - max-width: 100%; - - @include break-medium { - width: 300px; - } - } - .edit-site-patterns__sync-status-filter-option:not([aria-checked="true"]) { - color: $gray-600; - } - .edit-site-patterns__sync-status-filter-option:active { - background: $gray-700; - color: $gray-100; - } -} - .edit-site-patterns__header { position: sticky; top: 0; @@ -77,97 +10,13 @@ } } -.edit-site-patterns__section { - padding: $grid-unit-30 $grid-unit-40; - flex: 1; -} - .edit-site-patterns__section-header { .screen-reader-shortcut:focus { top: 0; } } -.edit-site-patterns__grid { - display: grid; - grid-template-columns: 1fr; - gap: $grid-unit-40; - margin-top: 0; - margin-bottom: 0; - @include break-large { - grid-template-columns: 1fr 1fr; - } - @include break-huge { - grid-template-columns: 1fr 1fr 1fr; - } - @include break-xhuge { - grid-template-columns: 1fr 1fr 1fr 1fr; - } - .edit-site-patterns__pattern { - break-inside: avoid-column; - display: flex; - flex-direction: column; - .edit-site-patterns__preview { - box-shadow: none; - border: none; - padding: 0; - background-color: unset; - box-sizing: border-box; - border-radius: 4px; - cursor: pointer; - overflow: hidden; - - &:focus { - box-shadow: inset 0 0 0 0 $white, 0 0 0 2px var(--wp-admin-theme-color); - // Windows High Contrast mode will show this outline, but not the box-shadow. - outline: 2px solid transparent; - } - - &.is-inactive { - cursor: default; - } - &.is-inactive:focus { - box-shadow: 0 0 0 var(--wp-admin-border-width-focus) $gray-800; - opacity: 0.8; - } - } - - .edit-site-patterns__footer, - .edit-site-patterns__button { - color: $gray-600; - } - - .edit-site-patterns__dropdown { - flex-shrink: 0; - } - - &.is-placeholder .edit-site-patterns__preview { - min-height: $grid-unit-80; - color: $gray-600; - border: 1px dashed $gray-800; - display: flex; - align-items: center; - justify-content: center; - - &:focus { - box-shadow: 0 0 0 var(--wp-admin-border-width-focus) var(--wp-admin-theme-color); - } - } - } - - .edit-site-patterns__preview { - flex: 0 1 auto; - margin-bottom: $grid-unit-15; - } -} - -.edit-site-patterns__load-more { - align-self: center; -} - .edit-site-patterns__pattern-title { - color: $gray-200; - .is-link { text-decoration: none; color: $gray-200; @@ -189,41 +38,10 @@ } } -.edit-site-patterns__no-results { - color: $gray-600; -} - .edit-site-patterns__delete-modal { width: $modal-width-small; } -.edit-site-patterns__pagination { - padding: $grid-unit-30 $grid-unit-40; - border-top: 1px solid $gray-800; - background: $gray-900; - position: sticky; - bottom: 0; - color: $gray-100; - z-index: z-index(".edit-site-patterns__grid-pagination"); - .components-button.is-tertiary { - background-color: $gray-800; - color: $gray-100; - - &:disabled { - color: $gray-600; - background: none; - } - - &:hover:not(:disabled) { - background-color: $gray-700; - } - } -} - -/** - * DataViews patterns styles. - * TODO: when this becomes stable, consolidate styles with the above. - */ .edit-site-page-patterns-dataviews { .page-patterns-preview-field { display: flex;