@topconsultnpm/sdkui-react 6.21.0-t1 → 6.21.0-t2

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.
Files changed (91) hide show
  1. package/lib/components/NewComponents/FloatingMenuBar/styles.d.ts +4 -4
  2. package/lib/components/base/TMAccordionNew.d.ts +1 -0
  3. package/lib/components/base/TMAccordionNew.js +6 -5
  4. package/lib/components/base/TMAreaManager.js +19 -3
  5. package/lib/components/base/TMDataGrid.js +2 -2
  6. package/lib/components/base/TMModal.d.ts +1 -0
  7. package/lib/components/base/TMModal.js +2 -2
  8. package/lib/components/base/TMPanel.d.ts +7 -4
  9. package/lib/components/base/TMPanel.js +58 -26
  10. package/lib/components/base/TMTreeView.js +12 -2
  11. package/lib/components/base/TMWaitPanel.js +7 -4
  12. package/lib/components/choosers/TMDistinctValues.js +35 -21
  13. package/lib/components/choosers/TMUserChooser.d.ts +4 -0
  14. package/lib/components/choosers/TMUserChooser.js +7 -5
  15. package/lib/components/editors/TMDateBox.js +4 -2
  16. package/lib/components/editors/TMFormulaEditor.d.ts +2 -0
  17. package/lib/components/editors/TMFormulaEditor.js +75 -21
  18. package/lib/components/editors/TMMetadataValues.js +2 -1
  19. package/lib/components/editors/TMRadioButton.js +7 -5
  20. package/lib/components/editors/TMTextArea.d.ts +2 -0
  21. package/lib/components/editors/TMTextArea.js +6 -3
  22. package/lib/components/editors/TMTextBox.d.ts +2 -0
  23. package/lib/components/editors/TMTextBox.js +3 -3
  24. package/lib/components/features/archive/TMArchive.js +1 -1
  25. package/lib/components/features/documents/TMCopyToFolderForm.d.ts +24 -0
  26. package/lib/components/features/documents/TMCopyToFolderForm.js +379 -0
  27. package/lib/components/features/documents/TMDcmtForm.d.ts +1 -0
  28. package/lib/components/features/documents/TMDcmtForm.js +147 -45
  29. package/lib/components/features/documents/TMDcmtFormActionButtons.js +259 -60
  30. package/lib/components/features/documents/TMDcmtPreview.d.ts +1 -0
  31. package/lib/components/features/documents/TMDcmtPreview.js +2 -2
  32. package/lib/components/features/documents/TMDcmtTasks.d.ts +1 -0
  33. package/lib/components/features/documents/TMDcmtTasks.js +2 -2
  34. package/lib/components/features/documents/TMDownloadRelationViewerSection.d.ts +23 -0
  35. package/lib/components/features/documents/TMDownloadRelationViewerSection.js +173 -0
  36. package/lib/components/features/documents/TMFileUploader.js +1 -1
  37. package/lib/components/features/documents/TMMasterDetailDcmts.d.ts +4 -0
  38. package/lib/components/features/documents/TMMasterDetailDcmts.js +29 -9
  39. package/lib/components/features/documents/TMMergeToPdfForm.d.ts +26 -0
  40. package/lib/components/features/documents/TMMergeToPdfForm.js +293 -0
  41. package/lib/components/features/documents/TMRelationViewer.d.ts +13 -0
  42. package/lib/components/features/documents/TMRelationViewer.js +75 -6
  43. package/lib/components/features/documents/copyAndMergeDcmtsShared.d.ts +71 -0
  44. package/lib/components/features/documents/copyAndMergeDcmtsShared.js +304 -0
  45. package/lib/components/features/search/SignatureParamsManager.d.ts +70 -0
  46. package/lib/components/features/search/SignatureParamsManager.js +145 -0
  47. package/lib/components/features/search/TMSavedQuerySelector.d.ts +2 -2
  48. package/lib/components/features/search/TMSavedQuerySelector.js +3 -2
  49. package/lib/components/features/search/TMSearch.d.ts +6 -1
  50. package/lib/components/features/search/TMSearch.js +16 -10
  51. package/lib/components/features/search/TMSearchQueryPanel.js +1 -1
  52. package/lib/components/features/search/TMSearchResult.d.ts +4 -0
  53. package/lib/components/features/search/TMSearchResult.js +118 -22
  54. package/lib/components/features/search/TMViewHistoryDcmt.js +1 -2
  55. package/lib/components/features/workflow/TMWorkflowPopup.js +3 -0
  56. package/lib/components/features/workflow/diagram/queryDescriptorParser.js +3 -6
  57. package/lib/components/forms/Login/TMLoginForm.d.ts +9 -0
  58. package/lib/components/forms/Login/TMLoginForm.js +61 -0
  59. package/lib/components/forms/TMResultDialog.d.ts +1 -1
  60. package/lib/components/forms/TMResultDialog.js +4 -2
  61. package/lib/components/grids/TMBlogAttachments.d.ts +1 -0
  62. package/lib/components/grids/TMBlogAttachments.js +38 -12
  63. package/lib/components/grids/TMBlogsPost.js +7 -1
  64. package/lib/components/grids/TMBlogsPostUtils.js +11 -17
  65. package/lib/components/index.d.ts +1 -0
  66. package/lib/components/index.js +1 -0
  67. package/lib/components/pages/TMPage.js +3 -1
  68. package/lib/components/query/TMQueryEditor.js +2 -2
  69. package/lib/components/viewers/TMTidViewer.js +1 -1
  70. package/lib/helper/GlobalStyles.js +6 -0
  71. package/lib/helper/SDKUI_Globals.d.ts +15 -0
  72. package/lib/helper/SDKUI_Globals.js +15 -1
  73. package/lib/helper/SDKUI_Localizator.d.ts +106 -2
  74. package/lib/helper/SDKUI_Localizator.js +1060 -12
  75. package/lib/helper/TMPdfViewer.js +25 -24
  76. package/lib/helper/TMUtils.d.ts +20 -0
  77. package/lib/helper/TMUtils.js +17 -0
  78. package/lib/helper/ZipManager.d.ts +56 -0
  79. package/lib/helper/ZipManager.js +127 -0
  80. package/lib/helper/index.d.ts +1 -0
  81. package/lib/helper/index.js +1 -0
  82. package/lib/hooks/useDataUserIdItem.js +6 -4
  83. package/lib/hooks/useDcmtOperations.d.ts +9 -2
  84. package/lib/hooks/useDcmtOperations.js +78 -35
  85. package/lib/hooks/useDocumentOperations.d.ts +5 -0
  86. package/lib/hooks/useDocumentOperations.js +238 -27
  87. package/lib/hooks/useForm.js +5 -2
  88. package/lib/hooks/useResizeObserver.d.ts +1 -1
  89. package/lib/hooks/useResizeObserver.js +16 -15
  90. package/lib/ts/types.d.ts +1 -0
  91. package/package.json +3 -2
