@webiny/app-file-manager 6.4.11 → 6.6.0-alpha.1

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 (235) hide show
  1. package/app.js +21 -2
  2. package/app.js.map +1 -1
  3. package/domain/tagsHelpers.d.ts +2 -2
  4. package/domain/types.d.ts +0 -8
  5. package/features/permissions/abstractions.d.ts +2 -2
  6. package/features/permissions/feature.d.ts +3 -3
  7. package/features/shared/FILE_FIELDS.js +4 -0
  8. package/features/shared/FILE_FIELDS.js.map +1 -1
  9. package/features/shared/FileFieldsProviderWithWcp.d.ts +5 -5
  10. package/features/shared/FileFieldsProviderWithWcp.js +7 -7
  11. package/features/shared/FileFieldsProviderWithWcp.js.map +1 -1
  12. package/modules/AiEnrichment/AiImageEnrichmentEventHandler.d.ts +18 -0
  13. package/modules/AiEnrichment/AiImageEnrichmentEventHandler.js +33 -0
  14. package/modules/AiEnrichment/AiImageEnrichmentEventHandler.js.map +1 -0
  15. package/modules/AiEnrichment/ReenrichFileGateway.d.ts +12 -0
  16. package/modules/AiEnrichment/ReenrichFileGateway.js +23 -0
  17. package/modules/AiEnrichment/ReenrichFileGateway.js.map +1 -0
  18. package/modules/AiEnrichment/ReenrichWithAi.d.ts +11 -0
  19. package/modules/AiEnrichment/ReenrichWithAi.js +57 -0
  20. package/modules/AiEnrichment/ReenrichWithAi.js.map +1 -0
  21. package/modules/AiEnrichment/ReenrichWithAiPresenter.d.ts +50 -0
  22. package/modules/AiEnrichment/ReenrichWithAiPresenter.js +114 -0
  23. package/modules/AiEnrichment/ReenrichWithAiPresenter.js.map +1 -0
  24. package/modules/AiEnrichment/StreamingPlaceholder.d.ts +15 -0
  25. package/modules/AiEnrichment/StreamingPlaceholder.js +32 -0
  26. package/modules/AiEnrichment/StreamingPlaceholder.js.map +1 -0
  27. package/modules/AiEnrichment/abstractions.d.ts +70 -0
  28. package/modules/AiEnrichment/abstractions.js +6 -0
  29. package/modules/AiEnrichment/abstractions.js.map +1 -0
  30. package/modules/AiEnrichment/feature.d.ts +3 -0
  31. package/modules/AiEnrichment/feature.js +21 -0
  32. package/modules/AiEnrichment/feature.js.map +1 -0
  33. package/modules/AiEnrichment/index.d.ts +2 -0
  34. package/modules/AiEnrichment/index.js +9 -0
  35. package/modules/AiEnrichment/index.js.map +1 -0
  36. package/modules/Enterprise/index.js +3 -3
  37. package/modules/Enterprise/index.js.map +1 -1
  38. package/modules/HeadlessCms/fieldType/AssetFieldSettingsModifier.d.ts +26 -0
  39. package/modules/HeadlessCms/fieldType/AssetFieldSettingsModifier.js +39 -0
  40. package/modules/HeadlessCms/fieldType/AssetFieldSettingsModifier.js.map +1 -0
  41. package/modules/HeadlessCms/fieldType/AssetFieldType.d.ts +34 -0
  42. package/modules/HeadlessCms/fieldType/AssetFieldType.js +42 -0
  43. package/modules/HeadlessCms/fieldType/AssetFieldType.js.map +1 -0
  44. package/modules/HeadlessCms/fieldType/AssetFormFieldType.d.ts +21 -0
  45. package/modules/HeadlessCms/fieldType/AssetFormFieldType.js +42 -0
  46. package/modules/HeadlessCms/fieldType/AssetFormFieldType.js.map +1 -0
  47. package/modules/HeadlessCms/fieldType/AssetInputRenderer.d.ts +15 -0
  48. package/modules/HeadlessCms/fieldType/AssetInputRenderer.js +20 -0
  49. package/modules/HeadlessCms/fieldType/AssetInputRenderer.js.map +1 -0
  50. package/modules/HeadlessCms/fieldType/AssetInputsRenderer.d.ts +15 -0
  51. package/modules/HeadlessCms/fieldType/AssetInputsRenderer.js +20 -0
  52. package/modules/HeadlessCms/fieldType/AssetInputsRenderer.js.map +1 -0
  53. package/modules/HeadlessCms/fieldType/FileFieldSettingsModifier.d.ts +16 -0
  54. package/modules/HeadlessCms/fieldType/FileFieldSettingsModifier.js +33 -0
  55. package/modules/HeadlessCms/fieldType/FileFieldSettingsModifier.js.map +1 -0
  56. package/modules/HeadlessCms/fieldType/FileFieldType.d.ts +25 -0
  57. package/modules/HeadlessCms/fieldType/FileFieldType.js +38 -0
  58. package/modules/HeadlessCms/fieldType/FileFieldType.js.map +1 -0
  59. package/modules/HeadlessCms/fieldType/FileInputRenderer.d.ts +15 -0
  60. package/modules/HeadlessCms/fieldType/FileInputRenderer.js +19 -0
  61. package/modules/HeadlessCms/fieldType/FileInputRenderer.js.map +1 -0
  62. package/modules/HeadlessCms/fieldType/FileInputsRenderer.d.ts +15 -0
  63. package/modules/HeadlessCms/fieldType/FileInputsRenderer.js +19 -0
  64. package/modules/HeadlessCms/fieldType/FileInputsRenderer.js.map +1 -0
  65. package/modules/HeadlessCms/fieldType/feature.d.ts +1 -0
  66. package/modules/HeadlessCms/fieldType/feature.js +27 -0
  67. package/modules/HeadlessCms/fieldType/feature.js.map +1 -0
  68. package/modules/HeadlessCms/fieldType/isAssetField.d.ts +2 -0
  69. package/modules/HeadlessCms/fieldType/isAssetField.js +4 -0
  70. package/modules/HeadlessCms/fieldType/isAssetField.js.map +1 -0
  71. package/package.json +24 -34
  72. package/presentation/FileActions/FileDetails/EditImage.d.ts +2 -0
  73. package/presentation/FileActions/FileDetails/EditImage.js +39 -0
  74. package/presentation/FileActions/FileDetails/EditImage.js.map +1 -0
  75. package/presentation/FileActions/index.js +8 -0
  76. package/presentation/FileActions/index.js.map +1 -1
  77. package/presentation/FileDetails/FileDetailsPresenter.d.ts +5 -4
  78. package/presentation/FileDetails/FileDetailsPresenter.js +40 -46
  79. package/presentation/FileDetails/FileDetailsPresenter.js.map +1 -1
  80. package/presentation/FileDetails/FileDetailsPresenter.test.js +39 -56
  81. package/presentation/FileDetails/FileDetailsPresenter.test.js.map +1 -1
  82. package/presentation/FileDetails/abstractions.d.ts +2 -0
  83. package/presentation/FileDetails/abstractions.js.map +1 -1
  84. package/presentation/FileDetails/components/Content.d.ts +6 -4
  85. package/presentation/FileDetails/components/Content.js +2 -2
  86. package/presentation/FileDetails/components/Content.js.map +1 -1
  87. package/presentation/FileDetails/components/FileDetailsDrawer.d.ts +2 -2
  88. package/presentation/FileDetails/components/FileDetailsDrawer.js +11 -5
  89. package/presentation/FileDetails/components/FileDetailsDrawer.js.map +1 -1
  90. package/presentation/FileList/FileListDataSource.d.ts +8 -12
  91. package/presentation/FileList/FileListDataSource.js +47 -87
  92. package/presentation/FileList/FileListDataSource.js.map +1 -1
  93. package/presentation/FileList/FileListPresenter.test.js +9 -3
  94. package/presentation/FileList/FileListPresenter.test.js.map +1 -1
  95. package/presentation/FileList/FileManagerPresenter.d.ts +1 -0
  96. package/presentation/FileList/FileManagerPresenter.js +15 -5
  97. package/presentation/FileList/FileManagerPresenter.js.map +1 -1
  98. package/presentation/FileList/abstractions.d.ts +3 -0
  99. package/presentation/FileList/abstractions.js.map +1 -1
  100. package/presentation/FileList/components/BulkActions/BulkActionBar.d.ts +2 -2
  101. package/presentation/FileList/components/BulkActions/BulkActionDelete.d.ts +2 -2
  102. package/presentation/FileList/components/BulkActions/BulkActionMove.d.ts +2 -2
  103. package/presentation/FileList/components/BulkActions/index.d.ts +0 -1
  104. package/presentation/FileList/components/BulkActions/index.js +0 -1
  105. package/presentation/FileList/components/BulkActions/useBulkActionWorker.d.ts +9 -8
  106. package/presentation/FileList/components/BulkActions/useBulkActionWorker.js +3 -9
  107. package/presentation/FileList/components/BulkActions/useBulkActionWorker.js.map +1 -1
  108. package/presentation/FileList/components/Filters/SubFoldersToggle.d.ts +2 -2
  109. package/presentation/FileList/components/Filters/TagsFilter.d.ts +2 -2
  110. package/presentation/FileList/components/Filters/TypeFilter.d.ts +2 -2
  111. package/presentation/FileList/components/Grid/FileGrid.d.ts +2 -2
  112. package/presentation/FileList/components/Grid/FileGrid.js +1 -1
  113. package/presentation/FileList/components/Grid/FileGrid.js.map +1 -1
  114. package/presentation/FileList/components/Grid/FileGrid.test.js +18 -7
  115. package/presentation/FileList/components/Grid/FileGrid.test.js.map +1 -1
  116. package/presentation/FileList/components/Header/FileManagerHeader.d.ts +2 -2
  117. package/presentation/FileList/components/Search/SearchBar.d.ts +2 -2
  118. package/presentation/FileList/components/Table/FileTable.d.ts +2 -2
  119. package/presentation/FileList/components/Table/FileTable.js +1 -1
  120. package/presentation/FileList/components/Table/FileTable.js.map +1 -1
  121. package/presentation/FileList/components/Table/FileTable.test.js +9 -4
  122. package/presentation/FileList/components/Table/FileTable.test.js.map +1 -1
  123. package/presentation/FileList/components/Upload/UploadProgress.d.ts +2 -2
  124. package/presentation/FileManager/FileManagerBreadcrumbs.d.ts +12 -0
  125. package/presentation/FileManager/FileManagerBreadcrumbs.js +37 -0
  126. package/presentation/FileManager/FileManagerBreadcrumbs.js.map +1 -0
  127. package/presentation/FileManager/FileManagerView.js +36 -2
  128. package/presentation/FileManager/FileManagerView.js.map +1 -1
  129. package/presentation/FileManager/RouteParamsSync.d.ts +2 -0
  130. package/presentation/FileManager/RouteParamsSync.js +30 -0
  131. package/presentation/FileManager/RouteParamsSync.js.map +1 -0
  132. package/presentation/Settings/components/SettingsView.d.ts +2 -2
  133. package/presentation/config/DefaultFileManagerConfig.js +13 -11
  134. package/presentation/config/DefaultFileManagerConfig.js.map +1 -1
  135. package/presentation/config/FileManagerViewConfig.d.ts +19 -25
  136. package/presentation/config/configComponents/Browser/BulkAction.d.ts +10 -8
  137. package/presentation/config/configComponents/Browser/BulkAction.js +3 -9
  138. package/presentation/config/configComponents/Browser/BulkAction.js.map +1 -1
  139. package/presentation/config/configComponents/Browser/FileAction.d.ts +2 -1
  140. package/presentation/config/configComponents/Browser/FolderAction.d.ts +2 -1
  141. package/presentation/config/configComponents/Browser/Table/Column.d.ts +9 -8
  142. package/presentation/config/configComponents/Browser/Table/index.d.ts +9 -9
  143. package/presentation/config/configComponents/Browser/index.d.ts +20 -20
  144. package/presentation/config/configComponents/Browser/index.js +0 -8
  145. package/presentation/config/configComponents/Browser/index.js.map +1 -1
  146. package/presentation/config/configComponents/FileDetails/index.d.ts +4 -2
  147. package/presentation/config/thumbnailRenderers/CroppedFileImage.d.ts +18 -0
  148. package/presentation/config/thumbnailRenderers/CroppedFileImage.js +66 -0
  149. package/presentation/config/thumbnailRenderers/CroppedFileImage.js.map +1 -0
  150. package/presentation/config/thumbnailRenderers/FilePreviewImageRenderer.js +6 -9
  151. package/presentation/config/thumbnailRenderers/FilePreviewImageRenderer.js.map +1 -1
  152. package/presentation/config/thumbnailRenderers/GridItemImageRenderer.js +6 -9
  153. package/presentation/config/thumbnailRenderers/GridItemImageRenderer.js.map +1 -1
  154. package/presentation/config/thumbnailRenderers/TableItemImageRenderer.js +6 -9
  155. package/presentation/config/thumbnailRenderers/TableItemImageRenderer.js.map +1 -1
  156. package/presentation/fieldRenderers/CmsAssetPickerRenderer.d.ts +22 -0
  157. package/presentation/fieldRenderers/CmsAssetPickerRenderer.js +56 -0
  158. package/presentation/fieldRenderers/CmsAssetPickerRenderer.js.map +1 -0
  159. package/presentation/fieldRenderers/CmsFilePickerRenderer.d.ts +13 -0
  160. package/presentation/fieldRenderers/CmsFilePickerRenderer.js +36 -0
  161. package/presentation/fieldRenderers/CmsFilePickerRenderer.js.map +1 -0
  162. package/presentation/fieldRenderers/CmsImageEditorDialog.d.ts +7 -0
  163. package/presentation/fieldRenderers/CmsImageEditorDialog.js +32 -0
  164. package/presentation/fieldRenderers/CmsImageEditorDialog.js.map +1 -0
  165. package/presentation/fieldRenderers/CmsMultiAssetPickerRenderer.d.ts +18 -0
  166. package/presentation/fieldRenderers/CmsMultiAssetPickerRenderer.js +88 -0
  167. package/presentation/fieldRenderers/CmsMultiAssetPickerRenderer.js.map +1 -0
  168. package/presentation/fieldRenderers/CmsMultiFilePickerRenderer.d.ts +13 -0
  169. package/presentation/fieldRenderers/CmsMultiFilePickerRenderer.js +60 -0
  170. package/presentation/fieldRenderers/CmsMultiFilePickerRenderer.js.map +1 -0
  171. package/presentation/fieldRenderers/CroppedAssetThumb.d.ts +15 -0
  172. package/presentation/fieldRenderers/CroppedAssetThumb.js +25 -0
  173. package/presentation/fieldRenderers/CroppedAssetThumb.js.map +1 -0
  174. package/presentation/fieldRenderers/assetValue.d.ts +10 -0
  175. package/presentation/fieldRenderers/assetValue.js +88 -0
  176. package/presentation/fieldRenderers/assetValue.js.map +1 -0
  177. package/presentation/hooks/useCmsImageEditor.d.ts +10 -0
  178. package/presentation/hooks/useCmsImageEditor.js +76 -0
  179. package/presentation/hooks/useCmsImageEditor.js.map +1 -0
  180. package/presentation/hooks/useEditImage.d.ts +14 -0
  181. package/presentation/hooks/useEditImage.js +67 -0
  182. package/presentation/hooks/useEditImage.js.map +1 -0
  183. package/routes.d.ts +1 -0
  184. package/routes.js +2 -1
  185. package/routes.js.map +1 -1
  186. package/modules/HeadlessCms/fileField.d.ts +0 -2
  187. package/modules/HeadlessCms/fileField.js +0 -44
  188. package/modules/HeadlessCms/fileField.js.map +0 -1
  189. package/modules/HeadlessCms/fileRenderer/fileField.d.ts +0 -2
  190. package/modules/HeadlessCms/fileRenderer/fileField.js +0 -53
  191. package/modules/HeadlessCms/fileRenderer/fileField.js.map +0 -1
  192. package/modules/HeadlessCms/fileRenderer/fileFields.d.ts +0 -2
  193. package/modules/HeadlessCms/fileRenderer/fileFields.js +0 -73
  194. package/modules/HeadlessCms/fileRenderer/fileFields.js.map +0 -1
  195. package/modules/HeadlessCms/fileRenderer/utils.d.ts +0 -2
  196. package/modules/HeadlessCms/fileRenderer/utils.js +0 -15
  197. package/modules/HeadlessCms/fileRenderer/utils.js.map +0 -1
  198. package/modules/HeadlessCms/index.d.ts +0 -1
  199. package/modules/HeadlessCms/index.js +0 -14
  200. package/modules/HeadlessCms/index.js.map +0 -1
  201. package/modules/Settings/graphql.d.ts +0 -12
  202. package/modules/Settings/graphql.js +0 -36
  203. package/modules/Settings/graphql.js.map +0 -1
  204. package/modules/Settings/views/FileManagerSettings.d.ts +0 -2
  205. package/modules/Settings/views/FileManagerSettings.js +0 -134
  206. package/modules/Settings/views/FileManagerSettings.js.map +0 -1
  207. package/presentation/FileList/components/BulkActions/BatchEditorDialog/AddOperation.d.ts +0 -7
  208. package/presentation/FileList/components/BulkActions/BatchEditorDialog/AddOperation.js +0 -15
  209. package/presentation/FileList/components/BulkActions/BatchEditorDialog/AddOperation.js.map +0 -1
  210. package/presentation/FileList/components/BulkActions/BatchEditorDialog/BatchEditor.d.ts +0 -15
  211. package/presentation/FileList/components/BulkActions/BatchEditorDialog/BatchEditor.js +0 -44
  212. package/presentation/FileList/components/BulkActions/BatchEditorDialog/BatchEditor.js.map +0 -1
  213. package/presentation/FileList/components/BulkActions/BatchEditorDialog/BatchEditorDialog.d.ts +0 -15
  214. package/presentation/FileList/components/BulkActions/BatchEditorDialog/BatchEditorDialog.js +0 -52
  215. package/presentation/FileList/components/BulkActions/BatchEditorDialog/BatchEditorDialog.js.map +0 -1
  216. package/presentation/FileList/components/BulkActions/BatchEditorDialog/BatchEditorDialogPresenter.d.ts +0 -61
  217. package/presentation/FileList/components/BulkActions/BatchEditorDialog/BatchEditorDialogPresenter.js +0 -111
  218. package/presentation/FileList/components/BulkActions/BatchEditorDialog/BatchEditorDialogPresenter.js.map +0 -1
  219. package/presentation/FileList/components/BulkActions/BatchEditorDialog/BatchEditorDialogPresenter.test.d.ts +0 -1
  220. package/presentation/FileList/components/BulkActions/BatchEditorDialog/BatchEditorDialogPresenter.test.js +0 -372
  221. package/presentation/FileList/components/BulkActions/BatchEditorDialog/BatchEditorDialogPresenter.test.js.map +0 -1
  222. package/presentation/FileList/components/BulkActions/BatchEditorDialog/FieldRenderer.d.ts +0 -8
  223. package/presentation/FileList/components/BulkActions/BatchEditorDialog/FieldRenderer.js +0 -31
  224. package/presentation/FileList/components/BulkActions/BatchEditorDialog/FieldRenderer.js.map +0 -1
  225. package/presentation/FileList/components/BulkActions/BatchEditorDialog/Operation.d.ts +0 -11
  226. package/presentation/FileList/components/BulkActions/BatchEditorDialog/Operation.js +0 -33
  227. package/presentation/FileList/components/BulkActions/BatchEditorDialog/Operation.js.map +0 -1
  228. package/presentation/FileList/components/BulkActions/BatchEditorDialog/index.d.ts +0 -1
  229. package/presentation/FileList/components/BulkActions/BatchEditorDialog/index.js +0 -1
  230. package/presentation/FileList/components/BulkActions/BulkActionEdit.d.ts +0 -4
  231. package/presentation/FileList/components/BulkActions/BulkActionEdit.js +0 -105
  232. package/presentation/FileList/components/BulkActions/BulkActionEdit.js.map +0 -1
  233. package/presentation/config/configComponents/Browser/FolderFieldDecorator.d.ts +0 -7
  234. package/presentation/config/configComponents/Browser/FolderFieldDecorator.js +0 -11
  235. package/presentation/config/configComponents/Browser/FolderFieldDecorator.js.map +0 -1
