Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add success and error snackbars to the templates list page #36808

Merged
merged 12 commits into from
Nov 26, 2021
1 change: 1 addition & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions packages/edit-site/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@
"@wordpress/primitives": "file:../primitives",
"@wordpress/reusable-blocks": "file:../reusable-blocks",
"@wordpress/url": "file:../url",
"@wordpress/viewport": "file:../viewport",
"classnames": "^2.3.1",
"downloadjs": "^1.4.7",
"file-saver": "^2.0.2",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,12 @@ import { kebabCase } from 'lodash';
* WordPress dependencies
*/
import { useState } from '@wordpress/element';
import { useDispatch } from '@wordpress/data';
import { Button } from '@wordpress/components';
import { addQueryArgs } from '@wordpress/url';
import apiFetch from '@wordpress/api-fetch';
import { __ } from '@wordpress/i18n';
import { store as noticesStore } from '@wordpress/notices';

/**
* Internal dependencies
Expand All @@ -18,31 +21,43 @@ import CreateTemplatePartModal from '../create-template-part-modal';

export default function NewTemplatePart( { postType } ) {
const [ isModalOpen, setIsModalOpen ] = useState( false );
const { createErrorNotice } = useDispatch( noticesStore );

async function createTemplatePart( { title, area } ) {
if ( ! title ) {
createErrorNotice( __( 'Title is not defined.' ), {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How would this happen? 🙂 I guess it's okay to leave it just in case.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, it will never happen for regular users. Maybe we should just remove this check altogether.

type: 'snackbar',
} );
return;
}

const templatePart = await apiFetch( {
path: '/wp/v2/template-parts',
method: 'POST',
data: {
slug: kebabCase( title ),
title,
content: '',
area,
},
} );
try {
const templatePart = await apiFetch( {
path: '/wp/v2/template-parts',
method: 'POST',
data: {
slug: kebabCase( title ),
title,
content: '',
area,
},
} );

// Navigate to the created template part editor.
window.location.href = addQueryArgs( window.location.href, {
postId: templatePart.id,
postType: 'wp_template_part',
} );
// Navigate to the created template part editor.
window.location.href = addQueryArgs( window.location.href, {
postId: templatePart.id,
postType: 'wp_template_part',
} );
} catch ( error ) {
const errorMessage =
error.message && error.code !== 'unknown_error'
? error.message
: __(
'An error occurred while creating the template part.'
);

// Wait for async navigation to happen before closing the modal.
await new Promise( () => {} );
createErrorNotice( errorMessage, { type: 'snackbar' } );
}
}

return (
Expand Down
55 changes: 37 additions & 18 deletions packages/edit-site/src/components/add-new-template/new-template.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,13 @@ import {
MenuItem,
NavigableMenu,
} from '@wordpress/components';
import { useSelect } from '@wordpress/data';
import { useSelect, useDispatch } from '@wordpress/data';
import { store as coreStore } from '@wordpress/core-data';
import { store as editorStore } from '@wordpress/editor';
import { addQueryArgs } from '@wordpress/url';
import apiFetch from '@wordpress/api-fetch';
import { __ } from '@wordpress/i18n';
import { store as noticesStore } from '@wordpress/notices';

const DEFAULT_TEMPLATE_SLUGS = [
'front-page',
Expand All @@ -42,27 +44,44 @@ export default function NewTemplate( { postType } ) {
} ),
[]
);
const { createErrorNotice } = useDispatch( noticesStore );

async function createTemplate( { slug } ) {
const { title, description } = find( defaultTemplateTypes, { slug } );
try {
const { title, description } = find( defaultTemplateTypes, {
slug,
} );

const template = await apiFetch( {
path: '/wp/v2/templates',
method: 'POST',
data: {
excerpt: description,
// Slugs need to be strings, so this is for template `404`
slug: slug.toString(),
status: 'publish',
title,
},
} );
const template = await apiFetch( {
path: '/wp/v2/templates',
method: 'POST',
data: {
excerpt: description,
// Slugs need to be strings, so this is for template `404`
slug: slug.toString(),
status: 'publish',
title,
},
} );

// Navigate to the created template editor.
window.location.href = addQueryArgs( window.location.href, {
postId: template.id,
postType: 'wp_template',
} );
// Navigate to the created template editor.
window.location.href = addQueryArgs( window.location.href, {
postId: template.id,
postType: 'wp_template',
} );

// Wait for async navigation to happen before closing the modal.
await new Promise( () => {} );
} catch ( error ) {
const errorMessage =
error.message && error.code !== 'unknown_error'
? error.message
: __( 'An error occurred while creating the template.' );

createErrorNotice( errorMessage, {
type: 'snackbar',
} );
}
}

const existingTemplateSlugs = map( templates, 'slug' );
Expand Down
38 changes: 27 additions & 11 deletions packages/edit-site/src/components/list/index.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
/**
* External dependencies
*/
import classnames from 'classnames';

/**
* WordPress dependencies
*/
Expand All @@ -6,6 +11,7 @@ import { useSelect } from '@wordpress/data';
import { InterfaceSkeleton } from '@wordpress/interface';
import { __, sprintf } from '@wordpress/i18n';
import { store as keyboardShortcutsStore } from '@wordpress/keyboard-shortcuts';
import { EditorSnackbars } from '@wordpress/editor';

