@webiny/app-file-manager 6.6.0-alpha.0 → 6.6.0-alpha.2

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 (132) hide show
  1. package/app.js +8 -0
  2. package/app.js.map +1 -1
  3. package/features/shared/FILE_FIELDS.js +4 -0
  4. package/features/shared/FILE_FIELDS.js.map +1 -1
  5. package/features/shared/FileFieldsProviderWithWcp.d.ts +5 -5
  6. package/features/shared/FileFieldsProviderWithWcp.js +7 -7
  7. package/features/shared/FileFieldsProviderWithWcp.js.map +1 -1
  8. package/modules/AiEnrichment/ReenrichFileGateway.d.ts +12 -0
  9. package/modules/AiEnrichment/ReenrichFileGateway.js +23 -0
  10. package/modules/AiEnrichment/ReenrichFileGateway.js.map +1 -0
  11. package/modules/AiEnrichment/ReenrichWithAi.d.ts +11 -0
  12. package/modules/AiEnrichment/ReenrichWithAi.js +57 -0
  13. package/modules/AiEnrichment/ReenrichWithAi.js.map +1 -0
  14. package/modules/AiEnrichment/ReenrichWithAiPresenter.d.ts +50 -0
  15. package/modules/AiEnrichment/ReenrichWithAiPresenter.js +114 -0
  16. package/modules/AiEnrichment/ReenrichWithAiPresenter.js.map +1 -0
  17. package/modules/AiEnrichment/StreamingPlaceholder.d.ts +15 -0
  18. package/modules/AiEnrichment/StreamingPlaceholder.js +32 -0
  19. package/modules/AiEnrichment/StreamingPlaceholder.js.map +1 -0
  20. package/modules/AiEnrichment/abstractions.d.ts +70 -0
  21. package/modules/AiEnrichment/abstractions.js +6 -0
  22. package/modules/AiEnrichment/abstractions.js.map +1 -0
  23. package/modules/AiEnrichment/feature.d.ts +3 -1
  24. package/modules/AiEnrichment/feature.js +10 -0
  25. package/modules/AiEnrichment/feature.js.map +1 -1
  26. package/modules/Enterprise/index.js +3 -3
  27. package/modules/Enterprise/index.js.map +1 -1
  28. package/modules/HeadlessCms/fieldType/AssetFieldSettingsModifier.d.ts +26 -0
  29. package/modules/HeadlessCms/fieldType/AssetFieldSettingsModifier.js +39 -0
  30. package/modules/HeadlessCms/fieldType/AssetFieldSettingsModifier.js.map +1 -0
  31. package/modules/HeadlessCms/fieldType/AssetFieldType.d.ts +34 -0
  32. package/modules/HeadlessCms/fieldType/AssetFieldType.js +42 -0
  33. package/modules/HeadlessCms/fieldType/AssetFieldType.js.map +1 -0
  34. package/modules/HeadlessCms/fieldType/AssetFormFieldType.d.ts +21 -0
  35. package/modules/HeadlessCms/fieldType/AssetFormFieldType.js +42 -0
  36. package/modules/HeadlessCms/fieldType/AssetFormFieldType.js.map +1 -0
  37. package/modules/HeadlessCms/fieldType/AssetInputRenderer.d.ts +15 -0
  38. package/modules/HeadlessCms/fieldType/AssetInputRenderer.js +20 -0
  39. package/modules/HeadlessCms/fieldType/AssetInputRenderer.js.map +1 -0
  40. package/modules/HeadlessCms/fieldType/AssetInputsRenderer.d.ts +15 -0
  41. package/modules/HeadlessCms/fieldType/AssetInputsRenderer.js +20 -0
  42. package/modules/HeadlessCms/fieldType/AssetInputsRenderer.js.map +1 -0
  43. package/modules/HeadlessCms/fieldType/FileFieldType.d.ts +1 -0
  44. package/modules/HeadlessCms/fieldType/FileFieldType.js +1 -0
  45. package/modules/HeadlessCms/fieldType/FileFieldType.js.map +1 -1
  46. package/modules/HeadlessCms/fieldType/feature.js +10 -0
  47. package/modules/HeadlessCms/fieldType/feature.js.map +1 -1
  48. package/modules/HeadlessCms/fieldType/isAssetField.d.ts +2 -0
  49. package/modules/HeadlessCms/fieldType/isAssetField.js +4 -0
  50. package/modules/HeadlessCms/fieldType/isAssetField.js.map +1 -0
  51. package/package.json +22 -22
  52. package/presentation/FileActions/FileDetails/EditImage.d.ts +2 -0
  53. package/presentation/FileActions/FileDetails/EditImage.js +39 -0
  54. package/presentation/FileActions/FileDetails/EditImage.js.map +1 -0
  55. package/presentation/FileActions/index.js +8 -0
  56. package/presentation/FileActions/index.js.map +1 -1
  57. package/presentation/FileDetails/FileDetailsPresenter.d.ts +2 -0
  58. package/presentation/FileDetails/FileDetailsPresenter.js +5 -0
  59. package/presentation/FileDetails/FileDetailsPresenter.js.map +1 -1
  60. package/presentation/FileDetails/FileDetailsPresenter.test.js +37 -4
  61. package/presentation/FileDetails/FileDetailsPresenter.test.js.map +1 -1
  62. package/presentation/FileDetails/abstractions.d.ts +2 -0
  63. package/presentation/FileDetails/abstractions.js.map +1 -1
  64. package/presentation/FileDetails/components/Content.d.ts +2 -1
  65. package/presentation/FileDetails/components/Content.js +2 -2
  66. package/presentation/FileDetails/components/Content.js.map +1 -1
  67. package/presentation/FileDetails/components/FileDetailsDrawer.d.ts +2 -2
  68. package/presentation/FileDetails/components/FileDetailsDrawer.js +11 -5
  69. package/presentation/FileDetails/components/FileDetailsDrawer.js.map +1 -1
  70. package/presentation/FileList/FileListDataSource.js +34 -1
  71. package/presentation/FileList/FileListDataSource.js.map +1 -1
  72. package/presentation/FileList/FileListPresenter.test.js +2 -1
  73. package/presentation/FileList/FileListPresenter.test.js.map +1 -1
  74. package/presentation/FileList/FileManagerPresenter.js +6 -4
  75. package/presentation/FileList/FileManagerPresenter.js.map +1 -1
  76. package/presentation/FileList/abstractions.d.ts +1 -0
  77. package/presentation/FileList/abstractions.js.map +1 -1
  78. package/presentation/FileList/components/BulkActions/BulkActionBar.d.ts +2 -2
  79. package/presentation/FileList/components/BulkActions/BulkActionDelete.d.ts +2 -2
  80. package/presentation/FileList/components/BulkActions/BulkActionMove.d.ts +2 -2
  81. package/presentation/FileList/components/Filters/SubFoldersToggle.d.ts +2 -2
  82. package/presentation/FileList/components/Filters/TagsFilter.d.ts +2 -2
  83. package/presentation/FileList/components/Filters/TypeFilter.d.ts +2 -2
  84. package/presentation/FileList/components/Grid/FileGrid.d.ts +2 -2
  85. package/presentation/FileList/components/Header/FileManagerHeader.d.ts +2 -2
  86. package/presentation/FileList/components/Search/SearchBar.d.ts +2 -2
  87. package/presentation/FileList/components/Table/FileTable.d.ts +2 -2
  88. package/presentation/FileList/components/Upload/UploadProgress.d.ts +2 -2
  89. package/presentation/FileManager/FileManagerBreadcrumbs.d.ts +12 -0
  90. package/presentation/FileManager/FileManagerBreadcrumbs.js +37 -0
  91. package/presentation/FileManager/FileManagerBreadcrumbs.js.map +1 -0
  92. package/presentation/FileManager/FileManagerView.js +40 -8
  93. package/presentation/FileManager/FileManagerView.js.map +1 -1
  94. package/presentation/Settings/components/SettingsView.d.ts +2 -2
  95. package/presentation/config/DefaultFileManagerConfig.js +12 -7
  96. package/presentation/config/DefaultFileManagerConfig.js.map +1 -1
  97. package/presentation/config/thumbnailRenderers/CroppedFileImage.d.ts +18 -0
  98. package/presentation/config/thumbnailRenderers/CroppedFileImage.js +66 -0
  99. package/presentation/config/thumbnailRenderers/CroppedFileImage.js.map +1 -0
  100. package/presentation/config/thumbnailRenderers/FilePreviewImageRenderer.js +6 -9
  101. package/presentation/config/thumbnailRenderers/FilePreviewImageRenderer.js.map +1 -1
  102. package/presentation/config/thumbnailRenderers/GridItemImageRenderer.js +6 -9
  103. package/presentation/config/thumbnailRenderers/GridItemImageRenderer.js.map +1 -1
  104. package/presentation/config/thumbnailRenderers/TableItemImageRenderer.js +6 -9
  105. package/presentation/config/thumbnailRenderers/TableItemImageRenderer.js.map +1 -1
  106. package/presentation/fieldRenderers/CmsAssetPickerRenderer.d.ts +22 -0
  107. package/presentation/fieldRenderers/CmsAssetPickerRenderer.js +56 -0
  108. package/presentation/fieldRenderers/CmsAssetPickerRenderer.js.map +1 -0
  109. package/presentation/fieldRenderers/CmsFilePickerRenderer.d.ts +1 -1
  110. package/presentation/fieldRenderers/CmsFilePickerRenderer.js +11 -4
  111. package/presentation/fieldRenderers/CmsFilePickerRenderer.js.map +1 -1
  112. package/presentation/fieldRenderers/CmsImageEditorDialog.d.ts +7 -0
  113. package/presentation/fieldRenderers/CmsImageEditorDialog.js +32 -0
  114. package/presentation/fieldRenderers/CmsImageEditorDialog.js.map +1 -0
  115. package/presentation/fieldRenderers/CmsMultiAssetPickerRenderer.d.ts +18 -0
  116. package/presentation/fieldRenderers/CmsMultiAssetPickerRenderer.js +88 -0
  117. package/presentation/fieldRenderers/CmsMultiAssetPickerRenderer.js.map +1 -0
  118. package/presentation/fieldRenderers/CmsMultiFilePickerRenderer.d.ts +1 -1
  119. package/presentation/fieldRenderers/CmsMultiFilePickerRenderer.js +11 -2
  120. package/presentation/fieldRenderers/CmsMultiFilePickerRenderer.js.map +1 -1
  121. package/presentation/fieldRenderers/CroppedAssetThumb.d.ts +15 -0
  122. package/presentation/fieldRenderers/CroppedAssetThumb.js +25 -0
  123. package/presentation/fieldRenderers/CroppedAssetThumb.js.map +1 -0
  124. package/presentation/fieldRenderers/assetValue.d.ts +10 -0
  125. package/presentation/fieldRenderers/assetValue.js +88 -0
  126. package/presentation/fieldRenderers/assetValue.js.map +1 -0
  127. package/presentation/hooks/useCmsImageEditor.d.ts +10 -0
  128. package/presentation/hooks/useCmsImageEditor.js +76 -0
  129. package/presentation/hooks/useCmsImageEditor.js.map +1 -0
  130. package/presentation/hooks/useEditImage.d.ts +14 -0
  131. package/presentation/hooks/useEditImage.js +67 -0
  132. package/presentation/hooks/useEditImage.js.map +1 -0
@@ -8,6 +8,8 @@ import { Download as Download_js_Download } from "./FileDetails/Download.js";
8
8
  import { MoveToFolder as MoveToFolder_js_MoveToFolder } from "./FileDetails/MoveToFolder.js";
9
9
  import { CopyUrl } from "./FileDetails/CopyUrl.js";
10
10
  import { DeleteImage } from "./FileDetails/DeleteImage.js";
11
+ import { EditImage } from "./FileDetails/EditImage.js";
12
+ import { ReenrichWithAi } from "../../modules/AiEnrichment/ReenrichWithAi.js";
11
13
  const { Browser: Browser, FileDetails: FileDetails } = FileManagerViewConfig;
12
14
  const FileActions = ()=>/*#__PURE__*/ react.createElement(react.Fragment, null, /*#__PURE__*/ react.createElement(Browser.Grid.Item.Action, {
13
15
  name: "settings",
@@ -21,6 +23,9 @@ const FileActions = ()=>/*#__PURE__*/ react.createElement(react.Fragment, null,
21
23
  }), /*#__PURE__*/ react.createElement(Browser.Grid.Item.Action, {
22
24
  name: "delete",
23
25
  element: /*#__PURE__*/ react.createElement(Delete, null)
26
+ }), /*#__PURE__*/ react.createElement(FileDetails.Action, {
27
+ name: "editImage",
28
+ element: /*#__PURE__*/ react.createElement(EditImage, null)
24
29
  }), /*#__PURE__*/ react.createElement(FileDetails.Action, {
25
30
  name: "download",
26
31
  element: /*#__PURE__*/ react.createElement(Download_js_Download, null)
@@ -33,6 +38,9 @@ const FileActions = ()=>/*#__PURE__*/ react.createElement(react.Fragment, null,
33
38
  }), /*#__PURE__*/ react.createElement(FileDetails.Action, {
34
39
  name: "delete",
35
40
  element: /*#__PURE__*/ react.createElement(DeleteImage, null)
41
+ }), /*#__PURE__*/ react.createElement(FileDetails.Action, {
42
+ name: "reenrichWithAi",
43
+ element: /*#__PURE__*/ react.createElement(ReenrichWithAi, null)
36
44
  }));
37
45
  export { FileActions };
38
46
 
@@ -1 +1 @@
1
- {"version":3,"file":"presentation/FileActions/index.js","sources":["../../../src/presentation/FileActions/index.tsx"],"sourcesContent":["import React from \"react\";\nimport { FileManagerViewConfig } from \"~/index.js\";\nimport { Delete as GridDelete } from \"./Grid/Delete.js\";\nimport { Download as GridDownload } from \"./Grid/Download.js\";\nimport { MoveToFolder as GridMoveToFolder } from \"./Grid/MoveToFolder.js\";\nimport { Settings as GridSettings } from \"./Grid/Settings.js\";\nimport { Download as FileDetailsDownload } from \"./FileDetails/Download.js\";\nimport { MoveToFolder as FileDetailsMoveToFolder } from \"./FileDetails/MoveToFolder.js\";\nimport { CopyUrl as FileDetailsCopyUrl } from \"./FileDetails/CopyUrl.js\";\nimport { DeleteImage as FileDetailsDeleteImage } from \"./FileDetails/DeleteImage.js\";\n\nconst { Browser, FileDetails } = FileManagerViewConfig;\n\nexport const FileActions = () => {\n return (\n <>\n {/* Grid actions. */}\n <Browser.Grid.Item.Action name={\"settings\"} element={<GridSettings />} />\n <Browser.Grid.Item.Action name={\"download\"} element={<GridDownload />} />\n <Browser.Grid.Item.Action name={\"move\"} element={<GridMoveToFolder />} />\n <Browser.Grid.Item.Action name={\"delete\"} element={<GridDelete />} />\n {/* File details actions. */}\n <FileDetails.Action name={\"download\"} element={<FileDetailsDownload />} />\n <FileDetails.Action name={\"moveToFolder\"} element={<FileDetailsMoveToFolder />} />\n <FileDetails.Action name={\"copyUrl\"} element={<FileDetailsCopyUrl />} />\n <FileDetails.Action name={\"delete\"} element={<FileDetailsDeleteImage />} />\n </>\n );\n};\n"],"names":["Browser","FileDetails","FileManagerViewConfig","FileActions","GridSettings","GridDownload","GridMoveToFolder","GridDelete","FileDetailsDownload","FileDetailsMoveToFolder","FileDetailsCopyUrl","FileDetailsDeleteImage"],"mappings":";;;;;;;;;;AAWA,MAAM,EAAEA,SAAAA,OAAO,EAAEC,aAAAA,WAAW,EAAE,GAAGC;AAE1B,MAAMC,cAAc,IAChB,WAAP,GACI,wDAEI,oBAACH,QAAQ,IAAI,CAAC,IAAI,CAAC,MAAM;QAAC,MAAM;QAAY,uBAAS,oBAACI,UAAYA;sBAClE,oBAACJ,QAAQ,IAAI,CAAC,IAAI,CAAC,MAAM;QAAC,MAAM;QAAY,uBAAS,oBAACK,UAAYA;sBAClE,oBAACL,QAAQ,IAAI,CAAC,IAAI,CAAC,MAAM;QAAC,MAAM;QAAQ,uBAAS,oBAACM,cAAgBA;sBAClE,oBAACN,QAAQ,IAAI,CAAC,IAAI,CAAC,MAAM;QAAC,MAAM;QAAU,uBAAS,oBAACO,QAAUA;sBAE9D,oBAACN,YAAY,MAAM;QAAC,MAAM;QAAY,uBAAS,oBAACO,sBAAmBA;sBACnE,oBAACP,YAAY,MAAM;QAAC,MAAM;QAAgB,uBAAS,oBAACQ,8BAAuBA;sBAC3E,oBAACR,YAAY,MAAM;QAAC,MAAM;QAAW,uBAAS,oBAACS,SAAkBA;sBACjE,oBAACT,YAAY,MAAM;QAAC,MAAM;QAAU,uBAAS,oBAACU,aAAsBA"}
1
+ {"version":3,"file":"presentation/FileActions/index.js","sources":["../../../src/presentation/FileActions/index.tsx"],"sourcesContent":["import React from \"react\";\nimport { FileManagerViewConfig } from \"~/index.js\";\nimport { Delete as GridDelete } from \"./Grid/Delete.js\";\nimport { Download as GridDownload } from \"./Grid/Download.js\";\nimport { MoveToFolder as GridMoveToFolder } from \"./Grid/MoveToFolder.js\";\nimport { Settings as GridSettings } from \"./Grid/Settings.js\";\nimport { Download as FileDetailsDownload } from \"./FileDetails/Download.js\";\nimport { MoveToFolder as FileDetailsMoveToFolder } from \"./FileDetails/MoveToFolder.js\";\nimport { CopyUrl as FileDetailsCopyUrl } from \"./FileDetails/CopyUrl.js\";\nimport { DeleteImage as FileDetailsDeleteImage } from \"./FileDetails/DeleteImage.js\";\nimport { EditImage as FileDetailsEditImage } from \"./FileDetails/EditImage.js\";\nimport { ReenrichWithAi } from \"~/modules/AiEnrichment/ReenrichWithAi.js\";\n\nconst { Browser, FileDetails } = FileManagerViewConfig;\n\nexport const FileActions = () => {\n return (\n <>\n {/* Grid actions. */}\n <Browser.Grid.Item.Action name={\"settings\"} element={<GridSettings />} />\n <Browser.Grid.Item.Action name={\"download\"} element={<GridDownload />} />\n <Browser.Grid.Item.Action name={\"move\"} element={<GridMoveToFolder />} />\n <Browser.Grid.Item.Action name={\"delete\"} element={<GridDelete />} />\n {/* File details actions. */}\n <FileDetails.Action name={\"editImage\"} element={<FileDetailsEditImage />} />\n <FileDetails.Action name={\"download\"} element={<FileDetailsDownload />} />\n <FileDetails.Action name={\"moveToFolder\"} element={<FileDetailsMoveToFolder />} />\n <FileDetails.Action name={\"copyUrl\"} element={<FileDetailsCopyUrl />} />\n <FileDetails.Action name={\"delete\"} element={<FileDetailsDeleteImage />} />\n <FileDetails.Action name={\"reenrichWithAi\"} element={<ReenrichWithAi />} />\n </>\n );\n};\n"],"names":["Browser","FileDetails","FileManagerViewConfig","FileActions","GridSettings","GridDownload","GridMoveToFolder","GridDelete","FileDetailsEditImage","FileDetailsDownload","FileDetailsMoveToFolder","FileDetailsCopyUrl","FileDetailsDeleteImage","ReenrichWithAi"],"mappings":";;;;;;;;;;;;AAaA,MAAM,EAAEA,SAAAA,OAAO,EAAEC,aAAAA,WAAW,EAAE,GAAGC;AAE1B,MAAMC,cAAc,IAChB,WAAP,GACI,wDAEI,oBAACH,QAAQ,IAAI,CAAC,IAAI,CAAC,MAAM;QAAC,MAAM;QAAY,uBAAS,oBAACI,UAAYA;sBAClE,oBAACJ,QAAQ,IAAI,CAAC,IAAI,CAAC,MAAM;QAAC,MAAM;QAAY,uBAAS,oBAACK,UAAYA;sBAClE,oBAACL,QAAQ,IAAI,CAAC,IAAI,CAAC,MAAM;QAAC,MAAM;QAAQ,uBAAS,oBAACM,cAAgBA;sBAClE,oBAACN,QAAQ,IAAI,CAAC,IAAI,CAAC,MAAM;QAAC,MAAM;QAAU,uBAAS,oBAACO,QAAUA;sBAE9D,oBAACN,YAAY,MAAM;QAAC,MAAM;QAAa,uBAAS,oBAACO,WAAoBA;sBACrE,oBAACP,YAAY,MAAM;QAAC,MAAM;QAAY,uBAAS,oBAACQ,sBAAmBA;sBACnE,oBAACR,YAAY,MAAM;QAAC,MAAM;QAAgB,uBAAS,oBAACS,8BAAuBA;sBAC3E,oBAACT,YAAY,MAAM;QAAC,MAAM;QAAW,uBAAS,oBAACU,SAAkBA;sBACjE,oBAACV,YAAY,MAAM;QAAC,MAAM;QAAU,uBAAS,oBAACW,aAAsBA;sBACpE,oBAACX,YAAY,MAAM;QAAC,MAAM;QAAkB,uBAAS,oBAACY,gBAAcA"}
@@ -5,6 +5,7 @@ import { DeleteFileUseCase } from "../../features/deleteFile/abstractions.js";
5
5
  import { FormModelFactory } from "@webiny/app-admin/features/formModel/abstractions.js";
6
6
  import { FileManagerPermissions } from "../../features/permissions/abstractions.js";
7
7
  import { GetSettingsRepository } from "../../features/settings/abstractions.js";
8
+ import type { FmFile } from "../../features/shared/types.js";
8
9
  declare class FileDetailsPresenterImpl implements IFileDetailsPresenter {
9
10
  private getFileUseCase;
10
11
  private updateFileUseCase;
@@ -18,6 +19,7 @@ declare class FileDetailsPresenterImpl implements IFileDetailsPresenter {
18
19
  constructor(getFileUseCase: GetFileUseCase.Interface, updateFileUseCase: UpdateFileUseCase.Interface, deleteFileUseCase: DeleteFileUseCase.Interface, formModelFactory: FormModelFactory.Interface, permissions: FileManagerPermissions.Interface, settingsRepository: GetSettingsRepository.Interface);
19
20
  get vm(): IFileDetailsViewModel;
20
21
  loadFile(id: string): Promise<void>;
22
+ setFile(file: FmFile): void;
21
23
  saveFile(): Promise<boolean>;
22
24
  private buildForm;
23
25
  private buildPreviewUrl;
@@ -65,6 +65,11 @@ class FileDetailsPresenterImpl {
65
65
  });
66
66
  }
67
67
  }
68
+ setFile(file) {
69
+ runInAction(()=>{
70
+ this.file = file;
71
+ });
72
+ }
68
73
  async saveFile() {
69
74
  if (!this.file) return false;
70
75
  const data = await this.form.submit();
@@ -1 +1 @@
1
- {"version":3,"file":"presentation/FileDetails/FileDetailsPresenter.js","sources":["../../../src/presentation/FileDetails/FileDetailsPresenter.ts"],"sourcesContent":["import { makeAutoObservable, runInAction, computed } from \"mobx\";\nimport {\n FileDetailsPresenter as Abstraction,\n type IFileDetailsPresenter,\n type IFileDetailsViewModel\n} from \"./abstractions.js\";\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 { FileManagerPermissions } from \"../../features/permissions/abstractions.js\";\nimport { GetSettingsRepository } from \"../../features/settings/abstractions.js\";\nimport type { FmFile } from \"../../features/shared/types.js\";\n\nclass FileDetailsPresenterImpl implements IFileDetailsPresenter {\n private file: FmFile | null = null;\n private loading: string | null = null;\n private form: IFormModel;\n\n constructor(\n private getFileUseCase: GetFileUseCase.Interface,\n private updateFileUseCase: UpdateFileUseCase.Interface,\n private deleteFileUseCase: DeleteFileUseCase.Interface,\n private formModelFactory: FormModelFactory.Interface,\n private permissions: FileManagerPermissions.Interface,\n private settingsRepository: GetSettingsRepository.Interface\n ) {\n // Build an empty form initially.\n this.form = this.buildForm();\n\n makeAutoObservable(this, { vm: computed });\n }\n\n get vm(): IFileDetailsViewModel {\n return {\n file: this.file,\n loading: this.loading,\n form: this.form.vm,\n previewUrl: this.buildPreviewUrl(),\n permissions: {\n canEdit: this.permissions.canEdit(\"file\", this.file ?? undefined),\n canDelete: this.permissions.canDelete(\"file\", this.file ?? undefined)\n }\n };\n }\n\n async loadFile(id: string): Promise<void> {\n runInAction(() => {\n this.loading = \"Loading file...\";\n });\n\n try {\n const result = await this.getFileUseCase.execute({ id });\n runInAction(() => {\n if (result.success) {\n this.file = result.file;\n this.form = this.buildForm();\n this.form.setData({\n name: result.file.name ?? \"\",\n description: result.file.description ?? \"\",\n tags: result.file.tags ?? [],\n accessControl: result.file.accessControl?.type ?? \"public\"\n });\n\n if (!this.permissions.canEdit(\"file\", result.file)) {\n this.form.field(\"name\").setDisabled(true);\n this.form.field(\"description\").setDisabled(true);\n this.form.field(\"tags\").setDisabled(true);\n this.form.field(\"accessControl\").setDisabled(true);\n }\n }\n });\n } finally {\n runInAction(() => {\n this.loading = null;\n });\n }\n }\n\n async saveFile(): Promise<boolean> {\n if (!this.file) {\n return false;\n }\n\n const data = await this.form.submit();\n if (!data) {\n return false;\n }\n\n runInAction(() => {\n this.loading = \"Saving changes...\";\n });\n\n try {\n await this.updateFileUseCase.execute({\n id: this.file.id,\n data: {\n name: data.name as string,\n description: data.description as string,\n tags: data.tags as string[],\n accessControl: {\n type: data.accessControl as \"public\" | \"private-authenticated\"\n }\n }\n });\n return true;\n } finally {\n runInAction(() => {\n this.loading = null;\n });\n }\n }\n\n private buildForm(): IFormModel {\n return this.formModelFactory.create({\n fields: fields => ({\n name: fields.text().label(\"Name\").required().placeholder(\"Enter name\"),\n description: fields\n .text()\n .label(\"Description\")\n .renderer(\"textarea\")\n .placeholder(\"Enter description\"),\n tags: fields.text().label(\"Tags\").list().renderer(\"tags\").placeholder(\"Add tags\"),\n accessControl: fields\n .text()\n .label(\"Access Control\")\n .help(\"Control who can access this file.\")\n .options([\n { label: \"Public\", value: \"public\" },\n { label: \"Private (Authenticated)\", value: \"private-authenticated\" }\n ])\n .defaultValue(\"public\")\n }),\n layout: layout => [\n layout.row(\"name\"),\n layout.row(\"description\"),\n layout.row(\"tags\"),\n layout.row(\"accessControl\")\n ]\n });\n }\n\n private buildPreviewUrl(): string | null {\n if (!this.file?.key) {\n return null;\n }\n const srcPrefix = this.settingsRepository.settings?.srcPrefix ?? \"\";\n return `${srcPrefix}${this.file.key}`;\n }\n}\n\nexport const FileDetailsPresenter = Abstraction.createImplementation({\n implementation: FileDetailsPresenterImpl,\n dependencies: [\n GetFileUseCase,\n UpdateFileUseCase,\n DeleteFileUseCase,\n FormModelFactory,\n FileManagerPermissions,\n GetSettingsRepository\n ]\n});\n"],"names":["FileDetailsPresenterImpl","getFileUseCase","updateFileUseCase","deleteFileUseCase","formModelFactory","permissions","settingsRepository","makeAutoObservable","computed","undefined","id","runInAction","result","data","fields","layout","srcPrefix","FileDetailsPresenter","Abstraction","GetFileUseCase","UpdateFileUseCase","DeleteFileUseCase","FormModelFactory","FileManagerPermissions","GetSettingsRepository"],"mappings":"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"}
1
+ {"version":3,"file":"presentation/FileDetails/FileDetailsPresenter.js","sources":["../../../src/presentation/FileDetails/FileDetailsPresenter.ts"],"sourcesContent":["import { makeAutoObservable, runInAction, computed } from \"mobx\";\nimport {\n FileDetailsPresenter as Abstraction,\n type IFileDetailsPresenter,\n type IFileDetailsViewModel\n} from \"./abstractions.js\";\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 { FileManagerPermissions } from \"../../features/permissions/abstractions.js\";\nimport { GetSettingsRepository } from \"../../features/settings/abstractions.js\";\nimport type { FmFile } from \"../../features/shared/types.js\";\n\nclass FileDetailsPresenterImpl implements IFileDetailsPresenter {\n private file: FmFile | null = null;\n private loading: string | null = null;\n private form: IFormModel;\n\n constructor(\n private getFileUseCase: GetFileUseCase.Interface,\n private updateFileUseCase: UpdateFileUseCase.Interface,\n private deleteFileUseCase: DeleteFileUseCase.Interface,\n private formModelFactory: FormModelFactory.Interface,\n private permissions: FileManagerPermissions.Interface,\n private settingsRepository: GetSettingsRepository.Interface\n ) {\n // Build an empty form initially.\n this.form = this.buildForm();\n\n makeAutoObservable(this, { vm: computed });\n }\n\n get vm(): IFileDetailsViewModel {\n return {\n file: this.file,\n loading: this.loading,\n form: this.form.vm,\n previewUrl: this.buildPreviewUrl(),\n permissions: {\n canEdit: this.permissions.canEdit(\"file\", this.file ?? undefined),\n canDelete: this.permissions.canDelete(\"file\", this.file ?? undefined)\n }\n };\n }\n\n async loadFile(id: string): Promise<void> {\n runInAction(() => {\n this.loading = \"Loading file...\";\n });\n\n try {\n const result = await this.getFileUseCase.execute({ id });\n runInAction(() => {\n if (result.success) {\n this.file = result.file;\n this.form = this.buildForm();\n this.form.setData({\n name: result.file.name ?? \"\",\n description: result.file.description ?? \"\",\n tags: result.file.tags ?? [],\n accessControl: result.file.accessControl?.type ?? \"public\"\n });\n\n if (!this.permissions.canEdit(\"file\", result.file)) {\n this.form.field(\"name\").setDisabled(true);\n this.form.field(\"description\").setDisabled(true);\n this.form.field(\"tags\").setDisabled(true);\n this.form.field(\"accessControl\").setDisabled(true);\n }\n }\n });\n } finally {\n runInAction(() => {\n this.loading = null;\n });\n }\n }\n\n setFile(file: FmFile): void {\n runInAction(() => {\n this.file = file;\n });\n }\n\n async saveFile(): Promise<boolean> {\n if (!this.file) {\n return false;\n }\n\n const data = await this.form.submit();\n if (!data) {\n return false;\n }\n\n runInAction(() => {\n this.loading = \"Saving changes...\";\n });\n\n try {\n await this.updateFileUseCase.execute({\n id: this.file.id,\n data: {\n name: data.name as string,\n description: data.description as string,\n tags: data.tags as string[],\n accessControl: {\n type: data.accessControl as \"public\" | \"private-authenticated\"\n }\n }\n });\n return true;\n } finally {\n runInAction(() => {\n this.loading = null;\n });\n }\n }\n\n private buildForm(): IFormModel {\n return this.formModelFactory.create({\n fields: fields => ({\n name: fields.text().label(\"Name\").required().placeholder(\"Enter name\"),\n description: fields\n .text()\n .label(\"Description\")\n .renderer(\"textarea\")\n .placeholder(\"Enter description\"),\n tags: fields.text().label(\"Tags\").list().renderer(\"tags\").placeholder(\"Add tags\"),\n accessControl: fields\n .text()\n .label(\"Access Control\")\n .help(\"Control who can access this file.\")\n .options([\n { label: \"Public\", value: \"public\" },\n { label: \"Private (Authenticated)\", value: \"private-authenticated\" }\n ])\n .defaultValue(\"public\")\n }),\n layout: layout => [\n layout.row(\"name\"),\n layout.row(\"description\"),\n layout.row(\"tags\"),\n layout.row(\"accessControl\")\n ]\n });\n }\n\n private buildPreviewUrl(): string | null {\n if (!this.file?.key) {\n return null;\n }\n const srcPrefix = this.settingsRepository.settings?.srcPrefix ?? \"\";\n return `${srcPrefix}${this.file.key}`;\n }\n}\n\nexport const FileDetailsPresenter = Abstraction.createImplementation({\n implementation: FileDetailsPresenterImpl,\n dependencies: [\n GetFileUseCase,\n UpdateFileUseCase,\n DeleteFileUseCase,\n FormModelFactory,\n FileManagerPermissions,\n GetSettingsRepository\n ]\n});\n"],"names":["FileDetailsPresenterImpl","getFileUseCase","updateFileUseCase","deleteFileUseCase","formModelFactory","permissions","settingsRepository","makeAutoObservable","computed","undefined","id","runInAction","result","file","data","fields","layout","srcPrefix","FileDetailsPresenter","Abstraction","GetFileUseCase","UpdateFileUseCase","DeleteFileUseCase","FormModelFactory","FileManagerPermissions","GetSettingsRepository"],"mappings":"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"}
@@ -9,6 +9,7 @@ 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
11
  function createMockFormModel(overrides) {
12
+ const data = {};
12
13
  const vm = {
13
14
  layout: [],
14
15
  errors: [],
@@ -17,13 +18,26 @@ function createMockFormModel(overrides) {
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(),
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];
40
+ }),
27
41
  fields: vi.fn(),
28
42
  layout: vi.fn(),
29
43
  getData: vi.fn(()=>({
@@ -204,6 +218,25 @@ describe("FileDetailsPresenter", ()=>{
204
218
  await presenter.loadFile("file-1");
205
219
  expect(presenter.vm.form.isDirty).toBe(false);
206
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
+ });
207
240
  it("should call UpdateFileUseCase.execute when saveFile is called", async ()=>{
208
241
  await presenter.loadFile("file-1");
209
242
  const mockForm = mocks.formModelFactory.create.mock.results[1].value;
@@ -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 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 hasErrors: false,\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() 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 // -----------------------------------------------------------------------\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","vm","vi","data","incoming","Object","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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"}
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,8 +1,9 @@
1
1
  import React from "react";
2
2
  interface ContentProps {
3
3
  children: React.ReactNode;
4
+ className?: string;
4
5
  }
5
- export declare function Content({ children }: ContentProps): React.JSX.Element;
6
+ export declare function Content({ children, className }: ContentProps): React.JSX.Element;
6
7
  export declare namespace Content {
7
8
  export { Panel };
8
9
  }
@@ -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"}
@@ -4,7 +4,17 @@ class FileListDataSource extends FolderAwareDataSource {
4
4
  constructor(listFilesUseCase, cache, getDescendantFoldersUseCase, scope){
5
5
  super({
6
6
  keyField: "id",
7
- getDescendantFolders: getDescendantFoldersUseCase
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
+ }
8
18
  }), this.listFilesUseCase = listFilesUseCase, this.cache = cache, this.scope = scope;
9
19
  }
10
20
  get rows() {
@@ -24,6 +34,8 @@ class FileListDataSource extends FolderAwareDataSource {
24
34
  }
25
35
  customizeWhere(where, filters) {
26
36
  delete where["includeSubFolders"];
37
+ const accept = where["accept"];
38
+ delete where["accept"];
27
39
  const tags = where["tags"];
28
40
  const tagsRule = where["tags_rule"] || "OR";
29
41
  delete where["tags"];
@@ -42,6 +54,27 @@ class FileListDataSource extends FolderAwareDataSource {
42
54
  })));
43
55
  where["AND"] = andConditions;
44
56
  }
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
+ }
77
+ }
45
78
  }
46
79
  }
47
80
  export { FileListDataSource };
@@ -1 +1 @@
1
- {"version":3,"file":"presentation/FileList/FileListDataSource.js","sources":["../../../src/presentation/FileList/FileListDataSource.ts"],"sourcesContent":["import { FolderAwareDataSource } from \"@webiny/app-admin/presentation/listPresenter/FolderAwareDataSource.js\";\nimport type {\n FetchParams,\n FetchResult\n} from \"@webiny/app-admin/presentation/listPresenter/FolderAwareDataSource.js\";\nimport type { IGetDescendantFoldersUseCase } from \"@webiny/app-aco/features/folders/getDescendantFolders/abstractions.js\";\nimport type { IListCache } from \"@webiny/app-admin/features/listCache/index.js\";\nimport { DEFAULT_SCOPE } from \"~/domain/constants.js\";\nimport type { IListFilesUseCase } from \"~/features/listFiles/index.js\";\nimport type { FmFile } from \"~/features/shared/types.js\";\n\nexport class FileListDataSource extends FolderAwareDataSource<FmFile> {\n constructor(\n private listFilesUseCase: IListFilesUseCase,\n private cache: IListCache<FmFile>,\n getDescendantFoldersUseCase?: IGetDescendantFoldersUseCase,\n private scope?: string\n ) {\n super({\n keyField: \"id\",\n getDescendantFolders: getDescendantFoldersUseCase\n });\n }\n\n get rows(): FmFile[] {\n return this.queryMatcher.filter(this.cache.getItems());\n }\n\n async fetch(params: FetchParams): Promise<FetchResult<FmFile>> {\n return this.listFilesUseCase.execute({\n search: params.search,\n where: params.where,\n sort: params.sort,\n limit: params.limit,\n after: params.after\n });\n }\n\n protected override shouldExpandFolders(filters: Record<string, unknown>): boolean {\n return filters[\"includeSubFolders\"] === true;\n }\n\n protected override customizeWhere(\n where: Record<string, unknown>,\n filters: Record<string, unknown>\n ): void {\n delete where[\"includeSubFolders\"];\n\n const tags = where[\"tags\"] as string[] | undefined;\n const tagsRule = (where[\"tags_rule\"] as string) || \"OR\";\n delete where[\"tags\"];\n delete where[\"tags_rule\"];\n\n if (this.scope) {\n where[\"tags_startsWith\"] = this.scope;\n } else {\n where[\"tags_not_startsWith\"] = DEFAULT_SCOPE;\n }\n\n if (tags && tags.length > 0) {\n const andConditions: Record<string, unknown>[] = [];\n if (tagsRule === \"OR\") {\n andConditions.push({ tags_in: tags });\n } else {\n andConditions.push(...tags.map(tag => ({ tags_in: [tag] })));\n }\n where[\"AND\"] = andConditions;\n }\n }\n}\n"],"names":["FileListDataSource","FolderAwareDataSource","listFilesUseCase","cache","getDescendantFoldersUseCase","scope","params","filters","where","tags","tagsRule","DEFAULT_SCOPE","andConditions","tag"],"mappings":";;AAWO,MAAMA,2BAA2BC;IACpC,YACYC,gBAAmC,EACnCC,KAAyB,EACjCC,2BAA0D,EAClDC,KAAc,CACxB;QACE,KAAK,CAAC;YACF,UAAU;YACV,sBAAsBD;QAC1B,SARQF,gBAAgB,GAAhBA,kBAAAA,IAAAA,CACAC,KAAK,GAALA,OAAAA,IAAAA,CAEAE,KAAK,GAALA;IAMZ;IAEA,IAAI,OAAiB;QACjB,OAAO,IAAI,CAAC,YAAY,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,QAAQ;IACvD;IAEA,MAAM,MAAMC,MAAmB,EAAgC;QAC3D,OAAO,IAAI,CAAC,gBAAgB,CAAC,OAAO,CAAC;YACjC,QAAQA,OAAO,MAAM;YACrB,OAAOA,OAAO,KAAK;YACnB,MAAMA,OAAO,IAAI;YACjB,OAAOA,OAAO,KAAK;YACnB,OAAOA,OAAO,KAAK;QACvB;IACJ;IAEmB,oBAAoBC,OAAgC,EAAW;QAC9E,OAAOA,AAAiC,SAAjCA,OAAO,CAAC,oBAAoB;IACvC;IAEmB,eACfC,KAA8B,EAC9BD,OAAgC,EAC5B;QACJ,OAAOC,KAAK,CAAC,oBAAoB;QAEjC,MAAMC,OAAOD,KAAK,CAAC,OAAO;QAC1B,MAAME,WAAYF,KAAK,CAAC,YAAY,IAAe;QACnD,OAAOA,KAAK,CAAC,OAAO;QACpB,OAAOA,KAAK,CAAC,YAAY;QAEzB,IAAI,IAAI,CAAC,KAAK,EACVA,KAAK,CAAC,kBAAkB,GAAG,IAAI,CAAC,KAAK;aAErCA,KAAK,CAAC,sBAAsB,GAAGG;QAGnC,IAAIF,QAAQA,KAAK,MAAM,GAAG,GAAG;YACzB,MAAMG,gBAA2C,EAAE;YACnD,IAAIF,AAAa,SAAbA,UACAE,cAAc,IAAI,CAAC;gBAAE,SAASH;YAAK;iBAEnCG,cAAc,IAAI,IAAIH,KAAK,GAAG,CAACI,CAAAA,MAAQ;oBAAE,SAAS;wBAACA;qBAAI;gBAAC;YAE5DL,KAAK,CAAC,MAAM,GAAGI;QACnB;IACJ;AACJ"}
1
+ {"version":3,"file":"presentation/FileList/FileListDataSource.js","sources":["../../../src/presentation/FileList/FileListDataSource.ts"],"sourcesContent":["import { FolderAwareDataSource } from \"@webiny/app-admin/presentation/listPresenter/FolderAwareDataSource.js\";\nimport type {\n FetchParams,\n FetchResult\n} from \"@webiny/app-admin/presentation/listPresenter/FolderAwareDataSource.js\";\nimport type { IGetDescendantFoldersUseCase } from \"@webiny/app-aco/features/folders/getDescendantFolders/abstractions.js\";\nimport type { IListCache } from \"@webiny/app-admin/features/listCache/index.js\";\nimport { DEFAULT_SCOPE } from \"~/domain/constants.js\";\nimport type { IListFilesUseCase } from \"~/features/listFiles/index.js\";\nimport type { FmFile } from \"~/features/shared/types.js\";\n\nexport class FileListDataSource extends FolderAwareDataSource<FmFile> {\n constructor(\n private listFilesUseCase: IListFilesUseCase,\n private cache: IListCache<FmFile>,\n getDescendantFoldersUseCase?: IGetDescendantFoldersUseCase,\n private scope?: string\n ) {\n super({\n keyField: \"id\",\n getDescendantFolders: getDescendantFoldersUseCase,\n localFilters: {\n accept: (item, value) => {\n const mimeTypes = value as string[];\n if (!mimeTypes || mimeTypes.length === 0) {\n return true;\n }\n return mimeTypes.some(pattern => {\n if (pattern.endsWith(\"/*\")) {\n return item.type.startsWith(pattern.slice(0, -1));\n }\n return item.type === pattern;\n });\n }\n }\n });\n }\n\n get rows(): FmFile[] {\n return this.queryMatcher.filter(this.cache.getItems());\n }\n\n async fetch(params: FetchParams): Promise<FetchResult<FmFile>> {\n return this.listFilesUseCase.execute({\n search: params.search,\n where: params.where,\n sort: params.sort,\n limit: params.limit,\n after: params.after\n });\n }\n\n protected override shouldExpandFolders(filters: Record<string, unknown>): boolean {\n return filters[\"includeSubFolders\"] === true;\n }\n\n protected override customizeWhere(\n where: Record<string, unknown>,\n filters: Record<string, unknown>\n ): void {\n delete where[\"includeSubFolders\"];\n\n const accept = where[\"accept\"] as string[] | undefined;\n delete where[\"accept\"];\n\n const tags = where[\"tags\"] as string[] | undefined;\n const tagsRule = (where[\"tags_rule\"] as string) || \"OR\";\n delete where[\"tags\"];\n delete where[\"tags_rule\"];\n\n if (this.scope) {\n where[\"tags_startsWith\"] = this.scope;\n } else {\n where[\"tags_not_startsWith\"] = DEFAULT_SCOPE;\n }\n\n if (tags && tags.length > 0) {\n const andConditions: Record<string, unknown>[] = [];\n if (tagsRule === \"OR\") {\n andConditions.push({ tags_in: tags });\n } else {\n andConditions.push(...tags.map(tag => ({ tags_in: [tag] })));\n }\n where[\"AND\"] = andConditions;\n }\n\n if (accept && accept.length > 0) {\n const exact: string[] = [];\n const prefixes: string[] = [];\n for (const mime of accept) {\n if (mime.endsWith(\"/*\")) {\n prefixes.push(mime.slice(0, -1));\n } else {\n exact.push(mime);\n }\n }\n\n if (prefixes.length === 0) {\n where[\"type_in\"] = exact;\n } else if (exact.length === 0 && prefixes.length === 1) {\n where[\"type_startsWith\"] = prefixes[0];\n } else {\n const orConditions: Record<string, unknown>[] = [];\n if (exact.length > 0) {\n orConditions.push({ type_in: exact });\n }\n for (const prefix of prefixes) {\n orConditions.push({ type_startsWith: prefix });\n }\n where[\"OR\"] = [\n ...((where[\"OR\"] as Record<string, unknown>[]) ?? []),\n ...orConditions\n ];\n }\n }\n }\n}\n"],"names":["FileListDataSource","FolderAwareDataSource","listFilesUseCase","cache","getDescendantFoldersUseCase","scope","item","value","mimeTypes","pattern","params","filters","where","accept","tags","tagsRule","DEFAULT_SCOPE","andConditions","tag","exact","prefixes","mime","orConditions","prefix"],"mappings":"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"}
@@ -213,7 +213,8 @@ function createMockFileDetailsPresenter() {
213
213
  }
214
214
  },
215
215
  loadFile: vi.fn().mockResolvedValue(void 0),
216
- saveFile: vi.fn().mockResolvedValue(void 0)
216
+ saveFile: vi.fn().mockResolvedValue(void 0),
217
+ setFile: vi.fn()
217
218
  };
218
219
  }
219
220
  function createMocks() {