Skip to content

Commit cd981dd

Browse files
committed
Plural Kind support
1 parent 8723ee9 commit cd981dd

File tree

20 files changed

+140
-109
lines changed

20 files changed

+140
-109
lines changed

@types/index.d.ts

Lines changed: 27 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,21 @@ declare namespace EntitiesSearch {
2424
label: string;
2525
}>;
2626

27+
type SingularControl<V> = {
28+
[K in keyof BaseControl<V>]: K extends 'value'
29+
? V
30+
: K extends 'onChange'
31+
? (value: V) => void
32+
: BaseControl<V>[K];
33+
};
34+
35+
interface BaseControl<V>
36+
extends Readonly<{
37+
value: OrderedSet<V>;
38+
options: OrderedSet<ControlOption<V>>;
39+
onChange(values: BaseControl<V>['value']): void;
40+
}> {}
41+
2742
/*
2843
* Hooks
2944
*/
@@ -60,8 +75,8 @@ declare namespace EntitiesSearch {
6075
* Storage
6176
*/
6277
type EntitiesState<CO> = Readonly<{
63-
contexualEntitiesOptions: OrderedSet<EntitiesSearch.ControlOption<CO>>;
64-
entitiesOptions: EntitiesState<CO>['contexualEntitiesOptions'];
78+
contextualEntitiesOptions: OrderedSet<EntitiesSearch.ControlOption<CO>>;
79+
entitiesOptions: EntitiesState<CO>['contextualEntitiesOptions'];
6580
selectedEntitiesOptions: EntitiesState<CO>['entitiesOptions'];
6681
}>;
6782

@@ -72,7 +87,7 @@ declare namespace EntitiesSearch {
7287
}
7388
| {
7489
type: 'UPDATE_CONTEXTUAL_ENTITIES_OPTIONS';
75-
contextualEntitiesOptions: EntitiesState<V>['contexualEntitiesOptions'];
90+
contextualEntitiesOptions: EntitiesState<V>['contextualEntitiesOptions'];
7691
}
7792
| {
7893
type: 'UPDATE_SELECTED_ENTITIES_OPTIONS';
@@ -82,43 +97,27 @@ declare namespace EntitiesSearch {
8297
/*
8398
* Components
8499
*/
85-
interface KindControl<V>
86-
extends Readonly<{
87-
value: V;
88-
options: OrderedSet<ControlOption<V>>;
89-
className?: string;
90-
onChange(value: KindControl<V>['value']): void;
91-
}> {}
92-
93-
interface EntitiesControl<V>
94-
extends Readonly<{
95-
value?: OrderedSet<V> | undefined;
96-
options: OrderedSet<ControlOption<V>>;
97-
className?: string;
98-
onChange(values: EntitiesControl<V>['value']): void;
99-
}> {}
100-
101100
interface SearchControl
102101
extends Readonly<{
103102
id?: string;
104103
onChange(phrase: string | React.ChangeEvent<HTMLInputElement>);
105104
}> {}
106105

107-
interface CompositeEntitiesKinds<P, T>
106+
interface CompositeEntitiesKinds<E, K>
108107
extends Readonly<{
109-
kind: KindControl<T>;
110-
entities: Omit<EntitiesControl<P>, 'options'>;
108+
kind: BaseControl<K>;
109+
entities: Omit<BaseControl<E>, 'options'>;
111110
searchEntities: (
112111
phrase: string,
113-
kind: KindControl<T>['value'],
112+
kind: BaseControl<K>['value'],
114113
queryArguments?: Record<string, unknown>
115-
) => Promise<EntitiesControl<P>['options']>;
114+
) => Promise<BaseControl<E>['options']>;
116115
children(
117-
entities: EntitiesControl<P>,
118-
kind: CompositeEntitiesKinds<P, T>['kind'],
116+
entities: BaseControl<E>,
117+
kind: CompositeEntitiesKinds<E, K>['kind'],
119118
search: (
120-
phrase: Parameters<SearchControl<P, T>['search']>[0]
121-
) => ReturnType<SearchControl<P, T>['search']>
119+
phrase: Parameters<SearchControl<E, K>['search']>[0]
120+
) => ReturnType<SearchControl<E, K>['search']>
122121
): React.ReactNode;
123122
}> {}
124123
}

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

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@ import { fetch } from './fetch';
77
// TODO Write the underline search function.
88
// TODO Make the `queryArguments` optional.
99
export async function searchEntities<E>(
10-
kindName: string,
11-
entityName: string,
10+
type: string,
11+
subtype: OrderedSet<string>,
1212
phrase: string,
1313
queryArguments: {
1414
exclude: Set<string>;
@@ -34,9 +34,9 @@ export async function searchEntities<E>(
3434
include: include?.toArray() ?? [],
3535
...restArguments,
3636
},
37-
type: kindName,
37+
type: type,
3838
search: phrase,
39-
subtype: entityName,
39+
subtype: subtype.toArray(),
4040
_fields: serializeFields(fields),
4141
});
4242

