mute feature

This commit is contained in:
Niels Lyngsø
2024-05-21 22:30:04 +02:00
parent b1317897ba
commit 85f0efe31f
5 changed files with 110 additions and 39 deletions

View File

@@ -290,7 +290,7 @@ describe('UmbElementMixin', () => {
const check2: CheckType<A, string> = value as string;
const check3: CheckType<A, null> = value as null;
const check4: CheckType<A, undefined> = 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]

View File

@@ -35,6 +35,7 @@ export class UmbArrayState<T> extends UmbDeepState<T[]> {
*/
sortBy(sortMethod?: (a: T, b: T) => number) {
this.#sortMethod = sortMethod;
super.setValue(this.getValue().sort(this.#sortMethod));
return this;
}

View File

@@ -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<T> extends UmbBasicState<T> {
#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<R>}
* @description - Creates an Observable from this State.
*/
asObservablePart<ReturnType>(
@@ -39,9 +43,63 @@ export class UmbDeepState<T> extends UmbBasicState<T> {
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<void>}
*/
getMutePromise() {
return new Promise<void>((resolve) => {
if (!this.#mute) {
resolve();
return;
}
const subscription = this._subject.subscribe(() => {
subscription.unsubscribe();
resolve();
});
});
}
}

View File

@@ -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();
});
});
*/
});

View File

@@ -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<T> extends UmbDeepState<T> {
#partialUpdateData?: Partial<T>;
#partialUpdateDebounce?: boolean;
/**
* @method update
* @param {Partial<T>} partialData - A object containing some of the data to update in this Subject.
@@ -23,39 +20,8 @@ export class UmbObjectState<T> extends UmbDeepState<T> {
* const myState = new UmbObjectState(data);
* myState.update({value: 'myNewValue'});
*/
update(partialData: Partial<T>) {
this.setValue({ ...this._subject.getValue(), ...partialData });
this.setValue({ ...this.getValue(), ...partialData });
return this;
}
/*
update(partialData: Partial<T>) {
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);
}
}
*/
}