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

Site Editor: Add "Added by" description to template part navigation sidebar #48732

Merged
merged 11 commits into from
Mar 15, 2023
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 @@ -101,10 +101,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