@@ -8,23 +8,35 @@ import { FileManagerPermissions } from "../../features/permissions/abstractions.
8
8
  import { GetSettingsRepository } from "../../features/settings/abstractions.js";
9
9
  import { FileDetailsPresenter } from "./abstractions.js";
10
10
  import { FileDetailsPresenter as external_FileDetailsPresenter_js_FileDetailsPresenter } from "./FileDetailsPresenter.js";
11
- import { WcpService } from "@webiny/app-admin/features/wcp/abstractions.js";
12
11
  function createMockFormModel(overrides) {
12
+ const data = {};
13
13
  const vm = {
14
14
  layout: [],
15
15
  errors: [],
16
+ hasErrors: false,
16
17
  isDirty: false,
17
18
  isValid: true,
18
19
  submitCount: 0,
19
20
  focusField: vi.fn(),
20
- getData: vi.fn(()=>({})),
21
- setData: vi.fn(),
21
+ getData: vi.fn(()=>({
22
+ ...data
23
+ })),
24
+ setData: vi.fn((incoming)=>{
25
+ Object.assign(data, incoming);
26
+ }),
22
27
  ...overrides
23
28
  };
24
- const data = {};
29
+ const fields = {};
25
30
  return {
26
- field: vi.fn().mockReturnValue({
27
- setDisabled: vi.fn()
31
+ field: vi.fn((name)=>{
32
+ fields[name] = fields[name] ?? {
33
+ setValue: vi.fn((value)=>{
34
+ data[name] = value;
35
+ }),
36
+ setValueSilent: vi.fn(),
37
+ setDisabled: vi.fn()
38
+ };
39
+ return fields[name];
28
40
  }),
29
41
  fields: vi.fn(),
30
42
  layout: vi.fn(),
@@ -107,16 +119,6 @@ function createMockSettingsRepository() {
107
119
  }
108
120
  };
109
121
  }
110
- function createMockWcpService(canUsePrivateFiles = true) {
111
- return {
112
- getProject: vi.fn().mockReturnValue({
113
- canUsePrivateFiles: vi.fn().mockReturnValue(canUsePrivateFiles)
114
- }),
115
- isLoaded: vi.fn().mockReturnValue(true),
116
- canUseFeature: vi.fn().mockReturnValue(false),
117
- loadProject: vi.fn().mockResolvedValue(void 0)
118
- };
119
- }
120
122
  function createTestFile(overrides) {
121
123
  return {
122
124
  id: "file-1",
@@ -151,15 +153,14 @@ function createTestFile(overrides) {
151
153
  ...overrides
152
154
  };
153
155
  }
154
- function createMocks(canUsePrivateFiles = true) {
156
+ function createMocks() {
155
157
  return {
156
158
  getFileUseCase: createMockGetFileUseCase(),
157
159
  updateFileUseCase: createMockUpdateFileUseCase(),
158
160
  deleteFileUseCase: createMockDeleteFileUseCase(),
159
161
  formModelFactory: createMockFormModelFactory(),
160
162
  permissions: createMockPermissions(),
161
- settingsRepository: createMockSettingsRepository(),
162
- wcpService: createMockWcpService(canUsePrivateFiles)
163
+ settingsRepository: createMockSettingsRepository()
163
164
  };
164
165
  }
165
166
  function createContainer(mocks) {
@@ -170,7 +171,6 @@ function createContainer(mocks) {
170
171
  container.registerInstance(FormModelFactory, mocks.formModelFactory);
171
172
  container.registerInstance(FileManagerPermissions, mocks.permissions);
172
173
  container.registerInstance(GetSettingsRepository, mocks.settingsRepository);
173
- container.registerInstance(WcpService, mocks.wcpService);
174
174
  container.register(external_FileDetailsPresenter_js_FileDetailsPresenter).inSingletonScope();
175
175
  return container;
176
176
  }
@@ -218,6 +218,25 @@ describe("FileDetailsPresenter", ()=>{
218
218
  await presenter.loadFile("file-1");
219
219
  expect(presenter.vm.form.isDirty).toBe(false);
220
220
  });
221
+ it("should expose a writable form on the view model", async ()=>{
222
+ await presenter.loadFile("file-1");
223
+ presenter.vm.form.setData({
224
+ tags: [
225
+ "cat",
226
+ "sofa"
227
+ ],
228
+ description: "A cat on a sofa."
229
+ }, {
230
+ dirty: true
231
+ });
232
+ expect(presenter.vm.form.getData()).toMatchObject({
233
+ description: "A cat on a sofa.",
234
+ tags: [
235
+ "cat",
236
+ "sofa"
237
+ ]
238
+ });
239
+ });
221
240
  it("should call UpdateFileUseCase.execute when saveFile is called", async ()=>{
222
241
  await presenter.loadFile("file-1");
223
242
  const mockForm = mocks.formModelFactory.create.mock.results[1].value;
@@ -290,42 +309,6 @@ describe("FileDetailsPresenter", ()=>{
290
309
  expect(loadingDuringExecution).toBe("Loading file...");
291
310
  expect(presenter.vm.loading).toBeNull();
292
311
  });
293
- describe("when Private Files is NOT licensed", ()=>{
294
- let unlicensedMocks;
295
- let unlicensedPresenter;
296
- beforeEach(()=>{
297
- unlicensedMocks = createMocks(false);
298
- const container = createContainer(unlicensedMocks);
299
- unlicensedPresenter = container.resolve(FileDetailsPresenter);
300
- });
301
- it("should NOT include accessControl in the save payload", async ()=>{
302
- await unlicensedPresenter.loadFile("file-1");
303
- const mockForm = unlicensedMocks.formModelFactory.create.mock.results[1].value;
304
- mockForm.submit.mockResolvedValue({
305
- name: "updated.jpg",
306
- description: "",
307
- tags: [
308
- "updated"
309
- ]
310
- });
311
- await unlicensedPresenter.saveFile();
312
- const callArgs = unlicensedMocks.updateFileUseCase.execute.mock.calls[0][0];
313
- expect(callArgs.data).not.toHaveProperty("accessControl");
314
- });
315
- it("should NOT set accessControl in form data during loadFile", async ()=>{
316
- await unlicensedPresenter.loadFile("file-1");
317
- const mockForm = unlicensedMocks.formModelFactory.create.mock.results[1].value;
318
- const setDataCall = mockForm.setData.mock.calls[0][0];
319
- expect(setDataCall).not.toHaveProperty("accessControl");
320
- });
321
- it("should NOT disable accessControl field when user lacks edit permission", async ()=>{
322
- unlicensedMocks.permissions.canEdit.mockReturnValue(false);
323
- await unlicensedPresenter.loadFile("file-1");
324
- const mockForm = unlicensedMocks.formModelFactory.create.mock.results[1].value;
325
- expect(mockForm.field).toHaveBeenCalledTimes(3);
326
- expect(mockForm.field).not.toHaveBeenCalledWith("accessControl");
327
- });
328
- });
329
312
  });
330
313
 
331
314
  //# sourceMappingURL=FileDetailsPresenter.test.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"presentation/FileDetails/FileDetailsPresenter.test.js","sources":["../../../src/presentation/FileDetails/FileDetailsPresenter.test.ts"],"sourcesContent":["// @vitest-environment jsdom\nimport { describe, it, expect, vi, beforeEach } from \"vitest\";\nimport { Container } from \"@webiny/di\";\nimport { GetFileUseCase } from \"../../features/getFile/abstractions.js\";\nimport { UpdateFileUseCase } from \"../../features/updateFile/abstractions.js\";\nimport { DeleteFileUseCase } from \"../../features/deleteFile/abstractions.js\";\nimport { FormModelFactory } from \"@webiny/app-admin/features/formModel/abstractions.js\";\nimport type { IFormModel } from \"@webiny/app-admin/features/formModel/abstractions.js\";\nimport type { IFormVM } from \"@webiny/app-admin/features/formModel/abstractions.js\";\nimport { FileManagerPermissions } from \"../../features/permissions/abstractions.js\";\nimport { GetSettingsRepository } from \"../../features/settings/abstractions.js\";\nimport { FileDetailsPresenter as Abstraction, type IFileDetailsPresenter } from \"./abstractions.js\";\nimport { FileDetailsPresenter } from \"./FileDetailsPresenter.js\";\nimport { WcpService } from \"@webiny/app-admin/features/wcp/abstractions.js\";\nimport type { FmFile } from \"../../features/shared/types.js\";\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nfunction createMockFormModel(overrides?: Partial<IFormVM>): IFormModel {\n const vm: IFormVM = {\n layout: [],\n errors: [],\n isDirty: false,\n isValid: true,\n submitCount: 0,\n focusField: vi.fn(),\n getData: vi.fn(() => ({})),\n setData: vi.fn(),\n ...overrides\n };\n\n const data: Record<string, unknown> = {};\n\n return {\n field: vi.fn().mockReturnValue({ setDisabled: vi.fn() }) as any,\n fields: vi.fn() as any,\n layout: vi.fn() as any,\n getData: vi.fn(() => ({ ...data })),\n setData: vi.fn((incoming: Record<string, unknown>) => {\n Object.assign(data, incoming);\n }),\n reset: vi.fn(),\n validate: vi.fn().mockResolvedValue(true),\n submit: vi.fn().mockResolvedValue({ ...data }),\n get isDirty() {\n return vm.isDirty;\n },\n get isValid() {\n return vm.isValid;\n },\n get errors() {\n return vm.errors;\n },\n get vm() {\n return vm;\n }\n } as unknown as IFormModel;\n}\n\nfunction createMockFormModelFactory(): FormModelFactory.Interface {\n const mockForm = createMockFormModel();\n return {\n create: vi.fn().mockReturnValue(mockForm)\n };\n}\n\nfunction createMockGetFileUseCase(): GetFileUseCase.Interface {\n return {\n execute: vi.fn().mockResolvedValue({ success: true, file: createTestFile() })\n };\n}\n\nfunction createMockUpdateFileUseCase(): UpdateFileUseCase.Interface {\n return {\n execute: vi.fn().mockResolvedValue({ success: true, file: createTestFile() })\n };\n}\n\nfunction createMockDeleteFileUseCase(): DeleteFileUseCase.Interface {\n return {\n execute: vi.fn().mockResolvedValue({ success: true })\n };\n}\n\nfunction createMockPermissions(): FileManagerPermissions.Interface {\n return {\n canAccess: vi.fn().mockReturnValue(true),\n canRead: vi.fn().mockReturnValue(true),\n canCreate: vi.fn().mockReturnValue(true),\n canEdit: vi.fn().mockReturnValue(true),\n canDelete: vi.fn().mockReturnValue(false),\n canAction: vi.fn().mockReturnValue(true)\n } as unknown as FileManagerPermissions.Interface;\n}\n\nfunction createMockSettingsRepository(): GetSettingsRepository.Interface {\n return {\n execute: vi.fn().mockResolvedValue({\n uploadMinFileSize: \"0\",\n uploadMaxFileSize: \"10485760\",\n srcPrefix: \"https://cdn.example.com/\"\n }),\n save: vi.fn().mockImplementation(async (data: Record<string, unknown>) => data),\n settings: {\n uploadMinFileSize: \"0\",\n uploadMaxFileSize: \"10485760\",\n srcPrefix: \"https://cdn.example.com/\"\n }\n };\n}\n\nfunction createMockWcpService(canUsePrivateFiles = true): WcpService.Interface {\n return {\n getProject: vi.fn().mockReturnValue({\n canUsePrivateFiles: vi.fn().mockReturnValue(canUsePrivateFiles)\n }),\n isLoaded: vi.fn().mockReturnValue(true),\n canUseFeature: vi.fn().mockReturnValue(false),\n loadProject: vi.fn().mockResolvedValue(undefined)\n } as unknown as WcpService.Interface;\n}\n\nfunction createTestFile(overrides?: Partial<FmFile>): FmFile {\n return {\n id: \"file-1\",\n name: \"photo.jpg\",\n key: \"files/photo.jpg\",\n src: \"https://cdn.example.com/files/photo.jpg\",\n type: \"image/jpeg\",\n size: 12345,\n metadata: {},\n tags: [\"nature\", \"landscape\"],\n createdOn: \"2025-01-01T00:00:00Z\",\n savedOn: \"2025-01-01T00:00:00Z\",\n createdBy: { id: \"user-1\", displayName: \"Test User\", type: \"admin\" },\n savedBy: { id: \"user-1\", displayName: \"Test User\", type: \"admin\" },\n location: { folderId: \"root\" },\n accessControl: { type: \"public\" },\n ...overrides\n };\n}\n\n// ---------------------------------------------------------------------------\n// Container setup\n// ---------------------------------------------------------------------------\n\ninterface Mocks {\n getFileUseCase: GetFileUseCase.Interface;\n updateFileUseCase: UpdateFileUseCase.Interface;\n deleteFileUseCase: DeleteFileUseCase.Interface;\n formModelFactory: FormModelFactory.Interface;\n permissions: FileManagerPermissions.Interface;\n settingsRepository: GetSettingsRepository.Interface;\n wcpService: WcpService.Interface;\n}\n\nfunction createMocks(canUsePrivateFiles = true): Mocks {\n return {\n getFileUseCase: createMockGetFileUseCase(),\n updateFileUseCase: createMockUpdateFileUseCase(),\n deleteFileUseCase: createMockDeleteFileUseCase(),\n formModelFactory: createMockFormModelFactory(),\n permissions: createMockPermissions(),\n settingsRepository: createMockSettingsRepository(),\n wcpService: createMockWcpService(canUsePrivateFiles)\n };\n}\n\nfunction createContainer(mocks: Mocks) {\n const container = new Container();\n\n container.registerInstance(GetFileUseCase, mocks.getFileUseCase);\n container.registerInstance(UpdateFileUseCase, mocks.updateFileUseCase);\n container.registerInstance(DeleteFileUseCase, mocks.deleteFileUseCase);\n container.registerInstance(FormModelFactory, mocks.formModelFactory);\n container.registerInstance(FileManagerPermissions, mocks.permissions);\n container.registerInstance(GetSettingsRepository, mocks.settingsRepository);\n container.registerInstance(WcpService, mocks.wcpService);\n\n // Register the real FileDetailsPresenter implementation.\n container.register(FileDetailsPresenter).inSingletonScope();\n\n return container;\n}\n\n// ---------------------------------------------------------------------------\n// Tests\n// ---------------------------------------------------------------------------\n\ndescribe(\"FileDetailsPresenter\", () => {\n let mocks: Mocks;\n let presenter: IFileDetailsPresenter;\n\n beforeEach(() => {\n mocks = createMocks();\n const container = createContainer(mocks);\n presenter = container.resolve(Abstraction);\n });\n\n // -----------------------------------------------------------------------\n // Initial state.\n // -----------------------------------------------------------------------\n\n it(\"should have null file and loading=null before loadFile is called\", () => {\n expect(presenter.vm.file).toBeNull();\n expect(presenter.vm.loading).toBeNull();\n });\n\n it(\"should have a form vm with layout and isDirty=false initially\", () => {\n expect(presenter.vm.form).toBeDefined();\n expect(presenter.vm.form.layout).toBeDefined();\n expect(presenter.vm.form.isDirty).toBe(false);\n });\n\n it(\"should have null previewUrl when no file is loaded\", () => {\n expect(presenter.vm.previewUrl).toBeNull();\n });\n\n // -----------------------------------------------------------------------\n // loadFile populates vm with file data and FormModel.\n // -----------------------------------------------------------------------\n\n it(\"should populate vm.file after loadFile\", async () => {\n await presenter.loadFile(\"file-1\");\n\n expect(presenter.vm.file).not.toBeNull();\n expect(presenter.vm.file!.id).toBe(\"file-1\");\n expect(presenter.vm.file!.name).toBe(\"photo.jpg\");\n });\n\n it(\"should set loading=null after loadFile completes\", async () => {\n await presenter.loadFile(\"file-1\");\n\n expect(presenter.vm.loading).toBeNull();\n });\n\n it(\"should call GetFileUseCase.execute with the file id\", async () => {\n await presenter.loadFile(\"file-1\");\n\n expect(mocks.getFileUseCase.execute).toHaveBeenCalledWith({ id: \"file-1\" });\n });\n\n it(\"should rebuild the form and call setData after loadFile\", async () => {\n await presenter.loadFile(\"file-1\");\n\n // FormModelFactory.create should be called twice: once in constructor, once in loadFile.\n expect(mocks.formModelFactory.create).toHaveBeenCalledTimes(2);\n });\n\n it(\"should have form.isDirty=false after loadFile\", async () => {\n await presenter.loadFile(\"file-1\");\n\n expect(presenter.vm.form.isDirty).toBe(false);\n });\n\n // -----------------------------------------------------------------------\n // Form submission triggers UpdateFileUseCase.\n // -----------------------------------------------------------------------\n\n it(\"should call UpdateFileUseCase.execute when saveFile is called\", async () => {\n await presenter.loadFile(\"file-1\");\n\n // Mock the form submit to return data.\n const mockForm = (mocks.formModelFactory.create as ReturnType<typeof vi.fn>).mock.results[1]\n .value as IFormModel;\n (mockForm.submit as ReturnType<typeof vi.fn>).mockResolvedValue({\n name: \"updated.jpg\",\n tags: [\"updated\"],\n accessControl: \"private-authenticated\"\n });\n\n await presenter.saveFile();\n\n expect(mocks.updateFileUseCase.execute).toHaveBeenCalledWith({\n id: \"file-1\",\n data: {\n name: \"updated.jpg\",\n tags: [\"updated\"],\n accessControl: { type: \"private-authenticated\" }\n }\n });\n });\n\n it(\"should not call UpdateFileUseCase when no file is loaded\", async () => {\n await presenter.saveFile();\n\n expect(mocks.updateFileUseCase.execute).not.toHaveBeenCalled();\n });\n\n it(\"should not call UpdateFileUseCase when form submit returns false\", async () => {\n await presenter.loadFile(\"file-1\");\n\n // Mock the form submit to return false (validation failure).\n const mockForm = (mocks.formModelFactory.create as ReturnType<typeof vi.fn>).mock.results[1]\n .value as IFormModel;\n (mockForm.submit as ReturnType<typeof vi.fn>).mockResolvedValue(false);\n\n await presenter.saveFile();\n\n expect(mocks.updateFileUseCase.execute).not.toHaveBeenCalled();\n });\n\n // -----------------------------------------------------------------------\n // Permission flags control action visibility.\n // -----------------------------------------------------------------------\n\n it(\"should expose permission flags in vm.permissions\", () => {\n expect(presenter.vm.permissions.canEdit).toBe(true);\n expect(presenter.vm.permissions.canDelete).toBe(false);\n });\n\n it(\"should reflect updated permission mock values\", () => {\n // Override permissions to both false.\n (mocks.permissions.canEdit as ReturnType<typeof vi.fn>).mockReturnValue(false);\n (mocks.permissions.canDelete as ReturnType<typeof vi.fn>).mockReturnValue(true);\n\n expect(presenter.vm.permissions.canEdit).toBe(false);\n expect(presenter.vm.permissions.canDelete).toBe(true);\n });\n\n // -----------------------------------------------------------------------\n // previewUrl construction from srcPrefix + key.\n // -----------------------------------------------------------------------\n\n it(\"should construct previewUrl from srcPrefix + file.key after loadFile\", async () => {\n await presenter.loadFile(\"file-1\");\n\n expect(presenter.vm.previewUrl).toBe(\"https://cdn.example.com/files/photo.jpg\");\n });\n\n it(\"should return null previewUrl when file has no key\", async () => {\n (mocks.getFileUseCase.execute as ReturnType<typeof vi.fn>).mockResolvedValue({\n success: true,\n file: createTestFile({ key: undefined })\n });\n\n await presenter.loadFile(\"file-1\");\n\n expect(presenter.vm.previewUrl).toBeNull();\n });\n\n // -----------------------------------------------------------------------\n // Loading state transitions.\n // -----------------------------------------------------------------------\n\n it(\"should set loading=true during loadFile and false after\", async () => {\n // Capture loading state during execution.\n let loadingDuringExecution: string | null = null;\n\n (mocks.getFileUseCase.execute as ReturnType<typeof vi.fn>).mockImplementation(async () => {\n loadingDuringExecution = presenter.vm.loading;\n return { success: true, file: createTestFile() };\n });\n\n await presenter.loadFile(\"file-1\");\n\n expect(loadingDuringExecution).toBe(\"Loading file...\");\n expect(presenter.vm.loading).toBeNull();\n });\n\n // -------------------------------------------------------------------\n // When Private Files is NOT licensed.\n // -------------------------------------------------------------------\n\n describe(\"when Private Files is NOT licensed\", () => {\n let unlicensedMocks: Mocks;\n let unlicensedPresenter: IFileDetailsPresenter;\n\n beforeEach(() => {\n unlicensedMocks = createMocks(false);\n const container = createContainer(unlicensedMocks);\n unlicensedPresenter = container.resolve(Abstraction);\n });\n\n it(\"should NOT include accessControl in the save payload\", async () => {\n await unlicensedPresenter.loadFile(\"file-1\");\n\n const mockForm = (unlicensedMocks.formModelFactory.create as ReturnType<typeof vi.fn>)\n .mock.results[1].value as IFormModel;\n (mockForm.submit as ReturnType<typeof vi.fn>).mockResolvedValue({\n name: \"updated.jpg\",\n description: \"\",\n tags: [\"updated\"]\n });\n\n await unlicensedPresenter.saveFile();\n\n const callArgs = (unlicensedMocks.updateFileUseCase.execute as ReturnType<typeof vi.fn>)\n .mock.calls[0][0];\n expect(callArgs.data).not.toHaveProperty(\"accessControl\");\n });\n\n it(\"should NOT set accessControl in form data during loadFile\", async () => {\n await unlicensedPresenter.loadFile(\"file-1\");\n\n const mockForm = (unlicensedMocks.formModelFactory.create as ReturnType<typeof vi.fn>)\n .mock.results[1].value as IFormModel;\n const setDataCall = (mockForm.setData as ReturnType<typeof vi.fn>).mock.calls[0][0];\n expect(setDataCall).not.toHaveProperty(\"accessControl\");\n });\n\n it(\"should NOT disable accessControl field when user lacks edit permission\", async () => {\n (unlicensedMocks.permissions.canEdit as ReturnType<typeof vi.fn>).mockReturnValue(\n false\n );\n\n await unlicensedPresenter.loadFile(\"file-1\");\n\n const mockForm = (unlicensedMocks.formModelFactory.create as ReturnType<typeof vi.fn>)\n .mock.results[1].value as IFormModel;\n expect(mockForm.field).toHaveBeenCalledTimes(3);\n expect(mockForm.field).not.toHaveBeenCalledWith(\"accessControl\");\n });\n });\n});\n"],"names":["createMockFormModel","overrides","vm","vi","data","incoming","Object","createMockFormModelFactory","mockForm","createMockGetFileUseCase","createTestFile","createMockUpdateFileUseCase","createMockDeleteFileUseCase","createMockPermissions","createMockSettingsRepository","createMockWcpService","canUsePrivateFiles","undefined","createMocks","createContainer","mocks","container","Container","GetFileUseCase","UpdateFileUseCase","DeleteFileUseCase","FormModelFactory","FileManagerPermissions","GetSettingsRepository","WcpService","FileDetailsPresenter","describe","presenter","beforeEach","Abstraction","it","expect","loadingDuringExecution","unlicensedMocks","unlicensedPresenter","callArgs","setDataCall"],"mappings":"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"}
1
+ {"version":3,"file":"presentation/FileDetails/FileDetailsPresenter.test.js","sources":["../../../src/presentation/FileDetails/FileDetailsPresenter.test.ts"],"sourcesContent":["// @vitest-environment jsdom\nimport { describe, it, expect, vi, beforeEach } from \"vitest\";\nimport { Container } from \"@webiny/di\";\nimport { GetFileUseCase } from \"../../features/getFile/abstractions.js\";\nimport { UpdateFileUseCase } from \"../../features/updateFile/abstractions.js\";\nimport { DeleteFileUseCase } from \"../../features/deleteFile/abstractions.js\";\nimport { FormModelFactory } from \"@webiny/app-admin/features/formModel/abstractions.js\";\nimport type { IFormModel } from \"@webiny/app-admin/features/formModel/abstractions.js\";\nimport type { IFormVM } from \"@webiny/app-admin/features/formModel/abstractions.js\";\nimport { FileManagerPermissions } from \"../../features/permissions/abstractions.js\";\nimport { GetSettingsRepository } from \"../../features/settings/abstractions.js\";\nimport { FileDetailsPresenter as Abstraction, type IFileDetailsPresenter } from \"./abstractions.js\";\nimport { FileDetailsPresenter } from \"./FileDetailsPresenter.js\";\nimport type { FmFile } from \"../../features/shared/types.js\";\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nfunction createMockFormModel(overrides?: Partial<IFormVM>): IFormModel {\n const data: Record<string, unknown> = {};\n\n // The view model reads and writes the SAME data as the model. Callers reach the form through\n // `vm.form`, so a vm whose getData/setData were inert stubs could not exercise them at all.\n const vm: IFormVM = {\n layout: [],\n errors: [],\n hasErrors: false,\n isDirty: false,\n isValid: true,\n submitCount: 0,\n focusField: vi.fn(),\n getData: vi.fn(() => ({ ...data })),\n setData: vi.fn((incoming: Record<string, unknown>) => {\n Object.assign(data, incoming);\n }),\n ...overrides\n };\n\n // `field(name)` has to return something usable: loadFile disables fields for a read-only user.\n const fields: Record<string, unknown> = {};\n\n return {\n field: vi.fn((name: string) => {\n fields[name] = fields[name] ?? {\n setValue: vi.fn((value: unknown) => {\n data[name] = value;\n }),\n setValueSilent: vi.fn(),\n setDisabled: vi.fn()\n };\n return fields[name];\n }) as any,\n fields: vi.fn() as any,\n layout: vi.fn() as any,\n getData: vi.fn(() => ({ ...data })),\n setData: vi.fn((incoming: Record<string, unknown>) => {\n Object.assign(data, incoming);\n }),\n reset: vi.fn(),\n validate: vi.fn().mockResolvedValue(true),\n submit: vi.fn().mockResolvedValue({ ...data }),\n get isDirty() {\n return vm.isDirty;\n },\n get isValid() {\n return vm.isValid;\n },\n get errors() {\n return vm.errors;\n },\n get vm() {\n return vm;\n }\n } as unknown as IFormModel;\n}\n\nfunction createMockFormModelFactory(): FormModelFactory.Interface {\n const mockForm = createMockFormModel();\n return {\n create: vi.fn().mockReturnValue(mockForm)\n };\n}\n\nfunction createMockGetFileUseCase(): GetFileUseCase.Interface {\n return {\n execute: vi.fn().mockResolvedValue({ success: true, file: createTestFile() })\n };\n}\n\nfunction createMockUpdateFileUseCase(): UpdateFileUseCase.Interface {\n return {\n execute: vi.fn().mockResolvedValue({ success: true, file: createTestFile() })\n };\n}\n\nfunction createMockDeleteFileUseCase(): DeleteFileUseCase.Interface {\n return {\n execute: vi.fn().mockResolvedValue({ success: true })\n };\n}\n\nfunction createMockPermissions(): FileManagerPermissions.Interface {\n return {\n canAccess: vi.fn().mockReturnValue(true),\n canRead: vi.fn().mockReturnValue(true),\n canCreate: vi.fn().mockReturnValue(true),\n canEdit: vi.fn().mockReturnValue(true),\n canDelete: vi.fn().mockReturnValue(false),\n canAction: vi.fn().mockReturnValue(true)\n } as unknown as FileManagerPermissions.Interface;\n}\n\nfunction createMockSettingsRepository(): GetSettingsRepository.Interface {\n return {\n execute: vi.fn().mockResolvedValue({\n uploadMinFileSize: \"0\",\n uploadMaxFileSize: \"10485760\",\n srcPrefix: \"https://cdn.example.com/\"\n }),\n save: vi.fn().mockImplementation(async (data: Record<string, unknown>) => data),\n settings: {\n uploadMinFileSize: \"0\",\n uploadMaxFileSize: \"10485760\",\n srcPrefix: \"https://cdn.example.com/\"\n }\n };\n}\n\nfunction createTestFile(overrides?: Partial<FmFile>): FmFile {\n return {\n id: \"file-1\",\n name: \"photo.jpg\",\n key: \"files/photo.jpg\",\n src: \"https://cdn.example.com/files/photo.jpg\",\n type: \"image/jpeg\",\n size: 12345,\n metadata: {},\n tags: [\"nature\", \"landscape\"],\n createdOn: \"2025-01-01T00:00:00Z\",\n savedOn: \"2025-01-01T00:00:00Z\",\n createdBy: { id: \"user-1\", displayName: \"Test User\", type: \"admin\" },\n savedBy: { id: \"user-1\", displayName: \"Test User\", type: \"admin\" },\n location: { folderId: \"root\" },\n accessControl: { type: \"public\" },\n ...overrides\n };\n}\n\n// ---------------------------------------------------------------------------\n// Container setup\n// ---------------------------------------------------------------------------\n\ninterface Mocks {\n getFileUseCase: GetFileUseCase.Interface;\n updateFileUseCase: UpdateFileUseCase.Interface;\n deleteFileUseCase: DeleteFileUseCase.Interface;\n formModelFactory: FormModelFactory.Interface;\n permissions: FileManagerPermissions.Interface;\n settingsRepository: GetSettingsRepository.Interface;\n}\n\nfunction createMocks(): Mocks {\n return {\n getFileUseCase: createMockGetFileUseCase(),\n updateFileUseCase: createMockUpdateFileUseCase(),\n deleteFileUseCase: createMockDeleteFileUseCase(),\n formModelFactory: createMockFormModelFactory(),\n permissions: createMockPermissions(),\n settingsRepository: createMockSettingsRepository()\n };\n}\n\nfunction createContainer(mocks: Mocks) {\n const container = new Container();\n\n container.registerInstance(GetFileUseCase, mocks.getFileUseCase);\n container.registerInstance(UpdateFileUseCase, mocks.updateFileUseCase);\n container.registerInstance(DeleteFileUseCase, mocks.deleteFileUseCase);\n container.registerInstance(FormModelFactory, mocks.formModelFactory);\n container.registerInstance(FileManagerPermissions, mocks.permissions);\n container.registerInstance(GetSettingsRepository, mocks.settingsRepository);\n\n // Register the real FileDetailsPresenter implementation.\n container.register(FileDetailsPresenter).inSingletonScope();\n\n return container;\n}\n\n// ---------------------------------------------------------------------------\n// Tests\n// ---------------------------------------------------------------------------\n\ndescribe(\"FileDetailsPresenter\", () => {\n let mocks: Mocks;\n let presenter: IFileDetailsPresenter;\n\n beforeEach(() => {\n mocks = createMocks();\n const container = createContainer(mocks);\n presenter = container.resolve(Abstraction);\n });\n\n // -----------------------------------------------------------------------\n // Initial state.\n // -----------------------------------------------------------------------\n\n it(\"should have null file and loading=null before loadFile is called\", () => {\n expect(presenter.vm.file).toBeNull();\n expect(presenter.vm.loading).toBeNull();\n });\n\n it(\"should have a form vm with layout and isDirty=false initially\", () => {\n expect(presenter.vm.form).toBeDefined();\n expect(presenter.vm.form.layout).toBeDefined();\n expect(presenter.vm.form.isDirty).toBe(false);\n });\n\n it(\"should have null previewUrl when no file is loaded\", () => {\n expect(presenter.vm.previewUrl).toBeNull();\n });\n\n // -----------------------------------------------------------------------\n // loadFile populates vm with file data and FormModel.\n // -----------------------------------------------------------------------\n\n it(\"should populate vm.file after loadFile\", async () => {\n await presenter.loadFile(\"file-1\");\n\n expect(presenter.vm.file).not.toBeNull();\n expect(presenter.vm.file!.id).toBe(\"file-1\");\n expect(presenter.vm.file!.name).toBe(\"photo.jpg\");\n });\n\n it(\"should set loading=null after loadFile completes\", async () => {\n await presenter.loadFile(\"file-1\");\n\n expect(presenter.vm.loading).toBeNull();\n });\n\n it(\"should call GetFileUseCase.execute with the file id\", async () => {\n await presenter.loadFile(\"file-1\");\n\n expect(mocks.getFileUseCase.execute).toHaveBeenCalledWith({ id: \"file-1\" });\n });\n\n it(\"should rebuild the form and call setData after loadFile\", async () => {\n await presenter.loadFile(\"file-1\");\n\n // FormModelFactory.create should be called twice: once in constructor, once in loadFile.\n expect(mocks.formModelFactory.create).toHaveBeenCalledTimes(2);\n });\n\n it(\"should have form.isDirty=false after loadFile\", async () => {\n await presenter.loadFile(\"file-1\");\n\n expect(presenter.vm.form.isDirty).toBe(false);\n });\n\n // Features fill fields in through `vm.form.setData` rather than a method on this presenter, so\n // the form has to be reachable and writable from the view model.\n it(\"should expose a writable form on the view model\", async () => {\n await presenter.loadFile(\"file-1\");\n\n presenter.vm.form.setData(\n { tags: [\"cat\", \"sofa\"], description: \"A cat on a sofa.\" },\n { dirty: true }\n );\n\n expect(presenter.vm.form.getData()).toMatchObject({\n description: \"A cat on a sofa.\",\n tags: [\"cat\", \"sofa\"]\n });\n });\n\n // -----------------------------------------------------------------------\n // Form submission triggers UpdateFileUseCase.\n // -----------------------------------------------------------------------\n\n it(\"should call UpdateFileUseCase.execute when saveFile is called\", async () => {\n await presenter.loadFile(\"file-1\");\n\n // Mock the form submit to return data.\n const mockForm = (mocks.formModelFactory.create as ReturnType<typeof vi.fn>).mock.results[1]\n .value as IFormModel;\n (mockForm.submit as ReturnType<typeof vi.fn>).mockResolvedValue({\n name: \"updated.jpg\",\n tags: [\"updated\"],\n accessControl: \"private-authenticated\"\n });\n\n await presenter.saveFile();\n\n expect(mocks.updateFileUseCase.execute).toHaveBeenCalledWith({\n id: \"file-1\",\n data: {\n name: \"updated.jpg\",\n tags: [\"updated\"],\n accessControl: { type: \"private-authenticated\" }\n }\n });\n });\n\n it(\"should not call UpdateFileUseCase when no file is loaded\", async () => {\n await presenter.saveFile();\n\n expect(mocks.updateFileUseCase.execute).not.toHaveBeenCalled();\n });\n\n it(\"should not call UpdateFileUseCase when form submit returns false\", async () => {\n await presenter.loadFile(\"file-1\");\n\n // Mock the form submit to return false (validation failure).\n const mockForm = (mocks.formModelFactory.create as ReturnType<typeof vi.fn>).mock.results[1]\n .value as IFormModel;\n (mockForm.submit as ReturnType<typeof vi.fn>).mockResolvedValue(false);\n\n await presenter.saveFile();\n\n expect(mocks.updateFileUseCase.execute).not.toHaveBeenCalled();\n });\n\n // -----------------------------------------------------------------------\n // Permission flags control action visibility.\n // -----------------------------------------------------------------------\n\n it(\"should expose permission flags in vm.permissions\", () => {\n expect(presenter.vm.permissions.canEdit).toBe(true);\n expect(presenter.vm.permissions.canDelete).toBe(false);\n });\n\n it(\"should reflect updated permission mock values\", () => {\n // Override permissions to both false.\n (mocks.permissions.canEdit as ReturnType<typeof vi.fn>).mockReturnValue(false);\n (mocks.permissions.canDelete as ReturnType<typeof vi.fn>).mockReturnValue(true);\n\n expect(presenter.vm.permissions.canEdit).toBe(false);\n expect(presenter.vm.permissions.canDelete).toBe(true);\n });\n\n // -----------------------------------------------------------------------\n // previewUrl construction from srcPrefix + key.\n // -----------------------------------------------------------------------\n\n it(\"should construct previewUrl from srcPrefix + file.key after loadFile\", async () => {\n await presenter.loadFile(\"file-1\");\n\n expect(presenter.vm.previewUrl).toBe(\"https://cdn.example.com/files/photo.jpg\");\n });\n\n it(\"should return null previewUrl when file has no key\", async () => {\n (mocks.getFileUseCase.execute as ReturnType<typeof vi.fn>).mockResolvedValue({\n success: true,\n file: createTestFile({ key: undefined })\n });\n\n await presenter.loadFile(\"file-1\");\n\n expect(presenter.vm.previewUrl).toBeNull();\n });\n\n // -----------------------------------------------------------------------\n // Loading state transitions.\n // -----------------------------------------------------------------------\n\n it(\"should set loading=true during loadFile and false after\", async () => {\n // Capture loading state during execution.\n let loadingDuringExecution: string | null = null;\n\n (mocks.getFileUseCase.execute as ReturnType<typeof vi.fn>).mockImplementation(async () => {\n loadingDuringExecution = presenter.vm.loading;\n return { success: true, file: createTestFile() };\n });\n\n await presenter.loadFile(\"file-1\");\n\n expect(loadingDuringExecution).toBe(\"Loading file...\");\n expect(presenter.vm.loading).toBeNull();\n });\n});\n"],"names":["createMockFormModel","overrides","data","vm","vi","incoming","Object","fields","name","value","createMockFormModelFactory","mockForm","createMockGetFileUseCase","createTestFile","createMockUpdateFileUseCase","createMockDeleteFileUseCase","createMockPermissions","createMockSettingsRepository","createMocks","createContainer","mocks","container","Container","GetFileUseCase","UpdateFileUseCase","DeleteFileUseCase","FormModelFactory","FileManagerPermissions","GetSettingsRepository","FileDetailsPresenter","describe","presenter","beforeEach","Abstraction","it","expect","undefined","loadingDuringExecution"],"mappings":"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"}
@@ -14,6 +14,8 @@ export interface IFileDetailsPresenter {
14
14
  vm: IFileDetailsViewModel;
15
15
  loadFile(id: string): Promise<void>;
16
16
  saveFile(): Promise<boolean>;
17
+ /** Replace the currently displayed file (e.g. after an out-of-band update). */
18
+ setFile(file: FmFile): void;
17
19
  }
18
20
  export declare const FileDetailsPresenter: import("@webiny/di").Abstraction<IFileDetailsPresenter>;
19
21
  export declare namespace FileDetailsPresenter {
@@ -1 +1 @@
1
- {"version":3,"file":"presentation/FileDetails/abstractions.js","sources":["../../../src/presentation/FileDetails/abstractions.ts"],"sourcesContent":["import { createAbstraction } from \"@webiny/feature/admin\";\nimport type { IFormVM } from \"@webiny/app-admin/features/formModel/abstractions.js\";\nimport type { FmFile } from \"../../features/shared/types.js\";\n\n// ---------------------------------------------------------------------------\n// FileDetailsViewModel\n// ---------------------------------------------------------------------------\n\nexport interface IFileDetailsViewModel {\n file: FmFile | null;\n loading: string | null;\n form: IFormVM;\n previewUrl: string | null;\n permissions: {\n canEdit: boolean;\n canDelete: boolean;\n };\n}\n\n// ---------------------------------------------------------------------------\n// IFileDetailsPresenter\n// ---------------------------------------------------------------------------\n\nexport interface IFileDetailsPresenter {\n vm: IFileDetailsViewModel;\n loadFile(id: string): Promise<void>;\n saveFile(): Promise<boolean>;\n}\n\nexport const FileDetailsPresenter =\n createAbstraction<IFileDetailsPresenter>(\"FileDetailsPresenter\");\n\nexport namespace FileDetailsPresenter {\n export type Interface = IFileDetailsPresenter;\n export type ViewModel = IFileDetailsViewModel;\n}\n"],"names":["FileDetailsPresenter","createAbstraction"],"mappings":";AA6BO,MAAMA,uBACTC,kBAAyC"}
1
+ {"version":3,"file":"presentation/FileDetails/abstractions.js","sources":["../../../src/presentation/FileDetails/abstractions.ts"],"sourcesContent":["import { createAbstraction } from \"@webiny/feature/admin\";\nimport type { IFormVM } from \"@webiny/app-admin/features/formModel/abstractions.js\";\nimport type { FmFile } from \"../../features/shared/types.js\";\n\n// ---------------------------------------------------------------------------\n// FileDetailsViewModel\n// ---------------------------------------------------------------------------\n\nexport interface IFileDetailsViewModel {\n file: FmFile | null;\n loading: string | null;\n form: IFormVM;\n previewUrl: string | null;\n permissions: {\n canEdit: boolean;\n canDelete: boolean;\n };\n}\n\n// ---------------------------------------------------------------------------\n// IFileDetailsPresenter\n// ---------------------------------------------------------------------------\n\nexport interface IFileDetailsPresenter {\n vm: IFileDetailsViewModel;\n loadFile(id: string): Promise<void>;\n saveFile(): Promise<boolean>;\n /** Replace the currently displayed file (e.g. after an out-of-band update). */\n setFile(file: FmFile): void;\n}\n\nexport const FileDetailsPresenter =\n createAbstraction<IFileDetailsPresenter>(\"FileDetailsPresenter\");\n\nexport namespace FileDetailsPresenter {\n export type Interface = IFileDetailsPresenter;\n export type ViewModel = IFileDetailsViewModel;\n}\n"],"names":["FileDetailsPresenter","createAbstraction"],"mappings":";AA+BO,MAAMA,uBACTC,kBAAyC"}
@@ -1,14 +1,16 @@
1
1
  import React from "react";
2
2
  interface ContentProps {
3
3
  children: React.ReactNode;
4
+ className?: string;
5
+ }
6
+ export declare function Content({ children, className }: ContentProps): React.JSX.Element;
7
+ export declare namespace Content {
8
+ export { Panel };
4
9
  }
5
- export declare const Content: {
6
- ({ children }: ContentProps): React.JSX.Element;
7
- Panel: ({ flex, children, className }: PanelProps) => React.JSX.Element;
8
- };
9
10
  interface PanelProps {
10
11
  flex?: number;
11
12
  children: React.ReactNode;
12
13
  className?: string;
13
14
  }
15
+ declare const Panel: ({ flex, children, className }: PanelProps) => React.JSX.Element;
14
16
  export {};
@@ -6,8 +6,8 @@ const Grow = ({ flex, children, className })=>/*#__PURE__*/ react.createElement(
6
6
  flex
7
7
  }
8
8
  }, children);
9
- const Content = ({ children })=>/*#__PURE__*/ react.createElement("div", {
10
- className: "flex h-full"
9
+ const Content = ({ children, className })=>/*#__PURE__*/ react.createElement("div", {
10
+ className: cn("flex min-h-0", className)
11
11
  }, children);
12
12
  const Panel = ({ flex, children, className })=>/*#__PURE__*/ react.createElement(Grow, {
13
13
  "data-role": "panel",
@@ -1 +1 @@
1
- {"version":3,"file":"presentation/FileDetails/components/Content.js","sources":["../../../../src/presentation/FileDetails/components/Content.tsx"],"sourcesContent":["import React from \"react\";\nimport { cn } from \"@webiny/admin-ui\";\n\ninterface GrowProps {\n flex: number;\n className?: string;\n}\n\nconst Grow = ({ flex, children, className }: React.PropsWithChildren<GrowProps>) => {\n return (\n <div\n className={cn(\n \"overflow-y-scroll last-of-type:border-l-sm border-neutral-dimmed\",\n className\n )}\n style={{ flex }}\n >\n {children}\n </div>\n );\n};\n\ninterface ContentProps {\n children: React.ReactNode;\n}\n\nexport const Content = ({ children }: ContentProps) => {\n return <div className={\"flex h-full\"}>{children}</div>;\n};\n\ninterface PanelProps {\n flex?: number;\n children: React.ReactNode;\n className?: string;\n}\n\nconst Panel = ({ flex, children, className }: PanelProps) => {\n return (\n <Grow data-role={\"panel\"} flex={flex ?? 1} className={className}>\n {children}\n </Grow>\n );\n};\n\nContent.Panel = Panel;\n"],"names":["Grow","flex","children","className","cn","Content","Panel"],"mappings":";;AAQA,MAAMA,OAAO,CAAC,EAAEC,IAAI,EAAEC,QAAQ,EAAEC,SAAS,EAAsC,GACpE,WAAP,GACI,oBAAC;QACG,WAAWC,GACP,oEACAD;QAEJ,OAAO;YAAEF;QAAK;OAEbC;AASN,MAAMG,UAAU,CAAC,EAAEH,QAAQ,EAAgB,GACvC,WAAP,GAAO,oBAAC;QAAI,WAAW;OAAgBA;AAS3C,MAAMI,QAAQ,CAAC,EAAEL,IAAI,EAAEC,QAAQ,EAAEC,SAAS,EAAc,GAC7C,WAAP,GACI,oBAACH,MAAIA;QAAC,aAAW;QAAS,MAAMC,QAAQ;QAAG,WAAWE;OACjDD;AAKbG,QAAQ,KAAK,GAAGC"}
1
+ {"version":3,"file":"presentation/FileDetails/components/Content.js","sources":["../../../../src/presentation/FileDetails/components/Content.tsx"],"sourcesContent":["import React from \"react\";\nimport { cn } from \"@webiny/admin-ui\";\n\ninterface GrowProps {\n flex: number;\n className?: string;\n}\n\nconst Grow = ({ flex, children, className }: React.PropsWithChildren<GrowProps>) => {\n return (\n <div\n className={cn(\n \"overflow-y-scroll last-of-type:border-l-sm border-neutral-dimmed\",\n className\n )}\n style={{ flex }}\n >\n {children}\n </div>\n );\n};\n\ninterface ContentProps {\n children: React.ReactNode;\n className?: string;\n}\n\nexport const Content = ({ children, className }: ContentProps) => {\n // `min-h-0` matters: without it this flex child refuses to shrink below its content height, so\n // the panels' own scrollbars never engage and the whole drawer body scrolls instead.\n return <div className={cn(\"flex min-h-0\", className)}>{children}</div>;\n};\n\ninterface PanelProps {\n flex?: number;\n children: React.ReactNode;\n className?: string;\n}\n\nconst Panel = ({ flex, children, className }: PanelProps) => {\n return (\n <Grow data-role={\"panel\"} flex={flex ?? 1} className={className}>\n {children}\n </Grow>\n );\n};\n\nContent.Panel = Panel;\n"],"names":["Grow","flex","children","className","cn","Content","Panel"],"mappings":";;AAQA,MAAMA,OAAO,CAAC,EAAEC,IAAI,EAAEC,QAAQ,EAAEC,SAAS,EAAsC,GACpE,WAAP,GACI,oBAAC;QACG,WAAWC,GACP,oEACAD;QAEJ,OAAO;YAAEF;QAAK;OAEbC;AAUN,MAAMG,UAAU,CAAC,EAAEH,QAAQ,EAAEC,SAAS,EAAgB,GAGlD,WAAP,GAAO,oBAAC;QAAI,WAAWC,GAAG,gBAAgBD;OAAaD;AAS3D,MAAMI,QAAQ,CAAC,EAAEL,IAAI,EAAEC,QAAQ,EAAEC,SAAS,EAAc,GAC7C,WAAP,GACI,oBAACH,MAAIA;QAAC,aAAW;QAAS,MAAMC,QAAQ;QAAG,WAAWE;OACjDD;AAKbG,QAAQ,KAAK,GAAGC"}
@@ -1,4 +1,4 @@
1
1
  import React from "react";
2
- export declare const FileDetailsDrawer: (() => React.JSX.Element | null) & {
3
- displayName: string;
2
+ export declare const FileDetailsDrawer: (() => React.JSX.Element | null) & React.NamedExoticComponent<unknown> & {
3
+ readonly type: () => React.JSX.Element | null;
4
4
  };
@@ -1,6 +1,6 @@
1
1
  import react from "react";
2
2
  import { observer } from "mobx-react-lite";
3
- import { Drawer, OverlayLoader } from "@webiny/admin-ui";
3
+ import { Drawer, OverlayLoader, Separator } from "@webiny/admin-ui";
4
4
  import { FormView } from "@webiny/app-admin/features/formModel/FormView.js";
5
5
  import { useFileManagerPresenter } from "../../FileList/index.js";
6
6
  import { useFileManagerConfig } from "../../config/FileManagerViewConfig.js";
@@ -23,18 +23,24 @@ const FileDetailsDrawer_FileDetailsContent = observer(function() {
23
23
  const { fileDetails: fileDetailsConfig } = useFileManagerConfig();
24
24
  if (!fileDetails) return null;
25
25
  const { leftFlex, rightFlex } = parseWidth(fileDetailsConfig.width);
26
- return /*#__PURE__*/ react.createElement(Content, null, /*#__PURE__*/ react.createElement(Content.Panel, {
26
+ return /*#__PURE__*/ react.createElement("div", {
27
+ className: "flex flex-col h-full"
28
+ }, /*#__PURE__*/ react.createElement("div", {
29
+ className: "px-lg py-sm"
30
+ }, /*#__PURE__*/ react.createElement(Actions, null)), /*#__PURE__*/ react.createElement(Separator, null), /*#__PURE__*/ react.createElement(Content, {
31
+ className: "flex-1"
32
+ }, /*#__PURE__*/ react.createElement(Content.Panel, {
27
33
  flex: leftFlex
28
34
  }, /*#__PURE__*/ react.createElement("div", {
29
- className: "flex flex-col justify-between gap-md h-full px-lg py-md"
30
- }, /*#__PURE__*/ react.createElement(Actions, null), /*#__PURE__*/ react.createElement(Preview, null))), /*#__PURE__*/ react.createElement(Content.Panel, {
35
+ className: "h-full p-lg"
36
+ }, /*#__PURE__*/ react.createElement(Preview, null))), /*#__PURE__*/ react.createElement(Content.Panel, {
31
37
  flex: rightFlex
32
38
  }, /*#__PURE__*/ react.createElement("div", {
33
39
  className: "p-lg"
34
40
  }, /*#__PURE__*/ react.createElement(FormView, {
35
41
  name: "File Details",
36
42
  form: fileDetails.vm.form
37
- }))));
43
+ })))));
38
44
  });
39
45
  const FileDetailsDrawer_FileDetailsDrawer = observer(function() {
40
46
  const { vm, actions } = useFileManagerPresenter();
@@ -1 +1 @@
1
- {"version":3,"file":"presentation/FileDetails/components/FileDetailsDrawer.js","sources":["../../../../src/presentation/FileDetails/components/FileDetailsDrawer.tsx"],"sourcesContent":["import React from \"react\";\nimport { observer } from \"mobx-react-lite\";\nimport { Drawer } from \"@webiny/admin-ui\";\nimport { OverlayLoader } from \"@webiny/admin-ui\";\nimport { FormView } from \"@webiny/app-admin/features/formModel/FormView.js\";\nimport { useFileManagerPresenter } from \"~/presentation/FileList/index.js\";\nimport { useFileManagerConfig } from \"~/presentation/config/FileManagerViewConfig.js\";\nimport { FileProvider } from \"~/presentation/contexts/FileProvider.js\";\nimport { Content } from \"~/presentation/FileDetails/components/Content.js\";\nimport { Preview } from \"~/presentation/FileDetails/components/Preview.js\";\nimport { Actions } from \"~/presentation/FileDetails/components/Actions.js\";\nimport { Description } from \"~/presentation/FileDetails/components/Description.js\";\n\nconst parseWidth = (width: string) => {\n const [drawerWidth, leftPanel = \"1\", rightPanel = \"1\"] = width.split(\",\");\n return {\n drawerWidth,\n leftFlex: parseFloat(leftPanel),\n rightFlex: parseFloat(rightPanel)\n };\n};\n\nconst FileDetailsContent = observer(function FileDetailsContent() {\n const { vm } = useFileManagerPresenter();\n const fileDetails = vm.fileDetails;\n const { fileDetails: fileDetailsConfig } = useFileManagerConfig();\n\n if (!fileDetails) {\n return null;\n }\n\n const { leftFlex, rightFlex } = parseWidth(fileDetailsConfig.width);\n\n return (\n <Content>\n <Content.Panel flex={leftFlex}>\n <div className={\"flex flex-col justify-between gap-md h-full px-lg py-md\"}>\n <Actions />\n <Preview />\n </div>\n </Content.Panel>\n <Content.Panel flex={rightFlex}>\n <div className={\"p-lg\"}>\n <FormView name=\"File Details\" form={fileDetails.vm.form} />\n </div>\n </Content.Panel>\n </Content>\n );\n});\n\nexport const FileDetailsDrawer = observer(function FileDetailsDrawer() {\n const { vm, actions } = useFileManagerPresenter();\n const fileDetails = vm.fileDetails;\n const { fileDetails: fileDetailsConfig } = useFileManagerConfig();\n\n const { drawerWidth } = parseWidth(fileDetailsConfig.width);\n\n if (!fileDetails || !fileDetails.vm.file) {\n return null;\n }\n\n return (\n <FileProvider file={fileDetails.vm.file}>\n <Drawer\n title={fileDetails.vm.file.name ?? \"\"}\n description={<Description />}\n width={drawerWidth}\n open={true}\n modal={true}\n bodyPadding={false}\n headerSeparator={true}\n footerSeparator={true}\n onClose={actions.hideFileDetails}\n data-testid={\"fm.file-details.drawer\"}\n actions={\n <>\n <Drawer.CancelButton text={\"Cancel\"} />\n {fileDetails.vm.permissions.canEdit && (\n <Drawer.ConfirmButton\n text={\"Update\"}\n onClick={async () => {\n const saved = await fileDetails.saveFile();\n if (saved) {\n actions.hideFileDetails();\n }\n }}\n />\n )}\n </>\n }\n >\n {fileDetails.vm.loading && <OverlayLoader text={fileDetails.vm.loading} />}\n <FileDetailsContent />\n </Drawer>\n </FileProvider>\n );\n});\n"],"names":["parseWidth","width","drawerWidth","leftPanel","rightPanel","parseFloat","FileDetailsContent","observer","vm","useFileManagerPresenter","fileDetails","fileDetailsConfig","useFileManagerConfig","leftFlex","rightFlex","Content","Actions","Preview","FormView","FileDetailsDrawer","actions","FileProvider","Drawer","Description","saved","OverlayLoader"],"mappings":";;;;;;;;;;;AAaA,MAAMA,aAAa,CAACC;IAChB,MAAM,CAACC,aAAaC,YAAY,GAAG,EAAEC,aAAa,GAAG,CAAC,GAAGH,MAAM,KAAK,CAAC;IACrE,OAAO;QACHC;QACA,UAAUG,WAAWF;QACrB,WAAWE,WAAWD;IAC1B;AACJ;AAEA,MAAME,uCAAqBC,SAAS;IAChC,MAAM,EAAEC,EAAE,EAAE,GAAGC;IACf,MAAMC,cAAcF,GAAG,WAAW;IAClC,MAAM,EAAE,aAAaG,iBAAiB,EAAE,GAAGC;IAE3C,IAAI,CAACF,aACD,OAAO;IAGX,MAAM,EAAEG,QAAQ,EAAEC,SAAS,EAAE,GAAGd,WAAWW,kBAAkB,KAAK;IAElE,OAAO,WAAP,GACI,oBAACI,SAAOA,MAAAA,WAAAA,GACJ,oBAACA,QAAQ,KAAK;QAAC,MAAMF;qBACjB,oBAAC;QAAI,WAAW;qBACZ,oBAACG,SAAOA,OAAAA,WAAAA,GACR,oBAACC,SAAOA,SAAAA,WAAAA,GAGhB,oBAACF,QAAQ,KAAK;QAAC,MAAMD;qBACjB,oBAAC;QAAI,WAAW;qBACZ,oBAACI,UAAQA;QAAC,MAAK;QAAe,MAAMR,YAAY,EAAE,CAAC,IAAI;;AAK3E;AAEO,MAAMS,sCAAoBZ,SAAS;IACtC,MAAM,EAAEC,EAAE,EAAEY,OAAO,EAAE,GAAGX;IACxB,MAAMC,cAAcF,GAAG,WAAW;IAClC,MAAM,EAAE,aAAaG,iBAAiB,EAAE,GAAGC;IAE3C,MAAM,EAAEV,WAAW,EAAE,GAAGF,WAAWW,kBAAkB,KAAK;IAE1D,IAAI,CAACD,eAAe,CAACA,YAAY,EAAE,CAAC,IAAI,EACpC,OAAO;IAGX,OAAO,WAAP,GACI,oBAACW,cAAYA;QAAC,MAAMX,YAAY,EAAE,CAAC,IAAI;qBACnC,oBAACY,QAAMA;QACH,OAAOZ,YAAY,EAAE,CAAC,IAAI,CAAC,IAAI,IAAI;QACnC,2BAAa,oBAACa,aAAWA;QACzB,OAAOrB;QACP,MAAM;QACN,OAAO;QACP,aAAa;QACb,iBAAiB;QACjB,iBAAiB;QACjB,SAASkB,QAAQ,eAAe;QAChC,eAAa;QACb,uBACI,wDACI,oBAACE,OAAO,YAAY;YAAC,MAAM;YAC1BZ,YAAY,EAAE,CAAC,WAAW,CAAC,OAAO,IAAI,WAAJ,GAC/B,oBAACY,OAAO,aAAa;YACjB,MAAM;YACN,SAAS;gBACL,MAAME,QAAQ,MAAMd,YAAY,QAAQ;gBACxC,IAAIc,OACAJ,QAAQ,eAAe;YAE/B;;OAMfV,YAAY,EAAE,CAAC,OAAO,IAAI,WAAJ,GAAI,oBAACe,eAAaA;QAAC,MAAMf,YAAY,EAAE,CAAC,OAAO;sBACtE,oBAACJ,sCAAkBA;AAInC"}
1
+ {"version":3,"file":"presentation/FileDetails/components/FileDetailsDrawer.js","sources":["../../../../src/presentation/FileDetails/components/FileDetailsDrawer.tsx"],"sourcesContent":["import React from \"react\";\nimport { observer } from \"mobx-react-lite\";\nimport { Drawer, Separator } from \"@webiny/admin-ui\";\nimport { OverlayLoader } from \"@webiny/admin-ui\";\nimport { FormView } from \"@webiny/app-admin/features/formModel/FormView.js\";\nimport { useFileManagerPresenter } from \"~/presentation/FileList/index.js\";\nimport { useFileManagerConfig } from \"~/presentation/config/FileManagerViewConfig.js\";\nimport { FileProvider } from \"~/presentation/contexts/FileProvider.js\";\nimport { Content } from \"~/presentation/FileDetails/components/Content.js\";\nimport { Preview } from \"~/presentation/FileDetails/components/Preview.js\";\nimport { Actions } from \"~/presentation/FileDetails/components/Actions.js\";\nimport { Description } from \"~/presentation/FileDetails/components/Description.js\";\n\nconst parseWidth = (width: string) => {\n const [drawerWidth, leftPanel = \"1\", rightPanel = \"1\"] = width.split(\",\");\n return {\n drawerWidth,\n leftFlex: parseFloat(leftPanel),\n rightFlex: parseFloat(rightPanel)\n };\n};\n\nconst FileDetailsContent = observer(function FileDetailsContent() {\n const { vm } = useFileManagerPresenter();\n const fileDetails = vm.fileDetails;\n const { fileDetails: fileDetailsConfig } = useFileManagerConfig();\n\n if (!fileDetails) {\n return null;\n }\n\n const { leftFlex, rightFlex } = parseWidth(fileDetailsConfig.width);\n\n return (\n <div className={\"flex flex-col h-full\"}>\n {/*\n * The actions row spans the whole drawer, ABOVE the two panels. Inside the left panel it\n * was bounded by that panel's width, so the last action's label was clipped as soon as\n * the row outgrew half the drawer — adding one action broke the one before it.\n *\n * The Separator is load-bearing, not decoration: the panels' vertical divider starts\n * where they start, so without a horizontal rule for it to meet, its top end just hangs\n * in the middle of the drawer. Same toolbar-then-Separator structure as ListViewHeader.\n */}\n <div className={\"px-lg py-sm\"}>\n <Actions />\n </div>\n <Separator />\n <Content className={\"flex-1\"}>\n <Content.Panel flex={leftFlex}>\n <div className={\"h-full p-lg\"}>\n <Preview />\n </div>\n </Content.Panel>\n <Content.Panel flex={rightFlex}>\n <div className={\"p-lg\"}>\n <FormView name=\"File Details\" form={fileDetails.vm.form} />\n </div>\n </Content.Panel>\n </Content>\n </div>\n );\n});\n\nexport const FileDetailsDrawer = observer(function FileDetailsDrawer() {\n const { vm, actions } = useFileManagerPresenter();\n const fileDetails = vm.fileDetails;\n const { fileDetails: fileDetailsConfig } = useFileManagerConfig();\n\n const { drawerWidth } = parseWidth(fileDetailsConfig.width);\n\n if (!fileDetails || !fileDetails.vm.file) {\n return null;\n }\n\n return (\n <FileProvider file={fileDetails.vm.file}>\n <Drawer\n title={fileDetails.vm.file.name ?? \"\"}\n description={<Description />}\n width={drawerWidth}\n open={true}\n modal={true}\n bodyPadding={false}\n headerSeparator={true}\n footerSeparator={true}\n onClose={actions.hideFileDetails}\n data-testid={\"fm.file-details.drawer\"}\n actions={\n <>\n <Drawer.CancelButton text={\"Cancel\"} />\n {fileDetails.vm.permissions.canEdit && (\n <Drawer.ConfirmButton\n text={\"Update\"}\n onClick={async () => {\n const saved = await fileDetails.saveFile();\n if (saved) {\n actions.hideFileDetails();\n }\n }}\n />\n )}\n </>\n }\n >\n {fileDetails.vm.loading && <OverlayLoader text={fileDetails.vm.loading} />}\n <FileDetailsContent />\n </Drawer>\n </FileProvider>\n );\n});\n"],"names":["parseWidth","width","drawerWidth","leftPanel","rightPanel","parseFloat","FileDetailsContent","observer","vm","useFileManagerPresenter","fileDetails","fileDetailsConfig","useFileManagerConfig","leftFlex","rightFlex","Actions","Separator","Content","Preview","FormView","FileDetailsDrawer","actions","FileProvider","Drawer","Description","saved","OverlayLoader"],"mappings":";;;;;;;;;;;AAaA,MAAMA,aAAa,CAACC;IAChB,MAAM,CAACC,aAAaC,YAAY,GAAG,EAAEC,aAAa,GAAG,CAAC,GAAGH,MAAM,KAAK,CAAC;IACrE,OAAO;QACHC;QACA,UAAUG,WAAWF;QACrB,WAAWE,WAAWD;IAC1B;AACJ;AAEA,MAAME,uCAAqBC,SAAS;IAChC,MAAM,EAAEC,EAAE,EAAE,GAAGC;IACf,MAAMC,cAAcF,GAAG,WAAW;IAClC,MAAM,EAAE,aAAaG,iBAAiB,EAAE,GAAGC;IAE3C,IAAI,CAACF,aACD,OAAO;IAGX,MAAM,EAAEG,QAAQ,EAAEC,SAAS,EAAE,GAAGd,WAAWW,kBAAkB,KAAK;IAElE,OAAO,WAAP,GACI,oBAAC;QAAI,WAAW;qBAUZ,oBAAC;QAAI,WAAW;qBACZ,oBAACI,SAAOA,QAAAA,WAAAA,GAEZ,oBAACC,WAASA,OAAAA,WAAAA,GACV,oBAACC,SAAOA;QAAC,WAAW;qBAChB,oBAACA,QAAQ,KAAK;QAAC,MAAMJ;qBACjB,oBAAC;QAAI,WAAW;qBACZ,oBAACK,SAAOA,SAAAA,WAAAA,GAGhB,oBAACD,QAAQ,KAAK;QAAC,MAAMH;qBACjB,oBAAC;QAAI,WAAW;qBACZ,oBAACK,UAAQA;QAAC,MAAK;QAAe,MAAMT,YAAY,EAAE,CAAC,IAAI;;AAM/E;AAEO,MAAMU,sCAAoBb,SAAS;IACtC,MAAM,EAAEC,EAAE,EAAEa,OAAO,EAAE,GAAGZ;IACxB,MAAMC,cAAcF,GAAG,WAAW;IAClC,MAAM,EAAE,aAAaG,iBAAiB,EAAE,GAAGC;IAE3C,MAAM,EAAEV,WAAW,EAAE,GAAGF,WAAWW,kBAAkB,KAAK;IAE1D,IAAI,CAACD,eAAe,CAACA,YAAY,EAAE,CAAC,IAAI,EACpC,OAAO;IAGX,OAAO,WAAP,GACI,oBAACY,cAAYA;QAAC,MAAMZ,YAAY,EAAE,CAAC,IAAI;qBACnC,oBAACa,QAAMA;QACH,OAAOb,YAAY,EAAE,CAAC,IAAI,CAAC,IAAI,IAAI;QACnC,2BAAa,oBAACc,aAAWA;QACzB,OAAOtB;QACP,MAAM;QACN,OAAO;QACP,aAAa;QACb,iBAAiB;QACjB,iBAAiB;QACjB,SAASmB,QAAQ,eAAe;QAChC,eAAa;QACb,uBACI,wDACI,oBAACE,OAAO,YAAY;YAAC,MAAM;YAC1Bb,YAAY,EAAE,CAAC,WAAW,CAAC,OAAO,IAAI,WAAJ,GAC/B,oBAACa,OAAO,aAAa;YACjB,MAAM;YACN,SAAS;gBACL,MAAME,QAAQ,MAAMf,YAAY,QAAQ;gBACxC,IAAIe,OACAJ,QAAQ,eAAe;YAE/B;;OAMfX,YAAY,EAAE,CAAC,OAAO,IAAI,WAAJ,GAAI,oBAACgB,eAAaA;QAAC,MAAMhB,YAAY,EAAE,CAAC,OAAO;sBACtE,oBAACJ,sCAAkBA;AAInC"}
@@ -1,20 +1,16 @@
1
- import type { IDataSource, IDataSourceQuery, IDataSourceMeta } from "@webiny/app-admin/presentation/listPresenter/abstractions.js";
2
- import type { IListFilesUseCase } from "../../features/listFiles/abstractions.js";
1
+ import { FolderAwareDataSource } from "@webiny/app-admin/presentation/listPresenter/FolderAwareDataSource.js";
2
+ import type { FetchParams, FetchResult } from "@webiny/app-admin/presentation/listPresenter/FolderAwareDataSource.js";
3
3
  import type { IGetDescendantFoldersUseCase } from "@webiny/app-aco/features/folders/getDescendantFolders/abstractions.js";
4
4
  import type { IListCache } from "@webiny/app-admin/features/listCache/index.js";
5
+ import type { IListFilesUseCase } from "../../features/listFiles/index.js";
5
6
  import type { FmFile } from "../../features/shared/types.js";
6
- export declare class FileListDataSource implements IDataSource<FmFile> {
7
+ export declare class FileListDataSource extends FolderAwareDataSource<FmFile> {
7
8
  private listFilesUseCase;
8
9
  private cache;
9
- private getDescendantFoldersUseCase?;
10
10
  private scope?;
11
- private _meta;
12
- private _loading;
13
- constructor(listFilesUseCase: IListFilesUseCase, cache: IListCache<FmFile>, getDescendantFoldersUseCase?: IGetDescendantFoldersUseCase | undefined, scope?: string | undefined);
11
+ constructor(listFilesUseCase: IListFilesUseCase, cache: IListCache<FmFile>, getDescendantFoldersUseCase?: IGetDescendantFoldersUseCase, scope?: string | undefined);
14
12
  get rows(): FmFile[];
15
- get meta(): IDataSourceMeta;
16
- get loading(): boolean;
17
- query(params: IDataSourceQuery): Promise<void>;
18
- loadMore(params: IDataSourceQuery): Promise<void>;
19
- private buildWhere;
13
+ fetch(params: FetchParams): Promise<FetchResult<FmFile>>;
14
+ protected shouldExpandFolders(filters: Record<string, unknown>): boolean;
15
+ protected customizeWhere(where: Record<string, unknown>, filters: Record<string, unknown>): void;
20
16
  }
@@ -1,86 +1,41 @@
1
- import { computed, makeAutoObservable, runInAction } from "mobx";
1
+ import { FolderAwareDataSource } from "@webiny/app-admin/presentation/listPresenter/FolderAwareDataSource.js";
2
2
  import { DEFAULT_SCOPE } from "../../domain/constants.js";
3
- class FileListDataSource {
3
+ class FileListDataSource extends FolderAwareDataSource {
4
4
  constructor(listFilesUseCase, cache, getDescendantFoldersUseCase, scope){
5
- this.listFilesUseCase = listFilesUseCase;
6
- this.cache = cache;
7
- this.getDescendantFoldersUseCase = getDescendantFoldersUseCase;
8
- this.scope = scope;
9
- this._meta = {
10
- cursor: null,
11
- hasMoreItems: false,
12
- totalCount: 0
13
- };
14
- this._loading = false;
15
- makeAutoObservable(this, {
16
- listFilesUseCase: false,
17
- getDescendantFoldersUseCase: false,
18
- rows: computed
19
- });
5
+ super({
6
+ keyField: "id",
7
+ getDescendantFolders: getDescendantFoldersUseCase,
8
+ localFilters: {
9
+ accept: (item, value)=>{
10
+ const mimeTypes = value;
11
+ if (!mimeTypes || 0 === mimeTypes.length) return true;
12
+ return mimeTypes.some((pattern)=>{
13
+ if (pattern.endsWith("/*")) return item.type.startsWith(pattern.slice(0, -1));
14
+ return item.type === pattern;
15
+ });
16
+ }
17
+ }
18
+ }), this.listFilesUseCase = listFilesUseCase, this.cache = cache, this.scope = scope;
20
19
  }
21
20
  get rows() {
22
- return this.cache.getItems();
23
- }
24
- get meta() {
25
- return this._meta;
21
+ return this.queryMatcher.filter(this.cache.getItems());
26
22
  }
27
- get loading() {
28
- return this._loading;
29
- }
30
- async query(params) {
31
- this._loading = true;
32
- this.cache.clear();
33
- const where = this.buildWhere(params);
34
- const sort = params.sort ? [
35
- `${params.sort.field}_${params.sort.direction}`
36
- ] : void 0;
37
- const result = await this.listFilesUseCase.execute({
23
+ async fetch(params) {
24
+ return this.listFilesUseCase.execute({
38
25
  search: params.search,
39
- where,
40
- sort,
26
+ where: params.where,
27
+ sort: params.sort,
41
28
  limit: params.limit,
42
- after: params.cursor
43
- });
44
- runInAction(()=>{
45
- this.cache.addItems(result.data);
46
- this._meta = {
47
- cursor: result.meta.cursor,
48
- hasMoreItems: result.meta.hasMoreItems,
49
- totalCount: result.meta.totalCount
50
- };
51
- this._loading = false;
29
+ after: params.after
52
30
  });
53
31
  }
54
- async loadMore(params) {
55
- if (!this._meta.hasMoreItems || this._loading) return;
56
- this._loading = true;
57
- const where = this.buildWhere(params);
58
- const sort = params.sort ? [
59
- `${params.sort.field}_${params.sort.direction}`
60
- ] : void 0;
61
- const result = await this.listFilesUseCase.execute({
62
- search: params.search,
63
- where,
64
- sort,
65
- limit: params.limit,
66
- after: params.cursor
67
- });
68
- runInAction(()=>{
69
- this.cache.addItems(result.data);
70
- this._meta = {
71
- cursor: result.meta.cursor,
72
- hasMoreItems: result.meta.hasMoreItems,
73
- totalCount: result.meta.totalCount
74
- };
75
- this._loading = false;
76
- });
32
+ shouldExpandFolders(filters) {
33
+ return true === filters["includeSubFolders"];
77
34
  }
78
- buildWhere(params) {
79
- const where = {
80
- ...params.filters
81
- };
82
- const includeSubFolders = true === where["includeSubFolders"];
35
+ customizeWhere(where, filters) {
83
36
  delete where["includeSubFolders"];
37
+ const accept = where["accept"];
38
+ delete where["accept"];
84
39
  const tags = where["tags"];
85
40
  const tagsRule = where["tags_rule"] || "OR";
86
41
  delete where["tags"];
@@ -99,22 +54,27 @@ class FileListDataSource {
99
54
  })));
100
55
  where["AND"] = andConditions;
101
56
  }
102
- if (where["folderId"]) {
103
- const currentFolderId = where["folderId"];
104
- const isRoot = "root" === currentFolderId;
105
- if (params.search && isRoot) ;
106
- else if ((params.search || includeSubFolders) && this.getDescendantFoldersUseCase) {
107
- const descendants = this.getDescendantFoldersUseCase.execute(currentFolderId);
108
- const folderIds = descendants.map((f)=>f.id);
109
- where["location"] = {
110
- folderId_in: folderIds
111
- };
112
- } else where["location"] = {
113
- folderId: currentFolderId
114
- };
115
- delete where["folderId"];
57
+ if (accept && accept.length > 0) {
58
+ const exact = [];
59
+ const prefixes = [];
60
+ for (const mime of accept)if (mime.endsWith("/*")) prefixes.push(mime.slice(0, -1));
61
+ else exact.push(mime);
62
+ if (0 === prefixes.length) where["type_in"] = exact;
63
+ else if (0 === exact.length && 1 === prefixes.length) where["type_startsWith"] = prefixes[0];
64
+ else {
65
+ const orConditions = [];
66
+ if (exact.length > 0) orConditions.push({
67
+ type_in: exact
68
+ });
69
+ for (const prefix of prefixes)orConditions.push({
70
+ type_startsWith: prefix
71
+ });
72
+ where["OR"] = [
73
+ ...where["OR"] ?? [],
74
+ ...orConditions
75
+ ];
76
+ }
116
77
  }
117
- return where;
118
78
  }
119
79
  }
120
80
  export { FileListDataSource };