@webiny/app-file-manager 6.4.7 → 6.4.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webiny/app-file-manager",
3
- "version": "6.4.7",
3
+ "version": "6.4.8",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": "./index.js",
@@ -16,22 +16,22 @@
16
16
  "@apollo/react-common": "3.1.4",
17
17
  "@apollo/react-components": "3.1.5",
18
18
  "@types/react": "18.3.31",
19
- "@webiny/admin-ui": "6.4.7",
20
- "@webiny/app": "6.4.7",
21
- "@webiny/app-aco": "6.4.7",
22
- "@webiny/app-admin": "6.4.7",
23
- "@webiny/app-headless-cms": "6.4.7",
24
- "@webiny/app-headless-cms-common": "6.4.7",
25
- "@webiny/app-websockets": "6.4.7",
19
+ "@webiny/admin-ui": "6.4.8",
20
+ "@webiny/app": "6.4.8",
21
+ "@webiny/app-aco": "6.4.8",
22
+ "@webiny/app-admin": "6.4.8",
23
+ "@webiny/app-headless-cms": "6.4.8",
24
+ "@webiny/app-headless-cms-common": "6.4.8",
25
+ "@webiny/app-websockets": "6.4.8",
26
26
  "@webiny/di": "1.0.2",
27
- "@webiny/feature": "6.4.7",
28
- "@webiny/form": "6.4.7",
29
- "@webiny/icons": "6.4.7",
30
- "@webiny/plugins": "6.4.7",
31
- "@webiny/react-composition": "6.4.7",
32
- "@webiny/react-properties": "6.4.7",
33
- "@webiny/sdk": "6.4.7",
34
- "@webiny/validation": "6.4.7",
27
+ "@webiny/feature": "6.4.8",
28
+ "@webiny/form": "6.4.8",
29
+ "@webiny/icons": "6.4.8",
30
+ "@webiny/plugins": "6.4.8",
31
+ "@webiny/react-composition": "6.4.8",
32
+ "@webiny/react-properties": "6.4.8",
33
+ "@webiny/sdk": "6.4.8",
34
+ "@webiny/validation": "6.4.8",
35
35
  "apollo-cache": "1.3.5",
36
36
  "apollo-client": "2.6.10",
37
37
  "apollo-link": "1.2.14",
@@ -53,7 +53,7 @@
53
53
  },
