Skip to content

Commit

Permalink
Plugin Management: Implement multi-site(large screen) view for the pl…
Browse files Browse the repository at this point in the history
…ugin management (#66219)

* Implement large screen view for multi-site plugins page

* Redirect to plugin details when clicked on sites count

* Update types

* Minor code refactor

* Fix all sites count

* Update page subtitle and link the plugin name to details page

* Append search term when switching tabs

* Update nav tab for mobile view
  • Loading branch information
yashwin committed Aug 8, 2022
1 parent 5fa0a94 commit b8be9a1
Show file tree
Hide file tree
Showing 18 changed files with 690 additions and 82 deletions.
1 change: 1 addition & 0 deletions client/components/section-nav/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ class SectionNav extends Component {

className = classNames( 'section-nav', this.props.className, {
'is-open': this.state.mobileOpen,
'section-nav-updated': this.props.applyUpdatedStyles,
'has-pinned-items': this.hasPinnedSearch || this.props.hasPinnedItems,
} );

Expand Down
27 changes: 27 additions & 0 deletions client/components/section-nav/item.scss
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
@import '@wordpress/base-styles/breakpoints';
@import '@wordpress/base-styles/mixins';

.section-nav-tab .count {
margin-left: 8px;
}
Expand Down Expand Up @@ -129,3 +132,27 @@
width: auto;
}
}
.section-nav-updated.section-nav {
.is-selected.section-nav-tab .section-nav-tab__link .count {
color: var( --color-primary );
}
.section-nav__mobile-header {
.count {
margin-inline-start: 8px;
}
.gridicon {
margin-inline-start: auto;
}
}
.count {
background: var( --color-primary-5 );
border-radius: 3px; // stylelint-disable-line scales/radii
border: none;
font-weight: normal;
}
margin-bottom: 0;
@include break-large() {
background: var( --color-primary-0 );
box-shadow: none;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,7 @@ export default function SitesOverview(): ReactElement {
</div>
</div>
<SectionNav
applyUpdatedStyles
selectedText={
<span>
{ selectedItem.label }
Expand All @@ -148,7 +149,6 @@ export default function SitesOverview(): ReactElement {
}
selectedCount={ selectedItem.count }
className={ classNames(
'sites-overview__section-nav',
isMobile &&
highlightTab &&
selectedItem.key === 'favorites' &&
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -254,30 +254,6 @@
.sites-overview__status-warning {
border-color: var( --studio-yellow-20 );
}
.sites-overview__section-nav.section-nav {
.is-selected.section-nav-tab .section-nav-tab__link .count {
color: var( --color-primary );
}
.section-nav__mobile-header {
.count {
margin-inline-start: 8px;
}
.gridicon {
margin-inline-start: auto;
}
}
.count {
background: var( --color-primary-5 );
border-radius: 3px; // stylelint-disable-line scales/radii
border: none;
font-weight: normal;
}
margin-bottom: 0;
@include break-large() {
background: unset;
box-shadow: none;
}
}
@keyframes highlight-tab-animation {
0% {
background: var( --color-neutral-70 );
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
.plugins-overview__container {
padding: 0 16px;
padding: 6px 0;
}
.plugins-overview__logo {
fill: var( --color-neutral-10 );
Expand Down
119 changes: 94 additions & 25 deletions client/my-sites/plugins/main.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
import { Button } from '@automattic/components';
import { subscribeIsWithinBreakpoint, isWithinBreakpoint } from '@automattic/viewport';
import { Icon, upload } from '@wordpress/icons';
import classNames from 'classnames';
import { localize } from 'i18n-calypso';
import { capitalize, find, flow, isEmpty } from 'lodash';
import page from 'page';
Expand Down Expand Up @@ -139,8 +140,10 @@ export class PluginsMain extends Component {
}

getFilters() {
const { translate } = this.props;
const siteFilter = this.props.selectedSiteSlug ? '/' + this.props.selectedSiteSlug : '';
const { translate, search } = this.props;
const siteFilter = `${ this.props.selectedSiteSlug ? '/' + this.props.selectedSiteSlug : '' }${
search ? '?s=' + search : ''
}`;

return [
{
Expand Down Expand Up @@ -303,10 +306,11 @@ export class PluginsMain extends Component {
}

renderPluginsContent() {
const { search } = this.props;
const { search, isJetpackCloud } = this.props;

const currentPlugins = this.getCurrentPlugins();
const showInstalledPluginList = ! isEmpty( currentPlugins ) || this.isFetchingPlugins();
const showInstalledPluginList =
isJetpackCloud || ! isEmpty( currentPlugins ) || this.isFetchingPlugins();

if ( ! showInstalledPluginList && ! search ) {
const emptyContentData = this.getEmptyContentData();
Expand All @@ -328,6 +332,9 @@ export class PluginsMain extends Component {
plugins={ currentPlugins }
pluginUpdateCount={ this.props.pluginUpdateCount }
isPlaceholder={ this.shouldShowPluginListPlaceholders() }
isLoading={ this.props.requestingPluginsForSites }
isJetpackCloud={ this.props.isJetpackCloud }
searchTerm={ search }
/>
);

Expand Down Expand Up @@ -422,16 +429,23 @@ export class PluginsMain extends Component {
if ( 'updates' === filterItem.id ) {
attr.count = this.props.pluginUpdateCount;
}
if ( 'all' === filterItem.id ) {
attr.count = this.props.allPluginsCount;
}
return <NavItem { ...attr }>{ filterItem.title }</NavItem>;
} );

return (
<Main wideLayout>
<DocumentHead title={ this.props.translate( 'Plugins', { textOnly: true } ) } />
const pageTitle = this.props.translate( 'Plugins', { textOnly: true } );

const { isJetpackCloud } = this.props;

const content = (
<>
<DocumentHead title={ pageTitle } />
<QueryJetpackPlugins siteIds={ this.props.siteIds } />
<QuerySiteFeatures siteIds={ this.props.siteIds } />
{ this.renderPageViewTracking() }
{ this.props.shouldDisplayNavigationHeader && (
{ ! isJetpackCloud && (
<FixedNavigationHeader
className="plugins__page-heading"
navigationItems={ this.getNavigationItems() }
Expand All @@ -442,25 +456,78 @@ export class PluginsMain extends Component {
</div>
</FixedNavigationHeader>
) }
<div className="plugins__main">
<div className="plugins__main-header">
<SectionNav selectedText={ this.getSelectedText() }>
<NavTabs>{ navItems }</NavTabs>
<Search
pinned
fitsContainer
onSearch={ this.props.doSearch }
initialValue={ this.props.search }
ref={ `url-search` }
analyticsGroup="Plugins"
placeholder={ this.getSearchPlaceholder() }
/>
</SectionNav>
<div className={ classNames( { 'plugins__top-container': isJetpackCloud } ) }>
<div
className={ classNames( {
'plugins__content-wrapper': isJetpackCloud,
} ) }
>
{ isJetpackCloud && (
<div className="plugins__page-title-container">
<h2 className="plugins__page-title">{ pageTitle }</h2>
<div className="plugins__page-subtitle">
{ this.props.selectedSite
? this.props.translate( 'Manage all plugins installed on %(selectedSite)s', {
args: {
selectedSite: this.props.selectedSite.domain,
},
} )
: this.props.translate( 'Manage plugins installed on all sites' ) }
</div>
</div>
) }
<div
className={ classNames( 'plugins__main', {
'plugins__jetpack-cloud': isJetpackCloud,
} ) }
>
<div className="plugins__main-header">
<SectionNav
applyUpdatedStyles={ isJetpackCloud }
selectedText={ this.getSelectedText() }
>
<NavTabs>{ navItems }</NavTabs>
{ ! isJetpackCloud && (
<Search
pinned
fitsContainer
onSearch={ this.props.doSearch }
initialValue={ this.props.search }
ref={ `url-search` }
analyticsGroup="Plugins"
placeholder={ this.getSearchPlaceholder() }
/>
) }
</SectionNav>
</div>
</div>
</div>
</div>
{ this.renderPluginsContent() }
</Main>
{ isJetpackCloud ? (
<div className="plugins__main-content">
<div className="plugins__content-wrapper">
<div className="plugins__search">
<Search
hideFocus
isOpen
onSearch={ this.props.doSearch }
initialValue={ this.props.search }
hideClose={ ! this.props.search }
ref={ `url-search` }
analyticsGroup="Plugins"
placeholder={ this.props.translate( 'Search plugins' ) }
/>
</div>
{ this.renderPluginsContent() }
</div>
</div>
) : (
this.renderPluginsContent()
) }
</>
);

return isJetpackCloud ? content : <Main wideLayout>{ content }</Main>;
}
}

Expand All @@ -475,6 +542,7 @@ export default flow(
const visibleSiteIds = siteObjectsToSiteIds( getVisibleSites( sites ) ) ?? [];
const siteIds = siteObjectsToSiteIds( sites ) ?? [];
const pluginsWithUpdates = getPlugins( state, siteIds, 'updates' );
const allPlugins = getPlugins( state, siteIds, 'all' );
const jetpackNonAtomic =
isJetpackSite( state, selectedSiteId ) && ! isAtomicSite( state, selectedSiteId );
const hasManagePlugins =
Expand All @@ -500,6 +568,7 @@ export default flow(
currentPlugins: getPlugins( state, siteIds, filter ),
currentPluginsOnVisibleSites: getPlugins( state, visibleSiteIds, filter ),
pluginUpdateCount: pluginsWithUpdates && pluginsWithUpdates.length,
allPluginsCount: allPlugins && allPlugins.length,
requestingPluginsForSites: isRequestingForSites( state, siteIds ),
updateableJetpackSites: getUpdateableJetpackSites( state ),
userCanManagePlugins: selectedSiteId
Expand All @@ -508,7 +577,7 @@ export default flow(
hasManagePlugins: hasManagePlugins,
hasUploadPlugins: hasUploadPlugins,
hasInstallPurchasedPlugins: hasInstallPurchasedPlugins,
shouldDisplayNavigationHeader: ! isJetpackCloud,
isJetpackCloud,
};
},
{ wporgFetchPluginData, recordTracksEvent, recordGoogleEvent }
Expand Down
53 changes: 53 additions & 0 deletions client/my-sites/plugins/plugin-management-v2/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { useTranslate } from 'i18n-calypso';
import { ReactElement } from 'react';
import PluginsTable from './plugins-table';
import type { Plugin } from './types';
import type { SiteData } from 'calypso/state/ui/selectors/site-data';

import './style.scss';

interface Props {
plugins: Array< Plugin >;
isLoading: boolean;
selectedSite: SiteData;
searchTerm: string;
}
export default function PluginManagementV2( {
plugins,
isLoading,
selectedSite,
searchTerm,
}: Props ): ReactElement {
const translate = useTranslate();
const columns = [
{
key: 'plugin',
title: translate( 'Installed Plugins' ),
},
{
key: 'sites',
title: translate( 'Sites' ),
smallColumn: true,
colSpan: 2,
},
];

if ( ! plugins.length && ! isLoading ) {
let emptyStateMessage = translate( 'No plugins found' );
if ( searchTerm ) {
emptyStateMessage = translate( 'No results found. Please try refining your search.' );
}
return <div className="plugin-management-v2__no-sites">{ emptyStateMessage }</div>;
}

return (
<div className="plugin-management-v2__main-content-container">
<PluginsTable
items={ plugins }
columns={ columns }
isLoading={ isLoading }
selectedSite={ selectedSite }
/>
</div>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { Gridicon, Button } from '@automattic/components';
import type { Plugin } from '../types';
import type { ReactChild, ReactElement } from 'react';

import './style.scss';

interface Props {
item: Plugin;
columnKey: string;
}

export default function PluginRowFormatter( { item, columnKey }: Props ): ReactElement | any {
const PluginDetailsButton = ( props: { className: string; children: ReactChild } ) => {
return <Button borderless compact href={ `/plugins/${ item.slug }` } { ...props } />;
};

switch ( columnKey ) {
case 'plugin':
return (
<span className="plugin-row-formatter__plugin-name-container">
{ item.icon ? (
<img
className="plugin-row-formatter__plugin-icon"
src={ item.icon }
alt={ item.name }
/>
) : (
<Gridicon className="plugin-row-formatter__plugin-icon has-opacity" icon="plugins" />
) }
<PluginDetailsButton className="plugin-row-formatter__plugin-name">
{ item.name }
</PluginDetailsButton>
<span className="plugin-row-formatter__overlay"></span>
</span>
);
case 'sites':
return (
<PluginDetailsButton className="plugin-row-formatter__sites-count-button">
{ Object.keys( item.sites ).length }
</PluginDetailsButton>
);
}
}
Loading

0 comments on commit b8be9a1

Please sign in to comment.