@firecms/core 3.0.0-beta.12 → 3.0.0-beta.14
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/dist/app/Scaffold.d.ts +4 -0
- package/dist/components/ArrayContainer.d.ts +13 -11
- package/dist/components/EntityCollectionView/EntityCollectionView.d.ts +4 -0
- package/dist/components/EntityJsonPreview.d.ts +3 -0
- package/dist/components/EntityPreview.d.ts +4 -2
- package/dist/components/SelectableTable/SelectableTable.d.ts +1 -1
- package/dist/components/VirtualTable/VirtualTableProps.d.ts +0 -4
- package/dist/components/index.d.ts +1 -0
- package/dist/core/DefaultAppBar.d.ts +8 -2
- package/dist/core/EntityEditView.d.ts +8 -1
- package/dist/core/FireCMS.d.ts +2 -2
- package/dist/form/EntityForm.d.ts +5 -1
- package/dist/form/PropertyFieldBinding.d.ts +1 -1
- package/dist/form/field_bindings/MapFieldBinding.d.ts +1 -1
- package/dist/form/field_bindings/StorageUploadFieldBinding.d.ts +3 -9
- package/dist/hooks/data/save.d.ts +1 -1
- package/dist/hooks/useBuildNavigationController.d.ts +2 -9
- package/dist/index.es.js +9251 -8721
- package/dist/index.es.js.map +1 -1
- package/dist/index.umd.js +16686 -16160
- package/dist/index.umd.js.map +1 -1
- package/dist/internal/useBuildDataSource.d.ts +3 -2
- package/dist/internal/useBuildSideEntityController.d.ts +3 -3
- package/dist/types/collections.d.ts +26 -2
- package/dist/types/fields.d.ts +4 -1
- package/dist/types/firecms.d.ts +3 -2
- package/dist/types/navigation.d.ts +9 -0
- package/dist/types/plugins.d.ts +13 -0
- package/dist/types/properties.d.ts +22 -1
- package/dist/types/side_entity_controller.d.ts +4 -0
- package/dist/util/callbacks.d.ts +2 -0
- package/dist/util/createFormexStub.d.ts +2 -0
- package/dist/util/index.d.ts +1 -0
- package/dist/util/navigation_utils.d.ts +2 -1
- package/dist/util/objects.d.ts +1 -1
- package/dist/util/property_utils.d.ts +2 -2
- package/dist/util/references.d.ts +2 -2
- package/dist/util/resolutions.d.ts +11 -6
- package/package.json +9 -6
- package/src/app/Scaffold.tsx +13 -3
- package/src/components/ArrayContainer.tsx +414 -282
- package/src/components/ClearFilterSortButton.tsx +1 -1
- package/src/components/ConfirmationDialog.tsx +9 -9
- package/src/components/DeleteEntityDialog.tsx +4 -2
- package/src/components/EntityCollectionTable/EntityCollectionRowActions.tsx +8 -6
- package/src/components/EntityCollectionTable/PropertyTableCell.tsx +1 -1
- package/src/components/EntityCollectionTable/internal/CollectionTableToolbar.tsx +1 -1
- package/src/components/EntityCollectionTable/internal/EntityTableCell.tsx +13 -2
- package/src/components/EntityCollectionTable/internal/popup_field/PopupFormField.tsx +58 -29
- package/src/components/EntityCollectionTable/internal/popup_field/useDraggable.tsx +9 -9
- package/src/components/EntityCollectionView/EntityCollectionView.tsx +26 -6
- package/src/components/EntityJsonPreview.tsx +66 -0
- package/src/components/EntityPreview.tsx +27 -16
- package/src/components/EntityView.tsx +4 -1
- package/src/components/ErrorView.tsx +1 -1
- package/src/components/HomePage/DefaultHomePage.tsx +2 -1
- package/src/components/HomePage/NavigationCardBinding.tsx +3 -1
- package/src/components/ReferenceTable/ReferenceSelectionTable.tsx +3 -1
- package/src/components/SelectableTable/SelectableTable.tsx +140 -143
- package/src/components/VirtualTable/VirtualTable.tsx +37 -31
- package/src/components/VirtualTable/VirtualTableProps.tsx +0 -5
- package/src/components/VirtualTable/fields/VirtualTableInput.tsx +0 -1
- package/src/components/common/default_entity_actions.tsx +15 -4
- package/src/components/index.tsx +2 -0
- package/src/core/DefaultAppBar.tsx +17 -5
- package/src/core/EntityEditView.tsx +135 -47
- package/src/core/EntitySidePanel.tsx +15 -20
- package/src/core/FireCMS.tsx +19 -12
- package/src/form/EntityForm.tsx +32 -24
- package/src/form/PropertyFieldBinding.tsx +8 -6
- package/src/form/components/CustomIdField.tsx +3 -1
- package/src/form/components/LabelWithIcon.tsx +1 -1
- package/src/form/field_bindings/ArrayCustomShapedFieldBinding.tsx +4 -1
- package/src/form/field_bindings/ArrayOfReferencesFieldBinding.tsx +4 -2
- package/src/form/field_bindings/BlockFieldBinding.tsx +1 -1
- package/src/form/field_bindings/KeyValueFieldBinding.tsx +1 -1
- package/src/form/field_bindings/MapFieldBinding.tsx +5 -4
- package/src/form/field_bindings/MarkdownEditorFieldBinding.tsx +6 -2
- package/src/form/field_bindings/MultiSelectFieldBinding.tsx +3 -3
- package/src/form/field_bindings/RepeatFieldBinding.tsx +9 -2
- package/src/form/field_bindings/SelectFieldBinding.tsx +3 -2
- package/src/form/field_bindings/StorageUploadFieldBinding.tsx +227 -156
- package/src/hooks/data/save.ts +24 -35
- package/src/hooks/useBuildNavigationController.tsx +42 -17
- package/src/hooks/useFireCMSContext.tsx +0 -30
- package/src/internal/useBuildDataSource.ts +9 -5
- package/src/internal/useBuildSideEntityController.tsx +26 -20
- package/src/preview/PropertyPreview.tsx +4 -2
- package/src/preview/components/ImagePreview.tsx +2 -2
- package/src/preview/components/ReferencePreview.tsx +1 -1
- package/src/preview/property_previews/ArrayOfMapsPreview.tsx +5 -4
- package/src/preview/property_previews/ArrayOfReferencesPreview.tsx +4 -3
- package/src/preview/property_previews/ArrayOfStorageComponentsPreview.tsx +4 -2
- package/src/preview/property_previews/ArrayOfStringsPreview.tsx +4 -3
- package/src/preview/property_previews/ArrayOneOfPreview.tsx +4 -2
- package/src/preview/property_previews/ArrayPropertyPreview.tsx +4 -2
- package/src/preview/property_previews/MapPropertyPreview.tsx +1 -1
- package/src/preview/property_previews/SkeletonPropertyComponent.tsx +1 -1
- package/src/preview/property_previews/StringPropertyPreview.tsx +1 -1
- package/src/routes/FireCMSRoute.tsx +15 -4
- package/src/types/collections.ts +31 -2
- package/src/types/fields.tsx +5 -1
- package/src/types/firecms.tsx +3 -3
- package/src/types/navigation.ts +11 -0
- package/src/types/plugins.tsx +17 -0
- package/src/types/properties.ts +27 -1
- package/src/types/side_entity_controller.tsx +5 -0
- package/src/util/callbacks.ts +119 -0
- package/src/util/createFormexStub.tsx +62 -0
- package/src/util/index.ts +1 -0
- package/src/util/join_collections.ts +3 -1
- package/src/util/navigation_from_path.ts +5 -1
- package/src/util/navigation_utils.ts +84 -20
- package/src/util/objects.ts +54 -17
- package/src/util/property_utils.tsx +7 -3
- package/src/util/references.ts +8 -6
- package/src/util/resolutions.ts +17 -9
- package/src/util/useStorageUploadController.tsx +21 -2
- package/dist/components/EntityCollectionTable/internal/popup_field/ElementResizeListener.d.ts +0 -5
- package/src/components/EntityCollectionTable/internal/popup_field/ElementResizeListener.tsx +0 -59
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import React, { useCallback } from "react";
|
|
1
|
+
import React, { useCallback, useState } from "react";
|
|
2
2
|
|
|
3
3
|
import {
|
|
4
|
-
ArrayProperty,
|
|
5
4
|
FieldProps,
|
|
6
5
|
PropertyOrBuilder,
|
|
7
6
|
ResolvedArrayProperty,
|
|
@@ -13,8 +12,24 @@ import { PreviewSize } from "../../preview";
|
|
|
13
12
|
import { FieldHelperText, LabelWithIconAndTooltip } from "../components";
|
|
14
13
|
|
|
15
14
|
import { getIconForProperty, isReadOnly, resolveProperty } from "../../util";
|
|
16
|
-
import { useSnackbarController, useStorageSource } from "../../hooks";
|
|
17
|
-
import {
|
|
15
|
+
import { useAuthController, useSnackbarController, useStorageSource } from "../../hooks";
|
|
16
|
+
import {
|
|
17
|
+
closestCenter,
|
|
18
|
+
DndContext,
|
|
19
|
+
DragEndEvent,
|
|
20
|
+
DragStartEvent,
|
|
21
|
+
KeyboardSensor,
|
|
22
|
+
PointerSensor,
|
|
23
|
+
useSensor,
|
|
24
|
+
useSensors
|
|
25
|
+
} from "@dnd-kit/core";
|
|
26
|
+
import {
|
|
27
|
+
horizontalListSortingStrategy,
|
|
28
|
+
SortableContext,
|
|
29
|
+
sortableKeyboardCoordinates,
|
|
30
|
+
useSortable
|
|
31
|
+
} from "@dnd-kit/sortable";
|
|
32
|
+
import { CSS } from "@dnd-kit/utilities";
|
|
18
33
|
import { StorageFieldItem, useStorageUploadController } from "../../util/useStorageUploadController";
|
|
19
34
|
import { StorageUploadProgress } from "../components/StorageUploadProgress";
|
|
20
35
|
import { StorageItemPreview } from "../components/StorageItemPreview";
|
|
@@ -36,13 +51,6 @@ const rejectDropClasses = "transition-colors duration-200 ease-[cubic-bezier(0,0
|
|
|
36
51
|
|
|
37
52
|
type StorageUploadFieldProps = FieldProps<string | string[]>;
|
|
38
53
|
|
|
39
|
-
/**
|
|
40
|
-
* Field that allows to upload files to Google Cloud Storage.
|
|
41
|
-
*
|
|
42
|
-
* This is one of the internal components that get mapped natively inside forms
|
|
43
|
-
* and tables to the specified properties.
|
|
44
|
-
* @group Form fields
|
|
45
|
-
*/
|
|
46
54
|
export function StorageUploadFieldBinding({
|
|
47
55
|
propertyKey,
|
|
48
56
|
value,
|
|
@@ -57,11 +65,13 @@ export function StorageUploadFieldBinding({
|
|
|
57
65
|
isSubmitting,
|
|
58
66
|
}: StorageUploadFieldProps) {
|
|
59
67
|
|
|
68
|
+
const authController = useAuthController();
|
|
69
|
+
|
|
60
70
|
if (!context.entityId)
|
|
61
71
|
throw new Error("StorageUploadFieldBinding: Entity id is null");
|
|
62
72
|
|
|
63
73
|
const storageSource = useStorageSource(context.collection);
|
|
64
|
-
const disabled = isReadOnly(property) || !!property.disabled || isSubmitting;
|
|
74
|
+
const disabled = isReadOnly(property) || !!property.disabled || isSubmitting || context.disabled;
|
|
65
75
|
|
|
66
76
|
const {
|
|
67
77
|
internalValue,
|
|
@@ -91,6 +101,7 @@ export function StorageUploadFieldBinding({
|
|
|
91
101
|
|
|
92
102
|
const resolvedProperty = resolveProperty({
|
|
93
103
|
propertyOrBuilder: property as PropertyOrBuilder,
|
|
104
|
+
authController
|
|
94
105
|
}) as ResolvedStringProperty | ResolvedArrayProperty<string[]>;
|
|
95
106
|
|
|
96
107
|
return (
|
|
@@ -129,13 +140,104 @@ export function StorageUploadFieldBinding({
|
|
|
129
140
|
);
|
|
130
141
|
}
|
|
131
142
|
|
|
143
|
+
interface SortableStorageItemProps {
|
|
144
|
+
id: number;
|
|
145
|
+
entry: StorageFieldItem;
|
|
146
|
+
property: ResolvedStringProperty;
|
|
147
|
+
name: string;
|
|
148
|
+
metadata?: Record<string, unknown>;
|
|
149
|
+
storagePathBuilder: (file: File) => string;
|
|
150
|
+
onFileUploadComplete: (uploadedPath: string, entry: StorageFieldItem, fileMetadata?: any) => Promise<void>;
|
|
151
|
+
onClear: (clearedStoragePathOrDownloadUrl: string) => void;
|
|
152
|
+
disabled: boolean;
|
|
153
|
+
isSortable: boolean; // Kept for consistency, though dnd-kit handles sortability via context
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function SortableStorageItem({
|
|
157
|
+
id,
|
|
158
|
+
entry,
|
|
159
|
+
property,
|
|
160
|
+
name,
|
|
161
|
+
metadata,
|
|
162
|
+
storagePathBuilder,
|
|
163
|
+
onFileUploadComplete,
|
|
164
|
+
onClear,
|
|
165
|
+
disabled,
|
|
166
|
+
isSortable // This prop might be redundant if SortableContext is always used for multiple items
|
|
167
|
+
}: SortableStorageItemProps) {
|
|
168
|
+
|
|
169
|
+
const {
|
|
170
|
+
attributes,
|
|
171
|
+
listeners,
|
|
172
|
+
setNodeRef,
|
|
173
|
+
transform,
|
|
174
|
+
transition,
|
|
175
|
+
isDragging
|
|
176
|
+
} = useSortable({ id });
|
|
177
|
+
|
|
178
|
+
const style: React.CSSProperties = {
|
|
179
|
+
transform: CSS.Transform.toString(transform),
|
|
180
|
+
transition,
|
|
181
|
+
zIndex: isDragging ? 100 : undefined, // Higher z-index when dragging
|
|
182
|
+
opacity: isDragging ? 0.8 : 1 // Slight opacity for dragged item
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
const getImageSizeNumber = (previewSize: PreviewSize): number => {
|
|
186
|
+
switch (previewSize) {
|
|
187
|
+
case "small":
|
|
188
|
+
return 40;
|
|
189
|
+
case "medium":
|
|
190
|
+
return 118; // As per original logic for multiple items
|
|
191
|
+
case "large":
|
|
192
|
+
return 220; // As per original logic for single item
|
|
193
|
+
default:
|
|
194
|
+
return 118;
|
|
195
|
+
}
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
let child: React.ReactNode;
|
|
199
|
+
if (entry.storagePathOrDownloadUrl) {
|
|
200
|
+
child = (
|
|
201
|
+
<StorageItemPreview
|
|
202
|
+
name={`storage_preview_${entry.storagePathOrDownloadUrl}`}
|
|
203
|
+
property={property}
|
|
204
|
+
disabled={disabled}
|
|
205
|
+
value={entry.storagePathOrDownloadUrl}
|
|
206
|
+
onRemove={() => onClear(entry.storagePathOrDownloadUrl!)}
|
|
207
|
+
size={entry.size}/>
|
|
208
|
+
);
|
|
209
|
+
} else if (entry.file) {
|
|
210
|
+
child = (
|
|
211
|
+
<StorageUploadProgress
|
|
212
|
+
entry={entry}
|
|
213
|
+
metadata={metadata}
|
|
214
|
+
storagePath={storagePathBuilder(entry.file)}
|
|
215
|
+
onFileUploadComplete={onFileUploadComplete}
|
|
216
|
+
imageSize={getImageSizeNumber(entry.size)}
|
|
217
|
+
simple={false}
|
|
218
|
+
/>
|
|
219
|
+
);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
return (
|
|
223
|
+
<div
|
|
224
|
+
ref={setNodeRef}
|
|
225
|
+
style={style}
|
|
226
|
+
{...attributes}
|
|
227
|
+
{...listeners}
|
|
228
|
+
className={cls("rounded-md m-1")} // Added margin for spacing between items
|
|
229
|
+
tabIndex={-1}
|
|
230
|
+
>
|
|
231
|
+
{child}
|
|
232
|
+
</div>
|
|
233
|
+
);
|
|
234
|
+
}
|
|
235
|
+
|
|
132
236
|
function FileDropComponent({
|
|
133
237
|
storage,
|
|
134
238
|
disabled,
|
|
135
|
-
isDraggingOver,
|
|
136
239
|
onFilesAdded,
|
|
137
240
|
multipleFilesSupported,
|
|
138
|
-
droppableProvided,
|
|
139
241
|
autoFocus,
|
|
140
242
|
internalValue,
|
|
141
243
|
property,
|
|
@@ -143,26 +245,24 @@ function FileDropComponent({
|
|
|
143
245
|
metadata,
|
|
144
246
|
storagePathBuilder,
|
|
145
247
|
onFileUploadComplete,
|
|
146
|
-
size,
|
|
147
248
|
name,
|
|
148
|
-
helpText
|
|
249
|
+
helpText,
|
|
250
|
+
isDndItemDragging // New prop to disable dropzone when internal D&D is active
|
|
149
251
|
}: {
|
|
150
252
|
storage: StorageConfig,
|
|
151
253
|
disabled: boolean,
|
|
152
|
-
|
|
153
|
-
droppableProvided: any,
|
|
154
|
-
onFilesAdded: (acceptedFiles: File[]) => void,
|
|
254
|
+
onFilesAdded: (acceptedFiles: File[]) => Promise<void>, // useStorageUploadController returns Promise<void>
|
|
155
255
|
multipleFilesSupported: boolean,
|
|
156
256
|
autoFocus: boolean,
|
|
157
257
|
internalValue: StorageFieldItem[],
|
|
158
258
|
property: ResolvedStringProperty,
|
|
159
259
|
onClear: (clearedStoragePathOrDownloadUrl: string) => void,
|
|
160
|
-
metadata
|
|
260
|
+
metadata?: any,
|
|
161
261
|
storagePathBuilder: (file: File) => string,
|
|
162
262
|
onFileUploadComplete: (uploadedPath: string, entry: StorageFieldItem, fileMetadata?: any) => Promise<void>,
|
|
163
|
-
size: PreviewSize,
|
|
164
263
|
name: string,
|
|
165
|
-
helpText: string
|
|
264
|
+
helpText: string,
|
|
265
|
+
isDndItemDragging?: boolean
|
|
166
266
|
}) {
|
|
167
267
|
|
|
168
268
|
const snackbarContext = useSnackbarController();
|
|
@@ -170,16 +270,19 @@ function FileDropComponent({
|
|
|
170
270
|
const {
|
|
171
271
|
getRootProps,
|
|
172
272
|
getInputProps,
|
|
173
|
-
isDragActive,
|
|
273
|
+
isDragActive, // This is for files dragged from OS
|
|
174
274
|
isDragAccept,
|
|
175
275
|
isDragReject
|
|
176
276
|
} = useDropzone({
|
|
177
|
-
accept: storage.acceptedFiles ? storage.acceptedFiles.
|
|
178
|
-
|
|
277
|
+
accept: storage.acceptedFiles ? storage.acceptedFiles.reduce((acc, ext) => ({
|
|
278
|
+
...acc,
|
|
279
|
+
[ext]: []
|
|
280
|
+
}), {}) : undefined,
|
|
281
|
+
disabled: disabled || isDndItemDragging, // Disable if form field is disabled OR an internal item is being dragged
|
|
179
282
|
noDragEventsBubbling: true,
|
|
180
283
|
maxSize: storage.maxSize,
|
|
181
284
|
onDrop: onFilesAdded,
|
|
182
|
-
onDropRejected: (fileRejections
|
|
285
|
+
onDropRejected: (fileRejections) => {
|
|
183
286
|
for (const fileRejection of fileRejections) {
|
|
184
287
|
for (const error of fileRejection.errors) {
|
|
185
288
|
console.error("Error uploading file: ", error);
|
|
@@ -208,79 +311,44 @@ function FileDropComponent({
|
|
|
208
311
|
disabled ? fieldBackgroundDisabledMixin : fieldBackgroundHoverMixin,
|
|
209
312
|
disabled ? "text-surface-accent-600 dark:text-surface-accent-500" : "",
|
|
210
313
|
dropZoneClasses,
|
|
211
|
-
multipleFilesSupported && internalValue.length
|
|
314
|
+
multipleFilesSupported && internalValue.length === 0 && "flex", // Keep flex for empty state centering
|
|
212
315
|
{
|
|
213
316
|
[nonActiveDropClasses]: !isDragActive,
|
|
214
|
-
[activeDropClasses]: isDragActive,
|
|
215
|
-
[rejectDropClasses]: isDragReject,
|
|
216
|
-
[acceptDropClasses]: isDragAccept,
|
|
217
|
-
[disabledClasses]: disabled
|
|
317
|
+
[activeDropClasses]: isDragActive, // OS file drag active
|
|
318
|
+
[rejectDropClasses]: isDragReject, // OS file drag reject
|
|
319
|
+
[acceptDropClasses]: isDragAccept, // OS file drag accept
|
|
320
|
+
[disabledClasses]: disabled || isDndItemDragging // Visually disable if internal drag
|
|
218
321
|
})}
|
|
219
322
|
>
|
|
220
323
|
<div
|
|
221
|
-
{...droppableProvided.droppableProps}
|
|
222
|
-
ref={droppableProvided.innerRef}
|
|
223
324
|
className={cls("flex items-center p-1 no-scrollbar",
|
|
224
|
-
multipleFilesSupported && internalValue.length ? "overflow-auto" : "",
|
|
225
|
-
|
|
325
|
+
multipleFilesSupported && internalValue.length ? "flex-row overflow-x-auto" : "flex-col", // flex-col for single or empty
|
|
326
|
+
internalValue.length === 0 && "min-h-[250px] justify-center", // Centering for empty dropzone
|
|
327
|
+
multipleFilesSupported && internalValue.length > 0 && "min-h-[180px]", // Min height for multiple items
|
|
328
|
+
!multipleFilesSupported && internalValue.length > 0 && "min-h-[250px]" // Min height for single item
|
|
226
329
|
)}
|
|
227
330
|
>
|
|
228
|
-
|
|
229
331
|
<input
|
|
230
332
|
autoFocus={autoFocus}
|
|
231
333
|
{...getInputProps()} />
|
|
232
334
|
|
|
233
|
-
{internalValue.map((entry
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
storagePath={storagePathBuilder(entry.file)}
|
|
251
|
-
onFileUploadComplete={onFileUploadComplete}
|
|
252
|
-
imageSize={size === "large" ? 220 : 118}
|
|
253
|
-
simple={false}
|
|
254
|
-
/>
|
|
255
|
-
);
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
return (
|
|
259
|
-
<Draggable
|
|
260
|
-
key={`array_field_${name}_${entry.id}`}
|
|
261
|
-
draggableId={`array_field_${name}_${entry.id}`}
|
|
262
|
-
index={index}>
|
|
263
|
-
{(provided, snapshot) => (
|
|
264
|
-
<div
|
|
265
|
-
tabIndex={-1}
|
|
266
|
-
ref={provided.innerRef}
|
|
267
|
-
{...provided.draggableProps}
|
|
268
|
-
{...provided.dragHandleProps}
|
|
269
|
-
className={cls("rounded-md")}
|
|
270
|
-
style={{
|
|
271
|
-
...provided.draggableProps.style
|
|
272
|
-
}}
|
|
273
|
-
>
|
|
274
|
-
{child}
|
|
275
|
-
</div>
|
|
276
|
-
)}
|
|
277
|
-
</Draggable>
|
|
278
|
-
);
|
|
279
|
-
})
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
{droppableProvided.placeholder}
|
|
283
|
-
|
|
335
|
+
{internalValue.map((entry) => (
|
|
336
|
+
<SortableStorageItem
|
|
337
|
+
key={entry.id}
|
|
338
|
+
id={entry.id}
|
|
339
|
+
entry={entry}
|
|
340
|
+
property={property}
|
|
341
|
+
name={name}
|
|
342
|
+
metadata={metadata}
|
|
343
|
+
storagePathBuilder={storagePathBuilder}
|
|
344
|
+
onFileUploadComplete={onFileUploadComplete}
|
|
345
|
+
onClear={onClear}
|
|
346
|
+
disabled={disabled}
|
|
347
|
+
isSortable={multipleFilesSupported}
|
|
348
|
+
/>
|
|
349
|
+
))}
|
|
350
|
+
|
|
351
|
+
{/* Placeholder for empty dropzone text is handled by the outer Typography */}
|
|
284
352
|
</div>
|
|
285
353
|
|
|
286
354
|
<div
|
|
@@ -291,7 +359,6 @@ function FileDropComponent({
|
|
|
291
359
|
{helpText}
|
|
292
360
|
</Typography>
|
|
293
361
|
</div>
|
|
294
|
-
|
|
295
362
|
</div>
|
|
296
363
|
);
|
|
297
364
|
}
|
|
@@ -306,7 +373,7 @@ export interface StorageUploadProps {
|
|
|
306
373
|
autoFocus: boolean;
|
|
307
374
|
disabled: boolean;
|
|
308
375
|
storage: StorageConfig;
|
|
309
|
-
onFilesAdded: (acceptedFiles: File[]) => void
|
|
376
|
+
onFilesAdded: (acceptedFiles: File[]) => Promise<void>; // Updated from useStorageUploadController
|
|
310
377
|
storagePathBuilder: (file: File) => string;
|
|
311
378
|
onFileUploadComplete: (uploadedPath: string, entry: StorageFieldItem, fileMetadata?: any) => Promise<void>;
|
|
312
379
|
}
|
|
@@ -314,7 +381,7 @@ export interface StorageUploadProps {
|
|
|
314
381
|
export function StorageUpload({
|
|
315
382
|
property,
|
|
316
383
|
name,
|
|
317
|
-
value,
|
|
384
|
+
value, // This is internalValue from useStorageUploadController
|
|
318
385
|
setInternalValue,
|
|
319
386
|
onChange,
|
|
320
387
|
multipleFilesSupported,
|
|
@@ -341,10 +408,10 @@ export function StorageUpload({
|
|
|
341
408
|
}
|
|
342
409
|
|
|
343
410
|
const metadata: Record<string, unknown> | undefined = storage?.metadata;
|
|
344
|
-
const
|
|
411
|
+
const [isDndItemDragging, setIsDndItemDragging] = useState(false);
|
|
345
412
|
|
|
346
413
|
const moveItem = useCallback((fromIndex: number, toIndex: number) => {
|
|
347
|
-
if (!multipleFilesSupported) return;
|
|
414
|
+
if (!multipleFilesSupported || fromIndex === toIndex) return;
|
|
348
415
|
const newValue = [...value];
|
|
349
416
|
const item = newValue[fromIndex];
|
|
350
417
|
newValue.splice(fromIndex, 1);
|
|
@@ -356,84 +423,88 @@ export function StorageUpload({
|
|
|
356
423
|
onChange(fieldValue);
|
|
357
424
|
}, [multipleFilesSupported, onChange, setInternalValue, value]);
|
|
358
425
|
|
|
359
|
-
const
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
426
|
+
const sensors = useSensors(
|
|
427
|
+
useSensor(PointerSensor, {
|
|
428
|
+
activationConstraint: {
|
|
429
|
+
distance: 5, // Start dragging after 5px movement
|
|
430
|
+
},
|
|
431
|
+
}),
|
|
432
|
+
useSensor(KeyboardSensor, {
|
|
433
|
+
coordinateGetter: sortableKeyboardCoordinates,
|
|
434
|
+
})
|
|
435
|
+
);
|
|
366
436
|
|
|
367
|
-
|
|
437
|
+
const handleDragStart = useCallback((event: DragStartEvent) => {
|
|
438
|
+
setIsDndItemDragging(true);
|
|
439
|
+
}, []);
|
|
440
|
+
|
|
441
|
+
const handleDragEnd = useCallback((event: DragEndEvent) => {
|
|
442
|
+
setIsDndItemDragging(false);
|
|
443
|
+
const {
|
|
444
|
+
active,
|
|
445
|
+
over
|
|
446
|
+
} = event;
|
|
447
|
+
if (over && active.id !== over.id) {
|
|
448
|
+
const oldIndex = value.findIndex(item => item.id === active.id);
|
|
449
|
+
const newIndex = value.findIndex(item => item.id === over.id);
|
|
450
|
+
if (oldIndex !== -1 && newIndex !== -1) {
|
|
451
|
+
moveItem(oldIndex, newIndex);
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
}, [value, moveItem]);
|
|
368
455
|
|
|
369
456
|
const onClear = useCallback((clearedStoragePathOrDownloadUrl: string) => {
|
|
457
|
+
let newValue: StorageFieldItem[];
|
|
370
458
|
if (multipleFilesSupported) {
|
|
371
|
-
|
|
459
|
+
newValue = value.filter(v => v.storagePathOrDownloadUrl !== clearedStoragePathOrDownloadUrl);
|
|
372
460
|
onChange(newValue.filter(v => !!v.storagePathOrDownloadUrl).map(v => v.storagePathOrDownloadUrl as string));
|
|
373
|
-
setInternalValue(newValue);
|
|
374
461
|
} else {
|
|
462
|
+
newValue = [];
|
|
375
463
|
onChange(null);
|
|
376
|
-
setInternalValue([]);
|
|
377
464
|
}
|
|
378
|
-
|
|
465
|
+
setInternalValue(newValue);
|
|
466
|
+
}, [value, multipleFilesSupported, onChange, setInternalValue]);
|
|
379
467
|
|
|
380
468
|
const helpText = multipleFilesSupported
|
|
381
|
-
? "Drag 'n' drop some files here, or click to select files"
|
|
469
|
+
? "Drag 'n' drop some files here, or click to select files. Drag to reorder."
|
|
382
470
|
: "Drag 'n' drop a file here, or click to select one";
|
|
383
471
|
|
|
384
472
|
const renderProperty: ResolvedStringProperty = multipleFilesSupported
|
|
385
|
-
? (property as
|
|
473
|
+
? (property as ResolvedArrayProperty<string[]>).of as ResolvedStringProperty
|
|
386
474
|
: property as ResolvedStringProperty;
|
|
387
475
|
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
>
|
|
405
|
-
<StorageItemPreview
|
|
406
|
-
name={`storage_preview_${entry.storagePathOrDownloadUrl}`}
|
|
407
|
-
placeholder={true}
|
|
408
|
-
property={renderProperty}
|
|
409
|
-
disabled={true}
|
|
410
|
-
value={entry.storagePathOrDownloadUrl as string}
|
|
411
|
-
onRemove={onClear}
|
|
412
|
-
size={entry.size}/>
|
|
413
|
-
</div>
|
|
414
|
-
);
|
|
415
|
-
}}
|
|
416
|
-
>
|
|
417
|
-
{(provided, snapshot) => {
|
|
418
|
-
return <FileDropComponent storage={storage}
|
|
419
|
-
disabled={disabled}
|
|
420
|
-
isDraggingOver={snapshot.isDraggingOver}
|
|
421
|
-
droppableProvided={provided}
|
|
422
|
-
onFilesAdded={onFilesAdded}
|
|
423
|
-
multipleFilesSupported={multipleFilesSupported}
|
|
424
|
-
autoFocus={autoFocus}
|
|
425
|
-
internalValue={value}
|
|
426
|
-
property={renderProperty}
|
|
427
|
-
onClear={onClear}
|
|
428
|
-
metadata={metadata}
|
|
429
|
-
storagePathBuilder={storagePathBuilder}
|
|
430
|
-
onFileUploadComplete={onFileUploadComplete}
|
|
431
|
-
size={size}
|
|
432
|
-
name={name}
|
|
433
|
-
helpText={helpText}/>
|
|
434
|
-
}}
|
|
435
|
-
</Droppable>
|
|
436
|
-
</DragDropContext>
|
|
437
|
-
);
|
|
476
|
+
const fileDropProps = {
|
|
477
|
+
storage,
|
|
478
|
+
disabled,
|
|
479
|
+
onFilesAdded,
|
|
480
|
+
multipleFilesSupported,
|
|
481
|
+
autoFocus,
|
|
482
|
+
internalValue: value, // Pass current internalValue
|
|
483
|
+
property: renderProperty,
|
|
484
|
+
onClear,
|
|
485
|
+
metadata,
|
|
486
|
+
storagePathBuilder,
|
|
487
|
+
onFileUploadComplete,
|
|
488
|
+
name,
|
|
489
|
+
helpText,
|
|
490
|
+
isDndItemDragging // Pass this down
|
|
491
|
+
};
|
|
438
492
|
|
|
493
|
+
if (multipleFilesSupported) {
|
|
494
|
+
return (
|
|
495
|
+
<DndContext
|
|
496
|
+
sensors={sensors}
|
|
497
|
+
collisionDetection={closestCenter}
|
|
498
|
+
onDragStart={handleDragStart}
|
|
499
|
+
onDragEnd={handleDragEnd}
|
|
500
|
+
>
|
|
501
|
+
<SortableContext items={value.map(v => v.id)} strategy={horizontalListSortingStrategy}>
|
|
502
|
+
<FileDropComponent {...fileDropProps} />
|
|
503
|
+
</SortableContext>
|
|
504
|
+
</DndContext>
|
|
505
|
+
);
|
|
506
|
+
} else {
|
|
507
|
+
// For single file, no D&D context is needed
|
|
508
|
+
return <FileDropComponent {...fileDropProps} isDndItemDragging={false}/>;
|
|
509
|
+
}
|
|
439
510
|
}
|
package/src/hooks/data/save.ts
CHANGED
|
@@ -1,12 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
DataSource,
|
|
3
|
-
Entity,
|
|
4
|
-
EntityCollection,
|
|
5
|
-
EntityValues,
|
|
6
|
-
FireCMSContext,
|
|
7
|
-
SaveEntityProps,
|
|
8
|
-
User
|
|
9
|
-
} from "../../types";
|
|
1
|
+
import { DataSource, Entity, EntityCollection, EntityValues, FireCMSContext, SaveEntityProps, User } from "../../types";
|
|
10
2
|
import { useDataSource } from "./useDataSource";
|
|
11
3
|
import { resolveCollection } from "../../util";
|
|
12
4
|
|
|
@@ -49,23 +41,23 @@ export type SaveEntityWithCallbacksProps<M extends Record<string, any>> =
|
|
|
49
41
|
* @group Hooks and utilities
|
|
50
42
|
*/
|
|
51
43
|
export async function saveEntityWithCallbacks<M extends Record<string, any>, USER extends User>({
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
44
|
+
collection,
|
|
45
|
+
path,
|
|
46
|
+
entityId,
|
|
47
|
+
values,
|
|
48
|
+
previousValues,
|
|
49
|
+
status,
|
|
50
|
+
dataSource,
|
|
51
|
+
context,
|
|
52
|
+
onSaveSuccess,
|
|
53
|
+
onSaveFailure,
|
|
54
|
+
onPreSaveHookError,
|
|
55
|
+
onSaveSuccessHookError
|
|
56
|
+
}: SaveEntityWithCallbacksProps<M> & {
|
|
57
|
+
collection: EntityCollection<M, USER>,
|
|
58
|
+
dataSource: DataSource,
|
|
59
|
+
context: FireCMSContext,
|
|
60
|
+
}
|
|
69
61
|
): Promise<void> {
|
|
70
62
|
|
|
71
63
|
if (status !== "new" && !entityId) {
|
|
@@ -85,7 +77,8 @@ export async function saveEntityWithCallbacks<M extends Record<string, any>, USE
|
|
|
85
77
|
path,
|
|
86
78
|
values: previousValues as EntityValues<M>,
|
|
87
79
|
entityId,
|
|
88
|
-
propertyConfigs: customizationController.propertyConfigs
|
|
80
|
+
propertyConfigs: customizationController.propertyConfigs,
|
|
81
|
+
authController: context.authController
|
|
89
82
|
});
|
|
90
83
|
updatedValues = await callbacks.onPreSave({
|
|
91
84
|
collection: resolvedCollection,
|
|
@@ -107,11 +100,6 @@ export async function saveEntityWithCallbacks<M extends Record<string, any>, USE
|
|
|
107
100
|
updatedValues = values;
|
|
108
101
|
}
|
|
109
102
|
|
|
110
|
-
console.debug("Saving entity", {
|
|
111
|
-
entityId,
|
|
112
|
-
updatedValues,
|
|
113
|
-
collection
|
|
114
|
-
});
|
|
115
103
|
return dataSource.saveEntity({
|
|
116
104
|
collection,
|
|
117
105
|
path: resolvedPath,
|
|
@@ -127,7 +115,8 @@ export async function saveEntityWithCallbacks<M extends Record<string, any>, USE
|
|
|
127
115
|
path,
|
|
128
116
|
values: updatedValues as EntityValues<M>,
|
|
129
117
|
entityId,
|
|
130
|
-
propertyConfigs: customizationController.propertyConfigs
|
|
118
|
+
propertyConfigs: customizationController.propertyConfigs,
|
|
119
|
+
authController: context.authController
|
|
131
120
|
});
|
|
132
121
|
callbacks.onSaveSuccess({
|
|
133
122
|
collection: resolvedCollection,
|
|
@@ -148,7 +137,6 @@ export async function saveEntityWithCallbacks<M extends Record<string, any>, USE
|
|
|
148
137
|
onSaveSuccess(entity);
|
|
149
138
|
})
|
|
150
139
|
.catch((e) => {
|
|
151
|
-
console.error("!!!", e);
|
|
152
140
|
if (callbacks?.onSaveFailure) {
|
|
153
141
|
|
|
154
142
|
const resolvedCollection = resolveCollection<M>({
|
|
@@ -156,7 +144,8 @@ export async function saveEntityWithCallbacks<M extends Record<string, any>, USE
|
|
|
156
144
|
path,
|
|
157
145
|
values: updatedValues as EntityValues<M>,
|
|
158
146
|
entityId,
|
|
159
|
-
propertyConfigs: customizationController.propertyConfigs
|
|
147
|
+
propertyConfigs: customizationController.propertyConfigs,
|
|
148
|
+
authController: context.authController
|
|
160
149
|
});
|
|
161
150
|
callbacks.onSaveFailure({
|
|
162
151
|
collection: resolvedCollection,
|