@firecms/collection_editor 3.0.1 → 3.1.0-canary.9e89e98

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.
Files changed (90) hide show
  1. package/dist/ConfigControllerProvider.d.ts +6 -0
  2. package/dist/api/generateCollectionApi.d.ts +71 -0
  3. package/dist/api/index.d.ts +1 -0
  4. package/dist/index.d.ts +5 -1
  5. package/dist/index.es.js +9418 -5587
  6. package/dist/index.es.js.map +1 -1
  7. package/dist/index.umd.js +9413 -5582
  8. package/dist/index.umd.js.map +1 -1
  9. package/dist/types/collection_editor_controller.d.ts +14 -0
  10. package/dist/types/collection_inference.d.ts +8 -2
  11. package/dist/types/config_controller.d.ts +23 -2
  12. package/dist/ui/AddKanbanColumnAction.d.ts +11 -0
  13. package/dist/ui/KanbanSetupAction.d.ts +10 -0
  14. package/dist/ui/collection_editor/AICollectionGeneratorPopover.d.ts +33 -0
  15. package/dist/ui/collection_editor/AIModifiedPathsContext.d.ts +20 -0
  16. package/dist/ui/collection_editor/CollectionDetailsForm.d.ts +2 -3
  17. package/dist/ui/collection_editor/CollectionEditorDialog.d.ts +20 -0
  18. package/dist/ui/collection_editor/CollectionEditorWelcomeView.d.ts +3 -1
  19. package/dist/ui/collection_editor/CollectionJsonImportDialog.d.ts +7 -0
  20. package/dist/ui/collection_editor/CollectionYupValidation.d.ts +9 -13
  21. package/dist/ui/collection_editor/DisplaySettingsForm.d.ts +3 -0
  22. package/dist/ui/collection_editor/EntityActionsEditTab.d.ts +2 -1
  23. package/dist/ui/collection_editor/ExtendSettingsForm.d.ts +14 -0
  24. package/dist/ui/collection_editor/GeneralSettingsForm.d.ts +7 -0
  25. package/dist/ui/collection_editor/KanbanConfigSection.d.ts +4 -0
  26. package/dist/ui/collection_editor/PropertyEditView.d.ts +6 -1
  27. package/dist/ui/collection_editor/PropertyTree.d.ts +2 -1
  28. package/dist/ui/collection_editor/SubcollectionsEditTab.d.ts +2 -1
  29. package/dist/ui/collection_editor/ViewModeSwitch.d.ts +6 -0
  30. package/dist/ui/collection_editor/properties/EnumPropertyField.d.ts +2 -1
  31. package/dist/ui/collection_editor/properties/conditions/ConditionsEditor.d.ts +10 -0
  32. package/dist/ui/collection_editor/properties/conditions/ConditionsPanel.d.ts +2 -0
  33. package/dist/ui/collection_editor/properties/conditions/EnumConditionsEditor.d.ts +6 -0
  34. package/dist/ui/collection_editor/properties/conditions/index.d.ts +6 -0
  35. package/dist/ui/collection_editor/properties/conditions/property_paths.d.ts +19 -0
  36. package/dist/useCollectionEditorPlugin.d.ts +7 -1
  37. package/dist/utils/validateCollectionJson.d.ts +22 -0
  38. package/package.json +11 -11
  39. package/src/ConfigControllerProvider.tsx +81 -47
  40. package/src/api/generateCollectionApi.ts +119 -0
  41. package/src/api/index.ts +1 -0
  42. package/src/index.ts +28 -1
  43. package/src/types/collection_editor_controller.tsx +16 -3
  44. package/src/types/collection_inference.ts +15 -2
  45. package/src/types/config_controller.tsx +27 -2
  46. package/src/ui/AddKanbanColumnAction.tsx +203 -0
  47. package/src/ui/EditorCollectionActionStart.tsx +1 -2
  48. package/src/ui/HomePageEditorCollectionAction.tsx +41 -13
  49. package/src/ui/KanbanSetupAction.tsx +38 -0
  50. package/src/ui/MissingReferenceWidget.tsx +1 -1
  51. package/src/ui/NewCollectionButton.tsx +1 -1
  52. package/src/ui/PropertyAddColumnComponent.tsx +1 -1
  53. package/src/ui/collection_editor/AICollectionGeneratorPopover.tsx +225 -0
  54. package/src/ui/collection_editor/AIModifiedPathsContext.tsx +88 -0
  55. package/src/ui/collection_editor/CollectionDetailsForm.tsx +209 -257
  56. package/src/ui/collection_editor/CollectionEditorDialog.tsx +226 -167
  57. package/src/ui/collection_editor/CollectionEditorWelcomeView.tsx +130 -67
  58. package/src/ui/collection_editor/CollectionJsonImportDialog.tsx +171 -0
  59. package/src/ui/collection_editor/CollectionPropertiesEditorForm.tsx +190 -91
  60. package/src/ui/collection_editor/DisplaySettingsForm.tsx +333 -0
  61. package/src/ui/collection_editor/EntityActionsEditTab.tsx +106 -96
  62. package/src/ui/collection_editor/EntityActionsSelectDialog.tsx +6 -7
  63. package/src/ui/collection_editor/EntityCustomViewsSelectDialog.tsx +1 -3
  64. package/src/ui/collection_editor/EnumForm.tsx +147 -100
  65. package/src/ui/collection_editor/ExtendSettingsForm.tsx +93 -0
  66. package/src/ui/collection_editor/GeneralSettingsForm.tsx +335 -0
  67. package/src/ui/collection_editor/GetCodeDialog.tsx +57 -36
  68. package/src/ui/collection_editor/KanbanConfigSection.tsx +207 -0
  69. package/src/ui/collection_editor/LayoutModeSwitch.tsx +22 -41
  70. package/src/ui/collection_editor/PropertyEditView.tsx +205 -141
  71. package/src/ui/collection_editor/PropertyFieldPreview.tsx +1 -1
  72. package/src/ui/collection_editor/PropertyTree.tsx +130 -58
  73. package/src/ui/collection_editor/SubcollectionsEditTab.tsx +171 -162
  74. package/src/ui/collection_editor/UnsavedChangesDialog.tsx +0 -2
  75. package/src/ui/collection_editor/ViewModeSwitch.tsx +41 -0
  76. package/src/ui/collection_editor/properties/BlockPropertyField.tsx +0 -2
  77. package/src/ui/collection_editor/properties/BooleanPropertyField.tsx +1 -0
  78. package/src/ui/collection_editor/properties/DateTimePropertyField.tsx +117 -35
  79. package/src/ui/collection_editor/properties/EnumPropertyField.tsx +28 -21
  80. package/src/ui/collection_editor/properties/MapPropertyField.tsx +0 -2
  81. package/src/ui/collection_editor/properties/MarkdownPropertyField.tsx +115 -39
  82. package/src/ui/collection_editor/properties/StoragePropertyField.tsx +1 -1
  83. package/src/ui/collection_editor/properties/conditions/ConditionsEditor.tsx +861 -0
  84. package/src/ui/collection_editor/properties/conditions/ConditionsPanel.tsx +28 -0
  85. package/src/ui/collection_editor/properties/conditions/EnumConditionsEditor.tsx +599 -0
  86. package/src/ui/collection_editor/properties/conditions/index.ts +6 -0
  87. package/src/ui/collection_editor/properties/conditions/property_paths.ts +92 -0
  88. package/src/ui/collection_editor/properties/validation/ValidationPanel.tsx +1 -1
  89. package/src/useCollectionEditorPlugin.tsx +32 -17
  90. package/src/utils/validateCollectionJson.ts +380 -0
