@webiny/app-aco 0.0.0-unstable.7be00a75a9 → 0.0.0-unstable.7c2def32a7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (243) hide show
  1. package/app.js +5 -1
  2. package/app.js.map +1 -1
  3. package/components/Actions/DeleteFolder/DeleteFolder.js +1 -1
  4. package/components/Actions/DeleteFolder/DeleteFolder.js.map +1 -1
  5. package/components/AdvancedSearch/AdvancedSearch.d.ts +2 -2
  6. package/components/AdvancedSearch/AdvancedSearch.js +1 -1
  7. package/components/AdvancedSearch/AdvancedSearch.js.map +1 -1
  8. package/components/AdvancedSearch/QueryBuilderDrawer/QueryBuilder/components/OperationSelector.js +2 -2
  9. package/components/AdvancedSearch/QueryBuilderDrawer/QueryBuilder/components/OperationSelector.js.map +1 -1
  10. package/components/AdvancedSearch/QueryBuilderDrawer/QueryBuilderDrawer.d.ts +2 -2
  11. package/components/AdvancedSearch/SelectedFilter/SelectedFilter.js +1 -1
  12. package/components/AdvancedSearch/SelectedFilter/SelectedFilter.js.map +1 -1
  13. package/components/AdvancedSearch/domain/Filter.d.ts +1 -1
  14. package/components/AdvancedSearch/domain/FilterRepositoryFactory.d.ts +2 -2
  15. package/components/AdvancedSearch/domain/FilterRepositoryFactory.js.map +1 -1
  16. package/components/AdvancedSearch/gateways/FiltersGraphQLGateway.d.ts +2 -2
  17. package/components/AdvancedSearch/gateways/FiltersGraphQLGateway.js +9 -10
  18. package/components/AdvancedSearch/gateways/FiltersGraphQLGateway.js.map +1 -1
  19. package/components/AdvancedSearch/gateways/filters.gql.d.ts +5 -5
  20. package/components/AdvancedSearch/gateways/filters.gql.js +5 -6
  21. package/components/AdvancedSearch/gateways/filters.gql.js.map +1 -1
  22. package/components/AdvancedSearch/useFilterRepository.js +7 -2
  23. package/components/AdvancedSearch/useFilterRepository.js.map +1 -1
  24. package/components/FolderTree/List/List.js +14 -6
  25. package/components/FolderTree/List/List.js.map +1 -1
  26. package/components/Table/cells/CellAuthor.d.ts +2 -0
  27. package/components/Table/cells/CellAuthor.js +10 -0
  28. package/components/Table/cells/CellAuthor.js.map +1 -0
  29. package/components/Table/cells/CellCreated.d.ts +2 -0
  30. package/components/Table/cells/CellCreated.js +13 -0
  31. package/components/Table/cells/CellCreated.js.map +1 -0
  32. package/components/Table/cells/CellModified.d.ts +2 -0
  33. package/components/Table/cells/CellModified.js +13 -0
  34. package/components/Table/cells/CellModified.js.map +1 -0
  35. package/components/Table/cells/index.d.ts +3 -0
  36. package/components/Table/cells/index.js +3 -0
  37. package/components/Table/components/Table/TableInner.d.ts +2 -2
  38. package/components/Table/createTableData.d.ts +7 -5
  39. package/components/Table/createTableData.js +22 -7
  40. package/components/Table/createTableData.js.map +1 -1
  41. package/components/Table/index.d.ts +2 -2
  42. package/components/Table/index.js +2 -2
  43. package/components/index.d.ts +0 -1
  44. package/components/index.js +0 -1
  45. package/config/AcoConfig.d.ts +8 -12
  46. package/config/advanced-search/FieldRenderer.d.ts +3 -1
  47. package/config/folder/Action.d.ts +1 -1
  48. package/config/folder/index.d.ts +3 -2
  49. package/config/index.d.ts +0 -1
  50. package/config/index.js +0 -1
  51. package/config/record/Action.d.ts +1 -1
  52. package/config/record/index.d.ts +1 -1
  53. package/config/table/Column.d.ts +8 -39
  54. package/config/table/Column.js +4 -67
  55. package/config/table/Column.js.map +1 -1
  56. package/config/table/Sorting.d.ts +2 -11
  57. package/config/table/Sorting.js +1 -28
  58. package/config/table/index.d.ts +5 -9
  59. package/contexts/index.d.ts +0 -3
  60. package/contexts/index.js +0 -3
  61. package/dialogs/DialogSetPermissions/UsersTeamsSelection/ListItemMeta.js +5 -0
  62. package/dialogs/DialogSetPermissions/UsersTeamsSelection/ListItemMeta.js.map +1 -1
  63. package/dialogs/DialogSetPermissions/graphql.d.ts +1 -1
  64. package/dialogs/DialogSetPermissions/graphql.js +1 -2
  65. package/dialogs/DialogSetPermissions/graphql.js.map +1 -1
  66. package/dialogs/useConfirmMoveFolderDialog.d.ts +1 -0
  67. package/dialogs/useConfirmMoveFolderDialog.js +3 -2
  68. package/dialogs/useConfirmMoveFolderDialog.js.map +1 -1
  69. package/dialogs/useCreateDialog.js +5 -9
  70. package/dialogs/useCreateDialog.js.map +1 -1
  71. package/dialogs/useEditDialog.js +2 -2
  72. package/dialogs/useEditDialog.js.map +1 -1
  73. package/dialogs/useSetPermissionsDialog.js +16 -4
  74. package/dialogs/useSetPermissionsDialog.js.map +1 -1
  75. package/domain/folder/Folder.d.ts +2 -1
  76. package/domain/folder/Folder.js.map +1 -1
  77. package/features/folders/cache/FoldersCacheFactory.d.ts +3 -1
  78. package/features/folders/cache/FoldersCacheFactory.js +5 -0
  79. package/features/folders/cache/FoldersCacheFactory.js.map +1 -1
  80. package/features/folders/cache/updateDescendantPermissions.d.ts +12 -0
  81. package/features/folders/cache/updateDescendantPermissions.js +27 -0
  82. package/features/folders/cache/updateDescendantPermissions.js.map +1 -0
  83. package/features/folders/createFolder/CreateFolderGqlGateway.d.ts +3 -3
  84. package/features/folders/createFolder/CreateFolderGqlGateway.js +15 -16
  85. package/features/folders/createFolder/CreateFolderGqlGateway.js.map +1 -1
  86. package/features/folders/deleteFolder/DeleteFolderGqlGateway.d.ts +3 -3
  87. package/features/folders/deleteFolder/DeleteFolderGqlGateway.js +5 -6
  88. package/features/folders/deleteFolder/DeleteFolderGqlGateway.js.map +1 -1
  89. package/features/folders/folderEvents/AddCreatedFolderToCache.d.ts +22 -0
  90. package/features/folders/folderEvents/AddCreatedFolderToCache.js +24 -0
  91. package/features/folders/folderEvents/AddCreatedFolderToCache.js.map +1 -0
  92. package/features/folders/folderEvents/FolderCreatedEvent.d.ts +14 -0
  93. package/features/folders/folderEvents/FolderCreatedEvent.js +13 -0
  94. package/features/folders/folderEvents/FolderCreatedEvent.js.map +1 -0
  95. package/features/folders/folderEvents/FolderDeletedEvent.d.ts +9 -0
  96. package/features/folders/folderEvents/FolderDeletedEvent.js +13 -0
  97. package/features/folders/folderEvents/FolderDeletedEvent.js.map +1 -0
  98. package/features/folders/folderEvents/FolderEvents.test.d.ts +1 -0
  99. package/features/folders/folderEvents/FolderEvents.test.js +238 -0
  100. package/features/folders/folderEvents/FolderEvents.test.js.map +1 -0
  101. package/features/folders/folderEvents/FolderUpdatedEvent.d.ts +12 -0
  102. package/features/folders/folderEvents/FolderUpdatedEvent.js +13 -0
  103. package/features/folders/folderEvents/FolderUpdatedEvent.js.map +1 -0
  104. package/features/folders/folderEvents/FolderWebsocketMessages.d.ts +15 -0
  105. package/features/folders/folderEvents/FolderWebsocketMessages.js +48 -0
  106. package/features/folders/folderEvents/FolderWebsocketMessages.js.map +1 -0
  107. package/features/folders/folderEvents/RefreshUpdatedFolderInCache.d.ts +19 -0
  108. package/features/folders/folderEvents/RefreshUpdatedFolderInCache.js +27 -0
  109. package/features/folders/folderEvents/RefreshUpdatedFolderInCache.js.map +1 -0
  110. package/features/folders/folderEvents/RemoveDeletedFolderFromCache.d.ts +13 -0
  111. package/features/folders/folderEvents/RemoveDeletedFolderFromCache.js +15 -0
  112. package/features/folders/folderEvents/RemoveDeletedFolderFromCache.js.map +1 -0
  113. package/features/folders/folderEvents/abstractions.d.ts +28 -0
  114. package/features/folders/folderEvents/abstractions.js +7 -0
  115. package/features/folders/folderEvents/abstractions.js.map +1 -0
  116. package/features/folders/folderEvents/feature.d.ts +9 -0
  117. package/features/folders/folderEvents/feature.js +21 -0
  118. package/features/folders/folderEvents/feature.js.map +1 -0
  119. package/features/folders/folderEvents/index.d.ts +5 -0
  120. package/features/folders/folderEvents/index.js +5 -0
  121. package/features/folders/folderModelProvider/FolderModelProvider.js +30 -31
  122. package/features/folders/folderModelProvider/FolderModelProvider.js.map +1 -1
  123. package/features/folders/folderModelProvider/GetFolderModelGqlGateway.d.ts +3 -3
  124. package/features/folders/folderModelProvider/GetFolderModelGqlGateway.js +5 -7
  125. package/features/folders/folderModelProvider/GetFolderModelGqlGateway.js.map +1 -1
  126. package/features/folders/getFolder/GetFolderGqlGateway.d.ts +3 -3
  127. package/features/folders/getFolder/GetFolderGqlGateway.js +5 -7
  128. package/features/folders/getFolder/GetFolderGqlGateway.js.map +1 -1
  129. package/features/folders/getFolderExtensionsFields/GetFolderExtensionsFieldsUseCase.d.ts +4 -3
  130. package/features/folders/getFolderExtensionsFields/useFolderExtensionsFields.d.ts +4 -3
  131. package/features/folders/getFolderLevelPermission/GetFolderLevelPermission.test.js +12 -16
  132. package/features/folders/getFolderLevelPermission/GetFolderLevelPermission.test.js.map +1 -1
  133. package/features/folders/getFolderLevelPermission/decorators/GetFolderLevelPermissionWithFlpDecorator.d.ts +3 -3
  134. package/features/folders/getFolderLevelPermission/decorators/GetFolderLevelPermissionWithFlpDecorator.js +5 -5
  135. package/features/folders/getFolderLevelPermission/decorators/GetFolderLevelPermissionWithFlpDecorator.js.map +1 -1
  136. package/features/folders/listFolders/ListFoldersGqlGateway.d.ts +1 -1
  137. package/features/folders/listFolders/ListFoldersGqlGateway.js +1 -2
  138. package/features/folders/listFolders/ListFoldersGqlGateway.js.map +1 -1
  139. package/features/folders/listFolders/useListFolders.d.ts +1 -1
  140. package/features/folders/listFoldersByParentIds/ListFoldersByParentIdsGqlGateway.d.ts +3 -3
  141. package/features/folders/listFoldersByParentIds/ListFoldersByParentIdsGqlGateway.js +5 -7
  142. package/features/folders/listFoldersByParentIds/ListFoldersByParentIdsGqlGateway.js.map +1 -1
  143. package/features/folders/listFoldersByParentIds/useListFoldersByParentIds.d.ts +2 -2
  144. package/features/folders/loadFolderHierarchy/useLoadFolderHierarchy.d.ts +2 -2
  145. package/features/folders/updateFolder/UpdateFolderGqlGateway.d.ts +3 -3
  146. package/features/folders/updateFolder/UpdateFolderGqlGateway.js +6 -7
  147. package/features/folders/updateFolder/UpdateFolderGqlGateway.js.map +1 -1
  148. package/features/folders/updateFolder/decorators/RepositoryWithPermissionsChange.d.ts +1 -4
  149. package/features/folders/updateFolder/decorators/RepositoryWithPermissionsChange.js +3 -30
  150. package/features/folders/updateFolder/decorators/RepositoryWithPermissionsChange.js.map +1 -1
  151. package/features/folders/updateFolder/decorators/{UseCaseWithoutInheritedPermissions.d.ts → UseCaseWithoutReadOnlyPermissions.d.ts} +2 -2
  152. package/features/folders/updateFolder/decorators/{UseCaseWithoutInheritedPermissions.js → UseCaseWithoutReadOnlyPermissions.js} +6 -6
  153. package/features/folders/updateFolder/decorators/UseCaseWithoutReadOnlyPermissions.js.map +1 -0
  154. package/features/folders/updateFolder/feature.js +2 -2
  155. package/features/folders/updateFolder/feature.js.map +1 -1
  156. package/hooks/index.d.ts +0 -6
  157. package/hooks/index.js +0 -6
  158. package/index.d.ts +3 -2
  159. package/index.js +2 -2
  160. package/package.json +23 -29
  161. package/presentation/folderTree/FolderTree.js +13 -5
  162. package/presentation/folderTree/FolderTree.js.map +1 -1
  163. package/presentation/folderTree/FolderTreePresenter.d.ts +5 -1
  164. package/presentation/folderTree/FolderTreePresenter.js +49 -31
  165. package/presentation/folderTree/FolderTreePresenter.js.map +1 -1
  166. package/presentation/folderTree/FolderTreePresenter.test.js +111 -7
  167. package/presentation/folderTree/FolderTreePresenter.test.js.map +1 -1
  168. package/table.types.d.ts +2 -9
  169. package/types.d.ts +5 -26
  170. package/types.js.map +1 -1
  171. package/components/Extensions/Extensions.d.ts +0 -2
  172. package/components/Extensions/Extensions.js +0 -30
  173. package/components/Extensions/Extensions.js.map +0 -1
  174. package/components/Extensions/index.d.ts +0 -1
  175. package/components/Extensions/index.js +0 -1
  176. package/config/folder/createFolderFieldDecoratorFactory.d.ts +0 -13
  177. package/config/folder/createFolderFieldDecoratorFactory.js +0 -17
  178. package/config/folder/createFolderFieldDecoratorFactory.js.map +0 -1
  179. package/config/table/Sorting.js.map +0 -1
  180. package/contexts/acoList.d.ts +0 -49
  181. package/contexts/acoList.js +0 -303
  182. package/contexts/acoList.js.map +0 -1
  183. package/contexts/app.d.ts +0 -33
  184. package/contexts/app.js +0 -47
  185. package/contexts/app.js.map +0 -1
  186. package/contexts/folders.d.ts +0 -12
  187. package/contexts/folders.js +0 -32
  188. package/contexts/folders.js.map +0 -1
  189. package/contexts/navigateFolder.d.ts +0 -17
  190. package/contexts/navigateFolder.js +0 -54
  191. package/contexts/navigateFolder.js.map +0 -1
  192. package/contexts/records.d.ts +0 -31
  193. package/contexts/records.js +0 -316
  194. package/contexts/records.js.map +0 -1
  195. package/exports/admin/aco.d.ts +0 -2
  196. package/exports/admin/aco.js +0 -2
  197. package/features/folders/updateFolder/decorators/UseCaseWithoutInheritedPermissions.js.map +0 -1
  198. package/graphql/app.gql.d.ts +0 -17
  199. package/graphql/app.gql.js +0 -22
  200. package/graphql/app.gql.js.map +0 -1
  201. package/graphql/records/common.d.ts +0 -4
  202. package/graphql/records/common.js +0 -57
  203. package/graphql/records/common.js.map +0 -1
  204. package/graphql/records/getRecord.d.ts +0 -2
  205. package/graphql/records/getRecord.js +0 -5
  206. package/graphql/records/getRecord.js.map +0 -1
  207. package/graphql/records/listRecords.d.ts +0 -2
  208. package/graphql/records/listRecords.js +0 -26
  209. package/graphql/records/listRecords.js.map +0 -1
  210. package/graphql/records/moveRecord.d.ts +0 -2
  211. package/graphql/records/moveRecord.js +0 -16
  212. package/graphql/records/moveRecord.js.map +0 -1
  213. package/graphql/records/types.d.ts +0 -108
  214. package/graphql/records/types.js +0 -0
  215. package/graphql/records.gql.d.ts +0 -3
  216. package/graphql/records.gql.js +0 -3
  217. package/handlers.d.ts +0 -23
  218. package/handlers.js +0 -22
  219. package/handlers.js.map +0 -1
  220. package/hooks/useAcoApp.d.ts +0 -2
  221. package/hooks/useAcoApp.js +0 -16
  222. package/hooks/useAcoApp.js.map +0 -1
  223. package/hooks/useAcoList.d.ts +0 -3
  224. package/hooks/useAcoList.js +0 -10
  225. package/hooks/useAcoList.js.map +0 -1
  226. package/hooks/useFolderModel.d.ts +0 -2
  227. package/hooks/useFolderModel.js +0 -16
  228. package/hooks/useFolderModel.js.map +0 -1
  229. package/hooks/useFoldersType.d.ts +0 -1
  230. package/hooks/useFoldersType.js +0 -12
  231. package/hooks/useFoldersType.js.map +0 -1
  232. package/hooks/useNavigateFolder.d.ts +0 -2
  233. package/hooks/useNavigateFolder.js +0 -10
  234. package/hooks/useNavigateFolder.js.map +0 -1
  235. package/hooks/useRecords.d.ts +0 -62
  236. package/hooks/useRecords.js +0 -64
  237. package/hooks/useRecords.js.map +0 -1
  238. package/hooks/useTags.d.ts +0 -14
  239. package/hooks/useTags.js +0 -27
  240. package/hooks/useTags.js.map +0 -1
  241. package/sorting.d.ts +0 -9
  242. package/sorting.js +0 -31
  243. package/sorting.js.map +0 -1
