diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/core/components/collection/collection.context.ts b/src/Umbraco.Web.UI.Client/src/backoffice/core/components/collection/collection.context.ts index 85433a378f..a3ff05cd5c 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/core/components/collection/collection.context.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/core/components/collection/collection.context.ts @@ -41,11 +41,11 @@ export class UmbCollectionContext< } connectedCallback() { - this._storeConsumer.attach(); + this._storeConsumer.hostConnected(); } disconnectedCallback() { - this._storeConsumer.detach(); + this._storeConsumer.hostDisconnected(); } public getData() { diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/core/components/entity-property/entity-property.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/core/components/entity-property/entity-property.element.ts index d783c63e53..6203896516 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/core/components/entity-property/entity-property.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/core/components/entity-property/entity-property.element.ts @@ -1,22 +1,26 @@ import { UUITextStyles } from '@umbraco-ui/uui-css/lib'; import { css, html, LitElement, PropertyValueMap } from 'lit'; import { customElement, property, state } from 'lit/decorators.js'; -import { UmbObserverMixin } from '@umbraco-cms/observable-api'; +import { UmbWorkspacePropertyContext } from './workspace-property.context'; import { createExtensionElement } from '@umbraco-cms/extensions-api'; import { umbExtensionsRegistry } from '@umbraco-cms/extensions-registry'; -import { UmbContextConsumerMixin } from '@umbraco-cms/context-api'; import type { ManifestPropertyEditorUI, ManifestTypes } from '@umbraco-cms/models'; import '../../property-actions/shared/property-action-menu/property-action-menu.element'; -import '../workspace/workspace-property-layout/workspace-property-layout.element'; +import 'src/backoffice/core/components/workspace/workspace-property-layout/workspace-property-layout.element'; +import { UmbContextProviderController } from 'src/core/context-api/provide/context-provider.controller'; +import { UmbControllerHostMixin } from 'src/core/controller/controller-host.mixin'; +import { UmbObserverController } from 'src/core/observable-api/observer.controller'; /** * @element umb-entity-property * @description - Component for displaying a entity property. The Element will render a Property Editor based on the Property Editor UI alias passed to the element. * The element will also render all Property Actions related to the Property Editor. */ + +// TODO: get rid of the other mixins: @customElement('umb-entity-property') -export class UmbEntityPropertyElement extends UmbContextConsumerMixin(UmbObserverMixin(LitElement)) { +export class UmbEntityPropertyElement extends UmbControllerHostMixin(LitElement) { static styles = [ UUITextStyles, css` @@ -114,14 +118,26 @@ export class UmbEntityPropertyElement extends UmbContextConsumerMixin(UmbObserve @state() private _element?: { value?: any; config?: any } & HTMLElement; // TODO: invent interface for propertyEditorUI. - connectedCallback(): void { - super.connectedCallback(); + + // TODO: How to get proper default value? + private _propertyContext = new UmbWorkspacePropertyContext(""); + + private propertyEditorUIObserver?: UmbObserverController; + + + constructor() { + super(); + + // TODO: make it easier to create a provider, unless a context should just extends a provider-controller? + new UmbContextProviderController(this, 'umbPropertyContext', this._propertyContext); + this._observePropertyEditorUI(); this.addEventListener('property-editor-change', this._onPropertyEditorChange as any as EventListener); } private _observePropertyEditorUI() { - this.observe(umbExtensionsRegistry.getByAlias(this.propertyEditorUIAlias), (manifest) => { + this.propertyEditorUIObserver?.destroy(); + this.propertyEditorUIObserver = new UmbObserverController(this, umbExtensionsRegistry.getByAlias(this.propertyEditorUIAlias), (manifest) => { if (manifest?.type === 'propertyEditorUI') { this._gotData(manifest); } diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/core/components/entity-property/workspace-property.context.ts b/src/Umbraco.Web.UI.Client/src/backoffice/core/components/entity-property/workspace-property.context.ts new file mode 100644 index 0000000000..6b165db95c --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/backoffice/core/components/entity-property/workspace-property.context.ts @@ -0,0 +1,64 @@ +import { BehaviorSubject, Observable } from "rxjs"; + +export type WorkspacePropertyData = { + alias?: string | null; + label?: string | null; + value?: ValueType | null; +}; + + + +export class UmbWorkspacePropertyContext { + + + private _data: BehaviorSubject>; + public readonly data: Observable>; + + + #defaultValue!: ValueType | null; + + + constructor(defaultValue: ValueType | null) { + + this.#defaultValue = defaultValue; + + // TODO: How do we connect this value with parent context? + // Ensuring the property editor value-property is updated... + + this._data = new BehaviorSubject({value: defaultValue} as WorkspacePropertyData); + this.data = this._data.asObservable(); + } + + /* + hostConnected() { + + } + + hostDisconnected() { + + } + */ + + public getData() { + return this._data.getValue(); + } + + + public update(data: Partial>) { + this._data.next({ ...this.getData(), ...data }); + } + + public resetValue() { + console.log("property context reset") + + this.update({value: this.#defaultValue}) + } + + + // TODO: how can we make sure to call this. + public destroy(): void { + this._data.unsubscribe(); + } + +} + diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/core/components/workspace/workspace-context/workspace-node.context.ts b/src/Umbraco.Web.UI.Client/src/backoffice/core/components/workspace/workspace-context/workspace-node.context.ts index b08411ddd9..78da7fc0c9 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/core/components/workspace/workspace-context/workspace-node.context.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/core/components/workspace/workspace-context/workspace-node.context.ts @@ -1,9 +1,9 @@ import { UmbNotificationService } from '../../../services/notification'; import { UmbNotificationDefaultData } from '../../../services/notification/layouts/default'; -import { UmbWorkspaceWithStoreContext } from './workspace-with-store.context'; import { UmbNodeStoreBase } from 'src/backoffice/core/stores/store'; import { ContentTreeItem } from '@umbraco-cms/backend-api'; import { UmbContextConsumer } from '@umbraco-cms/context-api'; +import { UmbWorkspaceWithStoreContext } from './workspace-with-store.context'; // TODO: Consider if its right to have this many class-inheritance of WorkspaceContext export class UmbWorkspaceNodeContext< @@ -39,12 +39,12 @@ export class UmbWorkspaceNodeContext< connectedCallback() { super.connectedCallback(); - this._notificationConsumer.attach(); + this._notificationConsumer.hostConnected(); } disconnectedCallback() { super.connectedCallback(); - this._notificationConsumer.detach(); + this._notificationConsumer.hostDisconnected(); } protected _onStoreSubscription(): void { @@ -67,3 +67,4 @@ export class UmbWorkspaceNodeContext< }); } } + diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/core/components/workspace/workspace-context/workspace-with-store.context.ts b/src/Umbraco.Web.UI.Client/src/backoffice/core/components/workspace/workspace-context/workspace-with-store.context.ts index b52ad4ffad..b0d08ecde5 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/core/components/workspace/workspace-context/workspace-with-store.context.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/core/components/workspace/workspace-context/workspace-with-store.context.ts @@ -29,11 +29,11 @@ export abstract class UmbWorkspaceWithStoreContext< } connectedCallback() { - this._storeConsumer.attach(); + this._storeConsumer.hostConnected(); } disconnectedCallback() { - this._storeConsumer.detach(); + this._storeConsumer.hostDisconnected(); } protected abstract _onStoreSubscription(): void; @@ -63,8 +63,8 @@ export abstract class UmbWorkspaceWithStoreContext< public destroy(): void { super.destroy(); - if (this._storeConsumer) { - this._storeConsumer.detach(); + if(this._storeConsumer) { + this._storeConsumer.hostDisconnected(); } if (this._dataObserver) { // I want to make sure that we unsubscribe, also if store(observer source) changes. diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/core/dashboards/performance-profiling/dashboard-performance-profiling.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/core/dashboards/performance-profiling/dashboard-performance-profiling.element.ts index 62fcc78cba..88810bce1b 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/core/dashboards/performance-profiling/dashboard-performance-profiling.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/core/dashboards/performance-profiling/dashboard-performance-profiling.element.ts @@ -1,8 +1,8 @@ import { UUITextStyles } from '@umbraco-ui/uui-css/lib'; import { css, html, LitElement } from 'lit'; import { customElement, state } from 'lit/decorators.js'; +import { UmbResourceController } from '@umbraco-cms/resources'; import { ProfilingResource } from '@umbraco-cms/backend-api'; -import { UmbResourceController } from '@umbraco-cms/controllers'; @customElement('umb-dashboard-performance-profiling') export class UmbDashboardPerformanceProfilingElement extends LitElement { diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/core/property-editors/uis/textarea/property-editor-ui-textarea.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/core/property-editors/uis/textarea/property-editor-ui-textarea.element.ts index 595ccadce4..4a708ee347 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/core/property-editors/uis/textarea/property-editor-ui-textarea.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/core/property-editors/uis/textarea/property-editor-ui-textarea.element.ts @@ -1,9 +1,12 @@ import { css, html, LitElement } from 'lit'; import { UUITextStyles } from '@umbraco-ui/uui-css/lib'; import { customElement, property } from 'lit/decorators.js'; +import { UmbControllerHostMixin } from 'src/core/controller/controller-host.mixin'; +import { UmbContextConsumerController } from 'src/core/context-api/consume/context-consumer.controller'; +import { UmbWorkspacePropertyContext } from 'src/backoffice/core/components/entity-property/workspace-property.context'; @customElement('umb-property-editor-ui-textarea') -export class UmbPropertyEditorUITextareaElement extends LitElement { +export class UmbPropertyEditorUITextareaElement extends UmbControllerHostMixin(LitElement) { static styles = [ UUITextStyles, css` @@ -19,14 +22,26 @@ export class UmbPropertyEditorUITextareaElement extends LitElement { @property({ type: Array, attribute: false }) config = []; + private propertyContext?: UmbWorkspacePropertyContext; + + constructor() { + super(); + + new UmbContextConsumerController(this, 'umbPropertyContext', (instance) => { + this.propertyContext = instance; + }); + } + private onInput(e: InputEvent) { this.value = (e.target as HTMLInputElement).value; this.dispatchEvent(new CustomEvent('property-editor-change', { bubbles: true, composed: true })); } render() { - return html` - ${this.config?.map((property: any) => html`
${property.alias}: ${property.value}
`)} `; + return html` + + ${this.config?.map((property: any) => html`
${property.alias}: ${property.value}
`)} + `; } } diff --git a/src/Umbraco.Web.UI.Client/src/core/context-api/consume/context-consumer.controller.ts b/src/Umbraco.Web.UI.Client/src/core/context-api/consume/context-consumer.controller.ts new file mode 100644 index 0000000000..831127dd01 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/core/context-api/consume/context-consumer.controller.ts @@ -0,0 +1,14 @@ +import { UmbContextConsumer } from './context-consumer'; +import { UmbContextCallback } from './context-request.event'; +import type { UmbController } from 'src/core/controller/controller.interface'; +import { UmbControllerHostInterface } from 'src/core/controller/controller-host.mixin'; + + +export class UmbContextConsumerController extends UmbContextConsumer implements UmbController { + + constructor(host:UmbControllerHostInterface, contextAlias: string, callback: UmbContextCallback) { + super(host, contextAlias, callback); + host.addController(this); + } + +} \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/core/context-api/consume/context-consumer.mixin.ts b/src/Umbraco.Web.UI.Client/src/core/context-api/consume/context-consumer.mixin.ts index 343b4aecba..63e44ead40 100644 --- a/src/Umbraco.Web.UI.Client/src/core/context-api/consume/context-consumer.mixin.ts +++ b/src/Umbraco.Web.UI.Client/src/core/context-api/consume/context-consumer.mixin.ts @@ -74,7 +74,7 @@ export const UmbContextConsumerMixin = (superC } if (this._attached) { - consumer.attach(); + consumer.hostConnected(); } }); } @@ -84,13 +84,13 @@ export const UmbContextConsumerMixin = (superC connectedCallback() { super.connectedCallback?.(); this._attached = true; - this._consumers.forEach((consumers) => consumers.forEach((consumer) => consumer.attach())); + this._consumers.forEach((consumers) => consumers.forEach((consumer) => consumer.hostConnected())); } disconnectedCallback() { super.disconnectedCallback?.(); this._attached = false; - this._consumers.forEach((consumers) => consumers.forEach((consumer) => consumer.detach())); + this._consumers.forEach((consumers) => consumers.forEach((consumer) => consumer.hostDisconnected())); this._resolved.clear(); } diff --git a/src/Umbraco.Web.UI.Client/src/core/context-api/consume/context-consumer.test.ts b/src/Umbraco.Web.UI.Client/src/core/context-api/consume/context-consumer.test.ts index 03da974bdf..07d32cf870 100644 --- a/src/Umbraco.Web.UI.Client/src/core/context-api/consume/context-consumer.test.ts +++ b/src/Umbraco.Web.UI.Client/src/core/context-api/consume/context-consumer.test.ts @@ -28,7 +28,7 @@ describe('UmbContextConsumer', () => { it('dispatches context request event when constructed', async () => { const listener = oneEvent(window, umbContextRequestEventType); - consumer.attach(); + consumer.hostConnected(); const event = (await listener) as unknown as UmbContextRequestEventImplementation; expect(event).to.exist; @@ -40,7 +40,7 @@ describe('UmbContextConsumer', () => { it('works with UmbContextProvider', (done) => { const provider = new UmbContextProvider(document.body, testContextAlias, new MyClass()); - provider.attach(); + provider.hostConnected(); const element = document.createElement('div'); document.body.appendChild(element); @@ -49,8 +49,8 @@ describe('UmbContextConsumer', () => { expect(_instance.prop).to.eq('value from provider'); done(); }); - localConsumer.attach(); + localConsumer.hostConnected(); - provider.detach(); + provider.hostDisconnected(); }); }); diff --git a/src/Umbraco.Web.UI.Client/src/core/context-api/consume/context-consumer.ts b/src/Umbraco.Web.UI.Client/src/core/context-api/consume/context-consumer.ts index 35205ccc58..45ace142f1 100644 --- a/src/Umbraco.Web.UI.Client/src/core/context-api/consume/context-consumer.ts +++ b/src/Umbraco.Web.UI.Client/src/core/context-api/consume/context-consumer.ts @@ -1,4 +1,4 @@ -import { isUmbContextProvideEvent, umbContextProvideEventType } from '../provide/context-provide.event'; +import { isUmbContextProvideEventType, umbContextProvideEventType } from '../provide/context-provide.event'; import { UmbContextRequestEventImplementation, UmbContextCallback } from './context-request.event'; /** @@ -23,19 +23,19 @@ export class UmbContextConsumer { this.target.dispatchEvent(event); } - public attach() { + public hostConnected() { // TODO: We need to use closets application element. We need this in order to have separate Backoffice running within or next to each other. window.addEventListener(umbContextProvideEventType, this._handleNewProvider); this.request(); } - public detach() { + public hostDisconnected() { // TODO: We need to use closets application element. We need this in order to have separate Backoffice running within or next to each other. window.removeEventListener(umbContextProvideEventType, this._handleNewProvider); } private _handleNewProvider = (event: Event) => { - if (!isUmbContextProvideEvent(event)) return; + if (!isUmbContextProvideEventType(event)) return; if (this._contextAlias === event.contextAlias) { this.request(); diff --git a/src/Umbraco.Web.UI.Client/src/core/context-api/provide/context-provide.event.ts b/src/Umbraco.Web.UI.Client/src/core/context-api/provide/context-provide.event.ts index 7478d4f7d0..c6c38a80e7 100644 --- a/src/Umbraco.Web.UI.Client/src/core/context-api/provide/context-provide.event.ts +++ b/src/Umbraco.Web.UI.Client/src/core/context-api/provide/context-provide.event.ts @@ -20,6 +20,6 @@ export class UmbContextProvideEventImplementation extends Event implements UmbCo } } -export const isUmbContextProvideEvent = (event: Event): event is UmbContextProvideEventImplementation => { +export const isUmbContextProvideEventType = (event: Event): event is UmbContextProvideEventImplementation => { return event.type === umbContextProvideEventType; }; diff --git a/src/Umbraco.Web.UI.Client/src/core/context-api/provide/context-provider.controller.ts b/src/Umbraco.Web.UI.Client/src/core/context-api/provide/context-provider.controller.ts new file mode 100644 index 0000000000..24f397c1c7 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/core/context-api/provide/context-provider.controller.ts @@ -0,0 +1,13 @@ +import { UmbContextProvider } from './context-provider'; +import type { UmbController } from 'src/core/controller/controller.interface'; +import { UmbControllerHostInterface } from 'src/core/controller/controller-host.mixin'; + + +export class UmbContextProviderController extends UmbContextProvider implements UmbController { + + constructor(host:UmbControllerHostInterface, contextAlias: string, instance: unknown) { + super(host, contextAlias, instance); + host.addController(this); + } + +} \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/core/context-api/provide/context-provider.mixin.ts b/src/Umbraco.Web.UI.Client/src/core/context-api/provide/context-provider.mixin.ts index a2c34575e4..8647039eb0 100644 --- a/src/Umbraco.Web.UI.Client/src/core/context-api/provide/context-provider.mixin.ts +++ b/src/Umbraco.Web.UI.Client/src/core/context-api/provide/context-provider.mixin.ts @@ -15,14 +15,14 @@ export const UmbContextProviderMixin = (superC // TODO: Consider if its right to re-publish the context? const existingProvider = this._providers.get(alias); if (existingProvider) { - existingProvider.detach(); + existingProvider.hostDisconnected(); } const provider = new UmbContextProvider(this, alias, instance); this._providers.set(alias, provider); // TODO: if already connected then attach the new one. if (this._attached) { - provider.attach(); + provider.hostConnected(); } } @@ -31,13 +31,13 @@ export const UmbContextProviderMixin = (superC connectedCallback() { super.connectedCallback?.(); this._attached = true; - this._providers.forEach((provider) => provider.attach()); + this._providers.forEach((provider) => provider.hostConnected()); } disconnectedCallback() { super.disconnectedCallback?.(); this._attached = false; - this._providers.forEach((provider) => provider.detach()); + this._providers.forEach((provider) => provider.hostDisconnected()); } } diff --git a/src/Umbraco.Web.UI.Client/src/core/context-api/provide/context-provider.test.ts b/src/Umbraco.Web.UI.Client/src/core/context-api/provide/context-provider.test.ts index 65c72c67fb..0d864cd608 100644 --- a/src/Umbraco.Web.UI.Client/src/core/context-api/provide/context-provider.test.ts +++ b/src/Umbraco.Web.UI.Client/src/core/context-api/provide/context-provider.test.ts @@ -12,11 +12,11 @@ describe('UmbContextProvider', () => { beforeEach(() => { provider = new UmbContextProvider(document.body, 'my-test-context', new MyClass()); - provider.attach(); + provider.hostConnected(); }); afterEach(async () => { - provider.detach(); + provider.hostDisconnected(); }); describe('Public API', () => { @@ -28,11 +28,11 @@ describe('UmbContextProvider', () => { describe('methods', () => { it('has an attach method', () => { - expect(provider).to.have.property('attach').that.is.a('function'); + expect(provider).to.have.property('hostConnected').that.is.a('function'); }); it('has a detach method', () => { - expect(provider).to.have.property('detach').that.is.a('function'); + expect(provider).to.have.property('hostDisconnected').that.is.a('function'); }); }); }); @@ -53,8 +53,8 @@ describe('UmbContextProvider', () => { const localConsumer = new UmbContextConsumer(element, 'my-test-context', (_instance: MyClass) => { expect(_instance.prop).to.eq('value from provider'); done(); - localConsumer.detach(); + localConsumer.hostDisconnected(); }); - localConsumer.attach(); + localConsumer.hostConnected(); }); }); diff --git a/src/Umbraco.Web.UI.Client/src/core/context-api/provide/context-provider.ts b/src/Umbraco.Web.UI.Client/src/core/context-api/provide/context-provider.ts index ba105eb2f9..f07c2c3551 100644 --- a/src/Umbraco.Web.UI.Client/src/core/context-api/provide/context-provider.ts +++ b/src/Umbraco.Web.UI.Client/src/core/context-api/provide/context-provider.ts @@ -26,7 +26,7 @@ export class UmbContextProvider { /** * @memberof UmbContextProvider */ - public attach() { + public hostConnected() { this.host.addEventListener(umbContextRequestEventType, this._handleContextRequest); this.host.dispatchEvent(new UmbContextProvideEventImplementation(this._contextAlias)); } @@ -34,7 +34,7 @@ export class UmbContextProvider { /** * @memberof UmbContextProvider */ - public detach() { + public hostDisconnected() { this.host.removeEventListener(umbContextRequestEventType, this._handleContextRequest); // TODO: fire unprovided event. } diff --git a/src/Umbraco.Web.UI.Client/src/core/controller/controller-host.mixin.ts b/src/Umbraco.Web.UI.Client/src/core/controller/controller-host.mixin.ts new file mode 100644 index 0000000000..a5ced89098 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/core/controller/controller-host.mixin.ts @@ -0,0 +1,56 @@ +import type { HTMLElementConstructor } from '../models'; +import { UmbController } from './controller.interface'; + +export declare class UmbControllerHostInterface extends HTMLElement { + //#controllers:UmbController[]; + //#attached:boolean; + addController(controller:UmbController): void; +} + +/** + * This mixin enables the component to host controllers. + * This is done by calling the `consumeContext` method. + * + * @param {Object} superClass - superclass to be extended. + * @mixin + */ +export const UmbControllerHostMixin = (superClass: T) => { + class UmbContextConsumerClass extends superClass { + + #controllers: UmbController[] = []; + + #attached = false; + + /** + * Append a controller to this element. + * @param {UmbController} ctrl + */ + addController(ctrl: UmbController): void { + this.#controllers.push(ctrl); + if(this.#attached) { + ctrl.hostConnected(); + } + } + + connectedCallback() { + super.connectedCallback?.(); + this.#attached = true; + this.#controllers.forEach((ctrl: UmbController) => ctrl.hostConnected()); + } + + disconnectedCallback() { + super.disconnectedCallback?.(); + this.#attached = false; + this.#controllers.forEach((ctrl: UmbController) => ctrl.hostDisconnected()); + } + } + + return UmbContextConsumerClass as unknown as HTMLElementConstructor & T; +}; + +declare global { + interface HTMLElement { + connectedCallback(): void; + disconnectedCallback(): void; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/core/controller/controller.interface.ts b/src/Umbraco.Web.UI.Client/src/core/controller/controller.interface.ts new file mode 100644 index 0000000000..655eb0ff59 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/core/controller/controller.interface.ts @@ -0,0 +1,4 @@ +export interface UmbController { + hostConnected(): void; + hostDisconnected(): void; +} \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/core/observable-api/observer.controller.ts b/src/Umbraco.Web.UI.Client/src/core/observable-api/observer.controller.ts new file mode 100644 index 0000000000..7b9094cd9a --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/core/observable-api/observer.controller.ts @@ -0,0 +1,18 @@ +import { Observable } from 'rxjs'; +import { UmbObserver } from './observer'; +import type { UmbController } from 'src/core/controller/controller.interface'; +import { UmbControllerHostInterface } from 'src/core/controller/controller-host.mixin'; + + +export class UmbObserverController extends UmbObserver implements UmbController { + + constructor(host:UmbControllerHostInterface, source: Observable, callback: (_value: Y) => void) { + super(source, callback); + host.addController(this); + } + + hostConnected() { + return; + } + +} \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/core/observable-api/observer.mixin.ts b/src/Umbraco.Web.UI.Client/src/core/observable-api/observer.mixin.ts index 490c40c335..3e2be5bfed 100644 --- a/src/Umbraco.Web.UI.Client/src/core/observable-api/observer.mixin.ts +++ b/src/Umbraco.Web.UI.Client/src/core/observable-api/observer.mixin.ts @@ -10,10 +10,14 @@ export const UmbObserverMixin = (superClass: T _subscriptions: Map, Subscription> = new Map(); observe(source: Observable, callback: (_value: Y) => void): ()=>void { + + // TODO: can be transferred to something using alias? + /* if (this._subscriptions.has(source)) { const subscription = this._subscriptions.get(source); subscription?.unsubscribe(); } + */ const subscription = source.subscribe((value) => callback(value)); this._subscriptions.set(source, subscription); diff --git a/src/Umbraco.Web.UI.Client/src/core/observable-api/observer.ts b/src/Umbraco.Web.UI.Client/src/core/observable-api/observer.ts new file mode 100644 index 0000000000..b577a2b6d3 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/core/observable-api/observer.ts @@ -0,0 +1,30 @@ +import { Observable, Subscription } from 'rxjs'; + +export class UmbObserver { + + #subscription!: Subscription; + + constructor(source: Observable, callback: (_value: Y) => void) { + + // TODO: can be transferred to something using alias? + /* + if (this._subscriptions.has(source)) { + const subscription = this._subscriptions.get(source); + subscription?.unsubscribe(); + } + */ + + this.#subscription = source.subscribe((value) => callback(value)); + } + + // Notice controller class implements empty hostConnected(). + + hostDisconnected() { + this.#subscription.unsubscribe(); + } + + destroy(): void { + this.#subscription.unsubscribe(); + } + +}; \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/core/controllers/index.ts b/src/Umbraco.Web.UI.Client/src/core/resources/index.ts similarity index 100% rename from src/Umbraco.Web.UI.Client/src/core/controllers/index.ts rename to src/Umbraco.Web.UI.Client/src/core/resources/index.ts diff --git a/src/Umbraco.Web.UI.Client/src/core/controllers/resource.controller.ts b/src/Umbraco.Web.UI.Client/src/core/resources/resource.controller.ts similarity index 83% rename from src/Umbraco.Web.UI.Client/src/core/controllers/resource.controller.ts rename to src/Umbraco.Web.UI.Client/src/core/resources/resource.controller.ts index c7ca7f23bf..6610ed05b8 100644 --- a/src/Umbraco.Web.UI.Client/src/core/controllers/resource.controller.ts +++ b/src/Umbraco.Web.UI.Client/src/core/resources/resource.controller.ts @@ -1,26 +1,23 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ -import { ReactiveController, ReactiveControllerHost } from 'lit'; +import { UmbController } from '../controller/controller.interface'; +import { UmbControllerHostInterface } from '../controller/controller-host.mixin'; +import { UmbContextConsumerController } from '../context-api/consume/context-consumer.controller'; import { ApiError, CancelablePromise, ProblemDetails } from '@umbraco-cms/backend-api'; import { UmbNotificationOptions, UmbNotificationService } from 'src/backoffice/core/services/notification'; import { UmbNotificationDefaultData } from 'src/backoffice/core/services/notification/layouts/default'; -import { UmbContextConsumer } from '@umbraco-cms/context-api'; +export class UmbResourceController implements UmbController { -export class UmbResourceController implements ReactiveController { - host: ReactiveControllerHost; #promises: Promise[] = []; - #notificationConsumer: UmbContextConsumer; - #notificationService?: UmbNotificationService; - constructor(host: ReactiveControllerHost) { - (this.host = host).addController(this); - this.#notificationConsumer = new UmbContextConsumer( - host as unknown as EventTarget, - 'umbNotificationService', + constructor(host: UmbControllerHostInterface) { + host.addController(this); + + new UmbContextConsumerController(host, 'umbNotificationService', (_instance: UmbNotificationService) => { this.#notificationService = _instance; } @@ -28,13 +25,12 @@ export class UmbResourceController implements ReactiveController { } hostConnected() { - this.#promises.length = 0; - this.#notificationConsumer.attach(); + // TODO: Make sure we do the right thing here, as connected can be called multiple times without disconnected invoked. + //this.#promises.length = 0; } hostDisconnected() { this.cancelAllResources(); - this.#notificationConsumer.detach(); } addResource(promise: Promise): void { diff --git a/src/Umbraco.Web.UI.Client/tsconfig.json b/src/Umbraco.Web.UI.Client/tsconfig.json index 6763dcdfee..6f278ee1d3 100644 --- a/src/Umbraco.Web.UI.Client/tsconfig.json +++ b/src/Umbraco.Web.UI.Client/tsconfig.json @@ -28,7 +28,7 @@ "@umbraco-cms/observable-api": ["src/core/observable-api"], "@umbraco-cms/utils": ["src/core/utils"], "@umbraco-cms/test-utils": ["src/core/test-utils"], - "@umbraco-cms/controllers": ["src/core/controllers"], + "@umbraco-cms/resources": ["src/core/resources"], "@umbraco-cms/services": ["src/core/services"], "@umbraco-cms/components/*": ["src/backoffice/components/*"], "@umbraco-cms/stores/*": ["src/core/stores/*"], diff --git a/src/Umbraco.Web.UI.Client/web-test-runner.config.mjs b/src/Umbraco.Web.UI.Client/web-test-runner.config.mjs index 2167c55a73..bf55d97a2d 100644 --- a/src/Umbraco.Web.UI.Client/web-test-runner.config.mjs +++ b/src/Umbraco.Web.UI.Client/web-test-runner.config.mjs @@ -19,7 +19,7 @@ export default { '@umbraco-cms/resource-api': './src/core/resource-api', '@umbraco-cms/utils': './src/core/utils/index.ts', '@umbraco-cms/test-utils': './src/core/test-utils/index.ts', - '@umbraco-cms/controllers': './src/core/controllers', + '@umbraco-cms/resources': './src/core/resources', '@umbraco-cms/services': './src/core/services', '@umbraco-cms/extensions-registry': './src/core/extensions-registry/index.ts', },