@firecms/collection_editor 3.0.0-alpha.17 → 3.0.0-alpha.18

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 (64) hide show
  1. package/package.json +4 -3
  2. package/src/ConfigControllerProvider.tsx +177 -0
  3. package/src/components/EditorCollectionAction.tsx +95 -0
  4. package/src/components/HomePageEditorCollectionAction.tsx +81 -0
  5. package/src/components/NewCollectionCard.tsx +45 -0
  6. package/src/components/RootCollectionSuggestions.tsx +53 -0
  7. package/src/components/collection_editor/CollectionDetailsForm.tsx +312 -0
  8. package/src/components/collection_editor/CollectionEditorDialog.tsx +640 -0
  9. package/src/components/collection_editor/CollectionEditorWelcomeView.tsx +212 -0
  10. package/src/components/collection_editor/CollectionPropertiesEditorForm.tsx +450 -0
  11. package/src/components/collection_editor/CollectionYupValidation.tsx +6 -0
  12. package/src/components/collection_editor/EntityCustomViewsSelectDialog.tsx +29 -0
  13. package/src/components/collection_editor/EnumForm.tsx +354 -0
  14. package/src/components/collection_editor/PropertyEditView.tsx +535 -0
  15. package/src/components/collection_editor/PropertyFieldPreview.tsx +205 -0
  16. package/src/components/collection_editor/PropertySelectItem.tsx +31 -0
  17. package/src/components/collection_editor/PropertyTree.tsx +228 -0
  18. package/src/components/collection_editor/SelectIcons.tsx +72 -0
  19. package/src/components/collection_editor/SubcollectionsEditTab.tsx +239 -0
  20. package/src/components/collection_editor/UnsavedChangesDialog.tsx +47 -0
  21. package/src/components/collection_editor/import/CollectionEditorImportDataPreview.tsx +37 -0
  22. package/src/components/collection_editor/import/CollectionEditorImportMapping.tsx +236 -0
  23. package/src/components/collection_editor/import/clean_import_data.ts +53 -0
  24. package/src/components/collection_editor/properties/BlockPropertyField.tsx +131 -0
  25. package/src/components/collection_editor/properties/BooleanPropertyField.tsx +36 -0
  26. package/src/components/collection_editor/properties/CommonPropertyFields.tsx +112 -0
  27. package/src/components/collection_editor/properties/DateTimePropertyField.tsx +86 -0
  28. package/src/components/collection_editor/properties/EnumPropertyField.tsx +116 -0
  29. package/src/components/collection_editor/properties/FieldHelperView.tsx +13 -0
  30. package/src/components/collection_editor/properties/KeyValuePropertyField.tsx +20 -0
  31. package/src/components/collection_editor/properties/MapPropertyField.tsx +154 -0
  32. package/src/components/collection_editor/properties/NumberPropertyField.tsx +38 -0
  33. package/src/components/collection_editor/properties/ReferencePropertyField.tsx +184 -0
  34. package/src/components/collection_editor/properties/RepeatPropertyField.tsx +115 -0
  35. package/src/components/collection_editor/properties/StoragePropertyField.tsx +194 -0
  36. package/src/components/collection_editor/properties/StringPropertyField.tsx +85 -0
  37. package/src/components/collection_editor/properties/advanced/AdvancedPropertyValidation.tsx +36 -0
  38. package/src/components/collection_editor/properties/validation/ArrayPropertyValidation.tsx +50 -0
  39. package/src/components/collection_editor/properties/validation/GeneralPropertyValidation.tsx +49 -0
  40. package/src/components/collection_editor/properties/validation/NumberPropertyValidation.tsx +99 -0
  41. package/src/components/collection_editor/properties/validation/StringPropertyValidation.tsx +131 -0
  42. package/src/components/collection_editor/properties/validation/ValidationPanel.tsx +28 -0
  43. package/src/components/collection_editor/templates/blog_template.ts +115 -0
  44. package/src/components/collection_editor/templates/products_template.ts +89 -0
  45. package/src/components/collection_editor/templates/users_template.ts +34 -0
  46. package/src/components/collection_editor/util.ts +21 -0
  47. package/src/components/collection_editor/utils/supported_fields.tsx +28 -0
  48. package/src/components/collection_editor/utils/update_property_for_widget.ts +258 -0
  49. package/src/components/collection_editor/utils/useTraceUpdate.tsx +23 -0
  50. package/src/index.ts +31 -0
  51. package/src/types/collection_editor_controller.tsx +31 -0
  52. package/src/types/collection_inference.ts +3 -0
  53. package/src/types/config_controller.tsx +30 -0
  54. package/src/types/config_permissions.ts +20 -0
  55. package/src/types/persisted_collection.ts +7 -0
  56. package/src/useCollectionEditorController.tsx +9 -0
  57. package/src/useCollectionEditorPlugin.tsx +103 -0
  58. package/src/useCollectionsConfigController.tsx +9 -0
  59. package/src/utils/arrays.ts +3 -0
  60. package/src/utils/entities.ts +38 -0
  61. package/src/utils/icons.ts +17 -0
  62. package/src/utils/join_collections.ts +144 -0
  63. package/src/utils/synonyms.ts +1952 -0
  64. package/src/vite-env.d.ts +1 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@firecms/collection_editor",
