From 1ae6a4c14baa2e6b27fd1b612840dc4b18caf6ec Mon Sep 17 00:00:00 2001 From: francesco Date: Wed, 22 Nov 2023 22:20:21 +0100 Subject: [PATCH] Allowing making complete collections editable --- .../src/collections/products_collection.tsx | 4 ++-- .../src/ConfigControllerProvider.tsx | 21 +++++++++++++++---- .../components/CollectionViewHeaderAction.tsx | 9 +++++--- .../components/PropertyAddColumnComponent.tsx | 3 ++- .../CollectionEditorDialog.tsx | 6 +++++- .../CollectionPropertiesEditorForm.tsx | 8 ++++++- .../components/collection_editor/EnumForm.tsx | 14 +++++++------ .../collection_editor/PropertyEditView.tsx | 19 +++++++++++++---- .../collection_editor/PropertyTree.tsx | 21 +++++++++++++------ .../import/CollectionEditorImportMapping.tsx | 9 +++++--- .../properties/BlockPropertyField.tsx | 7 +++++-- .../properties/MapPropertyField.tsx | 7 +++++-- .../properties/RepeatPropertyField.tsx | 9 +++++--- .../types/collection_editor_controller.tsx | 1 + packages/firebase_firecms/src/FireCMS3App.tsx | 1 - .../src/utils/collections_firestore.ts | 1 + .../EntityCollectionView.tsx | 1 + .../firecms_core/src/types/collections.ts | 7 +++++++ packages/firecms_core/src/types/plugins.tsx | 1 + 19 files changed, 110 insertions(+), 39 deletions(-) diff --git a/examples/example_v3/src/collections/products_collection.tsx b/examples/example_v3/src/collections/products_collection.tsx index f97caae5f..f21bef85a 100644 --- a/examples/example_v3/src/collections/products_collection.tsx +++ b/examples/example_v3/src/collections/products_collection.tsx @@ -43,6 +43,7 @@ export const productsCollection = buildCollection({ icon: "shopping_cart", description: "List of the products currently sold in our shop", textSearchEnabled: true, + editable: true, permissions: ({ authController }) => ({ edit: true, create: true, @@ -84,8 +85,7 @@ export const productsCollection = buildCollection({ clearable: true, validation: { required: true - }, - editable: true + } }, main_image: { dataType: "string", diff --git a/packages/collection_editor/src/ConfigControllerProvider.tsx b/packages/collection_editor/src/ConfigControllerProvider.tsx index 98a7b7fff..62dee3567 100644 --- a/packages/collection_editor/src/ConfigControllerProvider.tsx +++ b/packages/collection_editor/src/ConfigControllerProvider.tsx @@ -111,8 +111,11 @@ export const ConfigControllerProvider = React.memo( editedCollectionPath: string, fullPath?: string, parentPathSegments: string[], + collectionEditable: boolean; }>(); + console.log("currentPropertyDialog", currentPropertyDialog); + const defaultConfigPermissions: CollectionEditorPermissionsBuilder = useCallback(() => ({ createCollections: true, editCollections: true, @@ -146,14 +149,14 @@ export const ConfigControllerProvider = React.memo( editedCollectionPath, currentPropertiesOrder, parentPathSegments, - parentCollection + collection }: { propertyKey?: string, property?: Property, currentPropertiesOrder?: string[], editedCollectionPath: string, parentPathSegments: string[], - parentCollection?: EntityCollection + collection: EntityCollection, }) => { // namespace is all the path until the last dot const namespace = propertyKey && propertyKey.includes(".") @@ -162,6 +165,7 @@ export const ConfigControllerProvider = React.memo( const propertyKeyWithoutNamespace = propertyKey && propertyKey.includes(".") ? propertyKey.substring(propertyKey.lastIndexOf(".") + 1) : propertyKey; + console.log("edit property", propertyKeyWithoutNamespace, collection) setCurrentPropertyDialog({ propertyKey: propertyKeyWithoutNamespace, property, @@ -169,7 +173,7 @@ export const ConfigControllerProvider = React.memo( currentPropertiesOrder, editedCollectionPath, parentPathSegments, - parentCollection + collectionEditable: collection?.editable ?? false }); }, []); @@ -207,6 +211,9 @@ export const ConfigControllerProvider = React.memo( } } + console.log("aaa", getData, currentPropertyDialog?.editedCollectionPath); + console.log(currentPropertyDialog) + return ( getData(currentDialog.fullPath!) : undefined} + collectionEditable={currentPropertyDialog?.collectionEditable ?? false} + getData={getData && currentPropertyDialog?.editedCollectionPath + ? () => { + const resolvedPath = navigation.resolveAliasesFrom(currentPropertyDialog.editedCollectionPath!) + return getData(resolvedPath); + } + : undefined} onPropertyChanged={({ id, property diff --git a/packages/collection_editor/src/components/CollectionViewHeaderAction.tsx b/packages/collection_editor/src/components/CollectionViewHeaderAction.tsx index 1c94a5dda..4cebf4eba 100644 --- a/packages/collection_editor/src/components/CollectionViewHeaderAction.tsx +++ b/packages/collection_editor/src/components/CollectionViewHeaderAction.tsx @@ -1,4 +1,4 @@ -import { IconButton, ResolvedProperty, SettingsIcon, Tooltip } from "@firecms/core"; +import { EntityCollection, IconButton, ResolvedProperty, SettingsIcon, Tooltip } from "@firecms/core"; import React from "react"; import { useCollectionEditorController } from "../useCollectionEditorController"; @@ -7,13 +7,15 @@ export function CollectionViewHeaderAction({ onHover, property, fullPath, - parentPathSegments + parentPathSegments, + collection }: { property: ResolvedProperty, propertyKey: string, onHover: boolean, fullPath: string, parentPathSegments: string[], + collection: EntityCollection; }) { const collectionEditorController = useCollectionEditorController(); @@ -27,7 +29,8 @@ export function CollectionViewHeaderAction({ propertyKey, property, editedCollectionPath: fullPath, - parentPathSegments + parentPathSegments, + collection }); }} size={"small"}> diff --git a/packages/collection_editor/src/components/PropertyAddColumnComponent.tsx b/packages/collection_editor/src/components/PropertyAddColumnComponent.tsx index 315116235..87bb64b1a 100644 --- a/packages/collection_editor/src/components/PropertyAddColumnComponent.tsx +++ b/packages/collection_editor/src/components/PropertyAddColumnComponent.tsx @@ -29,7 +29,8 @@ export function PropertyAddColumnComponent({ collectionEditorController.editProperty({ editedCollectionPath: fullPath, parentPathSegments, - currentPropertiesOrder: getDefaultPropertiesOrder(collection) + currentPropertiesOrder: getDefaultPropertiesOrder(collection), + collection }); }}> diff --git a/packages/collection_editor/src/components/collection_editor/CollectionEditorDialog.tsx b/packages/collection_editor/src/components/collection_editor/CollectionEditorDialog.tsx index 478cebad9..cd30666ff 100644 --- a/packages/collection_editor/src/components/collection_editor/CollectionEditorDialog.tsx +++ b/packages/collection_editor/src/components/collection_editor/CollectionEditorDialog.tsx @@ -392,7 +392,8 @@ export function CollectionEditorDialogInternal getData(updatedFullPath) : undefined; + const resolvedPath = navigation.resolveAliasesFrom(updatedFullPath); + const getDataWithPath = resolvedPath && getData ? () => getData(resolvedPath) : undefined; // eslint-disable-next-line react-hooks/rules-of-hooks useEffect(() => { @@ -430,6 +431,7 @@ export function CollectionEditorDialogInternal {!isNewCollection && } {currentView === "import_data_preview" && importConfig && @@ -527,6 +530,7 @@ export function CollectionEditorDialogInternal Promise; doCollectionInference: (collection: PersistedCollection) => Promise | undefined; customFields: Record; + collectionEditable: boolean; }; export function CollectionPropertiesEditorForm({ @@ -55,7 +56,8 @@ export function CollectionPropertiesEditorForm({ getUser, getData, doCollectionInference, - customFields + customFields, + collectionEditable }: CollectionEditorFormProps) { const { @@ -354,6 +356,7 @@ export function CollectionPropertiesEditorForm({ propertiesOrder={usedPropertiesOrder} onPropertyMove={onPropertyMove} onPropertyRemove={isNewCollection ? deleteProperty : undefined} + collectionEditable={collectionEditable} errors={showErrors ? errors : {}}/> @@ -392,6 +395,7 @@ export function CollectionPropertiesEditorForm({ initialErrors={initialErrors} getData={getData} customFields={customFields} + collectionEditable={collectionEditable} />} {!selectedProperty && @@ -426,6 +430,7 @@ export function CollectionPropertiesEditorForm({ initialErrors={initialErrors} getData={getData} customFields={customFields} + collectionEditable={collectionEditable} onOkClicked={asDialog ? closePropertyDialog : undefined @@ -450,6 +455,7 @@ export function CollectionPropertiesEditorForm({ getData={getData} allowDataInference={!isNewCollection} customFields={customFields} + collectionEditable={collectionEditable} existingPropertyKeys={values.propertiesOrder as string[]}/> diff --git a/packages/collection_editor/src/components/collection_editor/EnumForm.tsx b/packages/collection_editor/src/components/collection_editor/EnumForm.tsx index a5866a366..0bd1854e5 100644 --- a/packages/collection_editor/src/components/collection_editor/EnumForm.tsx +++ b/packages/collection_editor/src/components/collection_editor/EnumForm.tsx @@ -14,7 +14,6 @@ import { FormikArrayContainer, IconButton, ListIcon, - LoadingButton, Paper, SettingsIcon, Typography @@ -132,7 +131,8 @@ function EnumFormFields({ }; const inferValues = async () => { - + if (!getData) + return; setInferring(true); getData?.().then((data) => { if (!data) @@ -169,12 +169,14 @@ function EnumFormFields({ Values {allowDataInference && - + } diff --git a/packages/collection_editor/src/components/collection_editor/PropertyEditView.tsx b/packages/collection_editor/src/components/collection_editor/PropertyEditView.tsx index 18544c064..6e3c624c8 100644 --- a/packages/collection_editor/src/components/collection_editor/PropertyEditView.tsx +++ b/packages/collection_editor/src/components/collection_editor/PropertyEditView.tsx @@ -20,7 +20,7 @@ import { isPropertyBuilder, mergeDeep, Property, - PropertyConfig, + PropertyConfig, PropertyOrBuilder, Select, toSnakeCase, Typography @@ -73,6 +73,7 @@ export type PropertyFormProps = { getData?: () => Promise; getHelpers?: (formikProps: FormikProps) => void; customFields: Record; + collectionEditable: boolean; }; export const PropertyForm = React.memo( @@ -95,7 +96,8 @@ export const PropertyForm = React.memo( allowDataInference, getHelpers, getData, - customFields + customFields, + collectionEditable }: PropertyFormProps) { const initialValue: PropertyWithId = { @@ -103,7 +105,8 @@ export const PropertyForm = React.memo( name: "" } as PropertyWithId; - const disabled = (property && !editableProperty(property)) ?? false; + const disabled = (Boolean(property && !editableProperty(property)) && !collectionEditable); + console.log("PropertyForm disabled", disabled) const lastSubmittedProperty = useRef(property ? { id: propertyKey, @@ -182,6 +185,7 @@ export const PropertyForm = React.memo( getData={getData} allowDataInference={allowDataInference} customFields={customFields} + collectionEditable={collectionEditable} {...props}/>; }} @@ -201,6 +205,7 @@ export function PropertyFormDialog({ onOkClicked, onPropertyChanged, getData, + collectionEditable, ...formProps }: PropertyFormProps & { open?: boolean; @@ -224,6 +229,7 @@ export function PropertyFormDialog({ onPropertyChanged?.(params); onOkClicked?.(); }} + collectionEditable={collectionEditable} onPropertyChangedImmediate={false} getHelpers={getHelpers} getData={getData} @@ -271,7 +277,8 @@ function PropertyEditView({ existingPropertyKeys, getData, allowDataInference, - customFields + customFields, + collectionEditable }: { includeIdAndTitle?: boolean; existing: boolean; @@ -288,6 +295,7 @@ function PropertyEditView({ getData?: () => Promise; allowDataInference: boolean; customFields: Record; + collectionEditable: boolean; } & FormikProps) { const [selectOpen, setSelectOpen] = useState(autoOpenTypeSelect); @@ -395,10 +403,12 @@ function PropertyEditView({ } else if (selectedFieldConfigId === "group") { childComponent = ; } else if (selectedFieldConfigId === "block") { childComponent = ; } else if (selectedFieldConfigId === "reference") { childComponent = @@ -421,6 +431,7 @@ function PropertyEditView({ getData={getData} allowDataInference={allowDataInference} disabled={disabled} + collectionEditable={collectionEditable} customFields={customFields}/>; } else if (selectedFieldConfigId === "key_value") { childComponent = diff --git a/packages/collection_editor/src/components/collection_editor/PropertyTree.tsx b/packages/collection_editor/src/components/collection_editor/PropertyTree.tsx index eab8bb91d..04beb5160 100644 --- a/packages/collection_editor/src/components/collection_editor/PropertyTree.tsx +++ b/packages/collection_editor/src/components/collection_editor/PropertyTree.tsx @@ -5,8 +5,10 @@ import { defaultBorderMixin, DragHandleIcon, ErrorBoundary, - IconButton, isPropertyBuilder, - PropertiesOrBuilders, PropertyOrBuilder, + IconButton, + isPropertyBuilder, + PropertiesOrBuilders, + PropertyOrBuilder, RemoveIcon, Tooltip } from "@firecms/core"; @@ -30,7 +32,8 @@ export function PropertyTree void; className?: string; inferredPropertyKeys?: string[]; + collectionEditable: boolean; }) { const propertiesOrder = propertiesOrderProp ?? Object.keys(properties); @@ -102,6 +106,7 @@ export function PropertyTree ); @@ -131,7 +136,8 @@ export function PropertyTreeEntry({ onPropertyClick, onPropertyMove, onPropertyRemove, - inferredPropertyKeys + inferredPropertyKeys, + collectionEditable }: { propertyKey: string; namespace?: string; @@ -144,6 +150,7 @@ export function PropertyTreeEntry({ onPropertyMove?: (propertiesOrder: string[], namespace?: string) => void; onPropertyRemove?: (propertyKey: string, namespace?: string) => void; inferredPropertyKeys?: string[]; + collectionEditable: boolean; }) { const isPropertyInferred = inferredPropertyKeys?.includes(namespace ? `${namespace}.${propertyKey}` : propertyKey); @@ -161,13 +168,15 @@ export function PropertyTreeEntry({ errors={errors} onPropertyClick={onPropertyClick} onPropertyMove={onPropertyMove} - onPropertyRemove={onPropertyRemove}/> + onPropertyRemove={onPropertyRemove} + collectionEditable={collectionEditable} + /> } } const hasError = fullId ? getIn(errors, idToPropertiesPath(fullId)) : false; const selected = selectedPropertyKey === fullId; - const editable = propertyOrBuilder && editableProperty(propertyOrBuilder); + const editable = propertyOrBuilder && ((collectionEditable && !isPropertyBuilder(propertyOrBuilder)) || editableProperty(propertyOrBuilder)); return (
+ customFields: Record, + collectionEditable: boolean }) { const { @@ -192,6 +194,7 @@ export function CollectionEditorImportMapping({ autoUpdateId={false} onPropertyChanged={onPropertyChanged} allowDataInference={false} + collectionEditable={collectionEditable} onOkClicked={() => { setSelectedProperty(undefined); }} diff --git a/packages/collection_editor/src/components/collection_editor/properties/BlockPropertyField.tsx b/packages/collection_editor/src/components/collection_editor/properties/BlockPropertyField.tsx index dd01a3d14..46db490fd 100644 --- a/packages/collection_editor/src/components/collection_editor/properties/BlockPropertyField.tsx +++ b/packages/collection_editor/src/components/collection_editor/properties/BlockPropertyField.tsx @@ -5,11 +5,12 @@ import { PropertyFormDialog } from "../PropertyEditView"; import { getFullId, idToPropertiesPath, namespaceToPropertiesOrderPath } from "../util"; import { PropertyTree } from "../PropertyTree"; -export function BlockPropertyField({ disabled, getData, allowDataInference, customFields }: { +export function BlockPropertyField({ disabled, getData, allowDataInference, customFields, collectionEditable }: { disabled: boolean; getData?: () => Promise; allowDataInference: boolean; - customFields: Record + customFields: Record, + collectionEditable: boolean; }) { const { @@ -81,6 +82,7 @@ export function BlockPropertyField({ disabled, getData, allowDataInference, cust properties={values.oneOf?.properties ?? {}} propertiesOrder={values.oneOf?.propertiesOrder} errors={{}} + collectionEditable={collectionEditable} onPropertyClick={disabled ? undefined : (propertyKey, namespace) => { @@ -116,6 +118,7 @@ export function BlockPropertyField({ disabled, getData, allowDataInference, cust setSelectedPropertyKey(undefined); setSelectedPropertyNamespace(undefined); }} + collectionEditable={collectionEditable} onDelete={deleteProperty} propertyKey={selectedPropertyKey} propertyNamespace={selectedPropertyNamespace} diff --git a/packages/collection_editor/src/components/collection_editor/properties/MapPropertyField.tsx b/packages/collection_editor/src/components/collection_editor/properties/MapPropertyField.tsx index 13b9a384e..11b6705dd 100644 --- a/packages/collection_editor/src/components/collection_editor/properties/MapPropertyField.tsx +++ b/packages/collection_editor/src/components/collection_editor/properties/MapPropertyField.tsx @@ -15,11 +15,12 @@ import { PropertyTree } from "../PropertyTree"; import { getFullId, idToPropertiesPath, namespaceToPropertiesOrderPath, namespaceToPropertiesPath } from "../util"; import { FieldHelperView } from "./FieldHelperView"; -export function MapPropertyField({ disabled, getData, allowDataInference, customFields }: { +export function MapPropertyField({ disabled, getData, allowDataInference, customFields, collectionEditable }: { disabled: boolean; getData?: () => Promise; allowDataInference: boolean; - customFields: Record + customFields: Record, + collectionEditable: boolean; }) { const { @@ -94,6 +95,7 @@ export function MapPropertyField({ disabled, getData, allowDataInference, custom properties={values.properties ?? {}} propertiesOrder={propertiesOrder} errors={{}} + collectionEditable={collectionEditable} onPropertyClick={(propertyKey, namespace) => { setSelectedPropertyKey(propertyKey); setSelectedPropertyNamespace(namespace); @@ -127,6 +129,7 @@ export function MapPropertyField({ disabled, getData, allowDataInference, custom forceShowErrors={false} open={propertyDialogOpen} allowDataInference={allowDataInference} + collectionEditable={collectionEditable} onCancel={() => { setPropertyDialogOpen(false); setSelectedPropertyKey(undefined); diff --git a/packages/collection_editor/src/components/collection_editor/properties/RepeatPropertyField.tsx b/packages/collection_editor/src/components/collection_editor/properties/RepeatPropertyField.tsx index 7d8de73e8..e83e5ce2f 100644 --- a/packages/collection_editor/src/components/collection_editor/properties/RepeatPropertyField.tsx +++ b/packages/collection_editor/src/components/collection_editor/properties/RepeatPropertyField.tsx @@ -2,10 +2,10 @@ import React, { useCallback, useState } from "react"; import { ArrayProperty, Button, - PropertyConfig, getFieldConfig, Paper, Property, + PropertyConfig, Typography, useFireCMSContext } from "@firecms/core"; @@ -21,14 +21,16 @@ export function RepeatPropertyField({ disabled, getData, allowDataInference, - customFields + customFields, + collectionEditable }: { showErrors: boolean, existing: boolean, disabled: boolean, getData?: () => Promise; allowDataInference: boolean; - customFields: Record + customFields: Record, + collectionEditable: boolean; }) { const { fields } = useFireCMSContext(); @@ -96,6 +98,7 @@ export function RepeatPropertyField({ onPropertyChanged={onPropertyChanged} forceShowErrors={showErrors} customFields={customFields} + collectionEditable={collectionEditable} /> )} diff --git a/packages/collection_editor/src/types/collection_editor_controller.tsx b/packages/collection_editor/src/types/collection_editor_controller.tsx index 97e2d96fb..eb80a8a36 100644 --- a/packages/collection_editor/src/types/collection_editor_controller.tsx +++ b/packages/collection_editor/src/types/collection_editor_controller.tsx @@ -31,6 +31,7 @@ export interface CollectionEditorController { currentPropertiesOrder?: string[], editedCollectionPath: string, parentPathSegments: string[], + collection: EntityCollection }) => void; configPermissions: CollectionEditorPermissionsBuilder; diff --git a/packages/firebase_firecms/src/FireCMS3App.tsx b/packages/firebase_firecms/src/FireCMS3App.tsx index d5958df83..f47d2adc6 100644 --- a/packages/firebase_firecms/src/FireCMS3App.tsx +++ b/packages/firebase_firecms/src/FireCMS3App.tsx @@ -446,7 +446,6 @@ function FireCMS3AppAuthenticated({ */ const userConfigPersistence = useBuildLocalConfigurationPersistence(); - const firestoreDelegate = useFirestoreDelegate({ firebaseApp, textSearchController: appConfig?.textSearchController, diff --git a/packages/firebase_firecms/src/utils/collections_firestore.ts b/packages/firebase_firecms/src/utils/collections_firestore.ts index 05acee576..99f2dfd55 100644 --- a/packages/firebase_firecms/src/utils/collections_firestore.ts +++ b/packages/firebase_firecms/src/utils/collections_firestore.ts @@ -78,6 +78,7 @@ export function prepareCollectionForPersistence typeof view === "string"); } + delete newCollection.editable; delete newCollection.additionalFields; delete newCollection.callbacks; delete newCollection.Actions; diff --git a/packages/firecms_core/src/core/components/EntityCollectionView/EntityCollectionView.tsx b/packages/firecms_core/src/core/components/EntityCollectionView/EntityCollectionView.tsx index 0291923b4..c000d6be4 100644 --- a/packages/firecms_core/src/core/components/EntityCollectionView/EntityCollectionView.tsx +++ b/packages/firecms_core/src/core/components/EntityCollectionView/EntityCollectionView.tsx @@ -527,6 +527,7 @@ export const EntityCollectionView = React.memo( propertyKey={propertyKey} property={property} fullPath={fullPath} + collection={collection} parentPathSegments={parentPathSegments ?? []}/>; })} ; diff --git a/packages/firecms_core/src/types/collections.ts b/packages/firecms_core/src/types/collections.ts index e2c14431e..750e27a9d 100644 --- a/packages/firecms_core/src/types/collections.ts +++ b/packages/firecms_core/src/types/collections.ts @@ -77,6 +77,13 @@ export interface EntityCollection = any, */ properties: PropertiesOrBuilders; + /** + * Can this collection be edited by the end user. + * Defaults to `false`. + * Keep in mind that you can also set this prop to individual properties. + */ + editable?: boolean; + /** * Order in which the properties are displayed. * If you are specifying your collection as code, the order is the same as the diff --git a/packages/firecms_core/src/types/plugins.tsx b/packages/firecms_core/src/types/plugins.tsx index 09fc38ad4..8530a2c0c 100644 --- a/packages/firecms_core/src/types/plugins.tsx +++ b/packages/firecms_core/src/types/plugins.tsx @@ -115,6 +115,7 @@ export type FireCMSPlugin = { fullPath: string, parentPathSegments: string[], onHover: boolean, + collection: EntityCollection; }>; /**