@firecms/collection_editor 3.0.0-canary.24 → 3.0.0-canary.241
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/LICENSE +114 -21
- package/README.md +165 -1
- package/dist/ConfigControllerProvider.d.ts +1 -2
- package/dist/index.d.ts +1 -0
- package/dist/index.es.js +10109 -4774
- package/dist/index.es.js.map +1 -1
- package/dist/index.umd.js +10795 -3
- package/dist/index.umd.js.map +1 -1
- package/dist/types/collection_editor_controller.d.ts +3 -2
- package/dist/types/collection_inference.d.ts +1 -1
- package/dist/types/config_permissions.d.ts +2 -2
- package/dist/types/persisted_collection.d.ts +1 -1
- package/dist/ui/CollectionViewHeaderAction.d.ts +3 -2
- package/dist/ui/EditorCollectionActionStart.d.ts +2 -0
- package/dist/ui/PropertyAddColumnComponent.d.ts +3 -1
- package/dist/ui/collection_editor/CollectionDetailsForm.d.ts +3 -1
- package/dist/ui/collection_editor/CollectionEditorDialog.d.ts +3 -2
- package/dist/ui/collection_editor/CollectionEditorWelcomeView.d.ts +2 -2
- package/dist/ui/collection_editor/CollectionPropertiesEditorForm.d.ts +1 -1
- package/dist/ui/collection_editor/LayoutModeSwitch.d.ts +5 -0
- package/dist/ui/collection_editor/PropertyEditView.d.ts +8 -0
- package/dist/ui/collection_editor/PropertyTree.d.ts +9 -9
- package/dist/ui/collection_editor/SubcollectionsEditTab.d.ts +1 -1
- package/dist/ui/collection_editor/import/CollectionEditorImportMapping.d.ts +7 -0
- package/dist/ui/collection_editor/properties/MarkdownPropertyField.d.ts +4 -0
- package/dist/ui/collection_editor/properties/StringPropertyField.d.ts +1 -1
- package/dist/useCollectionEditorPlugin.d.ts +8 -11
- package/dist/utils/collections.d.ts +6 -0
- package/package.json +24 -35
- package/src/ConfigControllerProvider.tsx +64 -66
- package/src/index.ts +1 -0
- package/src/types/collection_editor_controller.tsx +6 -5
- package/src/types/collection_inference.ts +1 -1
- package/src/types/config_permissions.ts +1 -1
- package/src/types/persisted_collection.ts +2 -3
- package/src/ui/CollectionViewHeaderAction.tsx +10 -5
- package/src/ui/EditorCollectionAction.tsx +10 -63
- package/src/ui/EditorCollectionActionStart.tsx +88 -0
- package/src/ui/HomePageEditorCollectionAction.tsx +19 -13
- package/src/ui/NewCollectionButton.tsx +1 -1
- package/src/ui/NewCollectionCard.tsx +3 -3
- package/src/ui/PropertyAddColumnComponent.tsx +11 -6
- package/src/ui/collection_editor/CollectionDetailsForm.tsx +112 -18
- package/src/ui/collection_editor/CollectionEditorDialog.tsx +101 -34
- package/src/ui/collection_editor/CollectionEditorWelcomeView.tsx +13 -28
- package/src/ui/collection_editor/CollectionPropertiesEditorForm.tsx +41 -39
- package/src/ui/collection_editor/EntityCustomViewsSelectDialog.tsx +6 -5
- package/src/ui/collection_editor/EnumForm.tsx +11 -7
- package/src/ui/collection_editor/GetCodeDialog.tsx +56 -26
- package/src/ui/collection_editor/LayoutModeSwitch.tsx +54 -0
- package/src/ui/collection_editor/PropertyEditView.tsx +257 -79
- package/src/ui/collection_editor/PropertyFieldPreview.tsx +7 -10
- package/src/ui/collection_editor/PropertyTree.tsx +9 -7
- package/src/ui/collection_editor/SubcollectionsEditTab.tsx +26 -19
- package/src/ui/collection_editor/UnsavedChangesDialog.tsx +3 -5
- package/src/ui/collection_editor/import/CollectionEditorImportDataPreview.tsx +33 -9
- package/src/ui/collection_editor/import/CollectionEditorImportMapping.tsx +42 -9
- package/src/ui/collection_editor/properties/BlockPropertyField.tsx +32 -20
- package/src/ui/collection_editor/properties/DateTimePropertyField.tsx +54 -47
- package/src/ui/collection_editor/properties/EnumPropertyField.tsx +3 -1
- package/src/ui/collection_editor/properties/MapPropertyField.tsx +7 -6
- package/src/ui/collection_editor/properties/MarkdownPropertyField.tsx +139 -0
- package/src/ui/collection_editor/properties/ReferencePropertyField.tsx +2 -0
- package/src/ui/collection_editor/properties/RepeatPropertyField.tsx +0 -1
- package/src/ui/collection_editor/properties/StoragePropertyField.tsx +34 -19
- package/src/ui/collection_editor/properties/StringPropertyField.tsx +1 -10
- package/src/ui/collection_editor/properties/UrlPropertyField.tsx +1 -0
- package/src/ui/collection_editor/properties/validation/ValidationPanel.tsx +2 -2
- package/src/ui/collection_editor/templates/pages_template.ts +1 -6
- package/src/ui/collection_editor/utils/strings.ts +13 -6
- package/src/useCollectionEditorPlugin.tsx +32 -32
- package/src/utils/collections.ts +37 -0
- package/dist/ui/RootCollectionSuggestions.d.ts +0 -3
- package/dist/ui/collection_editor/PropertySelectItem.d.ts +0 -8
- package/src/ui/RootCollectionSuggestions.tsx +0 -63
- package/src/ui/collection_editor/PropertySelectItem.tsx +0 -32
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React, { useEffect, useState } from "react";
|
|
2
2
|
import { EntityCollection, unslugify, } from "@firecms/core";
|
|
3
|
-
import { Button, Card, Chip, CircularProgress,
|
|
3
|
+
import { Button, Card, Chip, CircularProgress, cls, Container, Icon, Tooltip, Typography, } from "@firecms/ui";
|
|
4
4
|
|
|
5
5
|
import { productsCollectionTemplate } from "./templates/products_template";
|
|
6
6
|
import { blogCollectionTemplate } from "./templates/blog_template";
|
|
@@ -19,7 +19,7 @@ export function CollectionEditorWelcomeView({
|
|
|
19
19
|
path: string;
|
|
20
20
|
pathSuggestions?: (path: string) => Promise<string[]>;
|
|
21
21
|
parentCollection?: EntityCollection;
|
|
22
|
-
onContinue: (importData?: object[]) => void;
|
|
22
|
+
onContinue: (importData?: object[], propertiesOrder?: string[]) => void;
|
|
23
23
|
existingCollectionPaths?: string[];
|
|
24
24
|
}) {
|
|
25
25
|
|
|
@@ -37,18 +37,6 @@ export function CollectionEditorWelcomeView({
|
|
|
37
37
|
}
|
|
38
38
|
}, [existingCollectionPaths, path, pathSuggestions]);
|
|
39
39
|
|
|
40
|
-
// const {
|
|
41
|
-
// values,
|
|
42
|
-
// setFieldValue,
|
|
43
|
-
// setValues,
|
|
44
|
-
// handleChange,
|
|
45
|
-
// touched,
|
|
46
|
-
// errors,
|
|
47
|
-
// setFieldTouched,
|
|
48
|
-
// isSubmitting,
|
|
49
|
-
// submitCount
|
|
50
|
-
// } = useFormex<EntityCollection>();
|
|
51
|
-
|
|
52
40
|
const {
|
|
53
41
|
values,
|
|
54
42
|
setFieldValue,
|
|
@@ -56,6 +44,11 @@ export function CollectionEditorWelcomeView({
|
|
|
56
44
|
submitCount
|
|
57
45
|
} = useFormex<EntityCollection>();
|
|
58
46
|
|
|
47
|
+
const noSuggestions = !loadingPathSuggestions && (filteredPathSuggestions ?? [])?.length === 0;
|
|
48
|
+
if (!noSuggestions) {
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
|
|
59
52
|
return (
|
|
60
53
|
<div className={"overflow-auto my-auto"}>
|
|
61
54
|
<Container maxWidth={"4xl"} className={"flex flex-col gap-4 p-8 m-auto"}>
|
|
@@ -97,12 +90,6 @@ export function CollectionEditorWelcomeView({
|
|
|
97
90
|
</Chip>
|
|
98
91
|
))}
|
|
99
92
|
|
|
100
|
-
{!loadingPathSuggestions && (filteredPathSuggestions ?? [])?.length === 0 &&
|
|
101
|
-
<Typography variant={"caption"}>
|
|
102
|
-
No suggestions
|
|
103
|
-
</Typography>
|
|
104
|
-
}
|
|
105
|
-
|
|
106
93
|
</div>
|
|
107
94
|
|
|
108
95
|
</div>
|
|
@@ -154,7 +141,7 @@ export function CollectionEditorWelcomeView({
|
|
|
154
141
|
● Create a collection from a file (csv, json, xls, xslx...)
|
|
155
142
|
</Typography>
|
|
156
143
|
|
|
157
|
-
<ImportFileUpload onDataAdded={(data) => onContinue(data)}/>
|
|
144
|
+
<ImportFileUpload onDataAdded={(data, propertiesOrder) => onContinue(data, propertiesOrder)}/>
|
|
158
145
|
|
|
159
146
|
</div>}
|
|
160
147
|
|
|
@@ -185,14 +172,15 @@ export function TemplateButton({
|
|
|
185
172
|
}) {
|
|
186
173
|
|
|
187
174
|
return (
|
|
188
|
-
<Tooltip title={subtitle}
|
|
175
|
+
<Tooltip title={subtitle}
|
|
176
|
+
asChild={true}>
|
|
189
177
|
<Card
|
|
190
178
|
onClick={onClick}
|
|
191
|
-
className={
|
|
179
|
+
className={cls(
|
|
192
180
|
"my-2 rounded-md border mx-0 p-6 px-4 focus:outline-none transition ease-in-out duration-150 flex flex-row gap-4 items-center",
|
|
193
|
-
"text-
|
|
181
|
+
"text-surface-700 dark:text-surface-accent-300",
|
|
194
182
|
"hover:border-primary-dark hover:text-primary-dark dark:hover:text-primary focus:ring-primary hover:ring-1 hover:ring-primary",
|
|
195
|
-
"border-
|
|
183
|
+
"border-surface-400 dark:border-surface-600 "
|
|
196
184
|
)}
|
|
197
185
|
>
|
|
198
186
|
{icon}
|
|
@@ -201,9 +189,6 @@ export function TemplateButton({
|
|
|
201
189
|
<Typography variant={"subtitle1"}>
|
|
202
190
|
{title}
|
|
203
191
|
</Typography>
|
|
204
|
-
{/*<Typography>*/}
|
|
205
|
-
{/* {subtitle}*/}
|
|
206
|
-
{/*</Typography>*/}
|
|
207
192
|
|
|
208
193
|
</div>
|
|
209
194
|
</Card>
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, {
|
|
1
|
+
import React, { useEffect, useMemo, useState } from "react";
|
|
2
2
|
|
|
3
3
|
import { Field, getIn, useFormex } from "@firecms/formex";
|
|
4
4
|
import {
|
|
@@ -16,10 +16,10 @@ import {
|
|
|
16
16
|
} from "@firecms/core";
|
|
17
17
|
import {
|
|
18
18
|
AddIcon,
|
|
19
|
-
|
|
19
|
+
AutorenewIcon,
|
|
20
20
|
Button,
|
|
21
21
|
CircularProgress,
|
|
22
|
-
|
|
22
|
+
cls,
|
|
23
23
|
CodeIcon,
|
|
24
24
|
DebouncedTextField,
|
|
25
25
|
defaultBorderMixin,
|
|
@@ -43,7 +43,7 @@ type CollectionEditorFormProps = {
|
|
|
43
43
|
setDirty?: (dirty: boolean) => void;
|
|
44
44
|
reservedGroups?: string[];
|
|
45
45
|
extraIcon: React.ReactNode;
|
|
46
|
-
getUser
|
|
46
|
+
getUser?: (uid: string) => User | null;
|
|
47
47
|
getData?: () => Promise<object[]>;
|
|
48
48
|
doCollectionInference: (collection: PersistedCollection) => Promise<Partial<EntityCollection> | null> | undefined;
|
|
49
49
|
propertyConfigs: Record<string, PropertyConfig>;
|
|
@@ -158,20 +158,20 @@ export function CollectionPropertiesEditorForm({
|
|
|
158
158
|
}
|
|
159
159
|
: undefined;
|
|
160
160
|
|
|
161
|
-
const getCurrentPropertiesOrder =
|
|
162
|
-
if (!namespace) return currentPropertiesOrderRef.current[""];
|
|
161
|
+
const getCurrentPropertiesOrder = (namespace?: string) => {
|
|
162
|
+
if (!namespace) return currentPropertiesOrderRef.current[""] ?? getIn(values, namespaceToPropertiesOrderPath());
|
|
163
163
|
return currentPropertiesOrderRef.current[namespace] ?? getIn(values, namespaceToPropertiesOrderPath(namespace));
|
|
164
|
-
}
|
|
164
|
+
};
|
|
165
165
|
|
|
166
|
-
const updatePropertiesOrder =
|
|
166
|
+
const updatePropertiesOrder = (newPropertiesOrder: string[], namespace?: string) => {
|
|
167
167
|
const propertiesOrderPath = namespaceToPropertiesOrderPath(namespace);
|
|
168
168
|
|
|
169
169
|
setFieldValue(propertiesOrderPath, newPropertiesOrder, false);
|
|
170
170
|
currentPropertiesOrderRef.current[namespace ?? ""] = newPropertiesOrder;
|
|
171
171
|
|
|
172
|
-
}
|
|
172
|
+
};
|
|
173
173
|
|
|
174
|
-
const deleteProperty =
|
|
174
|
+
const deleteProperty = (propertyKey?: string, namespace?: string) => {
|
|
175
175
|
const fullId = propertyKey ? getFullId(propertyKey, namespace) : undefined;
|
|
176
176
|
if (!fullId)
|
|
177
177
|
throw Error("collection editor miss config");
|
|
@@ -179,15 +179,17 @@ export function CollectionPropertiesEditorForm({
|
|
|
179
179
|
setFieldValue(idToPropertiesPath(fullId), undefined, false);
|
|
180
180
|
|
|
181
181
|
const currentPropertiesOrder = getCurrentPropertiesOrder(namespace);
|
|
182
|
-
|
|
183
|
-
|
|
182
|
+
if (currentPropertiesOrder) {
|
|
183
|
+
const newPropertiesOrder = currentPropertiesOrder.filter((p) => p !== propertyKey);
|
|
184
|
+
updatePropertiesOrder(newPropertiesOrder, namespace);
|
|
185
|
+
}
|
|
184
186
|
|
|
185
187
|
setNewPropertyDialogOpen(false);
|
|
186
188
|
|
|
187
189
|
setSelectedPropertyIndex(undefined);
|
|
188
190
|
setSelectedPropertyKey(undefined);
|
|
189
191
|
setSelectedPropertyNamespace(undefined);
|
|
190
|
-
}
|
|
192
|
+
};
|
|
191
193
|
|
|
192
194
|
const onPropertyMove = (propertiesOrder: string[], namespace?: string) => {
|
|
193
195
|
setFieldValue(namespaceToPropertiesOrderPath(namespace), propertiesOrder, false);
|
|
@@ -207,8 +209,8 @@ export function CollectionPropertiesEditorForm({
|
|
|
207
209
|
...(values.properties ?? {}),
|
|
208
210
|
[id]: property
|
|
209
211
|
}, false);
|
|
210
|
-
const newPropertiesOrder = [...(values.propertiesOrder ?? Object.keys(values.properties)), id];
|
|
211
212
|
|
|
213
|
+
const newPropertiesOrder = [...(values.propertiesOrder ?? Object.keys(values.properties)), id];
|
|
212
214
|
updatePropertiesOrder(newPropertiesOrder);
|
|
213
215
|
|
|
214
216
|
setNewPropertyDialogOpen(false);
|
|
@@ -231,12 +233,6 @@ export function CollectionPropertiesEditorForm({
|
|
|
231
233
|
|
|
232
234
|
// If the id has changed we need to a little cleanup
|
|
233
235
|
if (previousId && previousId !== id) {
|
|
234
|
-
console.debug("onPropertyChanged, id change", {
|
|
235
|
-
id,
|
|
236
|
-
property,
|
|
237
|
-
previousId,
|
|
238
|
-
namespace
|
|
239
|
-
})
|
|
240
236
|
|
|
241
237
|
const previousFullId = getFullId(previousId, namespace);
|
|
242
238
|
const previousPropertyPath = idToPropertiesPath(previousFullId);
|
|
@@ -273,7 +269,7 @@ export function CollectionPropertiesEditorForm({
|
|
|
273
269
|
|
|
274
270
|
};
|
|
275
271
|
|
|
276
|
-
const onPropertyErrorInternal =
|
|
272
|
+
const onPropertyErrorInternal = (id: string, namespace?: string, error?: Record<string, any>) => {
|
|
277
273
|
const propertyPath = id ? getFullId(id, namespace) : undefined;
|
|
278
274
|
console.debug("onPropertyErrorInternal", {
|
|
279
275
|
id,
|
|
@@ -286,7 +282,7 @@ export function CollectionPropertiesEditorForm({
|
|
|
286
282
|
onPropertyError(id, namespace, hasError ? error : undefined);
|
|
287
283
|
setFieldError(idToPropertiesPath(propertyPath), hasError ? "Property error" : undefined);
|
|
288
284
|
}
|
|
289
|
-
}
|
|
285
|
+
}
|
|
290
286
|
|
|
291
287
|
const closePropertyDialog = () => {
|
|
292
288
|
setSelectedPropertyIndex(undefined);
|
|
@@ -301,9 +297,9 @@ export function CollectionPropertiesEditorForm({
|
|
|
301
297
|
? values.propertiesOrder
|
|
302
298
|
: Object.keys(values.properties)) as string[];
|
|
303
299
|
|
|
304
|
-
const owner = useMemo(() => values.ownerId ? getUser(values.ownerId) : null, [getUser, values.ownerId]);
|
|
300
|
+
const owner = useMemo(() => values.ownerId && getUser ? getUser(values.ownerId) : null, [getUser, values.ownerId]);
|
|
305
301
|
|
|
306
|
-
const onPropertyClick =
|
|
302
|
+
const onPropertyClick = (propertyKey: string, namespace?: string) => {
|
|
307
303
|
console.debug("CollectionEditor: onPropertyClick", {
|
|
308
304
|
propertyKey,
|
|
309
305
|
namespace
|
|
@@ -311,11 +307,12 @@ export function CollectionPropertiesEditorForm({
|
|
|
311
307
|
setSelectedPropertyIndex(usedPropertiesOrder.indexOf(propertyKey));
|
|
312
308
|
setSelectedPropertyKey(propertyKey);
|
|
313
309
|
setSelectedPropertyNamespace(namespace);
|
|
314
|
-
}
|
|
310
|
+
};
|
|
315
311
|
|
|
316
312
|
const body = (
|
|
317
|
-
<div className={"grid grid-cols-12 gap-2 h-full bg-
|
|
318
|
-
<div className={
|
|
313
|
+
<div className={"grid grid-cols-12 gap-2 h-full bg-white dark:bg-surface-950"}>
|
|
314
|
+
<div className={cls(
|
|
315
|
+
"bg-surface-50 dark:bg-surface-900",
|
|
319
316
|
"p-4 md:p-8 pb-20 md:pb-20",
|
|
320
317
|
"col-span-12 lg:col-span-5 h-full overflow-auto",
|
|
321
318
|
!asDialog && "border-r " + defaultBorderMixin
|
|
@@ -349,7 +346,8 @@ export function CollectionPropertiesEditorForm({
|
|
|
349
346
|
</div>}
|
|
350
347
|
|
|
351
348
|
<div className="ml-1 mt-2 flex flex-row gap-2">
|
|
352
|
-
<Tooltip title={"Get the code for this collection"}
|
|
349
|
+
<Tooltip title={"Get the code for this collection"}
|
|
350
|
+
asChild={true}>
|
|
353
351
|
<IconButton
|
|
354
352
|
variant={"filled"}
|
|
355
353
|
disabled={inferringProperties}
|
|
@@ -357,15 +355,17 @@ export function CollectionPropertiesEditorForm({
|
|
|
357
355
|
<CodeIcon/>
|
|
358
356
|
</IconButton>
|
|
359
357
|
</Tooltip>
|
|
360
|
-
{inferPropertiesFromData && <Tooltip title={"Add new properties based on data"}
|
|
358
|
+
{inferPropertiesFromData && <Tooltip title={"Add new properties based on data"}
|
|
359
|
+
asChild={true}>
|
|
361
360
|
<IconButton
|
|
362
361
|
variant={"filled"}
|
|
363
362
|
disabled={inferringProperties}
|
|
364
363
|
onClick={inferPropertiesFromData}>
|
|
365
|
-
{inferringProperties ? <CircularProgress size={"small"}/> : <
|
|
364
|
+
{inferringProperties ? <CircularProgress size={"small"}/> : <AutorenewIcon/>}
|
|
366
365
|
</IconButton>
|
|
367
366
|
</Tooltip>}
|
|
368
|
-
<Tooltip title={"Add new property"}
|
|
367
|
+
<Tooltip title={"Add new property"}
|
|
368
|
+
asChild={true}>
|
|
369
369
|
<Button
|
|
370
370
|
variant={"outlined"}
|
|
371
371
|
onClick={() => setNewPropertyDialogOpen(true)}>
|
|
@@ -402,8 +402,8 @@ export function CollectionPropertiesEditorForm({
|
|
|
402
402
|
|
|
403
403
|
{!asDialog &&
|
|
404
404
|
<div className={"col-span-12 lg:col-span-7 p-4 md:py-8 md:px-4 h-full overflow-auto pb-20 md:pb-20"}>
|
|
405
|
-
<
|
|
406
|
-
className="sticky top-8
|
|
405
|
+
<div
|
|
406
|
+
className="sticky top-8 min-h-full w-full flex flex-col justify-center">
|
|
407
407
|
|
|
408
408
|
{selectedPropertyFullId &&
|
|
409
409
|
selectedProperty &&
|
|
@@ -447,7 +447,7 @@ export function CollectionPropertiesEditorForm({
|
|
|
447
447
|
<Typography variant={"label"} className="flex items-center justify-center">
|
|
448
448
|
{"This property is defined as a property builder in code"}
|
|
449
449
|
</Typography>}
|
|
450
|
-
</
|
|
450
|
+
</div>
|
|
451
451
|
</div>}
|
|
452
452
|
|
|
453
453
|
{asDialog && <PropertyFormDialog
|
|
@@ -469,6 +469,7 @@ export function CollectionPropertiesEditorForm({
|
|
|
469
469
|
getData={getData}
|
|
470
470
|
propertyConfigs={propertyConfigs}
|
|
471
471
|
collectionEditable={collectionEditable}
|
|
472
|
+
onCancel={closePropertyDialog}
|
|
472
473
|
onOkClicked={asDialog
|
|
473
474
|
? closePropertyDialog
|
|
474
475
|
: undefined
|
|
@@ -496,11 +497,12 @@ export function CollectionPropertiesEditorForm({
|
|
|
496
497
|
collectionEditable={collectionEditable}
|
|
497
498
|
existingPropertyKeys={values.propertiesOrder as string[]}/>
|
|
498
499
|
|
|
499
|
-
<
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
500
|
+
<ErrorBoundary>
|
|
501
|
+
<GetCodeDialog
|
|
502
|
+
collection={values}
|
|
503
|
+
open={codeDialogOpen}
|
|
504
|
+
onOpenChange={setCodeDialogOpen}/>
|
|
505
|
+
</ErrorBoundary>
|
|
504
506
|
</>
|
|
505
507
|
);
|
|
506
508
|
}
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import { useCustomizationController } from "@firecms/core";
|
|
2
|
-
import { Button, Dialog, DialogActions, DialogContent, Typography } from "@firecms/ui";
|
|
2
|
+
import { Button, Dialog, DialogActions, DialogContent, DialogTitle, Typography } from "@firecms/ui";
|
|
3
3
|
import React from "react";
|
|
4
4
|
|
|
5
|
-
export function EntityCustomViewsSelectDialog({
|
|
5
|
+
export function EntityCustomViewsSelectDialog({
|
|
6
|
+
open,
|
|
7
|
+
onClose
|
|
8
|
+
}: { open: boolean, onClose: (selectedViewKey?: string) => void }) {
|
|
6
9
|
const {
|
|
7
10
|
entityViews,
|
|
8
11
|
} = useCustomizationController();
|
|
@@ -10,10 +13,8 @@ export function EntityCustomViewsSelectDialog({ open, onClose }: { open: boolean
|
|
|
10
13
|
return <Dialog
|
|
11
14
|
maxWidth={"md"}
|
|
12
15
|
open={open}>
|
|
16
|
+
<DialogTitle>Select custom view</DialogTitle>
|
|
13
17
|
<DialogContent className={"flex flex-col gap-4"}>
|
|
14
|
-
<Typography variant={"h6"}>
|
|
15
|
-
Select view
|
|
16
|
-
</Typography>
|
|
17
18
|
{entityViews?.map((view) => {
|
|
18
19
|
return <Button
|
|
19
20
|
key={view.key}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import React, { useEffect } from "react";
|
|
2
2
|
import equal from "react-fast-compare"
|
|
3
3
|
|
|
4
|
-
import { ArrayContainer, EnumValueConfig, EnumValues, FieldCaption, } from "@firecms/core";
|
|
4
|
+
import { ArrayContainer, ArrayEntryParams, EnumValueConfig, EnumValues, FieldCaption, } from "@firecms/core";
|
|
5
5
|
import {
|
|
6
|
-
|
|
6
|
+
AutorenewIcon,
|
|
7
7
|
Badge,
|
|
8
8
|
Button,
|
|
9
9
|
CircularProgress,
|
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
Dialog,
|
|
12
12
|
DialogActions,
|
|
13
13
|
DialogContent,
|
|
14
|
+
DialogTitle,
|
|
14
15
|
IconButton,
|
|
15
16
|
ListIcon,
|
|
16
17
|
Paper,
|
|
@@ -121,7 +122,10 @@ function EnumFormFields({
|
|
|
121
122
|
const inferredValuesRef = React.useRef(new Set());
|
|
122
123
|
const inferredValues = inferredValuesRef.current;
|
|
123
124
|
|
|
124
|
-
const buildEntry = (
|
|
125
|
+
const buildEntry = ({
|
|
126
|
+
index,
|
|
127
|
+
internalId
|
|
128
|
+
}:ArrayEntryParams) => {
|
|
125
129
|
const justAdded = lastInternalIdAdded === internalId;
|
|
126
130
|
const entryError = errors?.enumValues && errors?.enumValues[index];
|
|
127
131
|
return <EnumEntry index={index}
|
|
@@ -179,7 +183,7 @@ function EnumFormFields({
|
|
|
179
183
|
variant={"text"}
|
|
180
184
|
size={"small"}
|
|
181
185
|
onClick={inferValues}>
|
|
182
|
-
{inferring ? <CircularProgress size={"
|
|
186
|
+
{inferring ? <CircularProgress size={"smallest"}/> : <AutorenewIcon/>}
|
|
183
187
|
Infer values from data
|
|
184
188
|
</Button>}
|
|
185
189
|
</div>
|
|
@@ -193,7 +197,7 @@ function EnumFormFields({
|
|
|
193
197
|
size={"small"}
|
|
194
198
|
buildEntry={buildEntry}
|
|
195
199
|
onInternalIdAdded={setLastInternalIdAdded}
|
|
196
|
-
|
|
200
|
+
canAddElements={true}
|
|
197
201
|
onValueChange={(value) => setFieldValue(enumValuesPath, value)}
|
|
198
202
|
newDefaultEntry={{ id: "", label: "" }}/>
|
|
199
203
|
|
|
@@ -263,7 +267,7 @@ const EnumEntry = React.memo(
|
|
|
263
267
|
size="small"
|
|
264
268
|
autoFocus={autoFocus}
|
|
265
269
|
autoComplete="off"
|
|
266
|
-
endAdornment={inferredEntry && <
|
|
270
|
+
endAdornment={inferredEntry && <AutorenewIcon size={"small"}/>}
|
|
267
271
|
error={Boolean(entryError?.label)}/>
|
|
268
272
|
|
|
269
273
|
{!disabled &&
|
|
@@ -324,7 +328,7 @@ function EnumEntryDialog({
|
|
|
324
328
|
open={open}
|
|
325
329
|
onOpenChange={(open) => !open ? onClose() : undefined}
|
|
326
330
|
>
|
|
327
|
-
|
|
331
|
+
<DialogTitle hidden>Enum form dialog</DialogTitle>
|
|
328
332
|
<DialogContent>
|
|
329
333
|
{index !== undefined &&
|
|
330
334
|
<div>
|
|
@@ -1,35 +1,47 @@
|
|
|
1
|
-
import { EntityCollection, useSnackbarController } from "@firecms/core";
|
|
2
|
-
import { Button, ContentCopyIcon, Dialog, DialogActions, DialogContent, Typography, } from "@firecms/ui";
|
|
1
|
+
import { EntityCollection, isEmptyObject, useSnackbarController } from "@firecms/core";
|
|
2
|
+
import { Button, ContentCopyIcon, Dialog, DialogActions, DialogContent, DialogTitle, Typography, } from "@firecms/ui";
|
|
3
3
|
import React from "react";
|
|
4
4
|
import JSON5 from "json5";
|
|
5
5
|
import { Highlight, themes } from "prism-react-renderer"
|
|
6
6
|
import { camelCase } from "./utils/strings";
|
|
7
|
+
import { clone } from "@firecms/formex";
|
|
7
8
|
|
|
8
|
-
export function GetCodeDialog({
|
|
9
|
+
export function GetCodeDialog({
|
|
10
|
+
collection,
|
|
11
|
+
onOpenChange,
|
|
12
|
+
open
|
|
13
|
+
}: { onOpenChange: (open: boolean) => void, collection: any, open: any }) {
|
|
9
14
|
|
|
10
15
|
const snackbarController = useSnackbarController();
|
|
11
16
|
|
|
12
|
-
const code =
|
|
17
|
+
const code = collection
|
|
18
|
+
? "import { EntityCollection } from \"@firecms/core\";\n\nconst " + (collection?.name ? camelCase(collection.name) : "my") + "Collection:EntityCollection = " + JSON5.stringify(collectionToCode({ ...collection }), null, "\t")
|
|
19
|
+
: "No collection selected";
|
|
13
20
|
return <Dialog open={open}
|
|
14
21
|
onOpenChange={onOpenChange}
|
|
15
22
|
maxWidth={"4xl"}>
|
|
23
|
+
<DialogTitle variant={"h6"}>Code for {collection.name}</DialogTitle>
|
|
16
24
|
<DialogContent>
|
|
17
|
-
|
|
18
|
-
Code for {collection.name}
|
|
19
|
-
</Typography>
|
|
25
|
+
|
|
20
26
|
<Typography variant={"body2"} className={"my-4 mb-8"}>
|
|
21
27
|
If you want to customise the collection in code, you can add this collection code to your CMS
|
|
22
28
|
app configuration.
|
|
23
29
|
More info in the <a
|
|
24
30
|
rel="noopener noreferrer"
|
|
25
|
-
href={"https://firecms.co/docs/
|
|
31
|
+
href={"https://firecms.co/docs/cloud/quickstart"}>docs</a>.
|
|
26
32
|
</Typography>
|
|
27
33
|
<Highlight
|
|
28
34
|
theme={themes.vsDark}
|
|
29
35
|
code={code}
|
|
30
36
|
language="typescript"
|
|
31
37
|
>
|
|
32
|
-
{({
|
|
38
|
+
{({
|
|
39
|
+
className,
|
|
40
|
+
style,
|
|
41
|
+
tokens,
|
|
42
|
+
getLineProps,
|
|
43
|
+
getTokenProps
|
|
44
|
+
}) => (
|
|
33
45
|
<pre style={style} className={"p-4 rounded text-sm"}>
|
|
34
46
|
{tokens.map((line, i) => (
|
|
35
47
|
<div key={i} {...getLineProps({ line })}>
|
|
@@ -66,24 +78,40 @@ export function GetCodeDialog({ collection, onOpenChange, open }: { onOpenChange
|
|
|
66
78
|
|
|
67
79
|
function collectionToCode(collection: EntityCollection): object {
|
|
68
80
|
|
|
69
|
-
const propertyCleanup = (
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
81
|
+
const propertyCleanup = (value: any): any => {
|
|
82
|
+
if (value === undefined || value === null) {
|
|
83
|
+
return value;
|
|
84
|
+
}
|
|
85
|
+
const valueCopy = clone(value);
|
|
86
|
+
if (typeof valueCopy === "function") {
|
|
87
|
+
return valueCopy;
|
|
88
|
+
}
|
|
89
|
+
if (Array.isArray(valueCopy)) {
|
|
90
|
+
return valueCopy.map((v: any) => propertyCleanup(v));
|
|
91
|
+
}
|
|
92
|
+
if (typeof valueCopy === "object") {
|
|
93
|
+
if (valueCopy === null)
|
|
94
|
+
return valueCopy;
|
|
95
|
+
Object.keys(valueCopy).forEach((key) => {
|
|
96
|
+
if (!isEmptyObject(valueCopy)) {
|
|
97
|
+
const childRes = propertyCleanup(valueCopy[key]);
|
|
98
|
+
if (childRes !== null && childRes !== undefined && childRes !== false && !isEmptyObject(childRes)) {
|
|
99
|
+
valueCopy[key] = childRes;
|
|
100
|
+
} else {
|
|
101
|
+
delete valueCopy[key];
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
delete valueCopy.fromBuilder;
|
|
106
|
+
delete valueCopy.resolved;
|
|
107
|
+
delete valueCopy.propertiesOrder;
|
|
108
|
+
delete valueCopy.propertyConfig;
|
|
109
|
+
delete valueCopy.resolvedProperties;
|
|
110
|
+
delete valueCopy.editable;
|
|
79
111
|
|
|
80
|
-
if (updatedProperty.type === "map") {
|
|
81
|
-
return {
|
|
82
|
-
...updatedProperty,
|
|
83
|
-
properties: updatedProperty.properties.map(propertyCleanup)
|
|
84
|
-
}
|
|
85
112
|
}
|
|
86
|
-
|
|
113
|
+
|
|
114
|
+
return valueCopy;
|
|
87
115
|
}
|
|
88
116
|
|
|
89
117
|
return {
|
|
@@ -99,7 +127,9 @@ function collectionToCode(collection: EntityCollection): object {
|
|
|
99
127
|
customId: collection.customId,
|
|
100
128
|
initialFilter: collection.initialFilter,
|
|
101
129
|
initialSort: collection.initialSort,
|
|
102
|
-
properties: Object.entries(
|
|
130
|
+
properties: Object.entries({
|
|
131
|
+
...(collection.properties ?? {})
|
|
132
|
+
})
|
|
103
133
|
.map(([key, value]) => ({
|
|
104
134
|
[key]: propertyCleanup(value)
|
|
105
135
|
}))
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { Card, cls, SquareIcon, Tooltip, Typography, VerticalSplitIcon } from "@firecms/ui";
|
|
2
|
+
|
|
3
|
+
export function LayoutModeSwitch({
|
|
4
|
+
value,
|
|
5
|
+
onChange,
|
|
6
|
+
className
|
|
7
|
+
}: {
|
|
8
|
+
value: "side_panel" | "full_screen";
|
|
9
|
+
onChange: (value: "side_panel" | "full_screen") => void;
|
|
10
|
+
className?: string;
|
|
11
|
+
}) {
|
|
12
|
+
|
|
13
|
+
return <div className={cls(className)}>
|
|
14
|
+
<Typography variant={"label"} color={"secondary"} className={"ml-3.5"}>Document view</Typography>
|
|
15
|
+
<div className={cls("flex flex-row gap-4")}>
|
|
16
|
+
|
|
17
|
+
<Tooltip title={"Documents are open in a side panel"}>
|
|
18
|
+
<Card
|
|
19
|
+
onClick={() => onChange("side_panel")}
|
|
20
|
+
className={cls(
|
|
21
|
+
"my-2 rounded-md mx-0 p-4 focus:outline-none transition ease-in-out duration-150 flex flex-row gap-4 items-center",
|
|
22
|
+
"text-surface-700 dark:text-surface-accent-300",
|
|
23
|
+
"hover:text-primary-dark dark:hover:text-primary focus:ring-primary hover:ring-1 hover:ring-primary",
|
|
24
|
+
value === "side_panel" ? "border-primary dark:border-primary" : "border-surface-400 dark:border-surface-600",
|
|
25
|
+
)}
|
|
26
|
+
>
|
|
27
|
+
<VerticalSplitIcon/>
|
|
28
|
+
<Typography variant={"label"}>
|
|
29
|
+
Side panel
|
|
30
|
+
</Typography>
|
|
31
|
+
</Card>
|
|
32
|
+
</Tooltip>
|
|
33
|
+
|
|
34
|
+
<Tooltip title={"Documents are open full-screen"}>
|
|
35
|
+
<Card
|
|
36
|
+
onClick={() => onChange("full_screen")}
|
|
37
|
+
className={cls(
|
|
38
|
+
"my-2 rounded-md mx-0 p-4 focus:outline-none transition ease-in-out duration-150 flex flex-row gap-4 items-center",
|
|
39
|
+
"text-surface-700 dark:text-surface-accent-300",
|
|
40
|
+
"hover:text-primary-dark dark:hover:text-primary focus:ring-primary hover:ring-1 hover:ring-primary",
|
|
41
|
+
value === "full_screen" ? "border-primary dark:border-primary" : "border-surface-400 dark:border-surface-600",
|
|
42
|
+
)}
|
|
43
|
+
>
|
|
44
|
+
<SquareIcon/>
|
|
45
|
+
<Typography variant={"label"}>
|
|
46
|
+
Full screen
|
|
47
|
+
</Typography>
|
|
48
|
+
</Card>
|
|
49
|
+
</Tooltip>
|
|
50
|
+
|
|
51
|
+
</div>
|
|
52
|
+
<Typography variant={"caption"} color={"secondary"} className={"ml-3.5"}>Should documents be opened full screen or in an inline side dialog</Typography>
|
|
53
|
+
</div>
|
|
54
|
+
}
|