commit
78a51dbffa
|
@ -13,6 +13,7 @@
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
import { Directive, ElementRef, OnInit, Output, EventEmitter } from '@angular/core';
|
import { Directive, ElementRef, OnInit, Output, EventEmitter } from '@angular/core';
|
||||||
|
import { CoreDom } from '@singletons/dom';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Directive to emulate click and key actions following aria role button.
|
* Directive to emulate click and key actions following aria role button.
|
||||||
|
@ -36,22 +37,7 @@ export class CoreAriaButtonClickDirective implements OnInit {
|
||||||
* Initialize actions.
|
* Initialize actions.
|
||||||
*/
|
*/
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.element.addEventListener('click', async (event) => {
|
CoreDom.onActivate(this.element, (event) => this.ariaButtonClick.emit(event));
|
||||||
this.ariaButtonClick.emit(event);
|
|
||||||
});
|
|
||||||
|
|
||||||
this.element.addEventListener('keydown', async (event) => {
|
|
||||||
if ((event.key == ' ' || event.key == 'Enter')) {
|
|
||||||
event.preventDefault();
|
|
||||||
event.stopPropagation();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
this.element.addEventListener('keyup', async (event) => {
|
|
||||||
if ((event.key == ' ' || event.key == 'Enter')) {
|
|
||||||
this.ariaButtonClick.emit(event);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,6 +24,7 @@ import {
|
||||||
ViewContainerRef,
|
ViewContainerRef,
|
||||||
ViewChild,
|
ViewChild,
|
||||||
OnDestroy,
|
OnDestroy,
|
||||||
|
Inject,
|
||||||
} from '@angular/core';
|
} from '@angular/core';
|
||||||
import { IonContent } from '@ionic/angular';
|
import { IonContent } from '@ionic/angular';
|
||||||
|
|
||||||
|
@ -33,7 +34,7 @@ import { CoreIframeUtils, CoreIframeUtilsProvider } from '@services/utils/iframe
|
||||||
import { CoreTextUtils } from '@services/utils/text';
|
import { CoreTextUtils } from '@services/utils/text';
|
||||||
import { CoreUtils } from '@services/utils/utils';
|
import { CoreUtils } from '@services/utils/utils';
|
||||||
import { CoreSite } from '@classes/site';
|
import { CoreSite } from '@classes/site';
|
||||||
import { Translate } from '@singletons';
|
import { NgZone, Platform, Translate } from '@singletons';
|
||||||
import { CoreExternalContentDirective } from './external-content';
|
import { CoreExternalContentDirective } from './external-content';
|
||||||
import { CoreLinkDirective } from './link';
|
import { CoreLinkDirective } from './link';
|
||||||
import { CoreFilter, CoreFilterFilter, CoreFilterFormatTextOptions } from '@features/filter/services/filter';
|
import { CoreFilter, CoreFilterFilter, CoreFilterFormatTextOptions } from '@features/filter/services/filter';
|
||||||
|
@ -46,6 +47,8 @@ import { CoreCancellablePromise } from '@classes/cancellable-promise';
|
||||||
import { AsyncComponent } from '@classes/async-component';
|
import { AsyncComponent } from '@classes/async-component';
|
||||||
import { CoreText } from '@singletons/text';
|
import { CoreText } from '@singletons/text';
|
||||||
import { CoreDom } from '@singletons/dom';
|
import { CoreDom } from '@singletons/dom';
|
||||||
|
import { CoreEvents } from '@singletons/events';
|
||||||
|
import { CoreRefreshContext, CORE_REFRESH_CONTEXT } from '@/core/utils/refresh-context';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Directive to format text rendered. It renders the HTML and treats all links and media, using CoreLinkDirective
|
* Directive to format text rendered. It renders the HTML and treats all links and media, using CoreLinkDirective
|
||||||
|
@ -99,6 +102,7 @@ export class CoreFormatTextDirective implements OnChanges, OnDestroy, AsyncCompo
|
||||||
element: ElementRef,
|
element: ElementRef,
|
||||||
@Optional() protected content: IonContent,
|
@Optional() protected content: IonContent,
|
||||||
protected viewContainerRef: ViewContainerRef,
|
protected viewContainerRef: ViewContainerRef,
|
||||||
|
@Optional() @Inject(CORE_REFRESH_CONTEXT) protected refreshContext?: CoreRefreshContext,
|
||||||
) {
|
) {
|
||||||
CoreComponentsRegistry.register(element.nativeElement, this);
|
CoreComponentsRegistry.register(element.nativeElement, this);
|
||||||
|
|
||||||
|
@ -453,9 +457,16 @@ export class CoreFormatTextDirective implements OnChanges, OnDestroy, AsyncCompo
|
||||||
const svgImages = Array.from(div.querySelectorAll('image'));
|
const svgImages = Array.from(div.querySelectorAll('image'));
|
||||||
const promises: Promise<void>[] = [];
|
const promises: Promise<void>[] = [];
|
||||||
|
|
||||||
|
this.treatAppUrlElements(div, site);
|
||||||
|
|
||||||
// Walk through the content to find the links and add our directive to it.
|
// Walk through the content to find the links and add our directive to it.
|
||||||
// Important: We need to look for links first because in 'img' we add new links without core-link.
|
// Important: We need to look for links first because in 'img' we add new links without core-link.
|
||||||
anchors.forEach((anchor) => {
|
anchors.forEach((anchor) => {
|
||||||
|
if (anchor.dataset.appUrl) {
|
||||||
|
// Link already treated in treatAppUrlElements, ignore it.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Angular 2 doesn't let adding directives dynamically. Create the CoreLinkDirective manually.
|
// Angular 2 doesn't let adding directives dynamically. Create the CoreLinkDirective manually.
|
||||||
const linkDir = new CoreLinkDirective(new ElementRef(anchor), this.content);
|
const linkDir = new CoreLinkDirective(new ElementRef(anchor), this.content);
|
||||||
linkDir.capture = this.captureLinks ?? true;
|
linkDir.capture = this.captureLinks ?? true;
|
||||||
|
@ -547,6 +558,74 @@ export class CoreFormatTextDirective implements OnChanges, OnDestroy, AsyncCompo
|
||||||
await Promise.all(promises);
|
await Promise.all(promises);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Treat elements with an app-url data attribute.
|
||||||
|
*
|
||||||
|
* @param div Div containing the elements.
|
||||||
|
* @param site Site.
|
||||||
|
*/
|
||||||
|
protected treatAppUrlElements(div: HTMLElement, site?: CoreSite): void {
|
||||||
|
const appUrlElements = Array.from(div.querySelectorAll<HTMLElement>('*[data-app-url]'));
|
||||||
|
|
||||||
|
appUrlElements.forEach((element) => {
|
||||||
|
const url = element.dataset.appUrl;
|
||||||
|
if (!url) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (element.tagName !== 'BUTTON' && element.tagName !== 'A') {
|
||||||
|
element.setAttribute('tabindex', '0');
|
||||||
|
element.setAttribute('role', 'button');
|
||||||
|
}
|
||||||
|
|
||||||
|
CoreDom.onActivate(element, async (event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
|
||||||
|
site = site || CoreSites.getCurrentSite();
|
||||||
|
if (!site) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const confirmMessage = element.dataset.appUrlConfirm;
|
||||||
|
const openInApp = element.dataset.openIn === 'app';
|
||||||
|
const refreshOnResume = element.dataset.appUrlResumeAction === 'refresh';
|
||||||
|
|
||||||
|
if (confirmMessage) {
|
||||||
|
try {
|
||||||
|
await CoreDomUtils.showConfirm(Translate.instant(confirmMessage));
|
||||||
|
} catch {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (openInApp) {
|
||||||
|
site.openInAppWithAutoLoginIfSameSite(url);
|
||||||
|
|
||||||
|
if (refreshOnResume && this.refreshContext) {
|
||||||
|
// Refresh the context when the IAB is closed.
|
||||||
|
CoreEvents.once(CoreEvents.IAB_EXIT, () => {
|
||||||
|
this.refreshContext?.refreshContext();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
site.openInBrowserWithAutoLoginIfSameSite(url, undefined, {
|
||||||
|
showBrowserWarning: !confirmMessage,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (refreshOnResume && this.refreshContext) {
|
||||||
|
// Refresh the context when the app is resumed.
|
||||||
|
CoreSubscriptions.once(Platform.resume, () => {
|
||||||
|
NgZone.run(async () => {
|
||||||
|
this.refreshContext?.refreshContext();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the element width in pixels.
|
* Returns the element width in pixels.
|
||||||
*
|
*
|
||||||
|
|
|
@ -48,7 +48,7 @@ export class CoreLinkDirective implements OnInit {
|
||||||
@Input() autoLogin = 'check';
|
@Input() autoLogin = 'check';
|
||||||
@Input() showBrowserWarning = true; // Whether to show a warning before opening browser. Defaults to true.
|
@Input() showBrowserWarning = true; // Whether to show a warning before opening browser. Defaults to true.
|
||||||
|
|
||||||
protected element: Element;
|
protected element: HTMLElement;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
element: ElementRef,
|
element: ElementRef,
|
||||||
|
@ -68,22 +68,7 @@ export class CoreLinkDirective implements OnInit {
|
||||||
this.element.setAttribute('role', 'button');
|
this.element.setAttribute('role', 'button');
|
||||||
}
|
}
|
||||||
|
|
||||||
this.element.addEventListener('click', async (event) => {
|
CoreDom.onActivate(this.element, (event) => this.performAction(event));
|
||||||
this.performAction(event);
|
|
||||||
});
|
|
||||||
|
|
||||||
this.element.addEventListener('keydown', (event: KeyboardEvent) => {
|
|
||||||
if ((event.key == ' ' || event.key == 'Enter')) {
|
|
||||||
event.preventDefault();
|
|
||||||
event.stopPropagation();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
this.element.addEventListener('keyup', (event: KeyboardEvent) => {
|
|
||||||
if ((event.key == ' ' || event.key == 'Enter')) {
|
|
||||||
this.performAction(event);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
<ion-content>
|
<ion-content>
|
||||||
<ion-refresher slot="fixed" [disabled]="!dataLoaded || !displayRefresher" (ionRefresh)="doRefresh($event.target)">
|
<ion-refresher slot="fixed" [disabled]="!dataLoaded || updatingData || !displayRefresher" (ionRefresh)="doRefresh($event.target)">
|
||||||
<ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content>
|
<ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content>
|
||||||
</ion-refresher>
|
</ion-refresher>
|
||||||
|
|
||||||
<core-loading [hideUntil]="dataLoaded">
|
<core-loading [hideUntil]="dataLoaded && !updatingData">
|
||||||
<core-course-format [course]="course" [sections]="sections" [initialSectionId]="sectionId" [initialSectionNumber]="sectionNumber"
|
<core-course-format [course]="course" [sections]="sections" [initialSectionId]="sectionId" [initialSectionNumber]="sectionNumber"
|
||||||
[moduleId]="moduleId" class="core-course-format-{{course.format}}" *ngIf="dataLoaded">
|
[moduleId]="moduleId" class="core-course-format-{{course.format}}" *ngIf="dataLoaded">
|
||||||
</core-course-format>
|
</core-course-format>
|
||||||
|
|
|
@ -12,7 +12,7 @@
|
||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
import { Component, ViewChild, OnInit, OnDestroy } from '@angular/core';
|
import { Component, ViewChild, OnInit, OnDestroy, forwardRef } from '@angular/core';
|
||||||
import { IonContent, IonRefresher } from '@ionic/angular';
|
import { IonContent, IonRefresher } from '@ionic/angular';
|
||||||
|
|
||||||
import { CoreDomUtils } from '@services/utils/dom';
|
import { CoreDomUtils } from '@services/utils/dom';
|
||||||
|
@ -36,6 +36,7 @@ import {
|
||||||
CoreEventObserver,
|
CoreEventObserver,
|
||||||
} from '@singletons/events';
|
} from '@singletons/events';
|
||||||
import { CoreNavigator } from '@services/navigator';
|
import { CoreNavigator } from '@services/navigator';
|
||||||
|
import { CoreRefreshContext, CORE_REFRESH_CONTEXT } from '@/core/utils/refresh-context';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Page that displays the contents of a course.
|
* Page that displays the contents of a course.
|
||||||
|
@ -43,8 +44,12 @@ import { CoreNavigator } from '@services/navigator';
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'page-core-course-contents',
|
selector: 'page-core-course-contents',
|
||||||
templateUrl: 'contents.html',
|
templateUrl: 'contents.html',
|
||||||
|
providers: [{
|
||||||
|
provide: CORE_REFRESH_CONTEXT,
|
||||||
|
useExisting: forwardRef(() => CoreCourseContentsPage),
|
||||||
|
}],
|
||||||
})
|
})
|
||||||
export class CoreCourseContentsPage implements OnInit, OnDestroy {
|
export class CoreCourseContentsPage implements OnInit, OnDestroy, CoreRefreshContext {
|
||||||
|
|
||||||
@ViewChild(IonContent) content?: IonContent;
|
@ViewChild(IonContent) content?: IonContent;
|
||||||
@ViewChild(CoreCourseFormatComponent) formatComponent?: CoreCourseFormatComponent;
|
@ViewChild(CoreCourseFormatComponent) formatComponent?: CoreCourseFormatComponent;
|
||||||
|
@ -54,6 +59,7 @@ export class CoreCourseContentsPage implements OnInit, OnDestroy {
|
||||||
sectionId?: number;
|
sectionId?: number;
|
||||||
sectionNumber?: number;
|
sectionNumber?: number;
|
||||||
dataLoaded = false;
|
dataLoaded = false;
|
||||||
|
updatingData = false;
|
||||||
downloadCourseEnabled = false;
|
downloadCourseEnabled = false;
|
||||||
moduleId?: number;
|
moduleId?: number;
|
||||||
displayEnableDownload = false;
|
displayEnableDownload = false;
|
||||||
|
@ -127,7 +133,7 @@ export class CoreCourseContentsPage implements OnInit, OnDestroy {
|
||||||
CoreEvents.COMPLETION_MODULE_VIEWED,
|
CoreEvents.COMPLETION_MODULE_VIEWED,
|
||||||
(data) => {
|
(data) => {
|
||||||
if (data && data.courseId == this.course.id) {
|
if (data && data.courseId == this.course.id) {
|
||||||
this.refreshAfterCompletionChange(true);
|
this.showLoadingAndRefresh(true, false);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
@ -141,7 +147,7 @@ export class CoreCourseContentsPage implements OnInit, OnDestroy {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.refreshAfterCompletionChange(false);
|
this.showLoadingAndRefresh(false, false);
|
||||||
|
|
||||||
if (data.warnings && data.warnings[0]) {
|
if (data.warnings && data.warnings[0]) {
|
||||||
CoreDomUtils.showErrorModal(data.warnings[0]);
|
CoreDomUtils.showErrorModal(data.warnings[0]);
|
||||||
|
@ -315,7 +321,7 @@ export class CoreCourseContentsPage implements OnInit, OnDestroy {
|
||||||
|
|
||||||
await CoreUtils.ignoreErrors(this.invalidateData());
|
await CoreUtils.ignoreErrors(this.invalidateData());
|
||||||
|
|
||||||
await this.refreshAfterCompletionChange(true);
|
await this.showLoadingAndRefresh(true, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -341,23 +347,28 @@ export class CoreCourseContentsPage implements OnInit, OnDestroy {
|
||||||
* Refresh list after a completion change since there could be new activities.
|
* Refresh list after a completion change since there could be new activities.
|
||||||
*
|
*
|
||||||
* @param sync If it should try to sync.
|
* @param sync If it should try to sync.
|
||||||
|
* @param invalidateData Whether to invalidate data. Set it to false if data has already been invalidated.
|
||||||
* @return Promise resolved when done.
|
* @return Promise resolved when done.
|
||||||
*/
|
*/
|
||||||
protected async refreshAfterCompletionChange(sync?: boolean): Promise<void> {
|
protected async showLoadingAndRefresh(sync = false, invalidateData = true): Promise<void> {
|
||||||
// Save scroll position to restore it once done.
|
// Save scroll position to restore it once done.
|
||||||
const scrollElement = await this.content?.getScrollElement();
|
const scrollElement = await this.content?.getScrollElement();
|
||||||
const scrollTop = scrollElement?.scrollTop || 0;
|
const scrollTop = scrollElement?.scrollTop || 0;
|
||||||
const scrollLeft = scrollElement?.scrollLeft || 0;
|
const scrollLeft = scrollElement?.scrollLeft || 0;
|
||||||
|
|
||||||
this.dataLoaded = false;
|
this.updatingData = true;
|
||||||
this.content?.scrollToTop(0); // Scroll top so the spinner is seen.
|
this.content?.scrollToTop(0); // Scroll top so the spinner is seen.
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
if (invalidateData) {
|
||||||
|
await CoreUtils.ignoreErrors(this.invalidateData());
|
||||||
|
}
|
||||||
|
|
||||||
await this.loadData(true, sync);
|
await this.loadData(true, sync);
|
||||||
|
|
||||||
await this.formatComponent?.doRefresh(undefined, undefined, true);
|
await this.formatComponent?.doRefresh(undefined, undefined, true);
|
||||||
} finally {
|
} finally {
|
||||||
this.dataLoaded = true;
|
this.updatingData = false;
|
||||||
|
|
||||||
// Wait for new content height to be calculated and scroll without animation.
|
// Wait for new content height to be calculated and scroll without animation.
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
|
@ -366,6 +377,13 @@ export class CoreCourseContentsPage implements OnInit, OnDestroy {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
async refreshContext(): Promise<void> {
|
||||||
|
await this.showLoadingAndRefresh(true, true);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @inheritdoc
|
* @inheritdoc
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -482,6 +482,29 @@ export class CoreDom {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Listen to click and Enter/Space keys in an element.
|
||||||
|
*
|
||||||
|
* @param element Element to listen to events.
|
||||||
|
* @param callback Callback to call when clicked or the key is pressed.
|
||||||
|
*/
|
||||||
|
static onActivate(element: HTMLElement, callback: (event: MouseEvent | KeyboardEvent) => void): void {
|
||||||
|
element.addEventListener('click', (event) => callback(event));
|
||||||
|
|
||||||
|
element.addEventListener('keydown', (event) => {
|
||||||
|
if ((event.key == ' ' || event.key == 'Enter')) {
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
element.addEventListener('keyup', (event) => {
|
||||||
|
if ((event.key == ' ' || event.key == 'Enter')) {
|
||||||
|
callback(event);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -173,6 +173,31 @@ export class CoreEvents {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Listen once for a certain event. To stop listening to the event (in case it wasn't triggered):
|
||||||
|
* let observer = eventsProvider.on('something', myCallBack);
|
||||||
|
* ...
|
||||||
|
* observer.off();
|
||||||
|
*
|
||||||
|
* @param eventName Name of the event to listen to.
|
||||||
|
* @param callBack Function to call when the event is triggered.
|
||||||
|
* @param siteId Site where to trigger the event. Undefined won't check the site.
|
||||||
|
* @return Observer to stop listening.
|
||||||
|
*/
|
||||||
|
static once<Fallback = unknown, Event extends string = string>(
|
||||||
|
eventName: Event,
|
||||||
|
callBack: (value: CoreEventData<Event, Fallback> & CoreEventSiteData) => void,
|
||||||
|
siteId?: string,
|
||||||
|
): CoreEventObserver {
|
||||||
|
const listener = CoreEvents.on<Fallback, Event>(eventName, (value) => {
|
||||||
|
setTimeout(() => listener.off(), 0);
|
||||||
|
|
||||||
|
callBack(value);
|
||||||
|
}, siteId);
|
||||||
|
|
||||||
|
return listener;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Listen for several events. To stop listening to the events:
|
* Listen for several events. To stop listening to the events:
|
||||||
* let observer = eventsProvider.onMultiple(['something', 'another'], myCallBack);
|
* let observer = eventsProvider.onMultiple(['something', 'another'], myCallBack);
|
||||||
|
|
|
@ -0,0 +1,27 @@
|
||||||
|
// (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 { InjectionToken } from '@angular/core';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Context to refresh data when a certain action happens.
|
||||||
|
*/
|
||||||
|
export interface CoreRefreshContext {
|
||||||
|
/**
|
||||||
|
* Refresh the context.
|
||||||
|
*/
|
||||||
|
refreshContext(): Promise<void>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const CORE_REFRESH_CONTEXT = new InjectionToken<CoreRefreshContext>('CORE_REFRESH_CONTEXT');
|
Loading…
Reference in New Issue