From 9282f57198e4097f7c77d5b6b1b26a66aa529298 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Tue, 5 Nov 2024 14:44:15 +0100 Subject: [PATCH 01/17] visual corrections --- .../block-grid-block-inline.element.ts | 1 + .../components/block-grid-entry/block-grid-entry.element.ts | 6 +++--- .../user/user/components/user-avatar/user-avatar.element.ts | 2 +- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/packages/block/block-grid/components/block-grid-block-inline/block-grid-block-inline.element.ts b/src/packages/block/block-grid/components/block-grid-block-inline/block-grid-block-inline.element.ts index 82e3f4333f..d532cd46a8 100644 --- a/src/packages/block/block-grid/components/block-grid-block-inline/block-grid-block-inline.element.ts +++ b/src/packages/block/block-grid/components/block-grid-block-inline/block-grid-block-inline.element.ts @@ -294,6 +294,7 @@ export class UmbBlockGridBlockInlineElement extends UmbLitElement { #inside { position: relative; display: block; + padding: calc(var(--uui-size-layout-1)); } `, ]; diff --git a/src/packages/block/block-grid/components/block-grid-entry/block-grid-entry.element.ts b/src/packages/block/block-grid/components/block-grid-entry/block-grid-entry.element.ts index 4e39f774d5..b46881743f 100644 --- a/src/packages/block/block-grid/components/block-grid-entry/block-grid-entry.element.ts +++ b/src/packages/block/block-grid/components/block-grid-entry/block-grid-entry.element.ts @@ -598,15 +598,15 @@ export class UmbBlockGridEntryElement extends UmbLitElement implements UmbProper inset: 0; border: 1px solid transparent; border-radius: var(--uui-border-radius); - box-shadow: - 0 0 0 1px rgba(255, 255, 255, 0.7), - inset 0 0 0 1px rgba(255, 255, 255, 0.7); transition: border-color 240ms ease-in; } :host(:hover):not(:drop)::after { display: block; border-color: var(--uui-color-interactive-emphasis); + box-shadow: + 0 0 0 1px rgba(255, 255, 255, 0.7), + inset 0 0 0 1px rgba(255, 255, 255, 0.7); } :host([drag-placeholder])::after { diff --git a/src/packages/user/user/components/user-avatar/user-avatar.element.ts b/src/packages/user/user/components/user-avatar/user-avatar.element.ts index 44661991dc..84fe60e2da 100644 --- a/src/packages/user/user/components/user-avatar/user-avatar.element.ts +++ b/src/packages/user/user/components/user-avatar/user-avatar.element.ts @@ -117,8 +117,8 @@ export class UmbUserAvatarElement extends UmbLitElement { css` uui-avatar { background-color: transparent; - border: 1.5px solid var(--uui-color-border); color: inherit; + box-shadow: 0 0 0 1.5px var(--uui-color-border); } uui-avatar.has-image { From 9a347bd7dbfa2d57200027b5658df77666618f82 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Tue, 5 Nov 2024 15:38:26 +0100 Subject: [PATCH 02/17] validation translators ctrl alias --- ...nt-values-validation-path-translator.controller.ts | 4 +++- .../abstract-array-path-translator.controller.ts | 11 ++++++++--- .../validation-path-translator-base.controller.ts | 6 +++--- 3 files changed, 14 insertions(+), 7 deletions(-) diff --git a/src/packages/block/block/validation/block-element-values-validation-path-translator.controller.ts b/src/packages/block/block/validation/block-element-values-validation-path-translator.controller.ts index 2c2b9da88c..4eaa8b431c 100644 --- a/src/packages/block/block/validation/block-element-values-validation-path-translator.controller.ts +++ b/src/packages/block/block/validation/block-element-values-validation-path-translator.controller.ts @@ -4,9 +4,11 @@ import { UmbDataPathPropertyValueQuery, } from '@umbraco-cms/backoffice/validation'; +const ctrlAlias = Symbol(); + export class UmbBlockElementValuesDataValidationPathTranslator extends UmbAbstractArrayValidationPathTranslator { constructor(host: UmbControllerHost) { - super(host, '$.values[', UmbDataPathPropertyValueQuery); + super(host, '$.values[', UmbDataPathPropertyValueQuery, ctrlAlias); } getDataFromIndex(index: number) { diff --git a/src/packages/core/validation/translators/abstract-array-path-translator.controller.ts b/src/packages/core/validation/translators/abstract-array-path-translator.controller.ts index 121310af55..4e7200a340 100644 --- a/src/packages/core/validation/translators/abstract-array-path-translator.controller.ts +++ b/src/packages/core/validation/translators/abstract-array-path-translator.controller.ts @@ -1,12 +1,17 @@ import { UmbValidationPathTranslatorBase } from './validation-path-translator-base.controller.js'; -import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; +import type { UmbControllerAlias, UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; export abstract class UmbAbstractArrayValidationPathTranslator extends UmbValidationPathTranslatorBase { #initialPathToMatch: string; #queryMethod: (data: unknown) => string; - constructor(host: UmbControllerHost, initialPathToMatch: string, queryMethod: (data: any) => string) { - super(host); + constructor( + host: UmbControllerHost, + initialPathToMatch: string, + queryMethod: (data: any) => string, + ctrlAlias?: UmbControllerAlias, + ) { + super(host, ctrlAlias); this.#initialPathToMatch = initialPathToMatch; this.#queryMethod = queryMethod; diff --git a/src/packages/core/validation/translators/validation-path-translator-base.controller.ts b/src/packages/core/validation/translators/validation-path-translator-base.controller.ts index 27dda12276..b9a9bacb8e 100644 --- a/src/packages/core/validation/translators/validation-path-translator-base.controller.ts +++ b/src/packages/core/validation/translators/validation-path-translator-base.controller.ts @@ -1,6 +1,6 @@ import { UMB_VALIDATION_CONTEXT } from '../index.js'; import type { UmbValidationMessageTranslator } from './validation-message-path-translator.interface.js'; -import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; +import type { UmbControllerAlias, UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; import { UmbControllerBase } from '@umbraco-cms/backoffice/class-api'; export abstract class UmbValidationPathTranslatorBase @@ -10,8 +10,8 @@ export abstract class UmbValidationPathTranslatorBase // protected _context?: typeof UMB_VALIDATION_CONTEXT.TYPE; - constructor(host: UmbControllerHost) { - super(host); + constructor(host: UmbControllerHost, ctrlAlias?: UmbControllerAlias) { + super(host, ctrlAlias); this.consumeContext(UMB_VALIDATION_CONTEXT, (context) => { this._context?.removeTranslator(this); From 06839466b43e8aea8ea87e5817473cf9683eff06 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Tue, 5 Nov 2024 16:23:55 +0100 Subject: [PATCH 03/17] remember provider host for validation context --- .../core/validation/controllers/validation.controller.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/packages/core/validation/controllers/validation.controller.ts b/src/packages/core/validation/controllers/validation.controller.ts index feaf76897a..e535dc933d 100644 --- a/src/packages/core/validation/controllers/validation.controller.ts +++ b/src/packages/core/validation/controllers/validation.controller.ts @@ -79,13 +79,16 @@ export class UmbValidationController extends UmbControllerBase implements UmbVal this.messages.removeTranslator(translator); } + #currentProvideHost?: UmbClassInterface; /** * Provide this validation context to a specific controller host. * This can be used to Host a validation context in a Workspace, but provide it on a certain scope, like a specific Workspace View. * @param controllerHost {UmbClassInterface} */ provideAt(controllerHost: UmbClassInterface): void { + if (this.#currentProvideHost === controllerHost) return; this.#providerCtrl?.destroy(); + this.#currentProvideHost = controllerHost; this.#providerCtrl = controllerHost.provideContext(UMB_VALIDATION_CONTEXT, this); } From 24fc8acfe32e2bb87d31b902f0b347b015a6b6be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Tue, 5 Nov 2024 16:25:42 +0100 Subject: [PATCH 04/17] remove event listener --- src/libs/context-api/provide/context-provider.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/libs/context-api/provide/context-provider.ts b/src/libs/context-api/provide/context-provider.ts index 31f62cdc8e..61d8f461b4 100644 --- a/src/libs/context-api/provide/context-provider.ts +++ b/src/libs/context-api/provide/context-provider.ts @@ -103,6 +103,8 @@ export class UmbContextProvider Date: Tue, 5 Nov 2024 16:28:59 +0100 Subject: [PATCH 05/17] can be destroyed multiple times --- .../element-property-dataset.context.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/packages/core/content/property-dataset-context/element-property-dataset.context.ts b/src/packages/core/content/property-dataset-context/element-property-dataset.context.ts index 0490888eb7..35a1a81a5b 100644 --- a/src/packages/core/content/property-dataset-context/element-property-dataset.context.ts +++ b/src/packages/core/content/property-dataset-context/element-property-dataset.context.ts @@ -215,7 +215,8 @@ export abstract class UmbElementPropertyDatasetContext< override destroy() { super.destroy(); - this.#propertyVariantIdMap.destroy(); + + this.#propertyVariantIdMap?.destroy(); (this.#propertyVariantIdMap as unknown) = undefined; } } From 778866151dd57025499bfb79f62480b95f717e53 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Tue, 5 Nov 2024 16:29:51 +0100 Subject: [PATCH 06/17] error to help general error discovery --- src/libs/context-api/consume/context-consumer.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/libs/context-api/consume/context-consumer.ts b/src/libs/context-api/consume/context-consumer.ts index f4eab04f67..2805353c35 100644 --- a/src/libs/context-api/consume/context-consumer.ts +++ b/src/libs/context-api/consume/context-consumer.ts @@ -72,6 +72,10 @@ export class UmbContextConsumer Date: Tue, 5 Nov 2024 20:11:53 +0100 Subject: [PATCH 07/17] update to life cycle of extension initializers --- ...e-extension-initializer.controller.test.ts | 49 +++++------ .../base-extension-initializer.controller.ts | 83 +++++++++++-------- ...-extensions-initializer.controller.test.ts | 52 +++++++----- 3 files changed, 103 insertions(+), 81 deletions(-) diff --git a/src/libs/extension-api/controller/base-extension-initializer.controller.test.ts b/src/libs/extension-api/controller/base-extension-initializer.controller.test.ts index 29624ff01c..49853d3b33 100644 --- a/src/libs/extension-api/controller/base-extension-initializer.controller.test.ts +++ b/src/libs/extension-api/controller/base-extension-initializer.controller.test.ts @@ -59,12 +59,12 @@ class UmbTestConditionAlwaysInvalid extends UmbControllerBase implements UmbExte describe('UmbBaseExtensionController', () => { describe('Manifest without conditions', () => { - //let hostElement: UmbControllerHostElement; + let hostElement: UmbControllerHostElement; let extensionRegistry: UmbExtensionRegistry; let manifest: ManifestWithDynamicConditions; beforeEach(async () => { - //hostElement = await fixture(html``); + hostElement = await fixture(html``); extensionRegistry = new UmbExtensionRegistry(); manifest = { type: 'section', @@ -74,7 +74,7 @@ describe('UmbBaseExtensionController', () => { extensionRegistry.register(manifest); }); - /* + it('permits when there is no conditions', (done) => { const extensionController = new UmbTestExtensionController( hostElement, @@ -92,16 +92,15 @@ describe('UmbBaseExtensionController', () => { }, ); }); - */ }); describe('Manifest with empty conditions', () => { - //let hostElement: UmbControllerHostElement; + let hostElement: UmbControllerHostElement; let extensionRegistry: UmbExtensionRegistry; let manifest: ManifestWithDynamicConditions; beforeEach(async () => { - //hostElement = await fixture(html``); + hostElement = await fixture(html``); extensionRegistry = new UmbExtensionRegistry(); manifest = { type: 'section', @@ -113,7 +112,6 @@ describe('UmbBaseExtensionController', () => { extensionRegistry.register(manifest); }); - /* it('permits when there is empty conditions', (done) => { const extensionController = new UmbTestExtensionController( hostElement, @@ -124,7 +122,7 @@ describe('UmbBaseExtensionController', () => { if (extensionController.permitted) { expect(extensionController?.manifest?.alias).to.eq('Umb.Test.Section.1'); - // Also verifying that the promise gets resolved. + // Also verifying that the promise gets resolved. [NL] extensionController.asPromise().then(() => { done(); }); @@ -132,7 +130,6 @@ describe('UmbBaseExtensionController', () => { }, ); }); - */ }); describe('Manifest with valid conditions', () => { @@ -225,14 +222,14 @@ describe('UmbBaseExtensionController', () => { if (isPermitted) { count++; if (count === 1) { - // First time render, there is no conditions. + // First time render, there is no conditions. [NL] expect(extensionController.manifest?.weight).to.be.equal(2); expect(extensionController.manifest?.conditions?.length).to.be.equal(1); } else if (count === 2) { - // Second time render, there is conditions and weight is 22. + // Second time render, there is conditions and weight is 22. [NL] expect(extensionController.manifest?.weight).to.be.equal(22); expect(extensionController.manifest?.conditions?.length).to.be.equal(1); - // Check that the promise has been resolved for the first render to ensure timing is right. + // Check that the promise has been resolved for the first render to ensure timing is right. [NL] expect(initialPromiseResolved).to.be.true; done(); extensionController.destroy(); @@ -270,14 +267,14 @@ describe('UmbBaseExtensionController', () => { if (isPermitted) { count++; if (count === 1) { - // First time render, there is no conditions. + // First time render, there is no conditions. [NL] expect(extensionController.manifest?.weight).to.be.equal(3); expect(extensionController.manifest?.conditions?.length).to.be.equal(0); } else if (count === 2) { - // Second time render, there is conditions and weight is 33. + // Second time render, there is conditions and weight is 33. [NL] expect(extensionController.manifest?.weight).to.be.equal(33); expect(extensionController.manifest?.conditions?.length).to.be.equal(0); - // Check that the promise has been resolved for the first render to ensure timing is right. + // Check that the promise has been resolved for the first render to ensure timing is right. [NL] expect(initialPromiseResolved).to.be.true; done(); extensionController.destroy(); @@ -315,14 +312,14 @@ describe('UmbBaseExtensionController', () => { if (isPermitted) { count++; if (count === 1) { - // First time render, there is no conditions. + // First time render, there is no conditions. [NL] expect(extensionController.manifest?.weight).to.be.equal(4); expect(extensionController.manifest?.conditions?.length).to.be.equal(0); } else if (count === 2) { - // Second time render, there is conditions and weight is 33. + // Second time render, there is conditions and weight is updated. [NL] expect(extensionController.manifest?.weight).to.be.equal(44); expect(extensionController.manifest?.conditions?.length).to.be.equal(1); - // Check that the promise has been resolved for the first render to ensure timing is right. + // Check that the promise has been resolved for the first render to ensure timing is right. [NL] expect(initialPromiseResolved).to.be.true; done(); extensionController.destroy(); @@ -370,14 +367,14 @@ describe('UmbBaseExtensionController', () => { if (isPermitted) { count++; if (count === 1) { - // First time render, there is no conditions. + // First time render, there is no conditions. [NL] expect(extensionController.manifest?.weight).to.be.undefined; expect(extensionController.manifest?.conditions?.length).to.be.equal(0); } else if (count === 2) { - // Second time render, there is a matching kind and then weight is 123. + // Second time render, there is a matching kind and then weight is 123. [NL] expect(extensionController.manifest?.weight).to.be.equal(123); expect(extensionController.manifest?.conditions?.length).to.be.equal(0); - // Check that the promise has been resolved for the first render to ensure timing is right. + // Check that the promise has been resolved for the first render to ensure timing is right. [NL] expect(initialPromiseResolved).to.be.true; done(); extensionController.destroy(); @@ -531,7 +528,7 @@ describe('UmbBaseExtensionController', () => { 'Umb.Test.Section.1', async () => { count++; - // We want the controller callback to first fire when conditions are initialized. + // We want the controller callback to first fire when conditions are initialized. [NL] expect(extensionController.manifest?.conditions?.length).to.be.equal(1); expect(extensionController?.manifest?.alias).to.eq('Umb.Test.Section.1'); if (count === 1) { @@ -596,27 +593,27 @@ describe('UmbBaseExtensionController', () => { 'Umb.Test.Section.1', async (isPermitted) => { count++; - // We want the controller callback to first fire when conditions are initialized. + // We want the controller callback to first fire when conditions are initialized. [NL] expect(extensionController.manifest?.conditions?.length).to.be.equal(2); expect(extensionController?.manifest?.alias).to.eq('Umb.Test.Section.1'); if (count === 1) { expect(isPermitted).to.be.true; expect(extensionController?.permitted).to.be.true; - // Hack to double check that its two conditions that make up the state: + // Hack to double check that its two conditions that make up the state: [NL] expect( extensionController.getUmbControllers((controller) => (controller as any).permitted).length, ).to.equal(2); } else if (count === 2) { expect(isPermitted).to.be.false; expect(extensionController?.permitted).to.be.false; - // Hack to double check that its two conditions that make up the state, in this case its one, cause we already got the callback when one of the conditions changed. meaning in this split second one is still good: + // Hack to double check that its two conditions that make up the state, in this case its one, cause we already got the callback when one of the conditions changed. meaning in this split second one is still good: [NL] expect( extensionController.getUmbControllers((controller) => (controller as any).permitted).length, ).to.equal(1); // Then we are done: extensionController.destroy(); // End this test. - setTimeout(() => done(), 60); // Lets wait another round of the conditions approve/disapprove, just to see if the destroy stopped the conditions. (60ms, as that should be enough to test that another round does not happen.) + setTimeout(() => done(), 60); // Lets wait another round of the conditions approve/disapprove, just to see if the destroy stopped the conditions. (60ms, as that should be enough to test that another round does not happen.) [NL] } else if (count === 5) { expect(false).to.be.true; // This should not be called. } diff --git a/src/libs/extension-api/controller/base-extension-initializer.controller.ts b/src/libs/extension-api/controller/base-extension-initializer.controller.ts index cf5fcf63b7..d425586bd2 100644 --- a/src/libs/extension-api/controller/base-extension-initializer.controller.ts +++ b/src/libs/extension-api/controller/base-extension-initializer.controller.ts @@ -8,7 +8,10 @@ import type { ManifestWithDynamicConditions, UmbExtensionRegistry, } from '@umbraco-cms/backoffice/extension-api'; -import type { UmbObserverController } from '@umbraco-cms/backoffice/observable-api'; +import { jsonStringComparison, type UmbObserverController } from '@umbraco-cms/backoffice/observable-api'; + +const observeConditionsCtrlAlias = Symbol(); +const observeExtensionsCtrlAlias = Symbol(); /** * This abstract Controller holds the core to manage a single Extension. @@ -73,8 +76,6 @@ export abstract class UmbBaseExtensionInitializer< this.#manifestObserver = this.observe( this.#extensionRegistry.byAlias(this.#alias), (extensionManifest) => { - this.#clearPermittedState(); - this.#manifest = extensionManifest; if (extensionManifest) { if (extensionManifest.overwrites) { if (typeof extensionManifest.overwrites === 'string') { @@ -83,13 +84,15 @@ export abstract class UmbBaseExtensionInitializer< this.#overwrites = extensionManifest.overwrites; } } - this.#gotManifest(); + this.#gotManifest(extensionManifest); } else { + this.#manifest = undefined; + this.#clearPermittedState(); this.#overwrites = []; this.#cleanConditions(); } }, - '_observeExtensionManifest', + observeExtensionsCtrlAlias, ); } @@ -107,19 +110,22 @@ export abstract class UmbBaseExtensionInitializer< if (this.#conditionControllers === undefined || this.#conditionControllers.length === 0) return; this.#conditionControllers.forEach((controller) => controller.destroy()); this.#conditionControllers = []; - this.removeUmbControllerByAlias('_observeConditions'); + this.removeUmbControllerByAlias(observeConditionsCtrlAlias); } - #gotManifest() { - if (!this.#manifest) return; - const conditionConfigs = this.#manifest.conditions ?? []; + #gotManifest(manifest: ManifestType) { + const conditionConfigs = manifest.conditions ?? []; + const oldManifest = this.#manifest; + this.#manifest = manifest; - // As conditionConfigs might have been configured as something else than an array, then we ignorer them. + /* + // As conditionConfigs might have been configured as something else than an array, then we ignorer them. [NL] if (conditionConfigs.length === 0) { this.#cleanConditions(); this.#onConditionsChangedCallback(); return; } + */ const conditionAliases = conditionConfigs .map((condition) => condition.alias) @@ -130,32 +136,39 @@ export abstract class UmbBaseExtensionInitializer< this.#conditionControllers = this.#conditionControllers.filter((current) => { const continueExistence = conditionConfigs.find((config) => config === current.config); if (!continueExistence) { - // Destroy condition that is no longer needed. + // Destroy condition that is no longer needed. [NL] current.destroy(); } return continueExistence; }); - // Check if there was no change in conditions: - // First check if any got removed(old amount equal controllers after clean-up) - // && check if any new is about to be added(old equal new amount): - const noChangeInConditions = - oldAmountOfControllers === this.#conditionControllers.length && - oldAmountOfControllers === conditionConfigs.length; - if (conditionConfigs.length > 0) { - // Observes the conditions and initialize as they come in. + // Observes the conditions and initialize as they come in. [NL] this.observe( this.#extensionRegistry.byTypeAndAliases('condition', conditionAliases), this.#gotConditions, - '_observeConditions', + observeConditionsCtrlAlias, ); } else { - this.removeUmbControllerByAlias('_observeConditions'); + this.removeUmbControllerByAlias(observeConditionsCtrlAlias); } - if (noChangeInConditions) { - // There was not change in the amount of conditions, but the manifest was changed, this means this.#isPermitted is set to undefined and this will always fire the callback: + // If permitted we want to fire an update because we got a new manifest. [NL] + if (this.#isPermitted) { + // Check if there was no change in conditions: + // First check if any got removed(old amount equal controllers after clean-up) + // && check if any new is about to be added(old equal new amount): [NL] + // The reason for this is because we will get an update via the code above if there is a change in conditions. But if not we will trigger it here [NL] + const noChangeInConditions = + oldAmountOfControllers === this.#conditionControllers.length && + oldAmountOfControllers === conditionConfigs.length; + if (noChangeInConditions) { + if (jsonStringComparison(oldManifest, manifest) === false) { + // There was not change in the amount of conditions, but the manifest was changed, this means this.#isPermitted is set to undefined and this will always fire the callback: [NL] + this.#onPermissionChanged?.(this.#isPermitted, this as any); + } + } + } else { this.#onConditionsChangedCallback(); } } @@ -189,9 +202,9 @@ export abstract class UmbBaseExtensionInitializer< newConditionControllers .filter((x) => x !== undefined) .forEach((emerging) => { - // TODO: All of this could use a refactor at one point, when someone is fresh in their mind. - // Niels Notes: Current problem being that we are not aware about what is in the making, so we don't know if we end up creating the same condition multiple times. - // Because it took some time to create the conditions, it maybe have already gotten created by another cycle, so lets test again. + // TODO: All of this could use a refactor at one point, when someone is fresh in their mind. [NL] + // Niels Notes: Current problem being that we are not aware about what is in the making, so we don't know if we end up creating the same condition multiple times. [NL] + // Because it took some time to create the conditions, it maybe have already gotten created by another cycle, so lets test again. [NL] const existing = this.#conditionControllers.find((existing) => existing.config === emerging?.config); if (!existing) { this.#conditionControllers.push(emerging!); @@ -200,7 +213,7 @@ export abstract class UmbBaseExtensionInitializer< } }); - // If a change to amount of condition controllers, this will make sure that when new conditions are added, the callback is fired, so the extensions can be re-evaluated, starting out as bad. + // If a change to amount of condition controllers, this will make sure that when new conditions are added, the callback is fired, so the extensions can be re-evaluated, starting out as bad. [NL] if (oldLength !== this.#conditionControllers.length) { this.#onConditionsChangedCallback(); } @@ -230,14 +243,14 @@ export abstract class UmbBaseExtensionInitializer< #conditionsAreInitialized() { // Not good if we don't have a manifest. - // Only good if conditions of manifest is equal to the amount of condition controllers (one for each condition). + // Only good if conditions of manifest is equal to the amount of condition controllers (one for each condition). [NL] return ( this.#manifest !== undefined && this.#conditionControllers.length === (this.#manifest.conditions ?? []).length ); } #onConditionsChangedCallback = async () => { - // 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. + // 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] let oldValue = this.#isPermitted ?? false; // Find a condition that is not permitted (Notice how no conditions, means that this extension is permitted) @@ -250,13 +263,13 @@ export abstract class UmbBaseExtensionInitializer< if (isPositive === true) { if (this.#isPermitted !== true) { const newPermission = await this._conditionsAreGood(); - // Only set new permission if we are still positive, otherwise it means that we have been destroyed in the mean time. + // Only set new permission if we are still positive, otherwise it means that we have been destroyed in the mean time. [NL] if (newPermission === false || this._isConditionsPositive === false) { // Then we need to revert the above work: this._conditionsAreBad(); return; } - // 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. + // 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] oldValue = this.#isPermitted ?? false; this.#isPermitted = newPermission; } @@ -264,11 +277,11 @@ export abstract class UmbBaseExtensionInitializer< // Clean up: await this._conditionsAreBad(); - // Only continue if we are still negative, otherwise it means that something changed in the mean time. + // Only continue if we are still negative, otherwise it means that something changed in the mean time. [NL] if (this._isConditionsPositive === true) { return; } - // 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. + // 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] oldValue = this.#isPermitted ?? false; this.#isPermitted = false; } @@ -296,6 +309,7 @@ export abstract class UmbBaseExtensionInitializer< } */ + /* public override hostDisconnected(): void { super.hostDisconnected(); this._isConditionsPositive = false; @@ -305,6 +319,7 @@ export abstract class UmbBaseExtensionInitializer< this.#onPermissionChanged?.(false, this as unknown as SubClassType); } } + */ #clearPermittedState() { if (this.#isPermitted === true) { @@ -318,7 +333,7 @@ export abstract class UmbBaseExtensionInitializer< if (!this.#extensionRegistry) return; this.#manifest = undefined; this.#promiseResolvers = []; - this.#clearPermittedState(); // This fires the callback as not permitted, if it was permitted before. + this.#clearPermittedState(); // This fires the callback as not permitted, if it was permitted before. [NL] this.#isPermitted = undefined; this._isConditionsPositive = false; this.#overwrites = []; diff --git a/src/libs/extension-api/controller/base-extensions-initializer.controller.test.ts b/src/libs/extension-api/controller/base-extensions-initializer.controller.test.ts index abcf3326db..2b12443731 100644 --- a/src/libs/extension-api/controller/base-extensions-initializer.controller.test.ts +++ b/src/libs/extension-api/controller/base-extensions-initializer.controller.test.ts @@ -13,6 +13,8 @@ import { customElement, html } from '@umbraco-cms/backoffice/external/lit'; class UmbTestControllerHostElement extends UmbControllerHostElementMixin(HTMLElement) {} class UmbTestExtensionController extends UmbBaseExtensionInitializer { + testInsidesIsDestroyed?: boolean; + constructor( host: UmbControllerHost, extensionRegistry: UmbExtensionRegistry, @@ -28,7 +30,8 @@ class UmbTestExtensionController extends UmbBaseExtensionInitializer { } protected async _conditionsAreBad() { - // Destroy the element/class. + // Destroy the element/class. (or in the case of this test, then we just set a flag) [NL] + this.testInsidesIsDestroyed = true; } } @@ -111,7 +114,7 @@ describe('UmbBaseExtensionsController', () => { it('exposes both manifests', (done) => { let count = 0; - const extensionController = new UmbTestExtensionsController( + const extensionsInitController = new UmbTestExtensionsController( hostElement, testExtensionRegistry, 'extension-type', @@ -120,7 +123,7 @@ describe('UmbBaseExtensionsController', () => { count++; if (count === 1) { expect(permitted.length).to.eq(2); - extensionController.destroy(); + extensionsInitController.destroy(); } else if (count === 2) { done(); } @@ -138,7 +141,7 @@ describe('UmbBaseExtensionsController', () => { testExtensionRegistry.register(manifestExtra); let count = 0; - const extensionController = new UmbTestExtensionsController( + const extensionsInitController = new UmbTestExtensionsController( hostElement, testExtensionRegistry, ['extension-type', 'extension-type-extra'], @@ -151,9 +154,9 @@ describe('UmbBaseExtensionsController', () => { expect(permitted[1].alias).to.eq('Umb.Test.Extension.B'); expect(permitted[2].alias).to.eq('Umb.Test.Extension.Extra'); - extensionController.destroy(); + extensionsInitController.destroy(); } else if (count === 2) { - // Cause we destroyed there will be a last call to reset permitted controllers: + // Cause we destroyed there will be a last call to reset permitted controllers: [NL] expect(permitted.length).to.eq(0); done(); } @@ -189,7 +192,7 @@ describe('UmbBaseExtensionsController', () => { it('exposes just one manifests', (done) => { let count = 0; - const extensionController = new UmbTestExtensionsController( + const extensionsInitController = new UmbTestExtensionsController( hostElement, testExtensionRegistry, 'extension-type', @@ -197,17 +200,17 @@ describe('UmbBaseExtensionsController', () => { (permitted) => { count++; if (count === 1) { - // Still just equal one, as the second one overwrites the first one. + // Still just equal one, as the second one overwrites the first one. [NL] expect(permitted.length).to.eq(1); expect(permitted[0].alias).to.eq('Umb.Test.Extension.B'); - // lets remove the overwriting extension to see the original coming back. + // lets remove the overwriting extension to see the original coming back. [NL] testExtensionRegistry.unregister('Umb.Test.Extension.B'); } else if (count === 2) { expect(permitted.length).to.eq(1); expect(permitted[0].alias).to.eq('Umb.Test.Extension.A'); done(); - extensionController.destroy(); + extensionsInitController.destroy(); } }, ); @@ -255,7 +258,8 @@ describe('UmbBaseExtensionsController', () => { it('exposes only the overwriting manifest', (done) => { let count = 0; - const extensionController = new UmbTestExtensionsController( + let lastPermitted: PermittedControllerType[] = []; + const extensionsInitController = new UmbTestExtensionsController( hostElement, testExtensionRegistry, 'extension-type', @@ -263,24 +267,30 @@ describe('UmbBaseExtensionsController', () => { (permitted) => { count++; if (count === 1) { - // Still just equal one, as the second one overwrites the first one. + // Still just equal one, as the second one overwrites the first one. [NL] expect(permitted.length).to.eq(1); expect(permitted[0].alias).to.eq('Umb.Test.Extension.B'); - // lets remove the overwriting extension to see the original coming back. + // lets remove the overwriting extension to see the original coming back. [NL] testExtensionRegistry.unregister('Umb.Test.Extension.B'); } else if (count === 2) { expect(permitted.length).to.eq(1); expect(permitted[0].alias).to.eq('Umb.Test.Extension.A'); - extensionController.destroy(); + // CHecks that the controller that got overwritten is destroyed. [NL] + expect(lastPermitted[0].testInsidesIsDestroyed).to.be.true; + // Then continue the test and destroy the initializer. [NL] + extensionsInitController.destroy(); + // And then checks that the controller is destroyed. [NL] + expect(permitted[0].testInsidesIsDestroyed).to.be.true; } else if (count === 3) { - // Expect that destroy will only result in one last callback with no permitted controllers. + // Expect that destroy will only result in one last callback with no permitted controllers. [NL] expect(permitted.length).to.eq(0); - Promise.resolve().then(() => done()); // This wrap is to enable the test to detect if more callbacks are fired. + Promise.resolve().then(() => done()); // This wrap is to enable the test to detect if more callbacks are fired. [NL] } else if (count === 4) { - // This should not happen, we do only want one last callback when destroyed. + // This should not happen, we do only want one last callback when destroyed. [NL] expect(false).to.eq(true); } + lastPermitted = permitted; }, ); }); @@ -308,7 +318,7 @@ describe('UmbBaseExtensionsController', () => { ], }; - // Register opposite order, to ensure B is there when A comes around. A fix to be able to test this. Cause a late registration of B would not cause a change that is test able. + // Register opposite order, to ensure B is there when A comes around. A fix to be able to test this. Cause a late registration of B would not cause a change that is test able. [NL] testExtensionRegistry.register(manifestB); testExtensionRegistry.register(manifestA); testExtensionRegistry.register({ @@ -329,7 +339,7 @@ describe('UmbBaseExtensionsController', () => { it('exposes only the original manifest', (done) => { let count = 0; - const extensionController = new UmbTestExtensionsController( + const extensionsInitController = new UmbTestExtensionsController( hostElement, testExtensionRegistry, 'extension-type', @@ -338,11 +348,11 @@ describe('UmbBaseExtensionsController', () => { count++; if (count === 1) { - // First callback gives just one. We need to make a feature to gather changes to only reply after a computation cycle if we like to avoid this. + // First callback gives just one. We need to make a feature to gather changes to only reply after a computation cycle if we like to avoid this. [NL] expect(permitted.length).to.eq(1); expect(permitted[0].alias).to.eq('Umb.Test.Extension.A'); done(); - extensionController.destroy(); + extensionsInitController.destroy(); } }, ); From 099483d09d8c299780391be20c1285b25dc3cb51 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Tue, 5 Nov 2024 20:40:10 +0100 Subject: [PATCH 08/17] update style --- .../block-grid-block-inline.element.ts | 13 +++++++++++++ .../inline-list-block/inline-list-block.element.ts | 13 +++++++++++++ 2 files changed, 26 insertions(+) diff --git a/src/packages/block/block-grid/components/block-grid-block-inline/block-grid-block-inline.element.ts b/src/packages/block/block-grid/components/block-grid-block-inline/block-grid-block-inline.element.ts index d532cd46a8..0145768cc8 100644 --- a/src/packages/block/block-grid/components/block-grid-block-inline/block-grid-block-inline.element.ts +++ b/src/packages/block/block-grid/components/block-grid-block-inline/block-grid-block-inline.element.ts @@ -277,6 +277,19 @@ export class UmbBlockGridBlockInlineElement extends UmbLitElement { margin-right: var(--uui-size-1); } + #info { + display: flex; + flex-direction: column; + align-items: start; + justify-content: center; + height: 100%; + padding-left: var(--uui-size-2, 6px); + } + + #name { + font-weight: 700; + } + :host(:not([disabled])) #open-part:hover #icon { color: var(--uui-color-interactive-emphasis); } diff --git a/src/packages/block/block-list/components/inline-list-block/inline-list-block.element.ts b/src/packages/block/block-list/components/inline-list-block/inline-list-block.element.ts index b0685a4828..f6749f63d3 100644 --- a/src/packages/block/block-list/components/inline-list-block/inline-list-block.element.ts +++ b/src/packages/block/block-list/components/inline-list-block/inline-list-block.element.ts @@ -271,6 +271,19 @@ export class UmbInlineListBlockElement extends UmbLitElement { margin-right: var(--uui-size-1); } + #info { + display: flex; + flex-direction: column; + align-items: start; + justify-content: center; + height: 100%; + padding-left: var(--uui-size-2, 6px); + } + + #name { + font-weight: 700; + } + :host(:not([disabled])) #open-part:hover #icon { color: var(--uui-color-interactive-emphasis); } From 54f6b1b75e99b5df73203ab7b21857ff17557778 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Tue, 5 Nov 2024 20:52:15 +0100 Subject: [PATCH 09/17] temp removal of umbDestroyOnDisconnect --- .../block-grid-entry/block-grid-entry.element.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/packages/block/block-grid/components/block-grid-entry/block-grid-entry.element.ts b/src/packages/block/block-grid/components/block-grid-entry/block-grid-entry.element.ts index b46881743f..70cad07654 100644 --- a/src/packages/block/block-grid/components/block-grid-entry/block-grid-entry.element.ts +++ b/src/packages/block/block-grid/components/block-grid-entry/block-grid-entry.element.ts @@ -414,8 +414,8 @@ export class UmbBlockGridEntryElement extends UmbLitElement implements UmbProper .unpublished=${!this._exposed} .config=${this._blockViewProps.config} .content=${this._blockViewProps.content} - .settings=${this._blockViewProps.settings} - ${umbDestroyOnDisconnect()}>`; + .settings=${this._blockViewProps.settings}>`; + //TODO: investigate if we should have ${umbDestroyOnDisconnect()} here. Note how it works for drag n' drop in grid between areas and areas-root } #renderRefBlock() { @@ -426,8 +426,8 @@ export class UmbBlockGridEntryElement extends UmbLitElement implements UmbProper .unpublished=${!this._exposed} .config=${this._blockViewProps.config} .content=${this._blockViewProps.content} - .settings=${this._blockViewProps.settings} - ${umbDestroyOnDisconnect()}>`; + .settings=${this._blockViewProps.settings}>`; + //TODO: investigate if we should have ${umbDestroyOnDisconnect()} here. Note how it works for drag n' drop in grid between areas and areas-root } #renderBlock() { From b42ab48d18d8db00157bfbb07ae0880374fa6ff6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Tue, 5 Nov 2024 21:04:13 +0100 Subject: [PATCH 10/17] remove unused import --- .../components/block-grid-entry/block-grid-entry.element.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/packages/block/block-grid/components/block-grid-entry/block-grid-entry.element.ts b/src/packages/block/block-grid/components/block-grid-entry/block-grid-entry.element.ts index 70cad07654..c7f3811fa5 100644 --- a/src/packages/block/block-grid/components/block-grid-entry/block-grid-entry.element.ts +++ b/src/packages/block/block-grid/components/block-grid-entry/block-grid-entry.element.ts @@ -1,5 +1,5 @@ import { UmbBlockGridEntryContext } from '../../context/block-grid-entry.context.js'; -import { UmbLitElement, umbDestroyOnDisconnect } from '@umbraco-cms/backoffice/lit-element'; +import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; import { html, css, customElement, property, state, nothing } from '@umbraco-cms/backoffice/external/lit'; import type { PropertyValueMap } from '@umbraco-cms/backoffice/external/lit'; import type { UmbPropertyEditorUiElement } from '@umbraco-cms/backoffice/property-editor'; From 74119ea36e2b85c43d7598d5b0aa9ec6e7be53fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Tue, 5 Nov 2024 22:17:33 +0100 Subject: [PATCH 11/17] fix infinite layout update --- .../controller/base-extension-initializer.controller.ts | 7 ++++++- .../block/block/workspace/block-workspace.context.ts | 5 +++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/libs/extension-api/controller/base-extension-initializer.controller.ts b/src/libs/extension-api/controller/base-extension-initializer.controller.ts index d425586bd2..52cff378b7 100644 --- a/src/libs/extension-api/controller/base-extension-initializer.controller.ts +++ b/src/libs/extension-api/controller/base-extension-initializer.controller.ts @@ -250,6 +250,11 @@ export abstract class UmbBaseExtensionInitializer< } #onConditionsChangedCallback = async () => { + if (this.#manifest === undefined) { + // This is cause by this controller begin destroyed in the mean time. [NL] + // When writing this the only plausible case is a call from the conditionController to the onChange callback. + return; + } // 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] let oldValue = this.#isPermitted ?? false; @@ -299,7 +304,7 @@ export abstract class UmbBaseExtensionInitializer< protected abstract _conditionsAreBad(): Promise; public equal(otherClass: UmbBaseExtensionInitializer | undefined): boolean { - return otherClass?.manifest === this.manifest; + return otherClass?.manifest === this.#manifest; } /* diff --git a/src/packages/block/block/workspace/block-workspace.context.ts b/src/packages/block/block/workspace/block-workspace.context.ts index 31cf151c32..1d1827445b 100644 --- a/src/packages/block/block/workspace/block-workspace.context.ts +++ b/src/packages/block/block/workspace/block-workspace.context.ts @@ -348,10 +348,15 @@ export class UmbBlockWorkspaceContext { if (layoutData) { + if (initialLayoutSet) { + initialLayoutSet = false; + return; + } this.#blockManager?.setOneLayout( layoutData, this.#modalContext?.data.originData as UmbBlockWorkspaceOriginData, From b74ad48097d684a4026ec175b6e220a234bfd576 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Wed, 6 Nov 2024 13:01:15 +0100 Subject: [PATCH 12/17] make host optionally be a method --- .../consume/context-consumer.controller.ts | 2 +- .../consume/context-consumer.test.ts | 45 ++++++++++++++++++- .../context-api/consume/context-consumer.ts | 16 ++++--- ...e-extension-initializer.controller.test.ts | 7 +-- 4 files changed, 59 insertions(+), 11 deletions(-) diff --git a/src/libs/context-api/consume/context-consumer.controller.ts b/src/libs/context-api/consume/context-consumer.controller.ts index 892264047d..573cb5aadd 100644 --- a/src/libs/context-api/consume/context-consumer.controller.ts +++ b/src/libs/context-api/consume/context-consumer.controller.ts @@ -19,7 +19,7 @@ export class UmbContextConsumerController, callback?: UmbContextCallback, ) { - super(host.getHostElement(), contextAlias, callback); + super(() => host.getHostElement(), contextAlias, callback); this.#host = host; host.addUmbController(this); } diff --git a/src/libs/context-api/consume/context-consumer.test.ts b/src/libs/context-api/consume/context-consumer.test.ts index e45ea4170a..fdb78164b9 100644 --- a/src/libs/context-api/consume/context-consumer.test.ts +++ b/src/libs/context-api/consume/context-consumer.test.ts @@ -7,7 +7,7 @@ import { expect, oneEvent } from '@open-wc/testing'; const testContextAlias = 'my-test-context'; const testContextAliasAndApiAlias = 'my-test-context#testApi'; -const testContextAliasAndNotExstingApiAlias = 'my-test-context#notExistingTestApi'; +const testContextAliasAndNotExistingApiAlias = 'my-test-context#notExistingTestApi'; class UmbTestContextConsumerClass { public prop: string = 'value from provider'; @@ -74,6 +74,47 @@ describe('UmbContextConsumer', () => { localConsumer.hostConnected(); }); + it('works with host as a method', (done) => { + const provider = new UmbContextProvider(document.body, testContextAlias, new UmbTestContextConsumerClass()); + provider.hostConnected(); + + const element = document.createElement('div'); + document.body.appendChild(element); + + const localConsumer = new UmbContextConsumer( + () => element, + testContextAlias, + (_instance: UmbTestContextConsumerClass | undefined) => { + if (_instance) { + expect(_instance.prop).to.eq('value from provider'); + done(); + localConsumer.hostDisconnected(); + provider.hostDisconnected(); + } + }, + ); + localConsumer.hostConnected(); + }); + + it('works with host method returning undefined', async () => { + const element = undefined; + + const localConsumer = new UmbContextConsumer( + () => element, + testContextAlias, + (_instance: UmbTestContextConsumerClass | undefined) => { + if (_instance) { + expect.fail('Callback should not be called when never permitted'); + } + }, + ); + localConsumer.hostConnected(); + + await Promise.resolve(); + + localConsumer.hostDisconnected(); + }); + /* Unprovided feature is out commented currently. I'm not sure there is a use case. So lets leave the code around until we know for sure. it('acts to Context API disconnected', (done) => { @@ -139,7 +180,7 @@ describe('UmbContextConsumer', () => { const element = document.createElement('div'); document.body.appendChild(element); - const localConsumer = new UmbContextConsumer(element, testContextAliasAndNotExstingApiAlias, () => { + const localConsumer = new UmbContextConsumer(element, testContextAliasAndNotExistingApiAlias, () => { expect(false).to.be.true; }); localConsumer.hostConnected(); diff --git a/src/libs/context-api/consume/context-consumer.ts b/src/libs/context-api/consume/context-consumer.ts index 2805353c35..0230a3f1fd 100644 --- a/src/libs/context-api/consume/context-consumer.ts +++ b/src/libs/context-api/consume/context-consumer.ts @@ -3,11 +3,13 @@ import { isUmbContextProvideEventType, UMB_CONTEXT_PROVIDE_EVENT_TYPE } from '.. import type { UmbContextCallback } from './context-request.event.js'; import { UmbContextRequestEventImplementation } from './context-request.event.js'; +type HostElementMethod = () => Element | undefined; + /** * @class UmbContextConsumer */ export class UmbContextConsumer { - protected _host: Element; + protected _retrieveHost: HostElementMethod; #skipHost?: boolean; #stopAtContextMatch = true; @@ -33,11 +35,15 @@ export class UmbContextConsumer, callback?: UmbContextCallback, ) { - this._host = host; + if (typeof host === 'function') { + this._retrieveHost = host; + } else { + this._retrieveHost = () => host; + } const idSplit = contextIdentifier.toString().split('#'); this.#contextAlias = idSplit[0]; this.#apiAlias = idSplit[1] ?? 'default'; @@ -130,7 +136,7 @@ export class UmbContextConsumer { 'Umb.Test.Section.1', () => { // This should not be called. - expect(true).to.be.false; + expect.fail('Callback should not be called when never permitted'); }, ); Promise.resolve().then(() => { @@ -448,7 +448,7 @@ describe('UmbBaseExtensionController', () => { 'Umb.Test.Section.1', () => { // This should not be called. - expect(true).to.be.false; + expect.fail('Callback should not be called when never permitted'); }, ); @@ -615,7 +615,8 @@ describe('UmbBaseExtensionController', () => { extensionController.destroy(); // End this test. setTimeout(() => done(), 60); // Lets wait another round of the conditions approve/disapprove, just to see if the destroy stopped the conditions. (60ms, as that should be enough to test that another round does not happen.) [NL] } else if (count === 5) { - expect(false).to.be.true; // This should not be called. + // This should not be called. + expect.fail('Callback should not be called when never permitted'); } }, ); From a828eb9f668f82ef789f7c8a0e7a4d9210e599cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Wed, 6 Nov 2024 13:24:18 +0100 Subject: [PATCH 13/17] update JSDocs --- .../observable-api/utils/push-at-to-unique-array.function.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/libs/observable-api/utils/push-at-to-unique-array.function.ts b/src/libs/observable-api/utils/push-at-to-unique-array.function.ts index a46b329639..f31ab8c332 100644 --- a/src/libs/observable-api/utils/push-at-to-unique-array.function.ts +++ b/src/libs/observable-api/utils/push-at-to-unique-array.function.ts @@ -5,6 +5,7 @@ * @param {T} entry - The object to insert or replace with. * @param {getUniqueMethod: (entry: T) => unknown} [getUniqueMethod] - Method to get the unique value of an entry. * @description - Append or replaces an item of an Array. + * @returns {T[]} - Returns a new array with the updated entry. * @example Example append new entry for a Array. Where the key is unique and the item will be updated if matched with existing. * const entry = {key: 'myKey', value: 'myValue'}; * const newDataSet = pushToUniqueArray([], entry, x => x.key === key, 1); From 24c9902db8f908429878ed4ef967fef8873089df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Wed, 6 Nov 2024 13:42:45 +0100 Subject: [PATCH 14/17] give block workspace origin data to work from --- .../block-grid-block-inline.element.ts | 15 ++++++ .../context/block-grid-entries.context.ts | 21 +++++++- .../workspace/block-workspace.context.ts | 52 ++++++++----------- 3 files changed, 55 insertions(+), 33 deletions(-) diff --git a/src/packages/block/block-grid/components/block-grid-block-inline/block-grid-block-inline.element.ts b/src/packages/block/block-grid/components/block-grid-block-inline/block-grid-block-inline.element.ts index 0145768cc8..38a103f3a2 100644 --- a/src/packages/block/block-grid/components/block-grid-block-inline/block-grid-block-inline.element.ts +++ b/src/packages/block/block-grid/components/block-grid-block-inline/block-grid-block-inline.element.ts @@ -1,4 +1,6 @@ import { UMB_BLOCK_GRID_ENTRY_CONTEXT } from '../../context/block-grid-entry.context-token.js'; +import type { UmbBlockGridWorkspaceOriginData } from '../../workspace/block-grid-workspace.modal-token.js'; +import { UMB_BLOCK_GRID_ENTRIES_CONTEXT } from '../../context/block-grid-entries.context-token.js'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; import { css, customElement, html, property, state } from '@umbraco-cms/backoffice/external/lit'; import type { UmbPropertyTypeModel } from '@umbraco-cms/backoffice/content-type'; @@ -32,6 +34,8 @@ export class UmbBlockGridBlockInlineElement extends UmbLitElement { #blockContext?: typeof UMB_BLOCK_GRID_ENTRY_CONTEXT.TYPE; #workspaceContext?: typeof UMB_BLOCK_WORKSPACE_CONTEXT.TYPE; #contentKey?: string; + #parentUnique?: string | null; + #areaKey?: string | null; @property({ attribute: false }) config?: UmbBlockEditorCustomViewConfiguration; @@ -71,6 +75,10 @@ export class UmbBlockGridBlockInlineElement extends UmbLitElement { 'observeContentKey', ); }); + this.consumeContext(UMB_BLOCK_GRID_ENTRIES_CONTEXT, (entriesContext) => { + this.#parentUnique = entriesContext.getParentUnique(); + this.#areaKey = entriesContext.getAreaKey(); + }); new UmbExtensionApiInitializer( this, umbExtensionsRegistry, @@ -79,7 +87,14 @@ export class UmbBlockGridBlockInlineElement extends UmbLitElement { (permitted, ctrl) => { const context = ctrl.api as typeof UMB_BLOCK_WORKSPACE_CONTEXT.TYPE; if (permitted && context) { + if (this.#parentUnique === undefined || this.#areaKey === undefined) { + throw new Error('Parent unique and area key must be defined'); + } this.#workspaceContext = context; + context.setOriginData({ + areaKey: this.#areaKey, + parentUnique: this.#parentUnique, + } as UmbBlockGridWorkspaceOriginData); this.#workspaceContext.establishLiveSync(); this.#load(); diff --git a/src/packages/block/block-grid/context/block-grid-entries.context.ts b/src/packages/block/block-grid/context/block-grid-entries.context.ts index b69f1a2853..1850c2dd14 100644 --- a/src/packages/block/block-grid/context/block-grid-entries.context.ts +++ b/src/packages/block/block-grid/context/block-grid-entries.context.ts @@ -99,6 +99,10 @@ export class UmbBlockGridEntriesContext this.#catalogueModal.setUniquePathValue('parentUnique', pathFolderName(contentKey ?? 'null')); } + getParentUnique(): string | null | undefined { + return this.#parentUnique; + } + setAreaKey(areaKey: string | null) { this.#areaKey = areaKey; this.#workspaceModal.setUniquePathValue('areaKey', areaKey ?? 'null'); @@ -110,6 +114,10 @@ export class UmbBlockGridEntriesContext // If not, we want to set the layoutDataPath to a base one. } + getAreaKey(): string | null | undefined { + return this.#areaKey; + } + setLayoutColumns(columns: number | undefined) { this.#layoutColumns.setValue(columns); } @@ -157,7 +165,11 @@ export class UmbBlockGridEntriesContext blocks: this.#allowedBlockTypes.getValue(), blockGroups: this._manager.getBlockGroups() ?? [], openClipboard: routingInfo.view === 'clipboard', - originData: { index: index, areaKey: this.#areaKey, parentUnique: this.#parentUnique }, + originData: { + index: index, + areaKey: this.#areaKey, + parentUnique: this.#parentUnique, + } as UmbBlockGridWorkspaceOriginData, createBlockInWorkspace: this._manager.getInlineEditingMode() === false, }, }; @@ -195,7 +207,12 @@ export class UmbBlockGridEntriesContext data: { entityType: 'block', preset: {}, - originData: { areaKey: this.#areaKey, parentUnique: this.#parentUnique, baseDataPath: this._dataPath }, + originData: { + index: -1, + areaKey: this.#areaKey, + parentUnique: this.#parentUnique, + baseDataPath: this._dataPath, + } as UmbBlockGridWorkspaceOriginData, }, modal: { size: 'medium' }, }; diff --git a/src/packages/block/block/workspace/block-workspace.context.ts b/src/packages/block/block/workspace/block-workspace.context.ts index 1d1827445b..3794671862 100644 --- a/src/packages/block/block/workspace/block-workspace.context.ts +++ b/src/packages/block/block/workspace/block-workspace.context.ts @@ -16,13 +16,12 @@ import { observeMultiple, } from '@umbraco-cms/backoffice/observable-api'; import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; -import { UMB_MODAL_CONTEXT, type UmbModalContext } from '@umbraco-cms/backoffice/modal'; +import { UMB_MODAL_CONTEXT } from '@umbraco-cms/backoffice/modal'; import { decodeFilePath, UmbReadOnlyVariantStateManager } from '@umbraco-cms/backoffice/utils'; import { UMB_BLOCK_ENTRIES_CONTEXT, UMB_BLOCK_MANAGER_CONTEXT, type UmbBlockWorkspaceOriginData, - type UmbBlockWorkspaceData, UMB_BLOCK_ENTRY_CONTEXT, } from '@umbraco-cms/backoffice/block'; import { UmbVariantId } from '@umbraco-cms/backoffice/variant'; @@ -40,7 +39,11 @@ export class UmbBlockWorkspaceContext; + #originData?: UmbBlockWorkspaceOriginData; + // Set the origin data for this workspace. Example used by inline editing which setups the workspace context it self. + setOriginData(data: UmbBlockWorkspaceOriginData) { + this.#originData = data; + } #retrieveModalContext; #entityType: string; @@ -80,7 +83,7 @@ export class UmbBlockWorkspaceContext { - this.#modalContext = context as any; + this.#originData = context?.data.originData; context.onSubmit().catch(this.#modalRejected); }).asPromise(); @@ -238,19 +241,15 @@ export class UmbBlockWorkspaceContext { @@ -357,14 +357,11 @@ export class UmbBlockWorkspaceContext { @@ -435,7 +432,7 @@ export class UmbBlockWorkspaceContext Date: Wed, 6 Nov 2024 13:55:43 +0100 Subject: [PATCH 15/17] update destroy method --- .../block/block/workspace/block-workspace.context.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/packages/block/block/workspace/block-workspace.context.ts b/src/packages/block/block/workspace/block-workspace.context.ts index 3794671862..80ad8c64c6 100644 --- a/src/packages/block/block/workspace/block-workspace.context.ts +++ b/src/packages/block/block/workspace/block-workspace.context.ts @@ -348,7 +348,7 @@ export class UmbBlockWorkspaceContext { @@ -361,7 +361,7 @@ export class UmbBlockWorkspaceContext { @@ -499,9 +499,10 @@ export class UmbBlockWorkspaceContext Date: Wed, 6 Nov 2024 14:05:17 +0100 Subject: [PATCH 16/17] comment on risky business --- .../block-grid-block-inline/block-grid-block-inline.element.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/packages/block/block-grid/components/block-grid-block-inline/block-grid-block-inline.element.ts b/src/packages/block/block-grid/components/block-grid-block-inline/block-grid-block-inline.element.ts index 38a103f3a2..06156a1b8f 100644 --- a/src/packages/block/block-grid/components/block-grid-block-inline/block-grid-block-inline.element.ts +++ b/src/packages/block/block-grid/components/block-grid-block-inline/block-grid-block-inline.element.ts @@ -87,6 +87,7 @@ export class UmbBlockGridBlockInlineElement extends UmbLitElement { (permitted, ctrl) => { const context = ctrl.api as typeof UMB_BLOCK_WORKSPACE_CONTEXT.TYPE; if (permitted && context) { + // Risky business, cause here we are lucky that it seems to be consumed and set before this is called and there for this is acceptable for now. [NL] if (this.#parentUnique === undefined || this.#areaKey === undefined) { throw new Error('Parent unique and area key must be defined'); } From fd176109ffaf2aeb9b71d53709479fec2f5bf339 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Thu, 7 Nov 2024 14:04:32 +0100 Subject: [PATCH 17/17] fix const name --- src/libs/context-api/provide/context-provider.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/libs/context-api/provide/context-provider.ts b/src/libs/context-api/provide/context-provider.ts index 972addee89..56f231a2d2 100644 --- a/src/libs/context-api/provide/context-provider.ts +++ b/src/libs/context-api/provide/context-provider.ts @@ -68,7 +68,7 @@ export class UmbContextProvider