@@ -0,0 +1,293 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useRef, useState } from 'react';
3
+ import { DownloadTypes } from '../../../ts';
4
+ import { calcResponsiveSizes, IconFolderOpen, IconPlay, IconUndo, isConvertibleToPdfExt, SDKUI_Globals, SDKUI_Localizator, } from '../../../helper';
5
+ import { DcmtOpers, FileFormats, GeneralRetrieveFormats, ResultTypes, RetrieveFileOptions, ValidationItem, } from '@topconsultnpm/sdk-ts';
6
+ import TMModal from '../../base/TMModal';
7
+ import { useDeviceType } from '../../base/TMDeviceProvider';
8
+ import TMTextBox from '../../editors/TMTextBox';
9
+ import { ButtonNames, TMMessageBoxManager } from '../../base/TMPopUp';
10
+ import { useDcmtOperations } from '../../../hooks/useDcmtOperations';
11
+ import { TMLayoutWaitingContainer } from '../../base/TMWaitPanel';
12
+ import { TMSplitterLayout } from '../../base/TMLayout';
13
+ import { TMColors } from '../../../utils/theme';
14
+ import TMDownloadRelationViewerSection from './TMDownloadRelationViewerSection';
15
+ import { getDcmtInfosToDownload, getFloatingLabelStyle, isDirectoryPickerSupported, isPdfExt, MIN_PDF_FOR_MERGE, } from './copyAndMergeDcmtsShared';
16
+ import ShowAlert from '../../base/TMAlert';
17
+ import TMTooltip from '../../base/TMTooltip';
18
+ /**
19
+ * Form per l'unione di più documenti PDF in un singolo file.
20
+ * Condivide TMDownloadRelationViewerSection e gli helper in copyAndMergeDcmtsShared
21
+ * con TMCopyToFolderForm.
22
+ */
23
+ const TMMergeToPdfForm = ({ mode, selectedDcmtInfos, onClose, showTMRelationViewer, mergePdfManager, allTasks, getAllTasks, deleteTaskByIdsCallback, addTaskCallback, editTaskCallback, handleNavigateToWGs, handleNavigateToDossiers }) => {
24
+ const { abortController, showWaitPanel, waitPanelTitle, showPrimary, waitPanelTextPrimary, waitPanelValuePrimary, waitPanelMaxValuePrimary, showSecondary, waitPanelTextSecondary, waitPanelValueSecondary, waitPanelMaxValueSecondary, downloadDcmtsAsync, } = useDcmtOperations();
25
+ const deviceType = useDeviceType();
26
+ const [pdfFileName, setPdfFileName] = useState('merged.pdf');
27
+ const [destinationFolder, setDestinationFolder] = useState('Download');
28
+ const [selectedItemsRelationViewer, setSelectedItemsRelationViewer] = useState([]);
29
+ // ---- Ref per la selezione cartella (File System Access API) ----
30
+ const folderHandleRef = useRef(null);
31
+ const skipSelectFolderRef = useRef(false);
32
+ const isPickerActiveRef = useRef(false);
33
+ const isUsingDefaultDownloads = destinationFolder === 'Download' && !folderHandleRef.current;
34
+ // ---- Handler dei campi cartella ----
35
+ const handleFolderValueChange = (e) => {
36
+ setDestinationFolder(e.target.value);
37
+ folderHandleRef.current = null;
38
+ };
39
+ const handleSelectFolder = async () => {
40
+ if (skipSelectFolderRef.current) {
41
+ skipSelectFolderRef.current = false;
42
+ return;
43
+ }
44
+ if (isPickerActiveRef.current)
45
+ return;
46
+ try {
47
+ isPickerActiveRef.current = true;
48
+ const dirHandle = await window.showDirectoryPicker({ mode: 'readwrite' });
49
+ folderHandleRef.current = dirHandle;
50
+ setDestinationFolder(dirHandle.name);
51
+ }
52
+ catch (err) {
53
+ if (err.name !== 'AbortError') {
54
+ console.error('Error selecting folder::', err);
55
+ }
56
+ }
57
+ finally {
58
+ isPickerActiveRef.current = false;
59
+ }
60
+ };
61
+ const getTitle = () => {
62
+ const count = ` (${selectedDcmtInfos.length})`;
63
+ const modeLabelMap = {
64
+ onlySelected: SDKUI_Localizator.SelectedDocuments,
65
+ customized: SDKUI_Localizator.SelectedDocumentsAndRelated,
66
+ };
67
+ const modeLabel = modeLabelMap[mode] || modeLabelMap.onlySelected;
68
+ return `${SDKUI_Localizator.MergeToPdf} - ${modeLabel}${count}`;
69
+ };
70
+ // ---- Validazione ----
71
+ const pdfValidationItems = [];
72
+ if (!pdfFileName.trim()) {
73
+ pdfValidationItems.push(new ValidationItem(ResultTypes.ERROR, SDKUI_Localizator.PdfFileName, SDKUI_Localizator.RequiredField));
74
+ }
75
+ // ---- Statistiche selezione PDF / convertibili / non-PDF ----
76
+ const convertibleSelectedItems = (() => {
77
+ if (showTMRelationViewer) {
78
+ return selectedItemsRelationViewer
79
+ .filter(i => i.isDcmt && isConvertibleToPdfExt(i.fileExt))
80
+ .map(i => ({ key: `${i.tid}_${i.did}`, ext: i.fileExt ?? undefined }));
81
+ }
82
+ return selectedDcmtInfos
83
+ .filter(d => isConvertibleToPdfExt(d.FILEEXT))
84
+ .map(d => ({ key: `${d.TID}_${d.DID}`, ext: d.FILEEXT }));
85
+ })();
86
+ const hasConvertibleSelected = convertibleSelectedItems.length > 0;
87
+ const nonPdfSelectedItems = (() => {
88
+ if (showTMRelationViewer) {
89
+ return selectedItemsRelationViewer
90
+ .filter(i => i.isDcmt && !isPdfExt(i.fileExt) && !isConvertibleToPdfExt(i.fileExt))
91
+ .map(i => ({ key: `${i.tid}_${i.did}`, ext: i.fileExt ?? undefined }));
92
+ }
93
+ return selectedDcmtInfos
94
+ .filter(d => !isPdfExt(d.FILEEXT) && !isConvertibleToPdfExt(d.FILEEXT))
95
+ .map(d => ({ key: `${d.TID}_${d.DID}`, ext: d.FILEEXT }));
96
+ })();
97
+ const hasNonPdfSelected = nonPdfSelectedItems.length > 0;
98
+ // Conteggio file unibili: PDF nativi + file convertibili
99
+ const mergeableSelectedCount = (() => {
100
+ if (showTMRelationViewer) {
101
+ return selectedItemsRelationViewer.filter(i => i.isDcmt && (isPdfExt(i.fileExt) || isConvertibleToPdfExt(i.fileExt))).length;
102
+ }
103
+ return selectedDcmtInfos.filter(d => isPdfExt(d.FILEEXT) || isConvertibleToPdfExt(d.FILEEXT)).length;
104
+ })();
105
+ const hasEnoughPdfForMerge = mergeableSelectedCount >= MIN_PDF_FOR_MERGE;
106
+ const isFormValid = () => pdfValidationItems.length === 0;
107
+ // ---- Recupero dei file PDF da unire (condiviso tra Esegui e Anteprima) ----
108
+ const collectPdfFilesToMerge = async () => {
109
+ const dcmtInfosToDownload = getDcmtInfosToDownload(selectedDcmtInfos, selectedItemsRelationViewer, showTMRelationViewer);
110
+ if (dcmtInfosToDownload.length === 0) {
111
+ TMMessageBoxManager.show({ message: SDKUI_Localizator.NoDcmtSelected, buttons: [ButtonNames.OK] });
112
+ return null;
113
+ }
114
+ const nonPdfKeys = new Set(nonPdfSelectedItems.map(i => i.key));
115
+ const pdfDcmtInfosToDownload = dcmtInfosToDownload.filter(d => !nonPdfKeys.has(`${d.TID}_${d.DID}`));
116
+ if (pdfDcmtInfosToDownload.length === 0) {
117
+ TMMessageBoxManager.show({
118
+ message: SDKUI_Localizator.NoPdfOrConvertibleFilesAmongSelectedMergeBlocked,
119
+ buttons: [ButtonNames.OK]
120
+ });
121
+ return null;
122
+ }
123
+ if (pdfDcmtInfosToDownload.length < MIN_PDF_FOR_MERGE) {
124
+ TMMessageBoxManager.show({
125
+ message: SDKUI_Localizator.OnlyOnePdfOrConvertibleFileMergeBlocked.replaceParams(MIN_PDF_FOR_MERGE.toString()),
126
+ buttons: [ButtonNames.OK]
127
+ });
128
+ return null;
129
+ }
130
+ const pdfFiles = [];
131
+ const rfo = new RetrieveFileOptions();
132
+ rfo.retrieveReason = DcmtOpers.None;
133
+ rfo.generalRetrieveFormat = GeneralRetrieveFormats.OriginalUnsigned;
134
+ rfo.cvtFormat = FileFormats.PDF;
135
+ rfo.invoiceRetrieveFormat = SDKUI_Globals.userSettings?.searchSettings.invoiceRetrieveFormat;
136
+ rfo.orderRetrieveFormat = SDKUI_Globals.userSettings?.searchSettings.orderRetrieveFormat;
137
+ const collectFileForMerge = async (file, _dcmtInfo) => {
138
+ pdfFiles.push(file);
139
+ };
140
+ await downloadDcmtsAsync(pdfDcmtInfosToDownload, DownloadTypes.Dcmt, 'download', collectFileForMerge, undefined, true, rfo, false);
141
+ if (pdfFiles.length === 0) {
142
+ TMMessageBoxManager.show({ message: SDKUI_Localizator.NoFilesAvailableForMerge, buttons: [ButtonNames.OK] });
143
+ return null;
144
+ }
145
+ return pdfFiles;
146
+ };
147
+ // ---- Esecuzione: scarica i PDF e li unisce ----
148
+ const run = async () => {
149
+ if (pdfValidationItems.length > 0) {
150
+ TMMessageBoxManager.show({ message: SDKUI_Localizator.PleaseCorrectErrorsBeforeProceeding, buttons: [ButtonNames.OK] });
151
+ return;
152
+ }
153
+ if (!mergePdfManager) {
154
+ TMMessageBoxManager.show({ message: SDKUI_Localizator.PdfMergeFeatureNotAvailable, buttons: [ButtonNames.OK] });
155
+ return;
156
+ }
157
+ const pdfFiles = await collectPdfFilesToMerge();
158
+ if (!pdfFiles)
159
+ return;
160
+ const finalName = pdfFileName.trim().toLowerCase().endsWith('.pdf') ? pdfFileName.trim() : pdfFileName.trim() + '.pdf';
161
+ try {
162
+ // Se c'è una cartella selezionata (non Download di default), salva direttamente nella cartella
163
+ if (folderHandleRef.current) {
164
+ const mergedFile = await mergePdfManager.mergeToFile(pdfFiles, finalName);
165
+ const fileHandle = await folderHandleRef.current.getFileHandle(finalName, { create: true });
166
+ const writable = await fileHandle.createWritable();
167
+ await writable.write(mergedFile);
168
+ await writable.close();
169
+ ShowAlert({ message: SDKUI_Localizator.PdfMergedSavedInFolder.replaceParams(folderHandleRef.current.name), mode: 'success', duration: 5000, title: SDKUI_Localizator.MergeToPdf });
170
+ }
171
+ else {
172
+ // Altrimenti usa il download standard del browser
173
+ await mergePdfManager.mergeAndDownload(pdfFiles, finalName);
174
+ ShowAlert({ message: SDKUI_Localizator.PdfMergedSavedInFolder.replaceParams('Download'), mode: 'success', duration: 5000, title: SDKUI_Localizator.MergeToPdf });
175
+ }
176
+ }
177
+ catch (err) {
178
+ console.error('Error merging PDF or convertible files:', err);
179
+ TMMessageBoxManager.show({ message: SDKUI_Localizator.PdfMergeError, buttons: [ButtonNames.OK] });
180
+ return;
181
+ }
182
+ onClose();
183
+ };
184
+ // ---- Render della sezione configurazione (solo PDF file name + warning) ----
185
+ const configSection = (_jsxs("div", { style: {
186
+ position: 'relative',
187
+ border: `1px solid ${TMColors.border_normal}`,
188
+ borderRadius: '8px',
189
+ padding: '8px',
190
+ height: showTMRelationViewer ? 'calc(100% - 7px)' : undefined,
191
+ marginTop: '7px',
192
+ flex: showTMRelationViewer ? undefined : 1,
193
+ minHeight: 0,
194
+ display: 'flex',
195
+ flexDirection: 'column',
196
+ boxSizing: 'border-box'
197
+ }, children: [_jsx("span", { style: getFloatingLabelStyle(), title: `${SDKUI_Localizator.ConfigurationParameters} (${SDKUI_Localizator.MergeToPdf})`, children: SDKUI_Localizator.ConfigurationParameters }), _jsxs("div", { style: {
198
+ display: 'flex',
199
+ flexDirection: 'column',
200
+ flex: 1,
201
+ overflowY: 'auto',
202
+ minHeight: 0,
203
+ }, children: [_jsxs("div", { style: { display: 'flex', flexWrap: 'wrap', columnGap: '10px' }, children: [isDirectoryPickerSupported() ? (_jsx("div", { style: { flex: '1 1 280px', minWidth: '280px' }, children: _jsx(TMTextBox, { label: SDKUI_Localizator.FolderName, value: destinationFolder, onValueChanged: handleFolderValueChange, readOnly: true, placeHolder: "Download", onClick: handleSelectFolder, buttons: [
204
+ {
205
+ icon: _jsx(IconFolderOpen, {}),
206
+ text: SDKUI_Localizator.SelectFolder,
207
+ onClick: handleSelectFolder
208
+ },
209
+ ...(!isUsingDefaultDownloads ? [{
210
+ icon: _jsx(IconUndo, {}),
211
+ text: SDKUI_Localizator.RestoreDownload,
212
+ onClick: () => {
213
+ skipSelectFolderRef.current = true;
214
+ setDestinationFolder('Download');
215
+ folderHandleRef.current = null;
216
+ }
217
+ }] : [])
218
+ ] }) })) : (_jsx("div", { style: { flex: '1 1 280px', minWidth: '280px' }, children: _jsxs("div", { style: { position: 'relative', marginTop: '16px' }, children: [_jsx("span", { style: {
219
+ position: 'absolute',
220
+ top: '-10px',
221
+ left: '12px',
222
+ fontSize: '0.9rem',
223
+ color: TMColors.label_normal,
224
+ padding: '0 3px',
225
+ userSelect: 'none',
226
+ WebkitTouchCallout: 'none',
227
+ WebkitUserSelect: 'none',
228
+ width: 'max-content',
229
+ backgroundColor: TMColors.default_background,
230
+ zIndex: 1,
231
+ }, children: SDKUI_Localizator.Path }), _jsxs("div", { style: { border: `1px solid ${TMColors.border_normal}`, borderRadius: '5px', padding: '4px 10px 4px 13px', display: 'flex', alignItems: 'center', gap: '8px', backgroundColor: '#fafafa' }, children: [_jsx(IconFolderOpen, {}), _jsx("span", { style: { fontSize: '0.9rem', color: '#333' }, children: "Download" })] }), _jsx("span", { style: { fontSize: '0.8rem', color: '#888', fontStyle: 'italic', marginTop: '4px', display: 'block' }, children: SDKUI_Localizator.BrowserDoesNotSupportFolderSelection })] }) })), _jsx("div", { style: { flex: '1.5 1 420px', minWidth: '350px', width: '100%' }, children: _jsx(TMTextBox, { label: SDKUI_Localizator.PdfFileName, value: pdfFileName, validationItems: pdfValidationItems, autoComplete: "one-time-code", onValueChanged: (e) => setPdfFileName(e.target.value) }) })] }), (hasConvertibleSelected || !hasEnoughPdfForMerge || hasNonPdfSelected) && (_jsxs("div", { style: {
232
+ display: 'flex',
233
+ flexDirection: 'column',
234
+ gap: '6px',
235
+ padding: '8px 12px',
236
+ border: '1px solid #c0c0c0',
237
+ borderRadius: '5px',
238
+ backgroundColor: '#f8f8f8',
239
+ fontSize: '0.85rem',
240
+ marginTop: '12px',
241
+ }, children: [_jsx("div", { style: {
242
+ display: 'flex',
243
+ alignItems: 'center',
244
+ gap: '6px',
245
+ fontWeight: 600,
246
+ color: '#555',
247
+ fontSize: '0.9rem',
248
+ borderBottom: '1px solid #e0e0e0',
249
+ paddingBottom: '6px',
250
+ marginBottom: '2px'
251
+ }, children: SDKUI_Localizator.NotesAndWarnings }), _jsxs("ul", { style: { margin: 0, paddingLeft: '18px', display: 'flex', flexDirection: 'column', gap: '4px' }, children: [hasConvertibleSelected && (_jsx("li", { style: { color: '#00527a' }, children: (() => {
252
+ const extSet = Array.from(new Set(convertibleSelectedItems.map(i => (i.ext ?? '').toString().trim().toLowerCase().replace(/^\./, '')).filter(e => e.length > 0)));
253
+ const extLabel = extSet.length > 0
254
+ ? ` (${extSet.map(e => '.' + e).join(', ')})`
255
+ : '';
256
+ return convertibleSelectedItems.length === 1
257
+ ? SDKUI_Localizator.DocumentWillBeConvertedDuringMerge.replaceParams(extLabel)
258
+ : SDKUI_Localizator.DocumentsWillBeConvertedDuringMerge.replaceParams(convertibleSelectedItems.length.toString(), extLabel);
259
+ })() })), !hasEnoughPdfForMerge && (_jsx("li", { style: { color: '#7a5d00' }, children: mergeableSelectedCount === 0
260
+ ? SDKUI_Localizator.NoPdfOrConvertibleFilesSelected_Param.replaceParams(MIN_PDF_FOR_MERGE.toString())
261
+ : SDKUI_Localizator.OnlyOnePdfOrConvertibleFileSelected.replaceParams(MIN_PDF_FOR_MERGE.toString()) })), hasNonPdfSelected && (_jsx("li", { style: { color: '#7a5d00' }, children: (() => {
262
+ const extSet = Array.from(new Set(nonPdfSelectedItems.map(i => (i.ext ?? '').toString().trim().toLowerCase().replace(/^\./, '')).filter(e => e.length > 0)));
263
+ const extLabel = extSet.length > 0 ? ` (${extSet.map(e => '.' + e).join(', ')})` : '';
264
+ return nonPdfSelectedItems.length === 1
265
+ ? SDKUI_Localizator.FileNotPdfWillBeExcluded.replaceParams(extLabel)
266
+ : SDKUI_Localizator.FilesNotPdfWillBeExcluded.replaceParams(nonPdfSelectedItems.length.toString(), extLabel);
267
+ })() }))] })] }))] })] }));
268
+ return (_jsx(TMModal, { width: calcResponsiveSizes(deviceType, showTMRelationViewer ? '90%' : '500px', showTMRelationViewer ? '90%' : '500px', '95%'), height: showTMRelationViewer ? '95%' : 'auto', title: getTitle(), onClose: onClose, showCloseButton: true, children: _jsx(TMLayoutWaitingContainer, { direction: 'vertical', showWaitPanel: showWaitPanel, showWaitPanelPrimary: showPrimary, showWaitPanelSecondary: showSecondary, waitPanelTitle: waitPanelTitle, waitPanelTextPrimary: waitPanelTextPrimary, waitPanelValuePrimary: waitPanelValuePrimary, waitPanelMaxValuePrimary: waitPanelMaxValuePrimary, waitPanelTextSecondary: waitPanelTextSecondary, waitPanelValueSecondary: waitPanelValueSecondary, waitPanelMaxValueSecondary: waitPanelMaxValueSecondary, isCancelable: true, abortController: abortController, children: _jsxs("div", { onContextMenu: (e) => e.preventDefault(), style: { display: 'flex', flexDirection: 'column', gap: '16px', padding: '16px', width: '100%', height: '100%', boxSizing: 'border-box', overflow: 'hidden' }, children: [showTMRelationViewer ? (_jsx("div", { style: { flex: 1, minHeight: 0, display: 'flex', flexDirection: 'column' }, children: _jsxs(TMSplitterLayout, { direction: 'vertical', showSeparator: true, separatorSize: 8, separatorColor: 'transparent', separatorActiveColor: 'transparent', overflow: 'hidden', min: ["50", "50"], start: ["70%", "30%"], children: [_jsx(TMDownloadRelationViewerSection, { selectedDcmtInfos: selectedDcmtInfos, onSelectionChanged: setSelectedItemsRelationViewer, allTasks: allTasks, getAllTasks: getAllTasks, deleteTaskByIdsCallback: deleteTaskByIdsCallback, addTaskCallback: addTaskCallback, editTaskCallback: editTaskCallback, handleNavigateToWGs: handleNavigateToWGs, handleNavigateToDossiers: handleNavigateToDossiers }), configSection] }, "TMMergeToPdf-relation-config") })) : (configSection), _jsx("div", { style: { display: 'flex', justifyContent: 'center', alignItems: 'center', flexShrink: 0, marginTop: '12px' }, children: _jsxs("button", { disabled: !isFormValid() || (showTMRelationViewer && selectedItemsRelationViewer.filter(i => i.isDcmt).length === 0) || !hasEnoughPdfForMerge, onClick: run, style: {
269
+ display: 'flex',
270
+ alignItems: 'center',
271
+ justifyContent: 'center',
272
+ gap: '6px',
273
+ padding: '8px 20px',
274
+ fontSize: '0.95rem',
275
+ fontWeight: 600,
276
+ color: '#fff',
277
+ background: (!isFormValid() || (showTMRelationViewer && selectedItemsRelationViewer.filter(i => i.isDcmt).length === 0) || !hasEnoughPdfForMerge)
278
+ ? '#b0b0b0'
279
+ : 'linear-gradient(135deg, #28a745 0%, #218838 100%)',
280
+ border: 'none',
281
+ borderRadius: '6px',
282
+ cursor: (!isFormValid() || (showTMRelationViewer && selectedItemsRelationViewer.filter(i => i.isDcmt).length === 0) || !hasEnoughPdfForMerge) ? 'not-allowed' : 'pointer',
283
+ boxShadow: (!isFormValid() || (showTMRelationViewer && selectedItemsRelationViewer.filter(i => i.isDcmt).length === 0) || !hasEnoughPdfForMerge)
284
+ ? 'none'
285
+ : '0 2px 8px rgba(40, 167, 69, 0.35)',
286
+ transition: 'all 0.2s ease',
287
+ }, children: [_jsx(IconPlay, {}), _jsx("span", { children: mergeableSelectedCount === 0
288
+ ? SDKUI_Localizator.MergeToPdf
289
+ : mergeableSelectedCount > 999
290
+ ? _jsx(TMTooltip, { content: mergeableSelectedCount.toString(), children: SDKUI_Localizator.MergeFilesToPdf.replaceParams('999+') })
291
+ : SDKUI_Localizator.MergeFilesToPdf.replaceParams(mergeableSelectedCount.toString()) })] }) })] }) }) }));
292
+ };
293
+ export default TMMergeToPdfForm;
@@ -50,6 +50,8 @@ export interface TMRelationViewerProps {
50
50
  onFocusedItemChanged?: (item: RelationTreeItem | null) => void;
51
51
  /** Callback when selected items change */
52
52
  onSelectedItemsChanged?: (items: RelationTreeItem[]) => void;
53
+ /** Callback fired when the internal tree data changes, providing a flattened list of all items currently rendered */
54
+ onAllItemsChanged?: (items: RelationTreeItem[]) => void;
53
55
  /** Callback when a document is double-clicked */
54
56
  onDocumentDoubleClick?: (tid: number, did: number, name?: string) => void;
55
57
  /** Custom item renderer (full control). Return undefined to use default renderer. */
@@ -109,6 +111,17 @@ export interface TMRelationViewerProps {
109
111
  * (used in master-detail context)
110
112
  */
111
113
  focusedItemFormData?: MetadataValueDescriptorEx[];
114
+ /**
115
+ * If true, shows a toolbar button to expand/collapse all tree nodes.
116
+ * Default: false
117
+ */
118
+ showExpandAllButton?: boolean;
119
+ /**
120
+ * If true, all tree nodes are expanded by default on initial load.
121
+ * If false (default), uses the standard initial expansion behavior
122
+ * (root container + first document + first correlation folder).
123
+ */
124
+ defaultExpandAll?: boolean;
112
125
  }
113
126
  /**
114
127
  * Check if document type has detail relations
@@ -1,9 +1,9 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  import React, { useCallback, useEffect, useMemo, useState } from 'react';
3
3
  import { DcmtTypeListCacheService, SDK_Globals, DataColumnTypes, MetadataFormats, SystemMIDs, MetadataDataDomains, RelationCacheService, RelationTypes, UserListCacheService } from "@topconsultnpm/sdk-ts";
4
- import { genUniqueId, IconFolder, IconBackhandIndexPointingRight, IconCircleInfo, getDcmtCicoStatus } from '../../../helper';
4
+ import { genUniqueId, IconFolder, IconBackhandIndexPointingRight, IconCircleInfo, getDcmtCicoStatus, IconChevronDown, IconChevronRight, SDKUI_Localizator } from '../../../helper';
5
5
  import { TMColors } from '../../../utils/theme';
6
- import { StyledDivHorizontal, StyledBadge } from '../../base/Styled';
6
+ import { StyledDivHorizontal, StyledBadge, StyledToolbarForm } from '../../base/Styled';
7
7
  import TMTreeView from '../../base/TMTreeView';
8
8
  import { TMWaitPanel } from '../../base/TMWaitPanel';
9
9
  import TMDataListItemViewer from '../../viewers/TMDataListItemViewer';
@@ -136,12 +136,14 @@ export const searchResultToDataSource = async (searchResult, hideSysMetadata) =>
136
136
  }
137
137
  return output;
138
138
  };
139
- const TMRelationViewer = ({ inputDcmts, isForMaster = false, showCurrentDcmtIndicator = true, allowShowZeroDcmts = true, initialShowZeroDcmts = false, allowedTIDs, allowMultipleSelection = false, focusedItem, selectedItems, onFocusedItemChanged, onSelectedItemsChanged, onDocumentDoubleClick, customItemRender, customDocumentStyle, customMainContainerContent, customDocumentContent, showMetadataNames = false, maxDepthLevel = 2, invertMasterNavigation = true, additionalStaticItems, showMainDocument = true, labelMainContainer, onNoRelationsFound, onItemContextMenu, focusedItemFormData = [] }) => {
139
+ const TMRelationViewer = ({ inputDcmts, isForMaster = false, showCurrentDcmtIndicator = true, allowShowZeroDcmts = true, initialShowZeroDcmts = false, allowedTIDs, allowMultipleSelection = false, focusedItem, selectedItems, onFocusedItemChanged, onSelectedItemsChanged, onAllItemsChanged, onDocumentDoubleClick, customItemRender, customDocumentStyle, customMainContainerContent, customDocumentContent, showMetadataNames = false, maxDepthLevel = 2, invertMasterNavigation = true, additionalStaticItems, showMainDocument = true, labelMainContainer, onNoRelationsFound, onItemContextMenu, focusedItemFormData = [], showExpandAllButton = false, defaultExpandAll = false }) => {
140
140
  // State
141
141
  const [dcmtTypes, setDcmtTypes] = useState([]);
142
142
  const [treeData, setTreeData] = useState([]);
143
143
  const [showZeroDcmts, setShowZeroDcmts] = useState(initialShowZeroDcmts);
144
144
  const [staticItemsState, setStaticItemsState] = useState([]);
145
+ const [allExpanded, setAllExpanded] = useState(defaultExpandAll);
146
+ const initialExpandAllAppliedRef = React.useRef(false);
145
147
  const [showWaitPanel, setShowWaitPanel] = useState(false);
146
148
  const [waitPanelTextPrimary, setWaitPanelTextPrimary] = useState('');
147
149
  const [waitPanelValuePrimary, setWaitPanelValuePrimary] = useState(0);
@@ -271,7 +273,7 @@ const TMRelationViewer = ({ inputDcmts, isForMaster = false, showCurrentDcmtIndi
271
273
  console.error('❌ Error loading detail documents:', error);
272
274
  }
273
275
  return items;
274
- }, [allowedTIDs, focusedItemFormData, focusedItem?.dtd?.id]);
276
+ }, [allowedTIDs]);
275
277
  /**
276
278
  * Recursively retrieve master documents
277
279
  */
@@ -734,6 +736,24 @@ const TMRelationViewer = ({ inputDcmts, isForMaster = false, showCurrentDcmtIndi
734
736
  // and may contain custom properties that would be lost by the spread operator
735
737
  return [...treeData, ...itemsToMerge];
736
738
  }, [treeData, additionalStaticItems, staticItemsState]);
739
+ /**
740
+ * Notify parent with a flattened list of all items whenever the merged tree data changes.
741
+ */
742
+ useEffect(() => {
743
+ if (!onAllItemsChanged)
744
+ return;
745
+ const flatten = (items) => {
746
+ const out = [];
747
+ for (const it of items) {
748
+ out.push(it);
749
+ if (it.items && Array.isArray(it.items)) {
750
+ out.push(...flatten(it.items));
751
+ }
752
+ }
753
+ return out;
754
+ };
755
+ onAllItemsChanged(flatten(mergedTreeData));
756
+ }, [mergedTreeData, onAllItemsChanged]);
737
757
  /**
738
758
  * Load data when inputs change
739
759
  */
@@ -1118,7 +1138,7 @@ const TMRelationViewer = ({ inputDcmts, isForMaster = false, showCurrentDcmtIndi
1118
1138
  }
1119
1139
  }
1120
1140
  return (_jsxs("div", { onDoubleClick: handleDoubleClick, style: documentStyle, children: [item.did && item.tid && showCurrentDcmtIndicator && inputDcmts?.some(d => d.DID === item.did && d.TID === item.tid) ? _jsx(IconBackhandIndexPointingRight, { fontSize: 22, overflow: 'visible' }) : _jsx(_Fragment, {}), item.values && (_jsx(TMDcmtIcon, { tid: item.values?.TID?.value, did: item.values?.DID?.value, fileExtension: item.values?.FILEEXT?.value, fileCount: item.values?.FILECOUNT?.value, isLexProt: item.values?.IsLexProt?.value, isMail: item.values?.ISMAIL?.value, isShared: item.values?.ISSHARED?.value, isSigned: item.values?.ISSIGNED?.value, downloadMode: 'openInNewWindow' })), checkoutStatusIcon, metadataContent] }));
1121
- }, [onDocumentDoubleClick, showCurrentDcmtIndicator, inputDcmts, customMainContainerContent, customDocumentStyle, customDocumentContent, showMetadataNames, showMainDocument, focusedItemFormData, focusedItem?.dtd, allUsers]);
1141
+ }, [onDocumentDoubleClick, showCurrentDcmtIndicator, inputDcmts, customMainContainerContent, customDocumentStyle, customDocumentContent, showMetadataNames, showMainDocument, allUsers]);
1122
1142
  /**
1123
1143
  * Wrapper renderer that handles custom rendering if provided
1124
1144
  */
