slice-machine-ui 2.16.2-alpha.jp-cr-ui-types-internal-version.1 → 2.16.2-alpha.jp-cr-ui-legacy-and-new-picker.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/out/404.html +1 -1
- package/out/_next/static/{bjRIqA2BKLEQ0oCFi096L → YCSNkhIsNcvCJJbYQxF12}/_buildManifest.js +1 -1
- package/out/_next/static/chunks/{268-046fd0c9d105117f.js → 268-6a9214b97195af9c.js} +1 -1
- package/out/_next/static/chunks/33641354.3864aefb6106ae71.js +28 -0
- package/out/_next/static/chunks/{34-7e02d3123542d645.js → 34-e684c5fd75cc9dd0.js} +1 -1
- package/out/_next/static/chunks/{630-6b2adc6e86debe3d.js → 630-db2634510e265e9a.js} +1 -1
- package/out/_next/static/chunks/{867-3f1b306bb3e20dec.js → 867-bb2db4d365ee7e40.js} +1 -1
- package/out/_next/static/chunks/{882-34af0755031a0763.js → 882-636039ab926dbc22.js} +1 -1
- package/out/_next/static/chunks/{895-63d0e67d0014ea28.js → 895-e5bf82a4d5d7c3e1.js} +1 -1
- package/out/_next/static/chunks/pages/{_app-80da54270aab8938.js → _app-ff5b76fbd166df98.js} +64 -64
- package/out/_next/static/chunks/pages/{changelog-1f45bbf9399626c7.js → changelog-063c5e11dfc8fd55.js} +1 -1
- package/out/_next/static/chunks/pages/{changes-f331fca898fc0c3a.js → changes-564336edb0ed18b0.js} +1 -1
- package/out/_next/static/chunks/pages/{labs-7cf1a8180d15bc81.js → labs-9630bfb1005be02b.js} +1 -1
- package/out/_next/static/chunks/pages/{settings-9d6e23720fd95d41.js → settings-01f4aeb9112a1f87.js} +1 -1
- package/out/_next/static/chunks/pages/slices/[lib]/[sliceName]/[variation]/{simulator-35c5ab99351f266a.js → simulator-5008e29008aa04f4.js} +1 -1
- package/out/_next/static/chunks/pages/slices/[lib]/[sliceName]/{[variation]-b89f6513d4bdea03.js → [variation]-85ab73c10f8322e1.js} +1 -1
- package/out/_next/static/chunks/pages/{slices-097e95abd417dd2d.js → slices-4a60cd5f2c71327e.js} +1 -1
- package/out/_next/static/chunks/{webpack-a75559e2f94885b4.js → webpack-b3522fdebabf510a.js} +1 -1
- package/out/_next/static/css/{7393c1f29ee22bf7.css → cc9b10286400c2b9.css} +1 -1
- package/out/changelog.html +1 -1
- package/out/changes.html +1 -1
- package/out/custom-types/[customTypeId].html +1 -1
- package/out/custom-types.html +1 -1
- package/out/index.html +1 -1
- package/out/labs.html +1 -1
- package/out/page-types/[pageTypeId].html +1 -1
- package/out/settings.html +1 -1
- package/out/slices/[lib]/[sliceName]/[variation]/simulator.html +1 -1
- package/out/slices/[lib]/[sliceName]/[variation].html +1 -1
- package/out/slices.html +1 -1
- package/package.json +6 -6
- package/src/features/customTypes/fields/ContentRelationshipFieldPicker.tsx +721 -387
- package/out/_next/static/chunks/e1b0e87a.19cd118baf725e50.js +0 -28
- /package/out/_next/static/{bjRIqA2BKLEQ0oCFi096L → YCSNkhIsNcvCJJbYQxF12}/_ssgManifest.js +0 -0
|
@@ -1,23 +1,44 @@
|
|
|
1
1
|
import { pluralize } from "@prismicio/editor-support/String";
|
|
2
|
+
import { revalidateData, useRequest } from "@prismicio/editor-support/Suspense";
|
|
2
3
|
import {
|
|
4
|
+
AnimatedSuspense,
|
|
5
|
+
Badge,
|
|
3
6
|
Box,
|
|
7
|
+
Button,
|
|
8
|
+
DropdownMenu,
|
|
9
|
+
DropdownMenuContent,
|
|
10
|
+
DropdownMenuItem,
|
|
11
|
+
DropdownMenuLabel,
|
|
12
|
+
DropdownMenuTrigger,
|
|
13
|
+
Icon,
|
|
14
|
+
IconButton,
|
|
15
|
+
Skeleton,
|
|
4
16
|
Text,
|
|
17
|
+
TextOverflow,
|
|
18
|
+
Tooltip,
|
|
5
19
|
TreeView,
|
|
6
20
|
TreeViewCheckbox,
|
|
7
21
|
TreeViewSection,
|
|
8
22
|
} from "@prismicio/editor-ui";
|
|
9
|
-
import {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
23
|
+
import {
|
|
24
|
+
CustomType,
|
|
25
|
+
Group,
|
|
26
|
+
Link,
|
|
27
|
+
LinkConfig,
|
|
28
|
+
NestableWidget,
|
|
29
|
+
} from "@prismicio/types-internal/lib/customtypes";
|
|
30
|
+
|
|
31
|
+
import { ErrorBoundary } from "@/ErrorBoundary";
|
|
32
|
+
import { managerClient } from "@/managerClient";
|
|
15
33
|
import { isValidObject } from "@/utils/isValidObject";
|
|
34
|
+
import { useEffect, useState } from "react";
|
|
35
|
+
|
|
36
|
+
type NonReadonly<T> = { -readonly [P in keyof T]: T[P] };
|
|
16
37
|
|
|
17
38
|
/**
|
|
18
39
|
* Picker fields check map types. Used internally to keep track of the checked
|
|
19
40
|
* fields in the TreeView, as it's easier to handle objects than arrays and
|
|
20
|
-
* also
|
|
41
|
+
* also ensures field uniqueness.
|
|
21
42
|
*
|
|
22
43
|
* @example
|
|
23
44
|
* {
|
|
@@ -127,7 +148,7 @@ interface PickerNestedCustomTypeValue {
|
|
|
127
148
|
}
|
|
128
149
|
|
|
129
150
|
/**
|
|
130
|
-
*
|
|
151
|
+
* Content relationship Link customtypes property structure.
|
|
131
152
|
*
|
|
132
153
|
* @example
|
|
133
154
|
* [
|
|
@@ -175,87 +196,174 @@ interface PickerNestedCustomTypeValue {
|
|
|
175
196
|
* },
|
|
176
197
|
* ]
|
|
177
198
|
*/
|
|
178
|
-
type
|
|
179
|
-
|
|
180
|
-
interface TICustomType {
|
|
181
|
-
id: string;
|
|
182
|
-
fields: readonly (
|
|
183
|
-
| string
|
|
184
|
-
| TIContentRelationshipFieldValue
|
|
185
|
-
| TIGroupFieldValues
|
|
186
|
-
)[];
|
|
187
|
-
}
|
|
199
|
+
type LinkCustomtypes = NonNullable<LinkConfig["customtypes"]>;
|
|
188
200
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
201
|
+
type LinkCustomtypesFields = Exclude<
|
|
202
|
+
LinkCustomtypes[number],
|
|
203
|
+
string
|
|
204
|
+
>["fields"][number];
|
|
193
205
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
fields:
|
|
197
|
-
|
|
206
|
+
type LinkCustomtypesContentRelationshipFieldValue = Exclude<
|
|
207
|
+
LinkCustomtypesFields,
|
|
208
|
+
string | { fields: unknown }
|
|
209
|
+
>;
|
|
198
210
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
customtypes:
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
interface TICustomTypeFieldValues {
|
|
205
|
-
id: string;
|
|
206
|
-
fields: readonly (string | TICustomTypeRegularFieldValues)[];
|
|
207
|
-
}
|
|
211
|
+
type LinkCustomtypesGroupFieldValue = Exclude<
|
|
212
|
+
LinkCustomtypesFields,
|
|
213
|
+
string | { customtypes: unknown }
|
|
214
|
+
>;
|
|
208
215
|
|
|
209
216
|
interface ContentRelationshipFieldPickerProps {
|
|
210
|
-
value:
|
|
211
|
-
onChange: (fields:
|
|
217
|
+
value: LinkCustomtypes | undefined;
|
|
218
|
+
onChange: (fields: LinkCustomtypes) => void;
|
|
212
219
|
}
|
|
213
220
|
|
|
214
221
|
export function ContentRelationshipFieldPicker(
|
|
215
222
|
props: ContentRelationshipFieldPickerProps,
|
|
223
|
+
) {
|
|
224
|
+
return (
|
|
225
|
+
<ErrorBoundary
|
|
226
|
+
renderError={() => (
|
|
227
|
+
<Box alignItems="center" gap={8}>
|
|
228
|
+
<Icon name="alert" size="small" color="tomato10" />
|
|
229
|
+
<Text color="tomato10">Error loading your types</Text>
|
|
230
|
+
</Box>
|
|
231
|
+
)}
|
|
232
|
+
>
|
|
233
|
+
<AnimatedSuspense
|
|
234
|
+
fallback={
|
|
235
|
+
<Box flexDirection="column" position="relative">
|
|
236
|
+
<Skeleton height={240} />
|
|
237
|
+
<Box
|
|
238
|
+
position="absolute"
|
|
239
|
+
top="50%"
|
|
240
|
+
left="50%"
|
|
241
|
+
transform="translate(-50%, -50%)"
|
|
242
|
+
alignItems="center"
|
|
243
|
+
gap={8}
|
|
244
|
+
>
|
|
245
|
+
<Icon name="autorenew" size="small" color="grey11" />
|
|
246
|
+
<Text color="grey11">Loading your types...</Text>
|
|
247
|
+
</Box>
|
|
248
|
+
</Box>
|
|
249
|
+
}
|
|
250
|
+
>
|
|
251
|
+
<ContentRelationshipFieldPickerContent {...props} />
|
|
252
|
+
</AnimatedSuspense>
|
|
253
|
+
</ErrorBoundary>
|
|
254
|
+
);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
function ContentRelationshipFieldPickerContent(
|
|
258
|
+
props: ContentRelationshipFieldPickerProps,
|
|
216
259
|
) {
|
|
217
260
|
const { value, onChange } = props;
|
|
218
|
-
const
|
|
219
|
-
|
|
261
|
+
const { allCustomTypes, availableCustomTypes, pickedCustomTypes } =
|
|
262
|
+
useCustomTypes(value);
|
|
263
|
+
|
|
264
|
+
const [isNewType, setIsNewType] = useState(false);
|
|
265
|
+
|
|
266
|
+
const fieldCheckMap = value
|
|
267
|
+
? convertLinkCustomtypesToFieldCheckMap(value)
|
|
268
|
+
: {};
|
|
220
269
|
|
|
221
|
-
function onCustomTypesChange(
|
|
270
|
+
function onCustomTypesChange(id: string, newCustomType: PickerCustomType) {
|
|
271
|
+
// The picker does not handle strings (custom type ids), as it's only meant
|
|
272
|
+
// to pick fields from custom types (objects). So we need to merge it with
|
|
273
|
+
// the existing value, which can have strings in the first level that
|
|
274
|
+
// represent new types added without any picked fields.
|
|
222
275
|
onChange(
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
276
|
+
mergeAndConvertCheckMapToLinkCustomtypes({
|
|
277
|
+
existingLinkCustomtypes: value,
|
|
278
|
+
previousPickerCustomtypes: fieldCheckMap,
|
|
279
|
+
customTypeId: id,
|
|
280
|
+
newCustomType,
|
|
226
281
|
}),
|
|
227
282
|
);
|
|
228
283
|
}
|
|
229
284
|
|
|
285
|
+
function addCustomType(id: string) {
|
|
286
|
+
setIsNewType(true);
|
|
287
|
+
onChange([...(value ?? []), id]);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
function removeCustomType(id: string) {
|
|
291
|
+
if (value) {
|
|
292
|
+
onChange(value.filter((existingCt) => getId(existingCt) !== id));
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
230
296
|
return (
|
|
231
|
-
<Box
|
|
297
|
+
<Box
|
|
298
|
+
overflow="hidden"
|
|
299
|
+
flexDirection="column"
|
|
300
|
+
border
|
|
301
|
+
borderRadius={6}
|
|
302
|
+
width="100%"
|
|
303
|
+
>
|
|
232
304
|
<Box
|
|
233
305
|
border={{ bottom: true }}
|
|
234
306
|
padding={{ inline: 16, bottom: 16, top: 12 }}
|
|
235
307
|
flexDirection="column"
|
|
236
308
|
gap={8}
|
|
237
309
|
>
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
310
|
+
{pickedCustomTypes.length > 0 ? (
|
|
311
|
+
<>
|
|
312
|
+
<Box flexDirection="column">
|
|
313
|
+
<Text variant="h4" color="grey12">
|
|
314
|
+
Allowed Types
|
|
315
|
+
</Text>
|
|
316
|
+
<Text color="grey11">
|
|
317
|
+
Restrict the selection to specific types your content editors
|
|
318
|
+
can link to in the Page Builder.
|
|
319
|
+
<br />
|
|
320
|
+
For each type, choose which fields to expose in the API
|
|
321
|
+
response.
|
|
322
|
+
</Text>
|
|
323
|
+
</Box>
|
|
324
|
+
{pickedCustomTypes.map((customType) => (
|
|
325
|
+
<Box
|
|
326
|
+
key={customType.id}
|
|
327
|
+
gap={4}
|
|
328
|
+
padding={8}
|
|
329
|
+
border
|
|
330
|
+
borderRadius={6}
|
|
331
|
+
borderColor="grey6"
|
|
332
|
+
backgroundColor="white"
|
|
333
|
+
justifyContent="space-between"
|
|
334
|
+
>
|
|
335
|
+
<TreeView>
|
|
336
|
+
<TreeViewCustomType
|
|
337
|
+
customType={customType}
|
|
338
|
+
onChange={(value) =>
|
|
339
|
+
onCustomTypesChange(customType.id, value)
|
|
340
|
+
}
|
|
341
|
+
fieldCheckMap={fieldCheckMap[customType.id] ?? {}}
|
|
342
|
+
allCustomTypes={allCustomTypes}
|
|
343
|
+
isNewType={isNewType}
|
|
344
|
+
/>
|
|
345
|
+
</TreeView>
|
|
346
|
+
<IconButton
|
|
347
|
+
icon="close"
|
|
348
|
+
size="small"
|
|
349
|
+
onClick={() => removeCustomType(customType.id)}
|
|
350
|
+
sx={{ height: 24, width: 24 }}
|
|
351
|
+
hiddenLabel="Remove type"
|
|
352
|
+
/>
|
|
353
|
+
</Box>
|
|
354
|
+
))}
|
|
355
|
+
<AddTypeButton
|
|
356
|
+
onSelect={addCustomType}
|
|
357
|
+
pickedCustomTypes={pickedCustomTypes}
|
|
358
|
+
availableCustomTypes={availableCustomTypes}
|
|
256
359
|
/>
|
|
257
|
-
|
|
258
|
-
|
|
360
|
+
</>
|
|
361
|
+
) : (
|
|
362
|
+
<EmptyView
|
|
363
|
+
onSelect={addCustomType}
|
|
364
|
+
availableCustomTypes={availableCustomTypes}
|
|
365
|
+
/>
|
|
366
|
+
)}
|
|
259
367
|
</Box>
|
|
260
368
|
<Box backgroundColor="white" flexDirection="column" padding={12}>
|
|
261
369
|
<Text variant="normal" color="grey11">
|
|
@@ -275,132 +383,276 @@ export function ContentRelationshipFieldPicker(
|
|
|
275
383
|
);
|
|
276
384
|
}
|
|
277
385
|
|
|
386
|
+
type EmptyViewProps = {
|
|
387
|
+
onSelect: (customTypeId: string) => void;
|
|
388
|
+
availableCustomTypes: CustomType[];
|
|
389
|
+
};
|
|
390
|
+
|
|
391
|
+
function EmptyView(props: EmptyViewProps) {
|
|
392
|
+
const { availableCustomTypes, onSelect } = props;
|
|
393
|
+
|
|
394
|
+
return (
|
|
395
|
+
<Box
|
|
396
|
+
flexDirection="column"
|
|
397
|
+
gap={8}
|
|
398
|
+
alignItems="center"
|
|
399
|
+
padding={{ block: 24 }}
|
|
400
|
+
>
|
|
401
|
+
<Box flexDirection="column" alignItems="center" gap={4}>
|
|
402
|
+
<Text variant="h5" color="grey12">
|
|
403
|
+
No types selected yet.
|
|
404
|
+
</Text>
|
|
405
|
+
<Text color="grey11" component="p" align="center">
|
|
406
|
+
Add one or more document types your content editors can link to.
|
|
407
|
+
<br />
|
|
408
|
+
For each type, select the fields to include in the API response (used
|
|
409
|
+
in your frontend queries).
|
|
410
|
+
</Text>
|
|
411
|
+
</Box>
|
|
412
|
+
<Box>
|
|
413
|
+
<AddTypeButton
|
|
414
|
+
availableCustomTypes={availableCustomTypes}
|
|
415
|
+
onSelect={onSelect}
|
|
416
|
+
/>
|
|
417
|
+
</Box>
|
|
418
|
+
</Box>
|
|
419
|
+
);
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
type AddTypeButtonProps = {
|
|
423
|
+
onSelect: (customTypeId: string) => void;
|
|
424
|
+
disabled?: boolean;
|
|
425
|
+
availableCustomTypes: CustomType[];
|
|
426
|
+
pickedCustomTypes?: CustomType[];
|
|
427
|
+
};
|
|
428
|
+
|
|
429
|
+
function AddTypeButton(props: AddTypeButtonProps) {
|
|
430
|
+
const { availableCustomTypes, onSelect, pickedCustomTypes = [] } = props;
|
|
431
|
+
|
|
432
|
+
const triggerButton = (
|
|
433
|
+
<Button
|
|
434
|
+
startIcon="add"
|
|
435
|
+
color="grey"
|
|
436
|
+
disabled={availableCustomTypes.length === 0}
|
|
437
|
+
>
|
|
438
|
+
{pickedCustomTypes.length > 0 ? "Add another type" : "Add type"}
|
|
439
|
+
</Button>
|
|
440
|
+
);
|
|
441
|
+
|
|
442
|
+
const disabledButton = (
|
|
443
|
+
<Box>
|
|
444
|
+
<Tooltip
|
|
445
|
+
content="All available types have been added"
|
|
446
|
+
side="bottom"
|
|
447
|
+
align="start"
|
|
448
|
+
disableHoverableContent
|
|
449
|
+
>
|
|
450
|
+
{triggerButton}
|
|
451
|
+
</Tooltip>
|
|
452
|
+
</Box>
|
|
453
|
+
);
|
|
454
|
+
|
|
455
|
+
if (availableCustomTypes.length === 0) return disabledButton;
|
|
456
|
+
|
|
457
|
+
return (
|
|
458
|
+
<Box>
|
|
459
|
+
<DropdownMenu>
|
|
460
|
+
<DropdownMenuTrigger>{triggerButton}</DropdownMenuTrigger>
|
|
461
|
+
<DropdownMenuContent
|
|
462
|
+
maxHeight={400}
|
|
463
|
+
minWidth={256}
|
|
464
|
+
align={pickedCustomTypes.length > 0 ? "start" : "center"}
|
|
465
|
+
>
|
|
466
|
+
<DropdownMenuLabel>
|
|
467
|
+
<Text color="grey11">Types</Text>
|
|
468
|
+
</DropdownMenuLabel>
|
|
469
|
+
{availableCustomTypes.flatMap((customType) => (
|
|
470
|
+
<DropdownMenuItem
|
|
471
|
+
key={customType.id}
|
|
472
|
+
onSelect={() => onSelect(customType.id)}
|
|
473
|
+
>
|
|
474
|
+
<Box alignItems="center" justifyContent="space-between" gap={8}>
|
|
475
|
+
<TextOverflow>
|
|
476
|
+
<Text>{customType.id}</Text>
|
|
477
|
+
</TextOverflow>
|
|
478
|
+
<Badge
|
|
479
|
+
title={
|
|
480
|
+
<Text variant="extraSmall" color="purple11">
|
|
481
|
+
{getTypeFormatLabel(customType.format)}
|
|
482
|
+
</Text>
|
|
483
|
+
}
|
|
484
|
+
color="purple"
|
|
485
|
+
size="small"
|
|
486
|
+
/>
|
|
487
|
+
</Box>
|
|
488
|
+
</DropdownMenuItem>
|
|
489
|
+
))}
|
|
490
|
+
</DropdownMenuContent>
|
|
491
|
+
</DropdownMenu>
|
|
492
|
+
</Box>
|
|
493
|
+
);
|
|
494
|
+
}
|
|
495
|
+
|
|
278
496
|
interface TreeViewCustomTypeProps {
|
|
279
|
-
customType:
|
|
497
|
+
customType: CustomType;
|
|
280
498
|
fieldCheckMap: PickerCustomType;
|
|
281
499
|
onChange: (newValue: PickerCustomType) => void;
|
|
500
|
+
allCustomTypes: CustomType[];
|
|
501
|
+
isNewType: boolean;
|
|
282
502
|
}
|
|
283
503
|
|
|
284
504
|
function TreeViewCustomType(props: TreeViewCustomTypeProps) {
|
|
285
505
|
const {
|
|
286
506
|
customType,
|
|
287
|
-
fieldCheckMap:
|
|
507
|
+
fieldCheckMap: customTypeFieldsCheckMap,
|
|
288
508
|
onChange: onCustomTypeChange,
|
|
509
|
+
allCustomTypes,
|
|
510
|
+
isNewType,
|
|
289
511
|
} = props;
|
|
290
512
|
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
title={customType.id}
|
|
295
|
-
subtitle={getExposedFieldsLabel(
|
|
296
|
-
countPickedFields(customTypeFieldCheckMap),
|
|
297
|
-
)}
|
|
298
|
-
badge="Custom type"
|
|
299
|
-
>
|
|
300
|
-
{customType.fields.map((field) => {
|
|
301
|
-
// Checkbox field
|
|
302
|
-
|
|
303
|
-
if (typeof field === "string") {
|
|
304
|
-
const checked = customTypeFieldCheckMap[field]?.value ?? false;
|
|
305
|
-
|
|
306
|
-
const onCheckedChange = (newValue: boolean) => {
|
|
307
|
-
onCustomTypeChange({
|
|
308
|
-
...customTypeFieldCheckMap,
|
|
309
|
-
[field]: { type: "checkbox", value: newValue },
|
|
310
|
-
});
|
|
311
|
-
};
|
|
312
|
-
|
|
313
|
-
return (
|
|
314
|
-
<TreeViewCheckbox
|
|
315
|
-
key={field}
|
|
316
|
-
title={field}
|
|
317
|
-
checked={checked === true}
|
|
318
|
-
onCheckedChange={onCheckedChange}
|
|
319
|
-
/>
|
|
320
|
-
);
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
const crOrGroupFieldCheckMap = customTypeFieldCheckMap[field.id] ?? {};
|
|
513
|
+
const renderedFields = getCustomTypeStaticFields(customType).map(
|
|
514
|
+
({ fieldId, field }) => {
|
|
515
|
+
// Group field
|
|
324
516
|
|
|
325
|
-
|
|
517
|
+
if (isGroupField(field)) {
|
|
518
|
+
const onGroupFieldChange = (
|
|
519
|
+
newGroupFields: PickerFirstLevelGroupFieldValue,
|
|
520
|
+
) => {
|
|
521
|
+
onCustomTypeChange({
|
|
522
|
+
...customTypeFieldsCheckMap,
|
|
523
|
+
[fieldId]: { type: "group", value: newGroupFields },
|
|
524
|
+
});
|
|
525
|
+
};
|
|
326
526
|
|
|
327
|
-
|
|
328
|
-
const onGroupFieldChange = (
|
|
329
|
-
newGroupFields: PickerFirstLevelGroupFieldValue,
|
|
330
|
-
) => {
|
|
331
|
-
onCustomTypeChange({
|
|
332
|
-
...customTypeFieldCheckMap,
|
|
333
|
-
[field.id]: { type: "group", value: newGroupFields },
|
|
334
|
-
});
|
|
335
|
-
};
|
|
527
|
+
const groupFieldCheckMap = customTypeFieldsCheckMap[fieldId] ?? {};
|
|
336
528
|
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
529
|
+
return (
|
|
530
|
+
<TreeViewFirstLevelGroupField
|
|
531
|
+
key={fieldId}
|
|
532
|
+
group={field}
|
|
533
|
+
groupId={fieldId}
|
|
534
|
+
onChange={onGroupFieldChange}
|
|
535
|
+
fieldCheckMap={
|
|
536
|
+
groupFieldCheckMap.type === "group"
|
|
537
|
+
? groupFieldCheckMap.value
|
|
538
|
+
: {}
|
|
539
|
+
}
|
|
540
|
+
allCustomTypes={allCustomTypes}
|
|
541
|
+
/>
|
|
542
|
+
);
|
|
543
|
+
}
|
|
350
544
|
|
|
351
|
-
|
|
545
|
+
// Content relationship field
|
|
352
546
|
|
|
547
|
+
if (isContentRelationshipField(field)) {
|
|
353
548
|
const onContentRelationshipFieldChange = (
|
|
354
549
|
newCrFields: PickerContentRelationshipFieldValue,
|
|
355
550
|
) => {
|
|
356
551
|
onCustomTypeChange({
|
|
357
|
-
...
|
|
358
|
-
[
|
|
552
|
+
...customTypeFieldsCheckMap,
|
|
553
|
+
[fieldId]: {
|
|
554
|
+
type: "contentRelationship",
|
|
555
|
+
value: newCrFields,
|
|
556
|
+
},
|
|
359
557
|
});
|
|
360
558
|
};
|
|
361
559
|
|
|
560
|
+
const crFieldCheckMap = customTypeFieldsCheckMap[fieldId] ?? {};
|
|
561
|
+
|
|
362
562
|
return (
|
|
363
563
|
<TreeViewContentRelationshipField
|
|
364
|
-
key={
|
|
564
|
+
key={fieldId}
|
|
365
565
|
field={field}
|
|
566
|
+
fieldId={fieldId}
|
|
366
567
|
onChange={onContentRelationshipFieldChange}
|
|
367
568
|
fieldCheckMap={
|
|
368
|
-
|
|
369
|
-
?
|
|
569
|
+
crFieldCheckMap.type === "contentRelationship"
|
|
570
|
+
? crFieldCheckMap.value
|
|
370
571
|
: {}
|
|
371
572
|
}
|
|
573
|
+
allCustomTypes={allCustomTypes}
|
|
372
574
|
/>
|
|
373
575
|
);
|
|
374
|
-
}
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
// Regular field
|
|
579
|
+
|
|
580
|
+
const onCheckedChange = (newValue: boolean) => {
|
|
581
|
+
onCustomTypeChange({
|
|
582
|
+
...customTypeFieldsCheckMap,
|
|
583
|
+
[fieldId]: { type: "checkbox", value: newValue },
|
|
584
|
+
});
|
|
585
|
+
};
|
|
586
|
+
|
|
587
|
+
return (
|
|
588
|
+
<TreeViewCheckbox
|
|
589
|
+
key={fieldId}
|
|
590
|
+
title={fieldId}
|
|
591
|
+
checked={customTypeFieldsCheckMap[fieldId]?.value === true}
|
|
592
|
+
onCheckedChange={onCheckedChange}
|
|
593
|
+
/>
|
|
594
|
+
);
|
|
595
|
+
},
|
|
596
|
+
);
|
|
597
|
+
|
|
598
|
+
const exposedFieldsCount = countPickedFields(customTypeFieldsCheckMap);
|
|
599
|
+
return (
|
|
600
|
+
<TreeViewSection
|
|
601
|
+
key={customType.id}
|
|
602
|
+
title={customType.id}
|
|
603
|
+
subtitle={
|
|
604
|
+
exposedFieldsCount > 0
|
|
605
|
+
? getExposedFieldsLabel(exposedFieldsCount)
|
|
606
|
+
: "(No fields returned in the API)"
|
|
607
|
+
}
|
|
608
|
+
badge={getTypeFormatLabel(customType.format)}
|
|
609
|
+
defaultOpen={isNewType}
|
|
610
|
+
>
|
|
611
|
+
{renderedFields.length > 0 ? (
|
|
612
|
+
renderedFields
|
|
613
|
+
) : (
|
|
614
|
+
<Text color="grey11">No available fields to select</Text>
|
|
615
|
+
)}
|
|
375
616
|
</TreeViewSection>
|
|
376
617
|
);
|
|
377
618
|
}
|
|
378
619
|
|
|
379
620
|
interface TreeViewContentRelationshipFieldProps {
|
|
380
|
-
|
|
621
|
+
fieldId: string;
|
|
622
|
+
field: Link;
|
|
381
623
|
fieldCheckMap: PickerContentRelationshipFieldValue;
|
|
382
624
|
onChange: (newValue: PickerContentRelationshipFieldValue) => void;
|
|
625
|
+
allCustomTypes: CustomType[];
|
|
383
626
|
}
|
|
384
627
|
|
|
385
628
|
function TreeViewContentRelationshipField(
|
|
386
629
|
props: TreeViewContentRelationshipFieldProps,
|
|
387
630
|
) {
|
|
388
631
|
const {
|
|
389
|
-
field
|
|
632
|
+
field,
|
|
633
|
+
fieldId,
|
|
390
634
|
fieldCheckMap: crFieldsCheckMap,
|
|
391
635
|
onChange: onCrFieldChange,
|
|
636
|
+
allCustomTypes,
|
|
392
637
|
} = props;
|
|
393
638
|
|
|
639
|
+
if (!field.config?.customtypes) return null;
|
|
640
|
+
|
|
641
|
+
const resolvedCustomTypes = resolveContentRelationshipCustomTypes(
|
|
642
|
+
field.config.customtypes,
|
|
643
|
+
allCustomTypes,
|
|
644
|
+
);
|
|
645
|
+
|
|
646
|
+
if (resolvedCustomTypes.length === 0) return null;
|
|
647
|
+
|
|
394
648
|
return (
|
|
395
649
|
<TreeViewSection
|
|
396
|
-
title={
|
|
650
|
+
title={fieldId}
|
|
397
651
|
subtitle={getExposedFieldsLabel(countPickedFields(crFieldsCheckMap))}
|
|
398
652
|
>
|
|
399
|
-
{
|
|
653
|
+
{resolvedCustomTypes.map((customType) => {
|
|
400
654
|
if (typeof customType === "string") return null;
|
|
401
655
|
|
|
402
|
-
const nestedCtFieldsCheckMap = crFieldsCheckMap[customType.id] ?? {};
|
|
403
|
-
|
|
404
656
|
const onNestedCustomTypeChange = (
|
|
405
657
|
newNestedCustomTypeFields: PickerNestedCustomTypeValue,
|
|
406
658
|
) => {
|
|
@@ -410,51 +662,29 @@ function TreeViewContentRelationshipField(
|
|
|
410
662
|
});
|
|
411
663
|
};
|
|
412
664
|
|
|
413
|
-
|
|
414
|
-
<TreeViewSection
|
|
415
|
-
key={customType.id}
|
|
416
|
-
title={customType.id}
|
|
417
|
-
subtitle={getExposedFieldsLabel(
|
|
418
|
-
countPickedFields(nestedCtFieldsCheckMap),
|
|
419
|
-
)}
|
|
420
|
-
badge="Custom type"
|
|
421
|
-
>
|
|
422
|
-
{customType.fields.map((field) => {
|
|
423
|
-
if (typeof field === "string") {
|
|
424
|
-
const checked = nestedCtFieldsCheckMap[field]?.value === true;
|
|
425
|
-
|
|
426
|
-
const onCheckedChange = (newChecked: boolean) => {
|
|
427
|
-
onNestedCustomTypeChange({
|
|
428
|
-
...nestedCtFieldsCheckMap,
|
|
429
|
-
[field]: { type: "checkbox", value: newChecked },
|
|
430
|
-
});
|
|
431
|
-
};
|
|
432
|
-
|
|
433
|
-
return (
|
|
434
|
-
<TreeViewCheckbox
|
|
435
|
-
key={field}
|
|
436
|
-
title={field}
|
|
437
|
-
checked={checked}
|
|
438
|
-
onCheckedChange={onCheckedChange}
|
|
439
|
-
/>
|
|
440
|
-
);
|
|
441
|
-
}
|
|
665
|
+
const nestedCtFieldsCheckMap = crFieldsCheckMap[customType.id] ?? {};
|
|
442
666
|
|
|
443
|
-
|
|
667
|
+
const renderedFields = getCustomTypeStaticFields(customType).map(
|
|
668
|
+
({ fieldId, field }) => {
|
|
669
|
+
// Group field
|
|
444
670
|
|
|
671
|
+
if (isGroupField(field)) {
|
|
445
672
|
const onGroupFieldsChange = (
|
|
446
673
|
newGroupFields: PickerLeafGroupFieldValue,
|
|
447
674
|
) => {
|
|
448
675
|
onNestedCustomTypeChange({
|
|
449
676
|
...nestedCtFieldsCheckMap,
|
|
450
|
-
[
|
|
677
|
+
[fieldId]: { type: "group", value: newGroupFields },
|
|
451
678
|
});
|
|
452
679
|
};
|
|
453
680
|
|
|
681
|
+
const groupFieldCheckMap = nestedCtFieldsCheckMap[fieldId] ?? {};
|
|
682
|
+
|
|
454
683
|
return (
|
|
455
|
-
<
|
|
456
|
-
key={
|
|
684
|
+
<TreeViewLeafGroupField
|
|
685
|
+
key={fieldId}
|
|
457
686
|
group={field}
|
|
687
|
+
groupId={fieldId}
|
|
458
688
|
onChange={onGroupFieldsChange}
|
|
459
689
|
fieldCheckMap={
|
|
460
690
|
groupFieldCheckMap.type === "group"
|
|
@@ -463,7 +693,40 @@ function TreeViewContentRelationshipField(
|
|
|
463
693
|
}
|
|
464
694
|
/>
|
|
465
695
|
);
|
|
466
|
-
}
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
// Regular field
|
|
699
|
+
|
|
700
|
+
const onCheckedChange = (newChecked: boolean) => {
|
|
701
|
+
onNestedCustomTypeChange({
|
|
702
|
+
...nestedCtFieldsCheckMap,
|
|
703
|
+
[fieldId]: { type: "checkbox", value: newChecked },
|
|
704
|
+
});
|
|
705
|
+
};
|
|
706
|
+
|
|
707
|
+
return (
|
|
708
|
+
<TreeViewCheckbox
|
|
709
|
+
key={fieldId}
|
|
710
|
+
title={fieldId}
|
|
711
|
+
checked={nestedCtFieldsCheckMap[fieldId]?.value === true}
|
|
712
|
+
onCheckedChange={onCheckedChange}
|
|
713
|
+
/>
|
|
714
|
+
);
|
|
715
|
+
},
|
|
716
|
+
);
|
|
717
|
+
|
|
718
|
+
if (renderedFields.length === 0) return null;
|
|
719
|
+
|
|
720
|
+
return (
|
|
721
|
+
<TreeViewSection
|
|
722
|
+
key={customType.id}
|
|
723
|
+
title={customType.id}
|
|
724
|
+
subtitle={getExposedFieldsLabel(
|
|
725
|
+
countPickedFields(nestedCtFieldsCheckMap),
|
|
726
|
+
)}
|
|
727
|
+
badge={getTypeFormatLabel(customType.format)}
|
|
728
|
+
>
|
|
729
|
+
{renderedFields}
|
|
467
730
|
</TreeViewSection>
|
|
468
731
|
);
|
|
469
732
|
})}
|
|
@@ -471,108 +734,128 @@ function TreeViewContentRelationshipField(
|
|
|
471
734
|
);
|
|
472
735
|
}
|
|
473
736
|
|
|
474
|
-
interface
|
|
475
|
-
group:
|
|
737
|
+
interface TreeViewLeafGroupFieldProps {
|
|
738
|
+
group: Group;
|
|
739
|
+
groupId: string;
|
|
476
740
|
fieldCheckMap: PickerLeafGroupFieldValue;
|
|
477
741
|
onChange: (newValue: PickerLeafGroupFieldValue) => void;
|
|
478
742
|
}
|
|
479
743
|
|
|
480
|
-
function
|
|
481
|
-
props: TreeViewContentRelationshipFieldGroupProps,
|
|
482
|
-
) {
|
|
744
|
+
function TreeViewLeafGroupField(props: TreeViewLeafGroupFieldProps) {
|
|
483
745
|
const {
|
|
484
746
|
group,
|
|
747
|
+
groupId,
|
|
485
748
|
fieldCheckMap: groupFieldsCheckMap,
|
|
486
749
|
onChange: onGroupFieldChange,
|
|
487
750
|
} = props;
|
|
488
751
|
|
|
752
|
+
if (!group.config?.fields) return null;
|
|
753
|
+
|
|
754
|
+
const renderedFields = getGroupFields(group).map(({ fieldId }) => {
|
|
755
|
+
const onCheckedChange = (newChecked: boolean) => {
|
|
756
|
+
onGroupFieldChange({
|
|
757
|
+
...groupFieldsCheckMap,
|
|
758
|
+
[fieldId]: { type: "checkbox", value: newChecked },
|
|
759
|
+
});
|
|
760
|
+
};
|
|
761
|
+
|
|
762
|
+
return (
|
|
763
|
+
<TreeViewCheckbox
|
|
764
|
+
key={fieldId}
|
|
765
|
+
title={fieldId}
|
|
766
|
+
checked={groupFieldsCheckMap[fieldId]?.value === true}
|
|
767
|
+
onCheckedChange={onCheckedChange}
|
|
768
|
+
/>
|
|
769
|
+
);
|
|
770
|
+
});
|
|
771
|
+
|
|
772
|
+
if (renderedFields.length === 0) return null;
|
|
773
|
+
|
|
489
774
|
return (
|
|
490
775
|
<TreeViewSection
|
|
491
|
-
key={
|
|
492
|
-
title={
|
|
776
|
+
key={groupId}
|
|
777
|
+
title={groupId}
|
|
493
778
|
subtitle={getExposedFieldsLabel(countPickedFields(groupFieldsCheckMap))}
|
|
494
779
|
badge="Group"
|
|
495
780
|
>
|
|
496
|
-
{
|
|
497
|
-
const checked = groupFieldsCheckMap[field]?.value ?? false;
|
|
498
|
-
|
|
499
|
-
const onCheckedChange = (newChecked: boolean) => {
|
|
500
|
-
onGroupFieldChange({
|
|
501
|
-
...groupFieldsCheckMap,
|
|
502
|
-
[field]: { type: "checkbox", value: newChecked },
|
|
503
|
-
});
|
|
504
|
-
};
|
|
505
|
-
|
|
506
|
-
return (
|
|
507
|
-
<TreeViewCheckbox
|
|
508
|
-
key={field}
|
|
509
|
-
title={field}
|
|
510
|
-
checked={checked === true}
|
|
511
|
-
onCheckedChange={onCheckedChange}
|
|
512
|
-
/>
|
|
513
|
-
);
|
|
514
|
-
})}
|
|
781
|
+
{renderedFields}
|
|
515
782
|
</TreeViewSection>
|
|
516
783
|
);
|
|
517
784
|
}
|
|
518
785
|
|
|
519
|
-
interface
|
|
520
|
-
group:
|
|
786
|
+
interface TreeViewFirstLevelGroupFieldProps {
|
|
787
|
+
group: Group;
|
|
788
|
+
groupId: string;
|
|
521
789
|
fieldCheckMap: PickerFirstLevelGroupFieldValue;
|
|
522
790
|
onChange: (newValue: PickerFirstLevelGroupFieldValue) => void;
|
|
791
|
+
allCustomTypes: CustomType[];
|
|
523
792
|
}
|
|
524
793
|
|
|
525
|
-
function
|
|
794
|
+
function TreeViewFirstLevelGroupField(
|
|
795
|
+
props: TreeViewFirstLevelGroupFieldProps,
|
|
796
|
+
) {
|
|
526
797
|
const {
|
|
527
798
|
group,
|
|
799
|
+
groupId,
|
|
528
800
|
fieldCheckMap: groupFieldsCheckMap,
|
|
529
801
|
onChange: onGroupFieldChange,
|
|
802
|
+
allCustomTypes,
|
|
530
803
|
} = props;
|
|
531
804
|
|
|
532
|
-
return
|
|
533
|
-
<TreeViewSection key={group.id} title={group.id} badge="Group">
|
|
534
|
-
{group.fields.map((field) => {
|
|
535
|
-
if (typeof field === "string") {
|
|
536
|
-
const checked = groupFieldsCheckMap[field]?.value ?? false;
|
|
805
|
+
if (!group.config?.fields) return null;
|
|
537
806
|
|
|
538
|
-
|
|
807
|
+
return (
|
|
808
|
+
<TreeViewSection
|
|
809
|
+
key={groupId}
|
|
810
|
+
title={groupId}
|
|
811
|
+
subtitle={getExposedFieldsLabel(countPickedFields(groupFieldsCheckMap))}
|
|
812
|
+
badge="Group"
|
|
813
|
+
>
|
|
814
|
+
{getGroupFields(group).map(({ fieldId, field }) => {
|
|
815
|
+
if (isContentRelationshipField(field)) {
|
|
816
|
+
const onContentRelationshipFieldChange = (
|
|
817
|
+
newCrFields: PickerContentRelationshipFieldValue,
|
|
818
|
+
) => {
|
|
539
819
|
onGroupFieldChange({
|
|
540
820
|
...groupFieldsCheckMap,
|
|
541
|
-
[
|
|
821
|
+
[fieldId]: {
|
|
822
|
+
type: "contentRelationship",
|
|
823
|
+
value: newCrFields,
|
|
824
|
+
},
|
|
542
825
|
});
|
|
543
826
|
};
|
|
544
827
|
|
|
828
|
+
const crFieldCheckMap = groupFieldsCheckMap[fieldId] ?? {};
|
|
829
|
+
|
|
545
830
|
return (
|
|
546
|
-
<
|
|
547
|
-
key={
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
831
|
+
<TreeViewContentRelationshipField
|
|
832
|
+
key={fieldId}
|
|
833
|
+
field={field}
|
|
834
|
+
fieldId={fieldId}
|
|
835
|
+
fieldCheckMap={
|
|
836
|
+
crFieldCheckMap.type === "contentRelationship"
|
|
837
|
+
? crFieldCheckMap.value
|
|
838
|
+
: {}
|
|
839
|
+
}
|
|
840
|
+
onChange={onContentRelationshipFieldChange}
|
|
841
|
+
allCustomTypes={allCustomTypes}
|
|
551
842
|
/>
|
|
552
843
|
);
|
|
553
844
|
}
|
|
554
845
|
|
|
555
|
-
const
|
|
556
|
-
|
|
557
|
-
const onContentRelationshipFieldChange = (
|
|
558
|
-
newCrFields: PickerContentRelationshipFieldValue,
|
|
559
|
-
) => {
|
|
846
|
+
const onCheckedChange = (newChecked: boolean) => {
|
|
560
847
|
onGroupFieldChange({
|
|
561
848
|
...groupFieldsCheckMap,
|
|
562
|
-
[
|
|
849
|
+
[fieldId]: { type: "checkbox", value: newChecked },
|
|
563
850
|
});
|
|
564
851
|
};
|
|
565
852
|
|
|
566
853
|
return (
|
|
567
|
-
<
|
|
568
|
-
key={
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
? crFieldCheckMap.value
|
|
573
|
-
: {}
|
|
574
|
-
}
|
|
575
|
-
onChange={onContentRelationshipFieldChange}
|
|
854
|
+
<TreeViewCheckbox
|
|
855
|
+
key={fieldId}
|
|
856
|
+
title={fieldId}
|
|
857
|
+
checked={groupFieldsCheckMap[fieldId]?.value === true}
|
|
858
|
+
onCheckedChange={onCheckedChange}
|
|
576
859
|
/>
|
|
577
860
|
);
|
|
578
861
|
})}
|
|
@@ -582,124 +865,72 @@ function TreeViewGroupField(props: TreeViewGroupFieldProps) {
|
|
|
582
865
|
|
|
583
866
|
function getExposedFieldsLabel(count: number) {
|
|
584
867
|
if (count === 0) return undefined;
|
|
585
|
-
return `(${count} ${pluralize(
|
|
868
|
+
return `(${count} ${pluralize(
|
|
869
|
+
count,
|
|
870
|
+
"field",
|
|
871
|
+
"fields",
|
|
872
|
+
)} returned in the API)`;
|
|
586
873
|
}
|
|
587
874
|
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
* Link config `customtypes` structure {@link TICustomTypes}.
|
|
592
|
-
*/
|
|
593
|
-
function useCustomTypes() {
|
|
594
|
-
const allCustomTypes = useSelector(selectAllCustomTypes);
|
|
595
|
-
const localCustomTypes = allCustomTypes.flatMap((ct) => {
|
|
596
|
-
// In the store we have remote and local custom types, we want to show
|
|
597
|
-
// the local ones, so that the user is able to create a content
|
|
598
|
-
// relationship with custom types present on the user's computer (pushed
|
|
599
|
-
// or not).
|
|
600
|
-
return "local" in ct ? ct.local : [];
|
|
601
|
-
});
|
|
602
|
-
|
|
603
|
-
return useMemo(() => {
|
|
604
|
-
const customTypes = localCustomTypes.flatMap<TICustomType>((customType) => {
|
|
605
|
-
const tabFields = customType.tabs.flatMap((tab) => {
|
|
606
|
-
return tab.value.flatMap((field) => {
|
|
607
|
-
if (isUidField(field)) return [];
|
|
608
|
-
|
|
609
|
-
// Check if it's a content relationship link/field
|
|
610
|
-
if (
|
|
611
|
-
field.value.type === "Link" &&
|
|
612
|
-
field.value.config?.select === "document" &&
|
|
613
|
-
field.value.config.customtypes
|
|
614
|
-
) {
|
|
615
|
-
const resolvedFields = resolveContentRelationshipFields(
|
|
616
|
-
field.value.config.customtypes,
|
|
617
|
-
localCustomTypes,
|
|
618
|
-
);
|
|
619
|
-
|
|
620
|
-
return resolvedFields.length > 0
|
|
621
|
-
? { id: field.key, customtypes: resolvedFields }
|
|
622
|
-
: [];
|
|
623
|
-
}
|
|
875
|
+
function getTypeFormatLabel(format: CustomType["format"]) {
|
|
876
|
+
return format === "page" ? "Page type" : "Custom type";
|
|
877
|
+
}
|
|
624
878
|
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
fields: field.value.config.fields.map((field) => field.key),
|
|
629
|
-
};
|
|
630
|
-
}
|
|
879
|
+
/** Retrieves all existing page & custom types. */
|
|
880
|
+
function useCustomTypes(value: LinkCustomtypes | undefined) {
|
|
881
|
+
const allCustomTypes = useRequest(getCustomTypes, []);
|
|
631
882
|
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
883
|
+
useEffect(() => {
|
|
884
|
+
void revalidateData(getCustomTypes, []);
|
|
885
|
+
}, []);
|
|
635
886
|
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
887
|
+
if (!value) {
|
|
888
|
+
return {
|
|
889
|
+
allCustomTypes,
|
|
890
|
+
availableCustomTypes: allCustomTypes,
|
|
891
|
+
pickedCustomTypes: [],
|
|
892
|
+
};
|
|
893
|
+
}
|
|
640
894
|
|
|
641
|
-
|
|
895
|
+
const pickedCustomTypes = value.flatMap(
|
|
896
|
+
(pickedCt) => allCustomTypes.find((ct) => ct.id === getId(pickedCt)) ?? [],
|
|
897
|
+
);
|
|
642
898
|
|
|
643
|
-
|
|
644
|
-
|
|
899
|
+
return {
|
|
900
|
+
allCustomTypes,
|
|
901
|
+
pickedCustomTypes,
|
|
902
|
+
availableCustomTypes: allCustomTypes.filter(
|
|
903
|
+
(ct) => pickedCustomTypes.some((pct) => pct.id === ct.id) === false,
|
|
904
|
+
),
|
|
905
|
+
};
|
|
645
906
|
}
|
|
646
907
|
|
|
647
|
-
function
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
): TICustomTypeFieldValues[] {
|
|
651
|
-
const fields = customTypesArray.flatMap<TICustomTypeFieldValues>(
|
|
652
|
-
(customType) => {
|
|
653
|
-
if (typeof customType === "string") return [];
|
|
654
|
-
|
|
655
|
-
// Didn't find a custom type with a matching id in the store
|
|
656
|
-
const matchingCustomType = localCustomTypes.find(
|
|
657
|
-
(ct) => ct.id === customType.id,
|
|
658
|
-
);
|
|
659
|
-
if (!matchingCustomType) return [];
|
|
908
|
+
async function getCustomTypes(): Promise<CustomType[]> {
|
|
909
|
+
const { errors, models } =
|
|
910
|
+
await managerClient.customTypes.readAllCustomTypes();
|
|
660
911
|
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
// Group inside content relationship field
|
|
666
|
-
if (field.value.type === "Group" && field.value.config?.fields) {
|
|
667
|
-
return {
|
|
668
|
-
id: field.key,
|
|
669
|
-
fields: field.value.config.fields.map((field) => field.key),
|
|
670
|
-
};
|
|
671
|
-
}
|
|
672
|
-
|
|
673
|
-
return field.key;
|
|
674
|
-
});
|
|
675
|
-
});
|
|
912
|
+
if (errors.length > 0) throw errors;
|
|
913
|
+
return models.map(({ model }) => model);
|
|
914
|
+
}
|
|
676
915
|
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
)
|
|
916
|
+
function resolveContentRelationshipCustomTypes(
|
|
917
|
+
customTypes: LinkCustomtypes,
|
|
918
|
+
localCustomTypes: CustomType[],
|
|
919
|
+
): CustomType[] {
|
|
920
|
+
const fields = customTypes.flatMap<CustomType>((customType) => {
|
|
921
|
+
if (typeof customType === "string") return [];
|
|
922
|
+
return localCustomTypes.find((ct) => ct.id === customType.id) ?? [];
|
|
923
|
+
});
|
|
682
924
|
|
|
683
925
|
return fields;
|
|
684
926
|
}
|
|
685
927
|
|
|
686
|
-
function isUidField(
|
|
687
|
-
field: TabFields[number],
|
|
688
|
-
): field is { key: string; value: UID } {
|
|
689
|
-
// Filter out uid fields because it's a special field returned by the
|
|
690
|
-
// API and is not part of the data object in the document.
|
|
691
|
-
// We also filter by key "uid", because (as of the time of writing
|
|
692
|
-
// this), creating any field with that API id will result in it being
|
|
693
|
-
// used for metadata.
|
|
694
|
-
return field.key === "uid" && field.value.type === "UID";
|
|
695
|
-
}
|
|
696
|
-
|
|
697
928
|
/**
|
|
698
|
-
* Converts a Link config `customtypes` ({@link
|
|
929
|
+
* Converts a Link config `customtypes` ({@link LinkCustomtypes}) structure into
|
|
699
930
|
* picker fields check map ({@link PickerCustomTypes}).
|
|
700
931
|
*/
|
|
701
|
-
function
|
|
702
|
-
customTypes:
|
|
932
|
+
function convertLinkCustomtypesToFieldCheckMap(
|
|
933
|
+
customTypes: LinkCustomtypes,
|
|
703
934
|
): PickerCustomTypes {
|
|
704
935
|
return customTypes.reduce<PickerCustomTypes>((customTypes, customType) => {
|
|
705
936
|
if (typeof customType === "string") return customTypes;
|
|
@@ -711,10 +942,11 @@ function convertCustomTypesToFieldCheckMap(
|
|
|
711
942
|
customTypeFields[field] = { type: "checkbox", value: true };
|
|
712
943
|
} else if ("fields" in field && field.fields !== undefined) {
|
|
713
944
|
// Group field
|
|
714
|
-
customTypeFields[field.id] =
|
|
945
|
+
customTypeFields[field.id] = createGroupFieldCheckMap(field);
|
|
715
946
|
} else if ("customtypes" in field && field.customtypes !== undefined) {
|
|
716
947
|
// Content relationship field
|
|
717
|
-
customTypeFields[field.id] =
|
|
948
|
+
customTypeFields[field.id] =
|
|
949
|
+
createContentRelationshipFieldCheckMap(field);
|
|
718
950
|
}
|
|
719
951
|
|
|
720
952
|
return customTypeFields;
|
|
@@ -725,8 +957,8 @@ function convertCustomTypesToFieldCheckMap(
|
|
|
725
957
|
}, {});
|
|
726
958
|
}
|
|
727
959
|
|
|
728
|
-
function
|
|
729
|
-
group:
|
|
960
|
+
function createGroupFieldCheckMap(
|
|
961
|
+
group: LinkCustomtypesGroupFieldValue,
|
|
730
962
|
): PickerFirstLevelGroupField {
|
|
731
963
|
return {
|
|
732
964
|
type: "group",
|
|
@@ -737,7 +969,7 @@ function createGroupField(
|
|
|
737
969
|
fields[field] = { type: "checkbox", value: true };
|
|
738
970
|
} else if ("customtypes" in field && field.customtypes !== undefined) {
|
|
739
971
|
// Content relationship field
|
|
740
|
-
fields[field.id] =
|
|
972
|
+
fields[field.id] = createContentRelationshipFieldCheckMap(field);
|
|
741
973
|
}
|
|
742
974
|
|
|
743
975
|
return fields;
|
|
@@ -747,8 +979,8 @@ function createGroupField(
|
|
|
747
979
|
};
|
|
748
980
|
}
|
|
749
981
|
|
|
750
|
-
function
|
|
751
|
-
field:
|
|
982
|
+
function createContentRelationshipFieldCheckMap(
|
|
983
|
+
field: LinkCustomtypesContentRelationshipFieldValue,
|
|
752
984
|
): PickerContentRelationshipField {
|
|
753
985
|
const crField: PickerContentRelationshipField = {
|
|
754
986
|
type: "contentRelationship",
|
|
@@ -785,78 +1017,134 @@ function createNestedCustomTypeField(
|
|
|
785
1017
|
return crField;
|
|
786
1018
|
}
|
|
787
1019
|
|
|
1020
|
+
/**
|
|
1021
|
+
* Merges the existing Link `customtypes` array with the picker state, ensuring
|
|
1022
|
+
* that conversions from to string (custom type id) to object and vice versa are
|
|
1023
|
+
* made correctly and that the order is preserved.
|
|
1024
|
+
*/
|
|
1025
|
+
function mergeAndConvertCheckMapToLinkCustomtypes(args: {
|
|
1026
|
+
existingLinkCustomtypes: LinkCustomtypes | undefined;
|
|
1027
|
+
previousPickerCustomtypes: PickerCustomTypes;
|
|
1028
|
+
newCustomType: PickerCustomType;
|
|
1029
|
+
customTypeId: string;
|
|
1030
|
+
}): LinkCustomtypes {
|
|
1031
|
+
const {
|
|
1032
|
+
existingLinkCustomtypes,
|
|
1033
|
+
previousPickerCustomtypes,
|
|
1034
|
+
newCustomType,
|
|
1035
|
+
customTypeId,
|
|
1036
|
+
} = args;
|
|
1037
|
+
|
|
1038
|
+
const result: NonReadonly<LinkCustomtypes> = [];
|
|
1039
|
+
const pickerLinkCustomtypes = convertFieldCheckMapToLinkCustomtypes({
|
|
1040
|
+
...previousPickerCustomtypes,
|
|
1041
|
+
[customTypeId]: newCustomType,
|
|
1042
|
+
});
|
|
1043
|
+
|
|
1044
|
+
if (!existingLinkCustomtypes) return pickerLinkCustomtypes;
|
|
1045
|
+
|
|
1046
|
+
for (const existingLinkCt of existingLinkCustomtypes) {
|
|
1047
|
+
const existingPickerLinkCt = pickerLinkCustomtypes.find((ct) => {
|
|
1048
|
+
return getId(ct) === getId(existingLinkCt);
|
|
1049
|
+
});
|
|
1050
|
+
|
|
1051
|
+
if (existingPickerLinkCt !== undefined) {
|
|
1052
|
+
// Custom type with exposed fields, keep the customtypes object
|
|
1053
|
+
result.push(existingPickerLinkCt);
|
|
1054
|
+
} else if (getId(existingLinkCt) === customTypeId) {
|
|
1055
|
+
// Custom type that had exposed fields, but now has none, change to string
|
|
1056
|
+
result.push(getId(existingLinkCt));
|
|
1057
|
+
} else {
|
|
1058
|
+
// Custom type without exposed fields, keep the string
|
|
1059
|
+
result.push(existingLinkCt);
|
|
1060
|
+
}
|
|
1061
|
+
}
|
|
1062
|
+
|
|
1063
|
+
return result;
|
|
1064
|
+
}
|
|
1065
|
+
|
|
788
1066
|
/**
|
|
789
1067
|
* Converts a picker fields check map structure ({@link PickerCustomTypes}) into
|
|
790
|
-
* Link config `customtypes` ({@link
|
|
1068
|
+
* Link config `customtypes` ({@link LinkCustomtypes}) and filter out empty Custom
|
|
791
1069
|
* types.
|
|
792
1070
|
*/
|
|
793
|
-
function
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
1071
|
+
function convertFieldCheckMapToLinkCustomtypes(
|
|
1072
|
+
checkMap: PickerCustomTypes,
|
|
1073
|
+
): LinkCustomtypes {
|
|
1074
|
+
return Object.entries(checkMap).flatMap<LinkCustomtypes[number]>(
|
|
1075
|
+
([ctId, ctFields]) => {
|
|
1076
|
+
const fields = Object.entries(ctFields).flatMap<
|
|
1077
|
+
| string
|
|
1078
|
+
| LinkCustomtypesContentRelationshipFieldValue
|
|
1079
|
+
| LinkCustomtypesGroupFieldValue
|
|
1080
|
+
>(([fieldId, fieldValue]) => {
|
|
1081
|
+
// First level group field
|
|
1082
|
+
if (fieldValue.type === "group") {
|
|
1083
|
+
const fields = Object.entries(fieldValue.value).flatMap<
|
|
1084
|
+
string | LinkCustomtypesContentRelationshipFieldValue
|
|
1085
|
+
>(([fieldId, fieldValue]) => {
|
|
1086
|
+
if (fieldValue.type === "checkbox") {
|
|
1087
|
+
return fieldValue.value ? fieldId : [];
|
|
1088
|
+
}
|
|
801
1089
|
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
>(([fieldId, fieldValue]) => {
|
|
806
|
-
if (fieldValue.type === "checkbox") {
|
|
807
|
-
return fieldValue.value ? fieldId : [];
|
|
808
|
-
}
|
|
1090
|
+
const customTypes = createContentRelationshipLinkCustomtypes(
|
|
1091
|
+
fieldValue.value,
|
|
1092
|
+
);
|
|
809
1093
|
|
|
810
|
-
|
|
1094
|
+
return customTypes.length > 0
|
|
1095
|
+
? { id: fieldId, customtypes: customTypes }
|
|
1096
|
+
: [];
|
|
1097
|
+
});
|
|
1098
|
+
|
|
1099
|
+
return fields.length > 0 ? { id: fieldId, fields } : [];
|
|
1100
|
+
}
|
|
1101
|
+
|
|
1102
|
+
// Content relationship field
|
|
1103
|
+
if (fieldValue.type === "contentRelationship") {
|
|
1104
|
+
const customTypes = createContentRelationshipLinkCustomtypes(
|
|
811
1105
|
fieldValue.value,
|
|
812
1106
|
);
|
|
813
1107
|
|
|
814
1108
|
return customTypes.length > 0
|
|
815
1109
|
? { id: fieldId, customtypes: customTypes }
|
|
816
1110
|
: [];
|
|
817
|
-
}
|
|
818
|
-
|
|
819
|
-
return fields.length > 0 ? { id: fieldId, fields } : [];
|
|
820
|
-
}
|
|
821
|
-
|
|
822
|
-
// Content relationship field
|
|
823
|
-
const customTypes = convertContentRelationshipFieldValueToCustomTypes(
|
|
824
|
-
fieldValue.value,
|
|
825
|
-
);
|
|
1111
|
+
}
|
|
826
1112
|
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
});
|
|
1113
|
+
// Regular field
|
|
1114
|
+
return fieldValue.value ? fieldId : [];
|
|
1115
|
+
});
|
|
831
1116
|
|
|
832
|
-
|
|
833
|
-
|
|
1117
|
+
return fields.length > 0 ? { id: ctId, fields } : [];
|
|
1118
|
+
},
|
|
1119
|
+
);
|
|
834
1120
|
}
|
|
835
1121
|
|
|
836
|
-
function
|
|
1122
|
+
function createContentRelationshipLinkCustomtypes(
|
|
837
1123
|
value: PickerContentRelationshipFieldValue,
|
|
838
|
-
):
|
|
839
|
-
return Object.entries(value).flatMap
|
|
1124
|
+
): LinkCustomtypesContentRelationshipFieldValue["customtypes"] {
|
|
1125
|
+
return Object.entries(value).flatMap(
|
|
840
1126
|
([nestedCustomTypeId, nestedCustomTypeFields]) => {
|
|
841
|
-
const fields = Object.entries(nestedCustomTypeFields).flatMap
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
1127
|
+
const fields = Object.entries(nestedCustomTypeFields).flatMap(
|
|
1128
|
+
([nestedFieldId, nestedFieldValue]) => {
|
|
1129
|
+
// Leaf group field
|
|
1130
|
+
if (nestedFieldValue.type === "group") {
|
|
1131
|
+
const nestedGroupFields = Object.entries(
|
|
1132
|
+
nestedFieldValue.value,
|
|
1133
|
+
).flatMap<string>(([fieldId, fieldValue]) => {
|
|
1134
|
+
// Regular field
|
|
1135
|
+
return fieldValue.type === "checkbox" && fieldValue.value
|
|
1136
|
+
? fieldId
|
|
1137
|
+
: [];
|
|
1138
|
+
});
|
|
852
1139
|
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
1140
|
+
return nestedGroupFields.length > 0
|
|
1141
|
+
? { id: nestedFieldId, fields: nestedGroupFields }
|
|
1142
|
+
: [];
|
|
1143
|
+
}
|
|
857
1144
|
|
|
858
|
-
|
|
859
|
-
|
|
1145
|
+
return nestedFieldValue.value ? nestedFieldId : [];
|
|
1146
|
+
},
|
|
1147
|
+
);
|
|
860
1148
|
|
|
861
1149
|
return fields.length > 0 ? { id: nestedCustomTypeId, fields } : [];
|
|
862
1150
|
},
|
|
@@ -876,12 +1164,58 @@ function countPickedFields(
|
|
|
876
1164
|
if (!fields) return 0;
|
|
877
1165
|
return Object.values(fields).reduce<number>((count, value) => {
|
|
878
1166
|
if (!isValidObject(value)) return count;
|
|
879
|
-
if (
|
|
1167
|
+
if (isCheckboxValue(value)) return count + (value.value ? 1 : 0);
|
|
880
1168
|
return count + countPickedFields(value);
|
|
881
1169
|
}, 0);
|
|
882
1170
|
}
|
|
883
|
-
|
|
884
|
-
function isCheckboxField(value: unknown): value is PickerCheckboxField {
|
|
1171
|
+
function isCheckboxValue(value: unknown): value is PickerCheckboxField {
|
|
885
1172
|
if (!isValidObject(value)) return false;
|
|
886
1173
|
return "type" in value && value.type === "checkbox";
|
|
887
1174
|
}
|
|
1175
|
+
|
|
1176
|
+
function isGroupField(field: NestableWidget | Group): field is Group {
|
|
1177
|
+
return field.type === "Group";
|
|
1178
|
+
}
|
|
1179
|
+
|
|
1180
|
+
function isContentRelationshipField(
|
|
1181
|
+
field: NestableWidget | Group,
|
|
1182
|
+
): field is Link {
|
|
1183
|
+
return (
|
|
1184
|
+
field.type === "Link" &&
|
|
1185
|
+
field.config?.select === "document" &&
|
|
1186
|
+
field.config?.customtypes !== undefined
|
|
1187
|
+
);
|
|
1188
|
+
}
|
|
1189
|
+
|
|
1190
|
+
function getCustomTypeStaticFields(customType: CustomType) {
|
|
1191
|
+
return Object.values(customType.json).flatMap((tabFields) => {
|
|
1192
|
+
return Object.entries(tabFields).flatMap(([fieldId, field]) => {
|
|
1193
|
+
if (
|
|
1194
|
+
field.type !== "Slices" &&
|
|
1195
|
+
field.type !== "Choice" &&
|
|
1196
|
+
// Filter out uid fields because it's a special field returned by the
|
|
1197
|
+
// API and is not part of the data object in the document.
|
|
1198
|
+
// We also filter by key "uid", because (as of the time of writing
|
|
1199
|
+
// this), creating any field with that API id will result in it being
|
|
1200
|
+
// used for metadata.
|
|
1201
|
+
(field.type !== "UID" || fieldId !== "uid")
|
|
1202
|
+
) {
|
|
1203
|
+
return { fieldId, field: field as NestableWidget | Group };
|
|
1204
|
+
}
|
|
1205
|
+
|
|
1206
|
+
return [];
|
|
1207
|
+
});
|
|
1208
|
+
});
|
|
1209
|
+
}
|
|
1210
|
+
|
|
1211
|
+
function getGroupFields(group: Group) {
|
|
1212
|
+
if (!group.config?.fields) return [];
|
|
1213
|
+
return Object.entries(group.config.fields).map(([fieldId, field]) => {
|
|
1214
|
+
return { fieldId, field: field as NestableWidget };
|
|
1215
|
+
});
|
|
1216
|
+
}
|
|
1217
|
+
/** If it's a string, return it, otherwise return the `id` property. */
|
|
1218
|
+
function getId<T extends string | { id: string }>(customType: T): string {
|
|
1219
|
+
if (typeof customType === "string") return customType;
|
|
1220
|
+
return customType.id;
|
|
1221
|
+
}
|