@onehat/ui 0.4.77 → 0.4.78
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/package.json +1 -1
- package/src/Components/Editor/AttachmentDirectoriesEditor.js +51 -0
- package/src/Components/Editor/AttachmentsEditor.js +81 -0
- package/src/Components/Form/Field/Combo/AttachmentDirectoriesCombo.js +20 -0
- package/src/Components/Form/Field/Combo/AttachmentDirectoriesComboEditor.js +22 -0
- package/src/Components/Form/Field/Combo/AttachmentsCombo.js +20 -0
- package/src/Components/Form/Field/Combo/AttachmentsComboEditor.js +22 -0
- package/src/Components/Form/Field/Tag/AttachmentDirectoriesTag.js +22 -0
- package/src/Components/Form/Field/Tag/AttachmentDirectoriesTagEditor.js +22 -0
- package/src/Components/Form/Field/Tag/AttachmentsTag.js +22 -0
- package/src/Components/Form/Field/Tag/AttachmentsTagEditor.js +22 -0
- package/src/Components/Form/Form.js +17 -17
- package/src/Components/Grid/AttachmentDirectoriesFilteredGrid.js +17 -0
- package/src/Components/Grid/AttachmentDirectoriesFilteredGridEditor.js +17 -0
- package/src/Components/Grid/AttachmentDirectoriesFilteredInlineGridEditor.js +17 -0
- package/src/Components/Grid/AttachmentDirectoriesFilteredSideGridEditor.js +17 -0
- package/src/Components/Grid/AttachmentDirectoriesGrid.js +20 -0
- package/src/Components/Grid/AttachmentDirectoriesGridEditor.js +27 -0
- package/src/Components/Grid/AttachmentDirectoriesInlineGridEditor.js +25 -0
- package/src/Components/Grid/AttachmentDirectoriesSideGridEditor.js +24 -0
- package/src/Components/Grid/AttachmentsFilteredGrid.js +17 -0
- package/src/Components/Grid/AttachmentsFilteredGridEditor.js +17 -0
- package/src/Components/Grid/AttachmentsFilteredInlineGridEditor.js +17 -0
- package/src/Components/Grid/AttachmentsFilteredSideGridEditor.js +17 -0
- package/src/Components/Grid/AttachmentsGrid.js +20 -0
- package/src/Components/Grid/AttachmentsGridEditor.js +27 -0
- package/src/Components/Grid/AttachmentsInlineGridEditor.js +25 -0
- package/src/Components/Grid/AttachmentsSideGridEditor.js +24 -0
- package/src/Components/Grid/Columns/AttachmentDirectoriesGridColumns.js +32 -0
- package/src/Components/Grid/Columns/AttachmentsGridColumns.js +133 -0
- package/src/Components/Grid/Grid.js +193 -20
- package/src/Components/Grid/GridHeaderRow.js +10 -17
- package/src/Components/Grid/GridRow.js +49 -22
- package/src/Components/Grid/RowHandle.js +8 -6
- package/src/Components/Hoc/withEditor.js +1 -1
- package/src/Components/Hoc/withPdfButtons.js +1 -1
- package/src/Components/Hoc/withSelection.js +26 -4
- package/src/Components/Layout/AsyncOperation.js +299 -195
- package/src/Components/Messages/GlobalModals.js +1 -2
- package/src/Components/Panel/Panel.js +14 -2
- package/src/Components/Panel/TabPanel.js +1 -1
- package/src/Components/Panel/TreePanel.js +1 -1
- package/src/Components/Report/Report.js +106 -17
- package/src/Components/Toolbar/PaginationToolbar.js +4 -3
- package/src/Components/Toolbar/Toolbar.js +6 -3
- package/src/Components/Tree/Tree.js +218 -147
- package/src/Components/Tree/TreeNode.js +20 -13
- package/src/Components/Window/AttachmentDirectoriesEditorWindow.js +34 -0
- package/src/Components/Window/AttachmentsEditorWindow.js +34 -0
- package/src/Components/index.js +92 -1
- package/src/Constants/Attachments.js +2 -0
- package/src/Constants/Dates.js +2 -2
- package/src/Constants/Progress.js +5 -1
- package/src/Models/Schemas/AttachmentDirectories.js +66 -0
- package/src/Models/Schemas/Attachments.js +88 -0
- package/src/Models/Slices/SystemSlice.js +220 -0
- package/src/PlatformImports/Web/Attachments.js +783 -145
- package/src/Styles/Global.css +7 -2
|
@@ -7,22 +7,44 @@ import {
|
|
|
7
7
|
Text,
|
|
8
8
|
VStack,
|
|
9
9
|
} from '@project-components/Gluestack';
|
|
10
|
-
import Button from '../../Components/Buttons/Button';
|
|
11
10
|
import {
|
|
12
11
|
CURRENT_MODE,
|
|
13
12
|
UI_MODE_WEB,
|
|
14
13
|
UI_MODE_NATIVE,
|
|
15
14
|
} from '../../Constants/UiModes.js';
|
|
15
|
+
import {
|
|
16
|
+
HORIZONTAL,
|
|
17
|
+
} from '../../Constants/Directions.js';
|
|
18
|
+
import {
|
|
19
|
+
SELECTION_MODE_MULTI,
|
|
20
|
+
} from '../../Constants/Selection.js';
|
|
16
21
|
import UiGlobals from '../../UiGlobals.js';
|
|
17
22
|
import {
|
|
18
23
|
FILE_MODE_IMAGE,
|
|
19
24
|
FILE_MODE_FILE,
|
|
20
25
|
} from '../../Constants/File.js';
|
|
26
|
+
import clsx from 'clsx';
|
|
27
|
+
import oneHatData from '@onehat/data';
|
|
28
|
+
import * as yup from 'yup'; // https://github.com/jquense/yup#string
|
|
21
29
|
import { Avatar, Dropzone, FileMosaic, FileCard, FileInputButton, } from "@files-ui/react";
|
|
30
|
+
import TreePanel from '../../Components/Panel/TreePanel.js';
|
|
31
|
+
import AttachmentsGridEditor from '../../Components/Grid/AttachmentsGridEditor.js';
|
|
32
|
+
import Form from '../../Components/Form/Form.js';
|
|
33
|
+
import {
|
|
34
|
+
EDITOR_TYPE__PLAIN,
|
|
35
|
+
} from '../../Constants/Editor.js';
|
|
36
|
+
import {
|
|
37
|
+
ATTACHMENTS_VIEW_MODES__ICON,
|
|
38
|
+
ATTACHMENTS_VIEW_MODES__LIST,
|
|
39
|
+
} from '../../Constants/Attachments.js';
|
|
22
40
|
import inArray from '../../Functions/inArray.js';
|
|
41
|
+
import { withDragSource } from '../../Components/Hoc/withDnd.js';
|
|
42
|
+
import Button from '../../Components/Buttons/Button';
|
|
23
43
|
import IconButton from '../../Components/Buttons/IconButton.js';
|
|
24
44
|
import Xmark from '../../Components/Icons/Xmark.js';
|
|
25
45
|
import Eye from '../../Components/Icons/Eye.js';
|
|
46
|
+
import Images from '../../Components/Icons/Images.js';
|
|
47
|
+
import List from '../../Components/Icons/List.js';
|
|
26
48
|
import ChevronLeft from '../../Components/Icons/ChevronLeft.js';
|
|
27
49
|
import ChevronRight from '../../Components/Icons/ChevronRight.js';
|
|
28
50
|
import withAlert from '../../Components/Hoc/withAlert.js';
|
|
@@ -32,6 +54,13 @@ import CenterBox from '../../Components/Layout/CenterBox.js';
|
|
|
32
54
|
import downloadInBackground from '../../Functions/downloadInBackground.js';
|
|
33
55
|
import downloadWithFetch from '../../Functions/downloadWithFetch.js';
|
|
34
56
|
import useForceUpdate from '../../Hooks/useForceUpdate.js';
|
|
57
|
+
import getSaved from '../../Functions/getSaved.js';
|
|
58
|
+
import setSaved from '../../Functions/setSaved.js';
|
|
59
|
+
import Folder from '../../Components/Icons/Folder.js';
|
|
60
|
+
import Plus from '../../Components/Icons/Plus.js';
|
|
61
|
+
import Minus from '../../Components/Icons/Minus.js';
|
|
62
|
+
import Edit from '../../Components/Icons/Edit.js';
|
|
63
|
+
import delay from '../../Functions/delay.js';
|
|
35
64
|
import _ from 'lodash';
|
|
36
65
|
|
|
37
66
|
const
|
|
@@ -48,21 +77,98 @@ function FileCardCustom(props) {
|
|
|
48
77
|
onSee,
|
|
49
78
|
downloadUrl,
|
|
50
79
|
uploadStatus,
|
|
80
|
+
// Drag props
|
|
81
|
+
isDragSource = false,
|
|
82
|
+
dragSourceType = 'Attachments',
|
|
83
|
+
dragSourceItem = {},
|
|
84
|
+
item, // The actual attachment entity
|
|
51
85
|
} = props,
|
|
52
86
|
isDownloading = uploadStatus && inArray(uploadStatus, ['preparing', 'uploading', 'success']),
|
|
53
87
|
isPdf = mimetype === 'application/pdf';
|
|
54
88
|
|
|
55
|
-
|
|
89
|
+
let cardContent = <Pressable
|
|
56
90
|
onPress={() => {
|
|
57
91
|
downloadInBackground(downloadUrl);
|
|
58
92
|
}}
|
|
59
|
-
className="px-3 py-1 items-center flex-row rounded-[5px] border border-primary.700"
|
|
93
|
+
className="Pressable px-3 py-1 items-center flex-row rounded-[5px] border border-primary.700"
|
|
60
94
|
>
|
|
61
95
|
{isDownloading && <Spinner className="mr-2" />}
|
|
62
|
-
{onSee && isPdf && <IconButton mr
|
|
96
|
+
{onSee && isPdf && <IconButton className="mr-1" icon={Eye} onPress={() => onSee(id)} />}
|
|
63
97
|
<Text>{filename}</Text>
|
|
64
|
-
{onDelete && <IconButton ml
|
|
98
|
+
{onDelete && <IconButton className="ml-1" icon={Xmark} onPress={() => onDelete(id)} />}
|
|
65
99
|
</Pressable>;
|
|
100
|
+
|
|
101
|
+
// Wrap with drag source if needed
|
|
102
|
+
if (isDragSource) {
|
|
103
|
+
const DragSourceFileCard = withDragSource(({ children, ...dragProps }) => children);
|
|
104
|
+
return <DragSourceFileCard
|
|
105
|
+
isDragSource={isDragSource}
|
|
106
|
+
dragSourceType={dragSourceType}
|
|
107
|
+
dragSourceItem={dragSourceItem}
|
|
108
|
+
>
|
|
109
|
+
{cardContent}
|
|
110
|
+
</DragSourceFileCard>;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return cardContent;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function DraggableFileMosaic(props) {
|
|
117
|
+
const {
|
|
118
|
+
isDragSource = false,
|
|
119
|
+
dragSourceType = 'Attachments',
|
|
120
|
+
dragSourceItem = {},
|
|
121
|
+
onDragStart,
|
|
122
|
+
onDragEnd,
|
|
123
|
+
...fileMosaicProps
|
|
124
|
+
} = props;
|
|
125
|
+
|
|
126
|
+
console.log('DraggableFileMosaic render:', { isDragSource, dragSourceType, hasItem: !!dragSourceItem.item });
|
|
127
|
+
|
|
128
|
+
// If not a drag source, just return the regular FileMosaic
|
|
129
|
+
if (!isDragSource) {
|
|
130
|
+
return <FileMosaic {...fileMosaicProps} />;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Create a completely separate draggable container
|
|
134
|
+
const DragSourceContainer = withDragSource(({ dragSourceRef, ...dragProps }) => {
|
|
135
|
+
console.log('DragSourceContainer render with props:', dragProps);
|
|
136
|
+
return (
|
|
137
|
+
<div
|
|
138
|
+
ref={dragSourceRef}
|
|
139
|
+
style={{
|
|
140
|
+
display: 'inline-block',
|
|
141
|
+
cursor: 'grab'
|
|
142
|
+
}}
|
|
143
|
+
>
|
|
144
|
+
<FileMosaic
|
|
145
|
+
{...fileMosaicProps}
|
|
146
|
+
// Disable any built-in drag functionality of FileMosaic
|
|
147
|
+
draggable={false}
|
|
148
|
+
/>
|
|
149
|
+
</div>
|
|
150
|
+
);
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
// Add drag handlers to the dragSourceItem
|
|
154
|
+
const enhancedDragSourceItem = {
|
|
155
|
+
...dragSourceItem,
|
|
156
|
+
onDragStart: () => {
|
|
157
|
+
if (dragSourceItem.onDragStart) {
|
|
158
|
+
dragSourceItem.onDragStart();
|
|
159
|
+
}
|
|
160
|
+
if (onDragStart) {
|
|
161
|
+
onDragStart();
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
return <DragSourceContainer
|
|
167
|
+
isDragSource={true}
|
|
168
|
+
dragSourceType={dragSourceType}
|
|
169
|
+
dragSourceItem={enhancedDragSourceItem}
|
|
170
|
+
onDragEnd={onDragEnd}
|
|
171
|
+
/>;
|
|
66
172
|
}
|
|
67
173
|
|
|
68
174
|
|
|
@@ -80,6 +186,10 @@ function AttachmentsElement(props) {
|
|
|
80
186
|
_dropZone = {},
|
|
81
187
|
_fileMosaic = {},
|
|
82
188
|
useFileMosaic = true,
|
|
189
|
+
usesDirectories = false,
|
|
190
|
+
isDirectoriesByModel = true, // if false, directories are by modelid
|
|
191
|
+
AttachmentDirectories,
|
|
192
|
+
initialViewMode = ATTACHMENTS_VIEW_MODES__ICON,
|
|
83
193
|
accept, // 'image/*'
|
|
84
194
|
maxFiles = null,
|
|
85
195
|
disabled = false,
|
|
@@ -105,6 +215,7 @@ function AttachmentsElement(props) {
|
|
|
105
215
|
|
|
106
216
|
// withAlert
|
|
107
217
|
showModal,
|
|
218
|
+
hideModal,
|
|
108
219
|
updateModalBody,
|
|
109
220
|
alert,
|
|
110
221
|
confirm,
|
|
@@ -114,10 +225,32 @@ function AttachmentsElement(props) {
|
|
|
114
225
|
model = _.isArray(selectorSelected) && selectorSelected[0] ? selectorSelected[0].repository?.name : selectorSelected?.repository?.name,
|
|
115
226
|
modelidCalc = _.isArray(selectorSelected) ? _.map(selectorSelected, (entity) => entity[selectorSelectedField]) : selectorSelected?.[selectorSelectedField],
|
|
116
227
|
modelid = useRef(modelidCalc),
|
|
228
|
+
id = props.id || (model && modelid.current ? `attachments-${model}-${modelid.current}` : 'attachments'),
|
|
117
229
|
forceUpdate = useForceUpdate(),
|
|
118
230
|
[isReady, setIsReady] = useState(false),
|
|
119
231
|
[isUploading, setIsUploading] = useState(false),
|
|
232
|
+
[isLoading, setIsLoading] = useState(false),
|
|
233
|
+
[isDirectoriesLoading, setIsDirectoriesLoading] = useState(false),
|
|
234
|
+
[viewMode, setViewModeRaw] = useState(initialViewMode),
|
|
235
|
+
setViewMode = (newViewMode) => {
|
|
236
|
+
setViewModeRaw(newViewMode);
|
|
237
|
+
if (id) {
|
|
238
|
+
setSaved(id + '-viewMode', newViewMode);
|
|
239
|
+
}
|
|
240
|
+
},
|
|
120
241
|
[showAll, setShowAll] = useState(false),
|
|
242
|
+
[isDragging, setIsDragging] = useState(false),
|
|
243
|
+
treeSelectionRaw = useRef([]),
|
|
244
|
+
setTreeSelection = (selection) => {
|
|
245
|
+
treeSelectionRaw.current = selection;
|
|
246
|
+
forceUpdate();
|
|
247
|
+
},
|
|
248
|
+
getTreeSelection = () => {
|
|
249
|
+
return treeSelectionRaw.current;
|
|
250
|
+
},
|
|
251
|
+
treeSelection = getTreeSelection(),
|
|
252
|
+
|
|
253
|
+
// icon view only
|
|
121
254
|
setFilesRaw = useRef([]),
|
|
122
255
|
setFiles = (files) => {
|
|
123
256
|
setFilesRaw.current = files;
|
|
@@ -152,14 +285,66 @@ function AttachmentsElement(props) {
|
|
|
152
285
|
clearFiles = () => {
|
|
153
286
|
setFiles([]);
|
|
154
287
|
},
|
|
288
|
+
onFileDelete = (id) => {
|
|
289
|
+
const
|
|
290
|
+
files = getFiles(),
|
|
291
|
+
file = _.find(files, { id });
|
|
292
|
+
if (confirmBeforeDelete) {
|
|
293
|
+
confirm('Are you sure you want to delete the file "' + file.name + '"?', () => doDelete(id));
|
|
294
|
+
} else {
|
|
295
|
+
doDelete(id);
|
|
296
|
+
}
|
|
297
|
+
},
|
|
155
298
|
toggleShowAll = () => {
|
|
156
299
|
setShowAll(!showAll);
|
|
157
300
|
},
|
|
301
|
+
doDelete = (id) => {
|
|
302
|
+
const
|
|
303
|
+
files = getFiles(),
|
|
304
|
+
file = Repository.getById(id);
|
|
305
|
+
if (file) {
|
|
306
|
+
// if the file exists in the repository, delete it there
|
|
307
|
+
Repository.deleteById(id);
|
|
308
|
+
Repository.save();
|
|
309
|
+
|
|
310
|
+
} else {
|
|
311
|
+
// simply remove it from the files array
|
|
312
|
+
const newFiles = [];
|
|
313
|
+
_.each(files, (file) => {
|
|
314
|
+
if (file.id !== id) {
|
|
315
|
+
newFiles.push(file);
|
|
316
|
+
}
|
|
317
|
+
});
|
|
318
|
+
setFiles(newFiles);
|
|
319
|
+
}
|
|
320
|
+
if (onDelete) {
|
|
321
|
+
onDelete(id);
|
|
322
|
+
}
|
|
323
|
+
},
|
|
324
|
+
onDownload = (id, url) => {
|
|
325
|
+
if (isPwa) {
|
|
326
|
+
// This doesn't work because iOS doesn't allow you to open another window within a PWA.
|
|
327
|
+
// downloadWithFetch(url);
|
|
328
|
+
|
|
329
|
+
alert('Files cannot be downloaded and viewed within an iOS PWA. Please use the Safari browser instead.');
|
|
330
|
+
} else {
|
|
331
|
+
downloadInBackground(url);
|
|
332
|
+
}
|
|
333
|
+
},
|
|
334
|
+
|
|
335
|
+
// dropzone
|
|
158
336
|
onDropzoneChange = async (files) => {
|
|
159
337
|
if (!files.length) {
|
|
160
338
|
alert('No files accepted. Perhaps they were too large or the wrong file type?');
|
|
161
339
|
return;
|
|
162
340
|
}
|
|
341
|
+
if (usesDirectories) {
|
|
342
|
+
const treeSelection = getTreeSelection();
|
|
343
|
+
if (!treeSelection[0] || !treeSelection[0].id) {
|
|
344
|
+
alert('Please select a directory to upload the files to.');
|
|
345
|
+
return;
|
|
346
|
+
}
|
|
347
|
+
}
|
|
163
348
|
setFiles(files);
|
|
164
349
|
_.each(files, (file) => {
|
|
165
350
|
file.extraUploadData = {
|
|
@@ -167,6 +352,9 @@ function AttachmentsElement(props) {
|
|
|
167
352
|
modelid: modelid.current,
|
|
168
353
|
...extraUploadData,
|
|
169
354
|
};
|
|
355
|
+
if (usesDirectories) {
|
|
356
|
+
file.extraUploadData.attachment_directory_id = treeSelection[0].id;
|
|
357
|
+
}
|
|
170
358
|
});
|
|
171
359
|
if (onAfterDropzoneChange) {
|
|
172
360
|
const isChanged = await onAfterDropzoneChange(files);
|
|
@@ -207,82 +395,79 @@ function AttachmentsElement(props) {
|
|
|
207
395
|
}
|
|
208
396
|
}
|
|
209
397
|
},
|
|
210
|
-
|
|
398
|
+
|
|
399
|
+
// Lightbox
|
|
400
|
+
findFile = (id) => {
|
|
401
|
+
const files = getFiles();
|
|
402
|
+
if (useFileMosaic) {
|
|
403
|
+
return _.find(files, (file) => file.id === id);
|
|
404
|
+
}
|
|
405
|
+
return _.find(files, { id });
|
|
406
|
+
},
|
|
407
|
+
findPrevFile = (id) => {
|
|
211
408
|
const
|
|
212
409
|
files = getFiles(),
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
doDelete(id);
|
|
410
|
+
currentFile = findFile(id),
|
|
411
|
+
currentIx = _.findIndex(files, currentFile);
|
|
412
|
+
if (currentIx > 0) {
|
|
413
|
+
return files[currentIx - 1];
|
|
218
414
|
}
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
415
|
+
return null;
|
|
416
|
+
}
|
|
417
|
+
findNextFile = (url, id) => {
|
|
418
|
+
const
|
|
419
|
+
files = getFiles(),
|
|
420
|
+
currentFile = findFile(id),
|
|
421
|
+
currentIx = _.findIndex(files, currentFile);
|
|
422
|
+
if (currentIx < files.length - 1) {
|
|
423
|
+
return files[currentIx + 1];
|
|
228
424
|
}
|
|
425
|
+
return null;
|
|
229
426
|
},
|
|
230
|
-
buildModalBody = (
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
427
|
+
buildModalBody = (id) => {
|
|
428
|
+
let isPdf = false,
|
|
429
|
+
url = null,
|
|
430
|
+
body = null,
|
|
431
|
+
isPrevDisabled = false,
|
|
432
|
+
isNextDisabled = false,
|
|
433
|
+
onPrev,
|
|
434
|
+
onNext;
|
|
435
|
+
switch(viewMode) {
|
|
436
|
+
case ATTACHMENTS_VIEW_MODES__ICON: {
|
|
437
|
+
const
|
|
438
|
+
currentFile = findFile(id),
|
|
439
|
+
prevFile = findPrevFile(id),
|
|
440
|
+
nextFile = findNextFile(id);
|
|
441
|
+
isPrevDisabled = !prevFile;
|
|
442
|
+
isNextDisabled = !nextFile;
|
|
443
|
+
onPrev = () => {
|
|
444
|
+
updateModalBody(buildModalBody(prevFile.id));
|
|
445
|
+
};
|
|
446
|
+
onNext = () => {
|
|
447
|
+
updateModalBody(buildModalBody(nextFile.id));
|
|
448
|
+
};
|
|
449
|
+
url = currentFile.imageUrl;
|
|
450
|
+
isPdf = url?.match(/\.pdf$/);
|
|
451
|
+
break;
|
|
248
452
|
}
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
453
|
+
case ATTACHMENTS_VIEW_MODES__LIST: {
|
|
454
|
+
const
|
|
455
|
+
currentFile = Repository.getById(id),
|
|
456
|
+
currentIx = Repository.getIxById(id),
|
|
457
|
+
prevFile = Repository.getByIx(currentIx - 1),
|
|
458
|
+
nextFile = Repository.getByIx(currentIx + 1);
|
|
459
|
+
isPrevDisabled = !prevFile;
|
|
460
|
+
isNextDisabled = !nextFile;
|
|
461
|
+
onPrev = () => {
|
|
462
|
+
updateModalBody(buildModalBody(prevFile.id));
|
|
463
|
+
};
|
|
464
|
+
onNext = () => {
|
|
465
|
+
updateModalBody(buildModalBody(nextFile.id));
|
|
466
|
+
};
|
|
467
|
+
url = currentFile.attachments__uri;
|
|
468
|
+
isPdf = currentFile.attachments__mimetype === 'application/pdf';
|
|
257
469
|
}
|
|
258
|
-
return null;
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
const
|
|
262
|
-
prevFile = findPrevFile(url, id),
|
|
263
|
-
isPrevDisabled = !prevFile,
|
|
264
|
-
nextFile = findNextFile(url, id),
|
|
265
|
-
isNextDisabled = !nextFile,
|
|
266
|
-
onPrev = () => {
|
|
267
|
-
const { imageUrl, id } = prevFile;
|
|
268
|
-
updateModalBody(buildModalBody(imageUrl, id));
|
|
269
|
-
},
|
|
270
|
-
onNext = () => {
|
|
271
|
-
const { imageUrl, id } = nextFile;
|
|
272
|
-
updateModalBody(buildModalBody(imageUrl, id));
|
|
273
|
-
};
|
|
274
|
-
|
|
275
|
-
let isPdf = false,
|
|
276
|
-
body = null;
|
|
277
|
-
|
|
278
|
-
if (id) {
|
|
279
|
-
const file = _.find(files, { id });
|
|
280
|
-
url = file.imageUrl;
|
|
281
|
-
isPdf = true;
|
|
282
|
-
} else if (url?.match(/\.pdf$/)) {
|
|
283
|
-
isPdf = true;
|
|
284
470
|
}
|
|
285
|
-
|
|
286
471
|
if (isPdf) {
|
|
287
472
|
body = <iframe
|
|
288
473
|
src={url}
|
|
@@ -311,42 +496,116 @@ function AttachmentsElement(props) {
|
|
|
311
496
|
/>
|
|
312
497
|
</HStack>;
|
|
313
498
|
},
|
|
314
|
-
onViewLightbox = (
|
|
315
|
-
if (!
|
|
499
|
+
onViewLightbox = (id) => {
|
|
500
|
+
if (!id) {
|
|
316
501
|
alert('Cannot view lightbox until image is uploaded.');
|
|
317
502
|
return;
|
|
318
503
|
}
|
|
319
504
|
showModal({
|
|
320
505
|
title: 'Lightbox',
|
|
321
|
-
body: buildModalBody(
|
|
506
|
+
body: buildModalBody(id),
|
|
322
507
|
canClose: true,
|
|
323
508
|
includeCancel: true,
|
|
324
509
|
w: 1920,
|
|
325
510
|
h: 1080,
|
|
326
511
|
});
|
|
327
512
|
},
|
|
328
|
-
doDelete = (id) => {
|
|
329
|
-
const
|
|
330
|
-
files = getFiles(),
|
|
331
|
-
file = Repository.getById(id);
|
|
332
|
-
if (file) {
|
|
333
|
-
// if the file exists in the repository, delete it there
|
|
334
|
-
Repository.deleteById(id);
|
|
335
|
-
Repository.save();
|
|
336
513
|
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
514
|
+
// AttachmentDirectories
|
|
515
|
+
onCreateDirectory = () => {
|
|
516
|
+
const treeSelection = getTreeSelection();
|
|
517
|
+
showModal({
|
|
518
|
+
title: 'New Directory',
|
|
519
|
+
w: 400,
|
|
520
|
+
h: 200,
|
|
521
|
+
canClose: true,
|
|
522
|
+
includeReset: false,
|
|
523
|
+
includeCancel: false,
|
|
524
|
+
body: <Form
|
|
525
|
+
editorType={EDITOR_TYPE__PLAIN}
|
|
526
|
+
items={[
|
|
527
|
+
{
|
|
528
|
+
type: 'Input',
|
|
529
|
+
name: 'directoryName',
|
|
530
|
+
placeholder: 'New Directory Name',
|
|
531
|
+
}
|
|
532
|
+
]}
|
|
533
|
+
additionalFooterButtons={[
|
|
534
|
+
{
|
|
535
|
+
text: 'Cancel',
|
|
536
|
+
onPress: hideModal,
|
|
537
|
+
skipSubmit: true,
|
|
538
|
+
variant: 'outline',
|
|
539
|
+
}
|
|
540
|
+
]}
|
|
541
|
+
validator={yup.object({
|
|
542
|
+
directoryName: yup.string().required(),
|
|
543
|
+
})}
|
|
544
|
+
onSave={async (values)=> {
|
|
545
|
+
const { directoryName } = values;
|
|
546
|
+
await AttachmentDirectories.add({
|
|
547
|
+
name: directoryName,
|
|
548
|
+
model: selectorSelected.repository.name,
|
|
549
|
+
modelid: selectorSelected[selectorSelectedField],
|
|
550
|
+
parentId: treeSelection?.[0]?.id || null,
|
|
551
|
+
});
|
|
552
|
+
hideModal();
|
|
553
|
+
}}
|
|
554
|
+
/>,
|
|
555
|
+
});
|
|
556
|
+
},
|
|
557
|
+
onDeleteDirectory = async () => {
|
|
558
|
+
const attachmentDirectory = getTreeSelection()[0];
|
|
559
|
+
await attachmentDirectory.delete();
|
|
560
|
+
self.children.tree.buildAndSetTreeNodeData();
|
|
561
|
+
},
|
|
562
|
+
onRenameDirectory = () => {
|
|
563
|
+
const attachmentDirectory = getTreeSelection()[0];
|
|
564
|
+
showModal({
|
|
565
|
+
title: 'Rename Directory',
|
|
566
|
+
w: 400,
|
|
567
|
+
h: 200,
|
|
568
|
+
canClose: true,
|
|
569
|
+
includeReset: false,
|
|
570
|
+
includeCancel: false,
|
|
571
|
+
body: <Form
|
|
572
|
+
editorType={EDITOR_TYPE__PLAIN}
|
|
573
|
+
items={[
|
|
574
|
+
{
|
|
575
|
+
type: 'Input',
|
|
576
|
+
name: 'directoryName',
|
|
577
|
+
placeholder: 'New Directory Name',
|
|
578
|
+
}
|
|
579
|
+
]}
|
|
580
|
+
additionalFooterButtons={[
|
|
581
|
+
{
|
|
582
|
+
text: 'Cancel',
|
|
583
|
+
onPress: hideModal,
|
|
584
|
+
skipSubmit: true,
|
|
585
|
+
variant: 'outline',
|
|
586
|
+
}
|
|
587
|
+
]}
|
|
588
|
+
startingValues={{
|
|
589
|
+
directoryName: attachmentDirectory.attachment_directories__name,
|
|
590
|
+
}}
|
|
591
|
+
validator={yup.object({
|
|
592
|
+
directoryName: yup.string().required(),
|
|
593
|
+
})}
|
|
594
|
+
onSave={async (values)=> {
|
|
595
|
+
const {
|
|
596
|
+
directoryName,
|
|
597
|
+
} = values;
|
|
598
|
+
attachmentDirectory.attachment_directories__name = directoryName;
|
|
599
|
+
await delay(500);
|
|
600
|
+
await attachmentDirectory.save();
|
|
601
|
+
await delay(500);
|
|
602
|
+
self.children.tree.buildAndSetTreeNodeData();
|
|
603
|
+
hideModal();
|
|
604
|
+
|
|
605
|
+
|
|
606
|
+
}}
|
|
607
|
+
/>,
|
|
608
|
+
});
|
|
350
609
|
};
|
|
351
610
|
|
|
352
611
|
if (!_.isEqual(modelidCalc, modelid.current)) {
|
|
@@ -359,62 +618,125 @@ function AttachmentsElement(props) {
|
|
|
359
618
|
return () => {};
|
|
360
619
|
}
|
|
361
620
|
|
|
362
|
-
|
|
621
|
+
const
|
|
622
|
+
setTrue = () => setIsLoading(true),
|
|
623
|
+
setFalse = () => setIsLoading(false),
|
|
624
|
+
setDirectoriesTrue = () => setIsDirectoriesLoading(true),
|
|
625
|
+
setDirectoriesFalse = () => setIsDirectoriesLoading(false);
|
|
363
626
|
|
|
364
|
-
|
|
627
|
+
Repository.on('beforeLoad', setTrue);
|
|
628
|
+
Repository.on('load', setFalse);
|
|
629
|
+
Repository.on('load', buildFiles);
|
|
630
|
+
if (usesDirectories) {
|
|
631
|
+
AttachmentDirectories.on('beforeLoad', setDirectoriesTrue);
|
|
632
|
+
AttachmentDirectories.on('loadRootNodes', setDirectoriesFalse);
|
|
633
|
+
}
|
|
365
634
|
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
value: modelid.current,
|
|
635
|
+
(async () => {
|
|
636
|
+
|
|
637
|
+
if (modelid.current && !_.isArray(modelid.current)) {
|
|
638
|
+
const
|
|
639
|
+
currentConditions = Repository.getBaseParamConditions() || {},
|
|
640
|
+
newConditions = {
|
|
641
|
+
'conditions[Attachments.model]': model,
|
|
642
|
+
'conditions[Attachments.modelid]': modelid.current,
|
|
375
643
|
},
|
|
376
|
-
|
|
644
|
+
currentPageSize = Repository.pageSize,
|
|
645
|
+
newPageSize = showAll ? expandedMax : collapsedMax;
|
|
646
|
+
|
|
647
|
+
// figure out conditions
|
|
377
648
|
if (accept) {
|
|
378
|
-
let name,
|
|
649
|
+
let name = 'mimetype IN',
|
|
379
650
|
mimetypes;
|
|
380
651
|
if (_.isString(accept)) {
|
|
381
652
|
if (accept.match(/,/)) {
|
|
382
|
-
name = 'mimetype IN';
|
|
383
653
|
mimetypes = accept.split(',');
|
|
384
654
|
} else {
|
|
385
655
|
name = 'mimetype LIKE';
|
|
386
656
|
mimetypes = accept.replace('*', '%');
|
|
387
657
|
}
|
|
388
658
|
} else if (_.isArray(accept)) {
|
|
389
|
-
name = 'mimetype IN';
|
|
390
659
|
mimetypes = accept;
|
|
391
660
|
}
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
661
|
+
newConditions['conditions[Attachments.' + name + ']'] = mimetypes;
|
|
662
|
+
}
|
|
663
|
+
if (usesDirectories) {
|
|
664
|
+
const treeSelection = getTreeSelection();
|
|
665
|
+
newConditions['conditions[Attachments.attachment_directory_id]'] = treeSelection[0]?.id || null;
|
|
666
|
+
}
|
|
667
|
+
let doReload = false;
|
|
668
|
+
if (!_.isEqual(currentConditions, newConditions)) {
|
|
669
|
+
Repository.setBaseParams(newConditions);
|
|
670
|
+
doReload = true;
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
// figure out pageSize
|
|
674
|
+
if (!_.isEqual(currentPageSize, newPageSize)) {
|
|
675
|
+
Repository.setPageSize(newPageSize);
|
|
676
|
+
doReload = true;
|
|
677
|
+
}
|
|
678
|
+
if (doReload) {
|
|
679
|
+
await Repository.load();
|
|
680
|
+
}
|
|
681
|
+
if (usesDirectories) {
|
|
682
|
+
const
|
|
683
|
+
wasAlreadyLoaded = AttachmentDirectories.areRootNodesLoaded,
|
|
684
|
+
currentConditions = AttachmentDirectories.getBaseParamConditions() || {},
|
|
685
|
+
newConditions = {
|
|
686
|
+
'conditions[AttachmentDirectories.model]': selectorSelected.repository.name,
|
|
687
|
+
'conditions[AttachmentDirectories.modelid]': selectorSelected[selectorSelectedField],
|
|
688
|
+
};
|
|
689
|
+
let doReload = false;
|
|
690
|
+
if (!_.isEqual(currentConditions, newConditions)) {
|
|
691
|
+
AttachmentDirectories.setBaseParams(newConditions);
|
|
692
|
+
doReload = true;
|
|
693
|
+
}
|
|
694
|
+
if (doReload) {
|
|
695
|
+
// setTreeSelection([]); // clear it; otherwise we get stale nodes after reloading AttachmentDirectories
|
|
696
|
+
await AttachmentDirectories.reload();
|
|
697
|
+
if (wasAlreadyLoaded) {
|
|
698
|
+
const rootNodes = AttachmentDirectories.getRootNodes();
|
|
699
|
+
if (rootNodes) {
|
|
700
|
+
self.children.tree.setSelection(rootNodes);
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
}
|
|
396
704
|
}
|
|
397
|
-
Repository.filter(filters);
|
|
398
|
-
Repository.setPageSize(showAll ? expandedMax : collapsedMax);
|
|
399
|
-
await Repository.load();
|
|
400
705
|
|
|
401
706
|
buildFiles();
|
|
402
707
|
} else {
|
|
708
|
+
Repository.clear();
|
|
709
|
+
if (usesDirectories) {
|
|
710
|
+
AttachmentDirectories.clear();
|
|
711
|
+
}
|
|
403
712
|
clearFiles();
|
|
404
713
|
}
|
|
405
714
|
|
|
406
715
|
|
|
716
|
+
// Load saved view mode preference before setting ready
|
|
717
|
+
if (id && !isReady) {
|
|
718
|
+
const savedViewMode = await getSaved(id + '-viewMode');
|
|
719
|
+
if (!_.isNil(savedViewMode)) {
|
|
720
|
+
setViewModeRaw(savedViewMode);
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
|
|
407
724
|
if (!isReady) {
|
|
408
725
|
setIsReady(true);
|
|
409
726
|
}
|
|
410
727
|
|
|
411
728
|
})();
|
|
412
729
|
|
|
413
|
-
Repository.on('load', buildFiles);
|
|
414
730
|
return () => {
|
|
731
|
+
Repository.off('beforeLoad', setTrue);
|
|
732
|
+
Repository.off('load', setFalse);
|
|
415
733
|
Repository.off('load', buildFiles);
|
|
734
|
+
if (usesDirectories) {
|
|
735
|
+
AttachmentDirectories.off('beforeLoad', setDirectoriesTrue);
|
|
736
|
+
AttachmentDirectories.off('loadRootNodes', setDirectoriesFalse);
|
|
737
|
+
}
|
|
416
738
|
};
|
|
417
|
-
}, [model, modelid.current, showAll]);
|
|
739
|
+
}, [model, modelid.current, showAll, getTreeSelection()]);
|
|
418
740
|
|
|
419
741
|
if (!isReady) {
|
|
420
742
|
return null;
|
|
@@ -429,45 +751,97 @@ function AttachmentsElement(props) {
|
|
|
429
751
|
if (canCrud) {
|
|
430
752
|
_fileMosaic.onDelete = onFileDelete;
|
|
431
753
|
}
|
|
432
|
-
let className = `
|
|
433
|
-
AttachmentsElement
|
|
434
|
-
w-full
|
|
435
|
-
h-full
|
|
436
|
-
p-1
|
|
437
|
-
rounded-[5px]
|
|
438
|
-
`;
|
|
439
|
-
if (props.className) {
|
|
440
|
-
className += ' ' + props.className;
|
|
441
|
-
}
|
|
442
754
|
const files = getFiles();
|
|
443
|
-
let content =
|
|
444
|
-
|
|
445
|
-
|
|
755
|
+
let content = null;
|
|
756
|
+
// icon or list view
|
|
757
|
+
if (viewMode === ATTACHMENTS_VIEW_MODES__ICON) {
|
|
758
|
+
content = <VStack
|
|
759
|
+
className={clsx(
|
|
760
|
+
'AttachmentsElement-icon-VStack1',
|
|
761
|
+
'h-full',
|
|
762
|
+
'flex-1',
|
|
763
|
+
'border',
|
|
764
|
+
'p-1',
|
|
765
|
+
isLoading ? [
|
|
766
|
+
'border-t-4',
|
|
767
|
+
'border-t-[#f00]',
|
|
768
|
+
] : null,
|
|
769
|
+
)}
|
|
770
|
+
>
|
|
771
|
+
<HStack
|
|
772
|
+
className={clsx(
|
|
773
|
+
'AttachmentsElement-HStack',
|
|
774
|
+
'h-full',
|
|
775
|
+
'flex-1',
|
|
776
|
+
'flex-wrap',
|
|
777
|
+
files.length === 0 ? [
|
|
778
|
+
// So the 'No files' text is centered
|
|
779
|
+
'justify-center',
|
|
780
|
+
'items-center',
|
|
781
|
+
] : null,
|
|
782
|
+
)}
|
|
783
|
+
>
|
|
784
|
+
{files.length === 0 && <Text className="text-grey-600 italic">No files {usesDirectories ? 'in this directory' : ''}</Text>}
|
|
446
785
|
{files.map((file) => {
|
|
447
|
-
let
|
|
786
|
+
let eyeProps = {};
|
|
448
787
|
if (file.type && (file.type.match(/^image\//) || file.type === 'application/pdf')) {
|
|
449
|
-
|
|
788
|
+
eyeProps = {
|
|
450
789
|
onSee: onViewLightbox,
|
|
451
790
|
};
|
|
452
791
|
}
|
|
792
|
+
|
|
793
|
+
// Create drag source item for this file
|
|
794
|
+
const fileEntity = Repository.getById(file.id);
|
|
795
|
+
console.log('Drag setup for file:', file.id, 'Entity:', fileEntity, 'canCrud:', canCrud, 'usesDirectories:', usesDirectories);
|
|
796
|
+
const dragSourceItem = {
|
|
797
|
+
item: fileEntity, // Get the actual entity
|
|
798
|
+
sourceComponentRef: null, // Could be set to a ref if needed
|
|
799
|
+
getDragProxy: () => {
|
|
800
|
+
// Custom drag preview for file items
|
|
801
|
+
return <VStack className="bg-white border border-gray-300 rounded-lg p-3 shadow-lg max-w-[200px]">
|
|
802
|
+
<Text className="font-semibold text-gray-800">{file.name}</Text>
|
|
803
|
+
<Text className="text-sm text-gray-600">File</Text>
|
|
804
|
+
</VStack>;
|
|
805
|
+
}
|
|
806
|
+
};
|
|
807
|
+
|
|
453
808
|
return <Box
|
|
454
809
|
key={file.id}
|
|
455
810
|
className="mr-2"
|
|
456
811
|
>
|
|
457
812
|
{useFileMosaic &&
|
|
458
|
-
<
|
|
813
|
+
<DraggableFileMosaic
|
|
459
814
|
{...file}
|
|
460
815
|
backgroundBlurImage={false}
|
|
461
816
|
onDownload={onDownload}
|
|
462
817
|
{..._fileMosaic}
|
|
463
|
-
{...
|
|
818
|
+
{...eyeProps}
|
|
819
|
+
isDragSource={canCrud && usesDirectories}
|
|
820
|
+
dragSourceType="Attachments"
|
|
821
|
+
dragSourceItem={dragSourceItem}
|
|
822
|
+
onDragStart={() => {
|
|
823
|
+
setTimeout(() => setIsDragging(true), 50); // Delay to avoid interfering with drag initialization
|
|
824
|
+
}}
|
|
825
|
+
onDragEnd={() => {
|
|
826
|
+
setIsDragging(false);
|
|
827
|
+
}}
|
|
464
828
|
/>}
|
|
465
829
|
{!useFileMosaic &&
|
|
466
830
|
<FileCardCustom
|
|
467
831
|
{...file}
|
|
468
832
|
backgroundBlurImage={false}
|
|
469
833
|
{..._fileMosaic}
|
|
470
|
-
{...
|
|
834
|
+
{...eyeProps}
|
|
835
|
+
isDragSource={canCrud && usesDirectories}
|
|
836
|
+
dragSourceType="Attachments"
|
|
837
|
+
dragSourceItem={dragSourceItem}
|
|
838
|
+
item={Repository.getById(file.id)}
|
|
839
|
+
onDragStart={() => {
|
|
840
|
+
setTimeout(() => setIsDragging(true), 50); // Delay to avoid interfering with drag initialization
|
|
841
|
+
}}
|
|
842
|
+
onDragEnd={() => {
|
|
843
|
+
setIsDragging(false);
|
|
844
|
+
}}
|
|
471
845
|
/>}
|
|
472
846
|
</Box>;
|
|
473
847
|
})}
|
|
@@ -488,13 +862,138 @@ function AttachmentsElement(props) {
|
|
|
488
862
|
variant="outline"
|
|
489
863
|
/>}
|
|
490
864
|
</VStack>;
|
|
865
|
+
} else if (viewMode === ATTACHMENTS_VIEW_MODES__LIST) {
|
|
866
|
+
content = <AttachmentsGridEditor
|
|
867
|
+
Repository={Repository}
|
|
868
|
+
selectionMode={SELECTION_MODE_MULTI}
|
|
869
|
+
showSelectHandle={false}
|
|
870
|
+
disableAdd={true}
|
|
871
|
+
disableEdit={true}
|
|
872
|
+
disableView={true}
|
|
873
|
+
disableCopy={true}
|
|
874
|
+
disableDuplicate={true}
|
|
875
|
+
disableDelete={!canCrud}
|
|
876
|
+
className="flex-1 h-full" // Ensure it takes up full space
|
|
877
|
+
onDragStart={() => {
|
|
878
|
+
setTimeout(() => setIsDragging(true), 50); // Delay to avoid interfering with drag initialization
|
|
879
|
+
}}
|
|
880
|
+
onDragEnd={() => {
|
|
881
|
+
setIsDragging(false);
|
|
882
|
+
}}
|
|
883
|
+
columnsConfig={[
|
|
884
|
+
{
|
|
885
|
+
id: 'view',
|
|
886
|
+
header: 'View',
|
|
887
|
+
w: 70,
|
|
888
|
+
isSortable: false,
|
|
889
|
+
isEditable: false,
|
|
890
|
+
isReorderable: false,
|
|
891
|
+
isResizable: false,
|
|
892
|
+
isHidable: false,
|
|
893
|
+
renderer: (item) => {
|
|
894
|
+
return <IconButton
|
|
895
|
+
className="w-[70px]"
|
|
896
|
+
icon={Eye}
|
|
897
|
+
_icon={{
|
|
898
|
+
size: 'xl',
|
|
899
|
+
}}
|
|
900
|
+
onPress={() => onViewLightbox(item.id)}
|
|
901
|
+
tooltip="View"
|
|
902
|
+
/>;
|
|
903
|
+
},
|
|
904
|
+
},
|
|
905
|
+
{
|
|
906
|
+
"id": "attachments__filename",
|
|
907
|
+
"header": "Filename",
|
|
908
|
+
"fieldName": "attachments__filename",
|
|
909
|
+
"isSortable": true,
|
|
910
|
+
"isEditable": true,
|
|
911
|
+
"isReorderable": true,
|
|
912
|
+
"isResizable": true,
|
|
913
|
+
"w": 150
|
|
914
|
+
},
|
|
915
|
+
{
|
|
916
|
+
"id": "attachments__size_formatted",
|
|
917
|
+
"header": "Size Formatted",
|
|
918
|
+
"fieldName": "attachments__size_formatted",
|
|
919
|
+
"isSortable": false,
|
|
920
|
+
"isEditable": false,
|
|
921
|
+
"isReorderable": true,
|
|
922
|
+
"isResizable": true,
|
|
923
|
+
"w": 200
|
|
924
|
+
},
|
|
925
|
+
]}
|
|
926
|
+
areRowsDragSource={canCrud}
|
|
927
|
+
rowDragSourceType="Attachments"
|
|
928
|
+
getCustomDragProxy={(item, selection) => {
|
|
929
|
+
let selectionCount = selection?.length || 1,
|
|
930
|
+
displayText = item.attachments__filename || 'Selected TreeNode';
|
|
931
|
+
return <VStack className="bg-white border border-gray-300 rounded-lg p-3 shadow-lg max-w-[200px]">
|
|
932
|
+
<Text className="font-semibold text-gray-800">{displayText}</Text>
|
|
933
|
+
{selectionCount > 1 &&
|
|
934
|
+
<Text className="text-sm text-gray-600">(+{selectionCount -1} more item{selectionCount > 2 ? 's' : ''})</Text>
|
|
935
|
+
}
|
|
936
|
+
</VStack>;
|
|
937
|
+
}}
|
|
938
|
+
|
|
939
|
+
/>;
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
// switches for icon/list view
|
|
943
|
+
content = <VStack
|
|
944
|
+
className={clsx(
|
|
945
|
+
'w-full',
|
|
946
|
+
'h-full',
|
|
947
|
+
)}
|
|
948
|
+
>
|
|
949
|
+
<HStack
|
|
950
|
+
className={clsx(
|
|
951
|
+
'h-[30px]',
|
|
952
|
+
'w-full',
|
|
953
|
+
'gap-1',
|
|
954
|
+
'p-1',
|
|
955
|
+
'justify-start',
|
|
956
|
+
'items-center',
|
|
957
|
+
'bg-primary-500',
|
|
958
|
+
)}
|
|
959
|
+
>
|
|
960
|
+
<IconButton
|
|
961
|
+
onPress={() => setViewMode(ATTACHMENTS_VIEW_MODES__ICON)}
|
|
962
|
+
icon={Images}
|
|
963
|
+
className={clsx(
|
|
964
|
+
viewMode === ATTACHMENTS_VIEW_MODES__ICON ? 'bg-gray-400' : null,
|
|
965
|
+
'w-[25px]',
|
|
966
|
+
'h-[25px]',
|
|
967
|
+
'px-[2px]',
|
|
968
|
+
'py-[2px]',
|
|
969
|
+
)}
|
|
970
|
+
tooltip="Icon View"
|
|
971
|
+
/>
|
|
972
|
+
<IconButton
|
|
973
|
+
onPress={() => setViewMode(ATTACHMENTS_VIEW_MODES__LIST)}
|
|
974
|
+
icon={List}
|
|
975
|
+
className={clsx(
|
|
976
|
+
viewMode === ATTACHMENTS_VIEW_MODES__LIST ? 'bg-gray-400' : null,
|
|
977
|
+
'w-[25px]',
|
|
978
|
+
'h-[25px]',
|
|
979
|
+
'px-[2px]',
|
|
980
|
+
'py-[2px]',
|
|
981
|
+
)}
|
|
982
|
+
tooltip="List View"
|
|
983
|
+
/>
|
|
984
|
+
</HStack>
|
|
985
|
+
|
|
986
|
+
{content}
|
|
987
|
+
|
|
988
|
+
</VStack>;
|
|
491
989
|
|
|
990
|
+
// Always wrap content in dropzone when canCrud is true, but conditionally disable functionality
|
|
492
991
|
if (canCrud) {
|
|
493
992
|
content = <Dropzone
|
|
494
993
|
value={files}
|
|
495
|
-
onChange={onDropzoneChange}
|
|
496
|
-
accept={accept}
|
|
497
|
-
maxFiles={maxFiles}
|
|
994
|
+
onChange={isDragging ? () => {} : onDropzoneChange} // Disable onChange when dragging
|
|
995
|
+
accept={isDragging ? undefined : accept} // Remove accept types when dragging
|
|
996
|
+
maxFiles={isDragging ? 0 : maxFiles} // Set to 0 when dragging to prevent drops
|
|
498
997
|
maxFileSize={styles.ATTACHMENTS_MAX_FILESIZE}
|
|
499
998
|
autoClean={true}
|
|
500
999
|
uploadConfig={{
|
|
@@ -504,32 +1003,171 @@ function AttachmentsElement(props) {
|
|
|
504
1003
|
autoUpload,
|
|
505
1004
|
}}
|
|
506
1005
|
headerConfig={{
|
|
1006
|
+
className: '!hidden',
|
|
507
1007
|
deleteFiles: false,
|
|
508
1008
|
}}
|
|
1009
|
+
className="attachments-dropzone flex-1 h-full" // Add flex classes to ensure full height
|
|
509
1010
|
onUploadStart={onUploadStart}
|
|
510
1011
|
onUploadFinish={onUploadFinish}
|
|
511
1012
|
background={styles.ATTACHMENTS_BG}
|
|
512
1013
|
color={styles.ATTACHMENTS_COLOR}
|
|
513
1014
|
minHeight={150}
|
|
514
1015
|
footer={false}
|
|
515
|
-
clickable={clickable}
|
|
1016
|
+
clickable={viewMode === ATTACHMENTS_VIEW_MODES__ICON && !isDragging ? clickable : false} // Disable clickable when dragging
|
|
516
1017
|
{..._dropZone}
|
|
517
1018
|
>
|
|
518
1019
|
{content}
|
|
519
1020
|
</Dropzone>;
|
|
1021
|
+
}
|
|
1022
|
+
|
|
1023
|
+
// directories
|
|
1024
|
+
if (usesDirectories) {
|
|
1025
|
+
content = <HStack className="h-full w-full">
|
|
1026
|
+
<TreePanel
|
|
1027
|
+
_panel={{
|
|
1028
|
+
title: 'Directories',
|
|
1029
|
+
isScrollable: true,
|
|
1030
|
+
isCollapsible: false,
|
|
1031
|
+
isCollapsed: false,
|
|
1032
|
+
collapseDirection: HORIZONTAL,
|
|
1033
|
+
disableTitleChange: true,
|
|
1034
|
+
className: clsx(
|
|
1035
|
+
'TreePanel-Panel',
|
|
1036
|
+
'h-full',
|
|
1037
|
+
'w-1/3',
|
|
1038
|
+
),
|
|
1039
|
+
}}
|
|
1040
|
+
_tree={{
|
|
1041
|
+
reference: 'tree',
|
|
1042
|
+
parent: self,
|
|
1043
|
+
Repository: AttachmentDirectories,
|
|
1044
|
+
autoSelectRootNode: true,
|
|
1045
|
+
allowToggleSelection: false,
|
|
1046
|
+
allowDeselectAll: false,
|
|
1047
|
+
forceSelectionOnCollapse: true,
|
|
1048
|
+
showSelectHandle: canCrud,
|
|
1049
|
+
useFilters: false,
|
|
1050
|
+
showHeaderToolbar: false,
|
|
1051
|
+
canNodesMoveInternally: canCrud,
|
|
1052
|
+
hideReloadBtn: true,
|
|
1053
|
+
className: clsx(
|
|
1054
|
+
'TreePanel-Tree',
|
|
1055
|
+
'h-full',
|
|
1056
|
+
'w-full',
|
|
1057
|
+
'min-w-0', // override the Tree's min-w setting
|
|
1058
|
+
'flex-none',
|
|
1059
|
+
isDirectoriesLoading ? [
|
|
1060
|
+
'border-t-4',
|
|
1061
|
+
'border-t-[#f00]',
|
|
1062
|
+
] : null,
|
|
1063
|
+
),
|
|
1064
|
+
areNodesDropTarget: canCrud,
|
|
1065
|
+
dropTargetAccept: 'Attachments',
|
|
1066
|
+
canNodeAcceptDrop: (targetNode, draggedItem) => {
|
|
1067
|
+
// disallow drop onto its parent
|
|
1068
|
+
if (draggedItem.item.attachments__attachment_directory_id === targetNode.id) {
|
|
1069
|
+
return false;
|
|
1070
|
+
}
|
|
1071
|
+
return true;
|
|
1072
|
+
},
|
|
1073
|
+
onNodeDrop: async (targetNode, droppedItem) => {
|
|
1074
|
+
|
|
1075
|
+
let selectedNodes = [];
|
|
1076
|
+
if (droppedItem.getSelection) {
|
|
1077
|
+
selectedNodes = droppedItem.getSelection();
|
|
1078
|
+
}
|
|
1079
|
+
if (_.isEmpty(selectedNodes)) {
|
|
1080
|
+
selectedNodes = [droppedItem.item];
|
|
1081
|
+
}
|
|
1082
|
+
|
|
1083
|
+
// set the attachment_directory_id of the draggedItems to the targetNode.id
|
|
1084
|
+
for (let i = 0; i < selectedNodes.length; i++) {
|
|
1085
|
+
const node = selectedNodes[i];
|
|
1086
|
+
node.attachments__attachment_directory_id = targetNode.id;
|
|
1087
|
+
await node.save();
|
|
1088
|
+
}
|
|
1089
|
+
|
|
1090
|
+
// refresh the repository from the dragged node
|
|
1091
|
+
await selectedNodes[0].repository.reload();
|
|
1092
|
+
},
|
|
1093
|
+
getCustomDragProxy: (item, selection) => {
|
|
1094
|
+
let selectionCount = selection?.length || 1,
|
|
1095
|
+
displayText = item.displayValue || 'Selected TreeNode';
|
|
1096
|
+
return <VStack className="bg-white border border-gray-300 rounded-lg p-3 shadow-lg max-w-[200px]">
|
|
1097
|
+
<Text className="font-semibold text-gray-800">{displayText}</Text>
|
|
1098
|
+
{selectionCount > 1 &&
|
|
1099
|
+
<Text className="text-sm text-gray-600">(+{selectionCount -1} more item{selectionCount > 2 ? 's' : ''})</Text>
|
|
1100
|
+
}
|
|
1101
|
+
</VStack>;
|
|
1102
|
+
},
|
|
1103
|
+
getNodeIcon: (node) => {
|
|
1104
|
+
return Folder;
|
|
1105
|
+
},
|
|
1106
|
+
onChangeSelection: (selection) => {
|
|
1107
|
+
setTreeSelection(selection);
|
|
1108
|
+
},
|
|
1109
|
+
additionalToolbarButtons: canCrud ? [
|
|
1110
|
+
{
|
|
1111
|
+
key: 'Plus',
|
|
1112
|
+
text: 'New Directory',
|
|
1113
|
+
handler: onCreateDirectory,
|
|
1114
|
+
icon: Plus,
|
|
1115
|
+
isDisabled: !treeSelection.length, // disabled if no selection
|
|
1116
|
+
},
|
|
1117
|
+
{
|
|
1118
|
+
key: 'Edit',
|
|
1119
|
+
text: 'Rename Directory',
|
|
1120
|
+
handler: onRenameDirectory,
|
|
1121
|
+
icon: Edit,
|
|
1122
|
+
isDisabled: !treeSelection.length, // disabled if no selection
|
|
1123
|
+
},
|
|
1124
|
+
{
|
|
1125
|
+
key: 'Minus',
|
|
1126
|
+
text: 'Delete Directory',
|
|
1127
|
+
handler: onDeleteDirectory,
|
|
1128
|
+
icon: Minus,
|
|
1129
|
+
isDisabled: !treeSelection.length || !treeSelection[0].parentId, // disabled if selection is root or none
|
|
1130
|
+
},
|
|
1131
|
+
] : [],
|
|
1132
|
+
}}
|
|
1133
|
+
/>
|
|
520
1134
|
|
|
1135
|
+
<Box className="w-2/3">
|
|
1136
|
+
{content}
|
|
1137
|
+
</Box>
|
|
1138
|
+
|
|
1139
|
+
</HStack>;
|
|
1140
|
+
}
|
|
1141
|
+
|
|
1142
|
+
let className = clsx(
|
|
1143
|
+
'AttachmentsElement',
|
|
1144
|
+
'testx',
|
|
1145
|
+
'w-full',
|
|
1146
|
+
'h-[400px]',
|
|
1147
|
+
'border-2',
|
|
1148
|
+
'rounded-[5px]',
|
|
1149
|
+
);
|
|
1150
|
+
if (props.className) {
|
|
1151
|
+
className += ' ' + props.className;
|
|
521
1152
|
}
|
|
522
|
-
return content
|
|
1153
|
+
return <Box className={className}>{content}</Box>;
|
|
523
1154
|
}
|
|
524
1155
|
|
|
525
1156
|
function withAdditionalProps(WrappedComponent) {
|
|
526
1157
|
return (props) => {
|
|
1158
|
+
const {
|
|
1159
|
+
usesDirectories = false,
|
|
1160
|
+
} = props,
|
|
1161
|
+
AttachmentDirectories = usesDirectories ? oneHatData.getRepository('AttachmentDirectories', true) : null; // put this here; otherwise a new unique repository will be created on every render!
|
|
1162
|
+
|
|
527
1163
|
return <WrappedComponent
|
|
528
1164
|
model="Attachments"
|
|
529
1165
|
uniqueRepository={true}
|
|
1166
|
+
reference="attachments"
|
|
530
1167
|
{...props}
|
|
1168
|
+
AttachmentDirectories={AttachmentDirectories}
|
|
531
1169
|
/>;
|
|
532
1170
|
};
|
|
533
1171
|
}
|
|
534
1172
|
|
|
535
|
-
export default withComponent(
|
|
1173
|
+
export default withAdditionalProps(withComponent(withAlert(withData(AttachmentsElement))));
|