Skip to content

Commit ff8dfe5

Browse files
Add: Ability to persist dataviews on the database. (#55465)
* Add: Ability to persist dataviews on the database. * Fix typo * Fix rebase * Update the search value upon view filter changes * Comments and lint fixes. --------- Co-authored-by: André Maneiro <583546+oandregal@users.noreply.github.com>
1 parent d74d9e1 commit ff8dfe5

9 files changed

Lines changed: 340 additions & 26 deletions

File tree

lib/experimental/data-views.php

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
<?php
2+
/**
3+
* Dataviews custom post type and taxonomy.
4+
*
5+
* @package gutenberg
6+
*/
7+
8+
/**
9+
* Registers the `wp_dataviews` post type and the `wp_dataviews_type` taxonomy.
10+
*/
11+
function _gutenberg_register_data_views_post_type() {
12+
register_post_type(
13+
'wp_dataviews',
14+
array(
15+
'label' => _x( 'Dataviews', 'post type general name', 'gutenberg' ),
16+
'description' => __( 'Post which stores the different data views configurations', 'gutenberg' ),
17+
'public' => false,
18+
'show_ui' => false,
19+
'show_in_rest' => true,
20+
'rewrite' => false,
21+
'capabilities' => array(
22+
'read' => 'edit_published_posts',
23+
// 'create_posts' => 'edit_published_posts',
24+
// 'edit_posts' => 'edit_published_posts',
25+
// 'edit_published_posts' => 'edit_published_posts',
26+
// 'delete_published_posts' => 'delete_published_posts',
27+
// 'edit_others_posts' => 'edit_others_posts',
28+
// 'delete_others_posts' => 'edit_theme_options',
29+
),
30+
'map_meta_cap' => true,
31+
'supports' => array( 'title', 'slug', 'editor' ),
32+
)
33+
);
34+
35+
register_taxonomy(
36+
'wp_dataviews_type',
37+
array( 'wp_dataviews' ),
38+
array(
39+
'public' => true,
40+
'hierarchical' => false,
41+
'labels' => array(
42+
'name' => __( 'Dataview types', 'gutenberg' ),
43+
'singular_name' => __( 'Dataview type', 'gutenberg' ),
44+
),
45+
'rewrite' => false,
46+
'show_ui' => false,
47+
'show_in_nav_menus' => false,
48+
'show_in_rest' => true,
49+
)
50+
);
51+
}
52+
53+
add_action( 'init', '_gutenberg_register_data_views_post_type' );

lib/load.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -256,3 +256,6 @@ function () {
256256
require __DIR__ . '/block-supports/shadow.php';
257257
require __DIR__ . '/block-supports/background.php';
258258
require __DIR__ . '/block-supports/behaviors.php';
259+
260+
// Data views.
261+
require_once __DIR__ . '/experimental/data-views.php';

packages/edit-site/src/components/app/index.js

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import { privateApis as routerPrivateApis } from '@wordpress/router';
1414
*/
1515
import Layout from '../layout';
1616
import { GlobalStylesProvider } from '../global-styles/global-styles-provider';
17+
import DataviewsProvider from '../dataviews/provider';
1718
import { unlock } from '../../lock-unlock';
1819

1920
const { RouterProvider } = unlock( routerPrivateApis );
@@ -38,8 +39,10 @@ export default function App() {
3839
<GlobalStylesProvider>
3940
<UnsavedChangesWarning />
4041
<RouterProvider>
41-
<Layout />
42-
<PluginArea onError={ onPluginAreaError } />
42+
<DataviewsProvider>
43+
<Layout />
44+
<PluginArea onError={ onPluginAreaError } />
45+
</DataviewsProvider>
4346
</RouterProvider>
4447
</GlobalStylesProvider>
4548
</SlotFillProvider>
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
/**
2+
* WordPress dependencies
3+
*/
4+
import { createContext } from '@wordpress/element';
5+
6+
const DataviewsContext = createContext( {} );
7+
export default DataviewsContext;
Lines changed: 253 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,253 @@
1+
/**
2+
* WordPress dependencies
3+
*/
4+
import { privateApis as routerPrivateApis } from '@wordpress/router';
5+
import { useEffect, useState, useRef, useMemo } from '@wordpress/element';
6+
import { useDispatch, useSelect } from '@wordpress/data';
7+
import { store as coreStore } from '@wordpress/core-data';
8+
9+
/**
10+
* Internal dependencies
11+
*/
12+
import { unlock } from '../../lock-unlock';
13+
import DataviewsContext from './context';
14+
15+
const { useLocation } = unlock( routerPrivateApis );
16+
17+
// DEFAULT_STATUSES is intentionally sorted. Items do not have spaces in between them.
18+
// The reason for that is to match the default statuses coming from the endpoint
19+
// (entity request and useEffect to update the view).
20+
export const DEFAULT_STATUSES = 'draft,future,pending,private,publish'; // All statuses but 'trash'.
21+
22+
const DEFAULT_VIEWS = {
23+
page: {
24+
type: 'list',
25+
filters: {
26+
search: '',
27+
status: DEFAULT_STATUSES,
28+
},
29+
page: 1,
30+
perPage: 5,
31+
sort: {
32+
field: 'date',
33+
direction: 'desc',
34+
},
35+
visibleFilters: [ 'search', 'author', 'status' ],
36+
// All fields are visible by default, so it's
37+
// better to keep track of the hidden ones.
38+
hiddenFields: [ 'date', 'featured-image' ],
39+
layout: {},
40+
},
41+
};
42+
43+
const PATH_TO_DATAVIEW_TYPE = {
44+
'/pages': 'page',
45+
};
46+
47+
function useDataviewTypeTaxonomyId( type ) {
48+
const isCreatingATerm = useRef( false );
49+
const {
50+
dataViewTypeRecords,
51+
dataViewTypeIsResolving,
52+
dataViewTypeIsSaving,
53+
} = useSelect(
54+
( select ) => {
55+
const { getEntityRecords, isResolving, isSavingEntityRecord } =
56+
select( coreStore );
57+
const dataViewTypeQuery = { slug: type };
58+
return {
59+
dataViewTypeRecords: getEntityRecords(
60+
'taxonomy',
61+
'wp_dataviews_type',
62+
dataViewTypeQuery
63+
),
64+
dataViewTypeIsResolving: isResolving( 'getEntityRecords', [
65+
'taxonomy',
66+
'wp_dataviews_type',
67+
dataViewTypeQuery,
68+
] ),
69+
dataViewTypeIsSaving: isSavingEntityRecord(
70+
'taxonomy',
71+
'wp_dataviews_type'
72+
),
73+
};
74+
},
75+
[ type ]
76+
);
77+
const { saveEntityRecord } = useDispatch( coreStore );
78+
useEffect( () => {
79+
if (
80+
dataViewTypeRecords?.length === 0 &&
81+
! dataViewTypeIsResolving &&
82+
! dataViewTypeIsSaving &&
83+
! isCreatingATerm.current
84+
) {
85+
isCreatingATerm.current = true;
86+
saveEntityRecord( 'taxonomy', 'wp_dataviews_type', { name: type } );
87+
}
88+
}, [
89+
type,
90+
dataViewTypeRecords,
91+
dataViewTypeIsResolving,
92+
dataViewTypeIsSaving,
93+
saveEntityRecord,
94+
] );
95+
useEffect( () => {
96+
if ( dataViewTypeRecords?.length > 0 ) {
97+
isCreatingATerm.current = false;
98+
}
99+
}, [ dataViewTypeRecords ] );
100+
useEffect( () => {
101+
isCreatingATerm.current = false;
102+
}, [ type ] );
103+
if ( dataViewTypeRecords?.length > 0 ) {
104+
return dataViewTypeRecords[ 0 ].id;
105+
}
106+
return null;
107+
}
108+
109+
function useDataviews( type, typeTaxonomyId ) {
110+
const isCreatingADefaultView = useRef( false );
111+
const { dataViewRecords, dataViewIsLoading, dataViewIsSaving } = useSelect(
112+
( select ) => {
113+
const { getEntityRecords, isResolving, isSavingEntityRecord } =
114+
select( coreStore );
115+
if ( ! typeTaxonomyId ) {
116+
return {};
117+
}
118+
const dataViewQuery = {
119+
wp_dataviews_type: typeTaxonomyId,
120+
orderby: 'date',
121+
order: 'asc',
122+
};
123+
return {
124+
dataViewRecords: getEntityRecords(
125+
'postType',
126+
'wp_dataviews',
127+
dataViewQuery
128+
),
129+
dataViewIsLoading: isResolving( 'getEntityRecords', [
130+
'postType',
131+
'wp_dataviews',
132+
dataViewQuery,
133+
] ),
134+
dataViewIsSaving: isSavingEntityRecord(
135+
'postType',
136+
'wp_dataviews'
137+
),
138+
};
139+
},
140+
[ typeTaxonomyId ]
141+
);
142+
const { saveEntityRecord } = useDispatch( coreStore );
143+
useEffect( () => {
144+
if (
145+
dataViewRecords?.length === 0 &&
146+
! dataViewIsLoading &&
147+
! dataViewIsSaving &&
148+
! isCreatingADefaultView.current
149+
) {
150+
isCreatingADefaultView.current = true;
151+
saveEntityRecord( 'postType', 'wp_dataviews', {
152+
title: 'All',
153+
status: 'publish',
154+
wp_dataviews_type: typeTaxonomyId,
155+
content: JSON.stringify( DEFAULT_VIEWS[ type ] ),
156+
} );
157+
}
158+
}, [
159+
type,
160+
dataViewIsLoading,
161+
dataViewRecords,
162+
dataViewIsSaving,
163+
typeTaxonomyId,
164+
saveEntityRecord,
165+
] );
166+
useEffect( () => {
167+
if ( dataViewRecords?.length > 0 ) {
168+
isCreatingADefaultView.current = false;
169+
}
170+
}, [ dataViewRecords ] );
171+
useEffect( () => {
172+
isCreatingADefaultView.current = false;
173+
}, [ typeTaxonomyId ] );
174+
if ( dataViewRecords?.length > 0 ) {
175+
return dataViewRecords;
176+
}
177+
return null;
178+
}
179+
180+
function DataviewsProviderInner( { type, children } ) {
181+
const [ currentViewId, setCurrentViewId ] = useState( null );
182+
const dataviewTypeTaxonomyId = useDataviewTypeTaxonomyId( type );
183+
const dataviews = useDataviews( type, dataviewTypeTaxonomyId );
184+
const { editEntityRecord } = useDispatch( coreStore );
185+
useEffect( () => {
186+
if ( ! currentViewId && dataviews?.length > 0 ) {
187+
setCurrentViewId( dataviews[ 0 ].id );
188+
}
189+
}, [ currentViewId, dataviews ] );
190+
const editedViewRecord = useSelect(
191+
( select ) => {
192+
if ( ! currentViewId ) {
193+
return;
194+
}
195+
const { getEditedEntityRecord } = select( coreStore );
196+
const dataviewRecord = getEditedEntityRecord(
197+
'postType',
198+
'wp_dataviews',
199+
currentViewId
200+
);
201+
return dataviewRecord;
202+
},
203+
[ currentViewId ]
204+
);
205+
206+
const value = useMemo( () => {
207+
return {
208+
taxonomyId: dataviewTypeTaxonomyId,
209+
dataviews,
210+
currentViewId,
211+
setCurrentViewId,
212+
view: editedViewRecord?.content
213+
? JSON.parse( editedViewRecord?.content )
214+
: DEFAULT_VIEWS[ type ],
215+
setView( view ) {
216+
if ( ! currentViewId ) {
217+
return;
218+
}
219+
editEntityRecord( 'postType', 'wp_dataviews', currentViewId, {
220+
content: JSON.stringify( view ),
221+
} );
222+
},
223+
};
224+
}, [
225+
type,
226+
dataviewTypeTaxonomyId,
227+
dataviews,
228+
currentViewId,
229+
editedViewRecord?.content,
230+
editEntityRecord,
231+
] );
232+
233+
return (
234+
<DataviewsContext.Provider value={ value }>
235+
{ children }
236+
</DataviewsContext.Provider>
237+
);
238+
}
239+
export default function DataviewsProvider( { children } ) {
240+
const {
241+
params: { path },
242+
} = useLocation();
243+
const viewType = PATH_TO_DATAVIEW_TYPE[ path ];
244+
245+
if ( window?.__experimentalAdminViews && viewType ) {
246+
return (
247+
<DataviewsProviderInner type={ viewType }>
248+
{ children }
249+
</DataviewsProviderInner>
250+
);
251+
}
252+
return <> { children }</>;
253+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export default function DataViewsSidebarContent() {
2+
return <p>Add views ui here</p>;
3+
}

packages/edit-site/src/components/dataviews/text-filter.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@ export default function TextFilter( { filter, view, onChangeView } ) {
1414
const [ search, setSearch, debouncedSearch ] = useDebouncedInput(
1515
view.filters[ filter.id ]
1616
);
17+
useEffect( () => {
18+
setSearch( view.filters[ filter.id ] );
19+
}, [ view ] );
1720
const onChangeViewRef = useRef( onChangeView );
1821
useEffect( () => {
1922
onChangeViewRef.current = onChangeView;

0 commit comments

Comments
 (0)