mirror of
https://github.com/zhigang1992/firecms.git
synced 2026-06-18 04:08:07 +08:00
626 lines
15 KiB
TypeScript
626 lines
15 KiB
TypeScript
import * as React from "react";
|
|
import { TextSearchDelegate } from "./text_search_delegate";
|
|
import { CMSFieldProps } from "./form/form_props";
|
|
import { PreviewComponentProps } from "./preview";
|
|
import { storage } from "firebase";
|
|
|
|
/**
|
|
* This interface represents a view that includes a collection of entities.
|
|
* It can be in the root level of the configuration, defining the main
|
|
* menu navigation.
|
|
*/
|
|
export interface EntityCollectionView<S extends EntitySchema,
|
|
P extends Properties = S["properties"],
|
|
Key extends string = Extract<keyof P, string>> {
|
|
|
|
/**
|
|
* Plural name of the view. E.g. 'products'
|
|
*/
|
|
name: string;
|
|
|
|
/**
|
|
* Relative Firestore path of this view to its parent.
|
|
* If this view is in the root the path is equal to the absolute one.
|
|
* This path also determines the URL in FireCMS
|
|
*/
|
|
relativePath: string;
|
|
|
|
/**
|
|
* Schema representing the entities of this view
|
|
*/
|
|
schema: S;
|
|
|
|
/**
|
|
* Should the table be rendered in small format
|
|
*/
|
|
small?: boolean;
|
|
|
|
/**
|
|
* Is pagination enabled in this view. True if not specified
|
|
*/
|
|
pagination?: boolean;
|
|
|
|
/**
|
|
* You can add additional columns to the collection view by implementing
|
|
* an additional column delegate.
|
|
*/
|
|
additionalColumns?: AdditionalColumnDelegate<S>[];
|
|
|
|
/**
|
|
* If a text search delegate is supplied, a search bar is displayed on top
|
|
*/
|
|
textSearchDelegate?: TextSearchDelegate;
|
|
|
|
/**
|
|
* Can the elements in this collection be deleted. Defaults to true
|
|
*/
|
|
deleteEnabled?: boolean;
|
|
|
|
/**
|
|
* Following the Firestore document and collection schema, you can add
|
|
* subcollections to your entity in the same way you define the root
|
|
* collections.
|
|
*/
|
|
subcollections?: EntityCollectionView<any>[];
|
|
|
|
/**
|
|
* Properties displayed in this collection. If this property is not set
|
|
* every property is displayed
|
|
*/
|
|
properties?: Key[];
|
|
|
|
/**
|
|
* Properties that can be filtered in this view
|
|
*/
|
|
filterableProperties?: Key[];
|
|
|
|
/**
|
|
* Hook called when the user initiates a delete operation.
|
|
* This is called after the entity is deleted in Firestore.
|
|
*
|
|
* @param collectionPath the Firestore collection path.
|
|
* @param entity the entity being deleted.
|
|
*/
|
|
onEntityDelete?(collectionPath: string, entity: Entity<S>): void;
|
|
}
|
|
|
|
/**
|
|
* Specification for defining an entity
|
|
*/
|
|
export interface EntitySchema<Key extends string = string, P extends Properties<Key> = Properties<Key>> {
|
|
|
|
/**
|
|
* Singular name of the entity as displayed in an Add button . E.g. Product
|
|
*/
|
|
name: string;
|
|
|
|
/**
|
|
* Description of this entity
|
|
*/
|
|
description?: string;
|
|
|
|
/**
|
|
* If this property is not set Firestore will create a random ID.
|
|
* You can set the value to true to allow the users to choose the ID.
|
|
* You can also pass a set of values (as an EnumValues object) to allow them
|
|
* to pick from only those
|
|
*/
|
|
customId?: boolean | EnumValues<string>;
|
|
|
|
/**
|
|
* Set of properties that compose an entity
|
|
*/
|
|
properties: P;
|
|
|
|
/**
|
|
* Hook called when save is successful
|
|
* @param entitySaveProps
|
|
*/
|
|
onSaveSuccess?(entitySaveProps: EntitySaveProps<this>)
|
|
: Promise<void> | void;
|
|
|
|
/**
|
|
* Hook called when saving fails
|
|
* @param entitySaveProps
|
|
*/
|
|
onSaveFailure?(entitySaveProps: EntitySaveProps<this>)
|
|
: Promise<void> | void;
|
|
|
|
/**
|
|
* Hook called before saving, you need to return the values that will get
|
|
* saved. If you throw an error in this method the process stops, and an
|
|
* error snackbar gets displayed.
|
|
* @param entitySaveProps
|
|
*/
|
|
onPreSave?(entitySaveProps: EntitySaveProps<this>)
|
|
: Promise<EntityValues<this>> | EntityValues<this>
|
|
}
|
|
|
|
/**
|
|
*
|
|
*/
|
|
export interface EntitySaveProps<S extends EntitySchema,
|
|
P extends Properties = S["properties"],
|
|
Key extends string = Extract<keyof P, string>> {
|
|
schema: S;
|
|
collectionPath: string;
|
|
id?: string;
|
|
values: EntityValues<S, P, Key>;
|
|
status: EntityStatus;
|
|
}
|
|
|
|
/**
|
|
* Identity function we use to defeat the type system of Typescript and preserve
|
|
* the schema keys
|
|
* @param schema
|
|
*/
|
|
export function buildSchema<Key extends string, P extends Properties<Key>>(schema: EntitySchema<Key, P>): EntitySchema<Key, P> {
|
|
return schema;
|
|
}
|
|
|
|
/**
|
|
* New or existing status
|
|
*/
|
|
export enum EntityStatus { new = "new", existing = "existing"}
|
|
|
|
/**
|
|
* Representation of an entity fetched from Firestore
|
|
*/
|
|
export interface Entity<S extends EntitySchema,
|
|
P extends Properties = S["properties"],
|
|
Key extends string = Extract<keyof P, string>> {
|
|
id: string;
|
|
snapshot: firebase.firestore.DocumentSnapshot;
|
|
reference: firebase.firestore.DocumentReference;
|
|
values: EntityValues<S, P, Key>;
|
|
}
|
|
|
|
type DataType =
|
|
| "number"
|
|
| "string"
|
|
| "boolean"
|
|
| "map"
|
|
| "array"
|
|
| "timestamp"
|
|
| "geopoint"
|
|
| "reference";
|
|
|
|
export type MediaType =
|
|
| "image"
|
|
| "video"
|
|
| "audio";
|
|
|
|
export type Property<T = any, ArrayT = any> =
|
|
T extends string ? StringProperty :
|
|
T extends number ? NumberProperty :
|
|
T extends boolean ? BooleanProperty :
|
|
T extends firebase.firestore.Timestamp ? TimestampProperty :
|
|
T extends firebase.firestore.GeoPoint ? GeopointProperty :
|
|
T extends firebase.firestore.DocumentReference ? ReferenceProperty<EntitySchema> :
|
|
T extends Array<ArrayT> ? ArrayProperty<ArrayT> :
|
|
MapProperty<T>;
|
|
|
|
/**
|
|
* Use this interface for adding additional columns to entity collection views.
|
|
* If you need to do some async loading you can use AsyncPreviewComponent
|
|
*/
|
|
export interface AdditionalColumnDelegate<S extends EntitySchema,
|
|
P extends Properties = S["properties"],
|
|
Key extends string = Extract<keyof P, string>> {
|
|
|
|
title: string;
|
|
|
|
builder: (entity: Entity<S, P, Key>) => React.ReactNode;
|
|
|
|
}
|
|
|
|
/**
|
|
* Interface including all common properties of a CMS property
|
|
*/
|
|
export interface BaseProperty<T> {
|
|
|
|
/**
|
|
* Firestore datatype of the property
|
|
*/
|
|
dataType: DataType;
|
|
|
|
/**
|
|
* Property title (e.g. Product)
|
|
*/
|
|
title?: string;
|
|
|
|
/**
|
|
* Property description, always displayed under the field
|
|
*/
|
|
description?: string;
|
|
|
|
/**
|
|
* Longer description of a field, displayed under a popover
|
|
*/
|
|
longDescription?: string;
|
|
|
|
/**
|
|
* Is this a read only property
|
|
*/
|
|
disabled?: boolean;
|
|
|
|
/**
|
|
* Configure how this property field is displayed
|
|
*/
|
|
config?: FieldConfig<T>;
|
|
|
|
/**
|
|
* Rules for validating this property
|
|
*/
|
|
validation?: PropertyValidationSchema,
|
|
|
|
}
|
|
|
|
export type EnumType = number | string ;
|
|
|
|
/**
|
|
* We use this interface to define mapping between string or number values in
|
|
* Firestore to a label (such in a select dropdown)
|
|
* The key in this Record is the value saved in Firestore, and the value in
|
|
* this record is the label displayed in the UI
|
|
*/
|
|
export type EnumValues<T extends EnumType> = Record<T, string>; // id -> Label
|
|
|
|
/**
|
|
* Record of properties of an entity or a map property
|
|
*/
|
|
export type Properties<Key extends string = string, T extends any = any> = Record<Key, Property<T>>;
|
|
|
|
/**
|
|
* This type represents a record of key value pairs as described in an
|
|
* entity schema.
|
|
*/
|
|
export type EntityValues<S extends EntitySchema,
|
|
P extends Properties = S["properties"],
|
|
Key extends string = Extract<keyof P, string>>
|
|
= {
|
|
[K in Key]: P[K] extends Property<infer T> ? T : any;
|
|
};
|
|
|
|
export interface NumberProperty extends BaseProperty<number> {
|
|
|
|
dataType: "number";
|
|
|
|
/**
|
|
* Configure how this field is displayed
|
|
*/
|
|
config?: NumberFieldConfig;
|
|
|
|
/**
|
|
* Rules for validating this property
|
|
*/
|
|
validation?: NumberPropertyValidationSchema,
|
|
|
|
}
|
|
|
|
export interface BooleanProperty extends BaseProperty<boolean> {
|
|
|
|
dataType: "boolean";
|
|
|
|
/**
|
|
* Rules for validating this property
|
|
*/
|
|
validation?: PropertyValidationSchema,
|
|
}
|
|
|
|
export interface StringProperty extends BaseProperty<string> {
|
|
|
|
dataType: "string";
|
|
|
|
/**
|
|
* Configure how this field is displayed
|
|
*/
|
|
config?: StringFieldConfig;
|
|
|
|
/**
|
|
* Rules for validating this property
|
|
*/
|
|
validation?: StringPropertyValidationSchema,
|
|
}
|
|
|
|
export interface ArrayProperty<T = any> extends BaseProperty<T[]> {
|
|
|
|
dataType: "array";
|
|
|
|
/**
|
|
* The property of this array. You can specify any property.
|
|
* You can also specify an array or properties if you need the array to have
|
|
* a specific limited shape such as [string, number, string]
|
|
*/
|
|
of: Property | Property[];
|
|
|
|
/**
|
|
* Rules for validating this property
|
|
*/
|
|
validation?: ArrayPropertyValidationSchema,
|
|
}
|
|
|
|
export interface MapProperty<T = any, P extends Properties = Properties> extends BaseProperty<T> {
|
|
|
|
dataType: "map";
|
|
|
|
/**
|
|
* Record of properties included in this map.
|
|
*/
|
|
properties: P;
|
|
|
|
/**
|
|
* Rules for validating this property
|
|
*/
|
|
validation?: PropertyValidationSchema,
|
|
|
|
/**
|
|
* Properties that need to be rendered when as a preview of this reference
|
|
*/
|
|
previewProperties?: (keyof P)[];
|
|
|
|
|
|
/**
|
|
* Configure how this property field is displayed
|
|
*/
|
|
config?: MapFieldConfig<T>;
|
|
}
|
|
|
|
export interface TimestampProperty extends BaseProperty<firebase.firestore.Timestamp> {
|
|
dataType: "timestamp";
|
|
|
|
/**
|
|
* Rules for validating this property
|
|
*/
|
|
validation?: DatePropertyValidationSchema,
|
|
}
|
|
|
|
// TODO: currently this is the only unsupported field
|
|
export interface GeopointProperty extends BaseProperty<firebase.firestore.GeoPoint> {
|
|
dataType: "geopoint";
|
|
|
|
/**
|
|
* Rules for validating this property
|
|
*/
|
|
validation?: PropertyValidationSchema,
|
|
}
|
|
|
|
export interface ReferenceProperty<S extends EntitySchema = EntitySchema,
|
|
P extends Properties = S["properties"],
|
|
Key extends string = Extract<keyof P, string>>
|
|
extends BaseProperty<firebase.firestore.DocumentReference> {
|
|
|
|
dataType: "reference";
|
|
|
|
/**
|
|
* Absolute collection path.
|
|
*/
|
|
collectionPath: string;
|
|
|
|
/**
|
|
* Schema of the entity this reference points to.
|
|
*/
|
|
schema: S,
|
|
|
|
/**
|
|
* When the dialog for selecting the value of this reference, should
|
|
* a filter be applied to the possible entities.
|
|
*/
|
|
filter?: FilterValues<S>;
|
|
|
|
/**
|
|
* Rules for validating this property
|
|
*/
|
|
validation?: PropertyValidationSchema,
|
|
|
|
/**
|
|
* Properties that need to be rendered when as a preview of this reference
|
|
*/
|
|
previewProperties?: Key[];
|
|
}
|
|
|
|
|
|
/**
|
|
* Used to define filters applied in collections
|
|
*/
|
|
export type FilterValues<S extends EntitySchema> = { [K in keyof Partial<S["properties"]>]: [WhereFilterOp, any] };
|
|
|
|
/**
|
|
* Filter conditions in a `Query.where()` clause are specified using the
|
|
* strings '<', '<=', '==', '>=', '>', 'array-contains', 'in', and 'array-contains-any'.
|
|
*/
|
|
export type WhereFilterOp =
|
|
| "<"
|
|
| "<="
|
|
| "=="
|
|
| ">="
|
|
| ">"
|
|
| "array-contains"
|
|
| "in"
|
|
| "array-contains-any";
|
|
|
|
/**
|
|
* Rules to validate a property
|
|
*/
|
|
export interface PropertyValidationSchema {
|
|
required?: boolean;
|
|
requiredMessage?: string;
|
|
}
|
|
|
|
/**
|
|
* Validation rules for numbers
|
|
*/
|
|
export interface NumberPropertyValidationSchema extends PropertyValidationSchema {
|
|
min?: number;
|
|
max?: number;
|
|
lessThan?: number;
|
|
moreThan?: number;
|
|
positive?: boolean;
|
|
negative?: boolean;
|
|
integer?: boolean;
|
|
}
|
|
|
|
/**
|
|
* Validation rules for strings
|
|
*/
|
|
interface StringPropertyValidationSchema extends PropertyValidationSchema {
|
|
length?: number;
|
|
min?: number;
|
|
max?: number;
|
|
matches?: RegExp;
|
|
email?: boolean;
|
|
url?: boolean;
|
|
trim?: boolean;
|
|
lowercase?: boolean;
|
|
uppercase?: boolean;
|
|
}
|
|
|
|
/**
|
|
* Validation rules for dates
|
|
*/
|
|
interface DatePropertyValidationSchema extends PropertyValidationSchema {
|
|
min?: Date;
|
|
max?: Date;
|
|
}
|
|
|
|
/**
|
|
* Validation rules for arrays
|
|
*/
|
|
interface ArrayPropertyValidationSchema extends PropertyValidationSchema {
|
|
min?: number;
|
|
max?: number;
|
|
}
|
|
|
|
/**
|
|
* Configure how a field is displayed
|
|
*/
|
|
export interface FieldConfig<T> {
|
|
|
|
/**
|
|
* If you need to render a custom field.
|
|
*/
|
|
field?: React.ComponentType<CMSFieldProps<T>>;
|
|
|
|
/**
|
|
* Additional props that are passed to the default field generated by
|
|
* FireCMS or to the custom field
|
|
*/
|
|
fieldProps?: any;
|
|
|
|
/**
|
|
* Whether if this field should take the full width in the field.
|
|
* Defaults to false, but some fields like images take full width by
|
|
* default.
|
|
*/
|
|
forceFullWidth?: boolean;
|
|
|
|
/**
|
|
* Configure how a property is displayed as a preview, e.g. in the collection
|
|
* view
|
|
*/
|
|
customPreview?: React.ComponentType<PreviewComponentProps<T>>;
|
|
}
|
|
|
|
/**
|
|
* Possible configuration fields for a string property. Note that setting one
|
|
* config disables the others.
|
|
*/
|
|
export interface StringFieldConfig extends FieldConfig<string> {
|
|
|
|
/**
|
|
* Is this string property long enough so it should be displayed in
|
|
* a multiple line field. Defaults to false.
|
|
*/
|
|
multiline?: boolean | number;
|
|
|
|
/**
|
|
* Should this string property be displayed as a markdown field
|
|
*/
|
|
markdown?: boolean;
|
|
|
|
/**
|
|
* You can use the enum values providing a map of possible
|
|
* exclusive values the property can take, mapped to the label that it is
|
|
* displayed in the dropdown.
|
|
*/
|
|
enumValues?: EnumValues<string>;
|
|
|
|
/**
|
|
* You can specify a `StorageMeta` configuration. It is used to
|
|
* indicate that this string refers to a path in Google Cloud Storage.
|
|
*/
|
|
storageMeta?: StorageMeta;
|
|
|
|
/**
|
|
* If the value of this property is a URL, we can use the urlMediaType
|
|
* to render the content
|
|
*/
|
|
urlMediaType?: MediaType;
|
|
|
|
}
|
|
|
|
/**
|
|
* Possible configuration fields for a string property. Note that setting one
|
|
* config disables the others.
|
|
*/
|
|
export interface MapFieldConfig<T> extends FieldConfig<T> {
|
|
|
|
/**
|
|
* Allow the user to add only some of the keys in this map.
|
|
* By default all properties of the map have the corresponding field in
|
|
* the form view. Setting this flag to true allows to pick only some.
|
|
* Useful for map that can have a lot of subproperties that may not be
|
|
* needed
|
|
*/
|
|
pickOnlySomeKeys?: boolean;
|
|
|
|
}
|
|
|
|
/**
|
|
* Additional configuration related to Storage related fields
|
|
*/
|
|
export interface StorageMeta {
|
|
|
|
/**
|
|
* Media type of this reference, used for displaying the preview
|
|
*/
|
|
mediaType: MediaType;
|
|
|
|
/**
|
|
* Absolute path in your bucket
|
|
*/
|
|
storagePath: string;
|
|
|
|
/**
|
|
* File MIME types that can be uploaded to this reference
|
|
*/
|
|
acceptedFiles?: StorageFileTypes[];
|
|
|
|
/**
|
|
* Specific metadata set in your uploaded file
|
|
*/
|
|
metadata?: storage.UploadMetadata,
|
|
}
|
|
|
|
/**
|
|
* MIME types for storage fields
|
|
*/
|
|
export type StorageFileTypes =
|
|
"image/*"
|
|
| "video/*"
|
|
| "audio/*"
|
|
| "application/*"
|
|
| "text/*"
|
|
| "font/*" ;
|
|
|
|
export interface NumberFieldConfig extends FieldConfig<number> {
|
|
|
|
/**
|
|
* You can use the enum values providing a map of possible
|
|
* exclusive values the property can take, mapped to the label that it is
|
|
* displayed in the dropdown.
|
|
*/
|
|
enumValues?: EnumValues<number>;
|
|
|
|
}
|