Skip to content

Commit d1b8005

Browse files
authored
First step for Preset Post Types
1 parent 1a0e2c8 commit d1b8005

File tree

16 files changed

+793
-119
lines changed

16 files changed

+793
-119
lines changed

@types/index.d.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,12 @@ declare namespace EntitiesSearch {
1717
type Options<V> = Set<ControlOption<V>>;
1818
type Value = string | number;
1919

20-
interface QueryArguments<V>
20+
// TODO Can we convert QueryArguments to an Immutable Map?
21+
interface QueryArguments
2122
extends Partial<
2223
Readonly<{
23-
exclude: Set<V>;
24-
include: Set<V>;
24+
exclude: Set<string | number>;
25+
include: Set<string | number>;
2526
fields: EntitiesSearch.SearchQueryFields;
2627
[p: string]: unknown;
2728
}>
@@ -34,6 +35,8 @@ declare namespace EntitiesSearch {
3435
url: string;
3536
type: string;
3637
subtype: string;
38+
post_content: string;
39+
post_excerpt: string;
3740
}> {}
3841

3942
type SearchEntitiesFunction<E, K> = (
@@ -99,6 +102,7 @@ declare namespace EntitiesSearch {
99102
/*
100103
* Api
101104
*/
105+
// TODO Better to convert the SearchQueryFields to a Set.
102106
type SearchQueryFields = ReadonlyArray<
103107
keyof EntitiesSearch.SearchEntityFields
104108
>;

docs/api.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,3 +72,17 @@ NOTE: The function does not handle possible request exceptions.
7272

7373
The `searchEntities` will automatically abort the requests with the same parameters if a new request is made before the previous one is completed.
7474

75+
76+
77+
## `searchEntitiesOptions`
78+
79+
This function is a wrapper for the `searchEntites` which perform a small additional task you might want to do quite ofter after retrieving the entities that is
80+
to convert the entities to `EntitiesSearch.ControlOptions`. The argument taken are the same of the `searchEntities`.
81+
82+
### `createSearchEntitiesOptions`
83+
84+
This is a factory function, it returns a `searchEntitiesOptions` function preconfigured to search for a specific *root* kind.
85+
86+
For instance, if we call this function with `post` like `createSearchEntitiesOptions('post')` the returned function will be of type `searchEntitiesOptions` where you don't always have to pass the *root* type.
87+
88+
Most probably this will be the function you'll deal with most as it make easy to build a function you can consume with other components.

docs/components.md

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,10 @@ data down to the _Base Components_.
3535
- `CompositeEntitiesByKind` - A composite component that displays a list of entities by kind. In this context _kind_
3636
is `post` or `term` and _entity_ is the entity type of the content e.g. a `page` or a `category` term.
3737

38+
## Preset Components
39+
40+
- `PresetEntitiesByKind` - A top level component simplifying the DX with a preconfigured path of how to manage the data based on the context given.
41+
3842
## Composite Entities by Kind
3943

4044
The `CompositeEntitiesByKind` is a _generic_ component that can be used to display a list of entities by kind. It acts
@@ -188,6 +192,51 @@ export function MyComponent(props) {
188192
Obviously depending on what you want to achieve you can use different _Base Components_ or create new ones, as mentioned
189193
above the package comes with a set of _Base Components_ that can be used out of the box.
190194

195+
## Preset Entities By Kind
196+
197+
This component can be used for different scenarios since it allows you to pass all the necessary information to render the Selectors.
198+
199+
It gets passed the `entitiesFinder` which is the function performing the request to retrieve the Entities, but also it gives you the freedom to decide which components to render as Controls UI.
200+
201+
Below you can see how easy is to create a new control set compared to use the low level api `CompositeEntitiesByKind`.
202+
203+
```typescript jsx
204+
const entitiesFinder = createSearchEntitiesOptions( 'post' );
205+
const postTypesEntities = convertEntitiesToControlOptions(useQueryViewablePostTypes().records(), 'name', 'slug');
206+
207+
const props = {
208+
entitiesFinder,
209+
entities: new Set( [ 1, 2, 3 ] ),
210+
onChangeEntities: (entities) => {
211+
// Do something with the new set of entities
212+
},
213+
entitiesComponent: ToggleControl,
214+
kind: new Set(['post']),
215+
kindOptions: stubControlOptionsSet(),
216+
onChangeKind: (kinds) => {
217+
// Do something with the new set of kinds
218+
},
219+
kindComponent: ToggleControl
220+
};
221+
222+
return (<PresetEntitiesByKind { ...props } />);
223+
```
224+
225+
Therefore, the preset simplify and take care of some parts that you would have to configure otherwise. In the chapter below you can read the available properties.
226+
227+
### Properties
228+
229+
- `entitiesFinder` - The function which perform the search of the contextual entities. You can use the `createSearchEntitiesOptions` function by passing the `root` value such as `term` or `post`.
230+
- `className` - For better customization you can pass your own custom classes.
231+
- `entities` - A `EntitiesSearch.Entities` set of selected entities. For when you want some entities already selected.
232+
- `onChangeEntities` - A task to perform when the selection change due to a user interaction.
233+
- `entitiesComponent` - The component to use to render the control ui for the entities.
234+
- `kind` - The predefined set of kind (e.g. post-types or taxonomies) you want to have already selected.
235+
- `kindOptions` - A collection of `EntitiesSearch.ControlOption` among which the user can choose to retrieve the entities from.
236+
- `onChangeKind` - A task to perform when the selection change due to a user interaction.
237+
- `kindComponent` - The component to use to render the control ui for the kinds.
238+
- `entitiesFields` - Additional fields you want to retrieve and have available within your `entitiesComponent` and `kindComponent`. For more info read the [Control Option](./control-option.md) documentation.
239+
191240
## About Singular Base Components
192241

193242
The _Composite Component_ always give a collection of Entities and Kind even though you are consuming a Single* _Base
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
/**
2+
* External dependencies
3+
*/
4+
import EntitiesSearch from '@types';
5+
6+
/**
7+
* Internal dependencies
8+
*/
9+
import { Set } from '../models/set';
10+
import { searchEntitiesOptions } from './search-entities-options';
11+
12+
export function createSearchEntitiesOptions< E >( type: string ) {
13+
return async (
14+
phrase: string,
15+
postTypes: EntitiesSearch.Kind< string >,
16+
queryArguments?: EntitiesSearch.QueryArguments
17+
): Promise< Set< EntitiesSearch.ControlOption< E > > > =>
18+
searchEntitiesOptions( type, phrase, postTypes, queryArguments );
19+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/**
2+
* External dependencies
3+
*/
4+
import EntitiesSearch from '@types';
5+
6+
/**
7+
* Internal dependencies
8+
*/
9+
import { Set } from '../models/set';
10+
import { searchEntities } from './search-entities';
11+
import { convertEntitiesToControlOptions } from '../utils/convert-entities-to-control-options';
12+
13+
export async function searchEntitiesOptions< E >(
14+
type: string,
15+
phrase: string,
16+
postTypes: EntitiesSearch.Kind< string >,
17+
queryArguments?: EntitiesSearch.QueryArguments
18+
): Promise< Set< EntitiesSearch.ControlOption< E > > > {
19+
const postsEntities =
20+
await searchEntities< EntitiesSearch.SearchEntityFields >(
21+
type,
22+
postTypes,
23+
phrase,
24+
queryArguments
25+
);
26+
27+
const { fields = [] } = queryArguments ?? {};
28+
const [ label = 'title', value = 'id', ...extraFields ] = fields;
29+
30+
return convertEntitiesToControlOptions(
31+
postsEntities,
32+
label,
33+
value,
34+
...extraFields
35+
);
36+
}

sources/client/src/api/search-entities.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ export async function searchEntities< E >(
2020
type: string,
2121
subtype: Set< string >,
2222
phrase: string,
23-
queryArguments?: EntitiesSearch.QueryArguments< string >
23+
queryArguments?: EntitiesSearch.QueryArguments
2424
): Promise< Set< E > > {
2525
const {
2626
exclude,
Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
/**
2+
* External dependencies
3+
*/
4+
import EntitiesSearch from '@types';
5+
import React, { JSX } from 'react';
6+
import classnames from 'classnames';
7+
8+
/**
9+
* WordPress dependencies
10+
*/
11+
import { createHigherOrderComponent } from '@wordpress/compose';
12+
13+
/**
14+
* Internal dependencies
15+
*/
16+
import { CompositeEntitiesByKind } from './composite-entities-by-kind';
17+
import { SearchControl } from './search-control';
18+
import { Set } from '../models/set';
19+
20+
type EntitiesValue = EntitiesSearch.Value;
21+
type Entities = EntitiesSearch.Entities< EntitiesValue >;
22+
type KindValue = EntitiesSearch.Kind< string > | string;
23+
24+
type EntitiesFinder = (
25+
phrase: string,
26+
kind: EntitiesSearch.Kind< string >,
27+
queryArguments?: EntitiesSearch.QueryArguments
28+
) => Promise< Set< EntitiesSearch.ControlOption< EntitiesValue > > >;
29+
30+
type EntitiesComponent = React.ComponentType<
31+
EntitiesSearch.BaseControl< EntitiesValue >
32+
>;
33+
type KindComponent = React.ComponentType<
34+
EntitiesSearch.BaseControl< KindValue >
35+
>;
36+
37+
type PublicComponentProps = {
38+
entitiesFinder: EntitiesFinder;
39+
className?: string;
40+
entities: Entities;
41+
onChangeEntities: ( values: Entities ) => void;
42+
entitiesComponent: EntitiesComponent;
43+
kind: KindValue;
44+
kindOptions: EntitiesSearch.Options< string >;
45+
onChangeKind: ( values: KindValue ) => void;
46+
kindComponent: KindComponent;
47+
entitiesFields?: EntitiesSearch.QueryArguments[ 'fields' ];
48+
};
49+
50+
interface PrivateComponentProps
51+
extends Pick<
52+
EntitiesSearch.CompositeEntitiesKinds< EntitiesValue, string >,
53+
'kind' | 'entities'
54+
> {
55+
className?: string;
56+
entitiesFinder: EntitiesFinder;
57+
kindComponent: KindComponent;
58+
entitiesComponent: EntitiesComponent;
59+
}
60+
61+
function PrivateComponent( props: PrivateComponentProps ): JSX.Element {
62+
const className = classnames( 'wes-preset-entities-by-kind', {
63+
// @ts-ignore
64+
[ props.className ]: !! props.className,
65+
} );
66+
67+
return (
68+
<div className={ className }>
69+
<CompositeEntitiesByKind
70+
entities={ props.entities }
71+
kind={ props.kind }
72+
searchEntities={ props.entitiesFinder }
73+
>
74+
{ ( _entities, _kind, search ) => (
75+
<>
76+
<props.kindComponent { ..._kind } />
77+
<SearchControl onChange={ search } />
78+
<props.entitiesComponent { ..._entities } />
79+
</>
80+
) }
81+
</CompositeEntitiesByKind>
82+
</div>
83+
);
84+
}
85+
86+
const withDataBound = createHigherOrderComponent<
87+
React.ComponentType< PrivateComponentProps >,
88+
React.ComponentType< PublicComponentProps >
89+
>(
90+
( Component ) => ( props ) => {
91+
const {
92+
kind,
93+
entities,
94+
onChangeEntities,
95+
kindOptions,
96+
onChangeKind,
97+
entitiesFields,
98+
kindComponent,
99+
entitiesComponent,
100+
entitiesFinder,
101+
..._props
102+
} = props;
103+
104+
const kindValue = narrowKindValue( props.kind );
105+
106+
const _entities = {
107+
value: entities,
108+
onChange: onChangeEntities,
109+
};
110+
111+
const _kind = {
112+
value: kindValue,
113+
options: kindOptions,
114+
onChange: onChangeKind,
115+
};
116+
117+
const _entitiesFinder = entitiesFinderWithExtraFields(
118+
entitiesFinder,
119+
entitiesFields
120+
);
121+
122+
return (
123+
<Component
124+
entities={ _entities }
125+
kind={ _kind }
126+
entitiesFinder={ _entitiesFinder }
127+
kindComponent={ kindComponent }
128+
entitiesComponent={ entitiesComponent }
129+
{ ..._props }
130+
/>
131+
);
132+
},
133+
'withDataBound'
134+
);
135+
136+
function entitiesFinderWithExtraFields(
137+
entitiesFinder: EntitiesFinder,
138+
entitiesFields?: EntitiesSearch.QueryArguments[ 'fields' ]
139+
): EntitiesFinder {
140+
return (
141+
phrase: string,
142+
kind: EntitiesSearch.Kind< string >,
143+
queryArguments?: EntitiesSearch.QueryArguments
144+
) =>
145+
entitiesFinder( phrase, kind, {
146+
...queryArguments,
147+
fields: [
148+
...( queryArguments?.fields ?? [ 'title', 'id' ] ),
149+
...( entitiesFields ?? [] ),
150+
],
151+
} );
152+
}
153+
154+
function narrowKindValue( value: KindValue ): EntitiesSearch.Kind< string > {
155+
return typeof value === 'string' ? new Set( [ value ] ) : value;
156+
}
157+
158+
export const PresetEntitiesByKind = withDataBound( PrivateComponent );

sources/client/src/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
export * from './api/search-entities';
2+
export * from './api/search-entities-options';
3+
export * from './api/create-search-entities-options';
24

35
export * from './components/composite-entities-by-kind';
46
export * from './components/plural-select-control';
7+
export * from './components/preset-entities-by-kind';
58
export * from './components/radio-control';
69
export * from './components/search-control';
710
export * from './components/singular-select-control';

sources/client/src/utils/convert-entities-to-control-options.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ export function convertEntitiesToControlOptions<
1818
entities: Set< EntitiesFields >,
1919
labelKey: string,
2020
valueKey: string,
21-
...extraKeys: Array< string >
21+
...extraKeys: ReadonlyArray< string >
2222
): Set< EntitiesSearch.ControlOption< V > > {
2323
return entities.map( ( entity ) => {
2424
const label = entity[ labelKey ];

0 commit comments

Comments
 (0)