sources/js/src/components/composite-entities-by-kind.tsx

Lines changed: 34 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -7,24 +7,26 @@ import { orderSelectedOptionsAtTheTop } from '../utils/order-selected-options-at
77
import { uniqueControlOptions } from '../utils/unique-control-options';
88

99
type SearchPhrase = Parameters<EntitiesSearch.SearchControl['onChange']>[0];
10-
type Entities<V> = EntitiesSearch.EntitiesControl<V>['value'];
10+
type Entities<V> = EntitiesSearch.BaseControl<V>['value'];
11+
type Kind<V> = EntitiesSearch.BaseControl<V>['value'];
1112
type SearchEntities<P, T> = EntitiesSearch.CompositeEntitiesKinds<
1213
P,
1314
T
1415
>['searchEntities'];
1516

16-
export function CompositeEntitiesByKind<P, T>(
17-
props: EntitiesSearch.CompositeEntitiesKinds<P, T>
17+
export function CompositeEntitiesByKind<E, K>(
18+
props: EntitiesSearch.CompositeEntitiesKinds<E, K>
1819
): JSX.Element {
19-
const { state, dispatch } = useEntitiesOptionsStorage<P>();
20+
// TODO Move the local state into the reducer. Let's see if it is possible.
21+
const { state, dispatch } = useEntitiesOptionsStorage<E>();
2022
const [searchPhrase, setSearchPhrase] = useState('');
2123
const [entitiesAndKind, setEntitiesAndKind] = useState({
22-
entities: props.entities.value ?? OrderedSet([]),
24+
entities: props.entities.value,
2325
kind: props.kind.value,
2426
});
2527

2628
useEffect(() => {
27-
const promises = Set<ReturnType<SearchEntities<P, T>>>().asMutable();
29+
const promises = Set<ReturnType<SearchEntities<E, K>>>().asMutable();
2830

2931
promises.add(
3032
props.searchEntities('', entitiesAndKind.kind, {
@@ -65,7 +67,7 @@ export function CompositeEntitiesByKind<P, T>(
6567
// eslint-disable-next-line react-hooks/exhaustive-deps
6668
}, []);
6769

68-
const onChangeEntities = (entities: Entities<P>) => {
70+
const onChangeEntities = (entities: Entities<E>) => {
6971
// TODO It is the state still necessary having the reducer?
7072
setEntitiesAndKind({
7173
...entitiesAndKind,
@@ -80,12 +82,12 @@ export function CompositeEntitiesByKind<P, T>(
8082
});
8183
dispatch({
8284
type: 'UPDATE_ENTITIES_OPTIONS',
83-
entitiesOptions: state.contexualEntitiesOptions,
85+
entitiesOptions: state.contextualEntitiesOptions,
8486
});
8587
return;
8688
}
8789

88-
const promises = Set<ReturnType<SearchEntities<P, T>>>([
90+
const promises = Set<ReturnType<SearchEntities<E, K>>>([
8991
props.searchEntities(searchPhrase, entitiesAndKind.kind, {
9092
exclude: entities,
9193
}),
@@ -116,14 +118,29 @@ export function CompositeEntitiesByKind<P, T>(
116118
});
117119
};
118120

119-
const onChangeKind = (kind: T) => {
121+
const onChangeKind = (kind: Kind<K> | K) => {
122+
const _kind =
123+
kind instanceof OrderedSet ? kind : OrderedSet<K>([kind as K]);
124+
120125
const entities = OrderedSet([]);
121-
setEntitiesAndKind({ kind, entities });
122-
props.kind.onChange(kind);
126+
setEntitiesAndKind({ kind: _kind, entities });
127+
props.kind.onChange(_kind);
123128
props.entities.onChange(entities);
124129

130+
if (_kind.size <= 0) {
131+
dispatch({
132+
type: 'UPDATE_SELECTED_ENTITIES_OPTIONS',
133+
selectedEntitiesOptions: entities,
134+
});
135+
dispatch({
136+
type: 'UPDATE_ENTITIES_OPTIONS',
137+
entitiesOptions: entities,
138+
});
139+
return;
140+
}
141+
125142
props
126-
.searchEntities('', kind, {
143+
.searchEntities('', _kind, {
127144
exclude: entitiesAndKind.entities,
128145
})
129146
.then((result) => {
@@ -147,10 +164,10 @@ export function CompositeEntitiesByKind<P, T>(
147164
});
148165
};
149166

150-
const entities: EntitiesSearch.EntitiesControl<P> = {
167+
const entities: EntitiesSearch.BaseControl<E> = {
151168
...props.entities,
152169
value: entitiesAndKind.entities,
153-
options: orderSelectedOptionsAtTheTop<P>(
170+
options: orderSelectedOptionsAtTheTop<E>(
154171
uniqueControlOptions(
155172
state.selectedEntitiesOptions.merge(state.entitiesOptions)
156173
),
@@ -159,7 +176,7 @@ export function CompositeEntitiesByKind<P, T>(
159176
onChange: onChangeEntities,
160177
};
161178

162-
const kind: EntitiesSearch.KindControl<T> = {
179+
const kind: EntitiesSearch.BaseControl<K> = {
163180
...props.kind,
164181
value: entitiesAndKind.kind,
165182
onChange: onChangeKind,
@@ -174,7 +191,7 @@ export function CompositeEntitiesByKind<P, T>(
174191
if (_phrase === '') {
175192
dispatch({
176193
type: 'UPDATE_ENTITIES_OPTIONS',
177-
entitiesOptions: state.contexualEntitiesOptions,
194+
entitiesOptions: state.contextualEntitiesOptions,
178195
});
179196
return;
180197
}

sources/js/src/components/entities-select-control.tsx renamed to sources/js/src/components/plural-select-control.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,10 @@ import React, { JSX } from 'react';
55

66
import { NoOptionsMessage } from './no-options-message';
77

8-
// TODO Must accept string or number? because the value might be the ID of the posts.
9-
// TODO Add className property to the other related components
10-
export function EntitiesSelectControl(
11-
props: EntitiesSearch.EntitiesControl<string>
8+
export function PluralSelectControl(
9+
props: EntitiesSearch.BaseControl<string> & {
10+
className?: string;
11+
}
1212
): JSX.Element {
1313
const className = classnames(
1414
props.className,

sources/js/src/components/kind-radio-control.tsx renamed to sources/js/src/components/radio-control.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@ import EntitiesSearch from '@types';
22
import classnames from 'classnames';
33
import React, { JSX } from 'react';
44

5-
export function KindRadioControl(
6-
props: EntitiesSearch.KindControl<string>
5+
export function RadioControl(
6+
props: EntitiesSearch.SingularControl<string> & { className?: string }
77
): JSX.Element {
88
const className = classnames(
99
props.className,

sources/js/src/components/kind-select-control.tsx renamed to sources/js/src/components/singular-select-control.tsx

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,27 @@ import EntitiesSearch from '@types';
22
import classnames from 'classnames';
33
import React, { JSX } from 'react';
44

5-
export function KindSelectControl(
6-
props: EntitiesSearch.KindControl<string>
5+
import { NoOptionsMessage } from './no-options-message';
6+
7+
export function SingularSelectControl(
8+
props: EntitiesSearch.SingularControl<string> & {
9+
className?: string;
10+
}
711
): JSX.Element {
812
const className = classnames(
913
props.className,
1014
'wz-select-control',
11-
'wz-select-control--kind'
15+
'wz-select-control--entities'
1216
);
1317

18+
if (props.options.size <= 0) {
19+
return <NoOptionsMessage />;
20+
}
21+
1422
return (
1523
<select
1624
className={className}
17-
value={props.value}
25+
value={props?.value}
1826
onChange={(event) => props.onChange(event.target.value)}
1927
>
2028
{props.options.map((option) => (

sources/js/src/components/entities-toggle-control.tsx renamed to sources/js/src/components/toggle-control.tsx

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
import EntitiesSearch from '@types';
22
import classnames from 'classnames';
3+
import { OrderedSet } from 'immutable';
34
import React, { JSX } from 'react';
45

56
import { NoOptionsMessage } from './no-options-message';
67

7-
export function EntitiesToggleControl(
8-
props: EntitiesSearch.EntitiesControl<string>
8+
export function ToggleControl(
9+
props: EntitiesSearch.BaseControl<string> & { className?: string }
910
): JSX.Element {
1011
const className = classnames(
1112
props.className,
@@ -21,11 +22,16 @@ export function EntitiesToggleControl(
2122
const { target } = event;
2223

2324
if (target.checked && !props.value?.has(target.value)) {
24-
props.onChange(props.value?.add(target.value));
25+
props.onChange(
26+
OrderedSet(props.value?.add(target.value).toArray() ?? [])
27+
);
2528
return;
2629
}
2730

28-
props.onChange(props.value?.delete(target.value));
31+
// TODO Stop handling `undefined`.
32+
props.onChange(
33+
OrderedSet(props.value?.delete(target.value).toArray() ?? [])
34+
);
2935
};
3036

3137
return (

sources/js/src/index.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
1+
export * from './api/fetch';
2+
export * from './api/search-entities';
3+
14
export * from './components/composite-entities-by-kind';
2-
export * from './components/kind-select-control';
3-
export * from './components/kind-radio-control';
4-
export * from './components/entities-select-control';
5-
export * from './components/entities-toggle-control';
5+
export * from './components/plural-select-control';
6+
export * from './components/radio-control';
67
export * from './components/search-control';
8+
export * from './components/singular-select-control';
9+
export * from './components/toggle-control';
710

811
export * from './hooks/use-entity-records';
912
export * from './hooks/use-query-viewable-post-types';
1013
export * from './hooks/use-query-viewable-taxonomies';
1114

1215
export * from './utils/convert-entities-to-control-options';
13-
14-
export * from './api/fetch';
15-
export * from './api/search-entities';

sources/js/src/storage/entities/initial-state.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ type Options<V> = EntitiesSearch.ControlOption<V>;
55

66
export function makeInitialState<V>(): EntitiesSearch.EntitiesState<V> {
77
return {
8-
contexualEntitiesOptions: OrderedSet<Options<V>>([]),
8+
contextualEntitiesOptions: OrderedSet<Options<V>>([]),
99
entitiesOptions: OrderedSet<Options<V>>([]),
1010
selectedEntitiesOptions: OrderedSet<Options<V>>([]),
1111
};

sources/js/src/storage/entities/reducer.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ export function reducer<P>(
88
case 'UPDATE_CONTEXTUAL_ENTITIES_OPTIONS':
99
return {
1010
...state,
11-
contexualEntitiesOptions: action.contextualEntitiesOptions,
11+
contextualEntitiesOptions: action.contextualEntitiesOptions,
1212
};
1313

1414
case 'UPDATE_ENTITIES_OPTIONS':

0 commit comments

Comments
 (0)