@@ -1,5 +1,5 @@
1
1
  import { makeAutoObservable, reaction, runInAction } from "mobx";
2
- import slugify from "slugify";
2
+ import { StringFormatter } from "@webiny/app-admin/features/stringFormatter/abstractions.js";
3
3
  import { FolderTreePresenter } from "./abstractions.js";
4
4
  import { ROOT_FOLDER } from "../../constants.js";
5
5
  import { FoldersCache, FoldersContext } from "../../features/folders/abstractions.js";
@@ -13,7 +13,7 @@ import { GetFolderLevelPermissionUseCase } from "../../features/folders/getFolde
13
13
  import { FormModelFactory } from "@webiny/app-admin/features/formModel/abstractions.js";
14
14
  import { FolderDtoMapper } from "../../domain/folder/FolderDtoMapper.js";
15
15
  class FolderTreePresenterImpl {
16
- constructor(foldersContext, foldersCache, listFoldersUseCase, listFoldersByParentIdsUseCase, createFolderUseCase, updateFolderUseCase, deleteFolderUseCase, getFolderAncestorsUseCase, getFolderLevelPermissionUseCase, formModelFactory){
16
+ constructor(foldersContext, foldersCache, listFoldersUseCase, listFoldersByParentIdsUseCase, createFolderUseCase, updateFolderUseCase, deleteFolderUseCase, getFolderAncestorsUseCase, getFolderLevelPermissionUseCase, formModelFactory, stringFormatter){
17
17
  this.foldersContext = foldersContext;
18
18
  this.foldersCache = foldersCache;
19
19
  this.listFoldersUseCase = listFoldersUseCase;
@@ -24,6 +24,7 @@ class FolderTreePresenterImpl {
24
24
  this.getFolderAncestorsUseCase = getFolderAncestorsUseCase;
25
25
  this.getFolderLevelPermissionUseCase = getFolderLevelPermissionUseCase;
26
26
  this.formModelFactory = formModelFactory;
27
+ this.stringFormatter = stringFormatter;
27
28
  this.currentFolderId = null;
28
29
  this.loading = false;
29
30
  this._loadingNodeIds = [];
@@ -33,14 +34,32 @@ class FolderTreePresenterImpl {
33
34
  };
34
35
  this.callbacks = new Set();
35
36
  this.disposed = false;
37
+ this.previousFolders = [];
36
38
  makeAutoObservable(this, {
37
- callbacks: false
39
+ callbacks: false,
40
+ previousFolders: false
38
41
  }, {
39
42
  autoBind: true
40
43
  });
41
44
  reaction(()=>this.currentFolderId, (folderId)=>{
42
45
  if (!this.disposed) this.callbacks.forEach((cb)=>cb(folderId));
43
46
  });
47
+ reaction(()=>({
48
+ folders: this.foldersCache.getItems(),
49
+ currentFolderId: this.currentFolderId
50
+ }), ({ folders, currentFolderId })=>{
51
+ if (null !== currentFolderId) {
52
+ const stillExists = folders.some((f)=>f.id === currentFolderId);
53
+ const isTransientReloadClear = 0 === folders.length && this.loading;
54
+ if (!stillExists && !isTransientReloadClear) {
55
+ const resolvedId = this.resolveValidFolderId(currentFolderId, this.previousFolders);
56
+ this.selectFolder(resolvedId);
57
+ }
58
+ }
59
+ this.previousFolders = folders;
60
+ }, {
61
+ fireImmediately: true
62
+ });
44
63
  this.loadFolders();
45
64
  }
46
65
  get vm() {
@@ -66,9 +85,10 @@ class FolderTreePresenterImpl {
66
85
  };
67
86
  }
68
87
  selectFolder(folderId) {
69
- this.currentFolderId = folderId;
70
- if (folderId) this.loadChildFolders([
71
- folderId
88
+ const normalized = folderId === ROOT_FOLDER ? null : folderId;
89
+ this.currentFolderId = normalized;
90
+ if (normalized) this.loadChildFolders([
91
+ normalized
72
92
  ]);
73
93
  }
74
94
  onFolderChange(callback) {
@@ -114,15 +134,10 @@ class FolderTreePresenterImpl {
114
134
  createFolder(parentFolderId) {
115
135
  const form = this.formModelFactory.create({
116
136
  fields: (fields)=>({
117
- title: fields.text().label("Title").required("Title is required"),
118
- slug: fields.text().label("Slug").required("Slug is required").computedUntilDirty((f)=>{
119
- const title = f.field("title").getValue();
120
- return slugify(String(title ?? ""), {
121
- replacement: "-",
122
- lower: true,
123
- remove: /[*#\?<>_\{\}\[\]+~.()'"!:;@]/g,
124
- trim: false
125
- });
137
+ title: fields.text().label("Title").required("Title is required").autoFocus(),
138
+ slug: fields.text().label("Slug").required("Slug is required").computedUntilDirty(({ form })=>{
139
+ const title = form.field("title").getValue();
140
+ return this.stringFormatter.slugify(String(title ?? ""));
126
141
  }),
127
142
  parentId: fields.text().label("Parent folder").renderer("folderTree")
128
143
  }),
@@ -148,15 +163,10 @@ class FolderTreePresenterImpl {
148
163
  if (!folder) return;
149
164
  const form = this.formModelFactory.create({
150
165
  fields: (fields)=>({
151
- title: fields.text().label("Name").required("Name is required"),
152
- slug: fields.text().label("Slug").required("Slug is required").computedUntilDirty((f)=>{
153
- const title = f.field("title").getValue();
154
- return slugify(String(title ?? ""), {
155
- replacement: "-",
156
- lower: true,
157
- remove: /[*#\?<>_\{\}\[\]+~.()'"!:;@]/g,
158
- trim: false
159
- });
166
+ title: fields.text().label("Name").required("Name is required").autoFocus(),
167
+ slug: fields.text().label("Slug").required("Slug is required").computedUntilDirty(({ form })=>{
168
+ const title = form.field("title").getValue();
169
+ return this.stringFormatter.slugify(String(title ?? ""));
160
170
  })
161
171
  }),
162
172
  layout: (layout)=>[
@@ -199,7 +209,6 @@ class FolderTreePresenterImpl {
199
209
  permissions: folder?.permissions ?? []
200
210
  });
201
211
  }
202
- await this.loadFolders();
203
212
  runInAction(()=>{
204
213
  this.operation = {
205
214
  active: false,
@@ -209,22 +218,18 @@ class FolderTreePresenterImpl {
209
218
  return true;
210
219
  }
211
220
  async deleteFolder(folderId) {
212
- const folders = this.foldersCache.getItems();
213
- const folder = folders.find((f)=>f.id === folderId);
214
- const parentId = folder?.parentId ?? null;
215
221
  this.operation = {
216
222
  active: true,
217
223
  mode: "delete",
218
224
  folderId
219
225
  };
220
226
  await this.deleteFolderUseCase.execute(folderId);
221
- await this.loadFolders();
222
227
  runInAction(()=>{
228
+ this.foldersCache.removeItems((f)=>f.id === folderId);
223
229
  this.operation = {
224
230
  active: false,
225
231
  mode: null
226
232
  };
227
- this.currentFolderId = parentId;
228
233
  });
229
234
  }
230
235
  cancelOperation() {
@@ -245,6 +250,18 @@ class FolderTreePresenterImpl {
245
250
  });
246
251
  }
247
252
  }
253
+ resolveValidFolderId(folderId, previousFolders) {
254
+ let candidateId = folderId;
255
+ const visited = new Set();
256
+ while(null !== candidateId){
257
+ if (visited.has(candidateId)) break;
258
+ visited.add(candidateId);
259
+ if (this.foldersCache.getItem((f)=>f.id === candidateId)) return candidateId;
260
+ const prev = previousFolders.find((f)=>f.id === candidateId);
261
+ candidateId = prev?.parentId ?? null;
262
+ }
263
+ return null;
264
+ }
248
265
  buildTree() {
249
266
  const folders = this.foldersCache.getItems();
250
267
  return this.buildTreeFromFolders(folders);
@@ -295,7 +312,8 @@ const FolderTreePresenter_FolderTreePresenter = FolderTreePresenter.createImplem
295
312
  DeleteFolderUseCase,
296
313
  GetFolderAncestorsUseCase,
297
314
  GetFolderLevelPermissionUseCase,
298
- FormModelFactory
315
+ FormModelFactory,
316
+ StringFormatter
299
317
  ]
300
318
  });
301
319
  export { FolderTreePresenter_FolderTreePresenter as FolderTreePresenter };
@@ -1 +1 @@
1
- {"version":3,"file":"presentation/folderTree/FolderTreePresenter.js","sources":["../../../src/presentation/folderTree/FolderTreePresenter.ts"],"sourcesContent":["import { makeAutoObservable, reaction, runInAction } from \"mobx\";\nimport slugify from \"slugify\";\nimport {\n FolderTreePresenter as Abstraction,\n type IFolderTreeNode,\n type IFolderTreeViewModel,\n type IFolderOperationState\n} from \"./abstractions.js\";\nimport { ROOT_FOLDER } from \"~/constants.js\";\nimport { FoldersCache, FoldersContext } from \"~/features/folders/abstractions.js\";\nimport { ListFoldersUseCase } from \"~/features/folders/listFolders/abstractions.js\";\nimport { ListFoldersByParentIdsUseCase } from \"~/features/folders/listFoldersByParentIds/abstractions.js\";\nimport { CreateFolderUseCase } from \"~/features/folders/createFolder/abstractions.js\";\nimport { UpdateFolderUseCase } from \"~/features/folders/updateFolder/abstractions.js\";\nimport { DeleteFolderUseCase } from \"~/features/folders/deleteFolder/abstractions.js\";\nimport { GetFolderAncestorsUseCase } from \"~/features/folders/getFolderAncestors/abstractions.js\";\nimport { GetFolderLevelPermissionUseCase } from \"~/features/folders/getFolderLevelPermission/abstractions.js\";\nimport { FormModelFactory } from \"@webiny/app-admin/features/formModel/abstractions.js\";\nimport type { Folder } from \"~/domain/folder/Folder.js\";\nimport type { FolderDto } from \"~/domain/folder/FolderDto.js\";\nimport { FolderDtoMapper } from \"~/domain/folder/FolderDtoMapper.js\";\n\ntype FolderChangeCallback = (folderId: string | null) => void;\n\nclass FolderTreePresenterImpl implements Abstraction.Interface {\n private currentFolderId: string | null = null;\n private loading = false;\n private _loadingNodeIds: string[] = [];\n private operation: IFolderOperationState = { active: false, mode: null };\n private callbacks: Set<FolderChangeCallback> = new Set();\n private disposed = false;\n\n constructor(\n private foldersContext: FoldersContext.Interface,\n private foldersCache: FoldersCache.Interface,\n private listFoldersUseCase: ListFoldersUseCase.Interface,\n private listFoldersByParentIdsUseCase: ListFoldersByParentIdsUseCase.Interface,\n private createFolderUseCase: CreateFolderUseCase.Interface,\n private updateFolderUseCase: UpdateFolderUseCase.Interface,\n private deleteFolderUseCase: DeleteFolderUseCase.Interface,\n private getFolderAncestorsUseCase: GetFolderAncestorsUseCase.Interface,\n private getFolderLevelPermissionUseCase: GetFolderLevelPermissionUseCase.Interface,\n private formModelFactory: FormModelFactory.Interface\n ) {\n makeAutoObservable<FolderTreePresenterImpl, \"callbacks\">(\n this,\n { callbacks: false },\n { autoBind: true }\n );\n\n reaction(\n () => this.currentFolderId,\n folderId => {\n if (!this.disposed) {\n this.callbacks.forEach(cb => cb(folderId));\n }\n }\n );\n\n void this.loadFolders();\n }\n\n get vm(): IFolderTreeViewModel {\n const tree = this.buildTree();\n const allFolders = this.foldersCache.getItems();\n const currentNode = this.findNode(this.currentFolderId, tree);\n const currentFolder = this.findFolder(this.currentFolderId);\n const childNodes = currentNode\n ? currentNode.children\n : tree.filter(n => n.id !== ROOT_FOLDER);\n return {\n folders: allFolders.map(f => FolderDtoMapper.toDTO(f)),\n tree,\n currentFolderId: this.currentFolderId,\n currentFolder: currentFolder ? FolderDtoMapper.toDTO(currentFolder) : null,\n isRootFolder: this.currentFolderId === null,\n currentFolderTitle: currentNode?.name ?? \"All Files\",\n childFolders: childNodes\n .map(n => {\n const folder = allFolders.find(f => f.id === n.id);\n return folder ? FolderDtoMapper.toDTO(folder) : null;\n })\n .filter((f): f is FolderDto => f !== null),\n loading: this.loading,\n loadingNodeIds: this._loadingNodeIds,\n operation: this.operation\n };\n }\n\n selectFolder(folderId: string | null): void {\n this.currentFolderId = folderId;\n if (folderId) {\n void this.loadChildFolders([folderId]);\n }\n }\n\n onFolderChange(callback: FolderChangeCallback): () => void {\n this.callbacks.add(callback);\n return () => {\n this.callbacks.delete(callback);\n };\n }\n\n async loadChildFolders(parentIds: string[]): Promise<void> {\n const fetchableIds = parentIds.filter(id => id !== ROOT_FOLDER && id !== \"0\");\n if (fetchableIds.length === 0) {\n return;\n }\n\n runInAction(() => {\n this._loadingNodeIds = fetchableIds;\n });\n\n try {\n await this.listFoldersByParentIdsUseCase.execute(fetchableIds);\n } finally {\n runInAction(() => {\n this._loadingNodeIds = [];\n });\n }\n }\n\n async moveFolder(folderId: string, targetParentId: string | null): Promise<void> {\n const folders = this.foldersCache.getItems();\n const folder = folders.find(f => f.id === folderId);\n if (!folder) {\n return;\n }\n\n await this.updateFolderUseCase.execute({\n id: folderId,\n title: folder.title,\n slug: folder.slug,\n type: this.foldersContext.type,\n parentId: targetParentId,\n permissions: folder.permissions\n });\n\n await this.loadFolders();\n }\n\n canManageStructure(folderId: string): boolean {\n return this.getFolderLevelPermissionUseCase.execute(folderId, \"canManageStructure\");\n }\n\n getAncestorIds(folderId: string): string[] {\n return this.getFolderAncestorsUseCase.execute(folderId).map(f => f.id);\n }\n\n createFolder(parentFolderId?: string): void {\n const form = this.formModelFactory.create({\n fields: fields => ({\n title: fields.text().label(\"Title\").required(\"Title is required\"),\n slug: fields\n .text()\n .label(\"Slug\")\n .required(\"Slug is required\")\n .computedUntilDirty(f => {\n const title = f.field(\"title\").getValue();\n return slugify(String(title ?? \"\"), {\n replacement: \"-\",\n lower: true,\n remove: /[*#\\?<>_\\{\\}\\[\\]+~.()'\"!:;@]/g,\n trim: false\n });\n }),\n parentId: fields\n .text()\n .label(\"Parent folder\")\n .renderer(\"folderTree\" as any)\n }),\n layout: layout => [layout.row(\"title\"), layout.row(\"slug\"), layout.row(\"parentId\")]\n });\n\n form.setData({ parentId: parentFolderId ?? null });\n\n this.operation = {\n active: true,\n mode: \"create\",\n parentFolderId,\n form\n };\n }\n\n editFolder(folderId: string): void {\n const folders = this.foldersCache.getItems();\n const folder = folders.find(f => f.id === folderId);\n if (!folder) {\n return;\n }\n\n const form = this.formModelFactory.create({\n fields: fields => ({\n title: fields.text().label(\"Name\").required(\"Name is required\"),\n slug: fields\n .text()\n .label(\"Slug\")\n .required(\"Slug is required\")\n .computedUntilDirty(f => {\n const title = f.field(\"title\").getValue();\n return slugify(String(title ?? \"\"), {\n replacement: \"-\",\n lower: true,\n remove: /[*#\\?<>_\\{\\}\\[\\]+~.()'\"!:;@]/g,\n trim: false\n });\n })\n }),\n layout: layout => [layout.row(\"title\"), layout.row(\"slug\")]\n });\n\n form.setData({ title: folder.title, slug: folder.slug });\n\n this.operation = {\n active: true,\n mode: \"edit\",\n folderId,\n form\n };\n }\n\n async submitOperation(): Promise<boolean> {\n const { mode, form, parentFolderId, folderId } = this.operation;\n if (!form || !mode) {\n return false;\n }\n\n const data = await form.submit<{ title: string; slug: string; parentId?: string | null }>();\n if (!data) {\n return false;\n }\n\n if (mode === \"create\") {\n await this.createFolderUseCase.execute({\n title: data.title,\n slug: data.slug,\n type: this.foldersContext.type,\n parentId: data.parentId ?? parentFolderId ?? null,\n permissions: []\n });\n } else if (mode === \"edit\" && folderId) {\n const folders = this.foldersCache.getItems();\n const folder = folders.find(f => f.id === folderId);\n\n await this.updateFolderUseCase.execute({\n id: folderId,\n title: data.title,\n slug: data.slug,\n type: this.foldersContext.type,\n parentId: folder?.parentId ?? null,\n permissions: folder?.permissions ?? []\n });\n }\n\n await this.loadFolders();\n\n runInAction(() => {\n this.operation = { active: false, mode: null };\n });\n\n return true;\n }\n\n async deleteFolder(folderId: string): Promise<void> {\n const folders = this.foldersCache.getItems();\n const folder = folders.find(f => f.id === folderId);\n const parentId = folder?.parentId ?? null;\n\n this.operation = {\n active: true,\n mode: \"delete\",\n folderId\n };\n\n await this.deleteFolderUseCase.execute(folderId);\n await this.loadFolders();\n\n runInAction(() => {\n this.operation = { active: false, mode: null };\n this.currentFolderId = parentId;\n });\n }\n\n cancelOperation(): void {\n this.operation = { active: false, mode: null };\n }\n\n private async loadFolders(): Promise<void> {\n runInAction(() => {\n this.loading = true;\n });\n\n try {\n await this.listFoldersUseCase.execute();\n } finally {\n runInAction(() => {\n this.loading = false;\n });\n }\n }\n\n private buildTree(): IFolderTreeNode[] {\n const folders = this.foldersCache.getItems();\n return this.buildTreeFromFolders(folders);\n }\n\n private buildTreeFromFolders(folders: Folder[]): IFolderTreeNode[] {\n const nodeMap = new Map<string, IFolderTreeNode>();\n\n for (const folder of folders) {\n nodeMap.set(folder.id, {\n id: folder.id,\n name: folder.title,\n slug: folder.slug,\n parentId: folder.parentId,\n children: [],\n hasNonInheritedPermissions: folder.hasNonInheritedPermissions ?? false,\n canManagePermissions: folder.canManagePermissions ?? false,\n canManageStructure: folder.canManageStructure ?? true\n });\n }\n\n const roots: IFolderTreeNode[] = [];\n for (const folder of folders) {\n const node = nodeMap.get(folder.id)!;\n if (folder.parentId && nodeMap.has(folder.parentId)) {\n nodeMap.get(folder.parentId)!.children.push(node);\n } else {\n roots.push(node);\n }\n }\n\n return roots;\n }\n\n private findFolder(folderId: string | null): Folder | null {\n if (!folderId) {\n return null;\n }\n\n return this.foldersCache.getItem(f => f.id === folderId) ?? null;\n }\n\n private findNode(folderId: string | null, nodes: IFolderTreeNode[]): IFolderTreeNode | null {\n if (folderId === null) {\n return null;\n }\n\n for (const node of nodes) {\n if (node.id === folderId) {\n return node;\n }\n const found = this.findNode(folderId, node.children);\n if (found) {\n return found;\n }\n }\n\n return null;\n }\n}\n\nexport const FolderTreePresenter = Abstraction.createImplementation({\n implementation: FolderTreePresenterImpl,\n dependencies: [\n FoldersContext,\n FoldersCache,\n ListFoldersUseCase,\n ListFoldersByParentIdsUseCase,\n CreateFolderUseCase,\n UpdateFolderUseCase,\n DeleteFolderUseCase,\n GetFolderAncestorsUseCase,\n GetFolderLevelPermissionUseCase,\n FormModelFactory\n ]\n});\n"],"names":["FolderTreePresenterImpl","foldersContext","foldersCache","listFoldersUseCase","listFoldersByParentIdsUseCase","createFolderUseCase","updateFolderUseCase","deleteFolderUseCase","getFolderAncestorsUseCase","getFolderLevelPermissionUseCase","formModelFactory","Set","makeAutoObservable","reaction","folderId","cb","tree","allFolders","currentNode","currentFolder","childNodes","n","ROOT_FOLDER","f","FolderDtoMapper","folder","callback","parentIds","fetchableIds","id","runInAction","targetParentId","folders","parentFolderId","form","fields","title","slugify","String","layout","mode","data","parentId","nodeMap","Map","roots","node","nodes","found","FolderTreePresenter","Abstraction","FoldersContext","FoldersCache","ListFoldersUseCase","ListFoldersByParentIdsUseCase","CreateFolderUseCase","UpdateFolderUseCase","DeleteFolderUseCase","GetFolderAncestorsUseCase","GetFolderLevelPermissionUseCase","FormModelFactory"],"mappings":"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"}
1
+ {"version":3,"file":"presentation/folderTree/FolderTreePresenter.js","sources":["../../../src/presentation/folderTree/FolderTreePresenter.ts"],"sourcesContent":["import { makeAutoObservable, reaction, runInAction } from \"mobx\";\nimport { StringFormatter } from \"@webiny/app-admin/features/stringFormatter/abstractions.js\";\nimport {\n FolderTreePresenter as Abstraction,\n type IFolderTreeNode,\n type IFolderTreeViewModel,\n type IFolderOperationState\n} from \"./abstractions.js\";\nimport { ROOT_FOLDER } from \"~/constants.js\";\nimport { FoldersCache, FoldersContext } from \"~/features/folders/abstractions.js\";\nimport { ListFoldersUseCase } from \"~/features/folders/listFolders/abstractions.js\";\nimport { ListFoldersByParentIdsUseCase } from \"~/features/folders/listFoldersByParentIds/abstractions.js\";\nimport { CreateFolderUseCase } from \"~/features/folders/createFolder/abstractions.js\";\nimport { UpdateFolderUseCase } from \"~/features/folders/updateFolder/abstractions.js\";\nimport { DeleteFolderUseCase } from \"~/features/folders/deleteFolder/abstractions.js\";\nimport { GetFolderAncestorsUseCase } from \"~/features/folders/getFolderAncestors/abstractions.js\";\nimport { GetFolderLevelPermissionUseCase } from \"~/features/folders/getFolderLevelPermission/abstractions.js\";\nimport { FormModelFactory } from \"@webiny/app-admin/features/formModel/abstractions.js\";\nimport type { Folder } from \"~/domain/folder/Folder.js\";\nimport type { FolderDto } from \"~/domain/folder/FolderDto.js\";\nimport { FolderDtoMapper } from \"~/domain/folder/FolderDtoMapper.js\";\n\ntype FolderChangeCallback = (folderId: string | null) => void;\n\nclass FolderTreePresenterImpl implements Abstraction.Interface {\n private currentFolderId: string | null = null;\n private loading = false;\n private _loadingNodeIds: string[] = [];\n private operation: IFolderOperationState = { active: false, mode: null };\n private callbacks: Set<FolderChangeCallback> = new Set();\n private disposed = false;\n\n private previousFolders: Folder[] = [];\n\n constructor(\n private foldersContext: FoldersContext.Interface,\n private foldersCache: FoldersCache.Interface,\n private listFoldersUseCase: ListFoldersUseCase.Interface,\n private listFoldersByParentIdsUseCase: ListFoldersByParentIdsUseCase.Interface,\n private createFolderUseCase: CreateFolderUseCase.Interface,\n private updateFolderUseCase: UpdateFolderUseCase.Interface,\n private deleteFolderUseCase: DeleteFolderUseCase.Interface,\n private getFolderAncestorsUseCase: GetFolderAncestorsUseCase.Interface,\n private getFolderLevelPermissionUseCase: GetFolderLevelPermissionUseCase.Interface,\n private formModelFactory: FormModelFactory.Interface,\n private stringFormatter: StringFormatter.Interface\n ) {\n makeAutoObservable<FolderTreePresenterImpl, \"callbacks\" | \"previousFolders\">(\n this,\n { callbacks: false, previousFolders: false },\n { autoBind: true }\n );\n\n reaction(\n () => this.currentFolderId,\n folderId => {\n if (!this.disposed) {\n this.callbacks.forEach(cb => cb(folderId));\n }\n }\n );\n\n reaction(\n () => ({\n folders: this.foldersCache.getItems(),\n currentFolderId: this.currentFolderId\n }),\n ({ folders, currentFolderId }) => {\n if (currentFolderId !== null) {\n const stillExists = folders.some(f => f.id === currentFolderId);\n // Skip when folders is empty *and* a reload is in flight — the cache is\n // cleared before being repopulated, so this is a transient state, not a\n // real \"all folders were removed\" event.\n const isTransientReloadClear = folders.length === 0 && this.loading;\n if (!stillExists && !isTransientReloadClear) {\n const resolvedId = this.resolveValidFolderId(\n currentFolderId,\n this.previousFolders\n );\n this.selectFolder(resolvedId);\n }\n }\n\n this.previousFolders = folders;\n },\n { fireImmediately: true }\n );\n\n void this.loadFolders();\n }\n\n get vm(): IFolderTreeViewModel {\n const tree = this.buildTree();\n const allFolders = this.foldersCache.getItems();\n const currentNode = this.findNode(this.currentFolderId, tree);\n const currentFolder = this.findFolder(this.currentFolderId);\n const childNodes = currentNode\n ? currentNode.children\n : tree.filter(n => n.id !== ROOT_FOLDER);\n return {\n folders: allFolders.map(f => FolderDtoMapper.toDTO(f)),\n tree,\n currentFolderId: this.currentFolderId,\n currentFolder: currentFolder ? FolderDtoMapper.toDTO(currentFolder) : null,\n isRootFolder: this.currentFolderId === null,\n currentFolderTitle: currentNode?.name ?? \"All Files\",\n childFolders: childNodes\n .map(n => {\n const folder = allFolders.find(f => f.id === n.id);\n return folder ? FolderDtoMapper.toDTO(folder) : null;\n })\n .filter((f): f is FolderDto => f !== null),\n loading: this.loading,\n loadingNodeIds: this._loadingNodeIds,\n operation: this.operation\n };\n }\n\n selectFolder(folderId: string | null): void {\n const normalized = folderId === ROOT_FOLDER ? null : folderId;\n this.currentFolderId = normalized;\n if (normalized) {\n void this.loadChildFolders([normalized]);\n }\n }\n\n onFolderChange(callback: FolderChangeCallback): () => void {\n this.callbacks.add(callback);\n return () => {\n this.callbacks.delete(callback);\n };\n }\n\n async loadChildFolders(parentIds: string[]): Promise<void> {\n const fetchableIds = parentIds.filter(id => id !== ROOT_FOLDER && id !== \"0\");\n if (fetchableIds.length === 0) {\n return;\n }\n\n runInAction(() => {\n this._loadingNodeIds = fetchableIds;\n });\n\n try {\n await this.listFoldersByParentIdsUseCase.execute(fetchableIds);\n } finally {\n runInAction(() => {\n this._loadingNodeIds = [];\n });\n }\n }\n\n async moveFolder(folderId: string, targetParentId: string | null): Promise<void> {\n const folders = this.foldersCache.getItems();\n const folder = folders.find(f => f.id === folderId);\n if (!folder) {\n return;\n }\n\n await this.updateFolderUseCase.execute({\n id: folderId,\n title: folder.title,\n slug: folder.slug,\n type: this.foldersContext.type,\n parentId: targetParentId,\n permissions: folder.permissions\n });\n\n await this.loadFolders();\n }\n\n canManageStructure(folderId: string): boolean {\n return this.getFolderLevelPermissionUseCase.execute(folderId, \"canManageStructure\");\n }\n\n getAncestorIds(folderId: string): string[] {\n return this.getFolderAncestorsUseCase.execute(folderId).map(f => f.id);\n }\n\n createFolder(parentFolderId?: string): void {\n const form = this.formModelFactory.create({\n fields: fields => ({\n title: fields.text().label(\"Title\").required(\"Title is required\").autoFocus(),\n slug: fields\n .text()\n .label(\"Slug\")\n .required(\"Slug is required\")\n .computedUntilDirty(({ form }) => {\n const title = form.field(\"title\").getValue();\n return this.stringFormatter.slugify(String(title ?? \"\"));\n }),\n parentId: fields\n .text()\n .label(\"Parent folder\")\n .renderer(\"folderTree\" as any)\n }),\n layout: layout => [layout.row(\"title\"), layout.row(\"slug\"), layout.row(\"parentId\")]\n });\n\n form.setData({ parentId: parentFolderId ?? null });\n\n this.operation = {\n active: true,\n mode: \"create\",\n parentFolderId,\n form\n };\n }\n\n editFolder(folderId: string): void {\n const folders = this.foldersCache.getItems();\n const folder = folders.find(f => f.id === folderId);\n if (!folder) {\n return;\n }\n\n const form = this.formModelFactory.create({\n fields: fields => ({\n title: fields.text().label(\"Name\").required(\"Name is required\").autoFocus(),\n slug: fields\n .text()\n .label(\"Slug\")\n .required(\"Slug is required\")\n .computedUntilDirty(({ form }) => {\n const title = form.field(\"title\").getValue();\n return this.stringFormatter.slugify(String(title ?? \"\"));\n })\n }),\n layout: layout => [layout.row(\"title\"), layout.row(\"slug\")]\n });\n\n form.setData({ title: folder.title, slug: folder.slug });\n\n this.operation = {\n active: true,\n mode: \"edit\",\n folderId,\n form\n };\n }\n\n async submitOperation(): Promise<boolean> {\n const { mode, form, parentFolderId, folderId } = this.operation;\n if (!form || !mode) {\n return false;\n }\n\n const data = await form.submit<{ title: string; slug: string; parentId?: string | null }>();\n if (!data) {\n return false;\n }\n\n if (mode === \"create\") {\n await this.createFolderUseCase.execute({\n title: data.title,\n slug: data.slug,\n type: this.foldersContext.type,\n parentId: data.parentId ?? parentFolderId ?? null,\n permissions: []\n });\n } else if (mode === \"edit\" && folderId) {\n const folders = this.foldersCache.getItems();\n const folder = folders.find(f => f.id === folderId);\n\n await this.updateFolderUseCase.execute({\n id: folderId,\n title: data.title,\n slug: data.slug,\n type: this.foldersContext.type,\n parentId: folder?.parentId ?? null,\n permissions: folder?.permissions ?? []\n });\n }\n\n runInAction(() => {\n this.operation = { active: false, mode: null };\n });\n\n return true;\n }\n\n async deleteFolder(folderId: string): Promise<void> {\n this.operation = {\n active: true,\n mode: \"delete\",\n folderId\n };\n\n await this.deleteFolderUseCase.execute(folderId);\n\n runInAction(() => {\n this.foldersCache.removeItems(f => f.id === folderId);\n this.operation = { active: false, mode: null };\n });\n }\n\n cancelOperation(): void {\n this.operation = { active: false, mode: null };\n }\n\n private async loadFolders(): Promise<void> {\n runInAction(() => {\n this.loading = true;\n });\n\n try {\n await this.listFoldersUseCase.execute();\n } finally {\n runInAction(() => {\n this.loading = false;\n });\n }\n }\n\n private resolveValidFolderId(\n folderId: string | null,\n previousFolders: Folder[]\n ): string | null {\n let candidateId = folderId;\n const visited = new Set<string>();\n\n while (candidateId !== null) {\n if (visited.has(candidateId)) {\n return null;\n }\n visited.add(candidateId);\n\n if (this.foldersCache.getItem(f => f.id === candidateId)) {\n return candidateId;\n }\n\n const prev = previousFolders.find(f => f.id === candidateId);\n candidateId = prev?.parentId ?? null;\n }\n\n return null;\n }\n\n private buildTree(): IFolderTreeNode[] {\n const folders = this.foldersCache.getItems();\n return this.buildTreeFromFolders(folders);\n }\n\n private buildTreeFromFolders(folders: Folder[]): IFolderTreeNode[] {\n const nodeMap = new Map<string, IFolderTreeNode>();\n\n for (const folder of folders) {\n nodeMap.set(folder.id, {\n id: folder.id,\n name: folder.title,\n slug: folder.slug,\n parentId: folder.parentId,\n children: [],\n hasNonInheritedPermissions: folder.hasNonInheritedPermissions ?? false,\n canManagePermissions: folder.canManagePermissions ?? false,\n canManageStructure: folder.canManageStructure ?? true\n });\n }\n\n const roots: IFolderTreeNode[] = [];\n for (const folder of folders) {\n const node = nodeMap.get(folder.id)!;\n if (folder.parentId && nodeMap.has(folder.parentId)) {\n nodeMap.get(folder.parentId)!.children.push(node);\n } else {\n roots.push(node);\n }\n }\n\n return roots;\n }\n\n private findFolder(folderId: string | null): Folder | null {\n if (!folderId) {\n return null;\n }\n\n return this.foldersCache.getItem(f => f.id === folderId) ?? null;\n }\n\n private findNode(folderId: string | null, nodes: IFolderTreeNode[]): IFolderTreeNode | null {\n if (folderId === null) {\n return null;\n }\n\n for (const node of nodes) {\n if (node.id === folderId) {\n return node;\n }\n const found = this.findNode(folderId, node.children);\n if (found) {\n return found;\n }\n }\n\n return null;\n }\n}\n\nexport const FolderTreePresenter = Abstraction.createImplementation({\n implementation: FolderTreePresenterImpl,\n dependencies: [\n FoldersContext,\n FoldersCache,\n ListFoldersUseCase,\n ListFoldersByParentIdsUseCase,\n CreateFolderUseCase,\n UpdateFolderUseCase,\n DeleteFolderUseCase,\n GetFolderAncestorsUseCase,\n GetFolderLevelPermissionUseCase,\n FormModelFactory,\n StringFormatter\n ]\n});\n"],"names":["FolderTreePresenterImpl","foldersContext","foldersCache","listFoldersUseCase","listFoldersByParentIdsUseCase","createFolderUseCase","updateFolderUseCase","deleteFolderUseCase","getFolderAncestorsUseCase","getFolderLevelPermissionUseCase","formModelFactory","stringFormatter","Set","makeAutoObservable","reaction","folderId","cb","folders","currentFolderId","stillExists","f","isTransientReloadClear","resolvedId","tree","allFolders","currentNode","currentFolder","childNodes","n","ROOT_FOLDER","FolderDtoMapper","folder","normalized","callback","parentIds","fetchableIds","id","runInAction","targetParentId","parentFolderId","form","fields","title","String","layout","mode","data","previousFolders","candidateId","visited","prev","nodeMap","Map","roots","node","nodes","found","FolderTreePresenter","Abstraction","FoldersContext","FoldersCache","ListFoldersUseCase","ListFoldersByParentIdsUseCase","CreateFolderUseCase","UpdateFolderUseCase","DeleteFolderUseCase","GetFolderAncestorsUseCase","GetFolderLevelPermissionUseCase","FormModelFactory","StringFormatter"],"mappings":"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"}
@@ -11,6 +11,7 @@ import { ListFoldersByParentIdsUseCase } from "../../features/folders/listFolder
11
11
  import { GetFolderAncestorsUseCase } from "../../features/folders/getFolderAncestors/abstractions.js";
12
12
  import { GetFolderLevelPermissionUseCase } from "../../features/folders/getFolderLevelPermission/abstractions.js";
13
13
  import { FormModelFactory } from "@webiny/app-admin/features/formModel/abstractions.js";
14
+ import { StringFormatter } from "@webiny/app-admin/features/stringFormatter/abstractions.js";
14
15
  import { ListCache } from "../../features/folders/cache/index.js";
15
16
  import { Folder } from "../../domain/folder/Folder.js";
16
17
  const TYPE = "FmFile";
@@ -91,6 +92,7 @@ function createMockFormModel() {
91
92
  }),
92
93
  reset: vi.fn(),
93
94
  validate: vi.fn(async ()=>true),
95
+ setErrors: vi.fn(),
94
96
  submit: vi.fn(async ()=>data),
95
97
  evaluateRules: vi.fn(()=>({
96
98
  visible: true,
@@ -107,6 +109,7 @@ function createMockFormModel() {
107
109
  isDirty: false,
108
110
  isValid: null,
109
111
  errors: [],
112
+ hasErrors: false,
110
113
  submitCount: 0,
111
114
  focusField: vi.fn(),
112
115
  getData: vi.fn(()=>({})),
@@ -114,7 +117,8 @@ function createMockFormModel() {
114
117
  },
115
118
  getFieldBuilders: vi.fn(()=>[]),
116
119
  resolveChildLayout: vi.fn(()=>[]),
117
- registry: {}
120
+ registry: {},
121
+ traverse: vi.fn()
118
122
  };
119
123
  return form;
120
124
  }
@@ -170,6 +174,9 @@ function createTestPresenter(folders = []) {
170
174
  execute: vi.fn().mockReturnValue(true)
171
175
  });
172
176
  container.registerInstance(FormModelFactory, formModelFactory);
177
+ container.registerInstance(StringFormatter, {
178
+ slugify: (value)=>value
179
+ });
173
180
  FolderTreePresenterFeature.register(container);
174
181
  const presenter = container.resolve(FolderTreePresenter);
175
182
  return {
@@ -400,7 +407,7 @@ describe("FolderTreePresenter", ()=>{
400
407
  presenter.createFolder("root-1");
401
408
  await presenter.submitOperation();
402
409
  expect(createFolderUseCase.execute).toHaveBeenCalledTimes(1);
403
- expect(listFolders.execute).toHaveBeenCalledTimes(2);
410
+ expect(listFolders.execute).toHaveBeenCalledTimes(1);
404
411
  });
405
412
  });
406
413
  describe("editFolder", ()=>{
@@ -467,40 +474,137 @@ describe("FolderTreePresenter", ()=>{
467
474
  expect(presenter.vm.operation.active).toBe(false);
468
475
  expect(presenter.vm.operation.mode).toBeNull();
469
476
  });
470
- it("should navigate to parent folder after deletion", async ()=>{
477
+ it("should navigate to parent folder after deleting the currently open folder", async ()=>{
471
478
  const { presenter } = createTestPresenter(createFlatFolders());
472
479
  await vi.waitFor(()=>{
473
480
  expect(presenter.vm.loading).toBe(false);
474
481
  });
482
+ presenter.selectFolder("child-1");
475
483
  await presenter.deleteFolder("child-1");
476
484
  expect(presenter.vm.currentFolderId).toBe("root-1");
477
485
  });
478
- it("should navigate to root (null) when deleting a root folder", async ()=>{
486
+ it("should navigate to root (null) when deleting the currently open root folder", async ()=>{
479
487
  const { presenter } = createTestPresenter(createFlatFolders());
480
488
  await vi.waitFor(()=>{
481
489
  expect(presenter.vm.loading).toBe(false);
482
490
  });
491
+ presenter.selectFolder("root-1");
483
492
  await presenter.deleteFolder("root-1");
484
493
  expect(presenter.vm.currentFolderId).toBeNull();
485
494
  });
486
- it("should refresh the folder list after deletion", async ()=>{
495
+ it("should not change currentFolderId when deleting a folder that isn't open", async ()=>{
496
+ const { presenter } = createTestPresenter(createFlatFolders());
497
+ await vi.waitFor(()=>{
498
+ expect(presenter.vm.loading).toBe(false);
499
+ });
500
+ presenter.selectFolder("root-2");
501
+ await presenter.deleteFolder("child-1");
502
+ expect(presenter.vm.currentFolderId).toBe("root-2");
503
+ });
504
+ it("should remove the deleted folder from the cache without refetching from the API", async ()=>{
487
505
  const { presenter, listFolders } = createTestPresenter(createFlatFolders());
488
506
  await vi.waitFor(()=>{
489
507
  expect(presenter.vm.loading).toBe(false);
490
508
  });
491
509
  await presenter.deleteFolder("child-1");
492
- expect(listFolders.execute).toHaveBeenCalledTimes(2);
510
+ expect(listFolders.execute).toHaveBeenCalledTimes(1);
511
+ expect(presenter.vm.folders.find((f)=>"child-1" === f.id)).toBeUndefined();
493
512
  });
494
- it("should fire onFolderChange callback when navigating to parent after delete", async ()=>{
513
+ it("should fire onFolderChange callback when navigating to parent after deleting the open folder", async ()=>{
495
514
  const { presenter } = createTestPresenter(createFlatFolders());
496
515
  await vi.waitFor(()=>{
497
516
  expect(presenter.vm.loading).toBe(false);
498
517
  });
518
+ presenter.selectFolder("child-1");
499
519
  const callback = vi.fn();
500
520
  presenter.onFolderChange(callback);
501
521
  await presenter.deleteFolder("child-1");
502
522
  expect(callback).toHaveBeenCalledWith("root-1");
503
523
  });
524
+ it("should resolve the label for the parent folder after deleting the currently open folder", async ()=>{
525
+ const { presenter } = createTestPresenter(createFlatFolders());
526
+ await vi.waitFor(()=>{
527
+ expect(presenter.vm.loading).toBe(false);
528
+ });
529
+ presenter.selectFolder("grandchild-1");
530
+ await presenter.deleteFolder("grandchild-1");
531
+ expect(presenter.vm.currentFolderId).toBe("child-1");
532
+ expect(presenter.vm.currentFolderTitle).toBe("Invoices");
533
+ });
534
+ });
535
+ describe("stale currentFolderId validation", ()=>{
536
+ it("should fall back to root when the initial currentFolderId no longer exists", async ()=>{
537
+ const { presenter } = createTestPresenter(createFlatFolders());
538
+ presenter.selectFolder("does-not-exist");
539
+ await vi.waitFor(()=>{
540
+ expect(presenter.vm.loading).toBe(false);
541
+ });
542
+ expect(presenter.vm.currentFolderId).toBeNull();
543
+ });
544
+ it("should fall back to the nearest existing ancestor when a deeply nested folder was deleted in another session", async ()=>{
545
+ const { presenter, foldersCache, listFolders, updateFolderUseCase } = createTestPresenter(createFlatFolders());
546
+ await vi.waitFor(()=>{
547
+ expect(presenter.vm.loading).toBe(false);
548
+ });
549
+ presenter.selectFolder("grandchild-1");
550
+ expect(presenter.vm.currentFolderId).toBe("grandchild-1");
551
+ listFolders.execute.mockImplementationOnce(async ()=>{
552
+ foldersCache.removeItems((f)=>"grandchild-1" === f.id || "child-1" === f.id);
553
+ });
554
+ await presenter.moveFolder("root-2", "root-1");
555
+ expect(updateFolderUseCase.execute).toHaveBeenCalledTimes(1);
556
+ expect(presenter.vm.currentFolderId).toBe("root-1");
557
+ });
558
+ });
559
+ describe("external cache deletion (e.g. useDeleteFolder bypassing presenter)", ()=>{
560
+ it("should redirect to parent when the current folder is removed from cache externally", async ()=>{
561
+ const { presenter, foldersCache } = createTestPresenter(createFlatFolders());
562
+ await vi.waitFor(()=>{
563
+ expect(presenter.vm.loading).toBe(false);
564
+ });
565
+ presenter.selectFolder("child-1");
566
+ expect(presenter.vm.currentFolderId).toBe("child-1");
567
+ foldersCache.removeItems((f)=>"child-1" === f.id);
568
+ expect(presenter.vm.currentFolderId).toBe("root-1");
569
+ });
570
+ it("should fire onFolderChange callback on external cache deletion", async ()=>{
571
+ const { presenter, foldersCache } = createTestPresenter(createFlatFolders());
572
+ await vi.waitFor(()=>{
573
+ expect(presenter.vm.loading).toBe(false);
574
+ });
575
+ presenter.selectFolder("child-1");
576
+ const callback = vi.fn();
577
+ presenter.onFolderChange(callback);
578
+ foldersCache.removeItems((f)=>"child-1" === f.id);
579
+ expect(callback).toHaveBeenCalledWith("root-1");
580
+ });
581
+ it("should redirect to root when a root-level folder is removed externally", async ()=>{
582
+ const { presenter, foldersCache } = createTestPresenter(createFlatFolders());
583
+ await vi.waitFor(()=>{
584
+ expect(presenter.vm.loading).toBe(false);
585
+ });
586
+ presenter.selectFolder("root-1");
587
+ foldersCache.removeItems((f)=>"root-1" === f.id);
588
+ expect(presenter.vm.currentFolderId).toBeNull();
589
+ });
590
+ it("should redirect to root when the last remaining folder is removed externally", async ()=>{
591
+ const singleFolder = [
592
+ createFolder({
593
+ id: "root-1",
594
+ title: "Documents",
595
+ slug: "documents",
596
+ parentId: null
597
+ })
598
+ ];
599
+ const { presenter, foldersCache } = createTestPresenter(singleFolder);
600
+ await vi.waitFor(()=>{
601
+ expect(presenter.vm.loading).toBe(false);
602
+ });
603
+ presenter.selectFolder("root-1");
604
+ expect(presenter.vm.currentFolderId).toBe("root-1");
605
+ foldersCache.removeItems((f)=>"root-1" === f.id);
606
+ expect(presenter.vm.currentFolderId).toBeNull();
607
+ });
504
608
  });
505
609
  describe("operation state", ()=>{
506
610
  it("should start with inactive operation state", async ()=>{