@umbraco-cms/backoffice 17.4.0-rc → 17.4.0-rc2

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 (84) hide show
  1. package/custom-elements.json +1 -1
  2. package/dist-cms/apps/app/app.element.js +120 -7
  3. package/dist-cms/apps/backoffice/backoffice.context.js +1 -9
  4. package/dist-cms/apps/backoffice/backoffice.element.d.ts +0 -2
  5. package/dist-cms/apps/backoffice/backoffice.element.js +0 -58
  6. package/dist-cms/apps/backoffice/components/backoffice-main.element.d.ts +2 -2
  7. package/dist-cms/apps/backoffice/components/backoffice-main.element.js +16 -3
  8. package/dist-cms/libs/extension-api/controller/base-extension-initializer.controller.d.ts +1 -1
  9. package/dist-cms/libs/extension-api/controller/base-extension-initializer.controller.js +61 -19
  10. package/dist-cms/libs/extension-api/controller/extension-api-initializer.controller.d.ts +1 -1
  11. package/dist-cms/libs/extension-api/controller/extension-api-initializer.controller.js +10 -3
  12. package/dist-cms/libs/extension-api/controller/extension-element-and-api-initializer.controller.d.ts +1 -1
  13. package/dist-cms/libs/extension-api/controller/extension-element-and-api-initializer.controller.js +12 -3
  14. package/dist-cms/libs/extension-api/controller/extension-element-initializer.controller.d.ts +1 -1
  15. package/dist-cms/libs/extension-api/controller/extension-element-initializer.controller.js +11 -3
  16. package/dist-cms/libs/extension-api/controller/extension-manifest-initializer.controller.d.ts +1 -1
  17. package/dist-cms/libs/extension-api/controller/extension-manifest-initializer.controller.js +1 -1
  18. package/dist-cms/libs/extension-api/initializers/extension-initializer-base.d.ts +2 -2
  19. package/dist-cms/libs/extension-api/initializers/extension-initializer-base.js +7 -3
  20. package/dist-cms/libs/extension-api/registry/extension.registry.js +22 -2
  21. package/dist-cms/packages/block/block/conditions/block-workspace-is-readonly.condition.js +4 -2
  22. package/dist-cms/packages/block/block/context/block-entry.context.js +1 -4
  23. package/dist-cms/packages/block/block/workspace/block-element-manager.js +2 -2
  24. package/dist-cms/packages/block/block/workspace/block-workspace-editor.element.d.ts +1 -0
  25. package/dist-cms/packages/block/block/workspace/block-workspace-editor.element.js +31 -9
  26. package/dist-cms/packages/block/block/workspace/block-workspace-language-access.controller.d.ts +16 -0
  27. package/dist-cms/packages/block/block/workspace/block-workspace-language-access.controller.js +126 -0
  28. package/dist-cms/packages/block/block/workspace/block-workspace.context.js +23 -19
  29. package/dist-cms/packages/block/block/workspace/manifests.js +3 -0
  30. package/dist-cms/packages/block/block-custom-view/types.d.ts +1 -0
  31. package/dist-cms/packages/block/block-grid/components/block-grid-entry/block-grid-entry.element.js +4 -5
  32. package/dist-cms/packages/block/block-grid/property-editors/block-grid-editor/property-editor-ui-block-grid.element.js +2 -5
  33. package/dist-cms/packages/block/block-list/components/block-list-entry/block-list-entry.element.js +11 -6
  34. package/dist-cms/packages/block/block-rte/components/block-rte-entry/block-rte-entry.element.js +4 -5
  35. package/dist-cms/packages/block/block-single/components/block-single-entry/block-single-entry.element.js +4 -5
  36. package/dist-cms/packages/code-editor/umbraco-package.d.ts +1 -0
  37. package/dist-cms/packages/code-editor/umbraco-package.js +1 -0
  38. package/dist-cms/packages/content/content/global-components/content-workspace-property.element.js +1 -1
  39. package/dist-cms/packages/core/components/body-layout/body-layout.element.js +3 -1
  40. package/dist-cms/packages/core/extension-registry/conditions/condition-base.controller.js +3 -2
  41. package/dist-cms/packages/core/extension-registry/conditions/delay.condition.js +5 -1
  42. package/dist-cms/packages/core/extension-registry/conditions/switch.condition.d.ts +0 -2
  43. package/dist-cms/packages/core/extension-registry/conditions/switch.condition.js +13 -7
  44. package/dist-cms/packages/core/property/property-guard-manager/property-guard.manager.d.ts +1 -1
  45. package/dist-cms/packages/core/property/property-guard-manager/property-guard.manager.js +6 -3
  46. package/dist-cms/packages/core/property/property-guard-manager/variant-property-guard.manager.d.ts +1 -1
  47. package/dist-cms/packages/core/property/property-guard-manager/variant-property-guard.manager.js +7 -3
  48. package/dist-cms/packages/core/recycle-bin/conditions/is-trashed/entity-is-trashed.condition.js +2 -1
  49. package/dist-cms/packages/core/repository/data-mapper/mapping/data-mapping-resolver.js +1 -0
  50. package/dist-cms/packages/core/utils/guard-manager/guard.manager.base.d.ts +4 -2
  51. package/dist-cms/packages/core/utils/guard-manager/guard.manager.base.js +10 -5
  52. package/dist-cms/packages/core/utils/guard-manager/readonly-guard.manager.js +9 -5
  53. package/dist-cms/packages/core/utils/guard-manager/readonly-variant-guard.manager.d.ts +12 -2
  54. package/dist-cms/packages/core/utils/guard-manager/readonly-variant-guard.manager.js +26 -11
  55. package/dist-cms/packages/core/variant/variant-id.class.d.ts +4 -0
  56. package/dist-cms/packages/core/variant/variant-id.class.js +5 -1
  57. package/dist-cms/packages/core/workspace/components/workspace-action/common/submit/submit.action.d.ts +2 -2
  58. package/dist-cms/packages/core/workspace/components/workspace-action/common/submit/submit.action.js +8 -2
  59. package/dist-cms/packages/core/workspace/components/workspace-split-view/workspace-split-view-variant-selector.element.js +7 -10
  60. package/dist-cms/packages/core/workspace/namable/name-write-guard.manager.js +5 -2
  61. package/dist-cms/packages/core/workspace/workspace.element.d.ts +0 -2
  62. package/dist-cms/packages/core/workspace/workspace.element.js +1 -4
  63. package/dist-cms/packages/documents/document-types/property-type/manifests.js +2 -1
  64. package/dist-cms/packages/documents/documents/reference/repository/manifests.js +2 -1
  65. package/dist-cms/packages/documents/documents/user-permissions/document-property-value/conditions/document-property-value-user-permission.condition.js +2 -0
  66. package/dist-cms/packages/documents/documents/user-permissions/document-property-value/data/manifests.js +4 -2
  67. package/dist-cms/packages/documents/documents/user-permissions/document-property-value/workspace-context/document-block-property-value-user-permission.workspace-context.js +3 -5
  68. package/dist-cms/packages/documents/documents/workspace/actions/save.action.js +2 -0
  69. package/dist-cms/packages/documents/documents/workspace/document-workspace.context.js +6 -1
  70. package/dist-cms/packages/documents/umbraco-package.d.ts +1 -6
  71. package/dist-cms/packages/documents/umbraco-package.js +2 -8
  72. package/dist-cms/packages/media/media/reference/repository/manifests.js +2 -1
  73. package/dist-cms/packages/media/media-types/property-type/manifests.js +2 -1
  74. package/dist-cms/packages/members/member/reference/repository/manifests.js +2 -1
  75. package/dist-cms/packages/members/member-type/property-type/manifests.js +2 -1
  76. package/dist-cms/packages/rte/components/rte-base.element.d.ts +2 -1
  77. package/dist-cms/packages/rte/components/rte-base.element.js +19 -6
  78. package/dist-cms/packages/ufm/umbraco-package.d.ts +1 -0
  79. package/dist-cms/packages/ufm/umbraco-package.js +1 -0
  80. package/dist-cms/packages/user/current-user/current-user.context.d.ts +3 -1
  81. package/dist-cms/packages/user/current-user/current-user.context.js +11 -17
  82. package/dist-cms/tsconfig.build.tsbuildinfo +1 -1
  83. package/package.json +1 -1
  84. package/vscode-html-custom-data.json +3 -3