@@ -1148,6 +1168,42 @@ const TMRelationViewer = ({ inputDcmts, isForMaster = false, showCurrentDcmtIndi
1148
1168
  const handleSelectedItemsChanged = useCallback((items) => {
1149
1169
  onSelectedItemsChanged?.(items);
1150
1170
  }, [onSelectedItemsChanged]);
1171
+ /**
1172
+ * Recursively set the `expanded` property on every node that has children loaded.
1173
+ * Nodes without `items` are left untouched (will be loaded lazily on user click).
1174
+ */
1175
+ const setExpandedRecursively = useCallback((nodes, expanded) => {
1176
+ if (!nodes || !Array.isArray(nodes))
1177
+ return [];
1178
+ return nodes.map(node => {
1179
+ const hasChildren = Array.isArray(node.items) && node.items.length > 0;
1180
+ const newItems = hasChildren ? setExpandedRecursively(node.items, expanded) : node.items;
1181
+ // Only toggle expansion if the node is expandable (container/doc with children or marked expandible)
1182
+ const canExpand = hasChildren || node.isExpandible;
1183
+ return { ...node, expanded: canExpand ? expanded : node.expanded, items: newItems };
1184
+ });
1185
+ }, []);
1186
+ const handleToggleExpandAll = useCallback(() => {
1187
+ const next = !allExpanded;
1188
+ setAllExpanded(next);
1189
+ setTreeData(prev => setExpandedRecursively(prev, next));
1190
+ setStaticItemsState(prev => setExpandedRecursively(prev, next));
1191
+ userInteractedWithStaticItemsRef.current = true;
1192
+ }, [allExpanded, setExpandedRecursively]);
1193
+ /**
1194
+ * Apply defaultExpandAll once on initial tree load.
1195
+ */
1196
+ useEffect(() => {
1197
+ if (!defaultExpandAll)
1198
+ return;
1199
+ if (initialExpandAllAppliedRef.current)
1200
+ return;
1201
+ if (!treeData || treeData.length === 0)
1202
+ return;
1203
+ initialExpandAllAppliedRef.current = true;
1204
+ setTreeData(prev => setExpandedRecursively(prev, true));
1205
+ setStaticItemsState(prev => setExpandedRecursively(prev, true));
1206
+ }, [defaultExpandAll, treeData, setExpandedRecursively]);
1151
1207
  /**
1152
1208
  * Handle data changed - separate static items from dynamic tree data
1153
1209
  */
@@ -1169,7 +1225,20 @@ const TMRelationViewer = ({ inputDcmts, isForMaster = false, showCurrentDcmtIndi
1169
1225
  return null;
1170
1226
  return _jsx("div", { style: { padding: '20px', textAlign: 'center', color: '#666' }, children: "Nessuna relazione disponibile." });
1171
1227
  }
1172
- return (_jsxs(_Fragment, { children: [_jsx(TMTreeView, { dataSource: mergedTreeData, itemRender: finalItemRender, calculateItemsForNode: calculateItemsForNode, onDataChanged: handleDataChanged, focusedItem: focusedItem, onFocusedItemChanged: handleFocusedItemChanged, allowMultipleSelection: allowMultipleSelection, selectedItems: selectedItems, itemsPerPage: 100, onSelectionChanged: handleSelectedItemsChanged, onItemContextMenu: onItemContextMenu }), showExpansionWaitPanel && (_jsx(TMWaitPanel, { title: isForMaster ? 'Caricamento documenti master' : 'Caricamento documenti dettaglio', showPrimary: true, textPrimary: expansionWaitPanelText, valuePrimary: expansionWaitPanelValue, maxValuePrimary: expansionWaitPanelMaxValue, isCancelable: true, abortController: expansionAbortController, onAbortClick: (abortController) => {
1228
+ return (_jsxs("div", { style: { display: 'flex', flexDirection: 'column', height: '100%', minHeight: 0, width: '100%' }, children: [showExpandAllButton && (_jsx(StyledToolbarForm, { style: { flexShrink: 0 }, children: _jsxs("button", { type: "button", onClick: handleToggleExpandAll, title: allExpanded ? SDKUI_Localizator.CollapseAll : SDKUI_Localizator.ExpandAll, style: {
1229
+ display: 'inline-flex',
1230
+ alignItems: 'center',
1231
+ gap: '4px',
1232
+ padding: '4px 8px',
1233
+ fontSize: '0.8rem',
1234
+ color: TMColors.primary,
1235
+ background: 'transparent',
1236
+ border: `1px solid ${TMColors.border_normal}`,
1237
+ borderRadius: '4px',
1238
+ cursor: 'pointer',
1239
+ lineHeight: 1,
1240
+ whiteSpace: 'nowrap'
1241
+ }, children: [allExpanded ? _jsx(IconChevronRight, { fontSize: 14 }) : _jsx(IconChevronDown, { fontSize: 14 }), _jsx("span", { children: allExpanded ? SDKUI_Localizator.CollapseAll : SDKUI_Localizator.ExpandAll })] }) })), _jsx("div", { style: { flex: 1, minHeight: 0, overflow: 'auto' }, children: _jsx(TMTreeView, { dataSource: mergedTreeData, itemRender: finalItemRender, calculateItemsForNode: calculateItemsForNode, onDataChanged: handleDataChanged, focusedItem: focusedItem, onFocusedItemChanged: handleFocusedItemChanged, allowMultipleSelection: allowMultipleSelection, selectedItems: selectedItems, itemsPerPage: 100, onSelectionChanged: handleSelectedItemsChanged, onItemContextMenu: onItemContextMenu }) }), showExpansionWaitPanel && (_jsx(TMWaitPanel, { title: isForMaster ? 'Caricamento documenti master' : 'Caricamento documenti dettaglio', showPrimary: true, textPrimary: expansionWaitPanelText, valuePrimary: expansionWaitPanelValue, maxValuePrimary: expansionWaitPanelMaxValue, isCancelable: true, abortController: expansionAbortController, onAbortClick: (abortController) => {
1173
1242
  setTimeout(() => {
1174
1243
  abortController?.abort();
1175
1244
  }, 100);
@@ -0,0 +1,71 @@
1
+ import React from 'react';
2
+ import { DcmtInfo } from '../../../ts';
3
+ import { DocumentDownloadSettings, FileNamingMode } from '../../../helper';
4
+ import { IRelatedDcmt } from './TMMasterDetailDcmts';
5
+ /** Numero minimo di file PDF necessari per poterli unire in un unico documento. */
6
+ export declare const MIN_PDF_FOR_MERGE = 2;
7
+ /** Stile per le etichette "floating" posizionate sopra i bordi dei container */
8
+ export declare const getFloatingLabelStyle: () => React.CSSProperties;
9
+ /** Chiave univoca per un IRelatedDcmt basata sulla coppia tid+did */
10
+ export declare const getDcmtKey: (item: IRelatedDcmt) => string;
11
+ /** Deduplica gli IRelatedDcmt in base alla coppia tid+did (mantiene la prima occorrenza) */
12
+ export declare const dedupeByTidDid: (items: ReadonlyArray<IRelatedDcmt>) => Array<IRelatedDcmt>;
13
+ /** Verifica se showDirectoryPicker è supportato (Chrome, Edge) */
14
+ export declare const isDirectoryPickerSupported: () => boolean;
15
+ /**
16
+ * Verifica se un'estensione (con o senza punto iniziale, case-insensitive) è "pdf"
17
+ * o un PDF firmato (pdf.p7m, pdf.tsd, pdf.m7m).
18
+ * Questi formati firmati vengono trattati come PDF nativi perché con OriginalUnsigned
19
+ * il file viene restituito senza firma.
20
+ */
21
+ export declare const isPdfExt: (ext: string | null | undefined) => boolean;
22
+ /**
23
+ * Estrae l'estensione completa da un nome file, gestendo estensioni composite
24
+ * a più livelli come .PDF.P7M, .XML.P7M.TS, etc.
25
+ *
26
+ * Esempi:
27
+ * - "documento.pdf" -> ".pdf"
28
+ * - "DCMT_123.PDF.P7M" -> ".PDF.P7M"
29
+ * - "fattura.xml.p7m" -> ".xml.p7m"
30
+ * - "example.XML.P7M.TS" -> ".XML.P7M.TS"
31
+ * - "file.tar.gz" -> ".gz" (tar.gz non è gestito come firma)
32
+ * - "file" -> ""
33
+ */
34
+ export declare const getFullFileExtension: (fileName: string, depth?: number) => string;
35
+ /**
36
+ * Estrae il nome base del file (senza estensione completa).
37
+ * Gestisce estensioni composite come .PDF.P7M
38
+ */
39
+ export declare const getFileBaseName: (fileName: string) => string;
40
+ /** Sanitizza il nome file per Windows (rimuove caratteri illegali e limita la lunghezza) */
41
+ export declare const sanitizeFileName: (fileName: string, fallbackName: string, maxLength?: number) => string;
42
+ /** Verifica se un file esiste nella cartella (FileSystemDirectoryHandle) */
43
+ export declare const fileExists: (dirHandle: FileSystemDirectoryHandle, fileName: string) => Promise<boolean>;
44
+ /** Genera un nome file univoco aggiungendo un suffisso numerico se già esistente */
45
+ export declare const generateUniqueFileName: (dirHandle: FileSystemDirectoryHandle, originalName: string) => Promise<string>;
46
+ /** Recupera il nome leggibile del tipo documento (cache) */
47
+ export declare const getTypeName: (tid: number | undefined) => Promise<string>;
48
+ /** Formatta un valore convertendo le date in formato dd-MM-yyyy [HH-mm-ss] */
49
+ export declare const formatMetadataValue: (value: string) => string;
50
+ /** Recupera i metadati filtrati (primi 5 con mid > 100 e valore presente), concatenati con separatorChar */
51
+ export declare const getFilteredMetadata: (tid: number, did: number, separatorChar: string) => Promise<string | null>;
52
+ /** Opzioni di naming necessarie per generare il nome del file di destinazione */
53
+ export interface IFileNamingOptions {
54
+ fileNamingMode: FileNamingMode;
55
+ separatorChar: string;
56
+ }
57
+ /** Genera il nome file di destinazione in base alle impostazioni di naming */
58
+ export declare const generateTargetFileName: (file: File, dcmtInfo: DcmtInfo, options: IFileNamingOptions) => Promise<string>;
59
+ /**
60
+ * Restituisce i documenti da scaricare:
61
+ * - se è visibile il TMRelationViewer, usa gli isDcmt selezionati al suo interno
62
+ * - altrimenti restituisce i selectedDcmtInfos passati come prop
63
+ */
64
+ export declare const getDcmtInfosToDownload: (selectedDcmtInfos: Array<DcmtInfo>, selectedItemsRelationViewer: Array<IRelatedDcmt>, showTMRelationViewer: boolean) => Array<DcmtInfo>;
65
+ /**
66
+ * Costruisce lo stato iniziale di DocumentDownloadSettings combinando:
67
+ * - i valori salvati in userSettings (se presenti)
68
+ * - i default della classe
69
+ * Nota: destinationFolder e zipPassword vengono sempre resettati per sicurezza.
70
+ */
71
+ export declare const buildInitialDownloadSettings: (saved: DocumentDownloadSettings | undefined, defaultInvoiceFormat: DocumentDownloadSettings["invoiceFormat"], defaultOrderFormat: DocumentDownloadSettings["orderFormat"]) => DocumentDownloadSettings;