Vmeda.Online/src/core/classes/page-load-watcher.ts

166 lines
5.5 KiB
TypeScript

// (C) Copyright 2015 Moodle Pty Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import { CoreSitesReadingStrategy } from '@services/sites';
import { CoreUtils } from '@services/utils/utils';
import { Subscription } from 'rxjs';
import { AsyncDirective } from './async-directive';
import { PageLoadsManager } from './page-loads-manager';
import { CorePromisedValue } from './promised-value';
import { WSObservable } from './site';
/**
* Class to watch requests from a page load (including requests from page sub-components).
*/
export class PageLoadWatcher {
protected hasChanges = false;
protected ongoingRequests = 0;
protected components = new Set<AsyncDirective>();
protected loadedTimeout?: number;
protected hasChangesPromises: Promise<boolean>[] = [];
constructor(
protected loadsManager: PageLoadsManager,
protected updateInBackground: boolean,
) { }
/**
* Whether this load watcher can update data in background.
*
* @returns Whether this load watcher can update data in background.
*/
canUpdateInBackground(): boolean {
return this.updateInBackground;
}
/**
* Whether this load watcher had meaningful changes received in background.
*
* @returns Whether this load watcher had meaningful changes received in background.
*/
hasMeaningfulChanges(): boolean {
return this.hasChanges;
}
/**
* Set has meaningful changes to true.
*/
markMeaningfulChanges(): void {
this.hasChanges = true;
}
/**
* Watch a component, waiting for it to be ready.
*
* @param component Component instance.
*/
async watchComponent(component: AsyncDirective): Promise<void> {
this.components.add(component);
clearTimeout(this.loadedTimeout);
try {
await component.ready();
} finally {
this.components.delete(component);
this.checkHasLoaded();
}
}
/**
* Get the reading strategy to use.
*
* @returns Reading strategy to use.
*/
getReadingStrategy(): CoreSitesReadingStrategy | undefined {
return this.updateInBackground ? CoreSitesReadingStrategy.STALE_WHILE_REVALIDATE : undefined;
}
/**
* Watch a WS request, handling the different values it can return, calling the hasMeaningfulChanges callback if needed to
* detect if there are new meaningful changes in the page load, and completing the page load when all requests have
* finished and all components are ready.
*
* @param observable Observable of the request.
* @param hasMeaningfulChanges Callback to check if there are meaningful changes if data was updated in background.
* @returns First value of the observable.
*/
watchRequest<T>(
observable: WSObservable<T>,
hasMeaningfulChanges?: (previousValue: T, newValue: T) => Promise<boolean>,
): Promise<T> {
const promisedValue = new CorePromisedValue<T>();
let subscription: Subscription | null = null;
let firstValue: T | undefined;
this.ongoingRequests++;
clearTimeout(this.loadedTimeout);
const complete = async () => {
this.ongoingRequests--;
this.checkHasLoaded();
// Subscription variable might not be set because the observable completed immediately. Wait for next tick.
await CoreUtils.nextTick();
subscription?.unsubscribe();
};
subscription = observable.subscribe({
next: value => {
if (!firstValue) {
firstValue = value;
promisedValue.resolve(value);
return;
}
// Second value, it means data was updated in background. Compare data.
if (!hasMeaningfulChanges) {
return;
}
this.hasChangesPromises.push(CoreUtils.ignoreErrors(hasMeaningfulChanges(firstValue, value), false));
},
error: (error) => {
promisedValue.reject(error);
complete();
},
complete: () => complete(),
});
return promisedValue;
}
/**
* Check if the load has finished.
*/
protected checkHasLoaded(): void {
if (this.ongoingRequests !== 0 || this.components.size !== 0) {
// Load not finished.
return;
}
// It seems load has finished. Wait to make sure no new component has been rendered and started loading.
// If a new component or a new request starts the timeout will be cancelled, no need to double check it.
clearTimeout(this.loadedTimeout);
this.loadedTimeout = window.setTimeout(async () => {
// Loading finished. Calculate has changes.
const values = await Promise.all(this.hasChangesPromises);
this.hasChanges = this.hasChanges || values.includes(true);
this.loadsManager.onPageLoaded(this);
}, 100);
}
}