3
- "version": "3.0.0-alpha.17",
3
+ "version": "3.0.0-alpha.18",
4
4
  "main": "./dist/index.umd.js",
5
5
  "module": "./dist/index.es.js",
6
6
  "types": "dist/index.d.ts",
@@ -66,10 +66,11 @@
66
66
  "vite-plugin-fonts": "^0.7.0"
67
67
  },
68
68
  "files": [
69
- "dist"
69
+ "dist",
70
+ "src"
70
71
  ],
71
72
  "publishConfig": {
72
73
  "access": "public"
73
74
  },
74
- "gitHead": "3a2be58e39f08f1c1fe0d993acfab648e0513c53"
75
+ "gitHead": "ab75f612b0aa204b1a3f656367578b6ca65e8c5e"
75
76
  }
@@ -0,0 +1,177 @@
1
+ import React, { PropsWithChildren, useCallback, useEffect } from "react";
2
+ import equal from "react-fast-compare"
3
+
4
+ import { CollectionsConfigController } from "./types/config_controller";
5
+ import { EntityCollection, useNavigationContext, User } from "@firecms/core";
6
+ import { CollectionEditorDialog } from "./components/collection_editor/CollectionEditorDialog";
7
+ import { useNavigate } from "react-router";
8
+ import { CollectionEditorController } from "./types/collection_editor_controller";
9
+ import { CollectionEditorPermissionsBuilder } from "./types/config_permissions";
10
+ import { CollectionInference } from "./types/collection_inference";
11
+
12
+ export const ConfigControllerContext = React.createContext<CollectionsConfigController>({} as any);
13
+ export const CollectionEditorContext = React.createContext<CollectionEditorController>({} as any);
14
+
15
+ export interface ConfigControllerProviderProps {
16
+ /**
17
+ * Controller for managing the collections' config.
18
+ */
19
+ collectionConfigController: CollectionsConfigController;
20
+
21
+ /**
22
+ * Callback used to infer the schema from the data.
23
+ */
24
+ collectionInference?: CollectionInference;
25
+
26
+ /**
27
+ * Use this builder to define the permissions for the configuration per user.
28
+ */
29
+ configPermissions?: CollectionEditorPermissionsBuilder;
30
+
31
+ /**
32
+ * Groups that cannot be used to create new collections.
33
+ */
34
+ reservedGroups?: string[];
35
+
36
+ extraView?: {
37
+ View: React.ComponentType<{
38
+ path: string
39
+ }>,
40
+ icon: React.ReactNode
41
+ };
42
+
43
+ pathSuggestions?: (path?: string) => Promise<string[]>;
44
+
45
+ getUser: (uid: string) => User | null
46
+
47
+ getData?: (path: string) => Promise<object[]>;
48
+
49
+ }
50
+
51
+ export const ConfigControllerProvider = React.memo(
52
+ function ConfigControllerProvider({
53
+ children,
54
+ collectionConfigController,
55
+ configPermissions,
56
+ reservedGroups,
57
+ collectionInference,
58
+ extraView,
59
+ pathSuggestions,
60
+ getUser,
61
+ getData
62
+ }: PropsWithChildren<ConfigControllerProviderProps>) {
63
+
64
+ const navigation = useNavigationContext();
65
+ const navigate = useNavigate();
66
+
67
+ const {
68
+ collections
69
+ } = navigation;
70
+ const existingPaths = collections.map(col => col.path.trim().toLowerCase());
71
+
72
+ const [rootPathSuggestions, setRootPathSuggestions] = React.useState<string[] | undefined>();
73
+ useEffect(() => {
74
+ if (pathSuggestions) {
75
+ pathSuggestions().then((paths) => {
76
+ setRootPathSuggestions(paths.filter(p => !existingPaths.includes(p.trim().toLowerCase())));
77
+ });
78
+ }
79
+ }, [pathSuggestions]);
80
+
81
+ const [currentDialog, setCurrentDialog] = React.useState<{
82
+ isNewCollection: boolean,
83
+ parentCollection?: EntityCollection,
84
+ editedCollectionPath?: string,
85
+ fullPath?: string,
86
+ parentPathSegments: string[],
87
+ initialValues?: {
88
+ path?: string,
89
+ group?: string,
90
+ name?: string
91
+ },
92
+ }>();
93
+
94
+ const defaultConfigPermissions: CollectionEditorPermissionsBuilder = useCallback(() => ({
95
+ createCollections: true,
96
+ editCollections: true,
97
+ deleteCollections: true
98
+ }), []);
99
+
100
+ const editCollection = useCallback(({ path, fullPath, parentPathSegments, parentCollection }: {
101
+ path?: string,
102
+ fullPath?: string,
103
+ parentPathSegments: string[],
104
+ parentCollection?: EntityCollection
105
+ }) => {
106
+ setCurrentDialog({
107
+ editedCollectionPath: path,
108
+ fullPath,
109
+ parentPathSegments,
110
+ isNewCollection: false,
111
+ parentCollection
112
+ });
113
+ }, []);
114
+
115
+ const createCollection = React.useCallback(({ parentPathSegments, parentCollection, initialValues }: {
116
+ parentPathSegments: string[],
117
+ parentCollection?: EntityCollection<any, any, any>
118
+ initialValues?: {
119
+ group?: string,
120
+ path?: string,
121
+ name?: string
122
+ }
123
+ }) => {
124
+ setCurrentDialog({
125
+ isNewCollection: true,
126
+ parentPathSegments,
127
+ parentCollection,
128
+ initialValues
129
+ });
130
+ }, []);
131
+
132
+ const getPathSuggestions = !pathSuggestions
133
+ ? undefined
134
+ : (path?: string) => {
135
+ if (!path && rootPathSuggestions)
136
+ return Promise.resolve(rootPathSuggestions);
137
+ else {
138
+ return pathSuggestions?.(path);
139
+ }
140
+ }
141
+
142
+ return (
143
+ <ConfigControllerContext.Provider value={collectionConfigController}>
144
+ <CollectionEditorContext.Provider
145
+ value={{
146
+ editCollection,
147
+ createCollection,
148
+ configPermissions: configPermissions ?? defaultConfigPermissions,
149
+ rootPathSuggestions
150
+ }}>
151
+
152
+ {children}
153
+
154
+ <CollectionEditorDialog
155
+ open={Boolean(currentDialog)}
156
+ configController={collectionConfigController}
157
+ isNewCollection={false}
158
+ collectionInference={collectionInference}
159
+ {...currentDialog}
160
+ getData={getData}
161
+ reservedGroups={reservedGroups}
162
+ extraView={extraView}
163
+ pathSuggestions={getPathSuggestions}
164
+ getUser={getUser}
165
+ handleClose={(collection) => {
166
+ if (collection && currentDialog?.isNewCollection && !currentDialog.parentPathSegments.length) {
167
+ const url = navigation.buildUrlCollectionPath(collection.alias ?? collection.path);
168
+ navigate(url);
169
+ }
170
+ setCurrentDialog(undefined);
171
+ }}/>
172
+
173
+ </CollectionEditorContext.Provider>
174
+
175
+ </ConfigControllerContext.Provider>
176
+ );
177
+ }, equal);
@@ -0,0 +1,95 @@
1
+ import equal from "react-fast-compare"
2
+
3
+ import {
4
+ Button,
5
+ CollectionActionsProps,
6
+ IconButton,
7
+ mergeDeep,
8
+ SaveIcon,
9
+ SettingsIcon,
10
+ Tooltip,
11
+ useAuthController,
12
+ useNavigationContext,
13
+ useSnackbarController
14
+ } from "@firecms/core";
15
+
16
+ import { useCollectionEditorController } from "../useCollectionEditorController";
17
+ import { useCollectionsConfigController } from "../useCollectionsConfigController";
18
+ import { PersistedCollection } from "../types/persisted_collection";
19
+
20
+ export function EditorCollectionAction({
21
+ path: fullPath,
22
+ parentPathSegments,
23
+ collection,
24
+ tableController
25
+ }: CollectionActionsProps) {
26
+
27
+ const authController = useAuthController();
28
+ const navigationController = useNavigationContext();
29
+ const collectionEditorController = useCollectionEditorController();
30
+ const configController = useCollectionsConfigController();
31
+ const snackbarController = useSnackbarController();
32
+
33
+ const parentCollection = navigationController.getCollectionFromPaths(parentPathSegments);
34
+
35
+ const canEditCollection = collectionEditorController.configPermissions
36
+ ? collectionEditorController.configPermissions({
37
+ user: authController.user,
38
+ collection
39
+ }).editCollections
40
+ : true;
41
+
42
+ let saveDefaultFilterButton = null;
43
+ if (!equal(getObjectOrNull(tableController.filterValues), getObjectOrNull(collection.initialFilter)) ||
44
+ !equal(getObjectOrNull(tableController.sortBy), getObjectOrNull(collection.initialSort))) {
45
+ saveDefaultFilterButton = <Tooltip
46
+ title={tableController.sortBy || tableController.filterValues ? "Save default filter and sort" : "Clear default filter and sort"}>
47
+ <Button
48
+ color={"primary"}
49
+ size={"small"}
50
+ variant={"outlined"}
51
+ onClick={() => configController
52
+ ?.saveCollection({
53
+ path: collection.path,
54
+ parentPathSegments,
55
+ collectionData: mergeDeep(collection as PersistedCollection,
56
+ {
57
+ initialFilter: tableController.filterValues ?? null,
58
+ initialSort: tableController.sortBy ?? null
59
+ })
60
+ }).then(() => {
61
+ snackbarController.open({
62
+ type: "success",
63
+ message: "Default config saved"
64
+ });
65
+ })}>
66
+ <SaveIcon/>
67
+ {/*{tableController.sortBy || tableController.filterValues ? "Save default filter and sort" : "Clear default filter and sort"}*/}
68
+ </Button>
69
+ </Tooltip>;
70
+ }
71
+
72
+ const editorButton = <Tooltip
73
+ title={canEditCollection ? "Edit collection" : "You don't have permissions to edit this collection"}>
74
+ <IconButton
75
+ color={"primary"}
76
+ disabled={!canEditCollection}
77
+ onClick={canEditCollection
78
+ ? () => collectionEditorController?.editCollection({ path: collection.path, fullPath, parentPathSegments, parentCollection })
79
+ : undefined}>
80
+ <SettingsIcon/>
81
+ </IconButton>
82
+ </Tooltip>;
83
+
84
+ return <>
85
+ {canEditCollection && saveDefaultFilterButton}
86
+ {editorButton}
87
+ </>
88
+
89
+ }
90
+
91
+ function getObjectOrNull(o?: object): object | null {
92
+ if (o && Object.keys(o).length === 0)
93
+ return o
94
+ return o ?? null;
95
+ }
@@ -0,0 +1,81 @@
1
+ import {
2
+ DeleteConfirmationDialog,
3
+ DeleteIcon,
4
+ IconButton,
5
+ Menu,
6
+ MenuItem,
7
+ MoreVertIcon,
8
+ PluginHomePageActionsProps,
9
+ SettingsIcon,
10
+ useAuthController
11
+ } from "@firecms/core";
12
+ import { useCollectionEditorController } from "../useCollectionEditorController";
13
+ import { useCallback, useState } from "react";
14
+ import { useCollectionsConfigController } from "../useCollectionsConfigController";
15
+
16
+ export function HomePageEditorCollectionAction({
17
+ path,
18
+ collection
19
+ }: PluginHomePageActionsProps) {
20
+
21
+ const authController = useAuthController();
22
+ const configController = useCollectionsConfigController();
23
+ const collectionEditorController = useCollectionEditorController();
24
+
25
+ const permissions = collectionEditorController.configPermissions({
26
+ user: authController.user,
27
+ collection
28
+ });
29
+
30
+ const onEditCollectionClicked = useCallback(() => {
31
+ collectionEditorController?.editCollection({ path, parentPathSegments: [] });
32
+ }, [collectionEditorController, path]);
33
+
34
+ const [deleteRequested, setDeleteRequested] = useState(false);
35
+
36
+ const deleteCollection = useCallback(() => {
37
+ configController?.deleteCollection({ path });
38
+ }, [path, configController]);
39
+
40
+ return <>
41
+
42
+ <div>
43
+ {permissions.deleteCollections &&
44
+ <Menu
45
+ trigger={<IconButton>
46
+ <MoreVertIcon size={"small"}/>
47
+ </IconButton>}
48
+ >
49
+ <MenuItem onClick={(event) => {
50
+ event.preventDefault();
51
+ event.stopPropagation();
52
+ setDeleteRequested(true);
53
+ }}>
54
+ <DeleteIcon/>
55
+ Delete
56
+ </MenuItem>
57
+
58
+ </Menu>
59
+
60
+ }
61
+
62
+ {permissions.editCollections &&
63
+ <IconButton
64
+ onClick={(event) => {
65
+ onEditCollectionClicked();
66
+ }}>
67
+ <SettingsIcon size={"small"}/>
68
+ </IconButton>}
69
+ </div>
70
+
71
+ <DeleteConfirmationDialog
72
+ open={deleteRequested}
73
+ onAccept={deleteCollection}
74
+ onCancel={() => setDeleteRequested(false)}
75
+ title={<>Delete this collection?</>}
76
+ body={<> This will <b>not
77
+ delete any data</b>, only
78
+ the collection in the CMS</>}/>
79
+ </>;
80
+
81
+ }
@@ -0,0 +1,45 @@
1
+ import { AddIcon, Card, cn, PluginHomePageAdditionalCardsProps, Typography, useAuthController } from "@firecms/core";
2
+ import { useCollectionEditorController } from "../useCollectionEditorController";
3
+
4
+ export function NewCollectionCard({
5
+ group,
6
+ context
7
+ }: PluginHomePageAdditionalCardsProps) {
8
+
9
+ if (!context.navigation.topLevelNavigation)
10
+ throw Error("Navigation not ready in FireCMSHomePage");
11
+
12
+ const authController = useAuthController();
13
+
14
+ const collectionEditorController = useCollectionEditorController();
15
+ const canCreateCollections = collectionEditorController.configPermissions
16
+ ? collectionEditorController.configPermissions({
17
+ user: authController.user,
18
+ }).createCollections
19
+ : true;
20
+
21
+ return (
22
+ <Card className={cn("h-full p-4 min-h-[124px]")}
23
+ onClick={collectionEditorController && canCreateCollections
24
+ ? () => collectionEditorController.createCollection({
25
+ initialValues: group ? { group } : undefined,
26
+ parentPathSegments: []
27
+ })
28
+ : undefined}>
29
+
30
+ <div
31
+ className="flex flex-col items-start h-full w-full items-center justify-center h-full w-full flex-grow flex-col">
32
+ <AddIcon color="primary"/>
33
+ <Typography color="primary"
34
+ variant={"caption"}
35
+ className={"font-medium"}>{"Add new collection".toUpperCase()}</Typography>
36
+
37
+ {!canCreateCollections &&
38
+ <Typography variant={"caption"}>You don&apos;t have permissions to create
39
+ collections</Typography>
40
+ }
41
+ </div>
42
+
43
+ </Card>
44
+ );
45
+ }
@@ -0,0 +1,53 @@
1
+ import { AddIcon, Chip, Collapse, Typography, unslugify, useAuthController, useNavigationContext } from "@firecms/core";
2
+ import { useCollectionEditorController } from "../useCollectionEditorController";
3
+ import React from "react";
4
+
5
+ export function RootCollectionSuggestions() {
6
+
7
+ const authController = useAuthController();
8
+ const navigationContext = useNavigationContext();
9
+
10
+ const collectionEditorController = useCollectionEditorController();
11
+ const canCreateCollections = collectionEditorController.configPermissions
12
+ ? collectionEditorController.configPermissions({
13
+ user: authController.user
14
+ }).createCollections
15
+ : true;
16
+
17
+ const rootPathSuggestions = collectionEditorController.rootPathSuggestions ?? [];
18
+
19
+ const showSuggestions = rootPathSuggestions.length > 3 || (navigationContext.collections.length === 0 && rootPathSuggestions.length > 0);
20
+ return <Collapse
21
+ in={showSuggestions}>
22
+
23
+ <div
24
+ className={"flex flex-col gap-1 p-2"}>
25
+
26
+ <Typography variant={"body2"} color={"secondary"}>
27
+ Create a collection from your data:
28
+ </Typography>
29
+
30
+ <div
31
+ className={"flex flex-row gap-1 overflow-scroll no-scrollbar "}>
32
+ {rootPathSuggestions.map((path) => {
33
+ return (
34
+ <div key={path}>
35
+ <Chip
36
+ icon={<AddIcon size={"small"}/>}
37
+ colorScheme={"cyanLighter"}
38
+ onClick={collectionEditorController && canCreateCollections
39
+ ? () => collectionEditorController.createCollection({
40
+ initialValues: { path, name: unslugify(path) },
41
+ parentPathSegments: []
42
+ })
43
+ : undefined}
44
+ size="small">
45
+ {path}
46
+ </Chip>
47
+ </div>
48
+ );
49
+ })}
50
+ </div>
51
+ </div>
52
+ </Collapse>
53
+ }