Add product data views list to experimental product data views page (#51008)
* Add products data views list * Add navigation on the left * Update edit site package * Fix styling * Add changelog * Add wp/icons package to syncpack exception list * Delete some unused stuff and address types * Add changelog * Remove un needed css * Remove dependency on edit-site package * Fix custom status filters * Make sure page size works with view config * Remove use of canvasMode and navigation context as it is not needed * Remove wordpress/dom from syncpack
This commit is contained in:
parent
e589fa87e1
commit
2890e16c86
|
@ -201,7 +201,9 @@
|
|||
"@wordpress/interface",
|
||||
"@wordpress/router",
|
||||
"@wordpress/edit-site",
|
||||
"@wordpress/private-apis"
|
||||
"@wordpress/private-apis",
|
||||
"@wordpress/dataviews",
|
||||
"@wordpress/icons"
|
||||
],
|
||||
"packages": [
|
||||
"@woocommerce/block-templates",
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
Significance: minor
|
||||
Type: add
|
||||
|
||||
Add sidebar and dataviews list to the experimental dataviews products page.
|
|
@ -56,6 +56,7 @@
|
|||
"@wordpress/compose": "wp-6.0",
|
||||
"@wordpress/core-data": "wp-6.0",
|
||||
"@wordpress/data": "wp-6.0",
|
||||
"@wordpress/dataviews": "^4.2.0",
|
||||
"@wordpress/date": "wp-6.0",
|
||||
"@wordpress/deprecated": "wp-6.0",
|
||||
"@wordpress/edit-post": "wp-6.0",
|
||||
|
@ -64,7 +65,7 @@
|
|||
"@wordpress/hooks": "wp-6.0",
|
||||
"@wordpress/html-entities": "wp-6.0",
|
||||
"@wordpress/i18n": "wp-6.0",
|
||||
"@wordpress/icons": "wp-6.0",
|
||||
"@wordpress/icons": "10.6.0",
|
||||
"@wordpress/interface": "wp-6.0",
|
||||
"@wordpress/keyboard-shortcuts": "wp-6.0",
|
||||
"@wordpress/keycodes": "wp-6.0",
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
export const LAYOUT_GRID = 'grid';
|
||||
export const LAYOUT_TABLE = 'table';
|
||||
export const LAYOUT_LIST = 'list';
|
||||
|
||||
export const OPERATOR_IS = 'is';
|
||||
export const OPERATOR_IS_NOT = 'isNot';
|
||||
export const OPERATOR_IS_ANY = 'isAny';
|
||||
export const OPERATOR_IS_NONE = 'isNone';
|
|
@ -13,12 +13,16 @@ import {
|
|||
* Internal dependencies
|
||||
*/
|
||||
import { unlock } from '../lock-unlock';
|
||||
import useLayoutAreas from './router';
|
||||
import { Layout } from './layout';
|
||||
|
||||
const { RouterProvider } = unlock( routerPrivateApis );
|
||||
const { GlobalStylesProvider } = unlock( editorPrivateApis );
|
||||
|
||||
function ProductsLayout() {
|
||||
return <div>Initial Products Layout</div>;
|
||||
// This ensures the edited entity id and type are initialized properly.
|
||||
const route = useLayoutAreas();
|
||||
return <Layout route={ route } />;
|
||||
}
|
||||
|
||||
export function ProductsApp() {
|
||||
|
|
|
@ -0,0 +1,118 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { createElement, Fragment, useRef } from '@wordpress/element';
|
||||
import {
|
||||
useViewportMatch,
|
||||
useResizeObserver,
|
||||
useReducedMotion,
|
||||
} from '@wordpress/compose';
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import {
|
||||
// @ts-expect-error missing type.
|
||||
EditorSnackbars,
|
||||
// @ts-expect-error missing type.
|
||||
privateApis as editorPrivateApis,
|
||||
} from '@wordpress/editor';
|
||||
// eslint-disable-next-line @woocommerce/dependency-group
|
||||
import {
|
||||
// @ts-expect-error missing type.
|
||||
__unstableMotion as motion,
|
||||
// @ts-expect-error missing type.
|
||||
__unstableAnimatePresence as AnimatePresence,
|
||||
} from '@wordpress/components';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import SidebarContent from './sidebar';
|
||||
import SiteHub from './site-hub';
|
||||
import { Route } from './router';
|
||||
import { unlock } from '../lock-unlock';
|
||||
|
||||
const { NavigableRegion } = unlock( editorPrivateApis );
|
||||
|
||||
const ANIMATION_DURATION = 0.3;
|
||||
|
||||
type LayoutProps = {
|
||||
route: Route;
|
||||
};
|
||||
|
||||
export function Layout( { route }: LayoutProps ) {
|
||||
const [ fullResizer ] = useResizeObserver();
|
||||
const toggleRef = useRef< HTMLAnchorElement >( null );
|
||||
const isMobileViewport = useViewportMatch( 'medium', '<' );
|
||||
const disableMotion = useReducedMotion();
|
||||
|
||||
const { key: routeKey, areas, widths } = route;
|
||||
|
||||
return (
|
||||
<>
|
||||
{ fullResizer }
|
||||
<div className="edit-site-layout">
|
||||
<div className="edit-site-layout__content">
|
||||
{ /*
|
||||
The NavigableRegion must always be rendered and not use
|
||||
`inert` otherwise `useNavigateRegions` will fail.
|
||||
*/ }
|
||||
{ ( ! isMobileViewport || ! areas.mobile ) && (
|
||||
<NavigableRegion
|
||||
ariaLabel={ __( 'Navigation', 'woocommerce' ) }
|
||||
className="edit-site-layout__sidebar-region"
|
||||
>
|
||||
<AnimatePresence>
|
||||
<motion.div
|
||||
initial={ { opacity: 0 } }
|
||||
animate={ { opacity: 1 } }
|
||||
exit={ { opacity: 0 } }
|
||||
transition={ {
|
||||
type: 'tween',
|
||||
duration:
|
||||
// Disable transition in mobile to emulate a full page transition.
|
||||
disableMotion || isMobileViewport
|
||||
? 0
|
||||
: ANIMATION_DURATION,
|
||||
ease: 'easeOut',
|
||||
} }
|
||||
className="edit-site-layout__sidebar"
|
||||
>
|
||||
<SiteHub
|
||||
ref={ toggleRef }
|
||||
isTransparent={ false }
|
||||
/>
|
||||
<SidebarContent routeKey={ routeKey }>
|
||||
{ areas.sidebar }
|
||||
</SidebarContent>
|
||||
</motion.div>
|
||||
</AnimatePresence>
|
||||
</NavigableRegion>
|
||||
) }
|
||||
|
||||
<EditorSnackbars />
|
||||
|
||||
{ ! isMobileViewport && areas.content && (
|
||||
<div
|
||||
className="edit-site-layout__area"
|
||||
style={ {
|
||||
maxWidth: widths?.content,
|
||||
} }
|
||||
>
|
||||
{ areas.content }
|
||||
</div>
|
||||
) }
|
||||
|
||||
{ ! isMobileViewport && areas.edit && (
|
||||
<div
|
||||
className="edit-site-layout__area"
|
||||
style={ {
|
||||
maxWidth: widths?.edit,
|
||||
} }
|
||||
>
|
||||
{ areas.edit }
|
||||
</div>
|
||||
) }
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,343 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { Action, DataViews, View } from '@wordpress/dataviews';
|
||||
import {
|
||||
createElement,
|
||||
useState,
|
||||
useMemo,
|
||||
useCallback,
|
||||
useEffect,
|
||||
} from '@wordpress/element';
|
||||
import { Product, ProductQuery } from '@woocommerce/data';
|
||||
import { drawerRight } from '@wordpress/icons';
|
||||
import { privateApis as routerPrivateApis } from '@wordpress/router';
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { useSelect } from '@wordpress/data';
|
||||
import classNames from 'classnames';
|
||||
import {
|
||||
// @ts-expect-error missing types.
|
||||
__experimentalHeading as Heading,
|
||||
// @ts-expect-error missing types.
|
||||
__experimentalText as Text,
|
||||
// @ts-expect-error missing types.
|
||||
__experimentalHStack as HStack,
|
||||
// @ts-expect-error missing types.
|
||||
__experimentalVStack as VStack,
|
||||
FlexItem,
|
||||
Button,
|
||||
} from '@wordpress/components';
|
||||
// @ts-expect-error missing type.
|
||||
// eslint-disable-next-line @woocommerce/dependency-group
|
||||
import { privateApis as editorPrivateApis } from '@wordpress/editor';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { unlock } from '../../lock-unlock';
|
||||
import {
|
||||
useDefaultViews,
|
||||
defaultLayouts,
|
||||
} from '../sidebar-dataviews/default-views';
|
||||
import { LAYOUT_LIST, OPERATOR_IS } from '../constants';
|
||||
|
||||
const { NavigableRegion } = unlock( editorPrivateApis );
|
||||
const { useHistory, useLocation } = unlock( routerPrivateApis );
|
||||
|
||||
const STATUSES = [
|
||||
{ value: 'draft', label: __( 'Draft', 'woocommerce' ) },
|
||||
{ value: 'future', label: __( 'Scheduled', 'woocommerce' ) },
|
||||
{ value: 'private', label: __( 'Private', 'woocommerce' ) },
|
||||
{ value: 'publish', label: __( 'Published', 'woocommerce' ) },
|
||||
{ value: 'trash', label: __( 'Trash', 'woocommerce' ) },
|
||||
];
|
||||
|
||||
/**
|
||||
* TODO: auto convert some of the product editor blocks ( from the blocks directory ) to this format.
|
||||
* The edit function should work relatively well with the edit from the blocks, the only difference is that the blocks rely on getEntityProp to get the value
|
||||
*/
|
||||
const fields = [
|
||||
{
|
||||
id: 'name',
|
||||
label: __( 'Name', 'woocommerce' ),
|
||||
enableHiding: false,
|
||||
type: 'text',
|
||||
render: function nameRender( { item }: { item: Product } ) {
|
||||
return item.name;
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'sku',
|
||||
label: __( 'SKU', 'woocommerce' ),
|
||||
enableHiding: false,
|
||||
enableSorting: false,
|
||||
render: ( { item }: { item: Product } ) => {
|
||||
return item.sku;
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'date',
|
||||
label: __( 'Date', 'woocommerce' ),
|
||||
render: ( { item }: { item: Product } ) => {
|
||||
return <time>{ item.date_created }</time>;
|
||||
},
|
||||
},
|
||||
{
|
||||
label: __( 'Status', 'woocommerce' ),
|
||||
id: 'status',
|
||||
getValue: ( { item }: { item: Product } ) =>
|
||||
STATUSES.find( ( { value } ) => value === item.status )?.label ??
|
||||
item.status,
|
||||
elements: STATUSES,
|
||||
filterBy: {
|
||||
operators: [ OPERATOR_IS ],
|
||||
},
|
||||
enableSorting: false,
|
||||
},
|
||||
];
|
||||
|
||||
export type ProductListProps = {
|
||||
subTitle?: string;
|
||||
className?: string;
|
||||
hideTitleFromUI?: boolean;
|
||||
postType?: string;
|
||||
};
|
||||
|
||||
const PAGE_SIZE = 25;
|
||||
const EMPTY_ARRAY: Product[] = [];
|
||||
const EMPTY_ACTIONS_ARRAY: Action< Product >[] = [];
|
||||
|
||||
const getDefaultView = (
|
||||
defaultViews: Array< { slug: string; view: View } >,
|
||||
activeView: string
|
||||
) => {
|
||||
return defaultViews.find( ( { slug } ) => slug === activeView )?.view;
|
||||
};
|
||||
|
||||
/**
|
||||
* This function abstracts working with default & custom views by
|
||||
* providing a [ state, setState ] tuple based on the URL parameters.
|
||||
*
|
||||
* Consumers use the provided tuple to work with state
|
||||
* and don't have to deal with the specifics of default & custom views.
|
||||
*
|
||||
* @param {string} postType Post type to retrieve default views for.
|
||||
* @return {Array} The [ state, setState ] tuple.
|
||||
*/
|
||||
function useView(
|
||||
postType: string
|
||||
): [ View, ( view: View ) => void, ( view: View ) => void ] {
|
||||
const {
|
||||
params: { activeView = 'all', isCustom = 'false', layout },
|
||||
} = useLocation();
|
||||
const history = useHistory();
|
||||
|
||||
const defaultViews = useDefaultViews( { postType } );
|
||||
const [ view, setView ] = useState< View >( () => {
|
||||
const initialView = getDefaultView( defaultViews, activeView ) ?? {
|
||||
type: layout ?? LAYOUT_LIST,
|
||||
};
|
||||
|
||||
const type = layout ?? initialView.type;
|
||||
return {
|
||||
...initialView,
|
||||
type,
|
||||
};
|
||||
} );
|
||||
|
||||
const setViewWithUrlUpdate = useCallback(
|
||||
( newView: View ) => {
|
||||
const { params } = history.getLocationWithParams();
|
||||
|
||||
if ( newView.type === LAYOUT_LIST && ! params?.layout ) {
|
||||
// Skip updating the layout URL param if
|
||||
// it is not present and the newView.type is LAYOUT_LIST.
|
||||
} else if ( newView.type !== params?.layout ) {
|
||||
history.push( {
|
||||
...params,
|
||||
layout: newView.type,
|
||||
} );
|
||||
}
|
||||
|
||||
setView( newView );
|
||||
},
|
||||
[ history, isCustom ]
|
||||
);
|
||||
|
||||
// When layout URL param changes, update the view type
|
||||
// without affecting any other config.
|
||||
useEffect( () => {
|
||||
setView( ( prevView ) => ( {
|
||||
...prevView,
|
||||
type: layout ?? LAYOUT_LIST,
|
||||
} ) );
|
||||
}, [ layout ] );
|
||||
|
||||
// When activeView or isCustom URL parameters change, reset the view.
|
||||
useEffect( () => {
|
||||
const newView = getDefaultView( defaultViews, activeView );
|
||||
|
||||
if ( newView ) {
|
||||
const type = layout ?? newView.type;
|
||||
setView( {
|
||||
...newView,
|
||||
type,
|
||||
} );
|
||||
}
|
||||
}, [ activeView, isCustom, layout, defaultViews ] );
|
||||
|
||||
return [ view, setViewWithUrlUpdate, setViewWithUrlUpdate ];
|
||||
}
|
||||
|
||||
function getItemId( item: Product ) {
|
||||
return item.id.toString();
|
||||
}
|
||||
|
||||
export default function ProductList( {
|
||||
subTitle,
|
||||
className,
|
||||
hideTitleFromUI = false,
|
||||
}: ProductListProps ) {
|
||||
const history = useHistory();
|
||||
const location = useLocation();
|
||||
const {
|
||||
postId,
|
||||
quickEdit = false,
|
||||
postType = 'product',
|
||||
isCustom,
|
||||
activeView = 'all',
|
||||
} = location.params;
|
||||
const [ selection, setSelection ] = useState( [ postId ] );
|
||||
const [ view, setView ] = useView( postType );
|
||||
|
||||
const queryParams = useMemo( () => {
|
||||
const filters: Partial< ProductQuery > = {};
|
||||
view.filters?.forEach( ( filter ) => {
|
||||
if ( filter.field === 'status' ) {
|
||||
filters.status = Array.isArray( filter.value )
|
||||
? filter.value.join( ',' )
|
||||
: filter.value;
|
||||
}
|
||||
} );
|
||||
const orderby =
|
||||
view.sort?.field === 'name' ? 'title' : view.sort?.field;
|
||||
|
||||
return {
|
||||
per_page: view.perPage,
|
||||
page: view.page,
|
||||
order: view.sort?.direction,
|
||||
orderby,
|
||||
search: view.search,
|
||||
...filters,
|
||||
};
|
||||
}, [ location.params, view ] );
|
||||
|
||||
const onChangeSelection = useCallback(
|
||||
( items ) => {
|
||||
setSelection( items );
|
||||
history.push( {
|
||||
...location.params,
|
||||
postId: items.join( ',' ),
|
||||
} );
|
||||
},
|
||||
[ history, location.params, view?.type ]
|
||||
);
|
||||
|
||||
// TODO: Use the Woo data store to get all the products, as this doesn't contain all the product data.
|
||||
const { records, totalCount, isLoading } = useSelect(
|
||||
( select ) => {
|
||||
const { getProducts, getProductsTotalCount, isResolving } =
|
||||
select( 'wc/admin/products' );
|
||||
return {
|
||||
records: getProducts( queryParams ) as Product[],
|
||||
totalCount: getProductsTotalCount( queryParams ) as number,
|
||||
isLoading: isResolving( 'getProducts', [ queryParams ] ),
|
||||
};
|
||||
},
|
||||
[ queryParams ]
|
||||
);
|
||||
|
||||
const paginationInfo = useMemo(
|
||||
() => ( {
|
||||
totalItems: totalCount,
|
||||
totalPages: Math.ceil( totalCount / ( view.perPage || PAGE_SIZE ) ),
|
||||
} ),
|
||||
[ totalCount, view.perPage ]
|
||||
);
|
||||
|
||||
const classes = classNames( 'edit-site-page', className );
|
||||
|
||||
return (
|
||||
<NavigableRegion
|
||||
className={ classes }
|
||||
ariaLabel={ __( 'Products', 'woocommerce' ) }
|
||||
>
|
||||
<div className="edit-site-page-content">
|
||||
{ ! hideTitleFromUI && (
|
||||
<VStack
|
||||
className="edit-site-page-header"
|
||||
as="header"
|
||||
spacing={ 0 }
|
||||
>
|
||||
<HStack className="edit-site-page-header__page-title">
|
||||
<Heading
|
||||
as="h2"
|
||||
level={ 3 }
|
||||
weight={ 500 }
|
||||
className="edit-site-page-header__title"
|
||||
truncate
|
||||
>
|
||||
{ __( 'Products', 'woocommerce' ) }
|
||||
</Heading>
|
||||
<FlexItem className="edit-site-page-header__actions">
|
||||
{ /* { actions } */ }
|
||||
</FlexItem>
|
||||
</HStack>
|
||||
{ subTitle && (
|
||||
<Text
|
||||
variant="muted"
|
||||
as="p"
|
||||
className="edit-site-page-header__sub-title"
|
||||
>
|
||||
{ subTitle }
|
||||
</Text>
|
||||
) }
|
||||
</VStack>
|
||||
) }
|
||||
<DataViews
|
||||
key={ activeView + isCustom }
|
||||
paginationInfo={ paginationInfo }
|
||||
// @ts-expect-error types seem rather strict for this still.
|
||||
fields={ fields }
|
||||
actions={ EMPTY_ACTIONS_ARRAY }
|
||||
data={ records || EMPTY_ARRAY }
|
||||
isLoading={ isLoading }
|
||||
view={ view }
|
||||
onChangeView={ setView }
|
||||
onChangeSelection={ onChangeSelection }
|
||||
getItemId={ getItemId }
|
||||
selection={ selection }
|
||||
defaultLayouts={ defaultLayouts }
|
||||
header={
|
||||
<Button
|
||||
// @ts-expect-error outdated type.
|
||||
size="compact"
|
||||
isPressed={ quickEdit }
|
||||
icon={ drawerRight }
|
||||
label={ __(
|
||||
'Toggle details panel',
|
||||
'woocommerce'
|
||||
) }
|
||||
onClick={ () => {
|
||||
history.push( {
|
||||
...location.params,
|
||||
quickEdit: quickEdit ? undefined : true,
|
||||
} );
|
||||
} }
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</NavigableRegion>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,68 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { createElement } from '@wordpress/element';
|
||||
import { privateApis as routerPrivateApis } from '@wordpress/router';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { unlock } from '../lock-unlock';
|
||||
import ProductList from './product-list';
|
||||
import DataViewsSidebarContent from './sidebar-dataviews';
|
||||
import SidebarNavigationScreen from './sidebar-navigation-screen';
|
||||
|
||||
const { useLocation } = unlock( routerPrivateApis );
|
||||
|
||||
export type Route = {
|
||||
key: string;
|
||||
areas: {
|
||||
sidebar: React.JSX.Element | React.FunctionComponent;
|
||||
content?: React.JSX.Element | React.FunctionComponent;
|
||||
edit?: React.JSX.Element | React.FunctionComponent;
|
||||
mobile?: React.JSX.Element | React.FunctionComponent | boolean;
|
||||
preview?: boolean;
|
||||
};
|
||||
widths?: {
|
||||
content?: number;
|
||||
edit?: number;
|
||||
sidebar?: number;
|
||||
};
|
||||
};
|
||||
|
||||
export default function useLayoutAreas() {
|
||||
const { params = {} } = useLocation();
|
||||
const { postType = 'product', layout = 'table', canvas } = params;
|
||||
// Products list.
|
||||
if ( [ 'product' ].includes( postType ) ) {
|
||||
const isListLayout = layout === 'list' || ! layout;
|
||||
return {
|
||||
key: 'products-list',
|
||||
areas: {
|
||||
sidebar: (
|
||||
<SidebarNavigationScreen
|
||||
title={ 'Products' }
|
||||
isRoot
|
||||
content={ <DataViewsSidebarContent /> }
|
||||
/>
|
||||
),
|
||||
content: <ProductList />,
|
||||
preview: false,
|
||||
mobile: <ProductList postType={ postType } />,
|
||||
},
|
||||
widths: {
|
||||
content: isListLayout ? 380 : undefined,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
// Fallback shows the home page preview
|
||||
return {
|
||||
key: 'default',
|
||||
areas: {
|
||||
sidebar: () => null,
|
||||
preview: false,
|
||||
mobile: canvas === 'edit',
|
||||
},
|
||||
};
|
||||
}
|
|
@ -0,0 +1,110 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { createElement } from '@wordpress/element';
|
||||
import classNames from 'classnames';
|
||||
import { privateApis as routerPrivateApis } from '@wordpress/router';
|
||||
import { addQueryArgs, getQueryArgs, removeQueryArgs } from '@wordpress/url';
|
||||
import { VIEW_LAYOUTS } from '@wordpress/dataviews';
|
||||
// @ts-expect-error missing type.
|
||||
// eslint-disable-next-line @woocommerce/dependency-group
|
||||
import { __experimentalHStack as HStack } from '@wordpress/components';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import SidebarNavigationItem from '../sidebar-navigation-item';
|
||||
import { unlock } from '../../lock-unlock';
|
||||
|
||||
const { useHistory, useLocation } = unlock( routerPrivateApis );
|
||||
|
||||
type DataViewItemProps = {
|
||||
title: string;
|
||||
slug: string;
|
||||
customViewId?: string;
|
||||
type: string;
|
||||
icon: React.JSX.Element;
|
||||
isActive: boolean;
|
||||
isCustom: boolean;
|
||||
suffix?: string;
|
||||
};
|
||||
|
||||
function useLink(
|
||||
params: Record< string, string | undefined >,
|
||||
state?: Record< string, string | undefined >,
|
||||
shouldReplace = false
|
||||
) {
|
||||
const history = useHistory();
|
||||
function onClick( event: Event ) {
|
||||
event?.preventDefault();
|
||||
|
||||
if ( shouldReplace ) {
|
||||
history.replace( params, state );
|
||||
} else {
|
||||
history.push( params, state );
|
||||
}
|
||||
}
|
||||
|
||||
const currentArgs = getQueryArgs( window.location.href );
|
||||
const currentUrlWithoutArgs = removeQueryArgs(
|
||||
window.location.href,
|
||||
...Object.keys( currentArgs )
|
||||
);
|
||||
|
||||
const newUrl = addQueryArgs( currentUrlWithoutArgs, params );
|
||||
|
||||
return {
|
||||
href: newUrl,
|
||||
onClick,
|
||||
};
|
||||
}
|
||||
|
||||
export default function DataViewItem( {
|
||||
title,
|
||||
slug,
|
||||
customViewId,
|
||||
type,
|
||||
icon,
|
||||
isActive,
|
||||
isCustom,
|
||||
suffix,
|
||||
}: DataViewItemProps ) {
|
||||
const {
|
||||
params: { postType, page },
|
||||
} = useLocation();
|
||||
|
||||
const iconToUse =
|
||||
icon || VIEW_LAYOUTS.find( ( v ) => v.type === type )?.icon;
|
||||
|
||||
let activeView: undefined | string = isCustom ? customViewId : slug;
|
||||
if ( activeView === 'all' ) {
|
||||
activeView = undefined;
|
||||
}
|
||||
const linkInfo = useLink( {
|
||||
page,
|
||||
postType,
|
||||
layout: type,
|
||||
activeView,
|
||||
isCustom: isCustom ? 'true' : undefined,
|
||||
} );
|
||||
return (
|
||||
<HStack
|
||||
justify="flex-start"
|
||||
className={ classNames(
|
||||
'edit-site-sidebar-dataviews-dataview-item',
|
||||
{
|
||||
'is-selected': isActive,
|
||||
}
|
||||
) }
|
||||
>
|
||||
<SidebarNavigationItem
|
||||
icon={ iconToUse }
|
||||
{ ...linkInfo }
|
||||
aria-current={ isActive ? 'true' : undefined }
|
||||
>
|
||||
{ title }
|
||||
</SidebarNavigationItem>
|
||||
{ suffix }
|
||||
</HStack>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,179 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { useSelect } from '@wordpress/data';
|
||||
import { store as coreStore } from '@wordpress/core-data';
|
||||
import { useMemo } from '@wordpress/element';
|
||||
import {
|
||||
trash,
|
||||
pages,
|
||||
drafts,
|
||||
published,
|
||||
scheduled,
|
||||
notAllowed,
|
||||
} from '@wordpress/icons';
|
||||
import type { ColumnStyle, ViewTable } from '@wordpress/dataviews';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import {
|
||||
LAYOUT_LIST,
|
||||
LAYOUT_TABLE,
|
||||
LAYOUT_GRID,
|
||||
OPERATOR_IS,
|
||||
} from '../constants';
|
||||
|
||||
export const defaultLayouts: Record<
|
||||
string,
|
||||
{
|
||||
layout: {
|
||||
primaryField: string;
|
||||
mediaField?: string;
|
||||
styles?: Record< string, ColumnStyle >;
|
||||
};
|
||||
}
|
||||
> = {
|
||||
[ LAYOUT_TABLE ]: {
|
||||
layout: {
|
||||
primaryField: 'name',
|
||||
styles: {
|
||||
name: {
|
||||
maxWidth: 300,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
[ LAYOUT_GRID ]: {
|
||||
layout: {
|
||||
mediaField: 'featured-image',
|
||||
primaryField: 'name',
|
||||
},
|
||||
},
|
||||
[ LAYOUT_LIST ]: {
|
||||
layout: {
|
||||
primaryField: 'name',
|
||||
mediaField: 'featured-image',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const DEFAULT_POST_BASE: Omit< ViewTable, 'view' | 'title' | 'slug' | 'icon' > =
|
||||
{
|
||||
type: LAYOUT_TABLE,
|
||||
search: '',
|
||||
filters: [],
|
||||
page: 1,
|
||||
perPage: 20,
|
||||
sort: {
|
||||
field: 'date',
|
||||
direction: 'desc',
|
||||
},
|
||||
fields: [ 'name', 'sku', 'status', 'date' ],
|
||||
layout: defaultLayouts[ LAYOUT_LIST ].layout,
|
||||
};
|
||||
|
||||
export function useDefaultViews( { postType }: { postType: string } ): Array< {
|
||||
title: string;
|
||||
slug: string;
|
||||
icon: React.JSX.Element;
|
||||
view: ViewTable;
|
||||
} > {
|
||||
const labels = useSelect(
|
||||
( select ) => {
|
||||
const { getPostType } = select( coreStore );
|
||||
const postTypeData: { labels?: Record< string, string > } =
|
||||
getPostType( postType );
|
||||
return postTypeData?.labels;
|
||||
},
|
||||
[ postType ]
|
||||
);
|
||||
return useMemo( () => {
|
||||
return [
|
||||
{
|
||||
title: labels?.all_items || __( 'All items', 'woocommerce' ),
|
||||
slug: 'all',
|
||||
icon: pages,
|
||||
view: { ...DEFAULT_POST_BASE },
|
||||
},
|
||||
{
|
||||
title: __( 'Published', 'woocommerce' ),
|
||||
slug: 'published',
|
||||
icon: published,
|
||||
view: {
|
||||
...DEFAULT_POST_BASE,
|
||||
filters: [
|
||||
{
|
||||
field: 'status',
|
||||
operator: OPERATOR_IS,
|
||||
value: 'publish',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
title: __( 'Scheduled', 'woocommerce' ),
|
||||
slug: 'future',
|
||||
icon: scheduled,
|
||||
view: {
|
||||
...DEFAULT_POST_BASE,
|
||||
filters: [
|
||||
{
|
||||
field: 'status',
|
||||
operator: OPERATOR_IS,
|
||||
value: 'future',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
title: __( 'Drafts', 'woocommerce' ),
|
||||
slug: 'drafts',
|
||||
icon: drafts,
|
||||
view: {
|
||||
...DEFAULT_POST_BASE,
|
||||
filters: [
|
||||
{
|
||||
field: 'status',
|
||||
operator: OPERATOR_IS,
|
||||
value: 'draft',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
title: __( 'Private', 'woocommerce' ),
|
||||
slug: 'private',
|
||||
icon: notAllowed,
|
||||
view: {
|
||||
...DEFAULT_POST_BASE,
|
||||
filters: [
|
||||
{
|
||||
field: 'status',
|
||||
operator: OPERATOR_IS,
|
||||
value: 'private',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
title: __( 'Trash', 'woocommerce' ),
|
||||
slug: 'trash',
|
||||
icon: trash,
|
||||
view: {
|
||||
...DEFAULT_POST_BASE,
|
||||
type: LAYOUT_TABLE,
|
||||
layout: defaultLayouts[ LAYOUT_TABLE ].layout,
|
||||
filters: [
|
||||
{
|
||||
field: 'status',
|
||||
operator: OPERATOR_IS,
|
||||
value: 'trash',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
];
|
||||
}, [ labels ] );
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { createElement, Fragment } from '@wordpress/element';
|
||||
import { privateApis as routerPrivateApis } from '@wordpress/router';
|
||||
// @ts-expect-error missing type.
|
||||
// eslint-disable-next-line @wordpress/no-unsafe-wp-apis, @woocommerce/dependency-group
|
||||
import { __experimentalItemGroup as ItemGroup } from '@wordpress/components';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { unlock } from '../../lock-unlock';
|
||||
import DataViewItem from './dataview-item';
|
||||
import { useDefaultViews } from './default-views';
|
||||
|
||||
const { useLocation } = unlock( routerPrivateApis );
|
||||
|
||||
export default function DataViewsSidebarContent() {
|
||||
const {
|
||||
params: {
|
||||
postType = 'product',
|
||||
activeView = 'all',
|
||||
isCustom = 'false',
|
||||
},
|
||||
} = useLocation();
|
||||
const defaultViews = useDefaultViews( { postType } );
|
||||
if ( ! postType ) {
|
||||
return null;
|
||||
}
|
||||
const isCustomBoolean = isCustom === 'true';
|
||||
|
||||
return (
|
||||
<>
|
||||
<ItemGroup>
|
||||
{ defaultViews.map( ( dataview ) => {
|
||||
return (
|
||||
<DataViewItem
|
||||
key={ dataview.slug }
|
||||
slug={ dataview.slug }
|
||||
title={ dataview.title }
|
||||
icon={ dataview.icon }
|
||||
type={ dataview.view.type }
|
||||
isActive={
|
||||
! isCustomBoolean &&
|
||||
dataview.slug === activeView
|
||||
}
|
||||
isCustom={ false }
|
||||
/>
|
||||
);
|
||||
} ) }
|
||||
</ItemGroup>
|
||||
</>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
.edit-site-sidebar-navigation-screen__title-icon {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
background: $gray-900;
|
||||
padding-top: $grid-unit-60;
|
||||
margin-bottom: $grid-unit-10;
|
||||
padding-bottom: $grid-unit-10;
|
||||
}
|
||||
|
||||
.edit-site-sidebar-button {
|
||||
color: #e0e0e0;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.edit-site-sidebar-navigation-screen__title {
|
||||
flex-grow: 1;
|
||||
overflow-wrap: break-word;
|
||||
padding: 2px 0 0
|
||||
}
|
|
@ -0,0 +1,88 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { isRTL } from '@wordpress/i18n';
|
||||
import { chevronRightSmall, chevronLeftSmall, Icon } from '@wordpress/icons';
|
||||
import { privateApis as routerPrivateApis } from '@wordpress/router';
|
||||
import classNames from 'classnames';
|
||||
import { createElement } from '@wordpress/element';
|
||||
import {
|
||||
// @ts-expect-error missing type.
|
||||
__experimentalItem as Item,
|
||||
// @ts-expect-error missing type.
|
||||
__experimentalHStack as HStack,
|
||||
FlexBlock,
|
||||
} from '@wordpress/components';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { unlock } from '../../lock-unlock';
|
||||
|
||||
const { useHistory } = unlock( routerPrivateApis );
|
||||
|
||||
type SidebarNavigationItemProps = {
|
||||
className?: string;
|
||||
icon?: React.JSX.Element;
|
||||
suffix?: string;
|
||||
withChevron?: boolean;
|
||||
uid?: string;
|
||||
params?: Record< string, string >;
|
||||
onClick?: ( e: Event ) => void;
|
||||
children: React.ReactNode;
|
||||
};
|
||||
|
||||
export default function SidebarNavigationItem( {
|
||||
className,
|
||||
icon,
|
||||
withChevron = false,
|
||||
suffix,
|
||||
uid,
|
||||
params,
|
||||
onClick,
|
||||
children,
|
||||
...props
|
||||
}: SidebarNavigationItemProps ) {
|
||||
const history = useHistory();
|
||||
// If there is no custom click handler, create one that navigates to `params`.
|
||||
function handleClick( e: Event ) {
|
||||
if ( onClick ) {
|
||||
onClick( e );
|
||||
} else if ( params ) {
|
||||
e.preventDefault();
|
||||
history.push( params );
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Item
|
||||
className={ classNames(
|
||||
'edit-site-sidebar-navigation-item',
|
||||
{ 'with-suffix': ! withChevron && suffix },
|
||||
className
|
||||
) }
|
||||
onClick={ handleClick }
|
||||
id={ uid }
|
||||
{ ...props }
|
||||
>
|
||||
<HStack justify="flex-start">
|
||||
{ icon && (
|
||||
<Icon
|
||||
style={ { fill: 'currentcolor' } }
|
||||
icon={ icon }
|
||||
size={ 24 }
|
||||
/>
|
||||
) }
|
||||
<FlexBlock>{ children }</FlexBlock>
|
||||
{ withChevron && (
|
||||
<Icon
|
||||
icon={ isRTL() ? chevronLeftSmall : chevronRightSmall }
|
||||
className="edit-site-sidebar-navigation-item__drilldown-indicator"
|
||||
size={ 24 }
|
||||
/>
|
||||
) }
|
||||
{ ! withChevron && suffix }
|
||||
</HStack>
|
||||
</Item>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,136 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
|
||||
import { isRTL, __ } from '@wordpress/i18n';
|
||||
import classNames from 'classnames';
|
||||
import { chevronRight, chevronLeft } from '@wordpress/icons';
|
||||
import { useSelect } from '@wordpress/data';
|
||||
import { privateApis as routerPrivateApis } from '@wordpress/router';
|
||||
import { createElement, Fragment } from '@wordpress/element';
|
||||
import {
|
||||
// @ts-expect-error missing type.
|
||||
__experimentalHStack as HStack,
|
||||
// @ts-expect-error missing type.
|
||||
__experimentalHeading as Heading,
|
||||
// @ts-expect-error missing type.
|
||||
__experimentalVStack as VStack,
|
||||
} from '@wordpress/components';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { unlock } from '../../lock-unlock';
|
||||
import SidebarButton from './sidebar-button';
|
||||
|
||||
const { useHistory, useLocation } = unlock( routerPrivateApis );
|
||||
|
||||
type SidebarNavigationScreenProps = {
|
||||
isRoot?: boolean;
|
||||
title: string;
|
||||
actions?: React.JSX.Element;
|
||||
meta?: string;
|
||||
content: React.JSX.Element;
|
||||
footer?: string;
|
||||
description?: string;
|
||||
backPath?: string;
|
||||
};
|
||||
|
||||
export default function SidebarNavigationScreen( {
|
||||
isRoot,
|
||||
title,
|
||||
actions,
|
||||
meta,
|
||||
content,
|
||||
footer,
|
||||
description,
|
||||
backPath: backPathProp,
|
||||
}: SidebarNavigationScreenProps ) {
|
||||
const { dashboardLink, dashboardLinkText } = useSelect( ( select ) => {
|
||||
const { getSettings } = unlock( select( 'core/edit-site' ) );
|
||||
return {
|
||||
dashboardLink: getSettings().__experimentalDashboardLink,
|
||||
dashboardLinkText: getSettings().__experimentalDashboardLinkText,
|
||||
};
|
||||
}, [] );
|
||||
const location = useLocation();
|
||||
const history = useHistory();
|
||||
const backPath = backPathProp ?? location.state?.backPath;
|
||||
const icon = isRTL() ? chevronRight : chevronLeft;
|
||||
|
||||
return (
|
||||
<>
|
||||
<VStack
|
||||
className={ classNames(
|
||||
'edit-site-sidebar-navigation-screen__main',
|
||||
{
|
||||
'has-footer': !! footer,
|
||||
}
|
||||
) }
|
||||
spacing={ 0 }
|
||||
justify="flex-start"
|
||||
>
|
||||
<HStack
|
||||
spacing={ 3 }
|
||||
alignment="flex-start"
|
||||
className="edit-site-sidebar-navigation-screen__title-icon"
|
||||
>
|
||||
{ ! isRoot && (
|
||||
<SidebarButton
|
||||
onClick={ () => {
|
||||
history.push( backPath );
|
||||
} }
|
||||
icon={ icon }
|
||||
label={ __( 'Back', 'woocommerce' ) }
|
||||
showTooltip={ false }
|
||||
/>
|
||||
) }
|
||||
{ isRoot && (
|
||||
<SidebarButton
|
||||
icon={ icon }
|
||||
label={
|
||||
dashboardLinkText ||
|
||||
__( 'Go to the Dashboard', 'woocommerce' )
|
||||
}
|
||||
href={ dashboardLink || 'index.php' }
|
||||
/>
|
||||
) }
|
||||
<Heading
|
||||
className="edit-site-sidebar-navigation-screen__title"
|
||||
color={ '#e0e0e0' /* $gray-200 */ }
|
||||
level={ 1 }
|
||||
size={ 20 }
|
||||
>
|
||||
{ title }
|
||||
</Heading>
|
||||
{ actions && (
|
||||
<div className="edit-site-sidebar-navigation-screen__actions">
|
||||
{ actions }
|
||||
</div>
|
||||
) }
|
||||
</HStack>
|
||||
{ meta && (
|
||||
<>
|
||||
<div className="edit-site-sidebar-navigation-screen__meta">
|
||||
{ meta }
|
||||
</div>
|
||||
</>
|
||||
) }
|
||||
|
||||
<div className="edit-site-sidebar-navigation-screen__content">
|
||||
{ description && (
|
||||
<p className="edit-site-sidebar-navigation-screen__description">
|
||||
{ description }
|
||||
</p>
|
||||
) }
|
||||
{ content }
|
||||
</div>
|
||||
</VStack>
|
||||
{ footer && (
|
||||
<footer className="edit-site-sidebar-navigation-screen__footer">
|
||||
{ footer }
|
||||
</footer>
|
||||
) }
|
||||
</>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { createElement } from '@wordpress/element';
|
||||
import { Button } from '@wordpress/components';
|
||||
import classNames from 'classnames';
|
||||
|
||||
export default function SidebarButton( props: Button.Props ) {
|
||||
return (
|
||||
<Button
|
||||
{ ...props }
|
||||
className={ classNames(
|
||||
'edit-site-sidebar-button',
|
||||
props.className
|
||||
) }
|
||||
/>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { createElement, useRef } from '@wordpress/element';
|
||||
|
||||
function SidebarContentWrapper( { children }: { children: React.ReactNode } ) {
|
||||
const wrapperRef = useRef< HTMLDivElement | null >( null );
|
||||
const wrapperCls = 'edit-site-sidebar__screen-wrapper';
|
||||
|
||||
return (
|
||||
<div ref={ wrapperRef } className={ wrapperCls }>
|
||||
{ children }
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default function SidebarContent( {
|
||||
routeKey,
|
||||
children,
|
||||
}: {
|
||||
routeKey: string;
|
||||
children: React.ReactNode;
|
||||
} ) {
|
||||
return (
|
||||
<div className="edit-site-sidebar__content">
|
||||
<SidebarContentWrapper key={ routeKey }>
|
||||
{ children }
|
||||
</SidebarContentWrapper>
|
||||
</div>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,114 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { createElement, memo, forwardRef } from '@wordpress/element';
|
||||
import classNames from 'classnames';
|
||||
import { useSelect } from '@wordpress/data';
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { store as coreStore } from '@wordpress/core-data';
|
||||
import { decodeEntities } from '@wordpress/html-entities';
|
||||
import { filterURLForDisplay } from '@wordpress/url';
|
||||
import {
|
||||
Button,
|
||||
// @ts-expect-error missing types.
|
||||
__experimentalHStack as HStack,
|
||||
VisuallyHidden,
|
||||
} from '@wordpress/components';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import SiteIcon from './site-icon';
|
||||
import { unlock } from '../../lock-unlock';
|
||||
|
||||
const SiteHub = memo(
|
||||
forwardRef(
|
||||
(
|
||||
{ isTransparent }: { isTransparent: boolean },
|
||||
ref: React.Ref< HTMLAnchorElement >
|
||||
) => {
|
||||
const { dashboardLink, homeUrl, siteTitle } = useSelect(
|
||||
( select ) => {
|
||||
const { getSettings } = unlock(
|
||||
select( 'core/edit-site' )
|
||||
);
|
||||
|
||||
const {
|
||||
getSite,
|
||||
getUnstableBase, // Site index.
|
||||
} = select( coreStore );
|
||||
const _site: undefined | { title: string; url: string } =
|
||||
getSite();
|
||||
|
||||
const base: { home: string } | undefined =
|
||||
getUnstableBase();
|
||||
return {
|
||||
dashboardLink:
|
||||
getSettings().__experimentalDashboardLink ||
|
||||
'index.php',
|
||||
homeUrl: base?.home,
|
||||
siteTitle:
|
||||
! _site?.title && !! _site?.url
|
||||
? filterURLForDisplay( _site?.url )
|
||||
: _site?.title,
|
||||
};
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="edit-site-site-hub">
|
||||
<HStack justify="flex-start" spacing="0">
|
||||
<div
|
||||
className={ classNames(
|
||||
'edit-site-site-hub__view-mode-toggle-container',
|
||||
{
|
||||
'has-transparent-background': isTransparent,
|
||||
}
|
||||
) }
|
||||
>
|
||||
<Button
|
||||
ref={ ref }
|
||||
href={ dashboardLink }
|
||||
label={ __(
|
||||
'Go to the Dashboard',
|
||||
'woocommerce'
|
||||
) }
|
||||
className="edit-site-layout__view-mode-toggle"
|
||||
style={ {
|
||||
transform: 'scale(0.5)',
|
||||
borderRadius: 4,
|
||||
} }
|
||||
>
|
||||
<SiteIcon className="edit-site-layout__view-mode-toggle-icon" />
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<HStack>
|
||||
<div className="edit-site-site-hub__title">
|
||||
<Button
|
||||
variant="link"
|
||||
href={ homeUrl }
|
||||
target="_blank"
|
||||
>
|
||||
{ siteTitle && decodeEntities( siteTitle ) }
|
||||
<VisuallyHidden as="span">
|
||||
{
|
||||
/* translators: accessibility text */
|
||||
__(
|
||||
'(opens in a new tab)',
|
||||
'woocommerce'
|
||||
)
|
||||
}
|
||||
</VisuallyHidden>
|
||||
</Button>
|
||||
</div>
|
||||
</HStack>
|
||||
</HStack>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
export default SiteHub;
|
|
@ -0,0 +1,56 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { createElement } from '@wordpress/element';
|
||||
import { useSelect } from '@wordpress/data';
|
||||
import { Icon } from '@wordpress/components';
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { wordpress } from '@wordpress/icons';
|
||||
import { store as coreDataStore } from '@wordpress/core-data';
|
||||
import classNames from 'classnames';
|
||||
|
||||
type SiteIconProps = {
|
||||
className: string;
|
||||
};
|
||||
|
||||
function SiteIcon( { className }: SiteIconProps ) {
|
||||
const { isRequestingSite, siteIconUrl } = useSelect( ( select ) => {
|
||||
const { getEntityRecord } = select( coreDataStore );
|
||||
const siteData: { site_icon_url?: string } = getEntityRecord(
|
||||
'root',
|
||||
'__unstableBase',
|
||||
undefined
|
||||
);
|
||||
|
||||
return {
|
||||
isRequestingSite: ! siteData,
|
||||
siteIconUrl: siteData?.site_icon_url,
|
||||
};
|
||||
}, [] );
|
||||
|
||||
if ( isRequestingSite && ! siteIconUrl ) {
|
||||
return <div className="edit-site-site-icon__image" />;
|
||||
}
|
||||
|
||||
const icon = siteIconUrl ? (
|
||||
<img
|
||||
className="edit-site-site-icon__image"
|
||||
alt={ __( 'Site Icon', 'woocommerce' ) }
|
||||
src={ siteIconUrl }
|
||||
/>
|
||||
) : (
|
||||
<Icon
|
||||
className="edit-site-site-icon__icon"
|
||||
icon={ wordpress }
|
||||
size={ 48 }
|
||||
/>
|
||||
);
|
||||
|
||||
return (
|
||||
<div className={ classNames( className, 'edit-site-site-icon' ) }>
|
||||
{ icon }
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default SiteIcon;
|
|
@ -4,11 +4,12 @@
|
|||
.woocommerce_page_woocommerce-products-dashboard #adminmenumain {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.woocommerce_page_woocommerce-products-dashboard #wpcontent {
|
||||
margin-left: 0;
|
||||
}
|
||||
body.woocommerce_page_woocommerce-products-dashboard
|
||||
#woocommerce-products-dashboard {
|
||||
|
||||
body.woocommerce_page_woocommerce-products-dashboard #woocommerce-products-dashboard {
|
||||
@include wp-admin-reset("#woocommerce-products-dashboard");
|
||||
@include reset;
|
||||
display: block !important;
|
||||
|
@ -38,3 +39,5 @@ body.js.is-fullscreen-mode {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
@import "products-app/sidebar-dataviews/style.scss";
|
||||
|
|
|
@ -31,6 +31,7 @@ declare module '@wordpress/core-data' {
|
|||
isResolving: boolean;
|
||||
hasResolved: boolean;
|
||||
};
|
||||
const store: string;
|
||||
}
|
||||
declare module '@wordpress/keyboard-shortcuts' {
|
||||
function useShortcut(
|
||||
|
@ -43,3 +44,24 @@ declare module '@wordpress/keyboard-shortcuts' {
|
|||
declare module '@wordpress/router' {
|
||||
const privateApis;
|
||||
}
|
||||
|
||||
declare module '@wordpress/edit-site/build-module/components/sync-state-with-url/use-init-edited-entity-from-url' {
|
||||
export default function useInitEditedEntityFromURL(): void;
|
||||
}
|
||||
|
||||
declare module '@wordpress/edit-site/build-module/components/sidebar-navigation-screen' {
|
||||
const SidebarNavigationScreen: React.FunctionComponent< {
|
||||
title: string;
|
||||
isRoot: boolean;
|
||||
content: JSX.Element;
|
||||
} >;
|
||||
export default SidebarNavigationScreen;
|
||||
}
|
||||
|
||||
declare module '@wordpress/edit-site/build-module/components/site-hub' {
|
||||
const SiteHub: React.FunctionComponent< {
|
||||
ref: React.Ref;
|
||||
isTransparent: boolean;
|
||||
} >;
|
||||
export default SiteHub;
|
||||
}
|
||||
|
|
|
@ -186,6 +186,8 @@ const webpackConfig = {
|
|||
extensions: [ '.json', '.js', '.jsx', '.ts', '.tsx' ],
|
||||
alias: {
|
||||
'~': path.resolve( __dirname + '/client' ),
|
||||
'react/jsx-dev-runtime': require.resolve( 'react/jsx-dev-runtime' ),
|
||||
'react/jsx-runtime': require.resolve( 'react/jsx-runtime' ),
|
||||
},
|
||||
},
|
||||
plugins: [
|
||||
|
@ -240,6 +242,10 @@ const webpackConfig = {
|
|||
return null;
|
||||
}
|
||||
|
||||
if ( request.startsWith( '@wordpress/dataviews' ) ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ( request.startsWith( '@wordpress/edit-site' ) ) {
|
||||
// The external wp.editSite does not include edit-site components, so we need to skip requesting to external here. We can remove this once the edit-site components are exported in the external wp.editSite.
|
||||
// We use the edit-site components in the customize store.
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
Significance: minor
|
||||
Type: update
|
||||
|
||||
Update webpack config to bundle in @wordpress/dataviews package.
|
1242
pnpm-lock.yaml
1242
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue