@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.
- package/app.js +21 -2
- package/app.js.map +1 -1
- package/domain/tagsHelpers.d.ts +2 -2
- package/domain/types.d.ts +0 -8
- package/features/permissions/abstractions.d.ts +2 -2
- package/features/permissions/feature.d.ts +3 -3
- package/features/shared/FILE_FIELDS.js +4 -0
- package/features/shared/FILE_FIELDS.js.map +1 -1
- package/features/shared/FileFieldsProviderWithWcp.d.ts +5 -5
- package/features/shared/FileFieldsProviderWithWcp.js +7 -7
- package/features/shared/FileFieldsProviderWithWcp.js.map +1 -1
- package/modules/AiEnrichment/AiImageEnrichmentEventHandler.d.ts +18 -0
- package/modules/AiEnrichment/AiImageEnrichmentEventHandler.js +33 -0
- package/modules/AiEnrichment/AiImageEnrichmentEventHandler.js.map +1 -0
- package/modules/AiEnrichment/ReenrichFileGateway.d.ts +12 -0
- package/modules/AiEnrichment/ReenrichFileGateway.js +23 -0
- package/modules/AiEnrichment/ReenrichFileGateway.js.map +1 -0
- package/modules/AiEnrichment/ReenrichWithAi.d.ts +11 -0
- package/modules/AiEnrichment/ReenrichWithAi.js +57 -0
- package/modules/AiEnrichment/ReenrichWithAi.js.map +1 -0
- package/modules/AiEnrichment/ReenrichWithAiPresenter.d.ts +50 -0
- package/modules/AiEnrichment/ReenrichWithAiPresenter.js +114 -0
- package/modules/AiEnrichment/ReenrichWithAiPresenter.js.map +1 -0
- package/modules/AiEnrichment/StreamingPlaceholder.d.ts +15 -0
- package/modules/AiEnrichment/StreamingPlaceholder.js +32 -0
- package/modules/AiEnrichment/StreamingPlaceholder.js.map +1 -0
- package/modules/AiEnrichment/abstractions.d.ts +70 -0
- package/modules/AiEnrichment/abstractions.js +6 -0
- package/modules/AiEnrichment/abstractions.js.map +1 -0
- package/modules/AiEnrichment/feature.d.ts +3 -0
- package/modules/AiEnrichment/feature.js +21 -0
- package/modules/AiEnrichment/feature.js.map +1 -0
- package/modules/AiEnrichment/index.d.ts +2 -0
- package/modules/AiEnrichment/index.js +9 -0
- package/modules/AiEnrichment/index.js.map +1 -0
- package/modules/Enterprise/index.js +3 -3
- package/modules/Enterprise/index.js.map +1 -1
- package/modules/HeadlessCms/fieldType/AssetFieldSettingsModifier.d.ts +26 -0
- package/modules/HeadlessCms/fieldType/AssetFieldSettingsModifier.js +39 -0
- package/modules/HeadlessCms/fieldType/AssetFieldSettingsModifier.js.map +1 -0
- package/modules/HeadlessCms/fieldType/AssetFieldType.d.ts +34 -0
- package/modules/HeadlessCms/fieldType/AssetFieldType.js +42 -0
- package/modules/HeadlessCms/fieldType/AssetFieldType.js.map +1 -0
- package/modules/HeadlessCms/fieldType/AssetFormFieldType.d.ts +21 -0
- package/modules/HeadlessCms/fieldType/AssetFormFieldType.js +42 -0
- package/modules/HeadlessCms/fieldType/AssetFormFieldType.js.map +1 -0
- package/modules/HeadlessCms/fieldType/AssetInputRenderer.d.ts +15 -0
- package/modules/HeadlessCms/fieldType/AssetInputRenderer.js +20 -0
- package/modules/HeadlessCms/fieldType/AssetInputRenderer.js.map +1 -0
- package/modules/HeadlessCms/fieldType/AssetInputsRenderer.d.ts +15 -0
- package/modules/HeadlessCms/fieldType/AssetInputsRenderer.js +20 -0
- package/modules/HeadlessCms/fieldType/AssetInputsRenderer.js.map +1 -0
- package/modules/HeadlessCms/fieldType/FileFieldSettingsModifier.d.ts +16 -0
- package/modules/HeadlessCms/fieldType/FileFieldSettingsModifier.js +33 -0
- package/modules/HeadlessCms/fieldType/FileFieldSettingsModifier.js.map +1 -0
- package/modules/HeadlessCms/fieldType/FileFieldType.d.ts +25 -0
- package/modules/HeadlessCms/fieldType/FileFieldType.js +38 -0
- package/modules/HeadlessCms/fieldType/FileFieldType.js.map +1 -0
- package/modules/HeadlessCms/fieldType/FileInputRenderer.d.ts +15 -0
- package/modules/HeadlessCms/fieldType/FileInputRenderer.js +19 -0
- package/modules/HeadlessCms/fieldType/FileInputRenderer.js.map +1 -0
- package/modules/HeadlessCms/fieldType/FileInputsRenderer.d.ts +15 -0
- package/modules/HeadlessCms/fieldType/FileInputsRenderer.js +19 -0
- package/modules/HeadlessCms/fieldType/FileInputsRenderer.js.map +1 -0
- package/modules/HeadlessCms/fieldType/feature.d.ts +1 -0
- package/modules/HeadlessCms/fieldType/feature.js +27 -0
- package/modules/HeadlessCms/fieldType/feature.js.map +1 -0
- package/modules/HeadlessCms/fieldType/isAssetField.d.ts +2 -0
- package/modules/HeadlessCms/fieldType/isAssetField.js +4 -0
- package/modules/HeadlessCms/fieldType/isAssetField.js.map +1 -0
- package/package.json +24 -34
- package/presentation/FileActions/FileDetails/EditImage.d.ts +2 -0
- package/presentation/FileActions/FileDetails/EditImage.js +39 -0
- package/presentation/FileActions/FileDetails/EditImage.js.map +1 -0
- package/presentation/FileActions/index.js +8 -0
- package/presentation/FileActions/index.js.map +1 -1
- package/presentation/FileDetails/FileDetailsPresenter.d.ts +5 -4
- package/presentation/FileDetails/FileDetailsPresenter.js +40 -46
- package/presentation/FileDetails/FileDetailsPresenter.js.map +1 -1
- package/presentation/FileDetails/FileDetailsPresenter.test.js +39 -56
- package/presentation/FileDetails/FileDetailsPresenter.test.js.map +1 -1
- package/presentation/FileDetails/abstractions.d.ts +2 -0
- package/presentation/FileDetails/abstractions.js.map +1 -1
- package/presentation/FileDetails/components/Content.d.ts +6 -4
- package/presentation/FileDetails/components/Content.js +2 -2
- package/presentation/FileDetails/components/Content.js.map +1 -1
- package/presentation/FileDetails/components/FileDetailsDrawer.d.ts +2 -2
- package/presentation/FileDetails/components/FileDetailsDrawer.js +11 -5
- package/presentation/FileDetails/components/FileDetailsDrawer.js.map +1 -1
- package/presentation/FileList/FileListDataSource.d.ts +8 -12
- package/presentation/FileList/FileListDataSource.js +47 -87
- package/presentation/FileList/FileListDataSource.js.map +1 -1
- package/presentation/FileList/FileListPresenter.test.js +9 -3
- package/presentation/FileList/FileListPresenter.test.js.map +1 -1
- package/presentation/FileList/FileManagerPresenter.d.ts +1 -0
- package/presentation/FileList/FileManagerPresenter.js +15 -5
- package/presentation/FileList/FileManagerPresenter.js.map +1 -1
- package/presentation/FileList/abstractions.d.ts +3 -0
- package/presentation/FileList/abstractions.js.map +1 -1
- package/presentation/FileList/components/BulkActions/BulkActionBar.d.ts +2 -2
- package/presentation/FileList/components/BulkActions/BulkActionDelete.d.ts +2 -2
- package/presentation/FileList/components/BulkActions/BulkActionMove.d.ts +2 -2
- package/presentation/FileList/components/BulkActions/index.d.ts +0 -1
- package/presentation/FileList/components/BulkActions/index.js +0 -1
- package/presentation/FileList/components/BulkActions/useBulkActionWorker.d.ts +9 -8
- package/presentation/FileList/components/BulkActions/useBulkActionWorker.js +3 -9
- package/presentation/FileList/components/BulkActions/useBulkActionWorker.js.map +1 -1
- package/presentation/FileList/components/Filters/SubFoldersToggle.d.ts +2 -2
- package/presentation/FileList/components/Filters/TagsFilter.d.ts +2 -2
- package/presentation/FileList/components/Filters/TypeFilter.d.ts +2 -2
- package/presentation/FileList/components/Grid/FileGrid.d.ts +2 -2
- package/presentation/FileList/components/Grid/FileGrid.js +1 -1
- package/presentation/FileList/components/Grid/FileGrid.js.map +1 -1
- package/presentation/FileList/components/Grid/FileGrid.test.js +18 -7
- package/presentation/FileList/components/Grid/FileGrid.test.js.map +1 -1
- package/presentation/FileList/components/Header/FileManagerHeader.d.ts +2 -2
- package/presentation/FileList/components/Search/SearchBar.d.ts +2 -2
- package/presentation/FileList/components/Table/FileTable.d.ts +2 -2
- package/presentation/FileList/components/Table/FileTable.js +1 -1
- package/presentation/FileList/components/Table/FileTable.js.map +1 -1
- package/presentation/FileList/components/Table/FileTable.test.js +9 -4
- package/presentation/FileList/components/Table/FileTable.test.js.map +1 -1
- package/presentation/FileList/components/Upload/UploadProgress.d.ts +2 -2
- package/presentation/FileManager/FileManagerBreadcrumbs.d.ts +12 -0
- package/presentation/FileManager/FileManagerBreadcrumbs.js +37 -0
- package/presentation/FileManager/FileManagerBreadcrumbs.js.map +1 -0
- package/presentation/FileManager/FileManagerView.js +36 -2
- package/presentation/FileManager/FileManagerView.js.map +1 -1
- package/presentation/FileManager/RouteParamsSync.d.ts +2 -0
- package/presentation/FileManager/RouteParamsSync.js +30 -0
- package/presentation/FileManager/RouteParamsSync.js.map +1 -0
- package/presentation/Settings/components/SettingsView.d.ts +2 -2
- package/presentation/config/DefaultFileManagerConfig.js +13 -11
- package/presentation/config/DefaultFileManagerConfig.js.map +1 -1
- package/presentation/config/FileManagerViewConfig.d.ts +19 -25
- package/presentation/config/configComponents/Browser/BulkAction.d.ts +10 -8
- package/presentation/config/configComponents/Browser/BulkAction.js +3 -9
- package/presentation/config/configComponents/Browser/BulkAction.js.map +1 -1
- package/presentation/config/configComponents/Browser/FileAction.d.ts +2 -1
- package/presentation/config/configComponents/Browser/FolderAction.d.ts +2 -1
- package/presentation/config/configComponents/Browser/Table/Column.d.ts +9 -8
- package/presentation/config/configComponents/Browser/Table/index.d.ts +9 -9
- package/presentation/config/configComponents/Browser/index.d.ts +20 -20
- package/presentation/config/configComponents/Browser/index.js +0 -8
- package/presentation/config/configComponents/Browser/index.js.map +1 -1
- package/presentation/config/configComponents/FileDetails/index.d.ts +4 -2
- package/presentation/config/thumbnailRenderers/CroppedFileImage.d.ts +18 -0
- package/presentation/config/thumbnailRenderers/CroppedFileImage.js +66 -0
- package/presentation/config/thumbnailRenderers/CroppedFileImage.js.map +1 -0
- package/presentation/config/thumbnailRenderers/FilePreviewImageRenderer.js +6 -9
- package/presentation/config/thumbnailRenderers/FilePreviewImageRenderer.js.map +1 -1
- package/presentation/config/thumbnailRenderers/GridItemImageRenderer.js +6 -9
- package/presentation/config/thumbnailRenderers/GridItemImageRenderer.js.map +1 -1
- package/presentation/config/thumbnailRenderers/TableItemImageRenderer.js +6 -9
- package/presentation/config/thumbnailRenderers/TableItemImageRenderer.js.map +1 -1
- package/presentation/fieldRenderers/CmsAssetPickerRenderer.d.ts +22 -0
- package/presentation/fieldRenderers/CmsAssetPickerRenderer.js +56 -0
- package/presentation/fieldRenderers/CmsAssetPickerRenderer.js.map +1 -0
- package/presentation/fieldRenderers/CmsFilePickerRenderer.d.ts +13 -0
- package/presentation/fieldRenderers/CmsFilePickerRenderer.js +36 -0
- package/presentation/fieldRenderers/CmsFilePickerRenderer.js.map +1 -0
- package/presentation/fieldRenderers/CmsImageEditorDialog.d.ts +7 -0
- package/presentation/fieldRenderers/CmsImageEditorDialog.js +32 -0
- package/presentation/fieldRenderers/CmsImageEditorDialog.js.map +1 -0
- package/presentation/fieldRenderers/CmsMultiAssetPickerRenderer.d.ts +18 -0
- package/presentation/fieldRenderers/CmsMultiAssetPickerRenderer.js +88 -0
- package/presentation/fieldRenderers/CmsMultiAssetPickerRenderer.js.map +1 -0
- package/presentation/fieldRenderers/CmsMultiFilePickerRenderer.d.ts +13 -0
- package/presentation/fieldRenderers/CmsMultiFilePickerRenderer.js +60 -0
- package/presentation/fieldRenderers/CmsMultiFilePickerRenderer.js.map +1 -0
- package/presentation/fieldRenderers/CroppedAssetThumb.d.ts +15 -0
- package/presentation/fieldRenderers/CroppedAssetThumb.js +25 -0
- package/presentation/fieldRenderers/CroppedAssetThumb.js.map +1 -0
- package/presentation/fieldRenderers/assetValue.d.ts +10 -0
- package/presentation/fieldRenderers/assetValue.js +88 -0
- package/presentation/fieldRenderers/assetValue.js.map +1 -0
- package/presentation/hooks/useCmsImageEditor.d.ts +10 -0
- package/presentation/hooks/useCmsImageEditor.js +76 -0
- package/presentation/hooks/useCmsImageEditor.js.map +1 -0
- package/presentation/hooks/useEditImage.d.ts +14 -0
- package/presentation/hooks/useEditImage.js +67 -0
- package/presentation/hooks/useEditImage.js.map +1 -0
- package/routes.d.ts +1 -0
- package/routes.js +2 -1
- package/routes.js.map +1 -1
- package/modules/HeadlessCms/fileField.d.ts +0 -2
- package/modules/HeadlessCms/fileField.js +0 -44
- package/modules/HeadlessCms/fileField.js.map +0 -1
- package/modules/HeadlessCms/fileRenderer/fileField.d.ts +0 -2
- package/modules/HeadlessCms/fileRenderer/fileField.js +0 -53
- package/modules/HeadlessCms/fileRenderer/fileField.js.map +0 -1
- package/modules/HeadlessCms/fileRenderer/fileFields.d.ts +0 -2
- package/modules/HeadlessCms/fileRenderer/fileFields.js +0 -73
- package/modules/HeadlessCms/fileRenderer/fileFields.js.map +0 -1
- package/modules/HeadlessCms/fileRenderer/utils.d.ts +0 -2
- package/modules/HeadlessCms/fileRenderer/utils.js +0 -15
- package/modules/HeadlessCms/fileRenderer/utils.js.map +0 -1
- package/modules/HeadlessCms/index.d.ts +0 -1
- package/modules/HeadlessCms/index.js +0 -14
- package/modules/HeadlessCms/index.js.map +0 -1
- package/modules/Settings/graphql.d.ts +0 -12
- package/modules/Settings/graphql.js +0 -36
- package/modules/Settings/graphql.js.map +0 -1
- package/modules/Settings/views/FileManagerSettings.d.ts +0 -2
- package/modules/Settings/views/FileManagerSettings.js +0 -134
- package/modules/Settings/views/FileManagerSettings.js.map +0 -1
- package/presentation/FileList/components/BulkActions/BatchEditorDialog/AddOperation.d.ts +0 -7
- package/presentation/FileList/components/BulkActions/BatchEditorDialog/AddOperation.js +0 -15
- package/presentation/FileList/components/BulkActions/BatchEditorDialog/AddOperation.js.map +0 -1
- package/presentation/FileList/components/BulkActions/BatchEditorDialog/BatchEditor.d.ts +0 -15
- package/presentation/FileList/components/BulkActions/BatchEditorDialog/BatchEditor.js +0 -44
- package/presentation/FileList/components/BulkActions/BatchEditorDialog/BatchEditor.js.map +0 -1
- package/presentation/FileList/components/BulkActions/BatchEditorDialog/BatchEditorDialog.d.ts +0 -15
- package/presentation/FileList/components/BulkActions/BatchEditorDialog/BatchEditorDialog.js +0 -52
- package/presentation/FileList/components/BulkActions/BatchEditorDialog/BatchEditorDialog.js.map +0 -1
- package/presentation/FileList/components/BulkActions/BatchEditorDialog/BatchEditorDialogPresenter.d.ts +0 -61
- package/presentation/FileList/components/BulkActions/BatchEditorDialog/BatchEditorDialogPresenter.js +0 -111
- package/presentation/FileList/components/BulkActions/BatchEditorDialog/BatchEditorDialogPresenter.js.map +0 -1
- package/presentation/FileList/components/BulkActions/BatchEditorDialog/BatchEditorDialogPresenter.test.d.ts +0 -1
- package/presentation/FileList/components/BulkActions/BatchEditorDialog/BatchEditorDialogPresenter.test.js +0 -372
- package/presentation/FileList/components/BulkActions/BatchEditorDialog/BatchEditorDialogPresenter.test.js.map +0 -1
- package/presentation/FileList/components/BulkActions/BatchEditorDialog/FieldRenderer.d.ts +0 -8
- package/presentation/FileList/components/BulkActions/BatchEditorDialog/FieldRenderer.js +0 -31
- package/presentation/FileList/components/BulkActions/BatchEditorDialog/FieldRenderer.js.map +0 -1
- package/presentation/FileList/components/BulkActions/BatchEditorDialog/Operation.d.ts +0 -11
- package/presentation/FileList/components/BulkActions/BatchEditorDialog/Operation.js +0 -33
- package/presentation/FileList/components/BulkActions/BatchEditorDialog/Operation.js.map +0 -1
- package/presentation/FileList/components/BulkActions/BatchEditorDialog/index.d.ts +0 -1
- package/presentation/FileList/components/BulkActions/BatchEditorDialog/index.js +0 -1
- package/presentation/FileList/components/BulkActions/BulkActionEdit.d.ts +0 -4
- package/presentation/FileList/components/BulkActions/BulkActionEdit.js +0 -105
- package/presentation/FileList/components/BulkActions/BulkActionEdit.js.map +0 -1
- package/presentation/config/configComponents/Browser/FolderFieldDecorator.d.ts +0 -7
- package/presentation/config/configComponents/Browser/FolderFieldDecorator.js +0 -11
- 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
|
-
|
|
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
|
|
29
|
+
const fields = {};
|
|
25
30
|
return {
|
|
26
|
-
field: vi.fn()
|
|
27
|
-
|
|
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(
|
|
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":";
|
|
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-
|
|
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-
|
|
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
|
-
|
|
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(
|
|
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: "
|
|
30
|
-
}, /*#__PURE__*/ react.createElement(
|
|
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 <
|
|
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
|
|
2
|
-
import type {
|
|
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
|
|
7
|
+
export declare class FileListDataSource extends FolderAwareDataSource<FmFile> {
|
|
7
8
|
private listFilesUseCase;
|
|
8
9
|
private cache;
|
|
9
|
-
private getDescendantFoldersUseCase?;
|
|
10
10
|
private scope?;
|
|
11
|
-
|
|
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
|
-
|
|
16
|
-
|
|
17
|
-
|
|
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 {
|
|
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
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
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
|
-
|
|
28
|
-
return this.
|
|
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.
|
|
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
|
-
|
|
55
|
-
|
|
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
|
-
|
|
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 (
|
|
103
|
-
const
|
|
104
|
-
const
|
|
105
|
-
if (
|
|
106
|
-
else
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
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 };
|