@firecms/core 3.0.0-canary.242 → 3.0.0-canary.245
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/components/ArrayContainer.d.ts +7 -12
- package/dist/components/VirtualTable/VirtualTableProps.d.ts +0 -4
- package/dist/form/field_bindings/StorageUploadFieldBinding.d.ts +3 -9
- package/dist/index.es.js +731 -397
- package/dist/index.es.js.map +1 -1
- package/dist/index.umd.js +739 -408
- package/dist/index.umd.js.map +1 -1
- package/dist/types/collections.d.ts +4 -0
- package/package.json +8 -6
- package/src/components/ArrayContainer.tsx +408 -294
- package/src/components/HomePage/DefaultHomePage.tsx +1 -0
- package/src/components/VirtualTable/VirtualTable.tsx +0 -26
- package/src/components/VirtualTable/VirtualTableProps.tsx +0 -5
- package/src/core/EntityEditView.tsx +1 -0
- package/src/core/EntitySidePanel.tsx +1 -13
- package/src/form/components/CustomIdField.tsx +3 -1
- package/src/form/field_bindings/StorageUploadFieldBinding.tsx +222 -154
- package/src/hooks/data/save.ts +0 -6
- package/src/types/collections.ts +5 -0
- package/src/util/objects.ts +53 -20
|
@@ -114,6 +114,7 @@ export function DefaultHomePage({
|
|
|
114
114
|
);
|
|
115
115
|
})}
|
|
116
116
|
</>;
|
|
117
|
+
|
|
117
118
|
additionalPluginChildrenStart = <div className={"flex flex-col gap-2"}>
|
|
118
119
|
{customizationController.plugins.filter(plugin => plugin.homePage?.additionalChildrenStart)
|
|
119
120
|
.map((plugin, i) => {
|
|
@@ -115,7 +115,6 @@ export const VirtualTable = React.memo<VirtualTableProps<any>>(
|
|
|
115
115
|
endAdornment,
|
|
116
116
|
AddColumnComponent,
|
|
117
117
|
initialScroll = 0,
|
|
118
|
-
debug
|
|
119
118
|
}: VirtualTableProps<T>) {
|
|
120
119
|
|
|
121
120
|
const sortByProperty: string | undefined = sortBy ? sortBy[0] : undefined;
|
|
@@ -171,7 +170,6 @@ export const VirtualTable = React.memo<VirtualTableProps<any>>(
|
|
|
171
170
|
});
|
|
172
171
|
|
|
173
172
|
const onColumnResizeInternal = useCallback((params: OnVirtualTableColumnResizeParams) => {
|
|
174
|
-
if (debug) console.log("onColumnResizeInternal", params);
|
|
175
173
|
setColumns(prevColumns =>
|
|
176
174
|
prevColumns.map((column) =>
|
|
177
175
|
column.key === params.column.key ? params.column : column
|
|
@@ -180,8 +178,6 @@ export const VirtualTable = React.memo<VirtualTableProps<any>>(
|
|
|
180
178
|
}, []);
|
|
181
179
|
|
|
182
180
|
const onColumnResizeEndInternal = useCallback((params: OnVirtualTableColumnResizeParams) => {
|
|
183
|
-
if (debug)
|
|
184
|
-
console.log("onColumnResizeEndInternal", params);
|
|
185
181
|
setColumns(columns.map((column) => column.key === params.column.key ? params.column : column));
|
|
186
182
|
if (onColumnResize) {
|
|
187
183
|
onColumnResize(params);
|
|
@@ -192,14 +188,10 @@ export const VirtualTable = React.memo<VirtualTableProps<any>>(
|
|
|
192
188
|
const filterRef = useRef<VirtualTableFilterValues<any> | undefined>();
|
|
193
189
|
|
|
194
190
|
useEffect(() => {
|
|
195
|
-
if (debug)
|
|
196
|
-
console.log("Filter updated", filterInput);
|
|
197
191
|
filterRef.current = filterInput;
|
|
198
192
|
}, [filterInput]);
|
|
199
193
|
|
|
200
194
|
const scrollToTop = useCallback(() => {
|
|
201
|
-
if (debug)
|
|
202
|
-
console.log("scrollToTop");
|
|
203
195
|
endReachCallbackThreshold.current = 0;
|
|
204
196
|
if (tableRef.current) {
|
|
205
197
|
tableRef.current.scrollTo(tableRef.current?.scrollLeft, 0);
|
|
@@ -208,9 +200,6 @@ export const VirtualTable = React.memo<VirtualTableProps<any>>(
|
|
|
208
200
|
|
|
209
201
|
const onColumnSort = useCallback((key: string) => {
|
|
210
202
|
|
|
211
|
-
if (debug)
|
|
212
|
-
console.log("onColumnSort", key);
|
|
213
|
-
|
|
214
203
|
const isDesc = sortByProperty === key && currentSort === "desc";
|
|
215
204
|
const isAsc = sortByProperty === key && currentSort === "asc";
|
|
216
205
|
const newSort = isAsc ? "desc" : (isDesc ? undefined : "asc");
|
|
@@ -238,12 +227,8 @@ export const VirtualTable = React.memo<VirtualTableProps<any>>(
|
|
|
238
227
|
}, [checkFilterCombination, currentSort, onFilterUpdate, onResetPagination, onSortByUpdate, scrollToTop, sortByProperty]);
|
|
239
228
|
|
|
240
229
|
const maxScroll = Math.max((data?.length ?? 0) * rowHeight - bounds.height, 0);
|
|
241
|
-
if (debug)
|
|
242
|
-
console.log("maxScroll", maxScroll);
|
|
243
230
|
|
|
244
231
|
const onEndReachedInternal = useCallback((scrollOffset: number) => {
|
|
245
|
-
if (debug)
|
|
246
|
-
console.log("onEndReachedInternal", scrollOffset, endReachCallbackThreshold.current + endOffset);
|
|
247
232
|
if (onEndReached && (data?.length ?? 0) > 0 && scrollOffset > endReachCallbackThreshold.current + endOffset) {
|
|
248
233
|
endReachCallbackThreshold.current = scrollOffset;
|
|
249
234
|
onEndReached();
|
|
@@ -259,12 +244,6 @@ export const VirtualTable = React.memo<VirtualTableProps<any>>(
|
|
|
259
244
|
scrollOffset: number,
|
|
260
245
|
scrollUpdateWasRequested: boolean;
|
|
261
246
|
}) => {
|
|
262
|
-
if (debug)
|
|
263
|
-
console.log("onScroll", {
|
|
264
|
-
scrollDirection,
|
|
265
|
-
scrollOffset,
|
|
266
|
-
scrollUpdateWasRequested
|
|
267
|
-
});
|
|
268
247
|
if (onScrollProp) {
|
|
269
248
|
debouncedScroll({
|
|
270
249
|
scrollDirection,
|
|
@@ -277,8 +256,6 @@ export const VirtualTable = React.memo<VirtualTableProps<any>>(
|
|
|
277
256
|
}, [maxScroll, onEndReachedInternal]);
|
|
278
257
|
|
|
279
258
|
const onFilterUpdateInternal = useCallback((column: VirtualTableColumn, filterForProperty?: [VirtualTableWhereFilterOp, any]) => {
|
|
280
|
-
if (debug)
|
|
281
|
-
console.log("onFilterUpdateInternal", column, filterForProperty);
|
|
282
259
|
|
|
283
260
|
endReachCallbackThreshold.current = 0;
|
|
284
261
|
const filter = filterRef.current;
|
|
@@ -341,9 +318,6 @@ export const VirtualTable = React.memo<VirtualTableProps<any>>(
|
|
|
341
318
|
AddColumnComponent
|
|
342
319
|
};
|
|
343
320
|
|
|
344
|
-
if (debug)
|
|
345
|
-
console.log("VirtualTable render", virtualListController);
|
|
346
|
-
|
|
347
321
|
return (
|
|
348
322
|
<div
|
|
349
323
|
ref={measureRef}
|
|
@@ -255,6 +255,7 @@ export function EntityEditViewInner<M extends Record<string, any>>({
|
|
|
255
255
|
<ErrorBoundary>
|
|
256
256
|
{usedFormContext && <Builder
|
|
257
257
|
collection={collection}
|
|
258
|
+
parentCollectionIds={parentCollectionIds}
|
|
258
259
|
entity={usedEntity}
|
|
259
260
|
modifiedValues={usedFormContext?.formex?.values ?? usedEntity?.values}
|
|
260
261
|
formContext={usedFormContext}
|
|
@@ -74,19 +74,7 @@ export function EntitySidePanel(props: EntitySidePanelProps) {
|
|
|
74
74
|
return navigationController.getParentCollectionIds(path);
|
|
75
75
|
}, [navigationController, path]);
|
|
76
76
|
|
|
77
|
-
const collection =
|
|
78
|
-
if (props.collection) {
|
|
79
|
-
return props.collection;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
const registryCollection = navigationController.getCollection(path);
|
|
83
|
-
if (registryCollection) {
|
|
84
|
-
return registryCollection;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
console.error("ERROR: No collection found in path `", path, "`. Entity id: ", entityId);
|
|
88
|
-
throw Error("ERROR: No collection found in path `" + path + "`. Make sure you have defined a collection for this path in the root navigation.");
|
|
89
|
-
}, [navigationController, props.collection]);
|
|
77
|
+
const collection = props.collection ?? navigationController.getCollection(path);
|
|
90
78
|
|
|
91
79
|
useEffect(() => {
|
|
92
80
|
function beforeunload(e: any) {
|
|
@@ -103,7 +103,9 @@ export function CustomIdField<M extends Record<string, any>>({
|
|
|
103
103
|
size={"large"}
|
|
104
104
|
error={error}
|
|
105
105
|
fullWidth={true}
|
|
106
|
-
onValueChange={(v) =>
|
|
106
|
+
onValueChange={(v) => {
|
|
107
|
+
onChange(v as string);
|
|
108
|
+
}}
|
|
107
109
|
{...fieldProps}
|
|
108
110
|
renderValue={(option) => {
|
|
109
111
|
const enumConfig = enumValues.find(e => e.id === option);
|
|
@@ -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,
|
|
@@ -14,7 +13,23 @@ import { FieldHelperText, LabelWithIconAndTooltip } from "../components";
|
|
|
14
13
|
|
|
15
14
|
import { getIconForProperty, isReadOnly, resolveProperty } from "../../util";
|
|
16
15
|
import { useAuthController, useSnackbarController, useStorageSource } from "../../hooks";
|
|
17
|
-
import {
|
|
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,
|
|
@@ -132,13 +140,104 @@ export function StorageUploadFieldBinding({
|
|
|
132
140
|
);
|
|
133
141
|
}
|
|
134
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
|
+
|
|
135
236
|
function FileDropComponent({
|
|
136
237
|
storage,
|
|
137
238
|
disabled,
|
|
138
|
-
isDraggingOver,
|
|
139
239
|
onFilesAdded,
|
|
140
240
|
multipleFilesSupported,
|
|
141
|
-
droppableProvided,
|
|
142
241
|
autoFocus,
|
|
143
242
|
internalValue,
|
|
144
243
|
property,
|
|
@@ -146,26 +245,24 @@ function FileDropComponent({
|
|
|
146
245
|
metadata,
|
|
147
246
|
storagePathBuilder,
|
|
148
247
|
onFileUploadComplete,
|
|
149
|
-
size,
|
|
150
248
|
name,
|
|
151
|
-
helpText
|
|
249
|
+
helpText,
|
|
250
|
+
isDndItemDragging // New prop to disable dropzone when internal D&D is active
|
|
152
251
|
}: {
|
|
153
252
|
storage: StorageConfig,
|
|
154
253
|
disabled: boolean,
|
|
155
|
-
|
|
156
|
-
droppableProvided: any,
|
|
157
|
-
onFilesAdded: (acceptedFiles: File[]) => void,
|
|
254
|
+
onFilesAdded: (acceptedFiles: File[]) => Promise<void>, // useStorageUploadController returns Promise<void>
|
|
158
255
|
multipleFilesSupported: boolean,
|
|
159
256
|
autoFocus: boolean,
|
|
160
257
|
internalValue: StorageFieldItem[],
|
|
161
258
|
property: ResolvedStringProperty,
|
|
162
259
|
onClear: (clearedStoragePathOrDownloadUrl: string) => void,
|
|
163
|
-
metadata
|
|
260
|
+
metadata?: any,
|
|
164
261
|
storagePathBuilder: (file: File) => string,
|
|
165
262
|
onFileUploadComplete: (uploadedPath: string, entry: StorageFieldItem, fileMetadata?: any) => Promise<void>,
|
|
166
|
-
size: PreviewSize,
|
|
167
263
|
name: string,
|
|
168
|
-
helpText: string
|
|
264
|
+
helpText: string,
|
|
265
|
+
isDndItemDragging?: boolean
|
|
169
266
|
}) {
|
|
170
267
|
|
|
171
268
|
const snackbarContext = useSnackbarController();
|
|
@@ -173,16 +270,19 @@ function FileDropComponent({
|
|
|
173
270
|
const {
|
|
174
271
|
getRootProps,
|
|
175
272
|
getInputProps,
|
|
176
|
-
isDragActive,
|
|
273
|
+
isDragActive, // This is for files dragged from OS
|
|
177
274
|
isDragAccept,
|
|
178
275
|
isDragReject
|
|
179
276
|
} = useDropzone({
|
|
180
|
-
accept: storage.acceptedFiles ? storage.acceptedFiles.
|
|
181
|
-
|
|
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
|
|
182
282
|
noDragEventsBubbling: true,
|
|
183
283
|
maxSize: storage.maxSize,
|
|
184
284
|
onDrop: onFilesAdded,
|
|
185
|
-
onDropRejected: (fileRejections
|
|
285
|
+
onDropRejected: (fileRejections) => {
|
|
186
286
|
for (const fileRejection of fileRejections) {
|
|
187
287
|
for (const error of fileRejection.errors) {
|
|
188
288
|
console.error("Error uploading file: ", error);
|
|
@@ -211,79 +311,44 @@ function FileDropComponent({
|
|
|
211
311
|
disabled ? fieldBackgroundDisabledMixin : fieldBackgroundHoverMixin,
|
|
212
312
|
disabled ? "text-surface-accent-600 dark:text-surface-accent-500" : "",
|
|
213
313
|
dropZoneClasses,
|
|
214
|
-
multipleFilesSupported && internalValue.length
|
|
314
|
+
multipleFilesSupported && internalValue.length === 0 && "flex", // Keep flex for empty state centering
|
|
215
315
|
{
|
|
216
316
|
[nonActiveDropClasses]: !isDragActive,
|
|
217
|
-
[activeDropClasses]: isDragActive,
|
|
218
|
-
[rejectDropClasses]: isDragReject,
|
|
219
|
-
[acceptDropClasses]: isDragAccept,
|
|
220
|
-
[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
|
|
221
321
|
})}
|
|
222
322
|
>
|
|
223
323
|
<div
|
|
224
|
-
{...droppableProvided.droppableProps}
|
|
225
|
-
ref={droppableProvided.innerRef}
|
|
226
324
|
className={cls("flex items-center p-1 no-scrollbar",
|
|
227
|
-
multipleFilesSupported && internalValue.length ? "overflow-auto" : "",
|
|
228
|
-
|
|
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
|
|
229
329
|
)}
|
|
230
330
|
>
|
|
231
|
-
|
|
232
331
|
<input
|
|
233
332
|
autoFocus={autoFocus}
|
|
234
333
|
{...getInputProps()} />
|
|
235
334
|
|
|
236
|
-
{internalValue.map((entry
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
storagePath={storagePathBuilder(entry.file)}
|
|
254
|
-
onFileUploadComplete={onFileUploadComplete}
|
|
255
|
-
imageSize={size === "large" ? 220 : 118}
|
|
256
|
-
simple={false}
|
|
257
|
-
/>
|
|
258
|
-
);
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
return (
|
|
262
|
-
<Draggable
|
|
263
|
-
key={`array_field_${name}_${entry.id}`}
|
|
264
|
-
draggableId={`array_field_${name}_${entry.id}`}
|
|
265
|
-
index={index}>
|
|
266
|
-
{(provided, snapshot) => (
|
|
267
|
-
<div
|
|
268
|
-
tabIndex={-1}
|
|
269
|
-
ref={provided.innerRef}
|
|
270
|
-
{...provided.draggableProps}
|
|
271
|
-
{...provided.dragHandleProps}
|
|
272
|
-
className={cls("rounded-md")}
|
|
273
|
-
style={{
|
|
274
|
-
...provided.draggableProps.style
|
|
275
|
-
}}
|
|
276
|
-
>
|
|
277
|
-
{child}
|
|
278
|
-
</div>
|
|
279
|
-
)}
|
|
280
|
-
</Draggable>
|
|
281
|
-
);
|
|
282
|
-
})
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
{droppableProvided.placeholder}
|
|
286
|
-
|
|
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 */}
|
|
287
352
|
</div>
|
|
288
353
|
|
|
289
354
|
<div
|
|
@@ -294,7 +359,6 @@ function FileDropComponent({
|
|
|
294
359
|
{helpText}
|
|
295
360
|
</Typography>
|
|
296
361
|
</div>
|
|
297
|
-
|
|
298
362
|
</div>
|
|
299
363
|
);
|
|
300
364
|
}
|
|
@@ -309,7 +373,7 @@ export interface StorageUploadProps {
|
|
|
309
373
|
autoFocus: boolean;
|
|
310
374
|
disabled: boolean;
|
|
311
375
|
storage: StorageConfig;
|
|
312
|
-
onFilesAdded: (acceptedFiles: File[]) => void
|
|
376
|
+
onFilesAdded: (acceptedFiles: File[]) => Promise<void>; // Updated from useStorageUploadController
|
|
313
377
|
storagePathBuilder: (file: File) => string;
|
|
314
378
|
onFileUploadComplete: (uploadedPath: string, entry: StorageFieldItem, fileMetadata?: any) => Promise<void>;
|
|
315
379
|
}
|
|
@@ -317,7 +381,7 @@ export interface StorageUploadProps {
|
|
|
317
381
|
export function StorageUpload({
|
|
318
382
|
property,
|
|
319
383
|
name,
|
|
320
|
-
value,
|
|
384
|
+
value, // This is internalValue from useStorageUploadController
|
|
321
385
|
setInternalValue,
|
|
322
386
|
onChange,
|
|
323
387
|
multipleFilesSupported,
|
|
@@ -344,10 +408,10 @@ export function StorageUpload({
|
|
|
344
408
|
}
|
|
345
409
|
|
|
346
410
|
const metadata: Record<string, unknown> | undefined = storage?.metadata;
|
|
347
|
-
const
|
|
411
|
+
const [isDndItemDragging, setIsDndItemDragging] = useState(false);
|
|
348
412
|
|
|
349
413
|
const moveItem = useCallback((fromIndex: number, toIndex: number) => {
|
|
350
|
-
if (!multipleFilesSupported) return;
|
|
414
|
+
if (!multipleFilesSupported || fromIndex === toIndex) return;
|
|
351
415
|
const newValue = [...value];
|
|
352
416
|
const item = newValue[fromIndex];
|
|
353
417
|
newValue.splice(fromIndex, 1);
|
|
@@ -359,84 +423,88 @@ export function StorageUpload({
|
|
|
359
423
|
onChange(fieldValue);
|
|
360
424
|
}, [multipleFilesSupported, onChange, setInternalValue, value]);
|
|
361
425
|
|
|
362
|
-
const
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
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
|
+
);
|
|
369
436
|
|
|
370
|
-
|
|
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]);
|
|
371
455
|
|
|
372
456
|
const onClear = useCallback((clearedStoragePathOrDownloadUrl: string) => {
|
|
457
|
+
let newValue: StorageFieldItem[];
|
|
373
458
|
if (multipleFilesSupported) {
|
|
374
|
-
|
|
459
|
+
newValue = value.filter(v => v.storagePathOrDownloadUrl !== clearedStoragePathOrDownloadUrl);
|
|
375
460
|
onChange(newValue.filter(v => !!v.storagePathOrDownloadUrl).map(v => v.storagePathOrDownloadUrl as string));
|
|
376
|
-
setInternalValue(newValue);
|
|
377
461
|
} else {
|
|
462
|
+
newValue = [];
|
|
378
463
|
onChange(null);
|
|
379
|
-
setInternalValue([]);
|
|
380
464
|
}
|
|
381
|
-
|
|
465
|
+
setInternalValue(newValue);
|
|
466
|
+
}, [value, multipleFilesSupported, onChange, setInternalValue]);
|
|
382
467
|
|
|
383
468
|
const helpText = multipleFilesSupported
|
|
384
|
-
? "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."
|
|
385
470
|
: "Drag 'n' drop a file here, or click to select one";
|
|
386
471
|
|
|
387
472
|
const renderProperty: ResolvedStringProperty = multipleFilesSupported
|
|
388
|
-
? (property as
|
|
473
|
+
? (property as ResolvedArrayProperty<string[]>).of as ResolvedStringProperty
|
|
389
474
|
: property as ResolvedStringProperty;
|
|
390
475
|
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
>
|
|
408
|
-
<StorageItemPreview
|
|
409
|
-
name={`storage_preview_${entry.storagePathOrDownloadUrl}`}
|
|
410
|
-
placeholder={true}
|
|
411
|
-
property={renderProperty}
|
|
412
|
-
disabled={true}
|
|
413
|
-
value={entry.storagePathOrDownloadUrl as string}
|
|
414
|
-
onRemove={onClear}
|
|
415
|
-
size={entry.size}/>
|
|
416
|
-
</div>
|
|
417
|
-
);
|
|
418
|
-
}}
|
|
419
|
-
>
|
|
420
|
-
{(provided, snapshot) => {
|
|
421
|
-
return <FileDropComponent storage={storage}
|
|
422
|
-
disabled={disabled}
|
|
423
|
-
isDraggingOver={snapshot.isDraggingOver}
|
|
424
|
-
droppableProvided={provided}
|
|
425
|
-
onFilesAdded={onFilesAdded}
|
|
426
|
-
multipleFilesSupported={multipleFilesSupported}
|
|
427
|
-
autoFocus={autoFocus}
|
|
428
|
-
internalValue={value}
|
|
429
|
-
property={renderProperty}
|
|
430
|
-
onClear={onClear}
|
|
431
|
-
metadata={metadata}
|
|
432
|
-
storagePathBuilder={storagePathBuilder}
|
|
433
|
-
onFileUploadComplete={onFileUploadComplete}
|
|
434
|
-
size={size}
|
|
435
|
-
name={name}
|
|
436
|
-
helpText={helpText}/>
|
|
437
|
-
}}
|
|
438
|
-
</Droppable>
|
|
439
|
-
</DragDropContext>
|
|
440
|
-
);
|
|
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
|
+
};
|
|
441
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
|
+
}
|
|
442
510
|
}
|