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

Show theme, plugin or author in Added By column with appropriate icon or avatar #36763

Merged
merged 16 commits into from Nov 29, 2021
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
8 changes: 8 additions & 0 deletions packages/core-data/src/entities.js
Expand Up @@ -154,6 +154,14 @@ export const defaultEntities = [
baseURLParams: { context: 'edit' },
key: 'stylesheet',
},
{
label: __( 'Plugins' ),
name: 'plugin',
kind: 'root',
baseURL: '/wp/v2/plugins',
baseURLParams: { context: 'edit' },
key: 'plugin',
},
talldan marked this conversation as resolved.
Show resolved Hide resolved
];

export const kinds = [
Expand Down
184 changes: 184 additions & 0 deletions packages/edit-site/src/components/list/added-by.js
@@ -0,0 +1,184 @@
/**
* External dependencies
*/
import classnames from 'classnames';

/**
* WordPress dependencies
*/
import {
__experimentalHStack as HStack,
Icon,
Tooltip,
} from '@wordpress/components';
import { useRefEffect } from '@wordpress/compose';
import { store as coreStore } from '@wordpress/core-data';
import { useSelect } from '@wordpress/data';
import { useState } from '@wordpress/element';
import { layout as themeIcon, plugins as pluginIcon } from '@wordpress/icons';
import { __ } from '@wordpress/i18n';

const TEMPLATE_POST_TYPE_NAMES = [ 'wp_template', 'wp_template_part' ];

function useOnImageLoads( onLoad ) {
return useRefEffect( ( node ) => {
node.addEventListener( 'load', onLoad );

return () => {
node.removeEventListener( 'load', onLoad );
};
}, [] );
Copy link
Member

Choose a reason for hiding this comment

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

onLoad should be in the dependencies. I'm not sure why we need this? Couldn't we just use onLoad attribute on <img>?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good point! I'll update it.

}

function CustomizedTooltip( { isCustomized, children } ) {
if ( ! isCustomized ) {
return children;
}

return (
<Tooltip text={ __( 'This template has been customized' ) }>
{ children }
</Tooltip>
);
}

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

return (
<HStack alignment="left">
<CustomizedTooltip isCustomized={ isCustomized }>
<div
className={ classnames( 'edit-site-list-added-by__icon', {
'is-customized': isCustomized,
} ) }
>
<Icon icon={ themeIcon } />
</div>
</CustomizedTooltip>
<span>{ theme?.name?.rendered || slug }</span>
</HStack>
);
}

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

return (
<HStack alignment="left">
<CustomizedTooltip isCustomized={ isCustomized }>
<div className="edit-site-list-added-by__icon">
<Icon icon={ pluginIcon } />
</div>
</CustomizedTooltip>
<span>{ plugin?.name || slug }</span>
</HStack>
);
}

function AddedByAuthor( { id } ) {
const user = useSelect( ( select ) => select( coreStore ).getUser( id ), [
id,
] );
const [ isImageLoaded, setIsImageLoaded ] = useState( false );
const ref = useOnImageLoads( () => setIsImageLoaded( true ) );

return (
<HStack alignment="left">
<div
className={ classnames( 'edit-site-list-added-by__avatar', {
'is-loaded': isImageLoaded,
} ) }
>
<img ref={ ref } alt="" src={ user?.avatar_urls[ 48 ] } />
</div>
<span>{ user?.nickname }</span>
</HStack>
);
}

function AddedBySite() {
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,
};
}, [] );
const [ isImageLoaded, setIsImageLoaded ] = useState( false );
const ref = useOnImageLoads( () => setIsImageLoaded( true ) );

return (
<HStack alignment="left">
<div
className={ classnames( 'edit-site-list-added-by__avatar', {
'is-loaded': isImageLoaded,
} ) }
>
<img ref={ ref } alt="" src={ logoURL } />
</div>
<span>{ name }</span>
</HStack>
);
}

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' }
/>
);
}

// 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' }
/>
);
}

// 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 />;
}
}

// Simply show the author for templates created from scratch that have an
// author or for any other post type.
return <AddedByAuthor id={ template.author } />;
}
52 changes: 52 additions & 0 deletions packages/edit-site/src/components/list/style.scss
Expand Up @@ -154,3 +154,55 @@
margin-right: $grid-unit-10;
}
}

.edit-site-list-added-by__icon {
display: flex;
flex-shrink: 0;
position: relative;
align-items: center;
justify-content: center;
width: $grid-unit-40;
height: $grid-unit-40;
background: $gray-800;
border-radius: 100%;

svg {
fill: $white;
}

&.is-customized::after {
position: absolute;
content: "";
background: var(--wp-admin-theme-color);
height: $grid-unit-10;
width: $grid-unit-10;
outline: 2px solid $white;
border-radius: 100%;
top: -1px;
right: -1px;
}
}

.edit-site-list-added-by__avatar {
flex-shrink: 0;
overflow: hidden;
border-radius: 100%;
background: $gray-800;
width: $grid-unit-40;
height: $grid-unit-40;

img {
width: $grid-unit-40;
height: $grid-unit-40;
object-fit: cover;
opacity: 0;
transition: opacity 0.1s linear;
@include reduce-motion("transition");
}

&.is-loaded {
img {
opacity: 1;
}
}
}
6 changes: 5 additions & 1 deletion packages/edit-site/src/components/list/table.js
Expand Up @@ -14,6 +14,7 @@ import { addQueryArgs } from '@wordpress/url';
* Internal dependencies
*/
import Actions from './actions';
import AddedBy from './added-by';

export default function Table( { templateType } ) {
const { templates, isLoading, postType } = useSelect(
Expand Down Expand Up @@ -104,7 +105,10 @@ export default function Table( { templateType } ) {
</td>

<td className="edit-site-list-table-column" role="cell">
{ template.theme }
<AddedBy
templateType={ templateType }
template={ template }
/>
</td>
<td className="edit-site-list-table-column" role="cell">
<Actions template={ template } />
Expand Down