54
54
  "devDependencies": {
55
55
  "@svgr/webpack": "8.1.0",
56
- "@webiny/build-tools": "6.4.7",
56
+ "@webiny/build-tools": "6.4.8",
57
57
  "rimraf": "6.1.3",
58
58
  "typescript": "6.0.3",
59
59
  "vitest": "4.1.10"
@@ -1,25 +1,26 @@
1
+ import { WcpService } from "@webiny/app-admin/features/wcp/abstractions.js";
1
2
  import { type IFileDetailsPresenter, type IFileDetailsViewModel } from "./abstractions.js";
2
3
  import { GetFileUseCase } from "../../features/getFile/abstractions.js";
3
4
  import { UpdateFileUseCase } from "../../features/updateFile/abstractions.js";
4
- 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
8
  declare class FileDetailsPresenterImpl implements IFileDetailsPresenter {
9
9
  private getFileUseCase;
10
10
  private updateFileUseCase;
11
- private deleteFileUseCase;
12
11
  private formModelFactory;
13
12
  private permissions;
14
13
  private settingsRepository;
14
+ private wcp;
15
15
  private file;
16
16
  private loading;
17
17
  private form;
18
- constructor(getFileUseCase: GetFileUseCase.Interface, updateFileUseCase: UpdateFileUseCase.Interface, deleteFileUseCase: DeleteFileUseCase.Interface, formModelFactory: FormModelFactory.Interface, permissions: FileManagerPermissions.Interface, settingsRepository: GetSettingsRepository.Interface);
18
+ constructor(getFileUseCase: GetFileUseCase.Interface, updateFileUseCase: UpdateFileUseCase.Interface, formModelFactory: FormModelFactory.Interface, permissions: FileManagerPermissions.Interface, settingsRepository: GetSettingsRepository.Interface, wcp: WcpService.Interface);
19
19
  get vm(): IFileDetailsViewModel;
20
20
  loadFile(id: string): Promise<void>;
21
21
  saveFile(): Promise<boolean>;
22
22
  private buildForm;
23
+ private canUsePrivateFiles;
23
24
  private buildPreviewUrl;
24
25
  }
25
26
  export declare const FileDetailsPresenter: typeof FileDetailsPresenterImpl & {
@@ -1,19 +1,19 @@
1
1
  import { computed, makeAutoObservable, runInAction } from "mobx";
2
+ import { WcpService } from "@webiny/app-admin/features/wcp/abstractions.js";
2
3
  import { FileDetailsPresenter } from "./abstractions.js";
3
4
  import { GetFileUseCase } from "../../features/getFile/abstractions.js";
4
5
  import { UpdateFileUseCase } from "../../features/updateFile/abstractions.js";
5
- import { DeleteFileUseCase } from "../../features/deleteFile/abstractions.js";
6
6
  import { FormModelFactory } from "@webiny/app-admin/features/formModel/abstractions.js";
7
7
  import { FileManagerPermissions } from "../../features/permissions/abstractions.js";
8
8
  import { GetSettingsRepository } from "../../features/settings/abstractions.js";
9
9
  class FileDetailsPresenterImpl {
10
- constructor(getFileUseCase, updateFileUseCase, deleteFileUseCase, formModelFactory, permissions, settingsRepository){
10
+ constructor(getFileUseCase, updateFileUseCase, formModelFactory, permissions, settingsRepository, wcp){
11
11
  this.getFileUseCase = getFileUseCase;
12
12
  this.updateFileUseCase = updateFileUseCase;
13
- this.deleteFileUseCase = deleteFileUseCase;
14
13
  this.formModelFactory = formModelFactory;
15
14
  this.permissions = permissions;
16
15
  this.settingsRepository = settingsRepository;
16
+ this.wcp = wcp;
17
17
  this.file = null;
18
18
  this.loading = null;
19
19
  this.form = this.buildForm();
@@ -45,17 +45,18 @@ class FileDetailsPresenterImpl {
45
45
  if (result.success) {
46
46
  this.file = result.file;
47
47
  this.form = this.buildForm();
48
- this.form.setData({
48
+ const formData = {
49
49
  name: result.file.name ?? "",
50
50
  description: result.file.description ?? "",
51
- tags: result.file.tags ?? [],
52
- accessControl: result.file.accessControl?.type ?? "public"
53
- });
51
+ tags: result.file.tags ?? []
52
+ };
53
+ if (this.canUsePrivateFiles()) formData.accessControl = result.file.accessControl?.type ?? "public";
54
+ this.form.setData(formData);
54
55
  if (!this.permissions.canEdit("file", result.file)) {
55
56
  this.form.field("name").setDisabled(true);
56
57
  this.form.field("description").setDisabled(true);
57
58
  this.form.field("tags").setDisabled(true);
58
- this.form.field("accessControl").setDisabled(true);
59
+ if (this.canUsePrivateFiles()) this.form.field("accessControl").setDisabled(true);
59
60
  }
60
61
  }
61
62
  });
@@ -73,16 +74,17 @@ class FileDetailsPresenterImpl {
73
74
  this.loading = "Saving changes...";
74
75
  });
75
76
  try {
77
+ const updateData = {
78
+ name: data.name,
79
+ description: data.description,
80
+ tags: data.tags
81
+ };
82
+ if (this.canUsePrivateFiles()) updateData.accessControl = {
83
+ type: data.accessControl
84
+ };
76
85
  await this.updateFileUseCase.execute({
77
86
  id: this.file.id,
78
- data: {
79
- name: data.name,
80
- description: data.description,
81
- tags: data.tags,
82
- accessControl: {
83
- type: data.accessControl
84
- }
85
- }
87
+ data: updateData
86
88
  });
87
89
  return true;
88
90
  } finally{
@@ -93,29 +95,38 @@ class FileDetailsPresenterImpl {
93
95
  }
94
96
  buildForm() {
95
97
  return this.formModelFactory.create({
96
- fields: (fields)=>({
98
+ fields: (fields)=>{
99
+ const fieldDefs = {
97
100
  name: fields.text().label("Name").required().placeholder("Enter name"),
98
101
  description: fields.text().label("Description").renderer("textarea").placeholder("Enter description"),
99
- tags: fields.text().label("Tags").list().renderer("tags").placeholder("Add tags"),
100
- accessControl: fields.text().label("Access Control").help("Control who can access this file.").options([
101
- {
102
- label: "Public",
103
- value: "public"
104
- },
105
- {
106
- label: "Private (Authenticated)",
107
- value: "private-authenticated"
108
- }
109
- ]).defaultValue("public")
110
- }),
111
- layout: (layout)=>[
102
+ tags: fields.text().label("Tags").list().renderer("tags").placeholder("Add tags")
103
+ };
104
+ if (this.canUsePrivateFiles()) fieldDefs.accessControl = fields.text().label("Access Control").help("Control who can access this file.").options([
105
+ {
106
+ label: "Public",
107
+ value: "public"
108
+ },
109
+ {
110
+ label: "Private (Authenticated)",
111
+ value: "private-authenticated"
112
+ }
113
+ ]).defaultValue("public");
114
+ return fieldDefs;
115
+ },
116
+ layout: (layout)=>{
117
+ const rows = [
112
118
  layout.row("name"),
113
119
  layout.row("description"),
114
- layout.row("tags"),
115
- layout.row("accessControl")
116
- ]
120
+ layout.row("tags")
121
+ ];
122
+ if (this.canUsePrivateFiles()) rows.push(layout.row("accessControl"));
123
+ return rows;
124
+ }
117
125
  });
118
126
  }
127
+ canUsePrivateFiles() {
128
+ return this.wcp.getProject().canUsePrivateFiles();
129
+ }
119
130
  buildPreviewUrl() {
120
131
  if (!this.file?.key) return null;
121
132
  const srcPrefix = this.settingsRepository.settings?.srcPrefix ?? "";
@@ -127,10 +138,10 @@ const FileDetailsPresenter_FileDetailsPresenter = FileDetailsPresenter.createImp
127
138
  dependencies: [
128
139
  GetFileUseCase,
129
140
  UpdateFileUseCase,
130
- DeleteFileUseCase,
131
141
  FormModelFactory,
132
142
  FileManagerPermissions,
133
- GetSettingsRepository
143
+ GetSettingsRepository,
144
+ WcpService
134
145
  ]
135
146
  });
136
147
  export { FileDetailsPresenter_FileDetailsPresenter as FileDetailsPresenter };
@@ -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":";;;;;;;;AAeA,MAAMA;IAKF,YACYC,cAAwC,EACxCC,iBAA8C,EAC9CC,iBAA8C,EAC9CC,gBAA4C,EAC5CC,WAA6C,EAC7CC,kBAAmD,CAC7D;aANUL,cAAc,GAAdA;aACAC,iBAAiB,GAAjBA;aACAC,iBAAiB,GAAjBA;aACAC,gBAAgB,GAAhBA;aACAC,WAAW,GAAXA;aACAC,kBAAkB,GAAlBA;aAVJ,IAAI,GAAkB;aACtB,OAAO,GAAkB;QAY7B,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,SAAS;QAE1BC,mBAAmB,IAAI,EAAE;YAAE,IAAIC;QAAS;IAC5C;IAEA,IAAI,KAA4B;QAC5B,OAAO;YACH,MAAM,IAAI,CAAC,IAAI;YACf,SAAS,IAAI,CAAC,OAAO;YACrB,MAAM,IAAI,CAAC,IAAI,CAAC,EAAE;YAClB,YAAY,IAAI,CAAC,eAAe;YAChC,aAAa;gBACT,SAAS,IAAI,CAAC,WAAW,CAAC,OAAO,CAAC,QAAQ,IAAI,CAAC,IAAI,IAAIC;gBACvD,WAAW,IAAI,CAAC,WAAW,CAAC,SAAS,CAAC,QAAQ,IAAI,CAAC,IAAI,IAAIA;YAC/D;QACJ;IACJ;IAEA,MAAM,SAASC,EAAU,EAAiB;QACtCC,YAAY;YACR,IAAI,CAAC,OAAO,GAAG;QACnB;QAEA,IAAI;YACA,MAAMC,SAAS,MAAM,IAAI,CAAC,cAAc,CAAC,OAAO,CAAC;gBAAEF;YAAG;YACtDC,YAAY;gBACR,IAAIC,OAAO,OAAO,EAAE;oBAChB,IAAI,CAAC,IAAI,GAAGA,OAAO,IAAI;oBACvB,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,SAAS;oBAC1B,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC;wBACd,MAAMA,OAAO,IAAI,CAAC,IAAI,IAAI;wBAC1B,aAAaA,OAAO,IAAI,CAAC,WAAW,IAAI;wBACxC,MAAMA,OAAO,IAAI,CAAC,IAAI,IAAI,EAAE;wBAC5B,eAAeA,OAAO,IAAI,CAAC,aAAa,EAAE,QAAQ;oBACtD;oBAEA,IAAI,CAAC,IAAI,CAAC,WAAW,CAAC,OAAO,CAAC,QAAQA,OAAO,IAAI,GAAG;wBAChD,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,QAAQ,WAAW,CAAC;wBACpC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,eAAe,WAAW,CAAC;wBAC3C,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,QAAQ,WAAW,CAAC;wBACpC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,iBAAiB,WAAW,CAAC;oBACjD;gBACJ;YACJ;QACJ,SAAU;YACND,YAAY;gBACR,IAAI,CAAC,OAAO,GAAG;YACnB;QACJ;IACJ;IAEA,MAAM,WAA6B;QAC/B,IAAI,CAAC,IAAI,CAAC,IAAI,EACV,OAAO;QAGX,MAAME,OAAO,MAAM,IAAI,CAAC,IAAI,CAAC,MAAM;QACnC,IAAI,CAACA,MACD,OAAO;QAGXF,YAAY;YACR,IAAI,CAAC,OAAO,GAAG;QACnB;QAEA,IAAI;YACA,MAAM,IAAI,CAAC,iBAAiB,CAAC,OAAO,CAAC;gBACjC,IAAI,IAAI,CAAC,IAAI,CAAC,EAAE;gBAChB,MAAM;oBACF,MAAME,KAAK,IAAI;oBACf,aAAaA,KAAK,WAAW;oBAC7B,MAAMA,KAAK,IAAI;oBACf,eAAe;wBACX,MAAMA,KAAK,aAAa;oBAC5B;gBACJ;YACJ;YACA,OAAO;QACX,SAAU;YACNF,YAAY;gBACR,IAAI,CAAC,OAAO,GAAG;YACnB;QACJ;IACJ;IAEQ,YAAwB;QAC5B,OAAO,IAAI,CAAC,gBAAgB,CAAC,MAAM,CAAC;YAChC,QAAQG,CAAAA,SAAW;oBACf,MAAMA,OAAO,IAAI,GAAG,KAAK,CAAC,QAAQ,QAAQ,GAAG,WAAW,CAAC;oBACzD,aAAaA,OACR,IAAI,GACJ,KAAK,CAAC,eACN,QAAQ,CAAC,YACT,WAAW,CAAC;oBACjB,MAAMA,OAAO,IAAI,GAAG,KAAK,CAAC,QAAQ,IAAI,GAAG,QAAQ,CAAC,QAAQ,WAAW,CAAC;oBACtE,eAAeA,OACV,IAAI,GACJ,KAAK,CAAC,kBACN,IAAI,CAAC,qCACL,OAAO,CAAC;wBACL;4BAAE,OAAO;4BAAU,OAAO;wBAAS;wBACnC;4BAAE,OAAO;4BAA2B,OAAO;wBAAwB;qBACtE,EACA,YAAY,CAAC;gBACtB;YACA,QAAQC,CAAAA,SAAU;oBACdA,OAAO,GAAG,CAAC;oBACXA,OAAO,GAAG,CAAC;oBACXA,OAAO,GAAG,CAAC;oBACXA,OAAO,GAAG,CAAC;iBACd;QACL;IACJ;IAEQ,kBAAiC;QACrC,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,KACZ,OAAO;QAEX,MAAMC,YAAY,IAAI,CAAC,kBAAkB,CAAC,QAAQ,EAAE,aAAa;QACjE,OAAO,GAAGA,YAAY,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE;IACzC;AACJ;AAEO,MAAMC,4CAAuBC,qBAAAA,oBAAgC,CAAC;IACjE,gBAAgBlB;IAChB,cAAc;QACVmB;QACAC;QACAC;QACAC;QACAC;QACAC;KACH;AACL"}
1
+ {"version":3,"file":"presentation/FileDetails/FileDetailsPresenter.js","sources":["../../../src/presentation/FileDetails/FileDetailsPresenter.ts"],"sourcesContent":["import { makeAutoObservable, runInAction, computed } from \"mobx\";\nimport { WcpService } from \"@webiny/app-admin/features/wcp/abstractions.js\";\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 { 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\";\nimport type { UpdateFileData } from \"../../features/updateFile/abstractions.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 formModelFactory: FormModelFactory.Interface,\n private permissions: FileManagerPermissions.Interface,\n private settingsRepository: GetSettingsRepository.Interface,\n private wcp: WcpService.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\n const formData: Record<string, unknown> = {\n name: result.file.name ?? \"\",\n description: result.file.description ?? \"\",\n tags: result.file.tags ?? []\n };\n\n if (this.canUsePrivateFiles()) {\n formData.accessControl = result.file.accessControl?.type ?? \"public\";\n }\n\n this.form.setData(formData);\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 if (this.canUsePrivateFiles()) {\n this.form.field(\"accessControl\").setDisabled(true);\n }\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 const updateData: UpdateFileData = {\n name: data.name as string,\n description: data.description as string,\n tags: data.tags as string[]\n };\n\n if (this.canUsePrivateFiles()) {\n updateData.accessControl = {\n type: data.accessControl as \"public\" | \"private-authenticated\"\n };\n }\n\n await this.updateFileUseCase.execute({\n id: this.file.id,\n data: updateData\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 const fieldDefs: Record<string, any> = {\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\n .text()\n .label(\"Tags\")\n .list()\n .renderer(\"tags\")\n .placeholder(\"Add tags\")\n };\n\n if (this.canUsePrivateFiles()) {\n fieldDefs.accessControl = fields\n .text()\n .label(\"Access Control\")\n .help(\"Control who can access this file.\")\n .options([\n { label: \"Public\", value: \"public\" },\n {\n label: \"Private (Authenticated)\",\n value: \"private-authenticated\"\n }\n ])\n .defaultValue(\"public\");\n }\n\n return fieldDefs;\n },\n layout: layout => {\n const rows = [layout.row(\"name\"), layout.row(\"description\"), layout.row(\"tags\")];\n\n if (this.canUsePrivateFiles()) {\n rows.push(layout.row(\"accessControl\"));\n }\n\n return rows;\n }\n });\n }\n\n private canUsePrivateFiles(): boolean {\n return this.wcp.getProject().canUsePrivateFiles();\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 FormModelFactory,\n FileManagerPermissions,\n GetSettingsRepository,\n WcpService\n ]\n});\n"],"names":["FileDetailsPresenterImpl","getFileUseCase","updateFileUseCase","formModelFactory","permissions","settingsRepository","wcp","makeAutoObservable","computed","undefined","id","runInAction","result","formData","data","updateData","fields","fieldDefs","layout","rows","srcPrefix","FileDetailsPresenter","Abstraction","GetFileUseCase","UpdateFileUseCase","FormModelFactory","FileManagerPermissions","GetSettingsRepository","WcpService"],"mappings":"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"}
@@ -8,6 +8,7 @@ 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";
11
12
  function createMockFormModel(overrides) {
12
13
  const vm = {
13
14
  layout: [],
@@ -22,7 +23,9 @@ function createMockFormModel(overrides) {
22
23
  };
23
24
  const data = {};
24
25
  return {
25
- field: vi.fn(),
26
+ field: vi.fn().mockReturnValue({
27
+ setDisabled: vi.fn()
28
+ }),
26
29
  fields: vi.fn(),
27
30
  layout: vi.fn(),
28
31
  getData: vi.fn(()=>({
@@ -104,6 +107,16 @@ function createMockSettingsRepository() {
104
107
  }
105
108
  };
106
109
  }
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
+ }
107
120
  function createTestFile(overrides) {
108
121
  return {
109
122
  id: "file-1",
@@ -138,14 +151,15 @@ function createTestFile(overrides) {
138
151
  ...overrides
139
152
  };
140
153
  }
141
- function createMocks() {
154
+ function createMocks(canUsePrivateFiles = true) {
142
155
  return {
143
156
  getFileUseCase: createMockGetFileUseCase(),
144
157
  updateFileUseCase: createMockUpdateFileUseCase(),
145
158
  deleteFileUseCase: createMockDeleteFileUseCase(),
146
159
  formModelFactory: createMockFormModelFactory(),
147
160
  permissions: createMockPermissions(),
148
- settingsRepository: createMockSettingsRepository()
161
+ settingsRepository: createMockSettingsRepository(),
162
+ wcpService: createMockWcpService(canUsePrivateFiles)
149
163
  };
150
164
  }
151
165
  function createContainer(mocks) {
@@ -156,6 +170,7 @@ function createContainer(mocks) {
156
170
  container.registerInstance(FormModelFactory, mocks.formModelFactory);
157
171
  container.registerInstance(FileManagerPermissions, mocks.permissions);
158
172
  container.registerInstance(GetSettingsRepository, mocks.settingsRepository);
173
+ container.registerInstance(WcpService, mocks.wcpService);
159
174
  container.register(external_FileDetailsPresenter_js_FileDetailsPresenter).inSingletonScope();
160
175
  return container;
161
176
  }
@@ -275,6 +290,42 @@ describe("FileDetailsPresenter", ()=>{
275
290
  expect(loadingDuringExecution).toBe("Loading file...");
276
291
  expect(presenter.vm.loading).toBeNull();
277
292
  });
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
+ });
278
329
  });
279
330
 
280
331
  //# 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 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() 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 { 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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"}