/**
* Internal dependencies
Expand All @@ -14,20 +20,27 @@ import useRegisterShortcuts from './use-register-shortcuts';
import Header from './header';
import NavigationSidebar from '../navigation-sidebar';
import Table from './table';
import { store as editSiteStore } from '../../store';

export default function List( { templateType } ) {
useRegisterShortcuts();

const { previousShortcut, nextShortcut } = useSelect( ( select ) => {
return {
previousShortcut: select(
keyboardShortcutsStore
).getAllShortcutKeyCombinations( 'core/edit-site/previous-region' ),
nextShortcut: select(
keyboardShortcutsStore
).getAllShortcutKeyCombinations( 'core/edit-site/next-region' ),
};
}, [] );
const { previousShortcut, nextShortcut, isNavigationOpen } = useSelect(
( select ) => {
return {
previousShortcut: select(
keyboardShortcutsStore
).getAllShortcutKeyCombinations(
'core/edit-site/previous-region'
),
nextShortcut: select(
keyboardShortcutsStore
).getAllShortcutKeyCombinations( 'core/edit-site/next-region' ),
isNavigationOpen: select( editSiteStore ).isNavigationOpened(),
};
},
[]
);

const postType = useSelect(
( select ) => select( coreStore ).getPostType( templateType ),
Expand All @@ -54,7 +67,9 @@ export default function List( { templateType } ) {

return (
<InterfaceSkeleton
className="edit-site-list"
className={ classnames( 'edit-site-list', {
'is-navigation-open': isNavigationOpen,
} ) }
labels={ {
drawer: __( 'Navigation Sidebar' ),
...detailedRegionLabels,
Expand All @@ -66,6 +81,7 @@ export default function List( { templateType } ) {
isDefaultOpen
/>
}
notices={ <EditorSnackbars /> }
content={
<main className="edit-site-list-main">
<Table templateType={ templateType } />
Expand Down
6 changes: 6 additions & 0 deletions packages/edit-site/src/components/list/style.scss
Original file line number Diff line number Diff line change
Expand Up @@ -114,3 +114,9 @@
border-top: none;
}
}

.edit-site-list.is-navigation-open .components-snackbar-list {
@include break-medium() {
margin-left: $nav-sidebar-width;
}
}
25 changes: 23 additions & 2 deletions packages/edit-site/src/components/list/table.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
} from '@wordpress/components';
import { moreVertical } from '@wordpress/icons';
import { addQueryArgs } from '@wordpress/url';
import { store as noticesStore } from '@wordpress/notices';

/**
* Internal dependencies
Expand All @@ -23,6 +24,9 @@ import isTemplateRevertable from '../../utils/is-template-revertable';
function Actions( { template } ) {
const { removeTemplate, revertTemplate } = useDispatch( editSiteStore );
const { saveEditedEntityRecord } = useDispatch( coreStore );
const { createSuccessNotice, createErrorNotice } = useDispatch(
noticesStore
);

const isRemovable = isTemplateRemovable( template );
const isRevertable = isTemplateRevertable( template );
Expand All @@ -32,8 +36,25 @@ function Actions( { template } ) {
}

async function revertAndSaveTemplate() {
await revertTemplate( template, { allowUndo: false } );
await saveEditedEntityRecord( 'postType', template.type, template.id );
try {
await revertTemplate( template, { allowUndo: false } );
await saveEditedEntityRecord(
'postType',
template.type,
template.id
);

createSuccessNotice( __( 'Template reverted.' ), {
type: 'snackbar',
} );
} catch ( error ) {
const errorMessage =
error.message && error.code !== 'unknown_error'
? error.message
: __( 'An error occurred while reverting the template.' );

createErrorNotice( errorMessage, { type: 'snackbar' } );
}
}

return (
Expand Down
25 changes: 9 additions & 16 deletions packages/edit-site/src/components/navigation-sidebar/index.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
/**
* WordPress dependencies
*/
import { useEffect, useState } from '@wordpress/element';
import { useEffect } from '@wordpress/element';
import { createSlotFill } from '@wordpress/components';
import { useViewportMatch } from '@wordpress/compose';
import { useDispatch } from '@wordpress/data';

/**
* Internal dependencies
*/
import { store as editSiteStore } from '../../store';
import NavigationPanel from './navigation-panel';
import NavigationToggle from './navigation-toggle';

Expand All @@ -21,33 +23,24 @@ export default function NavigationSidebar( {
activeTemplateType,
} ) {
const isDesktopViewport = useViewportMatch( 'medium' );
const [ isNavigationOpen, setIsNavigationOpen ] = useState(
isDefaultOpen && isDesktopViewport
);
const { setIsNavigationPanelOpened } = useDispatch( editSiteStore );

useEffect( () => {
// When transitioning to desktop open the navigation if `isDefaultOpen` is true.
if ( isDefaultOpen && isDesktopViewport ) {
setIsNavigationOpen( true );
setIsNavigationPanelOpened( true );
}

// When transitioning to mobile/tablet, close the navigation.
if ( ! isDesktopViewport ) {
setIsNavigationOpen( false );
setIsNavigationPanelOpened( false );
}
}, [ isDefaultOpen, isDesktopViewport ] );
}, [ isDefaultOpen, isDesktopViewport, setIsNavigationPanelOpened ] );

return (
<>
<NavigationToggle
isOpen={ isNavigationOpen }
setIsOpen={ setIsNavigationOpen }
/>
<NavigationPanel
isOpen={ isNavigationOpen }
setIsOpen={ setIsNavigationOpen }
activeItem={ activeTemplateType }
/>
<NavigationToggle />
<NavigationPanel activeItem={ activeTemplateType } />
<NavigationPanelPreviewSlot />
</>
);
Expand Down
Loading