@firecms/core 3.0.0-canary.120 → 3.0.0-canary.121

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.
@@ -87,4 +87,79 @@ export interface StorageSource {
87
87
  * @param bucket
88
88
  */
89
89
  getFile: (path: string, bucket?: string) => Promise<File | null>;
90
+ /**
91
+ * Delete a file.
92
+ * @param path
93
+ * @param bucket
94
+ */
95
+ deleteFile: (path: string, bucket?: string) => Promise<void>;
96
+ /**
97
+ * List the contents of a path.
98
+ * @param path
99
+ * @param options
100
+ */
101
+ list: (path: string, options?: {
102
+ bucket?: string;
103
+ maxResults?: number;
104
+ pageToken?: string;
105
+ }) => Promise<StorageListResult>;
106
+ }
107
+ /**
108
+ * Result returned by list().
109
+ * @public
110
+ */
111
+ export declare interface StorageListResult {
112
+ /**
113
+ * References to prefixes (sub-folders). You can call list() on them to
114
+ * get its contents.
115
+ *
116
+ * Folders are implicit based on '/' in the object paths.
117
+ * For example, if a bucket has two objects '/a/b/1' and '/a/b/2', list('/a')
118
+ * will return '/a/b' as a prefix.
119
+ */
120
+ prefixes: StorageReference[];
121
+ /**
122
+ * Objects in this directory.
123
+ * You can call getMetadata() and getDownloadUrl() on them.
124
+ */
125
+ items: StorageReference[];
126
+ /**
127
+ * If set, there might be more results for this list. Use this token to resume the list.
128
+ */
129
+ nextPageToken?: string;
130
+ }
131
+ /**
132
+ * Represents a reference to a Google Cloud Storage object. Developers can
133
+ * upload, download, and delete objects, as well as get/set object metadata.
134
+ * @public
135
+ */
136
+ export declare interface StorageReference {
137
+ /**
138
+ * Returns a gs:// URL for this object in the form
139
+ * `gs://<bucket>/<path>/<to>/<object>`
140
+ * @returns The gs:// URL.
141
+ */
142
+ toString(): string;
143
+ /**
144
+ * A reference to the root of this object's bucket.
145
+ */
146
+ root: StorageReference;
147
+ /**
148
+ * The name of the bucket containing this reference's object.
149
+ */
150
+ bucket: string;
151
+ /**
152
+ * The full path of this object.
153
+ */
154
+ fullPath: string;
155
+ /**
156
+ * The short name of this object, which is the last component of the full path.
157
+ * For example, if fullPath is 'full/path/image.png', name is 'image.png'.
158
+ */
159
+ name: string;
160
+ /**
161
+ * A reference pointing to the parent location of this reference, or null if
162
+ * this reference is the root.
163
+ */
164
+ parent: StorageReference | null;
90
165
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@firecms/core",
3
3
  "type": "module",
4
- "version": "3.0.0-canary.120",
4
+ "version": "3.0.0-canary.121",
5
5
  "description": "Awesome Firebase/Firestore-based headless open-source CMS",
6
6
  "funding": {
7
7
  "url": "https://github.com/sponsors/firecmsco"
@@ -46,9 +46,9 @@
46
46
  "./package.json": "./package.json"
47
47
  },
48
48
  "dependencies": {
49
- "@firecms/editor": "^3.0.0-canary.120",
50
- "@firecms/formex": "^3.0.0-canary.120",
51
- "@firecms/ui": "^3.0.0-canary.120",
49
+ "@firecms/editor": "^3.0.0-canary.121",
50
+ "@firecms/formex": "^3.0.0-canary.121",
51
+ "@firecms/ui": "^3.0.0-canary.121",
52
52
  "@hello-pangea/dnd": "^16.6.0",
53
53
  "@radix-ui/react-portal": "^1.1.1",
54
54
  "clsx": "^2.1.1",
@@ -100,7 +100,7 @@
100
100
  "dist",
101
101
  "src"
102
102
  ],
103
- "gitHead": "aa817730cb582b5724774a50ac8a512bf0643ef3",
103
+ "gitHead": "b24af22a548699c954b226b60e55001473654948",
104
104
  "publishConfig": {
105
105
  "access": "public"
106
106
  },
@@ -45,22 +45,21 @@ const TableCellInner = ({
45
45
  children
46
46
  }: TableCellInnerProps) => {
47
47
  return (
48
- <div
49
- className={cls("flex flex-col max-h-full w-full",
50
- {
51
- "items-start": faded || scrollable
52
- })}
53
- style={{
54
- justifyContent,
55
- height: fullHeight ? "100%" : undefined,
56
- overflow: scrollable ? "auto" : undefined,
57
- WebkitMaskImage: faded
58
- ? "linear-gradient(to bottom, black 60%, transparent 100%)"
59
- : undefined,
60
- maskImage: faded
61
- ? "linear-gradient(to bottom, black 60%, transparent 100%)"
62
- : undefined
63
- }}
48
+ <div className={cls("flex flex-col max-h-full w-full",
49
+ {
50
+ "items-start": faded || scrollable
51
+ })}
52
+ style={{
53
+ justifyContent,
54
+ height: fullHeight ? "100%" : undefined,
55
+ overflow: scrollable ? "auto" : undefined,
56
+ WebkitMaskImage: faded
57
+ ? "linear-gradient(to bottom, black 60%, transparent 100%)"
58
+ : undefined,
59
+ maskImage: faded
60
+ ? "linear-gradient(to bottom, black 60%, transparent 100%)"
61
+ : undefined
62
+ }}
64
63
  >
65
64
  {children}
66
65
  </div>
@@ -156,8 +155,9 @@ export const EntityTableCell = React.memo<EntityTableCellProps>(
156
155
  }, [ref, onSelect, selected, disabled]);
157
156
 
158
157
  const onFocus = useCallback((event: React.SyntheticEvent<HTMLDivElement>) => {
159
- onSelectCallback();
160
158
  event.stopPropagation();
159
+ event.preventDefault();
160
+ onSelectCallback();
161
161
  }, [onSelectCallback]);
162
162
 
163
163
  const isOverflowing = useMemo(() => {
@@ -203,11 +203,9 @@ export const EntityTableCell = React.memo<EntityTableCellProps>(
203
203
  }}
204
204
  tabIndex={selected || disabled ? undefined : 0}
205
205
  onFocus={onFocus}
206
- // onClick={onClick}
207
206
  onMouseEnter={setOnHoverTrue}
208
207
  onMouseMove={setOnHoverTrue}
209
208
  onMouseLeave={setOnHoverFalse}
210
- // contain={scrollable ? "content" : "size"}
211
209
  >
212
210
 
213
211
  <ErrorBoundary>
@@ -237,7 +235,7 @@ export const EntityTableCell = React.memo<EntityTableCellProps>(
237
235
 
238
236
  {disabled && onHover && disabledTooltip &&
239
237
  <div className="absolute top-1 right-1 text-xs">
240
- <Tooltip title={disabledTooltip} >
238
+ <Tooltip title={disabledTooltip}>
241
239
  <RemoveCircleIcon size={"smallest"} color={"disabled"} className={"text-gray-500"}/>
242
240
  </Tooltip>
243
241
  </div>}
@@ -1,7 +1,17 @@
1
1
  import React, { useState } from "react";
2
2
  import { EnumValuesChip } from "../../../preview";
3
3
  import { VirtualTableWhereFilterOp } from "../../VirtualTable";
4
- import { Checkbox, ClearIcon, IconButton, Label, Select, SelectItem, TextField } from "@firecms/ui";
4
+ import {
5
+ Checkbox,
6
+ ClearIcon,
7
+ IconButton,
8
+ Label,
9
+ MultiSelect,
10
+ MultiSelectItem,
11
+ Select,
12
+ SelectItem,
13
+ TextField
14
+ } from "@firecms/ui";
5
15
  import { EnumValueConfig } from "../../../types";
6
16
 
7
17
  interface StringNumberFilterFieldProps {
@@ -82,6 +92,8 @@ export function StringNumberFilterField({
82
92
  }
83
93
 
84
94
  const multiple = multipleSelectOperations.includes(operation);
95
+
96
+ console.log("internalValue", { internalValue });
85
97
  return (
86
98
 
87
99
  <div className="flex w-[440px]">
@@ -117,17 +129,14 @@ export function StringNumberFilterField({
117
129
  </IconButton>}
118
130
  />}
119
131
 
120
- {enumValues &&
132
+ {enumValues && !multiple &&
121
133
  <Select
122
134
  position={"item-aligned"}
123
- value={internalValue !== undefined
124
- ? (Array.isArray(internalValue) ? internalValue.map(e => String(e)) : String(internalValue))
125
- : isArray ? [] : ""}
135
+ value={typeof internalValue === "string" ? internalValue : ""}
126
136
  onValueChange={(value) => {
127
137
  if (value !== "")
128
138
  updateFilter(operation, dataType === "number" ? parseInt(value as string) : value as string)
129
139
  }}
130
- multiple={multiple}
131
140
  endAdornment={internalValue && <IconButton
132
141
  className="absolute right-2 top-3"
133
142
  onClick={(e) => updateFilter(operation, undefined)}>
@@ -136,6 +145,8 @@ export function StringNumberFilterField({
136
145
  renderValue={(enumKey) => {
137
146
  if (enumKey === null)
138
147
  return "Filter for null values";
148
+ if (enumKey === undefined)
149
+ return null;
139
150
 
140
151
  return <EnumValuesChip
141
152
  key={`select_value_${name}_${enumKey}`}
@@ -144,7 +155,7 @@ export function StringNumberFilterField({
144
155
  size={"small"}/>;
145
156
  }}>
146
157
  {enumValues.map((enumConfig) => (
147
- <SelectItem key={`select_value_${name}_${enumConfig.id}`}
158
+ <SelectItem key={`select_item_${name}_${enumConfig.id}`}
148
159
  value={String(enumConfig.id)}>
149
160
  <EnumValuesChip
150
161
  enumKey={String(enumConfig.id)}
@@ -155,6 +166,43 @@ export function StringNumberFilterField({
155
166
  </Select>
156
167
  }
157
168
 
169
+ {enumValues && multiple &&
170
+ <MultiSelect
171
+ position={"item-aligned"}
172
+ value={Array.isArray(internalValue) ? internalValue.map(e => String(e)) : []}
173
+ onValueChange={(value) => {
174
+ updateFilter(operation, dataType === "number" ? value.map(v => parseInt(v)) : value)
175
+ }}
176
+ multiple={multiple}
177
+ endAdornment={internalValue && <IconButton
178
+ className="absolute right-2 top-3"
179
+ onClick={(e) => updateFilter(operation, undefined)}>
180
+ <ClearIcon/>
181
+ </IconButton>}
182
+ // renderValues={(enumKeys) => {
183
+ // console.log("renderValues", enumKeys);
184
+ // if (enumKeys === null)
185
+ // return "Filter for null values";
186
+ //
187
+ // return enumKeys.map(key => <EnumValuesChip
188
+ // key={`select_value_${name}_${enumKeys}`}
189
+ // enumKey={key}
190
+ // enumValues={enumValues}
191
+ // size={"small"}/>);
192
+ // }}
193
+ >
194
+ {enumValues.map((enumConfig) => (
195
+ <MultiSelectItem key={`select_value_${name}_${enumConfig.id}`}
196
+ value={String(enumConfig.id)}>
197
+ <EnumValuesChip
198
+ enumKey={String(enumConfig.id)}
199
+ enumValues={enumValues}
200
+ size={"small"}/>
201
+ </MultiSelectItem>
202
+ ))}
203
+ </MultiSelect>
204
+ }
205
+
158
206
  {!isArray && <Label
159
207
  className="border cursor-pointer rounded-md p-2 flex items-center gap-2 [&:has(:checked)]:bg-gray-100 dark:[&:has(:checked)]:bg-gray-800"
160
208
  htmlFor="null-filter"
@@ -30,14 +30,6 @@ export const VirtualTableCell = React.memo<VirtualTableCellProps<any>>(
30
30
  width: props.column.width
31
31
  } as CellRendererParams<T>
32
32
  );
33
- // return props.rowData && <props.cellRenderer
34
- // rowData={props.rowData}
35
- // rowIndex={props.rowIndex}
36
- // isScrolling={false}
37
- // column={props.column}
38
- // columns={props.columns}
39
- // columnIndex={props.columnIndex}
40
- // width={props.column.width}/>
41
33
  },
42
34
  (a, b) => {
43
35
  return equal(a.rowData, b.rowData) &&
@@ -1,5 +1,5 @@
1
1
  import { EnumValueConfig } from "../../../types";
2
- import { ArrayEnumPreview, EnumValuesChip } from "../../../preview";
2
+ import { EnumValuesChip } from "../../../preview";
3
3
  import React, { useCallback, useEffect } from "react";
4
4
  import { MultiSelect, MultiSelectItem, Select, SelectItem } from "@firecms/ui";
5
5
 
@@ -18,9 +18,7 @@ export function VirtualTableSelect(props: {
18
18
  }) {
19
19
 
20
20
  const {
21
- name,
22
21
  enumValues,
23
- error,
24
22
  internalValue,
25
23
  disabled,
26
24
  small,
@@ -41,6 +39,7 @@ export function VirtualTableSelect(props: {
41
39
  }, [focused, ref]);
42
40
 
43
41
  const onChange = useCallback((updatedValue: string | string[]) => {
42
+ console.trace("onChange");
44
43
  if (valueType === "number") {
45
44
  if (multiple) {
46
45
  const newValue = (updatedValue as string[]).map((v) => parseFloat(v));
@@ -58,35 +57,27 @@ export function VirtualTableSelect(props: {
58
57
  }
59
58
  }, [multiple, updateValue, valueType]);
60
59
 
61
- const renderValue = (enumKey: string | number, index: number) => {
62
- if (multiple && Array.isArray(enumKey)) {
63
- return <ArrayEnumPreview
64
- key={`${enumKey}-${index}`}
65
- value={enumKey}
66
- name={name}
67
- enumValues={enumValues}
68
- size={small ? "small" : "medium"}/>;
69
- } else {
70
- return <EnumValuesChip
71
- key={`${enumKey}-${index}`}
72
- enumKey={enumKey}
73
- enumValues={enumValues}
74
- size={small ? "small" : "medium"}/>;
75
- }
60
+ const renderValue = (enumKey?: string | number) => {
61
+ return <EnumValuesChip
62
+ key={`${enumKey}`}
63
+ enumKey={String(enumKey)}
64
+ enumValues={enumValues}
65
+ size={small ? "small" : "medium"}/>;
76
66
  };
67
+
77
68
  return (
78
69
  multiple
79
70
  ? <MultiSelect
80
71
  inputRef={ref}
81
- containerClassName="w-full h-full"
82
72
  className="w-full h-full p-0 bg-transparent"
83
73
  position={"item-aligned"}
84
74
  disabled={disabled}
75
+ includeClear={false}
76
+ useChips={false}
85
77
  value={validValue
86
78
  ? ((internalValue as any[]).map(v => v.toString()))
87
79
  : ([])}
88
- onMultiValueChange={onChange}
89
- renderValue={renderValue}>
80
+ onValueChange={onChange}>
90
81
  {enumValues?.map((enumConfig) => (
91
82
  <MultiSelectItem
92
83
  key={enumConfig.id}
@@ -103,14 +94,11 @@ export function VirtualTableSelect(props: {
103
94
  className="w-full h-full p-0 bg-transparent"
104
95
  position={"item-aligned"}
105
96
  disabled={disabled}
106
- multiple={multiple}
107
97
  padding={false}
108
- includeFocusOutline={false}
109
98
  value={validValue
110
- ? (multiple ? (internalValue as any[]).map(v => v.toString()) : internalValue?.toString())
111
- : (multiple ? [] : "")}
99
+ ? internalValue?.toString()
100
+ : ""}
112
101
  onValueChange={onChange}
113
- onMultiValueChange={onChange}
114
102
  renderValue={renderValue}>
115
103
  {enumValues?.map((enumConfig) => (
116
104
  <SelectItem
@@ -9,7 +9,7 @@ import {
9
9
  KeyValueFieldBinding,
10
10
  MapFieldBinding,
11
11
  MarkdownEditorFieldBinding,
12
- MultiSelectBinding,
12
+ MultiSelectFieldBinding,
13
13
  ReferenceFieldBinding,
14
14
  RepeatFieldBinding,
15
15
  SelectFieldBinding,
@@ -139,7 +139,7 @@ export const DEFAULT_FIELD_CONFIGS: Record<string, PropertyConfig<any>> = {
139
139
  dataType: "string",
140
140
  enumValues: [],
141
141
  },
142
- Field: MultiSelectBinding
142
+ Field: MultiSelectFieldBinding
143
143
  }
144
144
  },
145
145
  number_input: {
@@ -177,7 +177,7 @@ export const DEFAULT_FIELD_CONFIGS: Record<string, PropertyConfig<any>> = {
177
177
  dataType: "number",
178
178
  enumValues: [],
179
179
  },
180
- Field: MultiSelectBinding
180
+ Field: MultiSelectFieldBinding
181
181
  }
182
182
  },
183
183
  file_upload: {
@@ -14,18 +14,18 @@ import { useClearRestoreValue } from "../useClearRestoreValue";
14
14
  * and tables to the specified properties.
15
15
  * @group Form fields
16
16
  */
17
- export function MultiSelectBinding({
18
- propertyKey,
19
- value,
20
- setValue,
21
- error,
22
- showError,
23
- disabled,
24
- property,
25
- includeDescription,
26
- size = "medium",
27
- autoFocus
28
- }: FieldProps<EnumType[], any, any>) {
17
+ export function MultiSelectFieldBinding({
18
+ propertyKey,
19
+ value,
20
+ setValue,
21
+ error,
22
+ showError,
23
+ disabled,
24
+ property,
25
+ includeDescription,
26
+ size = "medium",
27
+ autoFocus
28
+ }: FieldProps<EnumType[], any, any>) {
29
29
 
30
30
  const of: ResolvedProperty<any> | ResolvedProperty<any>[] = property.of;
31
31
  if (!of) {
@@ -80,19 +80,20 @@ export function MultiSelectBinding({
80
80
  }, [enumValues, setValue, value]);
81
81
 
82
82
  return (
83
- <div className="mt-0.5 ml-0.5 mt-2">
83
+ <>
84
84
  <MultiSelect
85
+ className={"w-full mt-2"}
85
86
  size={size === "medium" ? "medium" : "small"}
86
87
  value={validValue ? value.map((v) => v.toString()) : []}
87
88
  disabled={disabled}
89
+ modalPopover={true}
88
90
  label={<LabelWithIconAndTooltip
89
91
  propertyKey={propertyKey}
90
92
  icon={getIconForProperty(property, "small")}
91
93
  required={property.validation?.required}
92
94
  title={property.name}
93
95
  className={"text-text-secondary dark:text-text-secondary-dark ml-3.5"}/>}
94
- renderValue={useCallback((v: string) => renderValue(v, false), [renderValue])}
95
- onMultiValueChange={(updatedValue: string[]) => {
96
+ onValueChange={(updatedValue: string[]) => {
96
97
  let newValue: EnumType[] | null;
97
98
  if (of && (of as ResolvedProperty)?.dataType === "number") {
98
99
  newValue = updatedValue ? (updatedValue as string[]).map((e) => parseFloat(e)) : [];
@@ -113,6 +114,6 @@ export function MultiSelectBinding({
113
114
  disabled={disabled}
114
115
  property={property}/>
115
116
 
116
- </div>
117
+ </>
117
118
  );
118
119
  }
@@ -1,5 +1,5 @@
1
1
  import { SelectFieldBinding } from "./field_bindings/SelectFieldBinding";
2
- import { MultiSelectBinding } from "./field_bindings/MultiSelectBinding";
2
+ import { MultiSelectFieldBinding } from "./field_bindings/MultiSelectFieldBinding";
3
3
  import { ArrayOfReferencesFieldBinding } from "./field_bindings/ArrayOfReferencesFieldBinding";
4
4
  import { StorageUploadFieldBinding } from "./field_bindings/StorageUploadFieldBinding";
5
5
  import { TextFieldBinding } from "./field_bindings/TextFieldBinding";
@@ -17,7 +17,7 @@ import { ArrayCustomShapedFieldBinding } from "./field_bindings/ArrayCustomShape
17
17
  export {
18
18
  ArrayCustomShapedFieldBinding,
19
19
  RepeatFieldBinding,
20
- MultiSelectBinding,
20
+ MultiSelectFieldBinding,
21
21
  ArrayOfReferencesFieldBinding,
22
22
  BlockFieldBinding,
23
23
  DateTimeFieldBinding,
@@ -204,15 +204,6 @@ export function useBuildDataSource({
204
204
  const collection = collectionProp ?? navigationController.getCollection(path);
205
205
  const usedDelegate = collection?.overrides?.dataSourceDelegate ?? delegate;
206
206
 
207
- console.log("useBuildDatasource save", {
208
- path,
209
- entityId,
210
- values,
211
- collectionProp,
212
- collection,
213
- status
214
- });
215
-
216
207
  const resolvedCollection = collection
217
208
  ? resolveCollection<M>({
218
209
  collection,
@@ -267,7 +258,10 @@ export function useBuildDataSource({
267
258
  }: DeleteEntityProps<M>
268
259
  ): Promise<void> => {
269
260
  const usedDelegate = collection?.overrides?.dataSourceDelegate ?? delegate;
270
- return usedDelegate.deleteEntity({ entity, collection });
261
+ return usedDelegate.deleteEntity({
262
+ entity,
263
+ collection
264
+ });
271
265
  }, [delegate.deleteEntity]),
272
266
 
273
267
  /**
@@ -102,4 +102,86 @@ export interface StorageSource {
102
102
  * @param bucket
103
103
  */
104
104
  getFile: (path: string, bucket?: string) => Promise<File | null>;
105
+
106
+ /**
107
+ * Delete a file.
108
+ * @param path
109
+ * @param bucket
110
+ */
111
+ deleteFile: (path: string, bucket?: string) => Promise<void>;
112
+
113
+ /**
114
+ * List the contents of a path.
115
+ * @param path
116
+ * @param options
117
+ */
118
+ list: (path: string, options?: {
119
+ bucket?: string,
120
+ maxResults?: number,
121
+ pageToken?: string
122
+ }) => Promise<StorageListResult>;
123
+
124
+ }
125
+
126
+ /**
127
+ * Result returned by list().
128
+ * @public
129
+ */
130
+ export declare interface StorageListResult {
131
+ /**
132
+ * References to prefixes (sub-folders). You can call list() on them to
133
+ * get its contents.
134
+ *
135
+ * Folders are implicit based on '/' in the object paths.
136
+ * For example, if a bucket has two objects '/a/b/1' and '/a/b/2', list('/a')
137
+ * will return '/a/b' as a prefix.
138
+ */
139
+ prefixes: StorageReference[];
140
+ /**
141
+ * Objects in this directory.
142
+ * You can call getMetadata() and getDownloadUrl() on them.
143
+ */
144
+ items: StorageReference[];
145
+ /**
146
+ * If set, there might be more results for this list. Use this token to resume the list.
147
+ */
148
+ nextPageToken?: string;
149
+ }
150
+
151
+ /**
152
+ * Represents a reference to a Google Cloud Storage object. Developers can
153
+ * upload, download, and delete objects, as well as get/set object metadata.
154
+ * @public
155
+ */
156
+ export declare interface StorageReference {
157
+ /**
158
+ * Returns a gs:// URL for this object in the form
159
+ * `gs://<bucket>/<path>/<to>/<object>`
160
+ * @returns The gs:// URL.
161
+ */
162
+ toString(): string;
163
+
164
+ /**
165
+ * A reference to the root of this object's bucket.
166
+ */
167
+ root: StorageReference;
168
+ /**
169
+ * The name of the bucket containing this reference's object.
170
+ */
171
+ bucket: string;
172
+ /**
173
+ * The full path of this object.
174
+ */
175
+ fullPath: string;
176
+ /**
177
+ * The short name of this object, which is the last component of the full path.
178
+ * For example, if fullPath is 'full/path/image.png', name is 'image.png'.
179
+ */
180
+ name: string;
181
+
182
+ /**
183
+ * A reference pointing to the parent location of this reference, or null if
184
+ * this reference is the root.
185
+ */
186
+ parent: StorageReference | null;
105
187
  }