@@ -382,7 +382,7 @@
382
382
  },
383
383
  {
384
384
  "name": "styles",
385
- "default": "\"css`\\n\\t\\t:host {\\n\\t\\t\\toverflow: hidden;\\n\\t\\t\\tmin-width: 920px;\\n\\t\\t}\\n\\n\\t\\t:host,\\n\\t\\t#router-slot {\\n\\t\\t\\tdisplay: block;\\n\\t\\t\\twidth: 100%;\\n\\t\\t\\theight: 100vh;\\n\\t\\t}\\n\\t`\""
385
+ "default": "\"css`\\n\\t\\t:host {\\n\\t\\t\\toverflow: hidden;\\n\\t\\t\\tmin-width: 920px;\\n\\t\\t}\\n\\n\\t\\t:host,\\n\\t\\t#router-slot {\\n\\t\\t\\tdisplay: block;\\n\\t\\t\\twidth: 100%;\\n\\t\\t\\theight: 100vh;\\n\\t\\t}\\n\\n\\t\\t#loader {\\n\\t\\t\\tdisplay: flex;\\n\\t\\t\\theight: 100%;\\n\\t\\t\\tjustify-content: center;\\n\\t\\t\\talign-items: center;\\n\\t\\t\\topacity: 0;\\n\\t\\t\\tanimation: fadeIn 240ms forwards;\\n\\t\\t}\\n\\t\\t@keyframes fadeIn {\\n\\t\\t\\tto {\\n\\t\\t\\t\\topacity: 1;\\n\\t\\t\\t}\\n\\t\\t}\\n\\t`\""
386
386
  }
387
387
  ]
388
388
  },
@@ -18,17 +18,59 @@ import { UmbLitElement } from '../../packages/core/lit-element/index.js';
18
18
  import { pathWithoutBasePath } from '../../packages/core/router/index.js';
19
19
  import { RuntimeLevelModel } from '../../packages/core/backend-api/index.js';
20
20
  import { UmbContextDebugController } from '../../packages/core/debug/index.js';
21
- import { UmbBundleExtensionInitializer, UmbServerExtensionRegistrator } from '../../libs/extension-api/index.js';
21
+ import { UmbBundleExtensionInitializer, UmbServerExtensionRegistrator, } from '../../libs/extension-api/index.js';
22
22
  import { UmbAppEntryPointExtensionInitializer, umbExtensionsRegistry, } from '../../packages/core/extension-registry/index.js';
23
- import { firstValueFrom } from '../../external/rxjs/index.js';
24
23
  import { redirectToStoredPath } from '../../packages/core/utils/index.js';
25
24
  import { umbHttpClient } from '../../packages/core/http-client/index.js';
26
25
  import { UmbViewContext } from '../../packages/core/view/index.js';
27
26
  import './app-logo.element.js';
