From d3d28ec2da16c93ef508cf5c167ffa83b56c88bf Mon Sep 17 00:00:00 2001 From: Jorge Costa Date: Wed, 18 Oct 2023 23:00:52 +0100 Subject: [PATCH 1/5] Add: Ability to persist dataviews on the database. --- lib/experimental/data-views.php | 45 ++++ lib/load.php | 3 + .../edit-site/src/components/app/index.js | 7 +- .../src/components/dataviews/context.js | 7 + .../src/components/dataviews/provider.js | 253 ++++++++++++++++++ .../components/dataviews/sidebar-content.js | 3 + .../src/components/page-pages/index.js | 28 +- .../edit-site/src/components/sidebar/index.js | 2 + 8 files changed, 323 insertions(+), 25 deletions(-) create mode 100644 lib/experimental/data-views.php create mode 100644 packages/edit-site/src/components/dataviews/context.js create mode 100644 packages/edit-site/src/components/dataviews/provider.js create mode 100644 packages/edit-site/src/components/dataviews/sidebar-content.js diff --git a/lib/experimental/data-views.php b/lib/experimental/data-views.php new file mode 100644 index 00000000000000..073d850508626e --- /dev/null +++ b/lib/experimental/data-views.php @@ -0,0 +1,45 @@ + _x( 'Datavivews', 'post type general name', 'gutenberg' ), + 'description' => __( 'Post which stores the different data views configurations', 'gutenberg' ), + 'public' => false, + 'show_ui' => false, + 'show_in_rest' => true, + 'rewrite' => false, + 'capabilities' => array( + 'read' => 'edit_published_posts', + // 'create_posts' => 'edit_published_posts', + // 'edit_posts' => 'edit_published_posts', + // 'edit_published_posts' => 'edit_published_posts', + // 'delete_published_posts' => 'delete_published_posts', + // 'edit_others_posts' => 'edit_others_posts', + // 'delete_others_posts' => 'edit_theme_options', + ), + 'map_meta_cap' => true, + 'supports' => array( 'title', 'slug', 'editor' ), + ) + ); + + register_taxonomy( + 'wp_dataviews_type', + array( 'wp_dataviews' ), + array( + 'public' => true, + 'hierarchical' => false, + 'labels' => array( + 'name' => __( 'Dataview types' ), + 'singular_name' => __( 'Dataview type' ), + ), + 'rewrite' => false, + 'show_ui' => false, + 'show_in_nav_menus' => false, + 'show_in_rest' => true, + ) + ); +} + +add_action( 'init', '_gutenberg_register_data_views_post_type' ); diff --git a/lib/load.php b/lib/load.php index aa2f0d3f964ac8..3514421e06976f 100644 --- a/lib/load.php +++ b/lib/load.php @@ -256,3 +256,6 @@ function () { require __DIR__ . '/block-supports/shadow.php'; require __DIR__ . '/block-supports/background.php'; require __DIR__ . '/block-supports/behaviors.php'; + +// Data views +require_once __DIR__ . '/experimental/data-views.php'; diff --git a/packages/edit-site/src/components/app/index.js b/packages/edit-site/src/components/app/index.js index cad76b3ea1fb83..c190c7ef4445ee 100644 --- a/packages/edit-site/src/components/app/index.js +++ b/packages/edit-site/src/components/app/index.js @@ -14,6 +14,7 @@ import { privateApis as routerPrivateApis } from '@wordpress/router'; */ import Layout from '../layout'; import { GlobalStylesProvider } from '../global-styles/global-styles-provider'; +import DataviewsProvider from '../dataviews/provider'; import { unlock } from '../../lock-unlock'; const { RouterProvider } = unlock( routerPrivateApis ); @@ -38,8 +39,10 @@ export default function App() { - - + + + + diff --git a/packages/edit-site/src/components/dataviews/context.js b/packages/edit-site/src/components/dataviews/context.js new file mode 100644 index 00000000000000..b7304e3961d934 --- /dev/null +++ b/packages/edit-site/src/components/dataviews/context.js @@ -0,0 +1,7 @@ +/** + * WordPress dependencies + */ +import { createContext } from '@wordpress/element'; + +const DataviewsContext = createContext( {} ); +export default DataviewsContext; diff --git a/packages/edit-site/src/components/dataviews/provider.js b/packages/edit-site/src/components/dataviews/provider.js new file mode 100644 index 00000000000000..bfde67a18a9c32 --- /dev/null +++ b/packages/edit-site/src/components/dataviews/provider.js @@ -0,0 +1,253 @@ +/** + * WordPress dependencies + */ +import { privateApis as routerPrivateApis } from '@wordpress/router'; +import { useEffect, useState, useRef, useMemo } from '@wordpress/element'; +import { useDispatch, useSelect } from '@wordpress/data'; +import { store as coreStore } from '@wordpress/core-data'; + +/** + * Internal dependencies + */ +import { unlock } from '../../lock-unlock'; +import DataviewsContext from './context'; + +const { useLocation } = unlock( routerPrivateApis ); + +// DEFAULT_STATUSES is intentionally sorted. Items do not have spaces in between them. +// The reason for that is to match the default statuses coming from the endpoint +// (entity request and useEffect to update the view). +export const DEFAULT_STATUSES = 'draft,future,pending,private,publish'; // All statuses but 'trash'. + +const DEFAULT_VIEWS = { + page: { + type: 'list', + filters: { + search: '', + status: DEFAULT_STATUSES, + }, + page: 1, + perPage: 5, + sort: { + field: 'date', + direction: 'desc', + }, + visibleFilters: [ 'search', 'author', 'status' ], + // All fields are visible by default, so it's + // better to keep track of the hidden ones. + hiddenFields: [ 'date', 'featured-image' ], + layout: {}, + }, +}; + +const PATH_TO_DATAVIEW_TYPE = { + '/pages': 'page', +}; + +function useDataviewTypeTaxonomyId( type ) { + const isCreatingATerm = useRef( false ); + const { + dataViewTypeRecords, + dataViewTypeIsResolving, + dataViewTypeIsSaving, + } = useSelect( + ( select ) => { + const { getEntityRecords, isResolving, isSavingEntityRecord } = + select( coreStore ); + const dataViewTypeQuery = { slug: type }; + return { + dataViewTypeRecords: getEntityRecords( + 'taxonomy', + 'wp_dataviews_type', + dataViewTypeQuery + ), + dataViewTypeIsResolving: isResolving( 'getEntityRecords', [ + 'taxonomy', + 'wp_dataviews_type', + dataViewTypeQuery, + ] ), + dataViewTypeIsSaving: isSavingEntityRecord( + 'taxonomy', + 'wp_dataviews_type' + ), + }; + }, + [ type ] + ); + const { saveEntityRecord } = useDispatch( coreStore ); + useEffect( () => { + if ( + dataViewTypeRecords?.length === 0 && + ! dataViewTypeIsResolving && + ! dataViewTypeIsSaving && + ! isCreatingATerm.current + ) { + isCreatingATerm.current = true; + saveEntityRecord( 'taxonomy', 'wp_dataviews_type', { name: type } ); + } + }, [ + type, + dataViewTypeRecords, + dataViewTypeIsResolving, + dataViewTypeIsSaving, + saveEntityRecord, + ] ); + useEffect( () => { + if ( dataViewTypeRecords?.length > 0 ) { + isCreatingATerm.current = false; + } + }, [ dataViewTypeRecords ] ); + useEffect( () => { + isCreatingATerm.current = false; + }, [ type ] ); + if ( dataViewTypeRecords?.length > 0 ) { + return dataViewTypeRecords[ 0 ].id; + } + return null; +} + +function useDataviews( type, typeTaxonomyId ) { + const isCreatingADefaultView = useRef( false ); + const { dataViewRecords, dataViewIsLoading, dataViewIsSaving } = useSelect( + ( select ) => { + const { getEntityRecords, isResolving, isSavingEntityRecord } = + select( coreStore ); + if ( ! typeTaxonomyId ) { + return {}; + } + const dataViewQuery = { + wp_dataviews_type: typeTaxonomyId, + orderby: 'date', + order: 'asc', + }; + return { + dataViewRecords: getEntityRecords( + 'postType', + 'wp_dataviews', + dataViewQuery + ), + dataViewIsLoading: isResolving( 'getEntityRecords', [ + 'postType', + 'wp_dataviews', + dataViewQuery, + ] ), + dataViewIsSaving: isSavingEntityRecord( + 'postType', + 'wp_dataviews' + ), + }; + }, + [ typeTaxonomyId ] + ); + const { saveEntityRecord } = useDispatch( coreStore ); + useEffect( () => { + if ( + dataViewRecords?.length === 0 && + ! dataViewIsLoading && + ! dataViewIsSaving && + ! isCreatingADefaultView.current + ) { + isCreatingADefaultView.current = true; + saveEntityRecord( 'postType', 'wp_dataviews', { + title: 'All', + status: 'publish', + wp_dataviews_type: typeTaxonomyId, + content: JSON.stringify( DEFAULT_VIEWS[ type ] ), + } ); + } + }, [ + type, + dataViewIsLoading, + dataViewRecords, + dataViewIsSaving, + typeTaxonomyId, + saveEntityRecord, + ] ); + useEffect( () => { + if ( dataViewRecords?.length > 0 ) { + isCreatingADefaultView.current = false; + } + }, [ dataViewRecords ] ); + useEffect( () => { + isCreatingADefaultView.current = false; + }, [ typeTaxonomyId ] ); + if ( dataViewRecords?.length > 0 ) { + return dataViewRecords; + } + return null; +} + +function DataviewsProviderInner( { type, children } ) { + const [ currentViewId, setCurrentViewId ] = useState( null ); + const dataviewTypeTaxonomyId = useDataviewTypeTaxonomyId( type ); + const dataviews = useDataviews( type, dataviewTypeTaxonomyId ); + const { editEntityRecord } = useDispatch( coreStore ); + useEffect( () => { + if ( ! currentViewId && dataviews?.length > 0 ) { + setCurrentViewId( dataviews[ 0 ].id ); + } + }, [ currentViewId, dataviews ] ); + const editedViewRecord = useSelect( + ( select ) => { + if ( ! currentViewId ) { + return; + } + const { getEditedEntityRecord } = select( coreStore ); + const dataviewRecord = getEditedEntityRecord( + 'postType', + 'wp_dataviews', + currentViewId + ); + return dataviewRecord; + }, + [ currentViewId ] + ); + + const value = useMemo( () => { + return { + taxonomyId: dataviewTypeTaxonomyId, + dataviews, + currentViewId, + setCurrentViewId, + view: editedViewRecord?.content + ? JSON.parse( editedViewRecord?.content ) + : DEFAULT_VIEWS[ type ], + setView( view ) { + if ( ! currentViewId ) { + return; + } + editEntityRecord( 'postType', 'wp_dataviews', currentViewId, { + content: JSON.stringify( view ), + } ); + }, + }; + }, [ + type, + dataviewTypeTaxonomyId, + dataviews, + currentViewId, + editedViewRecord?.content, + editEntityRecord, + ] ); + + return ( + + { children } + + ); +} +export default function DataviewsProvider( { children } ) { + const { + params: { path }, + } = useLocation(); + const viewType = PATH_TO_DATAVIEW_TYPE[ path ]; + + if ( window?.__experimentalAdminViews && viewType ) { + return ( + + { children } + + ); + } + return <> { children }; +} diff --git a/packages/edit-site/src/components/dataviews/sidebar-content.js b/packages/edit-site/src/components/dataviews/sidebar-content.js new file mode 100644 index 00000000000000..c10565b38de37f --- /dev/null +++ b/packages/edit-site/src/components/dataviews/sidebar-content.js @@ -0,0 +1,3 @@ +export default function DataViewsSidebarContent() { + return

Add views ui here

; +} diff --git a/packages/edit-site/src/components/page-pages/index.js b/packages/edit-site/src/components/page-pages/index.js index 284fb8e3477a2c..02ef8dd53ac83d 100644 --- a/packages/edit-site/src/components/page-pages/index.js +++ b/packages/edit-site/src/components/page-pages/index.js @@ -19,6 +19,8 @@ import Link from '../routes/link'; import { DataViews } from '../dataviews'; import useTrashPostAction from '../actions/trash-post'; import Media from '../media'; +import DataviewsContext from '../dataviews/context'; +import { DEFAULT_STATUSES } from '../dataviews/provider'; const EMPTY_ARRAY = []; const defaultConfigPerViewType = { @@ -29,28 +31,8 @@ const defaultConfigPerViewType = { }; export default function PagePages() { - // DEFAULT_STATUSES is intentionally sorted. Items do not have spaces in between them. - // The reason for that is to match defaultStatuses because we compare both strings below (see useEffect). - const DEFAULT_STATUSES = 'draft,future,pending,private,publish'; // All statuses but 'trash'. - const [ view, setView ] = useState( { - type: 'list', - filters: { - search: '', - status: DEFAULT_STATUSES, - }, - page: 1, - perPage: 5, - sort: { - field: 'date', - direction: 'desc', - }, - visibleFilters: [ 'search', 'author', 'status' ], - // All fields are visible by default, so it's - // better to keep track of the hidden ones. - hiddenFields: [ 'date', 'featured-image' ], - layout: {}, - } ); - + const { view, setView } = useContext( DataviewsContext ); + // Request post statuses to get the proper labels. const { records: statuses } = useEntityRecords( 'root', 'status' ); const defaultStatuses = useMemo( () => { return statuses === null @@ -239,7 +221,7 @@ export default function PagePages() { setView( updatedView ); }, - [ view ] + [ view, setView ] ); // TODO: we need to handle properly `data={ data || EMPTY_ARRAY }` for when `isLoading`. diff --git a/packages/edit-site/src/components/sidebar/index.js b/packages/edit-site/src/components/sidebar/index.js index a233cf1eca1988..7745b05dbcb640 100644 --- a/packages/edit-site/src/components/sidebar/index.js +++ b/packages/edit-site/src/components/sidebar/index.js @@ -29,6 +29,7 @@ import { unlock } from '../../lock-unlock'; import SidebarNavigationScreenPages from '../sidebar-navigation-screen-pages'; import SidebarNavigationScreenPage from '../sidebar-navigation-screen-page'; import SidebarNavigationScreen from '../sidebar-navigation-screen'; +import DataViewsSidebarContent from '../dataviews/sidebar-content'; const { useLocation } = unlock( routerPrivateApis ); @@ -61,6 +62,7 @@ function SidebarScreens() { title={ __( 'All Pages' ) } description={ __( 'Manage your pages.' ) } backPath="/page" + content={ } /> ) } From 9a2d8b929af72cb0395c5c58d5c656d657094b10 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Maneiro?= <583546+oandregal@users.noreply.github.com> Date: Thu, 19 Oct 2023 10:57:21 +0200 Subject: [PATCH 2/5] Fix typo --- lib/experimental/data-views.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/experimental/data-views.php b/lib/experimental/data-views.php index 073d850508626e..8cb6a365d4e667 100644 --- a/lib/experimental/data-views.php +++ b/lib/experimental/data-views.php @@ -4,7 +4,7 @@ function _gutenberg_register_data_views_post_type() { register_post_type( 'wp_dataviews', array( - 'label' => _x( 'Datavivews', 'post type general name', 'gutenberg' ), + 'label' => _x( 'Dataviews', 'post type general name', 'gutenberg' ), 'description' => __( 'Post which stores the different data views configurations', 'gutenberg' ), 'public' => false, 'show_ui' => false, From 3a4cd08dee9b1abc3ad67add17966c1ffea88f9d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Maneiro?= <583546+oandregal@users.noreply.github.com> Date: Thu, 19 Oct 2023 14:16:05 +0200 Subject: [PATCH 3/5] Fix rebase --- packages/edit-site/src/components/page-pages/index.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/edit-site/src/components/page-pages/index.js b/packages/edit-site/src/components/page-pages/index.js index 02ef8dd53ac83d..b9afcd7b696bd9 100644 --- a/packages/edit-site/src/components/page-pages/index.js +++ b/packages/edit-site/src/components/page-pages/index.js @@ -8,7 +8,12 @@ import { import { __ } from '@wordpress/i18n'; import { useEntityRecords } from '@wordpress/core-data'; import { decodeEntities } from '@wordpress/html-entities'; -import { useState, useMemo, useCallback, useEffect } from '@wordpress/element'; +import { + useContext, + useMemo, + useCallback, + useEffect, +} from '@wordpress/element'; import { dateI18n, getDate, getSettings } from '@wordpress/date'; /** From 5193146883c63897c4f8ef68c7a809f61b3019fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Maneiro?= <583546+oandregal@users.noreply.github.com> Date: Thu, 19 Oct 2023 14:31:16 +0200 Subject: [PATCH 4/5] Update the search value upon view filter changes --- packages/edit-site/src/components/dataviews/text-filter.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/edit-site/src/components/dataviews/text-filter.js b/packages/edit-site/src/components/dataviews/text-filter.js index c5df0022e411c3..79ae77c60b71aa 100644 --- a/packages/edit-site/src/components/dataviews/text-filter.js +++ b/packages/edit-site/src/components/dataviews/text-filter.js @@ -14,6 +14,9 @@ export default function TextFilter( { filter, view, onChangeView } ) { const [ search, setSearch, debouncedSearch ] = useDebouncedInput( view.filters[ filter.id ] ); + useEffect( () => { + setSearch( view.filters[ filter.id ] ); + }, [ view ] ); const onChangeViewRef = useRef( onChangeView ); useEffect( () => { onChangeViewRef.current = onChangeView; From cf7c32134b986320066ebb0ed8f6215bd79c3941 Mon Sep 17 00:00:00 2001 From: Jorge Costa Date: Thu, 19 Oct 2023 20:35:47 +0100 Subject: [PATCH 5/5] Comments and lint fixes. --- lib/experimental/data-views.php | 28 ++++++++++++++++++---------- lib/load.php | 2 +- 2 files changed, 19 insertions(+), 11 deletions(-) diff --git a/lib/experimental/data-views.php b/lib/experimental/data-views.php index 8cb6a365d4e667..e46fe36ba06fe1 100644 --- a/lib/experimental/data-views.php +++ b/lib/experimental/data-views.php @@ -1,5 +1,13 @@ true, 'rewrite' => false, 'capabilities' => array( - 'read' => 'edit_published_posts', - // 'create_posts' => 'edit_published_posts', - // 'edit_posts' => 'edit_published_posts', - // 'edit_published_posts' => 'edit_published_posts', - // 'delete_published_posts' => 'delete_published_posts', - // 'edit_others_posts' => 'edit_others_posts', - // 'delete_others_posts' => 'edit_theme_options', + 'read' => 'edit_published_posts', + // 'create_posts' => 'edit_published_posts', + // 'edit_posts' => 'edit_published_posts', + // 'edit_published_posts' => 'edit_published_posts', + // 'delete_published_posts' => 'delete_published_posts', + // 'edit_others_posts' => 'edit_others_posts', + // 'delete_others_posts' => 'edit_theme_options', ), 'map_meta_cap' => true, 'supports' => array( 'title', 'slug', 'editor' ), ) ); - register_taxonomy( + register_taxonomy( 'wp_dataviews_type', array( 'wp_dataviews' ), array( 'public' => true, 'hierarchical' => false, 'labels' => array( - 'name' => __( 'Dataview types' ), - 'singular_name' => __( 'Dataview type' ), + 'name' => __( 'Dataview types', 'gutenberg' ), + 'singular_name' => __( 'Dataview type', 'gutenberg' ), ), 'rewrite' => false, 'show_ui' => false, diff --git a/lib/load.php b/lib/load.php index 3514421e06976f..2b178af5fb9bfe 100644 --- a/lib/load.php +++ b/lib/load.php @@ -257,5 +257,5 @@ function () { require __DIR__ . '/block-supports/background.php'; require __DIR__ . '/block-supports/behaviors.php'; -// Data views +// Data views. require_once __DIR__ . '/experimental/data-views.php';