From 85f0efe31f4bb1aa461330abef8f480ac4c6b173 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Tue, 21 May 2024 22:30:04 +0200 Subject: [PATCH] mute feature --- .../src/libs/element-api/element.test.ts | 2 +- .../libs/observable-api/states/array-state.ts | 1 + .../libs/observable-api/states/deep-state.ts | 64 ++++++++++++++++++- .../states/object-state.test.ts | 46 +++++++++++++ .../observable-api/states/object-state.ts | 36 +---------- 5 files changed, 110 insertions(+), 39 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/libs/element-api/element.test.ts b/src/Umbraco.Web.UI.Client/src/libs/element-api/element.test.ts index 20618e4dec..c9a29170d9 100644 --- a/src/Umbraco.Web.UI.Client/src/libs/element-api/element.test.ts +++ b/src/Umbraco.Web.UI.Client/src/libs/element-api/element.test.ts @@ -290,7 +290,7 @@ describe('UmbElementMixin', () => { const check2: CheckType = value as string; const check3: CheckType = value as null; const check4: CheckType = value as undefined; - expect(check).to.be.equal('hello'); + expect(check).to.be.equal(null); expect(check2 === check3 && check2 === check4).to.be.true; // Just to use the const for something. }); // Because the source is potentially undefined, the controller could be undefined and the value of the callback method could be undefined [NL] diff --git a/src/Umbraco.Web.UI.Client/src/libs/observable-api/states/array-state.ts b/src/Umbraco.Web.UI.Client/src/libs/observable-api/states/array-state.ts index a96ff31ce5..9741bbcebb 100644 --- a/src/Umbraco.Web.UI.Client/src/libs/observable-api/states/array-state.ts +++ b/src/Umbraco.Web.UI.Client/src/libs/observable-api/states/array-state.ts @@ -35,6 +35,7 @@ export class UmbArrayState extends UmbDeepState { */ sortBy(sortMethod?: (a: T, b: T) => number) { this.#sortMethod = sortMethod; + super.setValue(this.getValue().sort(this.#sortMethod)); return this; } diff --git a/src/Umbraco.Web.UI.Client/src/libs/observable-api/states/deep-state.ts b/src/Umbraco.Web.UI.Client/src/libs/observable-api/states/deep-state.ts index a9a8b7f33a..f443fe02e1 100644 --- a/src/Umbraco.Web.UI.Client/src/libs/observable-api/states/deep-state.ts +++ b/src/Umbraco.Web.UI.Client/src/libs/observable-api/states/deep-state.ts @@ -13,15 +13,19 @@ import { UmbBasicState } from './basic-state.js'; * Additionally the Subject ensures the data is unique, not updating any Observes unless there is an actual change of the content. */ export class UmbDeepState extends UmbBasicState { + #mute?: boolean; + #value: T; + constructor(initialData: T) { super(deepFreeze(initialData)); + this.#value = this._subject.getValue(); } /** - * @export * @method createObservablePart * @param {(mappable: T) => R} mappingFunction - Method to return the part for this Observable to return. * @param {(previousResult: R, currentResult: R) => boolean} [memoizationFunction] - Method to Compare if the data has changed. Should return true when data is different. + * @returns {Observable} * @description - Creates an Observable from this State. */ asObservablePart( @@ -39,9 +43,63 @@ export class UmbDeepState extends UmbBasicState { setValue(data: T): void { if (!this._subject) return; const frozenData = deepFreeze(data); - // Only update data if its different than current data. - if (!jsonStringComparison(frozenData, this._subject.getValue())) { + this.#value = frozenData; + // Only update data if its not muted and is different than current data. [NL] + if (!this.#mute && !jsonStringComparison(frozenData, this._subject.getValue())) { this._subject.next(frozenData); } } + + getValue(): T { + return this.#value; + } + + /** + * @method mute + * @description - Set mute for this state. + */ + mute() { + if (this.#mute) return; + this.#mute = true; + } + + /** + * @method unmute + * @description - Unset the mute of this state. + */ + unmute() { + if (!this.#mute) return; + this.#mute = false; + // Only update data if it is different than current data. [NL] + if (!jsonStringComparison(this.#value, this._subject.getValue())) { + this._subject?.next(this.#value); + } + } + + /** + * @method isMuted + * @description - Check if the state is muted. + * @returns {boolean} - Returns true if the state is muted. + */ + isMuted() { + return this.#mute; + } + + /** + * @method getMutePromise + * @description - Get a promise which resolves when the mute is unset. + * @returns {Promise} + */ + getMutePromise() { + return new Promise((resolve) => { + if (!this.#mute) { + resolve(); + return; + } + const subscription = this._subject.subscribe(() => { + subscription.unsubscribe(); + resolve(); + }); + }); + } } diff --git a/src/Umbraco.Web.UI.Client/src/libs/observable-api/states/object-state.test.ts b/src/Umbraco.Web.UI.Client/src/libs/observable-api/states/object-state.test.ts index 76dc7705dd..010716e036 100644 --- a/src/Umbraco.Web.UI.Client/src/libs/observable-api/states/object-state.test.ts +++ b/src/Umbraco.Web.UI.Client/src/libs/observable-api/states/object-state.test.ts @@ -41,4 +41,50 @@ describe('UmbObjectState', () => { subject.update({ key: 'change_this_first_should_not_trigger_update' }); subject.update({ another: 'myNewValue' }); }); + + it('replays the latests value when unmuted.', (done) => { + let amountOfCallbacks = 0; + + const observer = subject.asObservable(); + observer.subscribe((value) => { + amountOfCallbacks++; + if (amountOfCallbacks === 1) { + // First callback gives us the initialized value. + expect(value.key).to.be.equal('some'); + } + if (amountOfCallbacks === 2) { + // Second callback gives us the first change. + expect(value.key).to.be.equal('firstChange'); + } + if (amountOfCallbacks === 3) { + // Third callback gives us the last change before unmuted. + expect(value.key).to.be.equal('thirdChange'); + done(); + } + }); + + subject.update({ key: 'firstChange' }); + subject.mute(); + subject.update({ key: 'secondChange' }); + subject.update({ key: 'thirdChange' }); + subject.unmute(); + }); + + /* + it('replays latests unmuted value when muted.', (done) => { + const observer = subject.asObservable(); + observer.subscribe((value) => { + expect(value).to.be.equal(initialData); + }); + + subject.mute(); + subject.update({ key: 'firstChange' }); + + observer.subscribe((value) => { + expect(value).to.be.equal(initialData); + done(); + }); + + }); + */ }); diff --git a/src/Umbraco.Web.UI.Client/src/libs/observable-api/states/object-state.ts b/src/Umbraco.Web.UI.Client/src/libs/observable-api/states/object-state.ts index fdcafa1bac..b325d50b8a 100644 --- a/src/Umbraco.Web.UI.Client/src/libs/observable-api/states/object-state.ts +++ b/src/Umbraco.Web.UI.Client/src/libs/observable-api/states/object-state.ts @@ -10,9 +10,6 @@ import { UmbDeepState } from './deep-state.js'; * The UmbObjectState provides methods to append data when the data is an Object. */ export class UmbObjectState extends UmbDeepState { - #partialUpdateData?: Partial; - #partialUpdateDebounce?: boolean; - /** * @method update * @param {Partial} partialData - A object containing some of the data to update in this Subject. @@ -23,39 +20,8 @@ export class UmbObjectState extends UmbDeepState { * const myState = new UmbObjectState(data); * myState.update({value: 'myNewValue'}); */ - update(partialData: Partial) { - this.setValue({ ...this._subject.getValue(), ...partialData }); + this.setValue({ ...this.getValue(), ...partialData }); return this; } - /* - update(partialData: Partial) { - this.#partialUpdateData = { ...this.#partialUpdateData, ...partialData }; - this.#performUpdate(); - return this; - } - - async #performUpdate() { - if (this.#partialUpdateDebounce) return; - this.#partialUpdateDebounce = true; - await Promise.resolve(); - if (this.#partialUpdateData) { - this.setValue({ ...this._subject.getValue(), ...this.#partialUpdateData }); - this.#partialUpdateData = undefined; - } - this.#partialUpdateDebounce = false; - } - - //getValue? — should this also include the partial update? but be aware that getValue is used for setValue comparison....!! [NL] - - setValue(data: T): void { - if (this.#partialUpdateData) { - console.error('SetValue was called while in debouncing mode.'); - super.setValue({ ...data, ...this.#partialUpdateData }); - //this.#partialUpdateData = undefined; // maybe not, cause keeping this enables that to be merged in despite a another change coming from above. - } else { - super.setValue(data); - } - } - */ }