@@ -3,15 +3,19 @@ import equal from "react-fast-compare"
3
3
 
4
4
  import { ArrayContainer, ArrayEntryParams, EnumValueConfig, EnumValues, FieldCaption, } from "@firecms/core";
5
5
  import {
6
- AutorenewIcon,
6
+ FindInPageIcon,
7
7
  Badge,
8
8
  Button,
9
+ ChipColorKey,
9
10
  CircularProgress,
11
+ ColorPicker,
10
12
  DebouncedTextField,
11
13
  Dialog,
12
14
  DialogActions,
13
15
  DialogContent,
14
16
  DialogTitle,
17
+ getColorSchemeForKey,
18
+ getColorSchemeForSeed,
15
19
  IconButton,
16
20
  ListIcon,
17
21
  Paper,
@@ -32,14 +36,14 @@ type EnumFormProps = {
32
36
  };
33
37
 
34
38
  export function EnumForm({
35
- enumValues,
36
- onValuesChanged,
37
- onError,
38
- updateIds,
39
- disabled,
40
- allowDataInference,
41
- getData
42
- }: EnumFormProps) {
39
+ enumValues,
40
+ onValuesChanged,
41
+ onError,
42
+ updateIds,
43
+ disabled,
44
+ allowDataInference,
45
+ getData
46
+ }: EnumFormProps) {
43
47
 
44
48
  const formex = useCreateFormex<{
45
49
  enumValues: EnumValueConfig[]
@@ -68,7 +72,10 @@ export function EnumForm({
68
72
  }
69
73
  });
70
74
 
71
- const { values, errors } = formex;
75
+ const {
76
+ values,
77
+ errors
78
+ } = formex;
72
79
 
73
80
  useEffect(() => {
74
81
  if (onValuesChanged) {
@@ -78,12 +85,12 @@ export function EnumForm({
78
85
 
79
86
  return <Formex value={formex}>
80
87
  <EnumFormFields enumValuesPath={"enumValues"}
81
- values={values}
82
- errors={errors}
83
- shouldUpdateId={updateIds}
84
- disabled={disabled}
85
- allowDataInference={allowDataInference}
86
- getData={getData}/>
88
+ values={values}
89
+ errors={errors}
90
+ shouldUpdateId={updateIds}
91
+ disabled={disabled}
92
+ allowDataInference={allowDataInference}
93
+ getData={getData} />
87
94
  </Formex>
88
95
 
89
96
  }
@@ -102,14 +109,14 @@ type EnumFormFieldsProps = {
102
109
 
103
110
  // const EnumFormFields = React.memo(
104
111
  function EnumFormFields({
105
- values,
106
- errors,
107
- disabled,
108
- enumValuesPath,
109
- shouldUpdateId,
110
- allowDataInference,
111
- getData,
112
- }: EnumFormFieldsProps) {
112
+ values,
113
+ errors,
114
+ disabled,
115
+ enumValuesPath,
116
+ shouldUpdateId,
117
+ allowDataInference,
118
+ getData,
119
+ }: EnumFormFieldsProps) {
113
120
 
114
121
  const {
115
122
  setFieldValue
@@ -123,25 +130,27 @@ function EnumFormFields({
123
130
  const inferredValues = inferredValuesRef.current;
124
131
 
125
132
  const buildEntry = ({
126
- index,
127
- internalId
128
- }:ArrayEntryParams) => {
133
+ index,
134
+ internalId
135
+ }: ArrayEntryParams) => {
129
136
  const justAdded = lastInternalIdAdded === internalId;
130
137
  const entryError = errors?.enumValues && errors?.enumValues[index];
131
138
  return <EnumEntry index={index}
132
- disabled={disabled}
133
- enumValuesPath={enumValuesPath}
134
- autoFocus={justAdded}
135
- entryError={entryError}
136
- shouldUpdateId={shouldUpdateId || justAdded}
137
- onDialogOpen={() => setEditDialogIndex(index)}
138
- inferredEntry={inferredValues.has(values.enumValues[index]?.id as string)}
139
- key={`${internalId}`}/>;
139
+ disabled={disabled}
140
+ enumValuesPath={enumValuesPath}
141
+ autoFocus={justAdded}
142
+ entryError={entryError}
143
+ shouldUpdateId={shouldUpdateId || justAdded}
144
+ onDialogOpen={() => setEditDialogIndex(index)}
145
+ inferredEntry={inferredValues.has(values.enumValues[index]?.id as string)}
146
+ key={`${internalId}`} />;
140
147
  };
141
148
 
142
149
  const inferValues = async () => {
143
- if (!getData)
150
+ if (!getData) {
151
+ console.warn("INTERNAL: No getData function provided for data inference");
144
152
  return;
153
+ }
145
154
  setInferring(true);
146
155
  getData?.().then((data) => {
147
156
  if (!data)
@@ -172,18 +181,18 @@ function EnumFormFields({
172
181
  return (
173
182
  <div className={"col-span-12"}>
174
183
  <div className="ml-3.5 flex flex-row items-center">
175
- <ListIcon/>
184
+ <ListIcon />
176
185
  <Typography variant={"subtitle2"}
177
- className="ml-2 grow">
186
+ className="ml-2 grow">
178
187
  Values
179
188
  </Typography>
180
189
  {allowDataInference &&
181
- <Button loading={inferring}
182
- disabled={disabled || inferring}
183
- variant={"text"}
184
- size={"small"}
185
- onClick={inferValues}>
186
- {inferring ? <CircularProgress size={"smallest"}/> : <AutorenewIcon/>}
190
+ <Button
191
+ disabled={disabled || inferring}
192
+ variant={"text"}
193
+ size={"small"}
194
+ onClick={inferValues}>
195
+ {inferring ? <CircularProgress size={"smallest"} /> : <FindInPageIcon />}
187
196
  Infer values from data
188
197
  </Button>}
189
198
  </div>
@@ -191,20 +200,23 @@ function EnumFormFields({
191
200
  <Paper className="p-4 m-1">
192
201
 
193
202
  <ArrayContainer droppableId={enumValuesPath}
194
- addLabel={"Add enum value"}
195
- value={values.enumValues}
196
- disabled={disabled}
197
- size={"small"}
198
- buildEntry={buildEntry}
199
- onInternalIdAdded={setLastInternalIdAdded}
200
- canAddElements={true}
201
- onValueChange={(value) => setFieldValue(enumValuesPath, value)}
202
- newDefaultEntry={{ id: "", label: "" }}/>
203
+ addLabel={"Add enum value"}
204
+ value={values.enumValues}
205
+ disabled={disabled}
206
+ size={"small"}
207
+ buildEntry={buildEntry}
208
+ onInternalIdAdded={setLastInternalIdAdded}
209
+ canAddElements={true}
210
+ onValueChange={(value) => setFieldValue(enumValuesPath, value)}
211
+ newDefaultEntry={{
212
+ id: "",
213
+ label: ""
214
+ }} />
203
215
 
204
216
  <EnumEntryDialog index={editDialogIndex}
205
- open={editDialogIndex !== undefined}
206
- enumValuesPath={enumValuesPath}
207
- onClose={() => setEditDialogIndex(undefined)}/>
217
+ open={editDialogIndex !== undefined}
218
+ enumValuesPath={enumValuesPath}
219
+ onClose={() => setEditDialogIndex(undefined)} />
208
220
  </Paper>
209
221
  </div>
210
222
  );
@@ -223,15 +235,15 @@ type EnumEntryProps = {
223
235
 
224
236
  const EnumEntry = React.memo(
225
237
  function EnumEntryInternal({
226
- index,
227
- shouldUpdateId: updateId,
228
- enumValuesPath,
229
- autoFocus,
230
- onDialogOpen,
231
- disabled,
232
- inferredEntry,
233
- entryError
234
- }: EnumEntryProps) {
238
+ index,
239
+ shouldUpdateId: updateId,
240
+ enumValuesPath,
241
+ autoFocus,
242
+ onDialogOpen,
243
+ disabled,
244
+ inferredEntry,
245
+ entryError
246
+ }: EnumEntryProps) {
235
247
 
236
248
  const {
237
249
  values,
@@ -256,40 +268,59 @@ const EnumEntry = React.memo(
256
268
  currentLabelRef.current = labelValue;
257
269
  }, [labelValue]);
258
270
 
271
+ const colorValue = getIn(values, `${enumValuesPath}[${index}].color`) as ChipColorKey | undefined;
272
+ const colorScheme = colorValue
273
+ ? getColorSchemeForKey(colorValue)
274
+ : idValue
275
+ ? getColorSchemeForSeed(String(idValue))
276
+ : undefined;
277
+
259
278
  return (
260
279
  <>
261
280
  <div className={"flex w-full align-center justify-center"}>
262
281
  <Field name={`${enumValuesPath}[${index}].label`}
263
- as={DebouncedTextField}
264
- className={"flex-grow"}
265
- required
266
- disabled={disabled}
267
- size="small"
268
- autoFocus={autoFocus}
269
- autoComplete="off"
270
- endAdornment={inferredEntry && <AutorenewIcon size={"small"}/>}
271
- error={Boolean(entryError?.label)}/>
272
-
273
- {!disabled &&
282
+ as={DebouncedTextField}
283
+ className={"flex-grow"}
284
+ required
285
+ disabled={disabled}
286
+ size="small"
287
+ autoFocus={autoFocus}
288
+ autoComplete="off"
289
+ endAdornment={inferredEntry && <FindInPageIcon size={"small"} />}
290
+ error={Boolean(entryError?.label)} />
291
+
292
+ {!disabled && <>
293
+ {/* Color indicator - clickable to open settings */}
294
+ <button
295
+ type="button"
296
+ onClick={() => onDialogOpen()}
297
+ className="w-5 h-5 rounded-full flex-shrink-0 self-center border border-surface-accent-200 dark:border-surface-accent-700 hover:scale-110 transition-transform cursor-pointer ml-3"
298
+ style={{
299
+ backgroundColor: colorScheme?.color ?? "#ccc"
300
+ }}
301
+ title={colorValue ? `Color: ${colorValue}` : "Auto color - Click to change"}
302
+ aria-label="Edit enum color"
303
+ />
274
304
  <Badge color={"error"} invisible={!entryError?.id}>
275
305
  <IconButton
276
306
  size="small"
277
307
  aria-label="edit"
278
308
  className={"m-1"}
279
309
  onClick={() => onDialogOpen()}>
280
- <SettingsIcon size={"small"}/>
310
+ <SettingsIcon size={"small"} />
281
311
  </IconButton>
282
- </Badge>}
312
+ </Badge>
313
+ </>}
283
314
 
284
315
  </div>
285
316
 
286
317
  {entryError?.label && <Typography variant={"caption"}
287
- className={"ml-3.5 text-red-500 dark:text-red-500"}>
318
+ className={"ml-3.5 text-red-500 dark:text-red-500"}>
288
319
  {entryError?.label}
289
320
  </Typography>}
290
321
 
291
322
  {entryError?.id && <Typography variant={"caption"}
292
- className={"ml-3.5 text-red-500 dark:text-red-500"}>
323
+ className={"ml-3.5 text-red-500 dark:text-red-500"}>
293
324
  {entryError?.id}
294
325
  </Typography>}
295
326
 
@@ -306,11 +337,11 @@ const EnumEntry = React.memo(
306
337
  );
307
338
 
308
339
  function EnumEntryDialog({
309
- index,
310
- open,
311
- onClose,
312
- enumValuesPath
313
- }: {
340
+ index,
341
+ open,
342
+ onClose,
343
+ enumValuesPath
344
+ }: {
314
345
  index?: number;
315
346
  open: boolean;
316
347
  enumValuesPath: string;
@@ -319,9 +350,13 @@ function EnumEntryDialog({
319
350
 
320
351
  const {
321
352
  errors,
353
+ values,
354
+ setFieldValue
322
355
  } = useFormex<EnumValues>();
323
356
 
324
357
  const idError = index !== undefined ? getIn(errors, `${enumValuesPath}[${index}].id`) : undefined;
358
+ const colorValue = index !== undefined ? getIn(values, `${enumValuesPath}[${index}].color`) as ChipColorKey | undefined : undefined;
359
+
325
360
  return <Dialog
326
361
  maxWidth="md"
327
362
  aria-labelledby="enum-edit-dialog"
@@ -331,27 +366,39 @@ function EnumEntryDialog({
331
366
  <DialogTitle hidden>Enum form dialog</DialogTitle>
332
367
  <DialogContent>
333
368
  {index !== undefined &&
334
- <div>
335
- <Field name={`${enumValuesPath}[${index}].id`}
336
- as={DebouncedTextField}
337
- required
338
- label={"ID"}
339
- size="small"
340
- autoComplete="off"
341
- error={Boolean(idError)}/>
342
-
343
- <FieldCaption error={Boolean(idError)}>
344
- {idError ?? "Value saved in the data source"}
345
- </FieldCaption>
369
+ <div className="flex flex-col gap-4">
370
+ <div>
371
+ <Field name={`${enumValuesPath}[${index}].id`}
372
+ as={DebouncedTextField}
373
+ required
374
+ label={"ID"}
375
+ size="small"
376
+ autoComplete="off"
377
+ error={Boolean(idError)} />
378
+
379
+ <FieldCaption error={Boolean(idError)}>
380
+ {idError ?? "Value saved in the data source"}
381
+ </FieldCaption>
382
+ </div>
383
+
384
+ <div>
385
+ <Typography variant="body2" className="font-medium mb-2">
386
+ Chip color
387
+ </Typography>
388
+ <ColorPicker
389
+ value={colorValue}
390
+ onChange={(color) => setFieldValue(`${enumValuesPath}[${index}].color`, color)}
391
+ size="small"
392
+ allowClear={true}
393
+ />
394
+ </div>
346
395
  </div>}
347
396
  </DialogContent>
348
397
 
349
398
  <DialogActions>
350
399
  <Button
351
400
  autoFocus
352
- variant="outlined"
353
- onClick={onClose}
354
- color="primary">
401
+ onClick={onClose}>
355
402
  Ok
356
403
  </Button>
357
404
  </DialogActions>
@@ -0,0 +1,93 @@
1
+ import React from "react";
2
+ import {
3
+ EntityCollection,
4
+ User
5
+ } from "@firecms/core";
6
+ import {
7
+ Button,
8
+ cls,
9
+ Container,
10
+ defaultBorderMixin,
11
+ Typography
12
+ } from "@firecms/ui";
13
+
14
+ import { useFormex } from "@firecms/formex";
15
+ import { CollectionsConfigController } from "../../types/config_controller";
16
+ import { CollectionInference } from "../../types/collection_inference";
17
+ import { SubcollectionsEditTab } from "./SubcollectionsEditTab";
18
+ import { EntityActionsEditTab } from "./EntityActionsEditTab";
19
+ import { PersistedCollection } from "../../types/persisted_collection";
20
+
21
+ export function ExtendSettingsForm({
22
+ collection,
23
+ parentCollection,
24
+ configController,
25
+ collectionInference,
26
+ getUser,
27
+ parentCollectionIds,
28
+ isMergedCollection,
29
+ onResetToCode
30
+ }: {
31
+ collection: PersistedCollection;
32
+ parentCollection?: EntityCollection;
33
+ configController: CollectionsConfigController;
34
+ collectionInference?: CollectionInference;
35
+ getUser?: (uid: string) => User | null;
36
+ parentCollectionIds?: string[];
37
+ isMergedCollection?: boolean;
38
+ onResetToCode?: () => void;
39
+ }) {
40
+
41
+ const {
42
+ values,
43
+ setFieldValue,
44
+ submitCount
45
+ } = useFormex<EntityCollection>();
46
+
47
+ return (
48
+ <div className={"overflow-auto my-auto"}>
49
+ <Container maxWidth={"4xl"} className={"flex flex-col gap-8 p-8 m-auto"}>
50
+
51
+ <div>
52
+ <Typography variant={"h5"} className={"flex-grow"}>
53
+ Extend
54
+ </Typography>
55
+ <Typography variant={"body2"} color={"secondary"}>
56
+ Add subcollections, custom views, and entity actions to this collection.
57
+ </Typography>
58
+ </div>
59
+
60
+ {/* Subcollections Section */}
61
+ <SubcollectionsEditTab
62
+ collection={collection}
63
+ parentCollection={parentCollection}
64
+ configController={configController}
65
+ collectionInference={collectionInference}
66
+ getUser={getUser}
67
+ parentCollectionIds={parentCollectionIds}
68
+ embedded={true}
69
+ />
70
+
71
+ {/* Entity Actions Section */}
72
+ <EntityActionsEditTab collection={collection} embedded={true} />
73
+
74
+ {/* Reset to code (for merged collections) */}
75
+ {isMergedCollection && onResetToCode && (
76
+ <div className={cls("flex flex-col gap-4 mt-8 border-t pt-8", defaultBorderMixin)}>
77
+ <Typography variant={"body2"} color={"secondary"}>
78
+ This collection is defined in code.
79
+ The changes done in this editor will override the properties defined in code.
80
+ You can delete the overridden values to revert to the state defined in code.
81
+ </Typography>
82
+ <Button color={"neutral"} onClick={onResetToCode}>
83
+ Reset to code
84
+ </Button>
85
+ </div>
86
+ )}
87
+
88
+ <div style={{ height: "52px" }} />
89
+
90
+ </Container>
91
+ </div>
92
+ );
93
+ }