Skip to content

Commit

Permalink
Site Editor: Add "Added by" description to template part navigation s…
Browse files Browse the repository at this point in the history
…idebar (#48732)

* Add Added by description to template navigation sidebar

* Add customized string

* Rename to AddedByText

* Code review

* Keep both descriptions

* Concat descriptions

* Refactor to postType and postId

* Modify into a local hook

* Design tweaks

* Style adjustments

- increase space between icon / label
- apply rounded corners to gravatar
- colorise fallback icon

* Don't show added by active theme

---------

Co-authored-by: James Koster <james@jameskoster.co.uk>
  • Loading branch information
kevin940726 and jameskoster authored Mar 15, 2023
1 parent dd9bc7b commit 213bc3f
Show file tree
Hide file tree
Showing 5 changed files with 245 additions and 156 deletions.
284 changes: 144 additions & 140 deletions packages/edit-site/src/components/list/added-by.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
// @ts-check
/**
* External dependencies
*/
Expand All @@ -6,7 +7,7 @@ import classnames from 'classnames';
/**
* WordPress dependencies
*/
import { __experimentalHStack as HStack, Icon } from '@wordpress/components';
import { Icon, __experimentalHStack as HStack } from '@wordpress/components';
import { store as coreStore } from '@wordpress/core-data';
import { useSelect } from '@wordpress/data';
import { useState } from '@wordpress/element';
Expand All @@ -18,25 +19,154 @@ import {
} from '@wordpress/icons';
import { _x } from '@wordpress/i18n';

/** @typedef {'wp_template'|'wp_template_part'} TemplateType */

/** @type {TemplateType} */
const TEMPLATE_POST_TYPE_NAMES = [ 'wp_template', 'wp_template_part' ];

function BaseAddedBy( { text, icon, imageUrl, isCustomized, templateType } ) {
/**
* @typedef {'theme'|'plugin'|'site'|'user'} AddedByType
*
* @typedef AddedByData
* @type {Object}
* @property {AddedByType} type The type of the data.
* @property {JSX.Element} icon The icon to display.
* @property {string} [imageUrl] The optional image URL to display.
* @property {string} [text] The text to display.
* @property {boolean} isCustomized Whether the template has been customized.
*
* @param {TemplateType} postType The template post type.
* @param {number} postId The template post id.
* @return {AddedByData} The added by object or null.
*/
export function useAddedBy( postType, postId ) {
return useSelect(
( select ) => {
const {
getTheme,
getPlugin,
getEntityRecord,
getMedia,
getUser,
getEditedEntityRecord,
} = select( coreStore );
const template = getEditedEntityRecord(
'postType',
postType,
postId
);

if ( TEMPLATE_POST_TYPE_NAMES.includes( template.type ) ) {
// Added by theme.
// Template originally provided by a theme, but customized by a user.
// Templates originally didn't have the 'origin' field so identify
// older customized templates by checking for no origin and a 'theme'
// or 'custom' source.
if (
template.has_theme_file &&
( template.origin === 'theme' ||
( ! template.origin &&
[ 'theme', 'custom' ].includes(
template.source
) ) )
) {
return {
type: 'theme',
icon: themeIcon,
text:
getTheme( template.theme )?.name?.rendered ||
template.theme,
isCustomized: template.source === 'custom',
};
}

// Added by plugin.
if ( template.has_theme_file && template.origin === 'plugin' ) {
return {
type: 'plugin',
icon: pluginIcon,
text:
getPlugin( template.theme )?.name || template.theme,
isCustomized: template.source === 'custom',
};
}

// Added by site.
// Template was created from scratch, but has no author. Author support
// was only added to templates in WordPress 5.9. Fallback to showing the
// site logo and title.
if (
! template.has_theme_file &&
template.source === 'custom' &&
! template.author
) {
const siteData = getEntityRecord(
'root',
'__unstableBase'
);
return {
type: 'site',
icon: globeIcon,
imageUrl: siteData?.site_logo
? getMedia( siteData.site_logo )?.source_url
: undefined,
text: siteData?.name,
isCustomized: false,
};
}
}

// Added by user.
const user = getUser( template.author );
return {
type: 'user',
icon: authorIcon,
imageUrl: user?.avatar_urls?.[ 48 ],
text: user?.nickname,
isCustomized: false,
};
},
[ postType, postId ]
);
}

/**
* @param {Object} props
* @param {string} props.imageUrl
*/
function AvatarImage( { imageUrl } ) {
const [ isImageLoaded, setIsImageLoaded ] = useState( false );

return (
<div
className={ classnames( 'edit-site-list-added-by__avatar', {
'is-loaded': isImageLoaded,
} ) }
>
<img
onLoad={ () => setIsImageLoaded( true ) }
alt=""
src={ imageUrl }
/>
</div>
);
}

/**
* @param {Object} props
* @param {TemplateType} props.postType The template post type.
* @param {number} props.postId The template post id.
*/
export default function AddedBy( { postType, postId } ) {
const { text, icon, imageUrl, isCustomized } = useAddedBy(
postType,
postId
);

return (
<HStack alignment="left">
{ imageUrl ? (
<div
className={ classnames( 'edit-site-list-added-by__avatar', {
'is-loaded': isImageLoaded,
} ) }
>
<img
onLoad={ () => setIsImageLoaded( true ) }
alt=""
src={ imageUrl }
/>
</div>
<AvatarImage imageUrl={ imageUrl } />
) : (
<div className="edit-site-list-added-by__icon">
<Icon icon={ icon } />
Expand All @@ -46,7 +176,7 @@ function BaseAddedBy( { text, icon, imageUrl, isCustomized, templateType } ) {
{ text }
{ isCustomized && (
<span className="edit-site-list-added-by__customized-info">
{ templateType === 'wp_template'
{ postType === 'wp_template'
? _x( 'Customized', 'template' )
: _x( 'Customized', 'template part' ) }
</span>
Expand All @@ -55,129 +185,3 @@ function BaseAddedBy( { text, icon, imageUrl, isCustomized, templateType } ) {
</HStack>
);
}

function AddedByTheme( { slug, isCustomized, templateType } ) {
const theme = useSelect(
( select ) => select( coreStore ).getTheme( slug ),
[ slug ]
);

return (
<BaseAddedBy
icon={ themeIcon }
text={ theme?.name?.rendered || slug }
isCustomized={ isCustomized }
templateType={ templateType }
/>
);
}

function AddedByPlugin( { slug, isCustomized, templateType } ) {
const plugin = useSelect(
( select ) => select( coreStore ).getPlugin( slug ),
[ slug ]
);

return (
<BaseAddedBy
icon={ pluginIcon }
text={ plugin?.name || slug }
isCustomized={ isCustomized }
templateType={ templateType }
/>
);
}

function AddedByAuthor( { id, templateType } ) {
const user = useSelect(
( select ) => select( coreStore ).getUser( id ),
[ id ]
);

return (
<BaseAddedBy
icon={ authorIcon }
imageUrl={ user?.avatar_urls?.[ 48 ] }
text={ user?.nickname }
templateType={ templateType }
/>
);
}

function AddedBySite( { templateType } ) {
const { name, logoURL } = useSelect( ( select ) => {
const { getEntityRecord, getMedia } = select( coreStore );
const siteData = getEntityRecord( 'root', '__unstableBase' );

return {
name: siteData?.name,
logoURL: siteData?.site_logo
? getMedia( siteData.site_logo )?.source_url
: undefined,
};
}, [] );

return (
<BaseAddedBy
icon={ globeIcon }
imageUrl={ logoURL }
text={ name }
templateType={ templateType }
/>
);
}

export default function AddedBy( { templateType, template } ) {
if ( ! template ) {
return;
}

if ( TEMPLATE_POST_TYPE_NAMES.includes( templateType ) ) {
// Template originally provided by a theme, but customized by a user.
// Templates originally didn't have the 'origin' field so identify
// older customized templates by checking for no origin and a 'theme'
// or 'custom' source.
if (
template.has_theme_file &&
( template.origin === 'theme' ||
( ! template.origin &&
[ 'theme', 'custom' ].includes( template.source ) ) )
) {
return (
<AddedByTheme
slug={ template.theme }
isCustomized={ template.source === 'custom' }
templateType={ templateType }
/>
);
}

// Template originally provided by a plugin, but customized by a user.
if ( template.has_theme_file && template.origin === 'plugin' ) {
return (
<AddedByPlugin
slug={ template.theme }
isCustomized={ template.source === 'custom' }
templateType={ templateType }
/>
);
}

// Template was created from scratch, but has no author. Author support
// was only added to templates in WordPress 5.9. Fallback to showing the
// site logo and title.
if (
! template.has_theme_file &&
template.source === 'custom' &&
! template.author
) {
return <AddedBySite templateType={ templateType } />;
}
}

// Simply show the author for templates created from scratch that have an
// author or for any other post type.
return (
<AddedByAuthor id={ template.author } templateType={ templateType } />
);
}
10 changes: 6 additions & 4 deletions packages/edit-site/src/components/list/table.js
Original file line number Diff line number Diff line change
Expand Up @@ -102,10 +102,12 @@ export default function Table( { templateType } ) {
</td>

<td className="edit-site-list-table-column" role="cell">
<AddedBy
templateType={ templateType }
template={ template }
/>
{ template ? (
<AddedBy
postType={ template.type }
postId={ template.id }
/>
) : null }
</td>
<td className="edit-site-list-table-column" role="cell">
<Actions template={ template } />
Expand Down
Loading

1 comment on commit 213bc3f

@github-actions
Copy link

Choose a reason for hiding this comment

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

Flaky tests detected in 213bc3f.
Some tests passed with failed attempts. The failures may not be related to this commit but are still reported for visibility. See the documentation for more information.

🔍 Workflow run URL: https://github.com/WordPress/gutenberg/actions/runs/4422326553
📝 Reported issues:

Please sign in to comment.