From e88c473d5417bdfcbf209fbdb80774af9e679240 Mon Sep 17 00:00:00 2001 From: Yashwin Date: Wed, 3 Aug 2022 13:35:44 +0530 Subject: [PATCH 1/8] Implement large screen view for multi-site plugins page --- client/components/section-nav/index.jsx | 1 + client/components/section-nav/item.scss | 25 ++++ .../agency-dashboard/sites-overview/index.tsx | 2 +- .../sites-overview/style.scss | 24 ---- .../plugins-overview/style.scss | 2 +- client/my-sites/plugins/main.jsx | 117 ++++++++++++++---- .../plugins/plugin-management-v2/index.tsx | 52 ++++++++ .../plugin-row-formatter/index.tsx | 33 +++++ .../plugin-row-formatter/style.scss | 33 +++++ .../plugins-table/index.tsx | 92 ++++++++++++++ .../plugins-table/style.scss | 90 ++++++++++++++ .../plugins/plugin-management-v2/style.scss | 15 +++ .../plugins/plugin-management-v2/types.ts | 22 ++++ .../update-plugin/index.tsx | 80 ++++++++++++ .../update-plugin/style.scss | 14 +++ .../utils/get-allowed-plugin-actions.ts | 20 +++ .../my-sites/plugins/plugins-list/index.jsx | 79 +++++++----- client/my-sites/plugins/style.scss | 42 +++++++ 18 files changed, 663 insertions(+), 80 deletions(-) create mode 100644 client/my-sites/plugins/plugin-management-v2/index.tsx create mode 100644 client/my-sites/plugins/plugin-management-v2/plugin-row-formatter/index.tsx create mode 100644 client/my-sites/plugins/plugin-management-v2/plugin-row-formatter/style.scss create mode 100644 client/my-sites/plugins/plugin-management-v2/plugins-table/index.tsx create mode 100644 client/my-sites/plugins/plugin-management-v2/plugins-table/style.scss create mode 100644 client/my-sites/plugins/plugin-management-v2/style.scss create mode 100644 client/my-sites/plugins/plugin-management-v2/types.ts create mode 100644 client/my-sites/plugins/plugin-management-v2/update-plugin/index.tsx create mode 100644 client/my-sites/plugins/plugin-management-v2/update-plugin/style.scss create mode 100644 client/my-sites/plugins/plugin-management-v2/utils/get-allowed-plugin-actions.ts diff --git a/client/components/section-nav/index.jsx b/client/components/section-nav/index.jsx index 9d3f46254e95e..26e4ea01b63cc 100644 --- a/client/components/section-nav/index.jsx +++ b/client/components/section-nav/index.jsx @@ -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, } ); diff --git a/client/components/section-nav/item.scss b/client/components/section-nav/item.scss index 842bd45c2a4a9..719a804206332 100644 --- a/client/components/section-nav/item.scss +++ b/client/components/section-nav/item.scss @@ -1,3 +1,6 @@ +@import '@wordpress/base-styles/breakpoints'; +@import '@wordpress/base-styles/mixins'; + .section-nav-tab .count { margin-left: 8px; } @@ -129,3 +132,25 @@ 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; + background: var( --color-primary-0 ); + box-shadow: none; +} diff --git a/client/jetpack-cloud/sections/agency-dashboard/sites-overview/index.tsx b/client/jetpack-cloud/sections/agency-dashboard/sites-overview/index.tsx index f2aef84969f83..64740bbcd2a42 100644 --- a/client/jetpack-cloud/sections/agency-dashboard/sites-overview/index.tsx +++ b/client/jetpack-cloud/sections/agency-dashboard/sites-overview/index.tsx @@ -140,6 +140,7 @@ export default function SitesOverview(): ReactElement { { selectedItem.label } @@ -148,7 +149,6 @@ export default function SitesOverview(): ReactElement { } selectedCount={ selectedItem.count } className={ classNames( - 'sites-overview__section-nav', isMobile && highlightTab && selectedItem.key === 'favorites' && diff --git a/client/jetpack-cloud/sections/agency-dashboard/sites-overview/style.scss b/client/jetpack-cloud/sections/agency-dashboard/sites-overview/style.scss index a94dd9f0f29e7..48a39ac02383e 100644 --- a/client/jetpack-cloud/sections/agency-dashboard/sites-overview/style.scss +++ b/client/jetpack-cloud/sections/agency-dashboard/sites-overview/style.scss @@ -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 ); diff --git a/client/jetpack-cloud/sections/plugin-management/plugins-overview/style.scss b/client/jetpack-cloud/sections/plugin-management/plugins-overview/style.scss index 0570672e48cd6..8d753de3b8e40 100644 --- a/client/jetpack-cloud/sections/plugin-management/plugins-overview/style.scss +++ b/client/jetpack-cloud/sections/plugin-management/plugins-overview/style.scss @@ -1,5 +1,5 @@ .plugins-overview__container { - padding: 0 16px; + padding: 6px 0; } .plugins-overview__logo { fill: var( --color-neutral-10 ); diff --git a/client/my-sites/plugins/main.jsx b/client/my-sites/plugins/main.jsx index 5dbe6e190d578..159f10e98e1ab 100644 --- a/client/my-sites/plugins/main.jsx +++ b/client/my-sites/plugins/main.jsx @@ -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'; @@ -303,10 +304,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(); @@ -328,6 +330,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 } /> ); @@ -422,16 +427,23 @@ export class PluginsMain extends Component { if ( 'updates' === filterItem.id ) { attr.count = this.props.pluginUpdateCount; } + if ( 'all' === filterItem.id ) { + attr.count = this.props.currentPlugins.length; + } return { filterItem.title }; } ); - return ( -
- + const pageTitle = this.props.translate( 'Plugins', { textOnly: true } ); + + const { isJetpackCloud } = this.props; + + const content = ( + <> + { this.renderPageViewTracking() } - { this.props.shouldDisplayNavigationHeader && ( + { ! isJetpackCloud && ( ) } -
-
- - { navItems } - - +
+
+ { isJetpackCloud && ( +
+

{ pageTitle }

+
+ { this.props.selectedSite + ? this.props.translate( 'Manage all plugins installed on %(selectedSite)s', { + args: { + selectedSite: this.props.selectedSite.domain, + }, + } ) + : this.props.translate( 'All sites' ) } +
+
+ ) } +
+
+ + { navItems } + { ! isJetpackCloud && ( + + ) } + +
+
- { this.renderPluginsContent() } -
+
+
+ { isJetpackCloud && ( +
+ +
+ ) } + { this.renderPluginsContent() } +
+
+ ); + + return isJetpackCloud ? content :
{ content }
; } } @@ -508,7 +579,7 @@ export default flow( hasManagePlugins: hasManagePlugins, hasUploadPlugins: hasUploadPlugins, hasInstallPurchasedPlugins: hasInstallPurchasedPlugins, - shouldDisplayNavigationHeader: ! isJetpackCloud, + isJetpackCloud, }; }, { wporgFetchPluginData, recordTracksEvent, recordGoogleEvent } diff --git a/client/my-sites/plugins/plugin-management-v2/index.tsx b/client/my-sites/plugins/plugin-management-v2/index.tsx new file mode 100644 index 0000000000000..91301c9106fc4 --- /dev/null +++ b/client/my-sites/plugins/plugin-management-v2/index.tsx @@ -0,0 +1,52 @@ +import { useTranslate } from 'i18n-calypso'; +import { ReactElement } from 'react'; +import PluginsTable from './plugins-table'; +import type { Plugin } from './types'; + +import './style.scss'; + +interface Props { + plugins: Array< Plugin >; + isLoading: boolean; + selectedSite: any; + 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
{ emptyStateMessage }
; + } + + return ( +
+ +
+ ); +} diff --git a/client/my-sites/plugins/plugin-management-v2/plugin-row-formatter/index.tsx b/client/my-sites/plugins/plugin-management-v2/plugin-row-formatter/index.tsx new file mode 100644 index 0000000000000..575333b8dda2e --- /dev/null +++ b/client/my-sites/plugins/plugin-management-v2/plugin-row-formatter/index.tsx @@ -0,0 +1,33 @@ +import { Gridicon } from '@automattic/components'; +import { ReactElement } from 'react'; +import type { Plugin } from '../types'; + +import './style.scss'; + +interface Props { + item: Plugin; + columnKey: string; +} + +export default function PluginRowFormatter( { item, columnKey }: Props ): ReactElement | any { + switch ( columnKey ) { + case 'plugin': + return ( + + { item.icon ? ( + { + ) : ( + + ) } + { item.name } + + + ); + case 'sites': + return Object.keys( item.sites ).length; + } +} diff --git a/client/my-sites/plugins/plugin-management-v2/plugin-row-formatter/style.scss b/client/my-sites/plugins/plugin-management-v2/plugin-row-formatter/style.scss new file mode 100644 index 0000000000000..8203a82829e6e --- /dev/null +++ b/client/my-sites/plugins/plugin-management-v2/plugin-row-formatter/style.scss @@ -0,0 +1,33 @@ +.plugin-row-formatter__plugin-name-container { + position: relative; +} +.plugin-row-formatter__plugin-icon { + width: 24px; + height: 24px; + vertical-align: middle; + margin-inline-end: 16px; +} +.plugin-row-formatter__plugin-name { + display: inline-block; + font-weight: 500; + white-space: nowrap; + overflow: hidden; + text-overflow: clip; + vertical-align: middle; + color: var( --studio-gray-100 ); + width: calc( 100% - 55px ); + font-size: 0.875rem; +} +.plugin-row-formatter__overlay { + display: block; + position: absolute; + height: 48px; + width: 20px; + background: linear-gradient( + to right, + rgba( 255, 255, 255, 0.8 ) 30%, + rgba( 255, 255, 255, 1 ) 100% + ); + inset-block-start: -14px; + inset-inline-end: 0; +} diff --git a/client/my-sites/plugins/plugin-management-v2/plugins-table/index.tsx b/client/my-sites/plugins/plugin-management-v2/plugins-table/index.tsx new file mode 100644 index 0000000000000..71675fe13ebcb --- /dev/null +++ b/client/my-sites/plugins/plugin-management-v2/plugins-table/index.tsx @@ -0,0 +1,92 @@ +import { Gridicon, Button } from '@automattic/components'; +import classNames from 'classnames'; +import TextPlaceholder from 'calypso/jetpack-cloud/sections/partner-portal/text-placeholder'; +import PluginRowFormatter from '../plugin-row-formatter'; +import UpdatePlugin from '../update-plugin'; +import type { PluginColumns, Plugin } from '../types'; +import type { ReactElement } from 'react'; + +import './style.scss'; + +interface Props { + isLoading: boolean; + columns: PluginColumns; + items: Array< Plugin >; + selectedSite: any; +} + +export default function PluginsTable( { + isLoading, + columns, + items, + selectedSite, +}: Props ): ReactElement { + return ( + + + + { columns.map( ( column ) => ( + + ) ) } + + + + + { isLoading ? ( + + { columns.map( ( column ) => ( + + ) ) } + + + + ) : ( + items.map( ( item ) => { + const id = item.id; + return ( + + { columns.map( ( column ) => { + return ( + + ); + } ) } + + { + + } + + ); + } ) + ) } + +
+ { column.title } +
+ { column.key === 'plugin' && ( + + ) } + + + + + +
+ { } + + + + +
+ ); +} diff --git a/client/my-sites/plugins/plugin-management-v2/plugins-table/style.scss b/client/my-sites/plugins/plugin-management-v2/plugins-table/style.scss new file mode 100644 index 0000000000000..d82a1f6126a3b --- /dev/null +++ b/client/my-sites/plugins/plugin-management-v2/plugins-table/style.scss @@ -0,0 +1,90 @@ + +@import '@wordpress/base-styles/_breakpoints.scss'; +@import '@wordpress/base-styles/_mixins.scss'; + +.plugins-table__table { + display: none; + @include break-xlarge() { + display: table; + } + border: 1px solid var( --studio-gray-5 ); + border-collapse: collapse; + tr { + height: 1px; + background: var( --studio-white ); + } + th { + font-size: 0.75rem; + color: var( --studio-gray-50 ); + font-weight: 400; + height: 50px; + } + td { + font-size: 0.875rem; + height: 50px; + width: 162px; + box-sizing: border-box; + &:nth-child( 1 ) { + max-width: 200px; + @include break-wide() { + max-width: 300px; + } + @include break-huge() { + max-width: 350px; + } + } + a { + display: flex; + } + } + td, th { + border-bottom: 1px solid var( --studio-gray-5 ); + text-align: left; + border-collapse: collapse; + vertical-align: middle; + @include break-xlarge() { + padding: 0 8px; + } + @include break-wide() { + padding: 0 16px; + } + } + .partner-portal-text-placeholder { + display: inline-flex; + min-width: 50%; + vertical-align: middle; + } +} +td.plugins-table__actions { + padding: 0; + width: 50px; + text-align: right; + padding-right: 16px; +} +.plugins-table__table-row { + &:hover { + background: var( --studio-gray-0 ); + .plugin-row-formatter__overlay { + background: linear-gradient( to right, rgba( 246, 247, 247, 0.8 ) 30%, rgba( 246, 247, 247, 1 ) 100% ); + } + } +} +.plugins-table__all-actions { + vertical-align: middle; + display: inline-flex; + color: var( --studio-gray-40 ); + margin: 0 0.1em; + height: 100%; +} +.plugins-table__small-column { + max-width: 100px; +} +.components-form-toggle.is-checked .components-form-toggle__track { + background-color: var( --studio-jetpack-green-50 ); +} +.plugins-table__plugin-icon { + width: 24px; + height: 24px; + vertical-align: middle; + margin-inline-end: 16px; +} diff --git a/client/my-sites/plugins/plugin-management-v2/style.scss b/client/my-sites/plugins/plugin-management-v2/style.scss new file mode 100644 index 0000000000000..3b176cd52949a --- /dev/null +++ b/client/my-sites/plugins/plugin-management-v2/style.scss @@ -0,0 +1,15 @@ +.has-opacity { + opacity: 0.3; +} +.is-loading { + animation: loading-fade 1.6s ease-in-out infinite; + opacity: 0.3; +} +.plugin-management-v2__main-content-container { + margin-top: 8px; +} +.plugin-management-v2__no-sites { + text-align: center; + font-size: 1.5rem; + margin-top: 16px; +} diff --git a/client/my-sites/plugins/plugin-management-v2/types.ts b/client/my-sites/plugins/plugin-management-v2/types.ts new file mode 100644 index 0000000000000..92e33b8f3292f --- /dev/null +++ b/client/my-sites/plugins/plugin-management-v2/types.ts @@ -0,0 +1,22 @@ +import type { MomentInput } from 'moment'; +import type { ReactChild } from 'react'; + +export type PluginColumns = Array< { + key: string; + title: ReactChild; + smallColumn?: boolean; + colSpan?: number; +} >; + +export type PluginSite = { ID: string | number; canUpdateFiles: any }; + +export interface Plugin { + id: number; + last_updated: MomentInput; + sites: Array< PluginSite >; + icon: string; + name: string; + pluginsOnSites: Array< any >; + slug: string; + wporg: string; +} diff --git a/client/my-sites/plugins/plugin-management-v2/update-plugin/index.tsx b/client/my-sites/plugins/plugin-management-v2/update-plugin/index.tsx new file mode 100644 index 0000000000000..27e4d0dd2475b --- /dev/null +++ b/client/my-sites/plugins/plugin-management-v2/update-plugin/index.tsx @@ -0,0 +1,80 @@ +import { Button } from '@automattic/components'; +import classNames from 'classnames'; +import { useTranslate } from 'i18n-calypso'; +import { useSelector } from 'react-redux'; +import Badge from 'calypso/components/badge'; +import { siteObjectsToSiteIds } from 'calypso/my-sites/plugins/utils'; +import { getPluginOnSites } from 'calypso/state/plugins/installed/selectors'; +import getSites from 'calypso/state/selectors/get-sites'; +import { getAllowedPluginActions } from '../utils/get-allowed-plugin-actions'; +import type { PluginSite, Plugin } from '../types'; +import type { ReactElement } from 'react'; + +import './style.scss'; + +interface Props { + plugin: Plugin; + selectedSite: any; + className?: string; +} + +export default function UpdatePlugin( { + plugin, + selectedSite, + className, +}: Props ): ReactElement | null { + const translate = useTranslate(); + const allSites = useSelector( getSites ); + const state = useSelector( ( state ) => state ); + + const getPluginSites = ( plugin: Plugin ) => { + return Object.keys( plugin.sites ).map( ( siteId: any ) => { + const site = allSites.find( ( s ) => s?.ID === parseInt( siteId ) ); + return { + ...site, + ...plugin.sites[ siteId ], + }; + } ); + }; + + const sites = getPluginSites( plugin ); + const siteIds = siteObjectsToSiteIds( sites ); + const pluginsOnSites: any = getPluginOnSites( state, siteIds, plugin?.slug ); + + const updated_versions = sites + .map( ( site: PluginSite ) => { + const sitePlugin = pluginsOnSites?.sites[ site.ID ]; + return sitePlugin?.update?.new_version; + } ) + .filter( ( version ) => version ); + + const hasUpdate = sites.some( ( site ) => { + const sitePlugin = pluginsOnSites?.sites[ site.ID ]; + return sitePlugin?.update && site.canUpdateFiles; + } ); + + const allowedActions = getAllowedPluginActions( plugin, state, selectedSite ); + + let content; + + if ( ! allowedActions.autoupdate ) { + content =
{ translate( 'Auto-managed on this site' ) }
; + } else if ( hasUpdate ) { + content = ( + <> + + { updated_versions[ 0 ] } { translate( 'Available' ) } + + + + ); + } + return content ?
{ content }
: null; +} diff --git a/client/my-sites/plugins/plugin-management-v2/update-plugin/style.scss b/client/my-sites/plugins/plugin-management-v2/update-plugin/style.scss new file mode 100644 index 0000000000000..49f2d2e4980bb --- /dev/null +++ b/client/my-sites/plugins/plugin-management-v2/update-plugin/style.scss @@ -0,0 +1,14 @@ +.update-plugin__badge { + font-size: 0.75rem !important; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + vertical-align: middle; + max-width: fit-content; +} +a.button.update-plugin__plugin-update-button { + color: var( --color-primary-80 ); + margin-left: 10px; + text-decoration: underline; + display: inline-flex; +} diff --git a/client/my-sites/plugins/plugin-management-v2/utils/get-allowed-plugin-actions.ts b/client/my-sites/plugins/plugin-management-v2/utils/get-allowed-plugin-actions.ts new file mode 100644 index 0000000000000..6df5f6ed55a4a --- /dev/null +++ b/client/my-sites/plugins/plugin-management-v2/utils/get-allowed-plugin-actions.ts @@ -0,0 +1,20 @@ +import { WPCOM_FEATURES_MANAGE_PLUGINS } from '@automattic/calypso-products'; +import isSiteAutomatedTransfer from 'calypso/state/selectors/is-site-automated-transfer'; +import siteHasFeature from 'calypso/state/selectors/site-has-feature'; +import { isJetpackSite } from 'calypso/state/sites/selectors'; +import type { Plugin } from '../types'; + +export const getAllowedPluginActions = ( plugin: Plugin, state, selectedSite ) => { + const autoManagedPlugins = [ 'jetpack', 'vaultpress', 'akismet' ]; + const siteIsAtomic = isSiteAutomatedTransfer( state, selectedSite?.ID ); + const siteIsJetpack = isJetpackSite( state, selectedSite?.ID ); + const hasManagePlugins = siteHasFeature( state, selectedSite?.ID, WPCOM_FEATURES_MANAGE_PLUGINS ); + const isManagedPlugin = siteIsAtomic && autoManagedPlugins.includes( plugin.slug ); + const canManagePlugins = + ! selectedSite || ( siteIsJetpack && ! siteIsAtomic ) || ( siteIsAtomic && hasManagePlugins ); + + return { + autoupdate: ! isManagedPlugin && canManagePlugins, + activation: ! isManagedPlugin && canManagePlugins, + }; +}; diff --git a/client/my-sites/plugins/plugins-list/index.jsx b/client/my-sites/plugins/plugins-list/index.jsx index 30f1f44843d37..3ec015fd6058f 100644 --- a/client/my-sites/plugins/plugins-list/index.jsx +++ b/client/my-sites/plugins/plugins-list/index.jsx @@ -32,6 +32,7 @@ import isSiteAutomatedTransfer from 'calypso/state/selectors/is-site-automated-t import siteHasFeature from 'calypso/state/selectors/site-has-feature'; import { isJetpackSite } from 'calypso/state/sites/selectors'; import { getSelectedSite, getSelectedSiteSlug } from 'calypso/state/ui/selectors'; +import PluginManagementV2 from '../plugin-management-v2'; import './style.scss'; @@ -82,6 +83,10 @@ export class PluginsList extends Component { return true; } + if ( this.props.searchTerm !== nextProps.searchTerm ) { + return true; + } + if ( this.state.bulkManagementActive !== nextState.bulkManagementActive ) { return true; } @@ -468,7 +473,7 @@ export class PluginsList extends Component { const selectedSiteSlug = this.props.selectedSiteSlug ? this.props.selectedSiteSlug : ''; - if ( this.props.isPlaceholder ) { + if ( this.props.isPlaceholder && ! this.props.isJetpackCloud ) { return (
- - - { this.orderPluginsByUpdates( this.props.plugins ).map( this.renderPlugin ) } - + { this.props.isJetpackCloud ? ( + + ) : ( + <> + + + { this.orderPluginsByUpdates( this.props.plugins ).map( this.renderPlugin ) } + + + ) }
); } @@ -583,7 +601,6 @@ export class PluginsList extends Component { export default connect( ( state, { plugins } ) => { const selectedSite = getSelectedSite( state ); - return { allSites: getSites( state ), pluginsOnSites: getPluginsOnSites( state, plugins ), diff --git a/client/my-sites/plugins/style.scss b/client/my-sites/plugins/style.scss index 5c1f27a948a5c..893ebbba9dcfb 100644 --- a/client/my-sites/plugins/style.scss +++ b/client/my-sites/plugins/style.scss @@ -315,3 +315,45 @@ body.is-section-plugins header .select-dropdown__item { } } } +.plugins__page-title { + color: var( --studio-gray-80 ); + font-weight: 400; +} +.plugins__page-subtitle { + font-size: 0.875rem; + color: var( --studio-gray-60 ); + margin-bottom: 8px; +} +.plugins__jetpack-cloud { + .plugins__main-header .section-nav { + border-width: 0; + } + .plugins__main-header { + margin: 0; + } +} +.plugins__top-container { + margin: 0 -32px; + padding: 0 48px; + border-bottom: 1px solid var( --color-primary-5 ); +} +.plugins__content-wrapper { + max-width: 1500px; + margin: auto; + padding: 0; +} +.plugins__main-content { + // We need these negative margin values because we want to make the container full-width, + // but our element is inside a limited-width parent. + margin: 0 -32px -32px; + padding: 8px 48px; + min-height: 100vh; + background: rgba( 255, 255, 255, 0.5 ); +} +.plugins__search { + height: 52px; + box-shadow: 0 0 0 1px var( --color-neutral-5 ); + .search.is-open { + box-shadow: none; + } +} From 0cbb528c1d31fe2edc53b1cfcc02b50c1e8b069b Mon Sep 17 00:00:00 2001 From: Yashwin Date: Wed, 3 Aug 2022 15:11:50 +0530 Subject: [PATCH 2/8] Redirect to plugin details when clicked on sites count --- .../plugin-row-formatter/index.tsx | 13 +++++++++++-- .../plugin-row-formatter/style.scss | 5 +++++ client/my-sites/plugins/style.scss | 2 +- 3 files changed, 17 insertions(+), 3 deletions(-) diff --git a/client/my-sites/plugins/plugin-management-v2/plugin-row-formatter/index.tsx b/client/my-sites/plugins/plugin-management-v2/plugin-row-formatter/index.tsx index 575333b8dda2e..821c148c033c3 100644 --- a/client/my-sites/plugins/plugin-management-v2/plugin-row-formatter/index.tsx +++ b/client/my-sites/plugins/plugin-management-v2/plugin-row-formatter/index.tsx @@ -1,4 +1,4 @@ -import { Gridicon } from '@automattic/components'; +import { Gridicon, Button } from '@automattic/components'; import { ReactElement } from 'react'; import type { Plugin } from '../types'; @@ -28,6 +28,15 @@ export default function PluginRowFormatter( { item, columnKey }: Props ): ReactE ); case 'sites': - return Object.keys( item.sites ).length; + return ( + + ); } } diff --git a/client/my-sites/plugins/plugin-management-v2/plugin-row-formatter/style.scss b/client/my-sites/plugins/plugin-management-v2/plugin-row-formatter/style.scss index 8203a82829e6e..49000e869ed05 100644 --- a/client/my-sites/plugins/plugin-management-v2/plugin-row-formatter/style.scss +++ b/client/my-sites/plugins/plugin-management-v2/plugin-row-formatter/style.scss @@ -31,3 +31,8 @@ inset-block-start: -14px; inset-inline-end: 0; } +.plugin-row-formatter__sites-count-button { + &:hover { + text-decoration: underline; + } +} diff --git a/client/my-sites/plugins/style.scss b/client/my-sites/plugins/style.scss index 893ebbba9dcfb..2ab880f874db6 100644 --- a/client/my-sites/plugins/style.scss +++ b/client/my-sites/plugins/style.scss @@ -346,7 +346,7 @@ body.is-section-plugins header .select-dropdown__item { // We need these negative margin values because we want to make the container full-width, // but our element is inside a limited-width parent. margin: 0 -32px -32px; - padding: 8px 48px; + padding: 16px 48px; min-height: 100vh; background: rgba( 255, 255, 255, 0.5 ); } From a3cf887588b124975be0cd63b098292fdb9dc047 Mon Sep 17 00:00:00 2001 From: Yashwin Date: Wed, 3 Aug 2022 15:58:38 +0530 Subject: [PATCH 3/8] Update types --- client/my-sites/plugins/plugin-management-v2/index.tsx | 3 ++- .../plugin-management-v2/plugin-row-formatter/index.tsx | 2 +- .../plugins/plugin-management-v2/plugins-table/index.tsx | 3 ++- client/my-sites/plugins/plugin-management-v2/types.ts | 4 ++-- .../plugins/plugin-management-v2/update-plugin/index.tsx | 9 +++++---- .../utils/get-allowed-plugin-actions.ts | 8 +++++++- 6 files changed, 19 insertions(+), 10 deletions(-) diff --git a/client/my-sites/plugins/plugin-management-v2/index.tsx b/client/my-sites/plugins/plugin-management-v2/index.tsx index 91301c9106fc4..e5b48d678b353 100644 --- a/client/my-sites/plugins/plugin-management-v2/index.tsx +++ b/client/my-sites/plugins/plugin-management-v2/index.tsx @@ -2,13 +2,14 @@ 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: any; + selectedSite: SiteData; searchTerm: string; } export default function PluginManagementV2( { diff --git a/client/my-sites/plugins/plugin-management-v2/plugin-row-formatter/index.tsx b/client/my-sites/plugins/plugin-management-v2/plugin-row-formatter/index.tsx index 821c148c033c3..64d2a597e5fdc 100644 --- a/client/my-sites/plugins/plugin-management-v2/plugin-row-formatter/index.tsx +++ b/client/my-sites/plugins/plugin-management-v2/plugin-row-formatter/index.tsx @@ -9,7 +9,7 @@ interface Props { columnKey: string; } -export default function PluginRowFormatter( { item, columnKey }: Props ): ReactElement | any { +export default function PluginRowFormatter( { item, columnKey }: Props ): ReactElement | null { switch ( columnKey ) { case 'plugin': return ( diff --git a/client/my-sites/plugins/plugin-management-v2/plugins-table/index.tsx b/client/my-sites/plugins/plugin-management-v2/plugins-table/index.tsx index 71675fe13ebcb..c32ade6acafd1 100644 --- a/client/my-sites/plugins/plugin-management-v2/plugins-table/index.tsx +++ b/client/my-sites/plugins/plugin-management-v2/plugins-table/index.tsx @@ -4,6 +4,7 @@ import TextPlaceholder from 'calypso/jetpack-cloud/sections/partner-portal/text- import PluginRowFormatter from '../plugin-row-formatter'; import UpdatePlugin from '../update-plugin'; import type { PluginColumns, Plugin } from '../types'; +import type { SiteData } from 'calypso/state/ui/selectors/site-data'; import type { ReactElement } from 'react'; import './style.scss'; @@ -12,7 +13,7 @@ interface Props { isLoading: boolean; columns: PluginColumns; items: Array< Plugin >; - selectedSite: any; + selectedSite: SiteData; } export default function PluginsTable( { diff --git a/client/my-sites/plugins/plugin-management-v2/types.ts b/client/my-sites/plugins/plugin-management-v2/types.ts index 92e33b8f3292f..504a09a1602fa 100644 --- a/client/my-sites/plugins/plugin-management-v2/types.ts +++ b/client/my-sites/plugins/plugin-management-v2/types.ts @@ -8,12 +8,12 @@ export type PluginColumns = Array< { colSpan?: number; } >; -export type PluginSite = { ID: string | number; canUpdateFiles: any }; +export type PluginSite = { [ key: string ]: { ID: number; canUpdateFiles: boolean } }; export interface Plugin { id: number; last_updated: MomentInput; - sites: Array< PluginSite >; + sites: PluginSite; icon: string; name: string; pluginsOnSites: Array< any >; diff --git a/client/my-sites/plugins/plugin-management-v2/update-plugin/index.tsx b/client/my-sites/plugins/plugin-management-v2/update-plugin/index.tsx index 27e4d0dd2475b..954770757c981 100644 --- a/client/my-sites/plugins/plugin-management-v2/update-plugin/index.tsx +++ b/client/my-sites/plugins/plugin-management-v2/update-plugin/index.tsx @@ -7,14 +7,15 @@ import { siteObjectsToSiteIds } from 'calypso/my-sites/plugins/utils'; import { getPluginOnSites } from 'calypso/state/plugins/installed/selectors'; import getSites from 'calypso/state/selectors/get-sites'; import { getAllowedPluginActions } from '../utils/get-allowed-plugin-actions'; -import type { PluginSite, Plugin } from '../types'; +import type { Plugin } from '../types'; +import type { SiteData } from 'calypso/state/ui/selectors/site-data'; import type { ReactElement } from 'react'; import './style.scss'; interface Props { plugin: Plugin; - selectedSite: any; + selectedSite: SiteData; className?: string; } @@ -28,7 +29,7 @@ export default function UpdatePlugin( { const state = useSelector( ( state ) => state ); const getPluginSites = ( plugin: Plugin ) => { - return Object.keys( plugin.sites ).map( ( siteId: any ) => { + return Object.keys( plugin.sites ).map( ( siteId ) => { const site = allSites.find( ( s ) => s?.ID === parseInt( siteId ) ); return { ...site, @@ -42,7 +43,7 @@ export default function UpdatePlugin( { const pluginsOnSites: any = getPluginOnSites( state, siteIds, plugin?.slug ); const updated_versions = sites - .map( ( site: PluginSite ) => { + .map( ( site ) => { const sitePlugin = pluginsOnSites?.sites[ site.ID ]; return sitePlugin?.update?.new_version; } ) diff --git a/client/my-sites/plugins/plugin-management-v2/utils/get-allowed-plugin-actions.ts b/client/my-sites/plugins/plugin-management-v2/utils/get-allowed-plugin-actions.ts index 6df5f6ed55a4a..b30c867af72a5 100644 --- a/client/my-sites/plugins/plugin-management-v2/utils/get-allowed-plugin-actions.ts +++ b/client/my-sites/plugins/plugin-management-v2/utils/get-allowed-plugin-actions.ts @@ -3,8 +3,14 @@ import isSiteAutomatedTransfer from 'calypso/state/selectors/is-site-automated-t import siteHasFeature from 'calypso/state/selectors/site-has-feature'; import { isJetpackSite } from 'calypso/state/sites/selectors'; import type { Plugin } from '../types'; +import type { SiteData } from 'calypso/state/ui/selectors/site-data'; +import type { AppState } from 'calypso/types'; -export const getAllowedPluginActions = ( plugin: Plugin, state, selectedSite ) => { +export const getAllowedPluginActions = ( + plugin: Plugin, + state: AppState, + selectedSite: SiteData +) => { const autoManagedPlugins = [ 'jetpack', 'vaultpress', 'akismet' ]; const siteIsAtomic = isSiteAutomatedTransfer( state, selectedSite?.ID ); const siteIsJetpack = isJetpackSite( state, selectedSite?.ID ); From 480e902db206f9fae09febea7dd11b15b5fc79d8 Mon Sep 17 00:00:00 2001 From: Yashwin Date: Wed, 3 Aug 2022 16:11:23 +0530 Subject: [PATCH 4/8] Minor code refactor --- client/components/section-nav/item.scss | 3 --- client/my-sites/plugins/main.jsx | 22 +++++++------------ .../plugin-row-formatter/index.tsx | 2 +- 3 files changed, 9 insertions(+), 18 deletions(-) diff --git a/client/components/section-nav/item.scss b/client/components/section-nav/item.scss index 719a804206332..0c3b31ab7ab9f 100644 --- a/client/components/section-nav/item.scss +++ b/client/components/section-nav/item.scss @@ -1,6 +1,3 @@ -@import '@wordpress/base-styles/breakpoints'; -@import '@wordpress/base-styles/mixins'; - .section-nav-tab .count { margin-left: 8px; } diff --git a/client/my-sites/plugins/main.jsx b/client/my-sites/plugins/main.jsx index 159f10e98e1ab..f12607b9fd02a 100644 --- a/client/my-sites/plugins/main.jsx +++ b/client/my-sites/plugins/main.jsx @@ -501,17 +501,9 @@ export class PluginsMain extends Component { -
-
- { isJetpackCloud && ( + { isJetpackCloud ? ( +
+
- ) } - { this.renderPluginsContent() } + { this.renderPluginsContent() } +
-
+ ) : ( + this.renderPluginsContent() + ) } ); diff --git a/client/my-sites/plugins/plugin-management-v2/plugin-row-formatter/index.tsx b/client/my-sites/plugins/plugin-management-v2/plugin-row-formatter/index.tsx index 64d2a597e5fdc..821c148c033c3 100644 --- a/client/my-sites/plugins/plugin-management-v2/plugin-row-formatter/index.tsx +++ b/client/my-sites/plugins/plugin-management-v2/plugin-row-formatter/index.tsx @@ -9,7 +9,7 @@ interface Props { columnKey: string; } -export default function PluginRowFormatter( { item, columnKey }: Props ): ReactElement | null { +export default function PluginRowFormatter( { item, columnKey }: Props ): ReactElement | any { switch ( columnKey ) { case 'plugin': return ( From 784755e1c60b76e2b09ffdf65ac3d09b0ac9ca8f Mon Sep 17 00:00:00 2001 From: Yashwin Date: Thu, 4 Aug 2022 16:30:58 +0530 Subject: [PATCH 5/8] Fix all sites count --- client/my-sites/plugins/main.jsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/client/my-sites/plugins/main.jsx b/client/my-sites/plugins/main.jsx index f12607b9fd02a..3a3bb207c8f69 100644 --- a/client/my-sites/plugins/main.jsx +++ b/client/my-sites/plugins/main.jsx @@ -428,7 +428,7 @@ export class PluginsMain extends Component { attr.count = this.props.pluginUpdateCount; } if ( 'all' === filterItem.id ) { - attr.count = this.props.currentPlugins.length; + attr.count = this.props.allPluginsCount; } return { filterItem.title }; } ); @@ -540,6 +540,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 = @@ -565,6 +566,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 From 4665de05ba1abfa236051e76f89c46eb6658cb53 Mon Sep 17 00:00:00 2001 From: Yashwin Date: Thu, 4 Aug 2022 17:21:44 +0530 Subject: [PATCH 6/8] Update page subtitle and link the plugin name to details page --- client/my-sites/plugins/main.jsx | 2 +- .../plugin-row-formatter/index.tsx | 19 ++++++++++--------- .../plugin-row-formatter/style.scss | 3 ++- 3 files changed, 13 insertions(+), 11 deletions(-) diff --git a/client/my-sites/plugins/main.jsx b/client/my-sites/plugins/main.jsx index 3a3bb207c8f69..21c131776ecdf 100644 --- a/client/my-sites/plugins/main.jsx +++ b/client/my-sites/plugins/main.jsx @@ -470,7 +470,7 @@ export class PluginsMain extends Component { selectedSite: this.props.selectedSite.domain, }, } ) - : this.props.translate( 'All sites' ) } + : this.props.translate( 'Manage plugins installed on all sites' ) }
) } diff --git a/client/my-sites/plugins/plugin-management-v2/plugin-row-formatter/index.tsx b/client/my-sites/plugins/plugin-management-v2/plugin-row-formatter/index.tsx index 821c148c033c3..3caf271e1d3a2 100644 --- a/client/my-sites/plugins/plugin-management-v2/plugin-row-formatter/index.tsx +++ b/client/my-sites/plugins/plugin-management-v2/plugin-row-formatter/index.tsx @@ -1,6 +1,6 @@ import { Gridicon, Button } from '@automattic/components'; -import { ReactElement } from 'react'; import type { Plugin } from '../types'; +import type { ReactChild, ReactElement } from 'react'; import './style.scss'; @@ -10,6 +10,10 @@ interface Props { } export default function PluginRowFormatter( { item, columnKey }: Props ): ReactElement | any { + const PluginDetailsButton = ( props: { className: string; children: ReactChild } ) => { + return + ); } } diff --git a/client/my-sites/plugins/plugin-management-v2/plugin-row-formatter/style.scss b/client/my-sites/plugins/plugin-management-v2/plugin-row-formatter/style.scss index 49000e869ed05..bd06ff5fb9724 100644 --- a/client/my-sites/plugins/plugin-management-v2/plugin-row-formatter/style.scss +++ b/client/my-sites/plugins/plugin-management-v2/plugin-row-formatter/style.scss @@ -7,7 +7,7 @@ vertical-align: middle; margin-inline-end: 16px; } -.plugin-row-formatter__plugin-name { +a.button.plugin-row-formatter__plugin-name { display: inline-block; font-weight: 500; white-space: nowrap; @@ -17,6 +17,7 @@ color: var( --studio-gray-100 ); width: calc( 100% - 55px ); font-size: 0.875rem; + text-align: left; } .plugin-row-formatter__overlay { display: block; From ecf34c0338b7f8da559423eb2706521e3e7c3eed Mon Sep 17 00:00:00 2001 From: Yashwin Date: Thu, 4 Aug 2022 18:40:03 +0530 Subject: [PATCH 7/8] Append search term when switching tabs --- client/my-sites/plugins/main.jsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/client/my-sites/plugins/main.jsx b/client/my-sites/plugins/main.jsx index 21c131776ecdf..475773ee02604 100644 --- a/client/my-sites/plugins/main.jsx +++ b/client/my-sites/plugins/main.jsx @@ -140,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 [ { From fb618f05bd608d70f6ad64d58b1aca67b9b45b28 Mon Sep 17 00:00:00 2001 From: Yashwin Date: Mon, 8 Aug 2022 11:22:05 +0530 Subject: [PATCH 8/8] Update nav tab for mobile view --- client/components/section-nav/item.scss | 9 +++++++-- .../plugin-management-v2/plugins-table/style.scss | 2 +- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/client/components/section-nav/item.scss b/client/components/section-nav/item.scss index 0c3b31ab7ab9f..2633cb79fc18c 100644 --- a/client/components/section-nav/item.scss +++ b/client/components/section-nav/item.scss @@ -1,3 +1,6 @@ +@import '@wordpress/base-styles/breakpoints'; +@import '@wordpress/base-styles/mixins'; + .section-nav-tab .count { margin-left: 8px; } @@ -148,6 +151,8 @@ font-weight: normal; } margin-bottom: 0; - background: var( --color-primary-0 ); - box-shadow: none; + @include break-large() { + background: var( --color-primary-0 ); + box-shadow: none; + } } diff --git a/client/my-sites/plugins/plugin-management-v2/plugins-table/style.scss b/client/my-sites/plugins/plugin-management-v2/plugins-table/style.scss index d82a1f6126a3b..eedba0975a494 100644 --- a/client/my-sites/plugins/plugin-management-v2/plugins-table/style.scss +++ b/client/my-sites/plugins/plugin-management-v2/plugins-table/style.scss @@ -59,7 +59,7 @@ td.plugins-table__actions { padding: 0; width: 50px; text-align: right; - padding-right: 16px; + padding-right: 16px; } .plugins-table__table-row { &:hover {