27
+ import { UMB_CURRENT_USER_CONTEXT } from '../../packages/user/current-user/index.js';
28
+ const CORE_PACKAGES = [
29
+ import('../../packages/block/umbraco-package.js'),
30
+ import('../../packages/clipboard/umbraco-package.js'),
31
+ import('../../packages/code-editor/umbraco-package.js'),
32
+ import('../../packages/content/umbraco-package.js'),
33
+ import('../../packages/data-type/umbraco-package.js'),
34
+ import('../../packages/dictionary/umbraco-package.js'),
35
+ import('../../packages/documents/umbraco-package.js'),
36
+ import('../../packages/embedded-media/umbraco-package.js'),
37
+ import('../../packages/extension-insights/umbraco-package.js'),
38
+ import('../../packages/health-check/umbraco-package.js'),
39
+ import('../../packages/help/umbraco-package.js'),
40
+ import('../../packages/language/umbraco-package.js'),
41
+ import('../../packages/log-viewer/umbraco-package.js'),
42
+ import('../../packages/management-api/umbraco-package.js'),
43
+ import('../../packages/markdown-editor/umbraco-package.js'),
44
+ import('../../packages/media/umbraco-package.js'),
45
+ import('../../packages/members/umbraco-package.js'),
46
+ import('../../packages/models-builder/umbraco-package.js'),
47
+ import('../../packages/multi-url-picker/umbraco-package.js'),
48
+ import('../../packages/packages/umbraco-package.js'),
49
+ import('../../packages/performance-profiling/umbraco-package.js'),
50
+ import('../../packages/property-editors/umbraco-package.js'),
51
+ import('../../packages/publish-cache/umbraco-package.js'),
52
+ import('../../packages/relations/umbraco-package.js'),
53
+ import('../../packages/rte/umbraco-package.js'),
54
+ import('../../packages/settings/umbraco-package.js'),
55
+ import('../../packages/static-file/umbraco-package.js'),
56
+ import('../../packages/sysinfo/umbraco-package.js'),
57
+ import('../../packages/tags/umbraco-package.js'),
58
+ import('../../packages/telemetry/umbraco-package.js'),
59
+ import('../../packages/templating/umbraco-package.js'),
60
+ import('../../packages/tiptap/umbraco-package.js'),
61
+ import('../../packages/translation/umbraco-package.js'),
62
+ import('../../packages/ufm/umbraco-package.js'),
63
+ import('../../packages/umbraco-news/umbraco-package.js'),
64
+ import('../../packages/user/umbraco-package.js'),
65
+ import('../../packages/webhook/umbraco-package.js'),
66
+ ];
28
67
  let UmbAppElement = class UmbAppElement extends UmbLitElement {
29
68
  #authContext;
30
69
  #serverConnection;
31
70
  #authController;
71
+ #bundleInitializer;
72
+ #currentUser;
73
+ #packageModules;
32
74
  constructor() {
33
75
  super();
34
76
  /**
@@ -120,15 +162,21 @@ let UmbAppElement = class UmbAppElement extends UmbLitElement {
120
162
  {
121
163
  path: '**',
122
164
  component: () => import('../backoffice/backoffice.element.js'),
123
- guards: [this.#isAuthorizedGuard()],
165
+ guards: [this.#isAuthorizedGuard(), this.#loadedGuard()],
124
166
  },
125
167
  ];
126
168
  this.#authController = new UmbAppAuthController(this);
127
- new UmbBundleExtensionInitializer(this, umbExtensionsRegistry);
169
+ this.#bundleInitializer = new UmbBundleExtensionInitializer(this, umbExtensionsRegistry);
128
170
  new UUIIconRegistryEssential().attach(this);
129
171
  new UmbContextDebugController(this);
130
172
  new UmbNetworkConnectionStatusManager(this);
131
173
  new UmbViewContext(this, null);
174
+ this.consumeContext(UMB_CURRENT_USER_CONTEXT, (userContext) => {
175
+ this.#currentUser = userContext;
176
+ if (userContext) {
177
+ this.#loadCurrentUser();
178
+ }
179
+ });
132
180
  }
133
181
  connectedCallback() {
134
182
  super.connectedCallback();
@@ -137,6 +185,18 @@ let UmbAppElement = class UmbAppElement extends UmbLitElement {
137
185
  async #setup() {
138
186
  this.#authContext = new UmbAuthContext(this, this.serverUrl, this.backofficePath, this.bypassAuth, this.keepUserLoggedIn);
139
187
  this.#authContext.configureClient(umbHttpClient);
188
+ this.observe(this.#authContext.isAuthorized, async (isAuthorized) => {
189
+ if (isAuthorized === undefined)
190
+ return;
191
+ if (isAuthorized) {
192
+ // TODO: Remove dependency on current user context from the app element in future [MR]
193
+ this.#loadCurrentUser();
194
+ }
195
+ else {
196
+ // TODO: Unregistering all extensions from v.18 [NL]
197
+ //void this.#unregisterExtensions();
198
+ }
199
+ }, null);
140
200
  this.#serverConnection = await new UmbServerConnection(this, this.serverUrl).connect();
141
201
  new UmbServerContext(this, {
142
202
  backofficePath: this.backofficePath,
@@ -147,8 +207,7 @@ let UmbAppElement = class UmbAppElement extends UmbLitElement {
147
207
  onInit(this, umbExtensionsRegistry);
148
208
  // Register public extensions (login extensions)
149
209
  await new UmbServerExtensionRegistrator(this, umbExtensionsRegistry).registerPublicExtensions();
150
- const initializer = new UmbAppEntryPointExtensionInitializer(this, umbExtensionsRegistry);
151
- await firstValueFrom(initializer.loaded);
210
+ new UmbAppEntryPointExtensionInitializer(this, umbExtensionsRegistry);
152
211
  // Try to initialise the auth flow and get the runtime status
153
212
  try {
154
213
  // If the runtime level is "install" or ?status=false is set, we should clear any cached tokens
@@ -198,6 +257,30 @@ let UmbAppElement = class UmbAppElement extends UmbLitElement {
198
257
  // Auth context configures umbHttpClient in its constructor, so we only need to set initial state
199
258
  await this.#authContext.setInitialState();
200
259
  }
260
+ async #registerExtensions() {
261
+ if (this.#packageModules === undefined) {
262
+ this.#packageModules = Promise.all(CORE_PACKAGES);
263
+ this.#packageModules.then(() => {
264
+ this.#loadCurrentUser();
265
+ });
266
+ }
267
+ umbExtensionsRegistry.registerMany((await this.#packageModules).flatMap((modules) => modules.extensions));
268
+ }
269
+ // TODO (V18): Unregister extensions on sign-out. [NL]
270
+ /*
271
+ async #unregisterExtensions() {
272
+ if (!this.#packageModules) return;
273
+ (await this.#packageModules).forEach((packageModule) => {
274
+ const aliases = packageModule.extensions.map((extension) => extension.alias);
275
+ umbExtensionsRegistry.unregisterMany(aliases);
276
+ });
277
+ }
278
+ */
279
+ #loadCurrentUser() {
280
+ if (!this.#currentUser || !this.#packageModules)
281
+ return;
282
+ this.#currentUser.load();
283
+ }
201
284
  #redirect() {
202
285
  const pathname = pathWithoutBasePath({ start: true, end: false });
203
286
  // If we are on the oauth_complete or error page, we should not redirect
@@ -238,6 +321,20 @@ let UmbAppElement = class UmbAppElement extends UmbLitElement {
238
321
  #isAuthorizedGuard() {
239
322
  return () => this.#authController.isAuthorized() ?? false;
240
323
  }
324
+ #loadedGuard() {
325
+ return async () => {
326
+ const results = await Promise.allSettled([
327
+ this.observe(this.#bundleInitializer?.loaded).asPromise(),
328
+ this.#registerExtensions(),
329
+ new UmbServerExtensionRegistrator(this, umbExtensionsRegistry).registerPrivateExtensions(),
330
+ ]);
331
+ const result = results.reduce((acc, curr) => acc && curr.status === 'fulfilled', true);
332
+ if (result === false) {
333
+ this.#errorPage('Extensions failed loading, this might be due to a network issue or a server error. Check that extensions registered on the server are valid.', undefined, { headline: 'Failed to load extensions' });
334
+ }
335
+ return result;
336
+ };
337
+ }
241
338
  #errorPage(errorMsg, error, options) {
242
339
  // Redirect to the error page
243
340
  this._routes = [
@@ -258,7 +355,9 @@ let UmbAppElement = class UmbAppElement extends UmbLitElement {
258
355
  this.requestUpdate();
259
356
  }
260
357
  render() {
261
- return html `<umb-router-slot id="router-slot" .routes=${this._routes}></umb-router-slot>`;
358
+ return html `<umb-router-slot id="router-slot" .routes=${this._routes}
359
+ ><div id="loader"><uui-loader data-mark="app-router-loader"></uui-loader></div
360
+ ></umb-router-slot>`;
262
361
  }
263
362
  static { this.styles = css `
264
363
  :host {
@@ -272,6 +371,20 @@ let UmbAppElement = class UmbAppElement extends UmbLitElement {
272
371
  width: 100%;
273
372
  height: 100vh;
274
373
  }
374
+
375
+ #loader {
376
+ display: flex;
377
+ height: 100%;
378
+ justify-content: center;
379
+ align-items: center;
380
+ opacity: 0;
381
+ animation: fadeIn 240ms forwards;
382
+ }
383
+ @keyframes fadeIn {
384
+ to {
385
+ opacity: 1;
386
+ }
387
+ }
275
388
  `; }
276
389
  };
277
390
  __decorate([
@@ -6,7 +6,6 @@ import { UmbContextBase } from '../../libs/class-api/index.js';
6
6
  import { UmbContextToken } from '../../libs/context-api/index.js';
7
7
  import { UmbExtensionsManifestInitializer } from '../../libs/extension-api/index.js';
8
8
  import { UmbSysinfoRepository } from '../../packages/sysinfo/index.js';
9
- import { UMB_AUTH_CONTEXT } from '../../packages/core/auth/index.js';
10
9
  import { UMB_CURRENT_USER_CONTEXT } from '../../packages/user/current-user/index.js';
11
10
  export class UmbBackofficeContext extends UmbContextBase {
12
11
  #activeSectionAlias;
@@ -22,14 +21,7 @@ export class UmbBackofficeContext extends UmbContextBase {
22
21
  this.allowedSections = this.#allowedSections.asObservable();
23
22
  this.#version = new UmbStringState(undefined);
24
23
  this.version = this.#version.asObservable();
25
- // TODO: We need to ensure this request is called every time the user logs in, but this should be done somewhere across the app and not here [JOV]
26
- this.consumeContext(UMB_AUTH_CONTEXT, (authContext) => {
27
- this.observe(authContext?.isAuthorized, (isAuthorized) => {
28
- if (!isAuthorized)
29
- return;
30
- this.#getVersion();
31
- });
32
- });
24
+ this.#getVersion();
33
25
  this.consumeContext(UMB_CURRENT_USER_CONTEXT, (userContext) => {
34
26
  this.observe(userContext?.allowedSections, (allowedSections) => {
35
27
  if (!allowedSections)
@@ -1,14 +1,12 @@
1
1
  import { UmbLitElement } from '../../packages/core/lit-element/index.js';
2
2
  import './components/index.js';
3
3
  export declare class UmbBackofficeElement extends UmbLitElement {
4
- #private;
5
4
  /**
6
5
  * Backoffice extension registry.
7
6
  * This enables to register and unregister extensions via DevTools, or just via querying this element via the DOM.
8
7
  */
9
8
  extensionRegistry: import("../../packages/core/extension-registry/index.js").UmbBackofficeExtensionRegistry;
10
9
  constructor();
11
- firstUpdated(): Promise<void>;
12
10
  render(): import("lit-html").TemplateResult<1>;
13
11
  static readonly styles: import("lit").CSSResult[];
14
12
  }
@@ -7,49 +7,8 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
7
7
  import { UmbBackofficeContext } from './backoffice.context.js';
8
8
  import { css, html, customElement } from '../../external/lit/index.js';
9
9
  import { UmbBackofficeEntryPointExtensionInitializer, UmbEntryPointExtensionInitializer, umbExtensionsRegistry, } from '../../packages/core/extension-registry/index.js';
10
- import { UmbServerExtensionRegistrator } from '../../libs/extension-api/index.js';
11
10
  import { UmbLitElement } from '../../packages/core/lit-element/index.js';
12
- import { UMB_AUTH_CONTEXT } from '../../packages/core/auth/index.js';
13
11
  import './components/index.js';
14
- const CORE_PACKAGES = [
15
- import('../../packages/block/umbraco-package.js'),
16
- import('../../packages/clipboard/umbraco-package.js'),
17
- import('../../packages/code-editor/umbraco-package.js'),
18
- import('../../packages/content/umbraco-package.js'),
19
- import('../../packages/data-type/umbraco-package.js'),
20
- import('../../packages/dictionary/umbraco-package.js'),
21
- import('../../packages/documents/umbraco-package.js'),
22
- import('../../packages/embedded-media/umbraco-package.js'),
23
- import('../../packages/extension-insights/umbraco-package.js'),
24
- import('../../packages/health-check/umbraco-package.js'),
25
- import('../../packages/help/umbraco-package.js'),
26
- import('../../packages/language/umbraco-package.js'),
27
- import('../../packages/log-viewer/umbraco-package.js'),
28
- import('../../packages/management-api/umbraco-package.js'),
29
- import('../../packages/markdown-editor/umbraco-package.js'),
30
- import('../../packages/media/umbraco-package.js'),
31
- import('../../packages/members/umbraco-package.js'),
32
- import('../../packages/models-builder/umbraco-package.js'),
33
- import('../../packages/multi-url-picker/umbraco-package.js'),
34
- import('../../packages/packages/umbraco-package.js'),
35
- import('../../packages/performance-profiling/umbraco-package.js'),
36
- import('../../packages/property-editors/umbraco-package.js'),
37
- import('../../packages/publish-cache/umbraco-package.js'),
38
- import('../../packages/relations/umbraco-package.js'),
39
- import('../../packages/rte/umbraco-package.js'),
40
- import('../../packages/settings/umbraco-package.js'),
41
- import('../../packages/static-file/umbraco-package.js'),
42
- import('../../packages/sysinfo/umbraco-package.js'),
43
- import('../../packages/tags/umbraco-package.js'),
44
- import('../../packages/telemetry/umbraco-package.js'),
45
- import('../../packages/templating/umbraco-package.js'),
46
- import('../../packages/tiptap/umbraco-package.js'),
47
- import('../../packages/translation/umbraco-package.js'),
48
- import('../../packages/ufm/umbraco-package.js'),
49
- import('../../packages/umbraco-news/umbraco-package.js'),
50
- import('../../packages/user/umbraco-package.js'),
51
- import('../../packages/webhook/umbraco-package.js'),
52
- ];
53
12
  let UmbBackofficeElement = class UmbBackofficeElement extends UmbLitElement {
54
13
  constructor() {
55
14
  super();
@@ -62,23 +21,6 @@ let UmbBackofficeElement = class UmbBackofficeElement extends UmbLitElement {
62
21
  new UmbBackofficeEntryPointExtensionInitializer(this, umbExtensionsRegistry);
63
22
  new UmbEntryPointExtensionInitializer(this, umbExtensionsRegistry);
64
23
  }
65
- async firstUpdated() {
66
- // TODO: Move this logic into the Context? [NL]
67
- await this.#extensionsAfterAuth();
68
- // So far local packages are this simple to register, so no need for a manager to do that:
69
- CORE_PACKAGES.forEach(async (packageImport) => {
70
- const packageModule = await packageImport;
71
- umbExtensionsRegistry.registerMany(packageModule.extensions);
72
- });
73
- }
74
- async #extensionsAfterAuth() {
75
- const authContext = await this.getContext(UMB_AUTH_CONTEXT, { preventTimeout: true });
76
- if (!authContext) {
77
- throw new Error('UmbBackofficeElement requires the UMB_AUTH_CONTEXT to be set.');
78
- }
79
- await this.observe(authContext.isAuthorized).asPromise();
80
- await new UmbServerExtensionRegistrator(this, umbExtensionsRegistry).registerPrivateExtensions();
81
- }
82
24
  render() {
83
25
  return html `
84
26
  <umb-backoffice-header></umb-backoffice-header>
@@ -1,11 +1,11 @@
1
1
  import { UmbLitElement } from '../../../packages/core/lit-element/index.js';
2
2
  export declare class UmbBackofficeMainElement extends UmbLitElement {
3
3
  #private;
4
- private _routes;
4
+ private _routes?;
5
5
  private _sections;
6
6
  constructor();
7
7
  private _observeBackoffice;
8
- render(): import("lit-html").TemplateResult<1> | undefined;
8
+ render(): import("lit-html").TemplateResult<1>;
9
9
  static styles: import("lit").CSSResult[];
10
10
  }
11
11
  declare global {
@@ -13,7 +13,6 @@ let UmbBackofficeMainElement = class UmbBackofficeMainElement extends UmbLitElem
13
13
  #backofficeContext;
14
14
  constructor() {
15
15
  super();
16
- this._routes = [];
17
16
  this._sections = [];
18
17
  this.consumeContext(UMB_BACKOFFICE_CONTEXT, (_instance) => {
19
18
  this.#backofficeContext = _instance;
@@ -64,8 +63,9 @@ let UmbBackofficeMainElement = class UmbBackofficeMainElement extends UmbLitElem
64
63
  this._routes = newRoutes;
65
64
  }
66
65
  render() {
67
- if (!this._routes.length)
68
- return;
66
+ if (!this._routes || !this._routes.length) {
67
+ return html `<div id="loader"><uui-loader></uui-loader></div>`;
68
+ }
69
69
  return html `<umb-router-slot .routes=${this._routes}></umb-router-slot>`;
70
70
  }
71
71
  static { this.styles = [
@@ -78,6 +78,19 @@ let UmbBackofficeMainElement = class UmbBackofficeMainElement extends UmbLitElem
78
78
  100% - 60px
79
79
  ); /* 60 => top header height, TODO: Make sure this comes from somewhere so it is maintainable and eventually responsive. */
80
80
  }
81
+
82
+ #loader {
83
+ display: flex;
84
+ justify-content: center;
85
+ align-items: center;
86
+ opacity: 0;
87
+ animation: fadeIn 240ms forwards;
88
+ }
89
+ @keyframes fadeIn {
90
+ to {
91
+ opacity: 1;
92
+ }
93
+ }
81
94
  `,
82
95
  ]; }
83
96
  };
@@ -20,7 +20,7 @@ export declare abstract class UmbBaseExtensionInitializer<ManifestType extends M
20
20
  constructor(host: UmbControllerHost, extensionRegistry: UmbExtensionRegistry<ManifestCondition>, controllerTypeName: string, alias: string, onPermissionChanged?: (isPermitted: boolean, controller: SubClassType) => void);
21
21
  protected _init(): void;
22
22
  asPromise(): Promise<void>;
23
- protected abstract _conditionsAreGood(): Promise<boolean>;
23
+ protected abstract _conditionsAreGood(signal: AbortSignal): Promise<boolean>;
24
24
  protected abstract _conditionsAreBad(): Promise<void>;
25
25
  equal(otherClass: UmbBaseExtensionInitializer | undefined): boolean;
26
26
  destroy(): void;
@@ -46,7 +46,7 @@ export class UmbBaseExtensionInitializer extends UmbControllerBase {
46
46
  return (this.#manifest?.conditions ?? []).length > 0;
47
47
  };
48
48
  this.#gotConditions = (manifests) => {
49
- manifests.forEach(this.#gotCondition);
49
+ manifests?.forEach(this.#gotCondition);
50
50
  };
51
51
  this.#gotCondition = async (conditionManifest) => {
52
52
  if (!this.#manifest)
@@ -88,34 +88,54 @@ export class UmbBaseExtensionInitializer extends UmbControllerBase {
88
88
  // When writing this the only plausible case is a call from the conditionController to the onChange callback.
89
89
  return;
90
90
  }
91
+ // Find a condition that is not permitted (Notice how no conditions, means that this extension is permitted)
92
+ const isPositive = this.#checkConditionsAreGood();
93
+ if (this._isConditionsPositive === isPositive) {
94
+ // No change in the conditions, so we don't need to do anything, this is an optimization to prevent multiple calls to the callback when there is no change. [NL]
95
+ return;
96
+ }
91
97
  // We will collect old value here, but we need to re-collect it after a async method have been called, as it could have changed in the mean time. [NL]
92
98
  let oldValue = this.#isPermitted ?? false;
93
- // Find a condition that is not permitted (Notice how no conditions, means that this extension is permitted)
94
- const isPositive = this.#conditionsAreInitialized() &&
95
- this.#conditionControllers.some((condition) => condition.permitted === false) === false;
96
99
  this._isConditionsPositive = isPositive;
97
100
  if (isPositive === true) {
98
101
  if (this.#isPermitted !== true) {
99
- const newPermission = await this._conditionsAreGood();
100
- // Only set new permission if we are still positive, otherwise it means that we have been destroyed in the mean time. [NL]
101
- if (newPermission === false || this._isConditionsPositive === false) {
102
- // Then we need to revert the above work:
103
- this._conditionsAreBad();
102
+ let newPermission;
103
+ // check current pending _conditionsAreGood call: [NL]
104
+ let entry = this.#pendingGoodCall;
105
+ if (entry && entry.manifest === this.#manifest) {
106
+ newPermission = await entry.promise;
107
+ }
108
+ else {
109
+ // Since this is a new call, then lets abort the pending call. [NL]
110
+ this.#abortPendingGoodCall();
111
+ await this._conditionsAreBad();
112
+ const abortController = new AbortController();
113
+ const promise = this._conditionsAreGood(abortController.signal);
114
+ entry = { manifest: this.#manifest, promise, abortController };
115
+ this.#pendingGoodCall = entry;
116
+ newPermission = await promise;
117
+ }
118
+ if (this.#pendingGoodCall !== entry) {
104
119
  return;
105
120
  }
121
+ else {
122
+ this.#pendingGoodCall = undefined;
123
+ }
124
+ // Only set new permission if we are still positive, otherwise it means that we have been destroyed in the mean time, or we got destroyed. [NL]
125
+ if (newPermission === false || this._isConditionsPositive !== true) {
126
+ // Then we need to revert the above work. Sync call — see the note in
127
+ // the else-if branch on why we don't await. [NL]
128
+ this._conditionsAreBad();
129
+ newPermission = false;
130
+ }
106
131
  // We update the oldValue as this point, cause in this way we are sure its the value at this point, when doing async code someone else might have changed the state in the mean time. [NL]
107
132
  oldValue = this.#isPermitted ?? false;
108
133
  this.#isPermitted = newPermission;
109
134
  }
110
135
  }
111
136
  else if (this.#isPermitted !== false) {
112
- // Clean up:
113
- await this._conditionsAreBad();
114
- // Only continue if we are still negative, otherwise it means that something changed in the mean time. [NL]
115
- if (this._isConditionsPositive === true) {
116
- return;
117
- }
118
- // We update the oldValue as this point, cause in this way we are sure its the value at this point, when doing async code someone else might have changed the state in the mean time. [NL]
137
+ // clean-up any current [NL]
138
+ this._conditionsAreBad();
119
139
  oldValue = this.#isPermitted ?? false;
120
140
  this.#isPermitted = false;
121
141
  }
@@ -146,6 +166,7 @@ export class UmbBaseExtensionInitializer extends UmbControllerBase {
146
166
  }
147
167
  else {
148
168
  this.#manifest = undefined;
169
+ this._isConditionsPositive = undefined;
149
170
  this.#clearPermittedState();
150
171
  this.#overwrites = [];
151
172
  this.#cleanConditions();
@@ -240,10 +261,28 @@ export class UmbBaseExtensionInitializer extends UmbControllerBase {
240
261
  }
241
262
  return undefined;
242
263
  }
243
- #conditionsAreInitialized() {
264
+ #checkConditionsAreGood() {
244
265
  // Not good if we don't have a manifest.
266
+ if (this.#manifest === undefined)
267
+ return false;
245
268
  // Only good if conditions of manifest is equal to the amount of condition controllers (one for each condition). [NL]
246
- return (this.#manifest !== undefined && this.#conditionControllers.length === (this.#manifest.conditions ?? []).length);
269
+ const hasAllConditions = (this.#manifest.conditions ?? []).length === this.#conditionControllers.length;
270
+ if (hasAllConditions === false)
271
+ return false;
272
+ // Compare all manifest conditions with the condition controllers configs to be sure we have the right ones, as we might end up in a state where we have the same amount of controllers as conditions, but they are not the right ones. [NL]
273
+ const allConditionsHaveControllers = (this.#manifest.conditions ?? []).every((condition) => this.#conditionControllers.some((controller) => controller.config === condition));
274
+ if (allConditionsHaveControllers === false)
275
+ return false;
276
+ // Only good if all the conditions are permitted:
277
+ return this.#conditionControllers.some((condition) => condition.permitted === false) === false;
278
+ }
279
+ // The currently-pending `_conditionsAreGood()` promise and manifest, to detect if we can reuse it.
280
+ #pendingGoodCall;
281
+ #abortPendingGoodCall() {
282
+ if (this.#pendingGoodCall) {
283
+ this.#pendingGoodCall.abortController.abort();
284
+ this.#pendingGoodCall = undefined;
285
+ }
247
286
  }
248
287
  #onConditionsChangedCallback;
249
288
  equal(otherClass) {
@@ -279,9 +318,12 @@ export class UmbBaseExtensionInitializer extends UmbControllerBase {
279
318
  return;
280
319
  this.#manifest = undefined;
281
320
  this.#promiseResolvers = [];
321
+ // Abort any pending good-call so its subclass run refuses to commit (sees
322
+ // `signal.aborted`) instead of trying to assign into a destroyed initializer. [NL]
323
+ this.#abortPendingGoodCall();
282
324
  this.#clearPermittedState(); // This fires the callback as not permitted, if it was permitted before. [NL]
283
325
  this.#isPermitted = undefined;
284
- this._isConditionsPositive = false;
326
+ this._isConditionsPositive = undefined;
285
327
  this.#overwrites = [];
286
328
  this.#cleanConditions();
287
329
  this.#onPermissionChanged = undefined;
@@ -36,7 +36,7 @@ export declare class UmbExtensionApiInitializer<ManifestType extends ManifestApi
36
36
  * ```
37
37
  */
38
38
  constructor(host: UmbControllerHost, extensionRegistry: UmbExtensionRegistry<ManifestCondition>, alias: string, constructorArguments: Array<unknown> | UmbApiConstructorArgumentsMethodType<ManifestType> | undefined, onPermissionChanged?: (isPermitted: boolean, controller: ControllerType) => void);
39
- protected _conditionsAreGood(): Promise<boolean>;
39
+ protected _conditionsAreGood(signal: AbortSignal): Promise<boolean>;
40
40
  protected _conditionsAreBad(): Promise<void>;
41
41
  destroy(): void;
42
42
  }
@@ -60,13 +60,20 @@ export class UmbExtensionApiInitializer extends UmbBaseExtensionInitializer {
60
60
  });
61
61
  };
62
62
  */
63
- async _conditionsAreGood() {
63
+ async _conditionsAreGood(signal) {
64
64
  const manifest = this.manifest; // In this case we are sure its not undefined.
65
65
  const newApi = await createExtensionApi(this._host, manifest, this.#constructorArguments);
66
- if (!this._isConditionsPositive) {
67
- // We are not positive anymore, so we will back out of this creation.
66
+ if (signal.aborted || !this._isConditionsPositive) {
67
+ newApi?.destroy?.();
68
68
  return false;
69
69
  }
70
+ // A previous _conditionsAreGood() on this same initializer may have already
71
+ // assigned this.#api and resolved before us. Without cleanup that instance would
72
+ // keep running until this._host is destroyed — not a hard leak, but any
73
+ // subscriptions / context consumers / async setup it started stay alive.
74
+ if (this.#api && this.#api !== newApi) {
75
+ this.#api.destroy?.();
76
+ }
70
77
  this.#api = newApi;
71
78
  if (this.#api) {
72
79
  this.#api.manifest = manifest;
@@ -32,7 +32,7 @@ export declare class UmbExtensionElementAndApiInitializer<ManifestType extends M
32
32
  get apiProps(): Record<string, unknown> | undefined;
33
33
  set apiProps(newVal: Record<string, unknown> | undefined);
34
34
  constructor(host: UmbControllerHost, extensionRegistry: UmbExtensionRegistry<ManifestCondition>, alias: string, constructorArguments: Array<unknown> | UmbApiConstructorArgumentsMethodType<ManifestType> | undefined, onPermissionChanged: (isPermitted: boolean, controller: ControllerType) => void, defaultElement?: string, defaultApi?: ApiLoaderProperty<ExtensionApiInterface>);
35
- protected _conditionsAreGood(): Promise<boolean>;
35
+ protected _conditionsAreGood(signal: AbortSignal): Promise<boolean>;
36
36
  protected _conditionsAreBad(): Promise<void>;
37
37
  destroy(): void;
38
38
  }
@@ -100,17 +100,26 @@ export class UmbExtensionElementAndApiInitializer extends UmbBaseExtensionInitia
100
100
  this.#api[key] = this.#apiProps[key];
101
101
  });
102
102
  };
103
- async _conditionsAreGood() {
103
+ async _conditionsAreGood(signal) {
104
104
  const manifest = this.manifest; // In this case we are sure its not undefined.
105
105
  const { element: newComponent, api: newApi } = await createExtensionElementWithApi(manifest, this.#constructorArguments, this.#defaultElement, this.#defaultApi);
106
- if (!this._isConditionsPositive) {
106
+ if (signal.aborted || !this._isConditionsPositive) {
107
107
  newApi?.destroy?.();
108
108
  if (newComponent && 'destroy' in newComponent) {
109
109
  newComponent.destroy();
110
110
  }
111
- // We are not positive anymore, so we will back out of this creation.
112
111
  return false;
113
112
  }
113
+ // A previous _conditionsAreGood() on this same initializer may have already
114
+ // assigned this.#api / this.#component and resolved before us. The API's host is
115
+ // the transient element (see createExtensionElementWithApi), not this initializer,
116
+ // so nothing else in the controller-host chain will clean an orphaned pair up.
117
+ if (this.#api && this.#api !== newApi) {
118
+ this.#api.destroy?.();
119
+ }
120
+ if (this.#component && this.#component !== newComponent && 'destroy' in this.#component) {
121
+ this.#component.destroy();
122
+ }
114
123
  this.#api = newApi;
115
124
  if (this.#api) {
116
125
  this.#assignApiProps();
@@ -22,7 +22,7 @@ export declare class UmbExtensionElementInitializer<ManifestType extends Manifes
22
22
  get properties(): Record<string, unknown> | undefined;
23
23
  set properties(newVal: Record<string, unknown> | undefined);
24
24
  constructor(host: UmbControllerHost, extensionRegistry: UmbExtensionRegistry<ManifestCondition>, alias: string, onPermissionChanged: (isPermitted: boolean, controller: ControllerType) => void, defaultElement?: string);
25
- protected _conditionsAreGood(): Promise<boolean>;
25
+ protected _conditionsAreGood(signal: AbortSignal): Promise<boolean>;
26
26
  protected _conditionsAreBad(): Promise<void>;
27
27
  destroy(): void;
28
28
  }
@@ -56,13 +56,21 @@ export class UmbExtensionElementInitializer extends UmbBaseExtensionInitializer
56
56
  this.#component[key] = this.#properties[key];
57
57
  });
58
58
  };
59
- async _conditionsAreGood() {
59
+ async _conditionsAreGood(signal) {
60
60
  const manifest = this.manifest; // In this case we are sure its not undefined.
61
61
  const newComponent = await createExtensionElement(manifest, this.#defaultElement);
62
- if (!this._isConditionsPositive) {
63
- // We are not positive anymore, so we will back out of this creation.
62
+ if (signal.aborted || !this._isConditionsPositive) {
63
+ if (newComponent && 'destroy' in newComponent) {
64
+ newComponent.destroy();
65
+ }
64
66
  return false;
65
67
  }
68
+ if (this.#component && this.#component !== newComponent) {
69
+ if ('destroy' in this.#component) {
70
+ this.#component.destroy();
71
+ }
72
+ this.#component = undefined;
73
+ }
66
74
  this.#component = newComponent;
67
75
  if (this.#component) {
68
76
  this.#assignProperties();
@@ -13,6 +13,6 @@ import type { UmbControllerHost } from '../../controller-api/index.js';
13
13
  */
14
14
  export declare class UmbExtensionManifestInitializer<ManifestType extends ManifestWithDynamicConditions = ManifestWithDynamicConditions, ControllerType extends UmbBaseExtensionInitializer<ManifestType, any> = any> extends UmbBaseExtensionInitializer<ManifestType, ControllerType> {
15
15
  constructor(host: UmbControllerHost, extensionRegistry: UmbExtensionRegistry<ManifestCondition>, alias: string, onPermissionChanged: (isPermitted: boolean, controller: ControllerType) => void);
16
- protected _conditionsAreGood(): Promise<boolean>;
16
+ protected _conditionsAreGood(_signal: AbortSignal): Promise<boolean>;
17
17
  protected _conditionsAreBad(): Promise<void>;
18
18
  }