forked from EVOgeek/Vmeda.Online
commit
8736014b35
|
@ -649,7 +649,6 @@
|
||||||
"addon.mod_forum.posttomygroups": "forum",
|
"addon.mod_forum.posttomygroups": "forum",
|
||||||
"addon.mod_forum.privatereply": "forum",
|
"addon.mod_forum.privatereply": "forum",
|
||||||
"addon.mod_forum.re": "forum",
|
"addon.mod_forum.re": "forum",
|
||||||
"addon.mod_forum.refreshdiscussions": "local_moodlemobileapp",
|
|
||||||
"addon.mod_forum.refreshposts": "local_moodlemobileapp",
|
"addon.mod_forum.refreshposts": "local_moodlemobileapp",
|
||||||
"addon.mod_forum.removefromfavourites": "forum",
|
"addon.mod_forum.removefromfavourites": "forum",
|
||||||
"addon.mod_forum.reply": "forum",
|
"addon.mod_forum.reply": "forum",
|
||||||
|
@ -1115,6 +1114,7 @@
|
||||||
"addon.storagemanager.deletedata": "local_moodlemobileapp",
|
"addon.storagemanager.deletedata": "local_moodlemobileapp",
|
||||||
"addon.storagemanager.deletedatafrom": "local_moodlemobileapp",
|
"addon.storagemanager.deletedatafrom": "local_moodlemobileapp",
|
||||||
"addon.storagemanager.downloadedcourses": "local_moodlemobileapp",
|
"addon.storagemanager.downloadedcourses": "local_moodlemobileapp",
|
||||||
|
"addon.storagemanager.downloads": "local_moodlemobileapp",
|
||||||
"addon.storagemanager.errordeletedownloadeddata": "local_moodlemobileapp",
|
"addon.storagemanager.errordeletedownloadeddata": "local_moodlemobileapp",
|
||||||
"addon.storagemanager.managedownloads": "local_moodlemobileapp",
|
"addon.storagemanager.managedownloads": "local_moodlemobileapp",
|
||||||
"addon.storagemanager.totaldownloads": "local_moodlemobileapp",
|
"addon.storagemanager.totaldownloads": "local_moodlemobileapp",
|
||||||
|
@ -1742,6 +1742,7 @@
|
||||||
"core.grades.fail": "grades",
|
"core.grades.fail": "grades",
|
||||||
"core.grades.feedback": "grades",
|
"core.grades.feedback": "grades",
|
||||||
"core.grades.grade": "grades",
|
"core.grades.grade": "grades",
|
||||||
|
"core.grades.gradebook": "grades",
|
||||||
"core.grades.gradeitem": "grades",
|
"core.grades.gradeitem": "grades",
|
||||||
"core.grades.gradepass": "grades",
|
"core.grades.gradepass": "grades",
|
||||||
"core.grades.grades": "grades",
|
"core.grades.grades": "grades",
|
||||||
|
|
|
@ -1,29 +1,8 @@
|
||||||
<!-- Buttons to add to the header. -->
|
<!-- Buttons to add to the header. -->
|
||||||
<core-navbar-buttons slot="end">
|
<core-navbar-buttons slot="end">
|
||||||
<core-context-menu>
|
<ion-button fill="clear" (click)="openModuleSummary()" aria-haspopup="true" [attr.aria-label]="'core.info' | translate">
|
||||||
<core-context-menu-item *ngIf="externalUrl" [priority]="900" [content]="'core.openinbrowser' | translate" [href]="externalUrl"
|
<ion-icon name="fas-info-circle" slot="icon-only" aria-hidden="true"></ion-icon>
|
||||||
iconAction="fas-external-link-alt" [showBrowserWarning]="false">
|
</ion-button>
|
||||||
</core-context-menu-item>
|
|
||||||
<core-context-menu-item *ngIf="assign && (description || (assign.introattachments && assign.introattachments.length))"
|
|
||||||
[priority]="800" [content]="'core.moduleintro' | translate" (action)="expandDescription()" iconAction="fas-arrow-right">
|
|
||||||
</core-context-menu-item>
|
|
||||||
<core-context-menu-item *ngIf="blog" [priority]="750" content="{{'addon.blog.blog' | translate}}" iconAction="far-newspaper"
|
|
||||||
(action)="gotoBlog()">
|
|
||||||
</core-context-menu-item>
|
|
||||||
<core-context-menu-item *ngIf="loaded && !hasOffline && isOnline" [priority]="700" [content]="'core.refresh' | translate"
|
|
||||||
(action)="doRefresh(null, $event)" [iconAction]="refreshIcon" [closeOnClick]="false">
|
|
||||||
</core-context-menu-item>
|
|
||||||
<core-context-menu-item *ngIf="loaded && hasOffline && isOnline" [priority]="600"
|
|
||||||
[content]="'core.settings.synchronizenow' | translate" (action)="doRefresh(null, $event, true)" [iconAction]="syncIcon"
|
|
||||||
[closeOnClick]="false">
|
|
||||||
</core-context-menu-item>
|
|
||||||
<core-context-menu-item *ngIf="prefetchStatusIcon" [priority]="500" [content]="prefetchText" (action)="prefetch($event)"
|
|
||||||
[iconAction]="prefetchStatusIcon" [closeOnClick]="false">
|
|
||||||
</core-context-menu-item>
|
|
||||||
<core-context-menu-item *ngIf="size" [priority]="400" [content]="'core.clearstoreddata' | translate:{$a: size}"
|
|
||||||
iconDescription="fas-archive" (action)="removeFiles($event)" iconAction="fas-trash" [closeOnClick]="false">
|
|
||||||
</core-context-menu-item>
|
|
||||||
</core-context-menu>
|
|
||||||
</core-navbar-buttons>
|
</core-navbar-buttons>
|
||||||
|
|
||||||
<!-- Content. -->
|
<!-- Content. -->
|
||||||
|
|
|
@ -23,7 +23,6 @@ import { CoreGroupInfo, CoreGroups } from '@services/groups';
|
||||||
import { CoreNavigator } from '@services/navigator';
|
import { CoreNavigator } from '@services/navigator';
|
||||||
import { CoreSites } from '@services/sites';
|
import { CoreSites } from '@services/sites';
|
||||||
import { CoreDomUtils } from '@services/utils/dom';
|
import { CoreDomUtils } from '@services/utils/dom';
|
||||||
import { CoreTextUtils } from '@services/utils/text';
|
|
||||||
import { CoreTimeUtils } from '@services/utils/time';
|
import { CoreTimeUtils } from '@services/utils/time';
|
||||||
import { CoreUtils } from '@services/utils/utils';
|
import { CoreUtils } from '@services/utils/utils';
|
||||||
import { Translate } from '@singletons';
|
import { Translate } from '@singletons';
|
||||||
|
@ -161,37 +160,11 @@ export class AddonModAssignIndexComponent extends CoreCourseModuleMainActivityCo
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Expand the description.
|
* @inheritdoc
|
||||||
*/
|
*/
|
||||||
expandDescription(ev?: Event): void {
|
protected async fetchContent(refresh?: boolean, sync = false, showErrors = false): Promise<void> {
|
||||||
ev?.preventDefault();
|
|
||||||
ev?.stopPropagation();
|
|
||||||
|
|
||||||
if (this.assign && (this.description || this.assign.introattachments)) {
|
|
||||||
CoreTextUtils.viewText(Translate.instant('core.description'), this.description || '', {
|
|
||||||
component: this.component,
|
|
||||||
componentId: this.module.id,
|
|
||||||
files: this.assign.introattachments,
|
|
||||||
filter: true,
|
|
||||||
contextLevel: 'module',
|
|
||||||
instanceId: this.module.id,
|
|
||||||
courseId: this.courseId,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get assignment data.
|
|
||||||
*
|
|
||||||
* @param refresh If it's refreshing content.
|
|
||||||
* @param sync If it should try to sync.
|
|
||||||
* @param showErrors If show errors to the user of hide them.
|
|
||||||
* @return Promise resolved when done.
|
|
||||||
*/
|
|
||||||
protected async fetchContent(refresh = false, sync = false, showErrors = false): Promise<void> {
|
|
||||||
|
|
||||||
// Get assignment data.
|
// Get assignment data.
|
||||||
try {
|
|
||||||
this.assign = await AddonModAssign.getAssignment(this.courseId, this.module.id);
|
this.assign = await AddonModAssign.getAssignment(this.courseId, this.module.id);
|
||||||
|
|
||||||
this.dataRetrieved.emit(this.assign);
|
this.dataRetrieved.emit(this.assign);
|
||||||
|
@ -257,9 +230,6 @@ export class AddonModAssignIndexComponent extends CoreCourseModuleMainActivityCo
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} finally {
|
|
||||||
this.fillContextMenu(refresh);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -1,22 +1,8 @@
|
||||||
<!-- Buttons to add to the header. -->
|
<!-- Buttons to add to the header. -->
|
||||||
<core-navbar-buttons slot="end">
|
<core-navbar-buttons slot="end">
|
||||||
<core-context-menu>
|
<ion-button fill="clear" (click)="openModuleSummary()" aria-haspopup="true" [attr.aria-label]="'core.info' | translate">
|
||||||
<core-context-menu-item *ngIf="externalUrl" [priority]="900" [content]="'core.openinbrowser' | translate" [href]="externalUrl"
|
<ion-icon name="fas-info-circle" slot="icon-only" aria-hidden="true"></ion-icon>
|
||||||
iconAction="fas-external-link-alt">
|
</ion-button>
|
||||||
</core-context-menu-item>
|
|
||||||
<core-context-menu-item *ngIf="description" [priority]="800" [content]="'core.moduleintro' | translate"
|
|
||||||
(action)="expandDescription()" iconAction="fas-arrow-right">
|
|
||||||
</core-context-menu-item>
|
|
||||||
<core-context-menu-item *ngIf="blog" [priority]="750" content="{{'addon.blog.blog' | translate}}" iconAction="far-newspaper"
|
|
||||||
(action)="gotoBlog()">
|
|
||||||
</core-context-menu-item>
|
|
||||||
<core-context-menu-item *ngIf="loaded && isOnline" [priority]="700" [content]="'core.refresh' | translate"
|
|
||||||
(action)="doRefresh(null, $event)" [iconAction]="refreshIcon" [closeOnClick]="false">
|
|
||||||
</core-context-menu-item>
|
|
||||||
<core-context-menu-item *ngIf="prefetchStatusIcon" [priority]="500" [content]="prefetchText" (action)="prefetch($event)"
|
|
||||||
[iconAction]="prefetchStatusIcon" [closeOnClick]="false">
|
|
||||||
</core-context-menu-item>
|
|
||||||
</core-context-menu>
|
|
||||||
</core-navbar-buttons>
|
</core-navbar-buttons>
|
||||||
|
|
||||||
<!-- Content. -->
|
<!-- Content. -->
|
||||||
|
|
|
@ -71,8 +71,7 @@ export class AddonModBBBIndexComponent extends CoreCourseModuleMainActivityCompo
|
||||||
/**
|
/**
|
||||||
* @inheritdoc
|
* @inheritdoc
|
||||||
*/
|
*/
|
||||||
protected async fetchContent(refresh: boolean = false): Promise<void> {
|
protected async fetchContent(): Promise<void> {
|
||||||
try {
|
|
||||||
this.bbb = await AddonModBBB.getBBB(this.courseId, this.module.id);
|
this.bbb = await AddonModBBB.getBBB(this.courseId, this.module.id);
|
||||||
|
|
||||||
this.description = this.bbb.intro;
|
this.description = this.bbb.intro;
|
||||||
|
@ -83,9 +82,7 @@ export class AddonModBBBIndexComponent extends CoreCourseModuleMainActivityCompo
|
||||||
this.groupId = CoreGroups.validateGroupId(this.groupId, this.groupInfo);
|
this.groupId = CoreGroups.validateGroupId(this.groupId, this.groupInfo);
|
||||||
|
|
||||||
await this.fetchMeetingInfo();
|
await this.fetchMeetingInfo();
|
||||||
} finally {
|
|
||||||
this.fillContextMenu(refresh);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -1,20 +1,8 @@
|
||||||
<!-- Buttons to add to the header. -->
|
<!-- Buttons to add to the header. -->
|
||||||
<core-navbar-buttons slot="end">
|
<core-navbar-buttons slot="end">
|
||||||
<core-context-menu>
|
<ion-button fill="clear" (click)="openModuleSummary()" aria-haspopup="true" [attr.aria-label]="'core.info' | translate">
|
||||||
<core-context-menu-item *ngIf="externalUrl" [priority]="900" [content]="'core.openinbrowser' | translate" [href]="externalUrl"
|
<ion-icon name="fas-info-circle" slot="icon-only" aria-hidden="true"></ion-icon>
|
||||||
iconAction="fas-external-link-alt" [showBrowserWarning]="false"></core-context-menu-item>
|
</ion-button>
|
||||||
<core-context-menu-item *ngIf="description" [priority]="800" [content]="'core.moduleintro' | translate"
|
|
||||||
(action)="expandDescription()" iconAction="fas-arrow-right"></core-context-menu-item>
|
|
||||||
<core-context-menu-item *ngIf="blog" [priority]="750" content="{{'addon.blog.blog' | translate}}" iconAction="far-newspaper"
|
|
||||||
(action)="gotoBlog()"></core-context-menu-item>
|
|
||||||
<core-context-menu-item [priority]="700" [content]="'core.refresh' | translate" (action)="doRefresh(null, $event)"
|
|
||||||
[iconAction]="refreshIcon" [closeOnClick]="false"></core-context-menu-item>
|
|
||||||
<core-context-menu-item *ngIf="prefetchStatusIcon" [priority]="600" [content]="prefetchText" (action)="prefetch($event)"
|
|
||||||
[iconAction]="prefetchStatusIcon" [closeOnClick]="false"></core-context-menu-item>
|
|
||||||
<core-context-menu-item *ngIf="size" [priority]="500" [content]="'core.clearstoreddata' | translate:{$a: size}"
|
|
||||||
iconDescription="fas-archive" (action)="removeFiles($event)" iconAction="fas-trash" [closeOnClick]="false">
|
|
||||||
</core-context-menu-item>
|
|
||||||
</core-context-menu>
|
|
||||||
</core-navbar-buttons>
|
</core-navbar-buttons>
|
||||||
|
|
||||||
<!-- Content. -->
|
<!-- Content. -->
|
||||||
|
|
|
@ -52,15 +52,11 @@ export class AddonModBookIndexComponent extends CoreCourseModuleMainResourceComp
|
||||||
/**
|
/**
|
||||||
* @inheritdoc
|
* @inheritdoc
|
||||||
*/
|
*/
|
||||||
protected async fetchContent(refresh?: boolean): Promise<void> {
|
protected async fetchContent(): Promise<void> {
|
||||||
try {
|
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
this.loadBook(),
|
this.loadBook(),
|
||||||
this.loadTOC(),
|
this.loadTOC(),
|
||||||
]);
|
]);
|
||||||
} finally {
|
|
||||||
this.fillContextMenu(refresh);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -1,26 +1,8 @@
|
||||||
<!-- Buttons to add to the header. -->
|
<!-- Buttons to add to the header. -->
|
||||||
<core-navbar-buttons slot="end">
|
<core-navbar-buttons slot="end">
|
||||||
<core-context-menu>
|
<ion-button fill="clear" (click)="openModuleSummary()" aria-haspopup="true" [attr.aria-label]="'core.info' | translate">
|
||||||
<core-context-menu-item *ngIf="externalUrl" [priority]="900" [content]="'core.openinbrowser' | translate" [href]="externalUrl"
|
<ion-icon name="fas-info-circle" slot="icon-only" aria-hidden="true"></ion-icon>
|
||||||
iconAction="fas-external-link-alt" [showBrowserWarning]="false">
|
</ion-button>
|
||||||
</core-context-menu-item>
|
|
||||||
<core-context-menu-item *ngIf="description" [priority]="800" [content]="'core.moduleintro' | translate"
|
|
||||||
(action)="expandDescription()" iconAction="fas-arrow-right">
|
|
||||||
</core-context-menu-item>
|
|
||||||
<core-context-menu-item *ngIf="blog" [priority]="750" content="{{'addon.blog.blog' | translate}}" iconAction="far-newspaper"
|
|
||||||
(action)="gotoBlog()">
|
|
||||||
</core-context-menu-item>
|
|
||||||
<core-context-menu-item *ngIf="loaded && isOnline" [priority]="700" [content]="'core.refresh' | translate"
|
|
||||||
(action)="doRefresh(null, $event)" [iconAction]="refreshIcon" [closeOnClick]="false">
|
|
||||||
</core-context-menu-item>
|
|
||||||
<core-context-menu-item *ngIf="loaded && hasOffline && isOnline" [priority]="600"
|
|
||||||
[content]="'core.settings.synchronizenow' | translate" (action)="doRefresh(null, $event, true)" [iconAction]="syncIcon"
|
|
||||||
[closeOnClick]="false">
|
|
||||||
</core-context-menu-item>
|
|
||||||
<core-context-menu-item *ngIf="prefetchStatusIcon" [priority]="500" [content]="prefetchText" (action)="prefetch($event)"
|
|
||||||
[iconAction]="prefetchStatusIcon" [closeOnClick]="false">
|
|
||||||
</core-context-menu-item>
|
|
||||||
</core-context-menu>
|
|
||||||
</core-navbar-buttons>
|
</core-navbar-buttons>
|
||||||
|
|
||||||
<!-- Content. -->
|
<!-- Content. -->
|
||||||
|
|
|
@ -70,8 +70,7 @@ export class AddonModChatIndexComponent extends CoreCourseModuleMainActivityComp
|
||||||
/**
|
/**
|
||||||
* @inheritdoc
|
* @inheritdoc
|
||||||
*/
|
*/
|
||||||
protected async fetchContent(refresh: boolean = false): Promise<void> {
|
protected async fetchContent(): Promise<void> {
|
||||||
try {
|
|
||||||
this.chat = await AddonModChat.getChat(this.courseId, this.module.id);
|
this.chat = await AddonModChat.getChat(this.courseId, this.module.id);
|
||||||
|
|
||||||
this.description = this.chat.intro;
|
this.description = this.chat.intro;
|
||||||
|
@ -88,9 +87,6 @@ export class AddonModChatIndexComponent extends CoreCourseModuleMainActivityComp
|
||||||
}
|
}
|
||||||
|
|
||||||
this.dataRetrieved.emit(this.chat);
|
this.dataRetrieved.emit(this.chat);
|
||||||
} finally {
|
|
||||||
this.fillContextMenu(refresh);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -1,28 +1,8 @@
|
||||||
<!-- Buttons to add to the header. -->
|
<!-- Buttons to add to the header. -->
|
||||||
<core-navbar-buttons slot="end">
|
<core-navbar-buttons slot="end">
|
||||||
<core-context-menu>
|
<ion-button fill="clear" (click)="openModuleSummary()" aria-haspopup="true" [attr.aria-label]="'core.info' | translate">
|
||||||
<core-context-menu-item *ngIf="externalUrl" [priority]="900" [content]="'core.openinbrowser' | translate" [href]="externalUrl"
|
<ion-icon name="fas-info-circle" slot="icon-only" aria-hidden="true"></ion-icon>
|
||||||
iconAction="fas-external-link-alt" [showBrowserWarning]="false">
|
</ion-button>
|
||||||
</core-context-menu-item>
|
|
||||||
<core-context-menu-item *ngIf="description" [priority]="800" [content]="'core.moduleintro' | translate"
|
|
||||||
(action)="expandDescription()" iconAction="fas-arrow-right">
|
|
||||||
</core-context-menu-item>
|
|
||||||
<core-context-menu-item *ngIf="blog" [priority]="750" content="{{'addon.blog.blog' | translate}}" iconAction="far-newspaper"
|
|
||||||
(action)="gotoBlog()">
|
|
||||||
</core-context-menu-item>
|
|
||||||
<core-context-menu-item *ngIf="loaded && !hasOffline && isOnline" [priority]="700" [content]="'core.refresh' | translate"
|
|
||||||
(action)="doRefresh(null, $event)" [iconAction]="refreshIcon" [closeOnClick]="false">
|
|
||||||
</core-context-menu-item>
|
|
||||||
<core-context-menu-item *ngIf="loaded && hasOffline && isOnline" [priority]="600" (action)="doRefresh(null, $event, true)"
|
|
||||||
[content]="'core.settings.synchronizenow' | translate" [iconAction]="syncIcon" [closeOnClick]="false">
|
|
||||||
</core-context-menu-item>
|
|
||||||
<core-context-menu-item *ngIf="prefetchStatusIcon" [priority]="500" [content]="prefetchText" (action)="prefetch($event)"
|
|
||||||
[iconAction]="prefetchStatusIcon" [closeOnClick]="false">
|
|
||||||
</core-context-menu-item>
|
|
||||||
<core-context-menu-item *ngIf="size" [priority]="400" [content]="'core.clearstoreddata' | translate:{$a: size}"
|
|
||||||
iconDescription="fas-archive" (action)="removeFiles($event)" iconAction="fas-trash" [closeOnClick]="false">
|
|
||||||
</core-context-menu-item>
|
|
||||||
</core-context-menu>
|
|
||||||
</core-navbar-buttons>
|
</core-navbar-buttons>
|
||||||
|
|
||||||
<!-- Content. -->
|
<!-- Content. -->
|
||||||
|
|
|
@ -132,10 +132,9 @@ export class AddonModChoiceIndexComponent extends CoreCourseModuleMainActivityCo
|
||||||
/**
|
/**
|
||||||
* @inheritdoc
|
* @inheritdoc
|
||||||
*/
|
*/
|
||||||
protected async fetchContent(refresh: boolean = false, sync: boolean = false, showErrors: boolean = false): Promise<void> {
|
protected async fetchContent(refresh?: boolean, sync = false, showErrors = false): Promise<void> {
|
||||||
this.now = Date.now();
|
this.now = Date.now();
|
||||||
|
|
||||||
try {
|
|
||||||
this.choice = await AddonModChoice.getChoice(this.courseId, this.module.id);
|
this.choice = await AddonModChoice.getChoice(this.courseId, this.module.id);
|
||||||
|
|
||||||
if (sync) {
|
if (sync) {
|
||||||
|
@ -166,9 +165,6 @@ export class AddonModChoiceIndexComponent extends CoreCourseModuleMainActivityCo
|
||||||
await this.fetchOptions(this.choice);
|
await this.fetchOptions(this.choice);
|
||||||
|
|
||||||
await this.fetchResults(this.choice);
|
await this.fetchResults(this.choice);
|
||||||
} finally {
|
|
||||||
this.fillContextMenu(refresh);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -433,7 +429,7 @@ export class AddonModChoiceIndexComponent extends CoreCourseModuleMainActivityCo
|
||||||
* @return Promise resolved when done.
|
* @return Promise resolved when done.
|
||||||
*/
|
*/
|
||||||
protected async dataUpdated(online: boolean): Promise<void> {
|
protected async dataUpdated(online: boolean): Promise<void> {
|
||||||
if (!online || !this.isPrefetched) {
|
if (!online || !this.isPrefetched()) {
|
||||||
// Not downloaded, just refresh the data.
|
// Not downloaded, just refresh the data.
|
||||||
return this.refreshContent(false);
|
return this.refreshContent(false);
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,35 +4,17 @@
|
||||||
<ion-icon name="fas-search" slot="icon-only" aria-hidden="true"></ion-icon>
|
<ion-icon name="fas-search" slot="icon-only" aria-hidden="true"></ion-icon>
|
||||||
</ion-button>
|
</ion-button>
|
||||||
<core-context-menu>
|
<core-context-menu>
|
||||||
<core-context-menu-item *ngIf="externalUrl" [priority]="900" [content]="'core.openinbrowser' | translate" [href]="externalUrl"
|
|
||||||
iconAction="fas-external-link-alt" [showBrowserWarning]="false">
|
|
||||||
</core-context-menu-item>
|
|
||||||
<core-context-menu-item *ngIf="description" [priority]="800" [content]="'core.moduleintro' | translate"
|
|
||||||
(action)="expandDescription()" iconAction="fas-arrow-right">
|
|
||||||
</core-context-menu-item>
|
|
||||||
<core-context-menu-item *ngIf="blog" [priority]="750" content="{{'addon.blog.blog' | translate}}" iconAction="far-newspaper"
|
|
||||||
(action)="gotoBlog()">
|
|
||||||
</core-context-menu-item>
|
|
||||||
<core-context-menu-item *ngIf="loaded && !(hasOffline || hasOfflineRatings) && isOnline" [priority]="700"
|
|
||||||
[content]="'core.refresh' | translate" (action)="doRefresh(null, $event)" [iconAction]="refreshIcon" [closeOnClick]="false">
|
|
||||||
</core-context-menu-item>
|
|
||||||
<core-context-menu-item *ngIf="loaded && (hasOffline || hasOfflineRatings) && isOnline" [priority]="600"
|
|
||||||
[content]="'core.settings.synchronizenow' | translate" (action)="doRefresh(null, $event, true)" [iconAction]="syncIcon"
|
|
||||||
[closeOnClick]="false">
|
|
||||||
</core-context-menu-item>
|
|
||||||
<core-context-menu-item [priority]="500" *ngIf="canAdd" [content]="'addon.mod_data.addentries' | translate" iconAction="fas-plus"
|
<core-context-menu-item [priority]="500" *ngIf="canAdd" [content]="'addon.mod_data.addentries' | translate" iconAction="fas-plus"
|
||||||
(action)="gotoAddEntries()">
|
(action)="gotoAddEntries()">
|
||||||
</core-context-menu-item>
|
</core-context-menu-item>
|
||||||
<core-context-menu-item [priority]="400" *ngIf="firstEntry" [content]="'addon.mod_data.single' | translate" iconAction="fas-file"
|
<core-context-menu-item [priority]="400" *ngIf="firstEntry" [content]="'addon.mod_data.single' | translate" iconAction="fas-file"
|
||||||
(action)="gotoEntry(firstEntry)">
|
(action)="gotoEntry(firstEntry)">
|
||||||
</core-context-menu-item>
|
</core-context-menu-item>
|
||||||
<core-context-menu-item *ngIf="prefetchStatusIcon" [priority]="300" [content]="prefetchText" (action)="prefetch($event)"
|
|
||||||
[iconAction]="prefetchStatusIcon" [closeOnClick]="false">
|
|
||||||
</core-context-menu-item>
|
|
||||||
<core-context-menu-item *ngIf="size" [priority]="200" [content]="'core.clearstoreddata' | translate:{$a: size}"
|
|
||||||
iconDescription="fas-archive" (action)="removeFiles($event)" iconAction="fas-trash" [closeOnClick]="false">
|
|
||||||
</core-context-menu-item>
|
|
||||||
</core-context-menu>
|
</core-context-menu>
|
||||||
|
|
||||||
|
<ion-button fill="clear" (click)="openModuleSummary()" aria-haspopup="true" [attr.aria-label]="'core.info' | translate">
|
||||||
|
<ion-icon name="fas-info-circle" slot="icon-only" aria-hidden="true"></ion-icon>
|
||||||
|
</ion-button>
|
||||||
</core-navbar-buttons>
|
</core-navbar-buttons>
|
||||||
|
|
||||||
<!-- Content. -->
|
<!-- Content. -->
|
||||||
|
|
|
@ -202,14 +202,9 @@ export class AddonModDataIndexComponent extends CoreCourseModuleMainActivityComp
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Download data contents.
|
* @inheritdoc
|
||||||
*
|
|
||||||
* @param refresh If it's refreshing content.
|
|
||||||
* @param sync If it should try to sync.
|
|
||||||
* @param showErrors If show errors to the user of hide them.
|
|
||||||
* @return Promise resolved when done.
|
|
||||||
*/
|
*/
|
||||||
protected async fetchContent(refresh: boolean = false, sync: boolean = false, showErrors: boolean = false): Promise<void> {
|
protected async fetchContent(refresh?: boolean, sync = false, showErrors = false): Promise<void> {
|
||||||
let canAdd = false;
|
let canAdd = false;
|
||||||
let canSearch = false;
|
let canSearch = false;
|
||||||
|
|
||||||
|
@ -270,7 +265,6 @@ export class AddonModDataIndexComponent extends CoreCourseModuleMainActivityComp
|
||||||
} finally {
|
} finally {
|
||||||
this.canAdd = canAdd;
|
this.canAdd = canAdd;
|
||||||
this.canSearch = canSearch;
|
this.canSearch = canSearch;
|
||||||
this.fillContextMenu(refresh);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -302,7 +296,7 @@ export class AddonModDataIndexComponent extends CoreCourseModuleMainActivityComp
|
||||||
this.hasNextPage = numEntries >= AddonModDataProvider.PER_PAGE && ((this.search.page + 1) *
|
this.hasNextPage = numEntries >= AddonModDataProvider.PER_PAGE && ((this.search.page + 1) *
|
||||||
AddonModDataProvider.PER_PAGE) < entries.totalcount;
|
AddonModDataProvider.PER_PAGE) < entries.totalcount;
|
||||||
|
|
||||||
this.hasOffline = entries.hasOfflineActions;
|
this.hasOffline = !!entries.hasOfflineActions;
|
||||||
|
|
||||||
this.hasOfflineRatings = !!entries.hasOfflineRatings;
|
this.hasOfflineRatings = !!entries.hasOfflineRatings;
|
||||||
|
|
||||||
|
|
|
@ -1,28 +1,8 @@
|
||||||
<!-- Buttons to add to the header. -->
|
<!-- Buttons to add to the header. -->
|
||||||
<core-navbar-buttons slot="end">
|
<core-navbar-buttons slot="end">
|
||||||
<core-context-menu>
|
<ion-button fill="clear" (click)="openModuleSummary()" aria-haspopup="true" [attr.aria-label]="'core.info' | translate">
|
||||||
<core-context-menu-item *ngIf="externalUrl" [priority]="900" [content]="'core.openinbrowser' | translate" [href]="externalUrl"
|
<ion-icon name="fas-info-circle" slot="icon-only" aria-hidden="true"></ion-icon>
|
||||||
iconAction="fas-external-link-alt" [showBrowserWarning]="false">
|
</ion-button>
|
||||||
</core-context-menu-item>
|
|
||||||
<core-context-menu-item *ngIf="description" [priority]="800" [content]="'core.moduleintro' | translate"
|
|
||||||
(action)="expandDescription()" iconAction="fas-arrow-right">
|
|
||||||
</core-context-menu-item>
|
|
||||||
<core-context-menu-item *ngIf="blog" [priority]="750" content="{{'addon.blog.blog' | translate}}" iconAction="far-newspaper"
|
|
||||||
(action)="gotoBlog()">
|
|
||||||
</core-context-menu-item>
|
|
||||||
<core-context-menu-item *ngIf="loaded && !hasOffline && isOnline" [priority]="700" [content]="'core.refresh' | translate"
|
|
||||||
(action)="doRefresh(null, $event)" [iconAction]="refreshIcon" [closeOnClick]="false">
|
|
||||||
</core-context-menu-item>
|
|
||||||
<core-context-menu-item *ngIf="loaded && hasOffline && isOnline" [priority]="600" (action)="doRefresh(null, $event, true)"
|
|
||||||
[content]="'core.settings.synchronizenow' | translate" [iconAction]="syncIcon" [closeOnClick]="false">
|
|
||||||
</core-context-menu-item>
|
|
||||||
<core-context-menu-item *ngIf="prefetchStatusIcon" [priority]="500" [content]="prefetchText" (action)="prefetch($event)"
|
|
||||||
[iconAction]="prefetchStatusIcon" [closeOnClick]="false">
|
|
||||||
</core-context-menu-item>
|
|
||||||
<core-context-menu-item *ngIf="size" [priority]="400" [content]="'core.clearstoreddata' | translate:{$a: size}"
|
|
||||||
iconDescription="fas-archive" (action)="removeFiles($event)" iconAction="fas-trash" [closeOnClick]="false">
|
|
||||||
</core-context-menu-item>
|
|
||||||
</core-context-menu>
|
|
||||||
</core-navbar-buttons>
|
</core-navbar-buttons>
|
||||||
|
|
||||||
<!-- Content. -->
|
<!-- Content. -->
|
||||||
|
|
|
@ -172,7 +172,7 @@ export class AddonModFeedbackIndexComponent extends CoreCourseModuleMainActivity
|
||||||
/**
|
/**
|
||||||
* @inheritdoc
|
* @inheritdoc
|
||||||
*/
|
*/
|
||||||
protected async fetchContent(refresh: boolean = false, sync: boolean = false, showErrors: boolean = false): Promise<void> {
|
protected async fetchContent(refresh?: boolean, sync = false, showErrors = false): Promise<void> {
|
||||||
try {
|
try {
|
||||||
this.feedback = await AddonModFeedback.getFeedback(this.courseId, this.module.id);
|
this.feedback = await AddonModFeedback.getFeedback(this.courseId, this.module.id);
|
||||||
|
|
||||||
|
@ -201,9 +201,6 @@ export class AddonModFeedbackIndexComponent extends CoreCourseModuleMainActivity
|
||||||
|
|
||||||
await this.fetchFeedbackOverviewData();
|
await this.fetchFeedbackOverviewData();
|
||||||
} finally {
|
} finally {
|
||||||
// Now fill the context menu.
|
|
||||||
this.fillContextMenu(refresh);
|
|
||||||
|
|
||||||
if (this.feedback) {
|
if (this.feedback) {
|
||||||
// Check if there are responses stored in offline.
|
// Check if there are responses stored in offline.
|
||||||
this.hasOffline = await AddonModFeedbackOffline.hasFeedbackOfflineData(this.feedback.id);
|
this.hasOffline = await AddonModFeedbackOffline.hasFeedbackOfflineData(this.feedback.id);
|
||||||
|
|
|
@ -1,25 +1,8 @@
|
||||||
<!-- Buttons to add to the header. -->
|
<!-- Buttons to add to the header. -->
|
||||||
<core-navbar-buttons slot="end">
|
<core-navbar-buttons slot="end">
|
||||||
<core-context-menu>
|
<ion-button fill="clear" (click)="openModuleSummary()" aria-haspopup="true" [attr.aria-label]="'core.info' | translate">
|
||||||
<core-context-menu-item *ngIf="externalUrl" [priority]="900" [content]="'core.openinbrowser' | translate" [href]="externalUrl"
|
<ion-icon name="fas-info-circle" slot="icon-only" aria-hidden="true"></ion-icon>
|
||||||
iconAction="fas-external-link-alt" [showBrowserWarning]="false">
|
</ion-button>
|
||||||
</core-context-menu-item>
|
|
||||||
<core-context-menu-item *ngIf="description" [priority]="800" [content]="'core.moduleintro' | translate"
|
|
||||||
(action)="expandDescription()" iconAction="fas-arrow-right">
|
|
||||||
</core-context-menu-item>
|
|
||||||
<core-context-menu-item *ngIf="blog" [priority]="750" content="{{'addon.blog.blog' | translate}}" iconAction="far-newspaper"
|
|
||||||
(action)="gotoBlog()">
|
|
||||||
</core-context-menu-item>
|
|
||||||
<core-context-menu-item *ngIf="!subfolder" [priority]="700" [content]="'core.refresh' | translate"
|
|
||||||
(action)="doRefresh(null, $event)" [iconAction]="refreshIcon" [closeOnClick]="false">
|
|
||||||
</core-context-menu-item>
|
|
||||||
<core-context-menu-item *ngIf="prefetchStatusIcon" [priority]="600" [content]="prefetchText" (action)="prefetch($event)"
|
|
||||||
[iconAction]="prefetchStatusIcon" [closeOnClick]="false">
|
|
||||||
</core-context-menu-item>
|
|
||||||
<core-context-menu-item *ngIf="size" [priority]="500" [content]="'core.clearstoreddata' | translate:{$a: size}"
|
|
||||||
iconDescription="fas-archive" (action)="removeFiles($event)" iconAction="fas-trash" [closeOnClick]="false">
|
|
||||||
</core-context-menu-item>
|
|
||||||
</core-context-menu>
|
|
||||||
</core-navbar-buttons>
|
</core-navbar-buttons>
|
||||||
|
|
||||||
<!-- Content. -->
|
<!-- Content. -->
|
||||||
|
|
|
@ -12,7 +12,6 @@
|
||||||
// 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 { CoreConstants } from '@/core/constants';
|
|
||||||
import { Component, Input, OnInit, Optional } from '@angular/core';
|
import { Component, Input, OnInit, Optional } from '@angular/core';
|
||||||
import { Params } from '@angular/router';
|
import { Params } from '@angular/router';
|
||||||
import { CoreCourseModuleMainResourceComponent } from '@features/course/classes/main-resource-component';
|
import { CoreCourseModuleMainResourceComponent } from '@features/course/classes/main-resource-component';
|
||||||
|
@ -57,7 +56,6 @@ export class AddonModFolderIndexComponent extends CoreCourseModuleMainResourceCo
|
||||||
this.contents = this.subfolder;
|
this.contents = this.subfolder;
|
||||||
|
|
||||||
this.loaded = true;
|
this.loaded = true;
|
||||||
this.refreshIcon = CoreConstants.ICON_REFRESH;
|
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -73,7 +71,6 @@ export class AddonModFolderIndexComponent extends CoreCourseModuleMainResourceCo
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
this.loaded = true;
|
this.loaded = true;
|
||||||
this.refreshIcon = CoreConstants.ICON_REFRESH;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -87,13 +84,9 @@ export class AddonModFolderIndexComponent extends CoreCourseModuleMainResourceCo
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Download folder contents.
|
* @inheritdoc
|
||||||
*
|
|
||||||
* @param refresh Whether we're refreshing data.
|
|
||||||
* @return Promise resolved when done.
|
|
||||||
*/
|
*/
|
||||||
protected async fetchContent(refresh = false): Promise<void> {
|
protected async fetchContent(refresh = false): Promise<void> {
|
||||||
try {
|
|
||||||
this.folderInstance = await AddonModFolder.getFolder(this.courseId, this.module.id);
|
this.folderInstance = await AddonModFolder.getFolder(this.courseId, this.module.id);
|
||||||
|
|
||||||
const contents = await CoreCourse.getModuleContents(this.module, undefined, undefined, false, refresh);
|
const contents = await CoreCourse.getModuleContents(this.module, undefined, undefined, false, refresh);
|
||||||
|
@ -102,9 +95,6 @@ export class AddonModFolderIndexComponent extends CoreCourseModuleMainResourceCo
|
||||||
|
|
||||||
this.description = this.folderInstance ? this.folderInstance.intro : this.module.description;
|
this.description = this.folderInstance ? this.folderInstance.intro : this.module.description;
|
||||||
this.contents = AddonModFolderHelper.formatContents(contents);
|
this.contents = AddonModFolderHelper.formatContents(contents);
|
||||||
} finally {
|
|
||||||
this.fillContextMenu(refresh);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -1,33 +1,8 @@
|
||||||
<!-- Buttons to add to the header. -->
|
<!-- Buttons to add to the header. -->
|
||||||
<core-navbar-buttons slot="end">
|
<core-navbar-buttons slot="end">
|
||||||
<core-context-menu>
|
<ion-button fill="clear" (click)="openModuleSummary()" aria-haspopup="true" [attr.aria-label]="'core.info' | translate">
|
||||||
<core-context-menu-item *ngIf="externalUrl" [priority]="900" [content]="'core.openinbrowser' | translate" [href]="externalUrl"
|
<ion-icon name="fas-info-circle" slot="icon-only" aria-hidden="true"></ion-icon>
|
||||||
iconAction="fas-external-link-alt" [showBrowserWarning]="false">
|
</ion-button>
|
||||||
</core-context-menu-item>
|
|
||||||
<core-context-menu-item *ngIf="description" [priority]="800" [content]="'core.moduleintro' | translate"
|
|
||||||
(action)="expandDescription()" iconAction="fas-arrow-right">
|
|
||||||
</core-context-menu-item>
|
|
||||||
<core-context-menu-item *ngIf="blog" [priority]="750" content="{{'addon.blog.blog' | translate}}" iconAction="far-newspaper"
|
|
||||||
(action)="gotoBlog()">
|
|
||||||
</core-context-menu-item>
|
|
||||||
<core-context-menu-item *ngIf="discussions && discussions.loaded && !(hasOffline || hasOfflineRatings) && isOnline" [priority]="700"
|
|
||||||
[content]="'addon.mod_forum.refreshdiscussions' | translate" [iconAction]="refreshIcon" [closeOnClick]="false"
|
|
||||||
(action)="doRefresh(null, $event)">
|
|
||||||
</core-context-menu-item>
|
|
||||||
<core-context-menu-item *ngIf="discussions && discussions.loaded && (hasOffline || hasOfflineRatings) && isOnline" [priority]="600"
|
|
||||||
[content]="'core.settings.synchronizenow' | translate" [iconAction]="syncIcon" [closeOnClick]="false"
|
|
||||||
(action)="doRefresh(null, $event, true)">
|
|
||||||
</core-context-menu-item>
|
|
||||||
<core-context-menu-item *ngIf="prefetchStatusIcon" [priority]="500" [content]="prefetchText" [iconAction]="prefetchStatusIcon"
|
|
||||||
[closeOnClick]="false" (action)="prefetch($event)">
|
|
||||||
</core-context-menu-item>
|
|
||||||
<core-context-menu-item *ngIf="size" iconDescription="fas-archive" iconAction="fas-trash" [priority]="400"
|
|
||||||
[content]="'core.clearstoreddata' | translate:{$a: size}" [closeOnClick]="false" (action)="removeFiles($event)">
|
|
||||||
</core-context-menu-item>
|
|
||||||
<core-context-menu-item *ngIf="sortingAvailable" iconAction="fas-sort" [priority]="300" [content]="'core.sort' | translate"
|
|
||||||
(action)="showSortOrderSelector()">
|
|
||||||
</core-context-menu-item>
|
|
||||||
</core-context-menu>
|
|
||||||
</core-navbar-buttons>
|
</core-navbar-buttons>
|
||||||
|
|
||||||
<!-- Content. -->
|
<!-- Content. -->
|
||||||
|
|
|
@ -296,13 +296,9 @@ export class AddonModForumIndexComponent extends CoreCourseModuleMainActivityCom
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Download the component contents.
|
* @inheritdoc
|
||||||
*
|
|
||||||
* @param refresh Whether we're refreshing data.
|
|
||||||
* @param sync If the refresh needs syncing.
|
|
||||||
* @param showErrors Wether to show errors to the user or hide them.
|
|
||||||
*/
|
*/
|
||||||
protected async fetchContent(refresh: boolean = false, sync: boolean = false, showErrors: boolean = false): Promise<void> {
|
protected async fetchContent(refresh = false, sync = false, showErrors = false): Promise<void> {
|
||||||
this.fetchFailed = false;
|
this.fetchFailed = false;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
@ -329,8 +325,6 @@ export class AddonModForumIndexComponent extends CoreCourseModuleMainActivityCom
|
||||||
this.fetchFailed = true; // Set to prevent infinite calls with infinite-loading.
|
this.fetchFailed = true; // Set to prevent infinite calls with infinite-loading.
|
||||||
|
|
||||||
throw error; // Pass the error to the parent catch.
|
throw error; // Pass the error to the parent catch.
|
||||||
} finally {
|
|
||||||
this.fillContextMenu(refresh);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -50,7 +50,6 @@
|
||||||
"posttomygroups": "Post a copy to all groups",
|
"posttomygroups": "Post a copy to all groups",
|
||||||
"privatereply": "Reply privately",
|
"privatereply": "Reply privately",
|
||||||
"re": "Re:",
|
"re": "Re:",
|
||||||
"refreshdiscussions": "Refresh discussions",
|
|
||||||
"refreshposts": "Refresh posts",
|
"refreshposts": "Refresh posts",
|
||||||
"removefromfavourites": "Unstar this discussion",
|
"removefromfavourites": "Unstar this discussion",
|
||||||
"reply": "Reply",
|
"reply": "Reply",
|
||||||
|
|
|
@ -9,33 +9,9 @@
|
||||||
<ion-icon name="fas-search" slot="icon-only" aria-hidden="true"></ion-icon>
|
<ion-icon name="fas-search" slot="icon-only" aria-hidden="true"></ion-icon>
|
||||||
</ion-button>
|
</ion-button>
|
||||||
|
|
||||||
<core-context-menu>
|
<ion-button fill="clear" (click)="openModuleSummary()" aria-haspopup="true" [attr.aria-label]="'core.info' | translate">
|
||||||
<core-context-menu-item *ngIf="externalUrl" [priority]="900" [content]="'core.openinbrowser' | translate" [href]="externalUrl"
|
<ion-icon name="fas-info-circle" slot="icon-only" aria-hidden="true"></ion-icon>
|
||||||
iconAction="fas-external-link-alt" [showBrowserWarning]="false">
|
</ion-button>
|
||||||
</core-context-menu-item>
|
|
||||||
<core-context-menu-item *ngIf="description" [priority]="800" [content]="'core.moduleintro' | translate"
|
|
||||||
(action)="expandDescription()" iconAction="fas-arrow-right">
|
|
||||||
</core-context-menu-item>
|
|
||||||
<core-context-menu-item *ngIf="blog" [priority]="750" content="{{'addon.blog.blog' | translate}}" iconAction="far-newspaper"
|
|
||||||
(action)="gotoBlog()">
|
|
||||||
</core-context-menu-item>
|
|
||||||
<core-context-menu-item *ngIf="loaded && !(hasOffline || hasOfflineRatings) && isOnline" [priority]="700"
|
|
||||||
[content]="'core.refresh' | translate" (action)="doRefresh(null, $event)" [iconAction]="refreshIcon" [closeOnClick]="false">
|
|
||||||
</core-context-menu-item>
|
|
||||||
<core-context-menu-item *ngIf="loaded && (hasOffline || hasOfflineRatings) && isOnline" [priority]="600"
|
|
||||||
(action)="doRefresh(null, $event, true)" [content]="'core.settings.synchronizenow' | translate" [iconAction]="syncIcon"
|
|
||||||
[closeOnClick]="false">
|
|
||||||
</core-context-menu-item>
|
|
||||||
<core-context-menu-item *ngIf="canAdd" [priority]="550" [content]="'addon.mod_glossary.addentry' | translate"
|
|
||||||
(action)="openNewEntry()" iconAction="fas-plus">
|
|
||||||
</core-context-menu-item>
|
|
||||||
<core-context-menu-item *ngIf="prefetchStatusIcon" [priority]="500" [content]="prefetchText" (action)="prefetch($event)"
|
|
||||||
[iconAction]="prefetchStatusIcon" [closeOnClick]="false">
|
|
||||||
</core-context-menu-item>
|
|
||||||
<core-context-menu-item *ngIf="size" [priority]="400" [content]="'core.clearstoreddata' | translate:{$a: size}"
|
|
||||||
iconDescription="fas-archive" (action)="removeFiles($event)" iconAction="fas-trash" [closeOnClick]="false">
|
|
||||||
</core-context-menu-item>
|
|
||||||
</core-context-menu>
|
|
||||||
</core-navbar-buttons>
|
</core-navbar-buttons>
|
||||||
|
|
||||||
<!-- Content. -->
|
<!-- Content. -->
|
||||||
|
|
|
@ -177,10 +177,9 @@ export class AddonModGlossaryIndexComponent extends CoreCourseModuleMainActivity
|
||||||
/**
|
/**
|
||||||
* @inheritdoc
|
* @inheritdoc
|
||||||
*/
|
*/
|
||||||
protected async fetchContent(refresh: boolean = false, sync: boolean = false, showErrors: boolean = false): Promise<void> {
|
protected async fetchContent(refresh = false, sync = false, showErrors = false): Promise<void> {
|
||||||
const entries = await this.promisedEntries;
|
const entries = await this.promisedEntries;
|
||||||
|
|
||||||
try {
|
|
||||||
await entries.getSource().loadGlossary();
|
await entries.getSource().loadGlossary();
|
||||||
|
|
||||||
if (!this.glossary) {
|
if (!this.glossary) {
|
||||||
|
@ -207,9 +206,6 @@ export class AddonModGlossaryIndexComponent extends CoreCourseModuleMainActivity
|
||||||
]);
|
]);
|
||||||
|
|
||||||
this.hasOfflineRatings = hasOfflineRatings;
|
this.hasOfflineRatings = hasOfflineRatings;
|
||||||
} finally {
|
|
||||||
this.fillContextMenu(refresh);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -3,33 +3,16 @@
|
||||||
<core-context-menu>
|
<core-context-menu>
|
||||||
<core-context-menu-item *ngIf="h5pActivity && h5pActivity.enabletracking && accessInfo && !accessInfo.canreviewattempts"
|
<core-context-menu-item *ngIf="h5pActivity && h5pActivity.enabletracking && accessInfo && !accessInfo.canreviewattempts"
|
||||||
[priority]="1000" [content]="'addon.mod_h5pactivity.attempts_report' | translate" (action)="viewMyAttempts()"
|
[priority]="1000" [content]="'addon.mod_h5pactivity.attempts_report' | translate" (action)="viewMyAttempts()"
|
||||||
iconAction="stats-chart">
|
iconAction="fas-chart-bar">
|
||||||
</core-context-menu-item>
|
</core-context-menu-item>
|
||||||
<core-context-menu-item *ngIf="canViewAllAttempts" [priority]="1000" [content]="'addon.mod_h5pactivity.attempts_report' | translate"
|
<core-context-menu-item *ngIf="canViewAllAttempts" [priority]="1000" [content]="'addon.mod_h5pactivity.attempts_report' | translate"
|
||||||
(action)="viewAllAttempts()" iconAction="stats-chart">
|
(action)="viewAllAttempts()" iconAction="fas-chart-bar">
|
||||||
</core-context-menu-item>
|
|
||||||
<core-context-menu-item *ngIf="externalUrl" [priority]="900" [content]="'core.openinbrowser' | translate" [href]="externalUrl"
|
|
||||||
iconAction="fas-external-link-alt" [showBrowserWarning]="false">
|
|
||||||
</core-context-menu-item>
|
|
||||||
<core-context-menu-item *ngIf="description" [priority]="800" [content]="'core.moduleintro' | translate"
|
|
||||||
(action)="expandDescription()" iconAction="fas-arrow-right">
|
|
||||||
</core-context-menu-item>
|
|
||||||
<core-context-menu-item *ngIf="blog" [priority]="750" content="{{'addon.blog.blog' | translate}}" iconAction="far-newspaper"
|
|
||||||
(action)="gotoBlog()">
|
|
||||||
</core-context-menu-item>
|
|
||||||
<core-context-menu-item *ngIf="loaded && !hasOffline && isOnline" [priority]="700" [content]="'core.refresh' | translate"
|
|
||||||
(action)="doRefresh(null, $event)" [iconAction]="refreshIcon" [closeOnClick]="false">
|
|
||||||
</core-context-menu-item>
|
|
||||||
<core-context-menu-item *ngIf="loaded && hasOffline && isOnline" [priority]="600" (action)="doRefresh(null, $event, true)"
|
|
||||||
[content]="'core.settings.synchronizenow' | translate" [iconAction]="syncIcon" [closeOnClick]="false">
|
|
||||||
</core-context-menu-item>
|
|
||||||
<core-context-menu-item *ngIf="prefetchStatusIcon" [priority]="500" [content]="prefetchText" (action)="prefetch($event)"
|
|
||||||
[iconAction]="prefetchStatusIcon" [closeOnClick]="false">
|
|
||||||
</core-context-menu-item>
|
|
||||||
<core-context-menu-item *ngIf="size" [priority]="400" [content]="'core.clearstoreddata' | translate:{$a: size}"
|
|
||||||
iconDescription="fas-archive" (action)="removeFiles($event)" iconAction="fas-trash" [closeOnClick]="false">
|
|
||||||
</core-context-menu-item>
|
</core-context-menu-item>
|
||||||
</core-context-menu>
|
</core-context-menu>
|
||||||
|
|
||||||
|
<ion-button fill="clear" (click)="openModuleSummary()" aria-haspopup="true" [attr.aria-label]="'core.info' | translate">
|
||||||
|
<ion-icon name="fas-info-circle" slot="icon-only" aria-hidden="true"></ion-icon>
|
||||||
|
</ion-button>
|
||||||
</core-navbar-buttons>
|
</core-navbar-buttons>
|
||||||
|
|
||||||
<!-- Content. -->
|
<!-- Content. -->
|
||||||
|
|
|
@ -112,8 +112,7 @@ export class AddonModH5PActivityIndexComponent extends CoreCourseModuleMainActiv
|
||||||
/**
|
/**
|
||||||
* @inheritdoc
|
* @inheritdoc
|
||||||
*/
|
*/
|
||||||
protected async fetchContent(refresh: boolean = false, sync: boolean = false, showErrors: boolean = false): Promise<void> {
|
protected async fetchContent(refresh?: boolean, sync = false, showErrors = false): Promise<void> {
|
||||||
try {
|
|
||||||
this.h5pActivity = await AddonModH5PActivity.getH5PActivity(this.courseId, this.module.id, {
|
this.h5pActivity = await AddonModH5PActivity.getH5PActivity(this.courseId, this.module.id, {
|
||||||
siteId: this.siteId,
|
siteId: this.siteId,
|
||||||
});
|
});
|
||||||
|
@ -155,9 +154,6 @@ export class AddonModH5PActivityIndexComponent extends CoreCourseModuleMainActiv
|
||||||
// Package is small, download it automatically. Don't block this function for this.
|
// Package is small, download it automatically. Don't block this function for this.
|
||||||
this.downloadAutomatically();
|
this.downloadAutomatically();
|
||||||
}
|
}
|
||||||
} finally {
|
|
||||||
this.fillContextMenu(refresh);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -529,19 +525,6 @@ export class AddonModH5PActivityIndexComponent extends CoreCourseModuleMainActiv
|
||||||
this.checkHasOffline();
|
this.checkHasOffline();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @inheritdoc
|
|
||||||
*/
|
|
||||||
async gotoBlog(): Promise<void> {
|
|
||||||
this.isOpeningPage = true;
|
|
||||||
|
|
||||||
try {
|
|
||||||
await super.gotoBlog();
|
|
||||||
} finally {
|
|
||||||
this.isOpeningPage = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Component destroyed.
|
* Component destroyed.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -3,26 +3,10 @@
|
||||||
<ion-button *ngIf="loaded" (click)="showToc()" aria-haspopup="true" [attr.aria-label]="'addon.mod_imscp.toc' | translate">
|
<ion-button *ngIf="loaded" (click)="showToc()" aria-haspopup="true" [attr.aria-label]="'addon.mod_imscp.toc' | translate">
|
||||||
<ion-icon name="fas-bookmark" slot="icon-only" aria-hidden="true"></ion-icon>
|
<ion-icon name="fas-bookmark" slot="icon-only" aria-hidden="true"></ion-icon>
|
||||||
</ion-button>
|
</ion-button>
|
||||||
<core-context-menu>
|
|
||||||
<core-context-menu-item *ngIf="externalUrl" [priority]="900" [content]="'core.openinbrowser' | translate" [href]="externalUrl"
|
<ion-button fill="clear" (click)="openModuleSummary()" aria-haspopup="true" [attr.aria-label]="'core.info' | translate">
|
||||||
iconAction="fas-external-link-alt" [showBrowserWarning]="false">
|
<ion-icon name="fas-info-circle" slot="icon-only" aria-hidden="true"></ion-icon>
|
||||||
</core-context-menu-item>
|
</ion-button>
|
||||||
<core-context-menu-item *ngIf="description" [priority]="800" [content]="'core.moduleintro' | translate"
|
|
||||||
(action)="expandDescription()" iconAction="fas-arrow-right">
|
|
||||||
</core-context-menu-item>
|
|
||||||
<core-context-menu-item *ngIf="blog" [priority]="750" content="{{'addon.blog.blog' | translate}}" iconAction="far-newspaper"
|
|
||||||
(action)="gotoBlog()">
|
|
||||||
</core-context-menu-item>
|
|
||||||
<core-context-menu-item [priority]="700" [content]="'core.refresh' | translate" (action)="doRefresh(null, $event)"
|
|
||||||
[iconAction]="refreshIcon" [closeOnClick]="false">
|
|
||||||
</core-context-menu-item>
|
|
||||||
<core-context-menu-item *ngIf="prefetchStatusIcon" [priority]="600" [content]="prefetchText" (action)="prefetch($event)"
|
|
||||||
[iconAction]="prefetchStatusIcon" [closeOnClick]="false">
|
|
||||||
</core-context-menu-item>
|
|
||||||
<core-context-menu-item *ngIf="size" [priority]="500" [content]="'core.clearstoreddata' | translate:{$a: size}"
|
|
||||||
iconDescription="fas-archive" (action)="removeFiles($event)" iconAction="fas-trash" [closeOnClick]="false">
|
|
||||||
</core-context-menu-item>
|
|
||||||
</core-context-menu>
|
|
||||||
</core-navbar-buttons>
|
</core-navbar-buttons>
|
||||||
|
|
||||||
<!-- Content. -->
|
<!-- Content. -->
|
||||||
|
|
|
@ -39,6 +39,7 @@ export class AddonModImscpIndexComponent extends CoreCourseModuleMainResourceCom
|
||||||
|
|
||||||
protected items: AddonModImscpTocItem[] = [];
|
protected items: AddonModImscpTocItem[] = [];
|
||||||
protected currentHref?: string;
|
protected currentHref?: string;
|
||||||
|
protected displayDescription = false;
|
||||||
|
|
||||||
constructor(@Optional() courseContentsPage?: CoreCourseContentsPage) {
|
constructor(@Optional() courseContentsPage?: CoreCourseContentsPage) {
|
||||||
super('AddonModImscpIndexComponent', courseContentsPage);
|
super('AddonModImscpIndexComponent', courseContentsPage);
|
||||||
|
@ -70,13 +71,9 @@ export class AddonModImscpIndexComponent extends CoreCourseModuleMainResourceCom
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Download imscp contents.
|
* @inheritdoc
|
||||||
*
|
|
||||||
* @param refresh Whether we're refreshing data.
|
|
||||||
* @return Promise resolved when done.
|
|
||||||
*/
|
*/
|
||||||
protected async fetchContent(refresh = false): Promise<void> {
|
protected async fetchContent(refresh = false): Promise<void> {
|
||||||
try {
|
|
||||||
const downloadResult = await this.downloadResourceIfNeeded(refresh);
|
const downloadResult = await this.downloadResourceIfNeeded(refresh);
|
||||||
|
|
||||||
const imscp = await AddonModImscp.getImscp(this.courseId, this.module.id);
|
const imscp = await AddonModImscp.getImscp(this.courseId, this.module.id);
|
||||||
|
@ -101,11 +98,6 @@ export class AddonModImscpIndexComponent extends CoreCourseModuleMainResourceCom
|
||||||
}
|
}
|
||||||
|
|
||||||
this.warning = downloadResult.failed ? this.getErrorDownloadingSomeFilesMessage(downloadResult.error!) : '';
|
this.warning = downloadResult.failed ? this.getErrorDownloadingSomeFilesMessage(downloadResult.error!) : '';
|
||||||
|
|
||||||
} finally {
|
|
||||||
// Pass false because downloadResourceIfNeeded already invalidates and refresh data if refresh=true.
|
|
||||||
this.fillContextMenu(false);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -1,28 +1,8 @@
|
||||||
<!-- Buttons to add to the header. -->
|
<!-- Buttons to add to the header. -->
|
||||||
<core-navbar-buttons slot="end">
|
<core-navbar-buttons slot="end">
|
||||||
<core-context-menu>
|
<ion-button fill="clear" (click)="openModuleSummary()" aria-haspopup="true" [attr.aria-label]="'core.info' | translate">
|
||||||
<core-context-menu-item *ngIf="externalUrl" [priority]="900" [content]="'core.openinbrowser' | translate" [href]="externalUrl"
|
<ion-icon name="fas-info-circle" slot="icon-only" aria-hidden="true"></ion-icon>
|
||||||
iconAction="fas-external-link-alt" [showBrowserWarning]="false">
|
</ion-button>
|
||||||
</core-context-menu-item>
|
|
||||||
<core-context-menu-item *ngIf="description" [priority]="800" [content]="'core.moduleintro' | translate"
|
|
||||||
(action)="expandDescription()" iconAction="fas-arrow-right">
|
|
||||||
</core-context-menu-item>
|
|
||||||
<core-context-menu-item *ngIf="blog" [priority]="750" content="{{'addon.blog.blog' | translate}}" iconAction="far-newspaper"
|
|
||||||
(action)="gotoBlog()">
|
|
||||||
</core-context-menu-item>
|
|
||||||
<core-context-menu-item *ngIf="loaded && !hasOffline && isOnline" [priority]="700" [content]="'core.refresh' | translate"
|
|
||||||
(action)="doRefresh(null, $event)" [iconAction]="refreshIcon" [closeOnClick]="false">
|
|
||||||
</core-context-menu-item>
|
|
||||||
<core-context-menu-item *ngIf="loaded && hasOffline && isOnline" [priority]="600" (action)="doRefresh(null, $event, true)"
|
|
||||||
[content]="'core.settings.synchronizenow' | translate" [iconAction]="syncIcon" [closeOnClick]="false">
|
|
||||||
</core-context-menu-item>
|
|
||||||
<core-context-menu-item *ngIf="prefetchStatusIcon" [priority]="500" [content]="prefetchText" (action)="prefetch($event)"
|
|
||||||
[iconAction]="prefetchStatusIcon" [closeOnClick]="false">
|
|
||||||
</core-context-menu-item>
|
|
||||||
<core-context-menu-item *ngIf="size" [priority]="400" [content]="'core.clearstoreddata' | translate:{$a: size}"
|
|
||||||
iconDescription="fas-archive" (action)="removeFiles($event)" iconAction="fas-trash" [closeOnClick]="false">
|
|
||||||
</core-context-menu-item>
|
|
||||||
</core-context-menu>
|
|
||||||
</core-navbar-buttons>
|
</core-navbar-buttons>
|
||||||
|
|
||||||
<!-- Content. -->
|
<!-- Content. -->
|
||||||
|
|
|
@ -73,7 +73,6 @@ export class AddonModLessonIndexComponent extends CoreCourseModuleMainActivityCo
|
||||||
canManage?: boolean; // Whether the user can manage the lesson.
|
canManage?: boolean; // Whether the user can manage the lesson.
|
||||||
canViewReports?: boolean; // Whether the user can view the lesson reports.
|
canViewReports?: boolean; // Whether the user can view the lesson reports.
|
||||||
showSpinner?: boolean; // Whether to display a spinner.
|
showSpinner?: boolean; // Whether to display a spinner.
|
||||||
hasOffline?: boolean; // Whether there's offline data.
|
|
||||||
retakeToReview?: AddonModLessonRetakeFinishedInSyncDBRecord; // A retake to review.
|
retakeToReview?: AddonModLessonRetakeFinishedInSyncDBRecord; // A retake to review.
|
||||||
preventReasons: AddonModLessonPreventAccessReason[] = []; // List of reasons that prevent the lesson from being seen.
|
preventReasons: AddonModLessonPreventAccessReason[] = []; // List of reasons that prevent the lesson from being seen.
|
||||||
leftDuringTimed?: boolean; // Whether the user has started and left a retake.
|
leftDuringTimed?: boolean; // Whether the user has started and left a retake.
|
||||||
|
@ -136,15 +135,9 @@ export class AddonModLessonIndexComponent extends CoreCourseModuleMainActivityCo
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the lesson data.
|
* @inheritdoc
|
||||||
*
|
|
||||||
* @param refresh If it's refreshing content.
|
|
||||||
* @param sync If it should try to sync.
|
|
||||||
* @param showErrors If show errors to the user of hide them.
|
|
||||||
* @return Promise resolved when done.
|
|
||||||
*/
|
*/
|
||||||
protected async fetchContent(refresh: boolean = false, sync: boolean = false, showErrors: boolean = false): Promise<void> {
|
protected async fetchContent(refresh?: boolean, sync = false, showErrors = false): Promise<void> {
|
||||||
try {
|
|
||||||
let lessonReady = true;
|
let lessonReady = true;
|
||||||
this.askPassword = false;
|
this.askPassword = false;
|
||||||
|
|
||||||
|
@ -213,9 +206,6 @@ export class AddonModLessonIndexComponent extends CoreCourseModuleMainActivityCo
|
||||||
// Lesson can be started, don't ask the password and don't show prevent messages.
|
// Lesson can be started, don't ask the password and don't show prevent messages.
|
||||||
this.lessonReady();
|
this.lessonReady();
|
||||||
}
|
}
|
||||||
} finally {
|
|
||||||
this.fillContextMenu(refresh);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -567,13 +557,9 @@ export class AddonModLessonIndexComponent extends CoreCourseModuleMainActivityCo
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Displays some data based on the current status.
|
* @inheritdoc
|
||||||
*
|
|
||||||
* @param status The current status.
|
|
||||||
* @param previousStatus The previous status. If not defined, there is no previous status.
|
|
||||||
*/
|
*/
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
protected showStatus(status: string): void {
|
||||||
protected showStatus(status: string, previousStatus?: string): void {
|
|
||||||
this.showSpinner = status == CoreConstants.DOWNLOADING;
|
this.showSpinner = status == CoreConstants.DOWNLOADING;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -633,8 +619,6 @@ export class AddonModLessonIndexComponent extends CoreCourseModuleMainActivityCo
|
||||||
}
|
}
|
||||||
|
|
||||||
this.loaded = false;
|
this.loaded = false;
|
||||||
this.refreshIcon = CoreConstants.ICON_LOADING;
|
|
||||||
this.syncIcon = CoreConstants.ICON_LOADING;
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await this.validatePassword(<string> password);
|
await this.validatePassword(<string> password);
|
||||||
|
@ -652,8 +636,6 @@ export class AddonModLessonIndexComponent extends CoreCourseModuleMainActivityCo
|
||||||
CoreDomUtils.showErrorModal(error);
|
CoreDomUtils.showErrorModal(error);
|
||||||
} finally {
|
} finally {
|
||||||
this.loaded = true;
|
this.loaded = true;
|
||||||
this.refreshIcon = CoreConstants.ICON_REFRESH;
|
|
||||||
this.syncIcon = CoreConstants.ICON_SYNC;
|
|
||||||
|
|
||||||
CoreForms.triggerFormSubmittedEvent(this.formElement, true, this.siteId);
|
CoreForms.triggerFormSubmittedEvent(this.formElement, true, this.siteId);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,26 +1,15 @@
|
||||||
<!-- Buttons to add to the header. -->
|
<!-- Buttons to add to the header. -->
|
||||||
<core-navbar-buttons slot="end">
|
<core-navbar-buttons slot="end">
|
||||||
<core-context-menu>
|
<ion-button fill="clear" (click)="openModuleSummary()" aria-haspopup="true" [attr.aria-label]="'core.info' | translate">
|
||||||
<core-context-menu-item *ngIf="externalUrl" [priority]="900" [content]="'core.openinbrowser' | translate" [href]="externalUrl"
|
<ion-icon name="fas-info-circle" slot="icon-only" aria-hidden="true"></ion-icon>
|
||||||
iconAction="fas-external-link-alt" [showBrowserWarning]="false">
|
</ion-button>
|
||||||
</core-context-menu-item>
|
|
||||||
<core-context-menu-item *ngIf="description" [priority]="800" [content]="'core.moduleintro' | translate"
|
|
||||||
(action)="expandDescription()" iconAction="fas-arrow-right">
|
|
||||||
</core-context-menu-item>
|
|
||||||
<core-context-menu-item *ngIf="blog" [priority]="750" content="{{'addon.blog.blog' | translate}}" iconAction="far-newspaper"
|
|
||||||
(action)="gotoBlog()">
|
|
||||||
</core-context-menu-item>
|
|
||||||
<core-context-menu-item *ngIf="loaded && isOnline" [priority]="700" [content]="'core.refresh' | translate"
|
|
||||||
(action)="doRefresh(null, $event)" [iconAction]="refreshIcon" [closeOnClick]="false">
|
|
||||||
</core-context-menu-item>
|
|
||||||
</core-context-menu>
|
|
||||||
</core-navbar-buttons>
|
</core-navbar-buttons>
|
||||||
|
|
||||||
<!-- Content. -->
|
<!-- Content. -->
|
||||||
<core-loading [hideUntil]="loaded" class="safe-area-padding">
|
<core-loading [hideUntil]="loaded" class="safe-area-padding">
|
||||||
|
|
||||||
<!-- Activity info. -->
|
<!-- Activity info. -->
|
||||||
<core-course-module-info [module]="module" [description]="lti && lti.showdescriptionlaunch && description" [component]="component"
|
<core-course-module-info [module]="module" [description]="displayDescription && description" [component]="component"
|
||||||
[componentId]="componentId" [courseId]="courseId">
|
[componentId]="componentId" [courseId]="courseId">
|
||||||
</core-course-module-info>
|
</core-course-module-info>
|
||||||
|
|
||||||
|
|
|
@ -31,6 +31,7 @@ export class AddonModLtiIndexComponent extends CoreCourseModuleMainActivityCompo
|
||||||
|
|
||||||
component = AddonModLtiProvider.COMPONENT;
|
component = AddonModLtiProvider.COMPONENT;
|
||||||
moduleName = 'lti';
|
moduleName = 'lti';
|
||||||
|
displayDescription = false;
|
||||||
|
|
||||||
lti?: AddonModLtiLti; // The LTI object.
|
lti?: AddonModLtiLti; // The LTI object.
|
||||||
|
|
||||||
|
@ -55,15 +56,13 @@ export class AddonModLtiIndexComponent extends CoreCourseModuleMainActivityCompo
|
||||||
/**
|
/**
|
||||||
* @inheritdoc
|
* @inheritdoc
|
||||||
*/
|
*/
|
||||||
protected async fetchContent(refresh: boolean = false): Promise<void> {
|
protected async fetchContent(): Promise<void> {
|
||||||
try {
|
|
||||||
this.lti = await AddonModLti.getLti(this.courseId, this.module.id);
|
this.lti = await AddonModLti.getLti(this.courseId, this.module.id);
|
||||||
|
|
||||||
this.description = this.lti.intro;
|
this.description = this.lti.intro;
|
||||||
|
|
||||||
|
this.displayDescription = this.lti && !!this.lti.showdescriptionlaunch;
|
||||||
this.dataRetrieved.emit(this.lti);
|
this.dataRetrieved.emit(this.lti);
|
||||||
} finally {
|
|
||||||
this.fillContextMenu(refresh);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -1,25 +1,8 @@
|
||||||
<!-- Buttons to add to the header. -->
|
<!-- Buttons to add to the header. -->
|
||||||
<core-navbar-buttons slot="end">
|
<core-navbar-buttons slot="end">
|
||||||
<core-context-menu>
|
<ion-button fill="clear" (click)="openModuleSummary()" aria-haspopup="true" [attr.aria-label]="'core.info' | translate">
|
||||||
<core-context-menu-item *ngIf="externalUrl" [priority]="900" [content]="'core.openinbrowser' | translate" [href]="externalUrl"
|
<ion-icon name="fas-info-circle" slot="icon-only" aria-hidden="true"></ion-icon>
|
||||||
iconAction="fas-external-link-alt" [showBrowserWarning]="false">
|
</ion-button>
|
||||||
</core-context-menu-item>
|
|
||||||
<core-context-menu-item *ngIf="description" [priority]="800" [content]="'core.moduleintro' | translate"
|
|
||||||
(action)="expandDescription()" iconAction="fas-arrow-right">
|
|
||||||
</core-context-menu-item>
|
|
||||||
<core-context-menu-item *ngIf="blog" [priority]="750" content="{{'addon.blog.blog' | translate}}" iconAction="far-newspaper"
|
|
||||||
(action)="gotoBlog()">
|
|
||||||
</core-context-menu-item>
|
|
||||||
<core-context-menu-item [priority]="700" [content]="'core.refresh' | translate" (action)="doRefresh(null, $event)"
|
|
||||||
[iconAction]="refreshIcon" [closeOnClick]="false">
|
|
||||||
</core-context-menu-item>
|
|
||||||
<core-context-menu-item *ngIf="prefetchStatusIcon" [priority]="600" [content]="prefetchText" (action)="prefetch($event)"
|
|
||||||
[iconAction]="prefetchStatusIcon" [closeOnClick]="false">
|
|
||||||
</core-context-menu-item>
|
|
||||||
<core-context-menu-item *ngIf="size" [priority]="500" [content]="'core.clearstoreddata' | translate:{$a: size}"
|
|
||||||
iconDescription="fas-archive" (action)="removeFiles($event)" iconAction="fas-trash" [closeOnClick]="false">
|
|
||||||
</core-context-menu-item>
|
|
||||||
</core-context-menu>
|
|
||||||
</core-navbar-buttons>
|
</core-navbar-buttons>
|
||||||
|
|
||||||
<!-- Content. -->
|
<!-- Content. -->
|
||||||
|
|
|
@ -71,13 +71,9 @@ export class AddonModPageIndexComponent extends CoreCourseModuleMainResourceComp
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Download page contents.
|
* @inheritdoc
|
||||||
*
|
|
||||||
* @param refresh Whether we're refreshing data.
|
|
||||||
* @return Promise resolved when done.
|
|
||||||
*/
|
*/
|
||||||
protected async fetchContent(refresh?: boolean): Promise<void> {
|
protected async fetchContent(refresh?: boolean): Promise<void> {
|
||||||
try {
|
|
||||||
// Download the resource if it needs to be downloaded.
|
// Download the resource if it needs to be downloaded.
|
||||||
const downloadResult = await this.downloadResourceIfNeeded(refresh);
|
const downloadResult = await this.downloadResourceIfNeeded(refresh);
|
||||||
|
|
||||||
|
@ -91,9 +87,6 @@ export class AddonModPageIndexComponent extends CoreCourseModuleMainResourceComp
|
||||||
|
|
||||||
this.contents = results[1];
|
this.contents = results[1];
|
||||||
this.warning = downloadResult?.failed ? this.getErrorDownloadingSomeFilesMessage(downloadResult.error!) : '';
|
this.warning = downloadResult?.failed ? this.getErrorDownloadingSomeFilesMessage(downloadResult.error!) : '';
|
||||||
} finally {
|
|
||||||
this.fillContextMenu(refresh);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -1,28 +1,8 @@
|
||||||
<!-- Buttons to add to the header. -->
|
<!-- Buttons to add to the header. -->
|
||||||
<core-navbar-buttons slot="end">
|
<core-navbar-buttons slot="end">
|
||||||
<core-context-menu>
|
<ion-button fill="clear" (click)="openModuleSummary()" aria-haspopup="true" [attr.aria-label]="'core.info' | translate">
|
||||||
<core-context-menu-item *ngIf="externalUrl" [priority]="900" [content]="'core.openinbrowser' | translate" [href]="externalUrl"
|
<ion-icon name="fas-info-circle" slot="icon-only" aria-hidden="true"></ion-icon>
|
||||||
iconAction="fas-external-link-alt" [showBrowserWarning]="false">
|
</ion-button>
|
||||||
</core-context-menu-item>
|
|
||||||
<core-context-menu-item *ngIf="description" [priority]="800" [content]="'core.moduleintro' | translate"
|
|
||||||
(action)="expandDescription()" iconAction="fas-arrow-right">
|
|
||||||
</core-context-menu-item>
|
|
||||||
<core-context-menu-item *ngIf="blog" [priority]="750" content="{{'addon.blog.blog' | translate}}" [iconAction]="'far-newspaper'"
|
|
||||||
(action)="gotoBlog()">
|
|
||||||
</core-context-menu-item>
|
|
||||||
<core-context-menu-item *ngIf="loaded && !hasOffline && isOnline" [priority]="700" [content]="'core.refresh' | translate"
|
|
||||||
(action)="doRefresh(null, $event)" [iconAction]="refreshIcon" [closeOnClick]="false">
|
|
||||||
</core-context-menu-item>
|
|
||||||
<core-context-menu-item *ngIf="loaded && hasOffline && isOnline" [priority]="600" (action)="doRefresh(null, $event, true)"
|
|
||||||
[content]="'core.settings.synchronizenow' | translate" [iconAction]="syncIcon" [closeOnClick]="false">
|
|
||||||
</core-context-menu-item>
|
|
||||||
<core-context-menu-item *ngIf="prefetchStatusIcon" [priority]="500" [content]="prefetchText" (action)="prefetch($event)"
|
|
||||||
[iconAction]="prefetchStatusIcon" [closeOnClick]="false">
|
|
||||||
</core-context-menu-item>
|
|
||||||
<core-context-menu-item *ngIf="size" [priority]="400" [content]="'core.clearstoreddata' | translate:{$a: size}"
|
|
||||||
iconDescription="fas-archive" (action)="removeFiles($event)" iconAction="fas-trash" [closeOnClick]="false">
|
|
||||||
</core-context-menu-item>
|
|
||||||
</core-context-menu>
|
|
||||||
</core-navbar-buttons>
|
</core-navbar-buttons>
|
||||||
|
|
||||||
<!-- Content. -->
|
<!-- Content. -->
|
||||||
|
@ -210,7 +190,7 @@
|
||||||
|
|
||||||
<!-- Button to open in browser if it cannot be attempted in the app. -->
|
<!-- Button to open in browser if it cannot be attempted in the app. -->
|
||||||
<ion-button class="ion-text-wrap ion-margin" *ngIf="!buttonText && ((!hasSupportedQuestions && unsupportedQuestions.length) ||
|
<ion-button class="ion-text-wrap ion-margin" *ngIf="!buttonText && ((!hasSupportedQuestions && unsupportedQuestions.length) ||
|
||||||
unsupportedRules.length || behaviourSupported === false)" expand="block" [href]="externalUrl" core-link
|
unsupportedRules.length || behaviourSupported === false)" expand="block" [href]="module.url" core-link
|
||||||
[showBrowserWarning]="false">
|
[showBrowserWarning]="false">
|
||||||
{{ 'core.openinbrowser' | translate }}
|
{{ 'core.openinbrowser' | translate }}
|
||||||
<ion-icon name="fas-external-link-alt" slot="end" aria-hidden="true"></ion-icon>
|
<ion-icon name="fas-external-link-alt" slot="end" aria-hidden="true"></ion-icon>
|
||||||
|
|
|
@ -180,15 +180,9 @@ export class AddonModQuizIndexComponent extends CoreCourseModuleMainActivityComp
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the quiz data.
|
* @inheritdoc
|
||||||
*
|
|
||||||
* @param refresh If it's refreshing content.
|
|
||||||
* @param sync If it should try to sync.
|
|
||||||
* @param showErrors If show errors to the user of hide them.
|
|
||||||
* @return Promise resolved when done.
|
|
||||||
*/
|
*/
|
||||||
protected async fetchContent(refresh: boolean = false, sync: boolean = false, showErrors: boolean = false): Promise<void> {
|
protected async fetchContent(refresh?: boolean, sync = false, showErrors = false): Promise<void> {
|
||||||
try {
|
|
||||||
// First get the quiz instance.
|
// First get the quiz instance.
|
||||||
const quiz = await AddonModQuiz.getQuiz(this.courseId, this.module.id);
|
const quiz = await AddonModQuiz.getQuiz(this.courseId, this.module.id);
|
||||||
|
|
||||||
|
@ -253,9 +247,6 @@ export class AddonModQuizIndexComponent extends CoreCourseModuleMainActivityComp
|
||||||
|
|
||||||
// Quiz is ready to be shown, move it to the variable that is displayed.
|
// Quiz is ready to be shown, move it to the variable that is displayed.
|
||||||
this.quiz = quiz;
|
this.quiz = quiz;
|
||||||
} finally {
|
|
||||||
this.fillContextMenu(refresh);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -465,16 +456,12 @@ export class AddonModQuizIndexComponent extends CoreCourseModuleMainActivityComp
|
||||||
|
|
||||||
// Refresh data.
|
// Refresh data.
|
||||||
this.loaded = false;
|
this.loaded = false;
|
||||||
this.refreshIcon = CoreConstants.ICON_LOADING;
|
|
||||||
this.syncIcon = CoreConstants.ICON_LOADING;
|
|
||||||
this.content?.scrollToTop();
|
this.content?.scrollToTop();
|
||||||
|
|
||||||
await promise;
|
await promise;
|
||||||
await CoreUtils.ignoreErrors(this.refreshContent(true));
|
await CoreUtils.ignoreErrors(this.refreshContent(true));
|
||||||
|
|
||||||
this.loaded = true;
|
this.loaded = true;
|
||||||
this.refreshIcon = CoreConstants.ICON_REFRESH;
|
|
||||||
this.syncIcon = CoreConstants.ICON_SYNC;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -1,29 +1,16 @@
|
||||||
<!-- Buttons to add to the header. -->
|
<!-- Buttons to add to the header. -->
|
||||||
<core-navbar-buttons slot="end">
|
<core-navbar-buttons slot="end">
|
||||||
<core-context-menu>
|
<ion-button fill="clear" (click)="openModuleSummary()" aria-haspopup="true" [attr.aria-label]="'core.info' | translate">
|
||||||
<core-context-menu-item *ngIf="externalUrl" [priority]="900" [content]="'core.openinbrowser' | translate" [href]="externalUrl"
|
<ion-icon name="fas-info-circle" slot="icon-only" aria-hidden="true"></ion-icon>
|
||||||
iconAction="fas-external-link-alt" [showBrowserWarning]="false"></core-context-menu-item>
|
</ion-button>
|
||||||
<core-context-menu-item *ngIf="description" [priority]="800" [content]="'core.moduleintro' | translate"
|
|
||||||
(action)="expandDescription()" iconAction="fas-arrow-right"></core-context-menu-item>
|
|
||||||
<core-context-menu-item *ngIf="blog" [priority]="750" content="{{'addon.blog.blog' | translate}}" iconAction="far-newspaper"
|
|
||||||
(action)="gotoBlog()"></core-context-menu-item>
|
|
||||||
<core-context-menu-item [priority]="700" [content]="'core.refresh' | translate" (action)="doRefresh(null, $event)"
|
|
||||||
[iconAction]="refreshIcon" [closeOnClick]="false"></core-context-menu-item>
|
|
||||||
<core-context-menu-item *ngIf="prefetchStatusIcon" [priority]="600" [content]="prefetchText" (action)="prefetch($event)"
|
|
||||||
[iconAction]="prefetchStatusIcon" [closeOnClick]="false"></core-context-menu-item>
|
|
||||||
<core-context-menu-item *ngIf="size" [priority]="500" [content]="'core.clearstoreddata' | translate:{$a: size}"
|
|
||||||
iconDescription="fas-archive" (action)="removeFiles($event)" iconAction="fas-trash" [closeOnClick]="false">
|
|
||||||
</core-context-menu-item>
|
|
||||||
</core-context-menu>
|
|
||||||
</core-navbar-buttons>
|
</core-navbar-buttons>
|
||||||
|
|
||||||
<!-- Content. -->
|
<!-- Content. -->
|
||||||
<core-loading [hideUntil]="loaded" class="safe-area-padding">
|
<core-loading [hideUntil]="loaded" class="safe-area-padding">
|
||||||
|
|
||||||
<!-- Activity info. -->
|
<!-- Activity info. -->
|
||||||
<core-course-module-info [module]="module" [courseId]="courseId"
|
<core-course-module-info [module]="module" [courseId]="courseId" [description]="displayDescription && description"
|
||||||
[description]="mode != 'iframe' && (mode != 'embedded' || displayDescription) && description" [component]="component"
|
[component]="component" [componentId]="componentId">
|
||||||
[componentId]="componentId">
|
|
||||||
</core-course-module-info>
|
</core-course-module-info>
|
||||||
|
|
||||||
<ion-card class="core-warning-card" *ngIf="warning">
|
<ion-card class="core-warning-card" *ngIf="warning">
|
||||||
|
@ -76,7 +63,7 @@
|
||||||
<ion-label>
|
<ion-label>
|
||||||
<h3>{{ 'core.lastdownloaded' | translate }}</h3>
|
<h3>{{ 'core.lastdownloaded' | translate }}</h3>
|
||||||
<p>{{ downloadTimeReadable }}</p>
|
<p>{{ downloadTimeReadable }}</p>
|
||||||
<ion-grid *ngIf="prefetchStatus === outdatedStatus" class="addon-mod_resource-outdated">
|
<ion-grid *ngIf="currentStatus === outdatedStatus" class="addon-mod_resource-outdated">
|
||||||
<ion-row class="ion-align-items-center">
|
<ion-row class="ion-align-items-center">
|
||||||
<ion-col size="auto">
|
<ion-col size="auto">
|
||||||
<ion-icon color="warning" name="fas-exclamation-triangle" aria-hidden="true"></ion-icon>
|
<ion-icon color="warning" name="fas-exclamation-triangle" aria-hidden="true"></ion-icon>
|
||||||
|
|
|
@ -116,20 +116,18 @@ export class AddonModResourceIndexComponent extends CoreCourseModuleMainResource
|
||||||
throw new CoreError(Translate.instant('core.filenotfound'));
|
throw new CoreError(Translate.instant('core.filenotfound'));
|
||||||
}
|
}
|
||||||
|
|
||||||
let hasCalledDownloadResource = false;
|
|
||||||
|
|
||||||
// Get the resource instance to get the latest name/description and to know if it's embedded.
|
// Get the resource instance to get the latest name/description and to know if it's embedded.
|
||||||
const resource = await AddonModResource.getResourceData(this.courseId, this.module.id);
|
const resource = await AddonModResource.getResourceData(this.courseId, this.module.id);
|
||||||
this.description = resource.intro || '';
|
this.description = resource.intro || '';
|
||||||
const options: AddonModResourceCustomData =
|
const options: AddonModResourceCustomData =
|
||||||
resource.displayoptions ? CoreTextUtils.unserialize(resource.displayoptions) : {};
|
resource.displayoptions ? CoreTextUtils.unserialize(resource.displayoptions) : {};
|
||||||
|
|
||||||
try {
|
|
||||||
this.displayDescription = options.printintro === undefined || !!options.printintro;
|
this.displayDescription = options.printintro === undefined || !!options.printintro;
|
||||||
this.dataRetrieved.emit(resource);
|
this.dataRetrieved.emit(resource);
|
||||||
|
|
||||||
|
this.setStatusListener();
|
||||||
|
|
||||||
if (AddonModResourceHelper.isDisplayedInIframe(this.module)) {
|
if (AddonModResourceHelper.isDisplayedInIframe(this.module)) {
|
||||||
hasCalledDownloadResource = true;
|
|
||||||
|
|
||||||
const downloadResult = await this.downloadResourceIfNeeded(refresh, true);
|
const downloadResult = await this.downloadResourceIfNeeded(refresh, true);
|
||||||
const src = await AddonModResourceHelper.getIframeSrc(this.module);
|
const src = await AddonModResourceHelper.getIframeSrc(this.module);
|
||||||
|
@ -146,6 +144,9 @@ export class AddonModResourceIndexComponent extends CoreCourseModuleMainResource
|
||||||
this.src = src;
|
this.src = src;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Never show description on iframe.
|
||||||
|
this.displayDescription = false;
|
||||||
|
|
||||||
this.warning = downloadResult.failed
|
this.warning = downloadResult.failed
|
||||||
? this.getErrorDownloadingSomeFilesMessage(downloadResult.error!)
|
? this.getErrorDownloadingSomeFilesMessage(downloadResult.error!)
|
||||||
: '';
|
: '';
|
||||||
|
@ -164,6 +165,9 @@ export class AddonModResourceIndexComponent extends CoreCourseModuleMainResource
|
||||||
this.warning = '';
|
this.warning = '';
|
||||||
let mimetype: string;
|
let mimetype: string;
|
||||||
|
|
||||||
|
// Always show description on external.
|
||||||
|
this.displayDescription = true;
|
||||||
|
|
||||||
if (this.isIOS) {
|
if (this.isIOS) {
|
||||||
this.shouldOpenInBrowser = CoreFileHelper.shouldOpenInBrowser(contents[0]);
|
this.shouldOpenInBrowser = CoreFileHelper.shouldOpenInBrowser(contents[0]);
|
||||||
}
|
}
|
||||||
|
@ -183,10 +187,6 @@ export class AddonModResourceIndexComponent extends CoreCourseModuleMainResource
|
||||||
this.type = CoreMimetypeUtils.getMimetypeDescription(mimetype);
|
this.type = CoreMimetypeUtils.getMimetypeDescription(mimetype);
|
||||||
this.isStreamedFile = CoreMimetypeUtils.isStreamedMimetype(mimetype);
|
this.isStreamedFile = CoreMimetypeUtils.isStreamedMimetype(mimetype);
|
||||||
}
|
}
|
||||||
} finally {
|
|
||||||
// Pass false in some cases because downloadResourceIfNeeded already invalidates and refresh data if refresh=true.
|
|
||||||
this.fillContextMenu(hasCalledDownloadResource ? false : refresh);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -204,7 +204,7 @@ export class AddonModResourceIndexComponent extends CoreCourseModuleMainResource
|
||||||
downloadable = await AddonModResourceHelper.isMainFileDownloadable(this.module);
|
downloadable = await AddonModResourceHelper.isMainFileDownloadable(this.module);
|
||||||
|
|
||||||
if (downloadable) {
|
if (downloadable) {
|
||||||
if (this.prefetchStatus === CoreConstants.OUTDATED && !this.isOnline) {
|
if (this.currentStatus === CoreConstants.OUTDATED && !this.isOnline) {
|
||||||
// Warn the user that the file isn't updated.
|
// Warn the user that the file isn't updated.
|
||||||
const alert = await CoreDomUtils.showAlert(
|
const alert = await CoreDomUtils.showAlert(
|
||||||
undefined,
|
undefined,
|
||||||
|
|
|
@ -1,28 +1,8 @@
|
||||||
<!-- Buttons to add to the header. -->
|
<!-- Buttons to add to the header. -->
|
||||||
<core-navbar-buttons slot="end">
|
<core-navbar-buttons slot="end">
|
||||||
<core-context-menu>
|
<ion-button fill="clear" (click)="openModuleSummary()" aria-haspopup="true" [attr.aria-label]="'core.info' | translate">
|
||||||
<core-context-menu-item *ngIf="externalUrl" [priority]="900" [content]="'core.openinbrowser' | translate" [href]="externalUrl"
|
<ion-icon name="fas-info-circle" slot="icon-only" aria-hidden="true"></ion-icon>
|
||||||
iconAction="fas-external-link-alt" [showBrowserWarning]="false">
|
</ion-button>
|
||||||
</core-context-menu-item>
|
|
||||||
<core-context-menu-item *ngIf="description" [priority]="800" [content]="'core.moduleintro' | translate"
|
|
||||||
(action)="expandDescription()" iconAction="fas-arrow-right">
|
|
||||||
</core-context-menu-item>
|
|
||||||
<core-context-menu-item *ngIf="blog" [priority]="750" content="{{'addon.blog.blog' | translate}}" iconAction="far-newspaper"
|
|
||||||
(action)="gotoBlog()">
|
|
||||||
</core-context-menu-item>
|
|
||||||
<core-context-menu-item *ngIf="loaded && !hasOffline && isOnline" [priority]="700" [content]="'core.refresh' | translate"
|
|
||||||
(action)="doRefresh(null, $event)" [iconAction]="refreshIcon" [closeOnClick]="false">
|
|
||||||
</core-context-menu-item>
|
|
||||||
<core-context-menu-item *ngIf="loaded && hasOffline && isOnline" [priority]="600" (action)="doRefresh(null, $event, true)"
|
|
||||||
[content]="'core.settings.synchronizenow' | translate" [iconAction]="syncIcon" [closeOnClick]="false">
|
|
||||||
</core-context-menu-item>
|
|
||||||
<core-context-menu-item *ngIf="prefetchStatusIcon" [priority]="500" [content]="prefetchText" (action)="prefetch($event)"
|
|
||||||
[iconAction]="prefetchStatusIcon" [closeOnClick]="false">
|
|
||||||
</core-context-menu-item>
|
|
||||||
<core-context-menu-item *ngIf="size" [priority]="400" [content]="'core.clearstoreddata' | translate:{$a: size}"
|
|
||||||
iconDescription="fas-archive" (action)="removeFiles($event)" iconAction="fas-trash" [closeOnClick]="false">
|
|
||||||
</core-context-menu-item>
|
|
||||||
</core-context-menu>
|
|
||||||
</core-navbar-buttons>
|
</core-navbar-buttons>
|
||||||
|
|
||||||
<!-- Content. -->
|
<!-- Content. -->
|
||||||
|
@ -172,7 +152,7 @@
|
||||||
<p class="text-danger">{{ errorMessage | translate }}</p>
|
<p class="text-danger">{{ errorMessage | translate }}</p>
|
||||||
</ion-label>
|
</ion-label>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
<ion-button class="ion-margin ion-text-wrap" expand="block" [href]="externalUrl" core-link [showBrowserWarning]="false">
|
<ion-button class="ion-margin ion-text-wrap" expand="block" [href]="module.url" core-link [showBrowserWarning]="false">
|
||||||
{{ 'core.openinbrowser' | translate }}
|
{{ 'core.openinbrowser' | translate }}
|
||||||
<ion-icon name="fas-external-link-alt" slot="end" aria-hidden="true"></ion-icon>
|
<ion-icon name="fas-external-link-alt" slot="end" aria-hidden="true"></ion-icon>
|
||||||
</ion-button>
|
</ion-button>
|
||||||
|
|
|
@ -175,8 +175,7 @@ export class AddonModScormIndexComponent extends CoreCourseModuleMainActivityCom
|
||||||
/**
|
/**
|
||||||
* @inheritdoc
|
* @inheritdoc
|
||||||
*/
|
*/
|
||||||
protected async fetchContent(refresh: boolean = false, sync: boolean = false, showErrors: boolean = false): Promise<void> {
|
protected async fetchContent(refresh?: boolean, sync = false, showErrors = false): Promise<void> {
|
||||||
try {
|
|
||||||
// Get the SCORM instance.
|
// Get the SCORM instance.
|
||||||
this.scorm = await AddonModScorm.getScorm(this.courseId, this.module.id, { moduleUrl: this.module.url });
|
this.scorm = await AddonModScorm.getScorm(this.courseId, this.module.id, { moduleUrl: this.module.url });
|
||||||
|
|
||||||
|
@ -210,9 +209,6 @@ export class AddonModScormIndexComponent extends CoreCourseModuleMainActivityCom
|
||||||
this.scorm.skipview! >= AddonModScormProvider.SKIPVIEW_FIRST &&
|
this.scorm.skipview! >= AddonModScormProvider.SKIPVIEW_FIRST &&
|
||||||
(this.scorm.skipview == AddonModScormProvider.SKIPVIEW_ALWAYS || this.lastAttempt == 0);
|
(this.scorm.skipview == AddonModScormProvider.SKIPVIEW_ALWAYS || this.lastAttempt == 0);
|
||||||
}
|
}
|
||||||
} finally {
|
|
||||||
this.fillContextMenu(refresh);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -1,29 +1,8 @@
|
||||||
<!-- Buttons to add to the header. -->
|
<!-- Buttons to add to the header. -->
|
||||||
<core-navbar-buttons slot="end">
|
<core-navbar-buttons slot="end">
|
||||||
<core-context-menu>
|
<ion-button fill="clear" (click)="openModuleSummary()" aria-haspopup="true" [attr.aria-label]="'core.info' | translate">
|
||||||
<core-context-menu-item *ngIf="externalUrl" [priority]="900" [content]="'core.openinbrowser' | translate" [href]="externalUrl"
|
<ion-icon name="fas-info-circle" slot="icon-only" aria-hidden="true"></ion-icon>
|
||||||
iconAction="fas-external-link-alt" [showBrowserWarning]="false">
|
</ion-button>
|
||||||
</core-context-menu-item>
|
|
||||||
<core-context-menu-item *ngIf="description" [priority]="800" [content]="'core.moduleintro' | translate"
|
|
||||||
(action)="expandDescription()" iconAction="fas-arrow-right">
|
|
||||||
</core-context-menu-item>
|
|
||||||
<core-context-menu-item *ngIf="blog" [priority]="750" content="{{'addon.blog.blog' | translate}}" iconAction="far-newspaper"
|
|
||||||
(action)="gotoBlog()">
|
|
||||||
</core-context-menu-item>
|
|
||||||
<core-context-menu-item *ngIf="loaded && !hasOffline && isOnline" [priority]="700" [content]="'core.refresh' | translate"
|
|
||||||
(action)="doRefresh(null, $event)" [iconAction]="refreshIcon" [closeOnClick]="false">
|
|
||||||
</core-context-menu-item>
|
|
||||||
<core-context-menu-item *ngIf="loaded && hasOffline && isOnline" [priority]="600"
|
|
||||||
[content]="'core.settings.synchronizenow' | translate" (action)="doRefresh(null, $event, true)" [iconAction]="syncIcon"
|
|
||||||
[closeOnClick]="false">
|
|
||||||
</core-context-menu-item>
|
|
||||||
<core-context-menu-item *ngIf="prefetchStatusIcon" [priority]="500" [content]="prefetchText" (action)="prefetch($event)"
|
|
||||||
[iconAction]="prefetchStatusIcon" [closeOnClick]="false">
|
|
||||||
</core-context-menu-item>
|
|
||||||
<core-context-menu-item *ngIf="size" [priority]="400" [content]="'core.clearstoreddata' | translate:{$a: size}"
|
|
||||||
iconDescription="fas-archive" (action)="removeFiles($event)" iconAction="fas-trash" [closeOnClick]="false">
|
|
||||||
</core-context-menu-item>
|
|
||||||
</core-context-menu>
|
|
||||||
</core-navbar-buttons>
|
</core-navbar-buttons>
|
||||||
|
|
||||||
<!-- Content. -->
|
<!-- Content. -->
|
||||||
|
@ -37,7 +16,7 @@
|
||||||
<!-- Survey already done -->
|
<!-- Survey already done -->
|
||||||
<ion-card class="ion-padding" *ngIf="survey && survey.surveydone">
|
<ion-card class="ion-padding" *ngIf="survey && survey.surveydone">
|
||||||
<p class="ion-padding">{{ 'addon.mod_survey.surveycompletednograph' | translate }}</p>
|
<p class="ion-padding">{{ 'addon.mod_survey.surveycompletednograph' | translate }}</p>
|
||||||
<ion-button expand="block" [href]="externalUrl" core-link>
|
<ion-button expand="block" [href]="module.url" core-link>
|
||||||
<ion-icon name="fas-external-link-alt" slot="start" aria-hidden="true"></ion-icon>
|
<ion-icon name="fas-external-link-alt" slot="start" aria-hidden="true"></ion-icon>
|
||||||
{{ 'addon.mod_survey.results' | translate }}
|
{{ 'addon.mod_survey.results' | translate }}
|
||||||
</ion-button>
|
</ion-button>
|
||||||
|
|
|
@ -115,15 +115,9 @@ export class AddonModSurveyIndexComponent extends CoreCourseModuleMainActivityCo
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Download survey contents.
|
* @inheritdoc
|
||||||
*
|
|
||||||
* @param refresh If it's refreshing content.
|
|
||||||
* @param sync If it should try to sync.
|
|
||||||
* @param showErrors If show errors to the user of hide them.
|
|
||||||
* @return Promise resolved when done.
|
|
||||||
*/
|
*/
|
||||||
protected async fetchContent(refresh: boolean = false, sync: boolean = false, showErrors: boolean = false): Promise<void> {
|
protected async fetchContent(refresh?: boolean, sync = false, showErrors = false): Promise<void> {
|
||||||
try {
|
|
||||||
this.survey = await AddonModSurvey.getSurvey(this.courseId, this.module.id);
|
this.survey = await AddonModSurvey.getSurvey(this.courseId, this.module.id);
|
||||||
|
|
||||||
this.description = this.survey.intro;
|
this.description = this.survey.intro;
|
||||||
|
@ -146,9 +140,6 @@ export class AddonModSurveyIndexComponent extends CoreCourseModuleMainActivityCo
|
||||||
if (!this.survey.surveydone && !this.hasOffline) {
|
if (!this.survey.surveydone && !this.hasOffline) {
|
||||||
await this.fetchQuestions();
|
await this.fetchQuestions();
|
||||||
}
|
}
|
||||||
} finally {
|
|
||||||
this.fillContextMenu(refresh);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -1,15 +1,8 @@
|
||||||
<!-- Buttons to add to the header. -->
|
<!-- Buttons to add to the header. -->
|
||||||
<core-navbar-buttons slot="end">
|
<core-navbar-buttons slot="end">
|
||||||
<core-context-menu>
|
<ion-button fill="clear" (click)="openModuleSummary()" aria-haspopup="true" [attr.aria-label]="'core.info' | translate">
|
||||||
<core-context-menu-item *ngIf="externalUrl" [priority]="900" [content]="'core.openinbrowser' | translate" [href]="externalUrl"
|
<ion-icon name="fas-info-circle" slot="icon-only" aria-hidden="true"></ion-icon>
|
||||||
iconAction="fas-external-link-alt" [showBrowserWarning]="false"></core-context-menu-item>
|
</ion-button>
|
||||||
<core-context-menu-item *ngIf="description" [priority]="800" [content]="'core.moduleintro' | translate"
|
|
||||||
(action)="expandDescription()" iconAction="fas-arrow-right"></core-context-menu-item>
|
|
||||||
<core-context-menu-item *ngIf="blog" [priority]="750" content="{{'addon.blog.blog' | translate}}" iconAction="far-newspaper"
|
|
||||||
(action)="gotoBlog()"></core-context-menu-item>
|
|
||||||
<core-context-menu-item [priority]="700" [content]="'core.refresh' | translate" (action)="doRefresh(null, $event)"
|
|
||||||
[iconAction]="refreshIcon" [closeOnClick]="false"></core-context-menu-item>
|
|
||||||
</core-context-menu>
|
|
||||||
</core-navbar-buttons>
|
</core-navbar-buttons>
|
||||||
|
|
||||||
<!-- Content. -->
|
<!-- Content. -->
|
||||||
|
|
|
@ -13,35 +13,17 @@
|
||||||
</ion-button>
|
</ion-button>
|
||||||
|
|
||||||
<core-context-menu>
|
<core-context-menu>
|
||||||
<core-context-menu-item *ngIf="externalUrl" [priority]="900" [content]="'core.openinbrowser' | translate" [href]="externalUrl"
|
|
||||||
iconAction="fas-external-link-alt" [showBrowserWarning]="false">
|
|
||||||
</core-context-menu-item>
|
|
||||||
<core-context-menu-item *ngIf="description" [priority]="800" [content]="'core.moduleintro' | translate"
|
|
||||||
(action)="expandDescription()" iconAction="fas-arrow-right">
|
|
||||||
</core-context-menu-item>
|
|
||||||
<core-context-menu-item *ngIf="blog" [priority]="750" content="{{'addon.blog.blog' | translate}}" iconAction="far-newspaper"
|
|
||||||
(action)="gotoBlog()">
|
|
||||||
</core-context-menu-item>
|
|
||||||
<core-context-menu-item *ngIf="loaded && !hasOffline && isOnline && !pageIsOffline" [priority]="700"
|
|
||||||
[content]="'core.refresh' | translate" (action)="doRefresh(null, $event)" [iconAction]="refreshIcon" [closeOnClick]="false">
|
|
||||||
</core-context-menu-item>
|
|
||||||
<core-context-menu-item *ngIf="loaded && isOnline && (hasOffline || pageIsOffline)" [priority]="600"
|
|
||||||
[content]="'core.settings.synchronizenow' | translate" (action)="doRefresh(null, $event, true)" [iconAction]="syncIcon"
|
|
||||||
[closeOnClick]="false">
|
|
||||||
</core-context-menu-item>
|
|
||||||
<core-context-menu-item *ngIf="canEdit && (isOnline || pageIsOffline)" [priority]="590" [content]="'core.edit' | translate"
|
<core-context-menu-item *ngIf="canEdit && (isOnline || pageIsOffline)" [priority]="590" [content]="'core.edit' | translate"
|
||||||
iconAction="fas-edit" (action)="goToEditPage()">
|
iconAction="fas-edit" (action)="goToEditPage()">
|
||||||
</core-context-menu-item>
|
</core-context-menu-item>
|
||||||
<core-context-menu-item *ngIf="canEdit" [priority]="580" [content]="'addon.mod_wiki.createpage' | translate" iconAction="fas-plus"
|
<core-context-menu-item *ngIf="canEdit" [priority]="580" [content]="'addon.mod_wiki.createpage' | translate" iconAction="fas-plus"
|
||||||
(action)="goToNewPage()">
|
(action)="goToNewPage()">
|
||||||
</core-context-menu-item>
|
</core-context-menu-item>
|
||||||
<core-context-menu-item *ngIf="prefetchStatusIcon" [priority]="500" [content]="prefetchText" (action)="prefetch($event)"
|
|
||||||
[iconAction]="prefetchStatusIcon" [closeOnClick]="false">
|
|
||||||
</core-context-menu-item>
|
|
||||||
<core-context-menu-item *ngIf="size" [priority]="400" [content]="'core.clearstoreddata' | translate:{$a: size}"
|
|
||||||
iconDescription="fas-archive" (action)="removeFiles($event)" iconAction="fas-trash" [closeOnClick]="false">
|
|
||||||
</core-context-menu-item>
|
|
||||||
</core-context-menu>
|
</core-context-menu>
|
||||||
|
|
||||||
|
<ion-button fill="clear" (click)="openModuleSummary()" aria-haspopup="true" [attr.aria-label]="'core.info' | translate">
|
||||||
|
<ion-icon name="fas-info-circle" slot="icon-only" aria-hidden="true"></ion-icon>
|
||||||
|
</ion-button>
|
||||||
</core-navbar-buttons>
|
</core-navbar-buttons>
|
||||||
|
|
||||||
<!-- Content. -->
|
<!-- Content. -->
|
||||||
|
|
|
@ -21,14 +21,16 @@ import { CoreCourse } from '@features/course/services/course';
|
||||||
import { CoreTag, CoreTagItem } from '@features/tag/services/tag';
|
import { CoreTag, CoreTagItem } from '@features/tag/services/tag';
|
||||||
import { CoreUser } from '@features/user/services/user';
|
import { CoreUser } from '@features/user/services/user';
|
||||||
import { IonContent } from '@ionic/angular';
|
import { IonContent } from '@ionic/angular';
|
||||||
|
import { CoreApp } from '@services/app';
|
||||||
import { CoreGroup, CoreGroups } from '@services/groups';
|
import { CoreGroup, CoreGroups } from '@services/groups';
|
||||||
import { CoreNavigator } from '@services/navigator';
|
import { CoreNavigator } from '@services/navigator';
|
||||||
import { CoreSites } from '@services/sites';
|
import { CoreSites } from '@services/sites';
|
||||||
import { CoreDomUtils } from '@services/utils/dom';
|
import { CoreDomUtils } from '@services/utils/dom';
|
||||||
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 { Translate } from '@singletons';
|
import { Network, Translate, NgZone } from '@singletons';
|
||||||
import { CoreEventObserver, CoreEvents } from '@singletons/events';
|
import { CoreEventObserver, CoreEvents } from '@singletons/events';
|
||||||
|
import { Subscription } from 'rxjs';
|
||||||
import { Md5 } from 'ts-md5';
|
import { Md5 } from 'ts-md5';
|
||||||
import { AddonModWikiPageDBRecord } from '../../services/database/wiki';
|
import { AddonModWikiPageDBRecord } from '../../services/database/wiki';
|
||||||
import { AddonModWikiModuleHandlerService } from '../../services/handlers/module';
|
import { AddonModWikiModuleHandlerService } from '../../services/handlers/module';
|
||||||
|
@ -76,6 +78,8 @@ export class AddonModWikiIndexComponent extends CoreCourseModuleMainActivityComp
|
||||||
moduleName = 'wiki';
|
moduleName = 'wiki';
|
||||||
groupWiki = false;
|
groupWiki = false;
|
||||||
|
|
||||||
|
isOnline = false;
|
||||||
|
|
||||||
wiki?: AddonModWikiWiki; // The wiki instance.
|
wiki?: AddonModWikiWiki; // The wiki instance.
|
||||||
isMainPage = false; // Whether the user is viewing wiki's main page (just entered the wiki).
|
isMainPage = false; // Whether the user is viewing wiki's main page (just entered the wiki).
|
||||||
canEdit = false; // Whether user can edit the page.
|
canEdit = false; // Whether user can edit the page.
|
||||||
|
@ -104,12 +108,23 @@ export class AddonModWikiIndexComponent extends CoreCourseModuleMainActivityComp
|
||||||
protected ignoreManualSyncEvent = false; // Whether manual sync event should be ignored.
|
protected ignoreManualSyncEvent = false; // Whether manual sync event should be ignored.
|
||||||
protected currentUserId?: number; // Current user ID.
|
protected currentUserId?: number; // Current user ID.
|
||||||
protected currentPath!: string;
|
protected currentPath!: string;
|
||||||
|
protected onlineSubscription: Subscription; // It will observe the status of the network connection.
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
protected content?: IonContent,
|
protected content?: IonContent,
|
||||||
@Optional() courseContentsPage?: CoreCourseContentsPage,
|
@Optional() courseContentsPage?: CoreCourseContentsPage,
|
||||||
) {
|
) {
|
||||||
super('AddonModLessonIndexComponent', content, courseContentsPage);
|
super('AddonModLessonIndexComponent', content, courseContentsPage);
|
||||||
|
|
||||||
|
this.isOnline = CoreApp.isOnline();
|
||||||
|
|
||||||
|
// Refresh online status when changes.
|
||||||
|
this.onlineSubscription = Network.onChange().subscribe(() => {
|
||||||
|
// Execute the callback in the Angular zone, so change detection doesn't stop working.
|
||||||
|
NgZone.run(() => {
|
||||||
|
this.isOnline = CoreApp.isOnline();
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -212,7 +227,7 @@ export class AddonModWikiIndexComponent extends CoreCourseModuleMainActivityComp
|
||||||
/**
|
/**
|
||||||
* @inheritdoc
|
* @inheritdoc
|
||||||
*/
|
*/
|
||||||
protected async fetchContent(refresh = false, sync = false, showErrors = false): Promise<void> {
|
protected async fetchContent(refresh?: boolean, sync = false, showErrors = false): Promise<void> {
|
||||||
try {
|
try {
|
||||||
// Get the wiki instance.
|
// Get the wiki instance.
|
||||||
this.wiki = await AddonModWiki.getWiki(this.courseId, this.module.id);
|
this.wiki = await AddonModWiki.getWiki(this.courseId, this.module.id);
|
||||||
|
@ -240,7 +255,6 @@ export class AddonModWikiIndexComponent extends CoreCourseModuleMainActivityComp
|
||||||
}
|
}
|
||||||
|
|
||||||
this.description = this.wiki.intro || this.module.description;
|
this.description = this.wiki.intro || this.module.description;
|
||||||
this.externalUrl = this.module.url;
|
|
||||||
this.componentId = this.module.id;
|
this.componentId = this.module.id;
|
||||||
|
|
||||||
await this.fetchSubwikis(this.wiki.id);
|
await this.fetchSubwikis(this.wiki.id);
|
||||||
|
@ -278,8 +292,6 @@ export class AddonModWikiIndexComponent extends CoreCourseModuleMainActivityComp
|
||||||
}
|
}
|
||||||
|
|
||||||
throw error;
|
throw error;
|
||||||
} finally {
|
|
||||||
this.fillContextMenu(refresh);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -835,6 +847,7 @@ export class AddonModWikiIndexComponent extends CoreCourseModuleMainActivityComp
|
||||||
|
|
||||||
this.manualSyncObserver?.off();
|
this.manualSyncObserver?.off();
|
||||||
this.newPageObserver?.off();
|
this.newPageObserver?.off();
|
||||||
|
this.onlineSubscription.unsubscribe();
|
||||||
if (this.wiki) {
|
if (this.wiki) {
|
||||||
AddonModWiki.wikiPageClosed(this.wiki.id, this.currentPath);
|
AddonModWiki.wikiPageClosed(this.wiki.id, this.currentPath);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,29 +1,8 @@
|
||||||
<!-- Buttons to add to the header. -->
|
<!-- Buttons to add to the header. -->
|
||||||
<core-navbar-buttons slot="end">
|
<core-navbar-buttons slot="end">
|
||||||
<core-context-menu>
|
<ion-button fill="clear" (click)="openModuleSummary()" aria-haspopup="true" [attr.aria-label]="'core.info' | translate">
|
||||||
<core-context-menu-item *ngIf="externalUrl" [priority]="900" [content]="'core.openinbrowser' | translate" [href]="externalUrl"
|
<ion-icon name="fas-info-circle" slot="icon-only" aria-hidden="true"></ion-icon>
|
||||||
iconAction="fas-external-link-alt" [showBrowserWarning]="false">
|
</ion-button>
|
||||||
</core-context-menu-item>
|
|
||||||
<core-context-menu-item *ngIf="description" [priority]="800" [content]="'core.moduleintro' | translate"
|
|
||||||
(action)="expandDescription()" iconAction="fas-arrow-right">
|
|
||||||
</core-context-menu-item>
|
|
||||||
<core-context-menu-item *ngIf="blog" [priority]="750" content="{{'addon.blog.blog' | translate}}" iconAction="far-newspaper"
|
|
||||||
(action)="gotoBlog()">
|
|
||||||
</core-context-menu-item>
|
|
||||||
<core-context-menu-item *ngIf="loaded && !hasOffline && isOnline" [priority]="700" [content]="'core.refresh' | translate"
|
|
||||||
(action)="doRefresh(null, $event)" [iconAction]="refreshIcon" [closeOnClick]="false">
|
|
||||||
</core-context-menu-item>
|
|
||||||
<core-context-menu-item *ngIf="loaded && hasOffline && isOnline" [priority]="600"
|
|
||||||
[content]="'core.settings.synchronizenow' | translate" (action)="doRefresh(null, $event, true)" [iconAction]="syncIcon"
|
|
||||||
[closeOnClick]="false">
|
|
||||||
</core-context-menu-item>
|
|
||||||
<core-context-menu-item *ngIf="prefetchStatusIcon" [priority]="500" [content]="prefetchText" (action)="prefetch($event)"
|
|
||||||
[iconAction]="prefetchStatusIcon" [closeOnClick]="false">
|
|
||||||
</core-context-menu-item>
|
|
||||||
<core-context-menu-item *ngIf="size" [priority]="200" [content]="'core.clearstoreddata' | translate:{$a: size}"
|
|
||||||
iconDescription="fas-archive" (action)="removeFiles($event)" iconAction="fas-trash" [closeOnClick]="false">
|
|
||||||
</core-context-menu-item>
|
|
||||||
</core-context-menu>
|
|
||||||
</core-navbar-buttons>
|
</core-navbar-buttons>
|
||||||
|
|
||||||
<!-- Content. -->
|
<!-- Content. -->
|
||||||
|
|
|
@ -216,15 +216,9 @@ export class AddonModWorkshopIndexComponent extends CoreCourseModuleMainActivity
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Download feedback contents.
|
* @inheritdoc
|
||||||
*
|
|
||||||
* @param refresh If it's refreshing content.
|
|
||||||
* @param sync If it should try to sync.
|
|
||||||
* @param showErrors If show errors to the user of hide them.
|
|
||||||
* @return Promise resolved when done.
|
|
||||||
*/
|
*/
|
||||||
protected async fetchContent(refresh = false, sync = false, showErrors = false): Promise<void> {
|
protected async fetchContent(refresh?: boolean, sync = false, showErrors = false): Promise<void> {
|
||||||
try {
|
|
||||||
this.workshop = await AddonModWorkshop.getWorkshop(this.courseId, this.module.id);
|
this.workshop = await AddonModWorkshop.getWorkshop(this.courseId, this.module.id);
|
||||||
|
|
||||||
this.description = this.workshop.intro;
|
this.description = this.workshop.intro;
|
||||||
|
@ -248,7 +242,7 @@ export class AddonModWorkshopIndexComponent extends CoreCourseModuleMainActivity
|
||||||
this.phases[this.workshop.phase].tasks.forEach((task) => {
|
this.phases[this.workshop.phase].tasks.forEach((task) => {
|
||||||
if (!task.link && (task.code == 'examples' || task.code == 'prepareexamples')) {
|
if (!task.link && (task.code == 'examples' || task.code == 'prepareexamples')) {
|
||||||
// Add links to manage examples.
|
// Add links to manage examples.
|
||||||
task.link = this.externalUrl!;
|
task.link = this.module.url || '';
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -261,10 +255,6 @@ export class AddonModWorkshopIndexComponent extends CoreCourseModuleMainActivity
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.setPhaseInfo();
|
await this.setPhaseInfo();
|
||||||
|
|
||||||
} finally {
|
|
||||||
this.fillContextMenu(refresh);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -394,7 +384,7 @@ export class AddonModWorkshopIndexComponent extends CoreCourseModuleMainActivity
|
||||||
componentProps: {
|
componentProps: {
|
||||||
phases: CoreUtils.objectToArray(this.phases),
|
phases: CoreUtils.objectToArray(this.phases),
|
||||||
workshopPhase: this.workshop!.phase,
|
workshopPhase: this.workshop!.phase,
|
||||||
externalUrl: this.externalUrl,
|
externalUrl: this.module.url,
|
||||||
showSubmit: this.showSubmit,
|
showSubmit: this.showSubmit,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -11,6 +11,7 @@
|
||||||
"deletedata": "Delete downloaded data",
|
"deletedata": "Delete downloaded data",
|
||||||
"deletedatafrom": "Delete all downloaded data from '{{name}}'",
|
"deletedatafrom": "Delete all downloaded data from '{{name}}'",
|
||||||
"downloadedcourses": "Downloaded courses",
|
"downloadedcourses": "Downloaded courses",
|
||||||
|
"downloads": "Downloads",
|
||||||
"errordeletedownloadeddata": "Error deleting downloaded data.",
|
"errordeletedownloadeddata": "Error deleting downloaded data.",
|
||||||
"managedownloads": "Manage downloads",
|
"managedownloads": "Manage downloads",
|
||||||
"totaldownloads": "Total downloads",
|
"totaldownloads": "Total downloads",
|
||||||
|
|
|
@ -13,6 +13,7 @@
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
import { Component, Input, Output, OnInit, OnDestroy, EventEmitter, OnChanges, SimpleChange } from '@angular/core';
|
import { Component, Input, Output, OnInit, OnDestroy, EventEmitter, OnChanges, SimpleChange } from '@angular/core';
|
||||||
|
import { CoreLogger } from '@singletons/logger';
|
||||||
import { CoreContextMenuComponent } from '../context-menu/context-menu';
|
import { CoreContextMenuComponent } from '../context-menu/context-menu';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -35,7 +36,6 @@ import { CoreContextMenuComponent } from '../context-menu/context-menu';
|
||||||
export class CoreContextMenuItemComponent implements OnInit, OnDestroy, OnChanges {
|
export class CoreContextMenuItemComponent implements OnInit, OnDestroy, OnChanges {
|
||||||
|
|
||||||
@Input() content?: string; // Content of the item.
|
@Input() content?: string; // Content of the item.
|
||||||
@Input() iconDescription?: string; // Name of the icon to be shown on the left side of the item.
|
|
||||||
@Input() iconAction?: string; // Name of the icon to show on the right side of the item. Represents the action to do on click.
|
@Input() iconAction?: string; // Name of the icon to show on the right side of the item. Represents the action to do on click.
|
||||||
// If is "spinner" an spinner will be shown.
|
// If is "spinner" an spinner will be shown.
|
||||||
// If is "toggle" a toggle switch will be shown.
|
// If is "toggle" a toggle switch will be shown.
|
||||||
|
@ -58,6 +58,11 @@ export class CoreContextMenuItemComponent implements OnInit, OnDestroy, OnChange
|
||||||
@Output() onClosed?: EventEmitter<() => void>; // Will emit an event when the popover is closed because the item was clicked.
|
@Output() onClosed?: EventEmitter<() => void>; // Will emit an event when the popover is closed because the item was clicked.
|
||||||
@Output() toggleChange = new EventEmitter<boolean>();// Will emit an event when toggle changes to enable 2-way data binding.
|
@Output() toggleChange = new EventEmitter<boolean>();// Will emit an event when toggle changes to enable 2-way data binding.
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated since 4.0.
|
||||||
|
*/
|
||||||
|
@Input() iconDescription?: string; // Name of the icon to be shown on the left side of the item. Not used anymore.
|
||||||
|
|
||||||
protected hasAction = false;
|
protected hasAction = false;
|
||||||
protected destroyed = false;
|
protected destroyed = false;
|
||||||
|
|
||||||
|
@ -88,6 +93,11 @@ export class CoreContextMenuItemComponent implements OnInit, OnDestroy, OnChange
|
||||||
if (!this.destroyed) {
|
if (!this.destroyed) {
|
||||||
this.ctxtMenu.addItem(this);
|
this.ctxtMenu.addItem(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.iconDescription !== undefined) {
|
||||||
|
CoreLogger.getInstance('CoreContextMenuItemComponent')
|
||||||
|
.warn('iconDescription Input is deprecated and should not be used');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -13,7 +13,7 @@
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
import { Component, Input, OnInit, OnDestroy, ElementRef, ChangeDetectorRef } from '@angular/core';
|
import { Component, Input, OnInit, OnDestroy, ElementRef, ChangeDetectorRef } from '@angular/core';
|
||||||
import { Subject } from 'rxjs';
|
import { Subject, Subscription } from 'rxjs';
|
||||||
import { auditTime } from 'rxjs/operators';
|
import { auditTime } from 'rxjs/operators';
|
||||||
import { CoreDomUtils } from '@services/utils/dom';
|
import { CoreDomUtils } from '@services/utils/dom';
|
||||||
import { CoreUtils } from '@services/utils/utils';
|
import { CoreUtils } from '@services/utils/utils';
|
||||||
|
@ -42,11 +42,12 @@ export class CoreContextMenuComponent implements OnInit, OnDestroy {
|
||||||
protected itemsChangedStream: Subject<void>; // Stream to update the hideMenu boolean when items change.
|
protected itemsChangedStream: Subject<void>; // Stream to update the hideMenu boolean when items change.
|
||||||
protected parentContextMenu?: CoreContextMenuComponent;
|
protected parentContextMenu?: CoreContextMenuComponent;
|
||||||
protected expanded = false;
|
protected expanded = false;
|
||||||
|
protected itemsSubscription: Subscription;
|
||||||
|
|
||||||
constructor(elementRef: ElementRef, changeDetector: ChangeDetectorRef) {
|
constructor(elementRef: ElementRef, changeDetector: ChangeDetectorRef) {
|
||||||
// Create the stream and subscribe to it. We ignore successive changes during 250ms.
|
// Create the stream and subscribe to it. We ignore successive changes during 250ms.
|
||||||
this.itemsChangedStream = new Subject<void>();
|
this.itemsChangedStream = new Subject<void>();
|
||||||
this.itemsChangedStream.pipe(auditTime(250)).subscribe(() => {
|
this.itemsSubscription = this.itemsChangedStream.pipe(auditTime(250)).subscribe(() => {
|
||||||
// Hide the menu if all items are hidden.
|
// Hide the menu if all items are hidden.
|
||||||
this.hideMenu = !this.items.some((item) => !item.hidden);
|
this.hideMenu = !this.items.some((item) => !item.hidden);
|
||||||
|
|
||||||
|
@ -63,7 +64,7 @@ export class CoreContextMenuComponent implements OnInit, OnDestroy {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Component being initialized.
|
* @inheritdoc
|
||||||
*/
|
*/
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.icon = this.icon || 'ellipsis-vertical';
|
this.icon = this.icon || 'ellipsis-vertical';
|
||||||
|
@ -197,10 +198,11 @@ export class CoreContextMenuComponent implements OnInit, OnDestroy {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Component destroyed.
|
* @inheritdoc
|
||||||
*/
|
*/
|
||||||
ngOnDestroy(): void {
|
ngOnDestroy(): void {
|
||||||
this.removeMergedItems();
|
this.removeMergedItems();
|
||||||
|
this.itemsSubscription.unsubscribe();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,8 +6,6 @@
|
||||||
[href]="item.href" (click)="itemClicked($event, item)" [attr.aria-label]="item.ariaAction" [hidden]="item.hidden"
|
[href]="item.href" (click)="itemClicked($event, item)" [attr.aria-label]="item.ariaAction" [hidden]="item.hidden"
|
||||||
[detail]="(item.href && !item.iconAction) || null" role="menuitem" [button]="(item.href && !item.iconAction)"
|
[detail]="(item.href && !item.iconAction) || null" role="menuitem" [button]="(item.href && !item.iconAction)"
|
||||||
[showBrowserWarning]="item.showBrowserWarning">
|
[showBrowserWarning]="item.showBrowserWarning">
|
||||||
<ion-icon *ngIf="item.iconDescription" [name]="item.iconDescription" aria-hidden="true" slot="start">
|
|
||||||
</ion-icon>
|
|
||||||
<ion-label>
|
<ion-label>
|
||||||
<p class="item-heading">
|
<p class="item-heading">
|
||||||
<core-format-text [clean]="true" [text]="item.content" [filter]="false"></core-format-text>
|
<core-format-text [clean]="true" [text]="item.content" [filter]="false"></core-format-text>
|
||||||
|
|
|
@ -1,8 +1,5 @@
|
||||||
<ion-header>
|
<ion-header class="no-title">
|
||||||
<ion-toolbar>
|
<ion-toolbar>
|
||||||
<ion-title>
|
|
||||||
<h2>{{ 'core.block.blocks' | translate }}</h2>
|
|
||||||
</ion-title>
|
|
||||||
<ion-buttons slot="end">
|
<ion-buttons slot="end">
|
||||||
<ion-button fill="clear" (click)="closeModal()" [attr.aria-label]="'core.close' | translate">
|
<ion-button fill="clear" (click)="closeModal()" [attr.aria-label]="'core.close' | translate">
|
||||||
<ion-icon name="fas-times" slot="icon-only" aria-hidden=true></ion-icon>
|
<ion-icon name="fas-times" slot="icon-only" aria-hidden=true></ion-icon>
|
||||||
|
@ -10,7 +7,7 @@
|
||||||
</ion-buttons>
|
</ion-buttons>
|
||||||
</ion-toolbar>
|
</ion-toolbar>
|
||||||
</ion-header>
|
</ion-header>
|
||||||
<ion-content>
|
<ion-content [fullscreen]="true">
|
||||||
<ion-refresher slot="fixed" [disabled]="!loaded" (ionRefresh)="doRefresh($event.target)">
|
<ion-refresher slot="fixed" [disabled]="!loaded" (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>
|
||||||
|
|
|
@ -17,15 +17,11 @@ import { IonContent } from '@ionic/angular';
|
||||||
|
|
||||||
import { CoreCourseModuleMainResourceComponent } from './main-resource-component';
|
import { CoreCourseModuleMainResourceComponent } from './main-resource-component';
|
||||||
import { CoreEventObserver, CoreEvents } from '@singletons/events';
|
import { CoreEventObserver, CoreEvents } from '@singletons/events';
|
||||||
import { Network, NgZone } from '@singletons';
|
|
||||||
import { Subscription } from 'rxjs';
|
|
||||||
import { CoreApp } from '@services/app';
|
|
||||||
import { CoreCourse } from '../services/course';
|
import { CoreCourse } from '../services/course';
|
||||||
import { CoreUtils } from '@services/utils/utils';
|
import { CoreUtils } from '@services/utils/utils';
|
||||||
import { CoreDomUtils } from '@services/utils/dom';
|
import { CoreDomUtils } from '@services/utils/dom';
|
||||||
import { CoreWSExternalWarning } from '@services/ws';
|
import { CoreWSExternalWarning } from '@services/ws';
|
||||||
import { CoreCourseContentsPage } from '../pages/contents/contents';
|
import { CoreCourseContentsPage } from '../pages/contents/contents';
|
||||||
import { CoreConstants } from '@/core/constants';
|
|
||||||
import { CoreSites } from '@services/sites';
|
import { CoreSites } from '@services/sites';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -40,13 +36,7 @@ export class CoreCourseModuleMainActivityComponent extends CoreCourseModuleMainR
|
||||||
|
|
||||||
moduleName?: string; // Raw module name to be translated. It will be translated on init.
|
moduleName?: string; // Raw module name to be translated. It will be translated on init.
|
||||||
|
|
||||||
// Data for context menu.
|
|
||||||
syncIcon?: string; // Sync icon.
|
|
||||||
hasOffline?: boolean; // If it has offline data to be synced.
|
|
||||||
isOnline?: boolean; // If the app is online or not.
|
|
||||||
|
|
||||||
protected syncObserver?: CoreEventObserver; // It will observe the sync auto event.
|
protected syncObserver?: CoreEventObserver; // It will observe the sync auto event.
|
||||||
protected onlineSubscription: Subscription; // It will observe the status of the network connection.
|
|
||||||
protected syncEventName?: string; // Auto sync event name.
|
protected syncEventName?: string; // Auto sync event name.
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
|
@ -55,14 +45,6 @@ export class CoreCourseModuleMainActivityComponent extends CoreCourseModuleMainR
|
||||||
courseContentsPage?: CoreCourseContentsPage,
|
courseContentsPage?: CoreCourseContentsPage,
|
||||||
) {
|
) {
|
||||||
super(loggerName, courseContentsPage);
|
super(loggerName, courseContentsPage);
|
||||||
|
|
||||||
// Refresh online status when changes.
|
|
||||||
this.onlineSubscription = Network.onChange().subscribe(() => {
|
|
||||||
// Execute the callback in the Angular zone, so change detection doesn't stop working.
|
|
||||||
NgZone.run(() => {
|
|
||||||
this.isOnline = CoreApp.isOnline();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -72,7 +54,6 @@ export class CoreCourseModuleMainActivityComponent extends CoreCourseModuleMainR
|
||||||
await super.ngOnInit();
|
await super.ngOnInit();
|
||||||
|
|
||||||
this.hasOffline = false;
|
this.hasOffline = false;
|
||||||
this.syncIcon = CoreConstants.ICON_LOADING;
|
|
||||||
this.moduleName = CoreCourse.translateModuleName(this.moduleName || '');
|
this.moduleName = CoreCourse.translateModuleName(this.moduleName || '');
|
||||||
|
|
||||||
if (this.syncEventName) {
|
if (this.syncEventName) {
|
||||||
|
@ -119,20 +100,12 @@ export class CoreCourseModuleMainActivityComponent extends CoreCourseModuleMainR
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.refreshIcon = CoreConstants.ICON_LOADING;
|
|
||||||
this.syncIcon = CoreConstants.ICON_LOADING;
|
|
||||||
|
|
||||||
try {
|
|
||||||
await CoreUtils.ignoreErrors(Promise.all([
|
await CoreUtils.ignoreErrors(Promise.all([
|
||||||
this.invalidateContent(),
|
this.invalidateContent(),
|
||||||
this.showCompletion ? CoreCourse.invalidateModule(this.module.id) : undefined,
|
this.showCompletion ? CoreCourse.invalidateModule(this.module.id) : undefined,
|
||||||
]));
|
]));
|
||||||
|
|
||||||
await this.loadContent(true, sync, showErrors);
|
await this.loadContent(true, sync, showErrors);
|
||||||
} finally {
|
|
||||||
this.refreshIcon = CoreConstants.ICON_REFRESH;
|
|
||||||
this.syncIcon = CoreConstants.ICON_SYNC;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -143,17 +116,10 @@ export class CoreCourseModuleMainActivityComponent extends CoreCourseModuleMainR
|
||||||
* @return Resolved when done.
|
* @return Resolved when done.
|
||||||
*/
|
*/
|
||||||
protected async showLoadingAndFetch(sync: boolean = false, showErrors: boolean = false): Promise<void> {
|
protected async showLoadingAndFetch(sync: boolean = false, showErrors: boolean = false): Promise<void> {
|
||||||
this.refreshIcon = CoreConstants.ICON_LOADING;
|
|
||||||
this.syncIcon = CoreConstants.ICON_LOADING;
|
|
||||||
this.loaded = false;
|
this.loaded = false;
|
||||||
this.content?.scrollToTop();
|
this.content?.scrollToTop();
|
||||||
|
|
||||||
try {
|
|
||||||
await this.loadContent(false, sync, showErrors);
|
await this.loadContent(false, sync, showErrors);
|
||||||
} finally {
|
|
||||||
this.refreshIcon = CoreConstants.ICON_REFRESH;
|
|
||||||
this.syncIcon = CoreConstants.ICON_REFRESH;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -164,8 +130,6 @@ export class CoreCourseModuleMainActivityComponent extends CoreCourseModuleMainR
|
||||||
* @return Resolved when done.
|
* @return Resolved when done.
|
||||||
*/
|
*/
|
||||||
protected showLoadingAndRefresh(sync: boolean = false, showErrors: boolean = false): Promise<void> {
|
protected showLoadingAndRefresh(sync: boolean = false, showErrors: boolean = false): Promise<void> {
|
||||||
this.refreshIcon = CoreConstants.ICON_LOADING;
|
|
||||||
this.syncIcon = CoreConstants.ICON_LOADING;
|
|
||||||
this.loaded = false;
|
this.loaded = false;
|
||||||
this.content?.scrollToTop();
|
this.content?.scrollToTop();
|
||||||
|
|
||||||
|
@ -194,8 +158,6 @@ export class CoreCourseModuleMainActivityComponent extends CoreCourseModuleMainR
|
||||||
* @return Promise resolved when done.
|
* @return Promise resolved when done.
|
||||||
*/
|
*/
|
||||||
protected async loadContent(refresh?: boolean, sync: boolean = false, showErrors: boolean = false): Promise<void> {
|
protected async loadContent(refresh?: boolean, sync: boolean = false, showErrors: boolean = false): Promise<void> {
|
||||||
this.isOnline = CoreApp.isOnline();
|
|
||||||
|
|
||||||
if (!this.module) {
|
if (!this.module) {
|
||||||
// This can happen if course format changes from single activity to weekly/topics.
|
// This can happen if course format changes from single activity to weekly/topics.
|
||||||
return;
|
return;
|
||||||
|
@ -216,8 +178,6 @@ export class CoreCourseModuleMainActivityComponent extends CoreCourseModuleMainR
|
||||||
CoreDomUtils.showErrorModalDefault(error, this.fetchContentDefaultError, true);
|
CoreDomUtils.showErrorModalDefault(error, this.fetchContentDefaultError, true);
|
||||||
} finally {
|
} finally {
|
||||||
this.loaded = true;
|
this.loaded = true;
|
||||||
this.refreshIcon = CoreConstants.ICON_REFRESH;
|
|
||||||
this.syncIcon = CoreConstants.ICON_REFRESH;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -270,8 +230,6 @@ export class CoreCourseModuleMainActivityComponent extends CoreCourseModuleMainR
|
||||||
*/
|
*/
|
||||||
ngOnDestroy(): void {
|
ngOnDestroy(): void {
|
||||||
super.ngOnDestroy();
|
super.ngOnDestroy();
|
||||||
|
|
||||||
this.onlineSubscription?.unsubscribe();
|
|
||||||
this.syncObserver?.off();
|
this.syncObserver?.off();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -13,14 +13,10 @@
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
import { CoreConstants } from '@/core/constants';
|
import { CoreConstants } from '@/core/constants';
|
||||||
import { AddonBlog } from '@addons/blog/services/blog';
|
|
||||||
import { AddonBlogMainMenuHandlerService } from '@addons/blog/services/handlers/mainmenu';
|
|
||||||
import { OnInit, OnDestroy, Input, Output, EventEmitter, Component, Optional, Inject } from '@angular/core';
|
import { OnInit, OnDestroy, Input, Output, EventEmitter, Component, Optional, Inject } from '@angular/core';
|
||||||
import { Params } from '@angular/router';
|
|
||||||
import { CoreAnyError } from '@classes/errors/error';
|
import { CoreAnyError } from '@classes/errors/error';
|
||||||
import { IonRefresher } from '@ionic/angular';
|
import { IonRefresher } from '@ionic/angular';
|
||||||
import { CoreApp } from '@services/app';
|
import { CoreApp } from '@services/app';
|
||||||
import { CoreNavigator } from '@services/navigator';
|
|
||||||
import { CoreSites } from '@services/sites';
|
import { CoreSites } from '@services/sites';
|
||||||
import { CoreDomUtils } from '@services/utils/dom';
|
import { CoreDomUtils } from '@services/utils/dom';
|
||||||
|
|
||||||
|
@ -29,6 +25,7 @@ import { CoreUtils } from '@services/utils/utils';
|
||||||
import { Translate } from '@singletons';
|
import { Translate } from '@singletons';
|
||||||
import { CoreEventObserver, CoreEvents } from '@singletons/events';
|
import { CoreEventObserver, CoreEvents } from '@singletons/events';
|
||||||
import { CoreLogger } from '@singletons/logger';
|
import { CoreLogger } from '@singletons/logger';
|
||||||
|
import { CoreCourseModuleSummaryComponent, CoreCourseModuleSummaryResult } from '../components/module-summary/module-summary';
|
||||||
import { CoreCourseContentsPage } from '../pages/contents/contents';
|
import { CoreCourseContentsPage } from '../pages/contents/contents';
|
||||||
import { CoreCourse } from '../services/course';
|
import { CoreCourse } from '../services/course';
|
||||||
import { CoreCourseHelper, CoreCourseModuleData } from '../services/course-helper';
|
import { CoreCourseHelper, CoreCourseModuleData } from '../services/course-helper';
|
||||||
|
@ -58,30 +55,23 @@ export class CoreCourseModuleMainResourceComponent implements OnInit, OnDestroy,
|
||||||
loaded = false; // If the component has been loaded.
|
loaded = false; // If the component has been loaded.
|
||||||
component?: string; // Component name.
|
component?: string; // Component name.
|
||||||
componentId?: number; // Component ID.
|
componentId?: number; // Component ID.
|
||||||
blog?: boolean; // If blog is available.
|
hasOffline = false; // Resources don't have any data to sync.
|
||||||
|
|
||||||
// Data for context menu.
|
|
||||||
externalUrl?: string; // External URL to open in browser.
|
|
||||||
description?: string; // Module description.
|
description?: string; // Module description.
|
||||||
refreshIcon = CoreConstants.ICON_LOADING; // Refresh icon, normally spinner or refresh.
|
|
||||||
prefetchStatusIcon?: string; // Used when calling fillContextMenu.
|
|
||||||
prefetchStatus?: string; // Used when calling fillContextMenu.
|
|
||||||
prefetchText?: string; // Used when calling fillContextMenu.
|
|
||||||
size?: string; // Used when calling fillContextMenu.
|
|
||||||
downloadTimeReadable?: string; // Last download time in a readable format. Used when calling fillContextMenu.
|
|
||||||
isDestroyed = false; // Whether the component is destroyed, used when calling fillContextMenu.
|
|
||||||
contextMenuStatusObserver?: CoreEventObserver; // Observer of package status, used when calling fillContextMenu.
|
|
||||||
contextFileStatusObserver?: CoreEventObserver; // Observer of file status, used when calling fillContextMenu.
|
|
||||||
|
|
||||||
protected fetchContentDefaultError = 'core.course.errorgetmodule'; // Default error to show when loading contents.
|
protected fetchContentDefaultError = 'core.course.errorgetmodule'; // Default error to show when loading contents.
|
||||||
protected isCurrentView = false; // Whether the component is in the current view.
|
protected isCurrentView = false; // Whether the component is in the current view.
|
||||||
protected siteId?: string; // Current Site ID.
|
protected siteId?: string; // Current Site ID.
|
||||||
protected statusObserver?: CoreEventObserver; // Observer of package status. Only if setStatusListener is called.
|
protected statusObserver?: CoreEventObserver; // Observer of package status. Only if setStatusListener is called.
|
||||||
protected currentStatus?: string; // The current status of the module. Only if setStatusListener is called.
|
currentStatus?: string; // The current status of the module. Only if setStatusListener is called.
|
||||||
|
downloadTimeReadable?: string; // Last download time in a readable format. Only if setStatusListener is called.
|
||||||
|
|
||||||
protected completionObserver?: CoreEventObserver;
|
protected completionObserver?: CoreEventObserver;
|
||||||
protected logger: CoreLogger;
|
protected logger: CoreLogger;
|
||||||
protected debouncedUpdateModule?: () => void; // Update the module after a certain time.
|
protected debouncedUpdateModule?: () => void; // Update the module after a certain time.
|
||||||
protected showCompletion = false; // Whether to show completion inside the activity.
|
protected showCompletion = false; // Whether to show completion inside the activity.
|
||||||
|
protected displayDescription = true; // Wether to show Module description on module page, and not on summary or the contrary.
|
||||||
|
protected isDestroyed = false; // Whether the component is destroyed.
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@Optional() @Inject('') loggerName: string = 'CoreCourseModuleMainResourceComponent',
|
@Optional() @Inject('') loggerName: string = 'CoreCourseModuleMainResourceComponent',
|
||||||
|
@ -91,13 +81,12 @@ export class CoreCourseModuleMainResourceComponent implements OnInit, OnDestroy,
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Component being initialized.
|
* @inheritdoc
|
||||||
*/
|
*/
|
||||||
async ngOnInit(): Promise<void> {
|
async ngOnInit(): Promise<void> {
|
||||||
this.siteId = CoreSites.getCurrentSiteId();
|
this.siteId = CoreSites.getCurrentSiteId();
|
||||||
this.description = this.module.description;
|
this.description = this.module.description;
|
||||||
this.componentId = this.module.id;
|
this.componentId = this.module.id;
|
||||||
this.externalUrl = this.module.url;
|
|
||||||
this.courseId = this.courseId || this.module.course;
|
this.courseId = this.courseId || this.module.course;
|
||||||
this.showCompletion = !!CoreSites.getRequiredCurrentSite().isVersionGreaterEqualThan('3.11');
|
this.showCompletion = !!CoreSites.getRequiredCurrentSite().isVersionGreaterEqualThan('3.11');
|
||||||
|
|
||||||
|
@ -116,20 +105,17 @@ export class CoreCourseModuleMainResourceComponent implements OnInit, OnDestroy,
|
||||||
this.fetchModule();
|
this.fetchModule();
|
||||||
}, 10000);
|
}, 10000);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.blog = await AddonBlog.isPluginEnabled();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Refresh the data.
|
* Refresh the data.
|
||||||
*
|
*
|
||||||
* @param refresher Refresher.
|
* @param refresher Refresher.
|
||||||
* @param done Function to call when done.
|
|
||||||
* @param showErrors If show errors to the user of hide them.
|
* @param showErrors If show errors to the user of hide them.
|
||||||
* @return Promise resolved when done.
|
* @return Promise resolved when done.
|
||||||
*/
|
*/
|
||||||
async doRefresh(refresher?: IonRefresher | null, done?: () => void, showErrors: boolean = false): Promise<void> {
|
async doRefresh(refresher?: IonRefresher | null, showErrors = false): Promise<void> {
|
||||||
if (!this.loaded || !this.module) {
|
if (!this.module) {
|
||||||
// Module can be undefined if course format changes from single activity to weekly/topics.
|
// Module can be undefined if course format changes from single activity to weekly/topics.
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -143,7 +129,6 @@ export class CoreCourseModuleMainResourceComponent implements OnInit, OnDestroy,
|
||||||
await CoreUtils.ignoreErrors(this.refreshContent(true, showErrors));
|
await CoreUtils.ignoreErrors(this.refreshContent(true, showErrors));
|
||||||
|
|
||||||
refresher?.complete();
|
refresher?.complete();
|
||||||
done && done();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -160,9 +145,6 @@ export class CoreCourseModuleMainResourceComponent implements OnInit, OnDestroy,
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.refreshIcon = CoreConstants.ICON_LOADING;
|
|
||||||
|
|
||||||
try {
|
|
||||||
await CoreUtils.ignoreErrors(Promise.all([
|
await CoreUtils.ignoreErrors(Promise.all([
|
||||||
this.invalidateContent(),
|
this.invalidateContent(),
|
||||||
this.showCompletion ? CoreCourse.invalidateModule(this.module.id) : undefined,
|
this.showCompletion ? CoreCourse.invalidateModule(this.module.id) : undefined,
|
||||||
|
@ -173,9 +155,6 @@ export class CoreCourseModuleMainResourceComponent implements OnInit, OnDestroy,
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.loadContent(true);
|
await this.loadContent(true);
|
||||||
} finally {
|
|
||||||
this.refreshIcon = CoreConstants.ICON_REFRESH;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -221,7 +200,6 @@ export class CoreCourseModuleMainResourceComponent implements OnInit, OnDestroy,
|
||||||
CoreDomUtils.showErrorModalDefault(error, this.fetchContentDefaultError, true);
|
CoreDomUtils.showErrorModalDefault(error, this.fetchContentDefaultError, true);
|
||||||
} finally {
|
} finally {
|
||||||
this.loaded = true;
|
this.loaded = true;
|
||||||
this.refreshIcon = CoreConstants.ICON_REFRESH;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -236,66 +214,25 @@ export class CoreCourseModuleMainResourceComponent implements OnInit, OnDestroy,
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fill the context menu options
|
* Updage package last downloaded.
|
||||||
*/
|
*/
|
||||||
protected fillContextMenu(refresh: boolean = false): void {
|
protected async getPackageLastDownloaded(): Promise<void> {
|
||||||
// All data obtained, now fill the context menu.
|
if (!this.module) {
|
||||||
CoreCourseHelper.fillContextMenu(this, this.module, this.courseId, refresh, this.component);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if the module is prefetched or being prefetched. To make it faster, just use the data calculated by fillContextMenu.
|
|
||||||
* This means that you need to call fillContextMenu to make this work.
|
|
||||||
*/
|
|
||||||
protected isPrefetched(): boolean {
|
|
||||||
return this.prefetchStatus != CoreConstants.NOT_DOWNLOADABLE && this.prefetchStatus != CoreConstants.NOT_DOWNLOADED;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Expand the description.
|
|
||||||
*/
|
|
||||||
expandDescription(): void {
|
|
||||||
CoreTextUtils.viewText(Translate.instant('core.description'), this.description!, {
|
|
||||||
component: this.component,
|
|
||||||
componentId: this.module.id,
|
|
||||||
filter: true,
|
|
||||||
contextLevel: 'module',
|
|
||||||
instanceId: this.module.id,
|
|
||||||
courseId: this.courseId,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Go to blog posts.
|
|
||||||
*/
|
|
||||||
async gotoBlog(): Promise<void> {
|
|
||||||
const params: Params = { cmId: this.module.id };
|
|
||||||
|
|
||||||
await CoreNavigator.navigateToSitePath(AddonBlogMainMenuHandlerService.PAGE_NAME, { params });
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Prefetch the module.
|
|
||||||
*
|
|
||||||
* @param done Function to call when done.
|
|
||||||
*/
|
|
||||||
prefetch(done?: () => void): void {
|
|
||||||
CoreCourseHelper.contextMenuPrefetch(this, this.module, this.courseId, done);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Confirm and remove downloaded files.
|
|
||||||
*
|
|
||||||
* @param done Function to call when done.
|
|
||||||
*/
|
|
||||||
removeFiles(done?: () => void): void {
|
|
||||||
if (this.prefetchStatus == CoreConstants.DOWNLOADING) {
|
|
||||||
CoreDomUtils.showAlertTranslated(undefined, 'core.course.cannotdeletewhiledownloading');
|
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
CoreCourseHelper.confirmAndRemoveFiles(this.module, this.courseId, done);
|
const lastDownloaded =
|
||||||
|
await CoreCourseHelper.getModulePackageLastDownloaded(this.module, this.component);
|
||||||
|
|
||||||
|
this.downloadTimeReadable = CoreTextUtils.ucFirst(lastDownloaded.downloadTimeReadable);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the module is prefetched or being prefetched.
|
||||||
|
* To make it faster, just use the data calculated by setStatusListener.
|
||||||
|
*/
|
||||||
|
protected isPrefetched(): boolean {
|
||||||
|
return this.currentStatus != CoreConstants.NOT_DOWNLOADABLE && this.currentStatus != CoreConstants.NOT_DOWNLOADED;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -355,6 +292,8 @@ export class CoreCourseModuleMainResourceComponent implements OnInit, OnDestroy,
|
||||||
const previousStatus = this.currentStatus;
|
const previousStatus = this.currentStatus;
|
||||||
this.currentStatus = data.status;
|
this.currentStatus = data.status;
|
||||||
|
|
||||||
|
this.getPackageLastDownloaded();
|
||||||
|
|
||||||
this.showStatus(this.currentStatus, previousStatus);
|
this.showStatus(this.currentStatus, previousStatus);
|
||||||
}, this.siteId);
|
}, this.siteId);
|
||||||
} else if (!refresh) {
|
} else if (!refresh) {
|
||||||
|
@ -369,6 +308,9 @@ export class CoreCourseModuleMainResourceComponent implements OnInit, OnDestroy,
|
||||||
const status = await CoreCourseModulePrefetchDelegate.getModuleStatus(this.module, this.courseId, undefined, refresh);
|
const status = await CoreCourseModulePrefetchDelegate.getModuleStatus(this.module, this.courseId, undefined, refresh);
|
||||||
|
|
||||||
this.currentStatus = status;
|
this.currentStatus = status;
|
||||||
|
|
||||||
|
this.getPackageLastDownloaded();
|
||||||
|
|
||||||
this.showStatus(status);
|
this.showStatus(status);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -449,13 +391,47 @@ export class CoreCourseModuleMainResourceComponent implements OnInit, OnDestroy,
|
||||||
this.module = module;
|
this.module = module;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Opens a module summary page.
|
||||||
|
*/
|
||||||
|
async openModuleSummary(): Promise<void> {
|
||||||
|
if (!this.module) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await CoreDomUtils.openSideModal<CoreCourseModuleSummaryResult>({
|
||||||
|
component: CoreCourseModuleSummaryComponent,
|
||||||
|
componentProps: {
|
||||||
|
moduleId: this.module.id,
|
||||||
|
module: this.module,
|
||||||
|
description: this.description,
|
||||||
|
component: this.component,
|
||||||
|
courseId: this.courseId,
|
||||||
|
hasOffline: this.hasOffline,
|
||||||
|
displayOptions: {
|
||||||
|
// Show description on summary if not shown on the page.
|
||||||
|
displayDescription: !this.displayDescription,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (data) {
|
||||||
|
if (this.loaded && (data.action == 'refresh' || data.action == 'sync')) {
|
||||||
|
this.loaded = false;
|
||||||
|
try {
|
||||||
|
await this.doRefresh(undefined, data.action == 'sync');
|
||||||
|
} finally {
|
||||||
|
this.loaded = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Component being destroyed.
|
* Component being destroyed.
|
||||||
*/
|
*/
|
||||||
ngOnDestroy(): void {
|
ngOnDestroy(): void {
|
||||||
this.isDestroyed = true;
|
this.isDestroyed = true;
|
||||||
this.contextMenuStatusObserver?.off();
|
|
||||||
this.contextFileStatusObserver?.off();
|
|
||||||
this.statusObserver?.off();
|
this.statusObserver?.off();
|
||||||
this.completionObserver?.off();
|
this.completionObserver?.off();
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,7 +16,7 @@ import { NgModule } from '@angular/core';
|
||||||
|
|
||||||
import { CoreSharedModule } from '@/core/shared.module';
|
import { CoreSharedModule } from '@/core/shared.module';
|
||||||
import { CoreBlockComponentsModule } from '@features/block/components/components.module';
|
import { CoreBlockComponentsModule } from '@features/block/components/components.module';
|
||||||
import { CoreCourseFormatComponent } from './format/format';
|
import { CoreCourseFormatComponent } from './course-format/course-format';
|
||||||
import { CoreCourseModuleComponent } from './module/module';
|
import { CoreCourseModuleComponent } from './module/module';
|
||||||
import { CoreCourseModuleCompletionComponent } from './module-completion/module-completion';
|
import { CoreCourseModuleCompletionComponent } from './module-completion/module-completion';
|
||||||
import { CoreCourseModuleDescriptionComponent } from './module-description/module-description';
|
import { CoreCourseModuleDescriptionComponent } from './module-description/module-description';
|
||||||
|
@ -27,6 +27,7 @@ import { CoreCourseModuleCompletionLegacyComponent } from './module-completion-l
|
||||||
import { CoreCourseModuleInfoComponent } from './module-info/module-info';
|
import { CoreCourseModuleInfoComponent } from './module-info/module-info';
|
||||||
import { CoreCourseModuleManualCompletionComponent } from './module-manual-completion/module-manual-completion';
|
import { CoreCourseModuleManualCompletionComponent } from './module-manual-completion/module-manual-completion';
|
||||||
import { CoreCourseModuleNavigationComponent } from './module-navigation/module-navigation';
|
import { CoreCourseModuleNavigationComponent } from './module-navigation/module-navigation';
|
||||||
|
import { CoreCourseModuleSummaryComponent } from './module-summary/module-summary';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
declarations: [
|
declarations: [
|
||||||
|
@ -41,6 +42,7 @@ import { CoreCourseModuleNavigationComponent } from './module-navigation/module-
|
||||||
CoreCourseTagAreaComponent,
|
CoreCourseTagAreaComponent,
|
||||||
CoreCourseUnsupportedModuleComponent,
|
CoreCourseUnsupportedModuleComponent,
|
||||||
CoreCourseModuleNavigationComponent,
|
CoreCourseModuleNavigationComponent,
|
||||||
|
CoreCourseModuleSummaryComponent,
|
||||||
],
|
],
|
||||||
imports: [
|
imports: [
|
||||||
CoreBlockComponentsModule,
|
CoreBlockComponentsModule,
|
||||||
|
@ -58,6 +60,7 @@ import { CoreCourseModuleNavigationComponent } from './module-navigation/module-
|
||||||
CoreCourseTagAreaComponent,
|
CoreCourseTagAreaComponent,
|
||||||
CoreCourseUnsupportedModuleComponent,
|
CoreCourseUnsupportedModuleComponent,
|
||||||
CoreCourseModuleNavigationComponent,
|
CoreCourseModuleNavigationComponent,
|
||||||
|
CoreCourseModuleSummaryComponent,
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
export class CoreCourseComponentsModule {}
|
export class CoreCourseComponentsModule {}
|
||||||
|
|
|
@ -55,8 +55,8 @@ import { CoreCourseModuleDelegate } from '@features/course/services/module-deleg
|
||||||
*/
|
*/
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'core-course-format',
|
selector: 'core-course-format',
|
||||||
templateUrl: 'core-course-format.html',
|
templateUrl: 'course-format.html',
|
||||||
styleUrls: ['format.scss'],
|
styleUrls: ['course-format.scss'],
|
||||||
})
|
})
|
||||||
export class CoreCourseFormatComponent implements OnInit, OnChanges, OnDestroy {
|
export class CoreCourseFormatComponent implements OnInit, OnChanges, OnDestroy {
|
||||||
|
|
|
@ -110,7 +110,7 @@ export class CoreCourseModuleNavigationComponent implements OnInit, OnDestroy {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Set a minimum height value.
|
// Set a minimum height value.
|
||||||
this.initialHeight = this.initialHeight || 56;
|
this.initialHeight = this.initialHeight || 48;
|
||||||
this.previousHeight = this.initialHeight;
|
this.previousHeight = this.initialHeight;
|
||||||
|
|
||||||
this.content = this.element.closest('ion-content');
|
this.content = this.element.closest('ion-content');
|
||||||
|
|
|
@ -0,0 +1,213 @@
|
||||||
|
<ion-header class="no-title">
|
||||||
|
<ion-toolbar>
|
||||||
|
<ion-buttons slot="end">
|
||||||
|
<ion-button fill="clear" (click)="closeModal()" [attr.aria-label]="'core.close' | translate">
|
||||||
|
<ion-icon slot="icon-only" name="fas-times" aria-hidden="true"></ion-icon>
|
||||||
|
</ion-button>
|
||||||
|
</ion-buttons>
|
||||||
|
</ion-toolbar>
|
||||||
|
</ion-header>
|
||||||
|
<ion-content [fullscreen]="true">
|
||||||
|
<!-- Content. -->
|
||||||
|
<core-loading [hideUntil]="loaded">
|
||||||
|
<!-- Activity info. -->
|
||||||
|
<ion-item class="ion-text-wrap" *ngIf="module" lines="full">
|
||||||
|
<ion-label>
|
||||||
|
<p *ngIf="moduleNameTranslated" class="core-modulename">
|
||||||
|
<core-mod-icon slot="start" [modicon]="modicon" [modname]="module.modname" [componentId]="module.instance">
|
||||||
|
</core-mod-icon>
|
||||||
|
{{moduleNameTranslated}}
|
||||||
|
</p>
|
||||||
|
<h1>
|
||||||
|
<core-format-text [text]="module.name" contextLevel="module" [component]="component" [componentId]="componentId"
|
||||||
|
[contextInstanceId]="module.id" [courseId]="courseId">
|
||||||
|
</core-format-text>
|
||||||
|
</h1>
|
||||||
|
</ion-label>
|
||||||
|
<ion-button fill="clear" *ngIf="displayOptions.displayOpenInBrowser" [href]="externalUrl" core-link [showBrowserWarning]="false"
|
||||||
|
color="dark" [attr.aria-label]="'core.openinbrowser' | translate" slot="end">
|
||||||
|
<ion-icon name="fas-external-link-alt" slot="icon-only" aria-hidden="true"></ion-icon>
|
||||||
|
</ion-button>
|
||||||
|
</ion-item>
|
||||||
|
|
||||||
|
<ion-item class="ion-text-wrap" *ngIf="course" (click)="openCourse()" button [detail]="true" lines="full">
|
||||||
|
<ion-label>
|
||||||
|
<p class="item-heading">
|
||||||
|
<ion-icon name="fas-graduation-cap" aria-hidden="true"></ion-icon>
|
||||||
|
{{ 'core.course' | translate}}
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<core-format-text [text]="course.displayname || course.fullname" contextLevel="course" [contextInstanceId]="courseId">
|
||||||
|
</core-format-text>
|
||||||
|
</p>
|
||||||
|
</ion-label>
|
||||||
|
</ion-item>
|
||||||
|
|
||||||
|
<ion-item class="ion-text-wrap" *ngIf="module && description && displayOptions.displayDescription" lines="full">
|
||||||
|
<ion-label>
|
||||||
|
<p class="item-heading">
|
||||||
|
{{ 'core.description' | translate}}
|
||||||
|
</p>
|
||||||
|
<core-format-text [text]="description" [component]="component" [componentId]="componentId" contextLevel="module"
|
||||||
|
[contextInstanceId]="module.id" [courseId]="courseId" [maxHeight]="120">
|
||||||
|
</core-format-text>
|
||||||
|
</ion-label>
|
||||||
|
</ion-item>
|
||||||
|
|
||||||
|
<ion-card *ngIf="(canPrefetch && displayOptions.displayPrefetch) || (sizeReadable && displayOptions.displaySize)">
|
||||||
|
<ion-item lines="full" class="ion-text-wrap">
|
||||||
|
<ion-label>
|
||||||
|
<h2>
|
||||||
|
<ion-icon name="cloud-done" aria-hidden="true"></ion-icon>
|
||||||
|
{{ 'addon.storagemanager.downloads' | translate }}
|
||||||
|
</h2>
|
||||||
|
</ion-label>
|
||||||
|
</ion-item>
|
||||||
|
<ion-item *ngIf="sizeReadable && displayOptions.displaySize" class="ion-text-wrap">
|
||||||
|
<ion-label>
|
||||||
|
<p class="item-heading ion-text-wrap">{{ 'addon.storagemanager.totalspaceusage' | translate }}</p>
|
||||||
|
<ion-badge color="light">{{ sizeReadable | coreBytesToSize }}</ion-badge>
|
||||||
|
</ion-label>
|
||||||
|
<ion-button *ngIf="!removeFilesLoading" [disabled]="prefetchLoading" (click)="removeFiles()" color="danger" fill="clear"
|
||||||
|
[attr.aria-label]="'core.clearstoreddata' | translate:{$a: sizeReadable}" slot="end">
|
||||||
|
<ion-icon name="fas-trash" slot="icon-only" aria-hidden="true"></ion-icon>
|
||||||
|
</ion-button>
|
||||||
|
<ion-spinner *ngIf="removeFilesLoading" slot="end" aria-hidden="true"></ion-spinner>
|
||||||
|
</ion-item>
|
||||||
|
<ion-item *ngIf="downloadTimeReadable" class="ion-text-wrap">
|
||||||
|
<ion-label>
|
||||||
|
<p class="ion-text-wrap">{{ 'core.lastdownloaded' | translate }} {{ downloadTimeReadable }}</p>
|
||||||
|
</ion-label>
|
||||||
|
</ion-item>
|
||||||
|
<ion-button fill="outline" expand="block" *ngIf="canPrefetch && displayOptions.displayPrefetch" class="ion-text-wrap"
|
||||||
|
(click)="prefetch()" color="primary" [disabled]="prefetchDisabled">
|
||||||
|
<ion-icon *ngIf="!prefetchLoading" name="cloud-done" slot="start" aria-hidden="true"></ion-icon>
|
||||||
|
<ion-spinner *ngIf="prefetchLoading" slot="start" aria-hidden="true"></ion-spinner>
|
||||||
|
<ion-label>
|
||||||
|
{{ 'core.download' | translate }}
|
||||||
|
</ion-label>
|
||||||
|
</ion-button>
|
||||||
|
</ion-card>
|
||||||
|
|
||||||
|
<ion-card *ngIf="displayOptions.displayGrades && grades?.length > 0">
|
||||||
|
<ion-list>
|
||||||
|
<ion-item lines="full" class="ion-text-wrap">
|
||||||
|
<ion-label>
|
||||||
|
<h2>
|
||||||
|
<ion-icon name="fas-chart-bar" slot="end" aria-hidden="true"></ion-icon>{{ 'core.grades.gradebook' | translate
|
||||||
|
}}
|
||||||
|
</h2>
|
||||||
|
</ion-label>
|
||||||
|
</ion-item>
|
||||||
|
<ng-container *ngFor="let grade of grades">
|
||||||
|
<ion-item button *ngIf="grade.gradeitem" class="ion-text-wrap divider" (click)="toggleGrade(grade)"
|
||||||
|
[attr.aria-label]="(grade.expanded ? 'core.collapse' : 'core.expand') | translate"
|
||||||
|
[attr.aria-expanded]="grade.expanded" [attr.aria-controls]="'grade-'+grade.id" role="heading" detail="false">
|
||||||
|
<ion-icon name="fas-chevron-right" flip-rtl slot="start" aria-hidden="true" class="expandable-status-icon"
|
||||||
|
[class.expandable-status-icon-expanded]="grade.expanded">
|
||||||
|
</ion-icon>
|
||||||
|
<ion-label>
|
||||||
|
<p class="item-heading" *ngIf="!grade.itemmodule">
|
||||||
|
<core-format-text [text]="grade.gradeitem" contextLevel="course" [contextInstanceId]="courseId">
|
||||||
|
</core-format-text>
|
||||||
|
</p>
|
||||||
|
<p class="item-heading" *ngIf="grade.itemmodule">
|
||||||
|
{{ 'core.grades.grade' | translate}}
|
||||||
|
</p>
|
||||||
|
<p *ngIf="grade.grade && grade.grade != '-'" [innerHTML]="grade.grade"></p>
|
||||||
|
<ion-badge *ngIf="!grade.grade || grade.grade == '-'" color="light">Not graded</ion-badge>
|
||||||
|
</ion-label>
|
||||||
|
<ion-icon *ngIf="grade.icon" name="{{grade.icon}}" slot="end" [attr.aria-label]="grade.iconAlt">
|
||||||
|
</ion-icon>
|
||||||
|
<img *ngIf="grade.image && !grade.itemmodule" [src]="grade.image" slot="end" [alt]="grade.iconAlt" />
|
||||||
|
<ion-icon *ngIf="grade.image && grade.itemmodule" name="fas-chart-bar" slot="end" [attr.aria-label]="grade.iconAlt">
|
||||||
|
</ion-icon>
|
||||||
|
</ion-item>
|
||||||
|
<div *ngIf="grade.expanded" [id]="'grade-'+grade.id">
|
||||||
|
<ion-item class="ion-text-wrap" *ngIf="grade.weight?.length > 0 && grade.weight != '-'">
|
||||||
|
<ion-label>
|
||||||
|
<p class="item-heading">{{ 'core.grades.weight' | translate}}</p>
|
||||||
|
<p [innerHTML]="grade.weight"></p>
|
||||||
|
</ion-label>
|
||||||
|
</ion-item>
|
||||||
|
<ion-item class="ion-text-wrap" *ngIf="grade.range?.length > 0 && grade.range != '-'">
|
||||||
|
<ion-label>
|
||||||
|
<p class="item-heading">{{ 'core.grades.range' | translate}}</p>
|
||||||
|
<p [innerHTML]="grade.range"></p>
|
||||||
|
</ion-label>
|
||||||
|
</ion-item>
|
||||||
|
|
||||||
|
<ion-item class="ion-text-wrap" *ngIf="grade.percentage?.length > 0 && grade.percentage != '-'">
|
||||||
|
<ion-label>
|
||||||
|
<p class="item-heading">{{ 'core.grades.percentage' | translate}}</p>
|
||||||
|
<p [innerHTML]="grade.percentage"></p>
|
||||||
|
</ion-label>
|
||||||
|
</ion-item>
|
||||||
|
|
||||||
|
<ion-item class="ion-text-wrap" *ngIf="grade.lettergrade?.length > 0 && grade.lettergrade != '-'">
|
||||||
|
<ion-label>
|
||||||
|
<p class="item-heading">{{ 'core.grades.lettergrade' | translate}}</p>
|
||||||
|
<p [innerHTML]="grade.lettergrade"></p>
|
||||||
|
</ion-label>
|
||||||
|
</ion-item>
|
||||||
|
|
||||||
|
<ion-item class="ion-text-wrap" *ngIf="grade.rank?.length > 0 && grade.rank != '-'">
|
||||||
|
<ion-label>
|
||||||
|
<p class="item-heading">{{ 'core.grades.rank' | translate}}</p>
|
||||||
|
<p [innerHTML]="grade.rank"></p>
|
||||||
|
</ion-label>
|
||||||
|
</ion-item>
|
||||||
|
|
||||||
|
<ion-item class="ion-text-wrap" *ngIf="grade.average?.length > 0 && grade.average != '-'">
|
||||||
|
<ion-label>
|
||||||
|
<p class="item-heading">{{ 'core.grades.average' | translate}}</p>
|
||||||
|
<p [innerHTML]="grade.average"></p>
|
||||||
|
</ion-label>
|
||||||
|
</ion-item>
|
||||||
|
|
||||||
|
<ion-item class="ion-text-wrap" *ngIf="grade.feedback?.length > 0 && grade.feedback != '-'">
|
||||||
|
<ion-label>
|
||||||
|
<p class="item-heading">{{ 'core.grades.feedback' | translate}}</p>
|
||||||
|
<p>
|
||||||
|
<core-format-text [maxHeight]="120" [text]="grade.feedback" contextLevel="course"
|
||||||
|
[contextInstanceId]="courseId">
|
||||||
|
</core-format-text>
|
||||||
|
</p>
|
||||||
|
</ion-label>
|
||||||
|
</ion-item>
|
||||||
|
|
||||||
|
<ion-item class="ion-text-wrap"
|
||||||
|
*ngIf="grade.contributiontocoursetotal?.length > 0 && grade.contributiontocoursetotal != '-'">
|
||||||
|
<ion-label>
|
||||||
|
<p class="item-heading">{{ 'core.grades.contributiontocoursetotal' | translate}}</p>
|
||||||
|
<p [innerHTML]="grade.contributiontocoursetotal"></p>
|
||||||
|
</ion-label>
|
||||||
|
</ion-item>
|
||||||
|
</div>
|
||||||
|
</ng-container>
|
||||||
|
</ion-list>
|
||||||
|
</ion-card>
|
||||||
|
|
||||||
|
<ion-item button *ngIf="blog && displayOptions.displayBlog" (click)="gotoBlog()" [detail]="true">
|
||||||
|
<ion-icon name="far-newspaper" slot="start" aria-hidden="true"></ion-icon>
|
||||||
|
<ion-label>
|
||||||
|
{{ 'addon.blog.blog' | translate }}
|
||||||
|
</ion-label>
|
||||||
|
</ion-item>
|
||||||
|
</core-loading>
|
||||||
|
</ion-content>
|
||||||
|
<ion-footer *ngIf="loaded && isOnline && displayOptions.displayRefresh">
|
||||||
|
<ion-button class="ion-margin" *ngIf="!hasOffline" (click)="refresh()" expand="block">
|
||||||
|
<ion-icon name="fas-redo-alt" slot="start" aria-hidden="true"></ion-icon>
|
||||||
|
<ion-label>
|
||||||
|
{{ 'core.refresh' | translate }}
|
||||||
|
</ion-label>
|
||||||
|
</ion-button>
|
||||||
|
|
||||||
|
<ion-button class="ion-margin" *ngIf="hasOffline" (click)="sync()" expand="block">
|
||||||
|
<ion-icon name="fas-sync-alt" slot="start" aria-hidden="true"></ion-icon>
|
||||||
|
<ion-label>
|
||||||
|
{{ 'core.settings.synchronizenow' | translate }}
|
||||||
|
</ion-label>
|
||||||
|
</ion-button>
|
||||||
|
</ion-footer>
|
|
@ -0,0 +1,24 @@
|
||||||
|
@import "~theme/globals";
|
||||||
|
|
||||||
|
:host ::ng-deep .collapsible-title ion-label {
|
||||||
|
margin-top: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-size: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.core-modulename {
|
||||||
|
text-transform: uppercase;
|
||||||
|
core-mod-icon {
|
||||||
|
padding: 3px;
|
||||||
|
--size: 10px;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
ion-item ion-label ion-icon {
|
||||||
|
@include margin-horizontal(0, 4px);
|
||||||
|
vertical-align: text-top;
|
||||||
|
}
|
|
@ -0,0 +1,385 @@
|
||||||
|
// (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 { CoreConstants } from '@/core/constants';
|
||||||
|
import { AddonBlog } from '@addons/blog/services/blog';
|
||||||
|
import { AddonBlogMainMenuHandlerService } from '@addons/blog/services/handlers/mainmenu';
|
||||||
|
import { Component, Input, OnDestroy, OnInit } from '@angular/core';
|
||||||
|
import { Params } from '@angular/router';
|
||||||
|
import { CoreCourse } from '@features/course/services/course';
|
||||||
|
import { CoreCourseHelper, CoreCourseModuleData } from '@features/course/services/course-helper';
|
||||||
|
import { CoreCourseModuleDelegate } from '@features/course/services/module-delegate';
|
||||||
|
import { CoreCourseModulePrefetchDelegate } from '@features/course/services/module-prefetch-delegate';
|
||||||
|
import { CoreCourses, CoreEnrolledCourseData } from '@features/courses/services/courses';
|
||||||
|
import { CoreGradesFormattedRow, CoreGradesFormattedTableRow, CoreGradesHelper } from '@features/grades/services/grades-helper';
|
||||||
|
import { CoreApp } from '@services/app';
|
||||||
|
import { CoreFilepool } from '@services/filepool';
|
||||||
|
import { CoreNavigator } from '@services/navigator';
|
||||||
|
import { CoreSites } from '@services/sites';
|
||||||
|
import { CoreDomUtils } from '@services/utils/dom';
|
||||||
|
import { CoreTextUtils } from '@services/utils/text';
|
||||||
|
import { CoreUtils } from '@services/utils/utils';
|
||||||
|
import { ModalController, Network, NgZone } from '@singletons';
|
||||||
|
import { CoreEventObserver, CoreEvents } from '@singletons/events';
|
||||||
|
import { Subscription } from 'rxjs';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Component to display a module summary modal.
|
||||||
|
*/
|
||||||
|
@Component({
|
||||||
|
selector: 'core-course-module-summary',
|
||||||
|
templateUrl: 'module-summary.html',
|
||||||
|
styleUrls: ['module-summary.scss'],
|
||||||
|
})
|
||||||
|
export class CoreCourseModuleSummaryComponent implements OnInit, OnDestroy {
|
||||||
|
|
||||||
|
@Input() module?: CoreCourseModuleData; // The module of the component.
|
||||||
|
@Input() courseId = 0; // Course ID the component belongs to.
|
||||||
|
@Input() moduleId = 0; // Module ID the component belongs to.
|
||||||
|
@Input() component = ''; // Component name.
|
||||||
|
@Input() description = ''; // Module description.
|
||||||
|
@Input() hasOffline = false; // If it has offline data to be synced.
|
||||||
|
@Input() displayOptions: CoreCourseModuleSummaryDisplayOptions = {};
|
||||||
|
|
||||||
|
loaded = false; // If the component has been loaded.
|
||||||
|
componentId?: number; // Component ID.
|
||||||
|
|
||||||
|
// Data for context menu.
|
||||||
|
externalUrl?: string; // External URL to open in browser.
|
||||||
|
|
||||||
|
removeFilesLoading = false;
|
||||||
|
prefetchLoading = false;
|
||||||
|
canPrefetch = false;;
|
||||||
|
prefetchDisabled = false;
|
||||||
|
sizeReadable = '';
|
||||||
|
downloadTimeReadable = ''; // Last download time in a readable format.
|
||||||
|
grades?: CoreGradesFormattedRow[];
|
||||||
|
blog = false; // If blog is available.
|
||||||
|
isOnline = false; // If the app is online or not.
|
||||||
|
course?: CoreEnrolledCourseData;
|
||||||
|
modicon = '';
|
||||||
|
moduleNameTranslated = '';
|
||||||
|
|
||||||
|
protected onlineSubscription: Subscription; // It will observe the status of the network connection.
|
||||||
|
protected packageStatusObserver?: CoreEventObserver; // Observer of package status.
|
||||||
|
protected fileStatusObserver?: CoreEventObserver; // Observer of file status.
|
||||||
|
protected siteId: string;
|
||||||
|
protected isDestroyed = false;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.siteId = CoreSites.getCurrentSiteId();
|
||||||
|
this.isOnline = CoreApp.isOnline();
|
||||||
|
|
||||||
|
// Refresh online status when changes.
|
||||||
|
this.onlineSubscription = Network.onChange().subscribe(() => {
|
||||||
|
// Execute the callback in the Angular zone, so change detection doesn't stop working.
|
||||||
|
NgZone.run(() => {
|
||||||
|
this.isOnline = CoreApp.isOnline();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
async ngOnInit(): Promise<void> {
|
||||||
|
if (!this.module) {
|
||||||
|
this.closeModal();
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.displayOptions = Object.assign({
|
||||||
|
displayOpenInBrowser: true,
|
||||||
|
displayDescription: true,
|
||||||
|
displayRefresh: true,
|
||||||
|
displayPrefetch: true,
|
||||||
|
displaySize: true,
|
||||||
|
displayBlog: true,
|
||||||
|
displayGrades: true,
|
||||||
|
}, this.displayOptions);
|
||||||
|
|
||||||
|
this.displayOptions.displayGrades = this.displayOptions.displayGrades &&
|
||||||
|
CoreCourseModuleDelegate.supportsFeature(this.module.modname, CoreConstants.FEATURE_GRADE_HAS_GRADE, true);
|
||||||
|
|
||||||
|
this.displayOptions.displayDescription = this.displayOptions.displayDescription &&
|
||||||
|
CoreCourseModuleDelegate.supportsFeature(this.module.modname, CoreConstants.FEATURE_SHOW_DESCRIPTION, true);
|
||||||
|
|
||||||
|
this.fetchContent();
|
||||||
|
|
||||||
|
if (this.component) {
|
||||||
|
this.packageStatusObserver = CoreEvents.on(
|
||||||
|
CoreEvents.PACKAGE_STATUS_CHANGED,
|
||||||
|
(data) => {
|
||||||
|
if (data.componentId == module.id && data.component == this.component) {
|
||||||
|
this.getPackageStatus();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
this.siteId,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Debounce the update size function to prevent too many calls when downloading or deleting a whole activity.
|
||||||
|
const debouncedUpdateSize = CoreUtils.debounce(async () => {
|
||||||
|
if (!this.module) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const moduleSize = await CoreCourseModulePrefetchDelegate.getModuleStoredSize(this.module, this.courseId);
|
||||||
|
|
||||||
|
this.sizeReadable = moduleSize > 0 ? CoreTextUtils.bytesToSize(moduleSize, 2) : '';
|
||||||
|
}, 1000);
|
||||||
|
|
||||||
|
this.fileStatusObserver = CoreEvents.on(
|
||||||
|
CoreEvents.COMPONENT_FILE_ACTION,
|
||||||
|
(data) => {
|
||||||
|
if (data.component != this.component || data.componentId != module.id) {
|
||||||
|
// The event doesn't belong to this component, ignore.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!CoreFilepool.isFileEventDownloadedOrDeleted(data)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the module size.
|
||||||
|
debouncedUpdateSize();
|
||||||
|
},
|
||||||
|
this.siteId,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch content to populate the page.
|
||||||
|
*/
|
||||||
|
protected async fetchContent(): Promise<void> {
|
||||||
|
if (!this.module) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.componentId = this.module.id;
|
||||||
|
this.externalUrl = this.module.url;
|
||||||
|
this.courseId = this.courseId || this.module.course;
|
||||||
|
|
||||||
|
this.modicon = await CoreCourseModuleDelegate.getModuleIconSrc(this.module.modname, this.module.modicon, this.module);
|
||||||
|
this.moduleNameTranslated = CoreCourse.translateModuleName(this.module.modname || '');
|
||||||
|
|
||||||
|
this.blog = await AddonBlog.isPluginEnabled();
|
||||||
|
|
||||||
|
await Promise.all([
|
||||||
|
this.getPackageStatus(),
|
||||||
|
this.fetchGrades(),
|
||||||
|
this.fetchCourse(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
this.loaded = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updage package status.
|
||||||
|
*
|
||||||
|
* @param refresh If prefetch info has to be refreshed.
|
||||||
|
*/
|
||||||
|
protected async getPackageStatus(refresh = false): Promise<void> {
|
||||||
|
if (!this.module) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const moduleInfo =
|
||||||
|
await CoreCourseHelper.getModulePrefetchInfo(this.module, this.courseId, refresh, this.component);
|
||||||
|
|
||||||
|
this.canPrefetch = moduleInfo.status != CoreConstants.NOT_DOWNLOADABLE;
|
||||||
|
this.downloadTimeReadable = '';
|
||||||
|
|
||||||
|
if (this.canPrefetch) {
|
||||||
|
if (moduleInfo.downloadTime && moduleInfo.downloadTime > 0) {
|
||||||
|
this.downloadTimeReadable = CoreTextUtils.ucFirst(moduleInfo.downloadTimeReadable);
|
||||||
|
}
|
||||||
|
this.prefetchLoading = moduleInfo.status == CoreConstants.DOWNLOADING;
|
||||||
|
this.prefetchDisabled = moduleInfo.status == CoreConstants.DOWNLOADED;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.sizeReadable = moduleInfo.size && moduleInfo.size > 0
|
||||||
|
? moduleInfo.sizeReadable
|
||||||
|
: '';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Go to blog posts.
|
||||||
|
*/
|
||||||
|
async gotoBlog(): Promise<void> {
|
||||||
|
const params: Params = { cmId: this.moduleId };
|
||||||
|
|
||||||
|
await CoreNavigator.navigateToSitePath(AddonBlogMainMenuHandlerService.PAGE_NAME, { params });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch grade module info.
|
||||||
|
*/
|
||||||
|
protected async fetchGrades(): Promise<void> {
|
||||||
|
if (!this.displayOptions.displayGrades) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.grades = await CoreGradesHelper.getModuleGrades(this.courseId, this.moduleId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Toggle grades expand.
|
||||||
|
*
|
||||||
|
* @param grade Row to expand.
|
||||||
|
*/
|
||||||
|
toggleGrade(grade: CoreGradesFormattedTableRow): void {
|
||||||
|
grade.expanded = !grade.expanded;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch course.
|
||||||
|
*/
|
||||||
|
protected async fetchCourse(): Promise<void> {
|
||||||
|
this.course = await CoreCourses.getUserCourse(this.courseId, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Open course.
|
||||||
|
*/
|
||||||
|
openCourse(): void {
|
||||||
|
if (!this.course) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
CoreCourse.openCourse(
|
||||||
|
this.course,
|
||||||
|
{
|
||||||
|
replace: true,
|
||||||
|
animationDirection: 'back',
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prefetch the module.
|
||||||
|
*/
|
||||||
|
async prefetch(): Promise<void> {
|
||||||
|
if (!this.module) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.prefetchLoading = true; // Show spinner since this operation might take a while.
|
||||||
|
|
||||||
|
try {
|
||||||
|
// We need to call getDownloadSize, the package might have been updated.
|
||||||
|
const size = await CoreCourseModulePrefetchDelegate.getModuleDownloadSize(this.module, this.courseId, true);
|
||||||
|
|
||||||
|
await CoreDomUtils.confirmDownloadSize(size);
|
||||||
|
|
||||||
|
await CoreCourseModulePrefetchDelegate.prefetchModule(this.module, this.courseId, true);
|
||||||
|
|
||||||
|
await this.getPackageStatus(true);
|
||||||
|
} catch (error) {
|
||||||
|
this.prefetchLoading = false;
|
||||||
|
|
||||||
|
if (!this.isDestroyed) {
|
||||||
|
CoreDomUtils.showErrorModalDefault(error, 'core.errordownloading', true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Confirm and remove downloaded files.
|
||||||
|
*/
|
||||||
|
async removeFiles(): Promise<void> {
|
||||||
|
if (!this.module) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.prefetchLoading) {
|
||||||
|
CoreDomUtils.showAlertTranslated(undefined, 'core.course.cannotdeletewhiledownloading');
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await CoreDomUtils.showDeleteConfirm('addon.storagemanager.confirmdeletedatafrom', { name: this.module.name });
|
||||||
|
|
||||||
|
this.removeFilesLoading = true;
|
||||||
|
|
||||||
|
await CoreCourseHelper.removeModuleStoredData(this.module, this.courseId);
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
if (!this.isDestroyed &&error) {
|
||||||
|
CoreDomUtils.showErrorModal(error);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
this.removeFilesLoading = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.getPackageStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Refresh the data.
|
||||||
|
*/
|
||||||
|
async refresh(): Promise<void> {
|
||||||
|
if (!this.module) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ModalController.dismiss({ action: 'refresh' });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sync the data.
|
||||||
|
*/
|
||||||
|
async sync(): Promise<void> {
|
||||||
|
if (!this.module) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ModalController.dismiss({ action: 'sync' });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Close the modal.
|
||||||
|
*/
|
||||||
|
closeModal(): void {
|
||||||
|
ModalController.dismiss();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
ngOnDestroy(): void {
|
||||||
|
this.isDestroyed = true;
|
||||||
|
this.packageStatusObserver?.off();
|
||||||
|
this.fileStatusObserver?.off();
|
||||||
|
this.onlineSubscription.unsubscribe();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export type CoreCourseModuleSummaryResult = {
|
||||||
|
action: 'sync'|'refresh';
|
||||||
|
};
|
||||||
|
|
||||||
|
export type CoreCourseModuleSummaryDisplayOptions = {
|
||||||
|
displayOpenInBrowser?: boolean;
|
||||||
|
displayDescription?: boolean;
|
||||||
|
displayRefresh?: boolean;
|
||||||
|
displayPrefetch?: boolean;
|
||||||
|
displaySize?: boolean;
|
||||||
|
displayBlog?: boolean;
|
||||||
|
displayGrades?: boolean;
|
||||||
|
};
|
|
@ -31,7 +31,7 @@ import { CoreCourseFormatDelegate } from '@features/course/services/format-deleg
|
||||||
import { CoreCourseModulePrefetchDelegate } from '@features/course/services/module-prefetch-delegate';
|
import { CoreCourseModulePrefetchDelegate } from '@features/course/services/module-prefetch-delegate';
|
||||||
import { CoreCourseOptionsMenuHandlerToDisplay } from '@features/course/services/course-options-delegate';
|
import { CoreCourseOptionsMenuHandlerToDisplay } from '@features/course/services/course-options-delegate';
|
||||||
import { CoreCourseSync, CoreCourseSyncProvider } from '@features/course/services/sync';
|
import { CoreCourseSync, CoreCourseSyncProvider } from '@features/course/services/sync';
|
||||||
import { CoreCourseFormatComponent } from '../../components/format/format';
|
import { CoreCourseFormatComponent } from '../../components/course-format/course-format';
|
||||||
import {
|
import {
|
||||||
CoreEvents,
|
CoreEvents,
|
||||||
CoreEventObserver,
|
CoreEventObserver,
|
||||||
|
|
|
@ -11,11 +11,10 @@
|
||||||
</ion-title>
|
</ion-title>
|
||||||
|
|
||||||
<ion-buttons slot="end">
|
<ion-buttons slot="end">
|
||||||
<core-context-menu>
|
<ion-button fill="clear" *ngIf="module.url" [href]="module.url" core-link [showBrowserWarning]="false" color="dark"
|
||||||
<core-context-menu-item [priority]="900" *ngIf="module.url" [href]="module!.url"
|
[attr.aria-label]="'core.openinbrowser' | translate">
|
||||||
[content]="'core.openinbrowser' | translate" iconAction="fas-external-link-alt">
|
<ion-icon name="fas-external-link-alt" slot="icon-only" aria-hidden="true"></ion-icon>
|
||||||
</core-context-menu-item>
|
</ion-button>
|
||||||
</core-context-menu>
|
|
||||||
</ion-buttons>
|
</ion-buttons>
|
||||||
</ion-toolbar>
|
</ion-toolbar>
|
||||||
</ion-header>
|
</ion-header>
|
||||||
|
|
|
@ -64,7 +64,6 @@ import { CoreFile } from '@services/file';
|
||||||
import { CoreUrlUtils } from '@services/utils/url';
|
import { CoreUrlUtils } from '@services/utils/url';
|
||||||
import { CoreTextUtils } from '@services/utils/text';
|
import { CoreTextUtils } from '@services/utils/text';
|
||||||
import { CoreTimeUtils } from '@services/utils/time';
|
import { CoreTimeUtils } from '@services/utils/time';
|
||||||
import { CoreEventObserver, CoreEvents } from '@singletons/events';
|
|
||||||
import { CoreFilterHelper } from '@features/filter/services/filter-helper';
|
import { CoreFilterHelper } from '@features/filter/services/filter-helper';
|
||||||
import { CoreNetworkError } from '@classes/errors/network-error';
|
import { CoreNetworkError } from '@classes/errors/network-error';
|
||||||
import { CoreSiteHome } from '@features/sitehome/services/sitehome';
|
import { CoreSiteHome } from '@features/sitehome/services/sitehome';
|
||||||
|
@ -75,36 +74,19 @@ import { CoreStatusWithWarningsWSResponse } from '@services/ws';
|
||||||
/**
|
/**
|
||||||
* Prefetch info of a module.
|
* Prefetch info of a module.
|
||||||
*/
|
*/
|
||||||
export type CoreCourseModulePrefetchInfo = {
|
export type CoreCourseModulePrefetchInfo = CoreCourseModulePackageLastDownloaded & {
|
||||||
/**
|
size: number; // Downloaded size.
|
||||||
* Downloaded size.
|
sizeReadable: string; // Downloadable size in a readable format.
|
||||||
*/
|
status: string; // Module status.
|
||||||
size: number;
|
statusIcon?: string; // Icon's name of the module status.
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Downloadable size in a readable format.
|
* Prefetch info of a module.
|
||||||
*/
|
*/
|
||||||
sizeReadable: string;
|
export type CoreCourseModulePackageLastDownloaded = {
|
||||||
|
downloadTime: number; // Time when the module was last downloaded.
|
||||||
/**
|
downloadTimeReadable: string; // Download time in a readable format.
|
||||||
* Module status.
|
|
||||||
*/
|
|
||||||
status: string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Icon's name of the module status.
|
|
||||||
*/
|
|
||||||
statusIcon?: string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Time when the module was last downloaded.
|
|
||||||
*/
|
|
||||||
downloadTime: number;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Download time in a readable format.
|
|
||||||
*/
|
|
||||||
downloadTimeReadable: string;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -491,22 +473,18 @@ export class CoreCourseHelperProvider {
|
||||||
*
|
*
|
||||||
* @param module Module to remove the files.
|
* @param module Module to remove the files.
|
||||||
* @param courseId Course ID the module belongs to.
|
* @param courseId Course ID the module belongs to.
|
||||||
* @param done Function to call when done. It will close the context menu.
|
|
||||||
* @return Promise resolved when done.
|
* @return Promise resolved when done.
|
||||||
|
* @deprecated since 4.0
|
||||||
*/
|
*/
|
||||||
async confirmAndRemoveFiles(module: CoreCourseModuleData, courseId: number, done?: () => void): Promise<void> {
|
async confirmAndRemoveFiles(module: CoreCourseModuleData, courseId: number): Promise<void> {
|
||||||
let modal: CoreIonLoadingElement | undefined;
|
let modal: CoreIonLoadingElement | undefined;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
||||||
await CoreDomUtils.showDeleteConfirm('addon.storagemanager.confirmdeletedatafrom', { name: module.name });
|
await CoreDomUtils.showDeleteConfirm('addon.storagemanager.confirmdeletedatafrom', { name: module.name });
|
||||||
|
|
||||||
modal = await CoreDomUtils.showModalLoading();
|
modal = await CoreDomUtils.showModalLoading();
|
||||||
|
|
||||||
await this.removeModuleStoredData(module, courseId);
|
await this.removeModuleStoredData(module, courseId);
|
||||||
|
|
||||||
done && done();
|
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error) {
|
if (error) {
|
||||||
CoreDomUtils.showErrorModal(error);
|
CoreDomUtils.showErrorModal(error);
|
||||||
|
@ -571,44 +549,6 @@ export class CoreCourseHelperProvider {
|
||||||
await CoreDomUtils.confirmDownloadSize(sizeSum, undefined, undefined, undefined, undefined, alwaysConfirm);
|
await CoreDomUtils.confirmDownloadSize(sizeSum, undefined, undefined, undefined, undefined, alwaysConfirm);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Helper function to prefetch a module, showing a confirmation modal if the size is big.
|
|
||||||
* This function is meant to be called from a context menu option. It will also modify some data like the prefetch icon.
|
|
||||||
*
|
|
||||||
* @param instance The component instance that has the context menu.
|
|
||||||
* @param module Module to be prefetched
|
|
||||||
* @param courseId Course ID the module belongs to.
|
|
||||||
* @param done Function to call when done. It will close the context menu.
|
|
||||||
* @return Promise resolved when done.
|
|
||||||
*/
|
|
||||||
async contextMenuPrefetch(
|
|
||||||
instance: ComponentWithContextMenu,
|
|
||||||
module: CoreCourseModuleData,
|
|
||||||
courseId: number,
|
|
||||||
done?: () => void,
|
|
||||||
): Promise<void> {
|
|
||||||
const initialIcon = instance.prefetchStatusIcon;
|
|
||||||
instance.prefetchStatusIcon = CoreConstants.ICON_DOWNLOADING; // Show spinner since this operation might take a while.
|
|
||||||
|
|
||||||
try {
|
|
||||||
// We need to call getDownloadSize, the package might have been updated.
|
|
||||||
const size = await CoreCourseModulePrefetchDelegate.getModuleDownloadSize(module, courseId, true);
|
|
||||||
|
|
||||||
await CoreDomUtils.confirmDownloadSize(size);
|
|
||||||
|
|
||||||
await CoreCourseModulePrefetchDelegate.prefetchModule(module, courseId, true);
|
|
||||||
|
|
||||||
// Success, close menu.
|
|
||||||
done && done();
|
|
||||||
} catch (error) {
|
|
||||||
instance.prefetchStatusIcon = initialIcon;
|
|
||||||
|
|
||||||
if (!instance.isDestroyed) {
|
|
||||||
CoreDomUtils.showErrorModalDefault(error, 'core.errordownloading', true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check whether a course is accessed using guest access.
|
* Check whether a course is accessed using guest access.
|
||||||
*
|
*
|
||||||
|
@ -1045,87 +985,6 @@ export class CoreCourseHelperProvider {
|
||||||
await CoreFilepool.downloadOrPrefetchFiles(siteId, files, false, false, component, componentId);
|
await CoreFilepool.downloadOrPrefetchFiles(siteId, files, false, false, component, componentId);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Fill the Context Menu for a certain module.
|
|
||||||
*
|
|
||||||
* @param instance The component instance that has the context menu.
|
|
||||||
* @param module Module to be prefetched
|
|
||||||
* @param courseId Course ID the module belongs to.
|
|
||||||
* @param invalidateCache Invalidates the cache first.
|
|
||||||
* @param component Component of the module.
|
|
||||||
* @return Promise resolved when done.
|
|
||||||
*/
|
|
||||||
async fillContextMenu(
|
|
||||||
instance: ComponentWithContextMenu,
|
|
||||||
module: CoreCourseModuleData,
|
|
||||||
courseId: number,
|
|
||||||
invalidateCache?: boolean,
|
|
||||||
component?: string,
|
|
||||||
): Promise<void> {
|
|
||||||
const siteId = CoreSites.getCurrentSiteId();
|
|
||||||
|
|
||||||
const moduleInfo = await this.getModulePrefetchInfo(module, courseId, invalidateCache, component);
|
|
||||||
|
|
||||||
instance.size = moduleInfo.sizeReadable;
|
|
||||||
instance.prefetchStatusIcon = moduleInfo.statusIcon;
|
|
||||||
instance.prefetchStatus = moduleInfo.status;
|
|
||||||
instance.downloadTimeReadable = CoreTextUtils.ucFirst(moduleInfo.downloadTimeReadable);
|
|
||||||
|
|
||||||
if (moduleInfo.status != CoreConstants.NOT_DOWNLOADABLE) {
|
|
||||||
// Module is downloadable, get the text to display to prefetch.
|
|
||||||
if (moduleInfo.downloadTime && moduleInfo.downloadTime > 0) {
|
|
||||||
instance.prefetchText = Translate.instant('core.lastdownloaded') + ': ' + moduleInfo.downloadTimeReadable;
|
|
||||||
} else {
|
|
||||||
// Module not downloaded, show a default text.
|
|
||||||
instance.prefetchText = Translate.instant('core.download');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (moduleInfo.status == CoreConstants.DOWNLOADING) {
|
|
||||||
// Set this to empty to prevent "remove file" option showing up while downloading.
|
|
||||||
instance.size = '';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!instance.contextMenuStatusObserver && component) {
|
|
||||||
instance.contextMenuStatusObserver = CoreEvents.on(
|
|
||||||
CoreEvents.PACKAGE_STATUS_CHANGED,
|
|
||||||
(data) => {
|
|
||||||
if (data.componentId == module.id && data.component == component) {
|
|
||||||
this.fillContextMenu(instance, module, courseId, false, component);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
siteId,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!instance.contextFileStatusObserver && component) {
|
|
||||||
// Debounce the update size function to prevent too many calls when downloading or deleting a whole activity.
|
|
||||||
const debouncedUpdateSize = CoreUtils.debounce(async () => {
|
|
||||||
const moduleSize = await CoreCourseModulePrefetchDelegate.getModuleStoredSize(module, courseId);
|
|
||||||
|
|
||||||
instance.size = moduleSize > 0 ? CoreTextUtils.bytesToSize(moduleSize, 2) : '';
|
|
||||||
}, 1000);
|
|
||||||
|
|
||||||
instance.contextFileStatusObserver = CoreEvents.on(
|
|
||||||
CoreEvents.COMPONENT_FILE_ACTION,
|
|
||||||
(data) => {
|
|
||||||
if (data.component != component || data.componentId != module.id) {
|
|
||||||
// The event doesn't belong to this component, ignore.
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!CoreFilepool.isFileEventDownloadedOrDeleted(data)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update the module size.
|
|
||||||
debouncedUpdateSize();
|
|
||||||
},
|
|
||||||
siteId,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get a course. It will first check the user courses, and fallback to another WS if not enrolled.
|
* Get a course. It will first check the user courses, and fallback to another WS if not enrolled.
|
||||||
*
|
*
|
||||||
|
@ -1482,12 +1341,9 @@ export class CoreCourseHelperProvider {
|
||||||
async getModulePrefetchInfo(
|
async getModulePrefetchInfo(
|
||||||
module: CoreCourseModuleData,
|
module: CoreCourseModuleData,
|
||||||
courseId: number,
|
courseId: number,
|
||||||
invalidateCache?: boolean,
|
invalidateCache = false,
|
||||||
component?: string,
|
component = '',
|
||||||
): Promise<CoreCourseModulePrefetchInfo> {
|
): Promise<CoreCourseModulePrefetchInfo> {
|
||||||
|
|
||||||
const siteId = CoreSites.getCurrentSiteId();
|
|
||||||
|
|
||||||
if (invalidateCache) {
|
if (invalidateCache) {
|
||||||
// Currently, some modules pass invalidateCache=false because they already invalidate data in downloadResourceIfNeeded.
|
// Currently, some modules pass invalidateCache=false because they already invalidate data in downloadResourceIfNeeded.
|
||||||
// If this function is changed to do more actions if invalidateCache=true, please review those modules.
|
// If this function is changed to do more actions if invalidateCache=true, please review those modules.
|
||||||
|
@ -1499,7 +1355,7 @@ export class CoreCourseHelperProvider {
|
||||||
const results = await Promise.all([
|
const results = await Promise.all([
|
||||||
CoreCourseModulePrefetchDelegate.getModuleStoredSize(module, courseId),
|
CoreCourseModulePrefetchDelegate.getModuleStoredSize(module, courseId),
|
||||||
CoreCourseModulePrefetchDelegate.getModuleStatus(module, courseId),
|
CoreCourseModulePrefetchDelegate.getModuleStatus(module, courseId),
|
||||||
CoreUtils.ignoreErrors(CoreFilepool.getPackageData(siteId, component || '', module.id)),
|
this.getModulePackageLastDownloaded(module, component),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// Treat stored size.
|
// Treat stored size.
|
||||||
|
@ -1526,33 +1382,51 @@ export class CoreCourseHelperProvider {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Treat download time.
|
const packageData = results[2];
|
||||||
if (!results[2] || !results[2].downloadTime || !CoreFileHelper.isStateDownloaded(results[2].status || '')) {
|
|
||||||
// Not downloaded.
|
|
||||||
return {
|
return {
|
||||||
size,
|
size,
|
||||||
sizeReadable,
|
sizeReadable,
|
||||||
status,
|
status,
|
||||||
statusIcon,
|
statusIcon,
|
||||||
|
downloadTime: packageData.downloadTime,
|
||||||
|
downloadTimeReadable: packageData.downloadTimeReadable,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get prefetch info for a module.
|
||||||
|
*
|
||||||
|
* @param module Module to get the info from.
|
||||||
|
* @param component Component of the module.
|
||||||
|
* @return Promise resolved with the info.
|
||||||
|
*/
|
||||||
|
async getModulePackageLastDownloaded(
|
||||||
|
module: CoreCourseModuleData,
|
||||||
|
component = '',
|
||||||
|
): Promise<CoreCourseModulePackageLastDownloaded> {
|
||||||
|
const siteId = CoreSites.getCurrentSiteId();
|
||||||
|
const packageData = await CoreUtils.ignoreErrors(CoreFilepool.getPackageData(siteId, component, module.id));
|
||||||
|
|
||||||
|
// Treat download time.
|
||||||
|
if (!packageData || !packageData.downloadTime || !CoreFileHelper.isStateDownloaded(packageData.status || '')) {
|
||||||
|
// Not downloaded.
|
||||||
|
return {
|
||||||
downloadTime: 0,
|
downloadTime: 0,
|
||||||
downloadTimeReadable: '',
|
downloadTimeReadable: '',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const now = CoreTimeUtils.timestamp();
|
const now = CoreTimeUtils.timestamp();
|
||||||
const downloadTime = results[2].downloadTime;
|
const downloadTime = packageData.downloadTime;
|
||||||
let downloadTimeReadable = '';
|
let downloadTimeReadable = '';
|
||||||
if (now - results[2].downloadTime < 7 * 86400) {
|
if (now - downloadTime < 7 * 86400) {
|
||||||
downloadTimeReadable = moment(results[2].downloadTime * 1000).fromNow();
|
downloadTimeReadable = moment(downloadTime * 1000).fromNow();
|
||||||
} else {
|
} else {
|
||||||
downloadTimeReadable = moment(results[2].downloadTime * 1000).calendar();
|
downloadTimeReadable = moment(downloadTime * 1000).calendar();
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
size,
|
|
||||||
sizeReadable,
|
|
||||||
status,
|
|
||||||
statusIcon,
|
|
||||||
downloadTime,
|
downloadTime,
|
||||||
downloadTimeReadable,
|
downloadTimeReadable,
|
||||||
};
|
};
|
||||||
|
@ -2223,14 +2097,3 @@ export type CoreCourseOpenModuleOptions = {
|
||||||
sectionId?: number; // Section the module belongs to.
|
sectionId?: number; // Section the module belongs to.
|
||||||
modNavOptions?: CoreNavigationOptions; // Navigation options to open the module, including params to pass to the module.
|
modNavOptions?: CoreNavigationOptions; // Navigation options to open the module, including params to pass to the module.
|
||||||
};
|
};
|
||||||
|
|
||||||
type ComponentWithContextMenu = {
|
|
||||||
prefetchStatusIcon?: string;
|
|
||||||
isDestroyed?: boolean;
|
|
||||||
size?: string;
|
|
||||||
prefetchStatus?: string;
|
|
||||||
prefetchText?: string;
|
|
||||||
downloadTimeReadable?: string;
|
|
||||||
contextMenuStatusObserver?: CoreEventObserver;
|
|
||||||
contextFileStatusObserver?: CoreEventObserver;
|
|
||||||
};
|
|
||||||
|
|
|
@ -130,9 +130,13 @@ export class CoreCourseFormatDefaultHandler implements CoreCourseFormatHandler {
|
||||||
navOptions.params = navOptions.params || {};
|
navOptions.params = navOptions.params || {};
|
||||||
Object.assign(navOptions.params, { course: course });
|
Object.assign(navOptions.params, { course: course });
|
||||||
|
|
||||||
|
// When replace is true, disable route depth.
|
||||||
|
let routeDepth = 0;
|
||||||
|
if (!navOptions.replace) {
|
||||||
// Don't return the .push promise, we don't want to display a loading modal during the page transition.
|
// Don't return the .push promise, we don't want to display a loading modal during the page transition.
|
||||||
const currentTab = CoreNavigator.getCurrentMainMenuTab();
|
const currentTab = CoreNavigator.getCurrentMainMenuTab();
|
||||||
const routeDepth = CoreNavigator.getRouteDepth(`/main/${currentTab}/course/${course.id}`);
|
routeDepth = CoreNavigator.getRouteDepth(`/main/${currentTab}/course/${course.id}`);
|
||||||
|
}
|
||||||
const deepPath = '/deep'.repeat(routeDepth);
|
const deepPath = '/deep'.repeat(routeDepth);
|
||||||
|
|
||||||
CoreNavigator.navigateToSitePath(`course${deepPath}/${course.id}`, navOptions);
|
CoreNavigator.navigateToSitePath(`course${deepPath}/${course.id}`, navOptions);
|
||||||
|
|
|
@ -202,10 +202,10 @@ export interface CoreCourseModuleMainComponent {
|
||||||
* Refresh the data.
|
* Refresh the data.
|
||||||
*
|
*
|
||||||
* @param refresher Refresher.
|
* @param refresher Refresher.
|
||||||
* @param done Function to call when done.
|
* @param showErrors If show errors to the user of hide them.
|
||||||
* @return Promise resolved when done.
|
* @return Promise resolved when done.
|
||||||
*/
|
*/
|
||||||
doRefresh(refresher?: IonRefresher, done?: () => void): Promise<void>;
|
doRefresh(refresher?: IonRefresher | null, showErrors?: boolean): Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
<div *ngIf="layout == 'card' || layout == 'summarycard'" (click)="openCourse()" class="core-course-thumb"
|
<div *ngIf="layout == 'card' || layout == 'summarycard'" (click)="openCourse()" class="core-course-thumb"
|
||||||
[class.core-course-color-img]="course.courseImage">
|
[class.core-course-color-img]="course.courseImage">
|
||||||
<img *ngIf="course.courseImage" [src]="course.courseImage" core-external-content alt="" />
|
<img *ngIf="course.courseImage" [src]="course.courseImage" core-external-content alt="" />
|
||||||
<ion-icon *ngIf="!course.courseImage" name="fas-graduation-cap" class="course-icon">
|
<ion-icon *ngIf="!course.courseImage" name="fas-graduation-cap" class="course-icon" aria-hidden="true">
|
||||||
</ion-icon>
|
</ion-icon>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -31,7 +31,8 @@
|
||||||
[class.item-disabled]="course.visible == 0">
|
[class.item-disabled]="course.visible == 0">
|
||||||
|
|
||||||
<ng-container *ngIf="layout == 'list' || layout == 'listwithenrol'">
|
<ng-container *ngIf="layout == 'list' || layout == 'listwithenrol'">
|
||||||
<ion-icon *ngIf="!course.courseImage" name="fas-graduation-cap" slot="start" class="course-icon core-course-thumb">
|
<ion-icon *ngIf="!course.courseImage" name="fas-graduation-cap" slot="start" class="course-icon core-course-thumb"
|
||||||
|
aria-hidden="true">
|
||||||
</ion-icon>
|
</ion-icon>
|
||||||
<ion-avatar *ngIf="course.courseImage" slot="start" class="core-course-thumb">
|
<ion-avatar *ngIf="course.courseImage" slot="start" class="core-course-thumb">
|
||||||
<img [src]="course.courseImage" core-external-content alt="" />
|
<img [src]="course.courseImage" core-external-content alt="" />
|
||||||
|
|
|
@ -22,7 +22,7 @@ import { CoreMainMenuTabRoutingModule } from '@features/mainmenu/mainmenu-tab-ro
|
||||||
import { CoreUserDelegate } from '@features/user/services/user-delegate';
|
import { CoreUserDelegate } from '@features/user/services/user-delegate';
|
||||||
import { PARTICIPANTS_PAGE_NAME } from '@features/user/user.module';
|
import { PARTICIPANTS_PAGE_NAME } from '@features/user/user.module';
|
||||||
import { CoreGradesProvider } from './services/grades';
|
import { CoreGradesProvider } from './services/grades';
|
||||||
import { CoreGradesHelperProvider } from './services/grades-helper';
|
import { CoreGradesHelperProvider, GRADES_PAGE_NAME } from './services/grades-helper';
|
||||||
import { CoreGradesCourseOptionHandler } from './services/handlers/course-option';
|
import { CoreGradesCourseOptionHandler } from './services/handlers/course-option';
|
||||||
import { CoreGradesOverviewLinkHandler } from './services/handlers/overview-link';
|
import { CoreGradesOverviewLinkHandler } from './services/handlers/overview-link';
|
||||||
import { CoreGradesUserHandler } from './services/handlers/user';
|
import { CoreGradesUserHandler } from './services/handlers/user';
|
||||||
|
@ -33,8 +33,6 @@ export const CORE_GRADES_SERVICES: Type<unknown>[] = [
|
||||||
CoreGradesHelperProvider,
|
CoreGradesHelperProvider,
|
||||||
];
|
];
|
||||||
|
|
||||||
export const GRADES_PAGE_NAME = 'grades';
|
|
||||||
|
|
||||||
const mainMenuChildrenRoutes: Routes = [
|
const mainMenuChildrenRoutes: Routes = [
|
||||||
{
|
{
|
||||||
path: GRADES_PAGE_NAME,
|
path: GRADES_PAGE_NAME,
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
"fail": "Fail",
|
"fail": "Fail",
|
||||||
"feedback": "Feedback",
|
"feedback": "Feedback",
|
||||||
"grade": "Grade",
|
"grade": "Grade",
|
||||||
|
"gradebook": "Gradebook",
|
||||||
"gradeitem": "Grade item",
|
"gradeitem": "Grade item",
|
||||||
"gradepass": "Grade to pass",
|
"gradepass": "Grade to pass",
|
||||||
"grades": "Grades",
|
"grades": "Grades",
|
||||||
|
|
|
@ -31,7 +31,7 @@
|
||||||
[attr.tabindex]="row.expandable && showSummary && 0" [attr.aria-expanded]="row.expanded"
|
[attr.tabindex]="row.expandable && showSummary && 0" [attr.aria-expanded]="row.expanded"
|
||||||
[attr.aria-label]="rowAriaLabel(row)" [attr.aria-controls]="row.detailsid"
|
[attr.aria-label]="rowAriaLabel(row)" [attr.aria-controls]="row.detailsid"
|
||||||
(ariaButtonClick)="row.expandable && showSummary && toggleRow(row)" [class]="row.rowclass"
|
(ariaButtonClick)="row.expandable && showSummary && toggleRow(row)" [class]="row.rowclass"
|
||||||
[class.core-grades-grade-clickable]="row.expandable && showSummary">
|
[class.core-grades-grade-clickable]="row.expandable && showSummary" [id]="'grade-'+row.id">
|
||||||
<ng-container *ngIf="row.itemtype">
|
<ng-container *ngIf="row.itemtype">
|
||||||
<td *ngIf="row.itemtype == 'category'" class="core-grades-table-category" [attr.rowspan]="row.rowspan">
|
<td *ngIf="row.itemtype == 'category'" class="core-grades-table-category" [attr.rowspan]="row.rowspan">
|
||||||
</td>
|
</td>
|
||||||
|
@ -71,40 +71,6 @@
|
||||||
<tr *ngIf="row.expandable" [id]="row.detailsid" [class]="row.rowclass" [hidden]="!row.expanded">
|
<tr *ngIf="row.expandable" [id]="row.detailsid" [class]="row.rowclass" [hidden]="!row.expanded">
|
||||||
<td [attr.colspan]="totalColumnsSpan">
|
<td [attr.colspan]="totalColumnsSpan">
|
||||||
<ion-list>
|
<ion-list>
|
||||||
<ion-item *ngIf="row.itemname && row.link" class="ion-text-wrap" detail="true" [href]="row.link"
|
|
||||||
core-link capture="true">
|
|
||||||
<ion-icon *ngIf="row.icon" name="{{row.icon}}" slot="start" [attr.aria-label]="row.iconAlt">
|
|
||||||
</ion-icon>
|
|
||||||
<img *ngIf="row.image && !row.itemmodule" [src]="row.image && row.itemmodule" slot="start"
|
|
||||||
[alt]="row.iconAlt" />
|
|
||||||
<core-mod-icon *ngIf="row.image && row.itemmodule" [modicon]="row.image" slot="start"
|
|
||||||
[modname]="row.itemmodule">
|
|
||||||
</core-mod-icon>
|
|
||||||
<ion-label>
|
|
||||||
<h2>
|
|
||||||
<core-format-text [text]="row.itemname" contextLevel="course"
|
|
||||||
[contextInstanceId]="courseId">
|
|
||||||
</core-format-text>
|
|
||||||
</h2>
|
|
||||||
</ion-label>
|
|
||||||
</ion-item>
|
|
||||||
|
|
||||||
<ion-item *ngIf="row.itemname && !row.link" class="ion-text-wrap">
|
|
||||||
<ion-icon *ngIf="row.icon" name="{{row.icon}}" slot="start" [attr.aria-label]="row.iconAlt">
|
|
||||||
</ion-icon>
|
|
||||||
<img *ngIf="row.image && !row.itemmodule" [src]="row.image" slot="start" [alt]="row.iconAlt" />
|
|
||||||
<core-mod-icon *ngIf="row.image && row.itemmodule" [modicon]="row.image" slot="start"
|
|
||||||
[modname]="row.itemmodule">
|
|
||||||
</core-mod-icon>
|
|
||||||
<ion-label>
|
|
||||||
<h2>
|
|
||||||
<core-format-text [text]="row.itemname" contextLevel="course"
|
|
||||||
[contextInstanceId]="courseId">
|
|
||||||
</core-format-text>
|
|
||||||
</h2>
|
|
||||||
</ion-label>
|
|
||||||
</ion-item>
|
|
||||||
|
|
||||||
<ion-item class="ion-text-wrap" *ngIf="row.weight">
|
<ion-item class="ion-text-wrap" *ngIf="row.weight">
|
||||||
<ion-label>
|
<ion-label>
|
||||||
<h2>{{ 'core.grades.weight' | translate}}</h2>
|
<h2>{{ 'core.grades.weight' | translate}}</h2>
|
||||||
|
|
|
@ -13,8 +13,8 @@
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
import { ActivatedRoute } from '@angular/router';
|
import { ActivatedRoute } from '@angular/router';
|
||||||
import { AfterViewInit, Component, ElementRef, OnDestroy } from '@angular/core';
|
import { AfterViewInit, Component, ElementRef, OnDestroy, Optional } from '@angular/core';
|
||||||
import { IonRefresher } from '@ionic/angular';
|
import { IonContent, IonRefresher } from '@ionic/angular';
|
||||||
|
|
||||||
import { CoreDomUtils } from '@services/utils/dom';
|
import { CoreDomUtils } from '@services/utils/dom';
|
||||||
import { CoreGrades } from '@features/grades/services/grades';
|
import { CoreGrades } from '@features/grades/services/grades';
|
||||||
|
@ -44,6 +44,7 @@ export class CoreGradesCoursePage implements AfterViewInit, OnDestroy {
|
||||||
|
|
||||||
courseId!: number;
|
courseId!: number;
|
||||||
userId!: number;
|
userId!: number;
|
||||||
|
gradeId?: number;
|
||||||
expandLabel!: string;
|
expandLabel!: string;
|
||||||
collapseLabel!: string;
|
collapseLabel!: string;
|
||||||
title?: string;
|
title?: string;
|
||||||
|
@ -53,10 +54,16 @@ export class CoreGradesCoursePage implements AfterViewInit, OnDestroy {
|
||||||
totalColumnsSpan?: number;
|
totalColumnsSpan?: number;
|
||||||
withinSplitView?: boolean;
|
withinSplitView?: boolean;
|
||||||
|
|
||||||
constructor(protected route: ActivatedRoute, protected element: ElementRef<HTMLElement>) {
|
constructor(
|
||||||
|
protected route: ActivatedRoute,
|
||||||
|
protected element: ElementRef<HTMLElement>,
|
||||||
|
@Optional() protected content?: IonContent,
|
||||||
|
) {
|
||||||
try {
|
try {
|
||||||
this.courseId = CoreNavigator.getRequiredRouteNumberParam('courseId', { route });
|
this.courseId = CoreNavigator.getRequiredRouteNumberParam('courseId', { route });
|
||||||
this.userId = CoreNavigator.getRouteNumberParam('userId', { route }) ?? CoreSites.getCurrentSiteUserId();
|
this.userId = CoreNavigator.getRouteNumberParam('userId', { route }) ?? CoreSites.getCurrentSiteUserId();
|
||||||
|
this.gradeId = CoreNavigator.getRouteNumberParam('gradeId', { route });
|
||||||
|
|
||||||
this.expandLabel = Translate.instant('core.expand');
|
this.expandLabel = Translate.instant('core.expand');
|
||||||
this.collapseLabel = Translate.instant('core.collapse');
|
this.collapseLabel = Translate.instant('core.collapse');
|
||||||
|
|
||||||
|
@ -116,13 +123,14 @@ export class CoreGradesCoursePage implements AfterViewInit, OnDestroy {
|
||||||
* Toggle whether a row is expanded or collapsed.
|
* Toggle whether a row is expanded or collapsed.
|
||||||
*
|
*
|
||||||
* @param row Row.
|
* @param row Row.
|
||||||
|
* @param expand If defined, force expand or collapse.
|
||||||
*/
|
*/
|
||||||
toggleRow(row: CoreGradesFormattedTableRow): void {
|
toggleRow(row: CoreGradesFormattedTableRow, expand?: boolean): void {
|
||||||
if (!this.rows || !this.columns) {
|
if (!this.rows || !this.columns) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
row.expanded = !row.expanded;
|
row.expanded = expand ?? !row.expanded;
|
||||||
|
|
||||||
let colspan: number = this.columns.length + (row.colspan ?? 0) - 1;
|
let colspan: number = this.columns.length + (row.colspan ?? 0) - 1;
|
||||||
for (let i = this.rows.indexOf(row) - 1; i >= 0; i--) {
|
for (let i = this.rows.indexOf(row) - 1; i >= 0; i--) {
|
||||||
|
@ -155,6 +163,22 @@ export class CoreGradesCoursePage implements AfterViewInit, OnDestroy {
|
||||||
private async fetchInitialGrades(): Promise<void> {
|
private async fetchInitialGrades(): Promise<void> {
|
||||||
try {
|
try {
|
||||||
await this.fetchGrades();
|
await this.fetchGrades();
|
||||||
|
|
||||||
|
if (this.gradeId && this.rows) {
|
||||||
|
const row = this.rows.find((row) => row.id == this.gradeId);
|
||||||
|
|
||||||
|
if (row) {
|
||||||
|
this.toggleRow(row, true);
|
||||||
|
await CoreUtils.nextTick();
|
||||||
|
|
||||||
|
CoreDomUtils.scrollToElementBySelector(
|
||||||
|
this.element.nativeElement,
|
||||||
|
this.content,
|
||||||
|
'#grade-' + row.id,
|
||||||
|
);
|
||||||
|
this.gradeId = undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
CoreDomUtils.showErrorModalDefault(error, 'Error loading course');
|
CoreDomUtils.showErrorModalDefault(error, 'Error loading course');
|
||||||
|
|
||||||
|
|
|
@ -80,8 +80,8 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
core-mod-icon {
|
core-mod-icon {
|
||||||
padding: 0.1rem;
|
padding: 3px;
|
||||||
--size: 16px;
|
--size: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -35,7 +35,8 @@ import { CoreNavigator } from '@services/navigator';
|
||||||
import { makeSingleton, Translate } from '@singletons';
|
import { makeSingleton, Translate } from '@singletons';
|
||||||
import { CoreError } from '@classes/errors/error';
|
import { CoreError } from '@classes/errors/error';
|
||||||
import { CoreCourseHelper } from '@features/course/services/course-helper';
|
import { CoreCourseHelper } from '@features/course/services/course-helper';
|
||||||
import { GRADES_PAGE_NAME } from '../grades.module';
|
|
||||||
|
export const GRADES_PAGE_NAME = 'grades';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Service that provides some features regarding grades information.
|
* Service that provides some features regarding grades information.
|
||||||
|
@ -456,6 +457,38 @@ export class CoreGradesHelperProvider {
|
||||||
}).map((row) => this.formatGradeRow(row)));
|
}).map((row) => this.formatGradeRow(row)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get module grades to display.
|
||||||
|
*
|
||||||
|
* @param courseId Course Id.
|
||||||
|
* @param moduleId Module Id.
|
||||||
|
* @return Formatted table rows.
|
||||||
|
*/
|
||||||
|
async getModuleGrades(courseId: number, moduleId: number): Promise<CoreGradesFormattedTableRow[] > {
|
||||||
|
const table = await CoreGrades.getCourseGradesTable(courseId);
|
||||||
|
|
||||||
|
if (!table.tabledata) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find href containing "/mod/xxx/xxx.php".
|
||||||
|
const regex = /href="([^"]*\/mod\/[^"|^/]*\/[^"|^.]*\.php[^"]*)/;
|
||||||
|
|
||||||
|
return await Promise.all(table.tabledata.filter((row) => {
|
||||||
|
if (row.itemname && row.itemname.content) {
|
||||||
|
const matches = row.itemname.content.match(regex);
|
||||||
|
|
||||||
|
if (matches && matches.length) {
|
||||||
|
const hrefParams = CoreUrlUtils.extractUrlParams(matches[1]);
|
||||||
|
|
||||||
|
return hrefParams && parseInt(hrefParams.id) === moduleId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}).map((row) => this.formatGradeRowForTable(row)));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Go to view grades.
|
* Go to view grades.
|
||||||
*
|
*
|
||||||
|
@ -497,9 +530,12 @@ export class CoreGradesHelperProvider {
|
||||||
const gradeId = item.id;
|
const gradeId = item.id;
|
||||||
|
|
||||||
await CoreUtils.ignoreErrors(
|
await CoreUtils.ignoreErrors(
|
||||||
CoreNavigator.navigateToSitePath(`/${GRADES_PAGE_NAME}/${courseId}/${gradeId}`, { siteId }),
|
CoreNavigator.navigateToSitePath(
|
||||||
|
`/${GRADES_PAGE_NAME}/${courseId}`,
|
||||||
|
{ params: { gradeId }, siteId },
|
||||||
|
),
|
||||||
);
|
);
|
||||||
} catch (error) {
|
} catch {
|
||||||
try {
|
try {
|
||||||
// Cannot get grade items or there's no need to.
|
// Cannot get grade items or there's no need to.
|
||||||
if (userId && userId != currentUserId) {
|
if (userId && userId != currentUserId) {
|
||||||
|
@ -519,7 +555,7 @@ export class CoreGradesHelperProvider {
|
||||||
|
|
||||||
// Open the course with the grades tab selected.
|
// Open the course with the grades tab selected.
|
||||||
await CoreCourseHelper.getAndOpenCourse(courseId, { selectedTab: 'CoreGrades' }, siteId);
|
await CoreCourseHelper.getAndOpenCourse(courseId, { selectedTab: 'CoreGrades' }, siteId);
|
||||||
} catch (error) {
|
} catch {
|
||||||
// Cannot get course for some reason, just open the grades page.
|
// Cannot get course for some reason, just open the grades page.
|
||||||
await CoreNavigator.navigateToSitePath(`/${GRADES_PAGE_NAME}/${courseId}`, { siteId });
|
await CoreNavigator.navigateToSitePath(`/${GRADES_PAGE_NAME}/${courseId}`, { siteId });
|
||||||
}
|
}
|
||||||
|
@ -565,7 +601,7 @@ export class CoreGradesHelperProvider {
|
||||||
row.iconAlt = Translate.instant('core.grades.aggregatesum');
|
row.iconAlt = Translate.instant('core.grades.aggregatesum');
|
||||||
} else if (text.indexOf('/outcomes') > -1 || text.indexOf('fa-tasks') > -1) {
|
} else if (text.indexOf('/outcomes') > -1 || text.indexOf('fa-tasks') > -1) {
|
||||||
row.itemtype = 'outcome';
|
row.itemtype = 'outcome';
|
||||||
row.icon = 'fas-chart-pie';
|
row.icon = 'fas-tasks';
|
||||||
row.iconAlt = Translate.instant('core.grades.outcome');
|
row.iconAlt = Translate.instant('core.grades.outcome');
|
||||||
} else if (text.indexOf('i/folder') > -1 || text.indexOf('fa-folder') > -1) {
|
} else if (text.indexOf('i/folder') > -1 || text.indexOf('fa-folder') > -1) {
|
||||||
row.itemtype = 'category';
|
row.itemtype = 'category';
|
||||||
|
|
|
@ -199,7 +199,7 @@ export class CoreGradesProvider {
|
||||||
const table = await site.read<CoreGradesGetUserGradesTableWSResponse>('gradereport_user_get_grades_table', params, preSets);
|
const table = await site.read<CoreGradesGetUserGradesTableWSResponse>('gradereport_user_get_grades_table', params, preSets);
|
||||||
|
|
||||||
if (!table?.tables?.[0]) {
|
if (!table?.tables?.[0]) {
|
||||||
throw new CoreError('Coudln\'t get course grades table');
|
throw new CoreError('Couldn\'t get course grades table');
|
||||||
}
|
}
|
||||||
|
|
||||||
return table.tables[0];
|
return table.tables[0];
|
||||||
|
|
|
@ -15,10 +15,10 @@
|
||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { CoreContentLinksHandlerBase } from '@features/contentlinks/classes/base-handler';
|
import { CoreContentLinksHandlerBase } from '@features/contentlinks/classes/base-handler';
|
||||||
import { CoreContentLinksAction } from '@features/contentlinks/services/contentlinks-delegate';
|
import { CoreContentLinksAction } from '@features/contentlinks/services/contentlinks-delegate';
|
||||||
import { GRADES_PAGE_NAME } from '@features/grades/grades.module';
|
|
||||||
import { CoreNavigator } from '@services/navigator';
|
import { CoreNavigator } from '@services/navigator';
|
||||||
import { makeSingleton } from '@singletons';
|
import { makeSingleton } from '@singletons';
|
||||||
import { CoreGrades } from '../grades';
|
import { CoreGrades } from '../grades';
|
||||||
|
import { GRADES_PAGE_NAME } from '../grades-helper';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handler to treat links to overview courses grades.
|
* Handler to treat links to overview courses grades.
|
||||||
|
|
|
@ -14,7 +14,6 @@
|
||||||
|
|
||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { COURSE_PAGE_NAME } from '@features/course/course.module';
|
import { COURSE_PAGE_NAME } from '@features/course/course.module';
|
||||||
import { GRADES_PAGE_NAME } from '@features/grades/grades.module';
|
|
||||||
|
|
||||||
import { CoreGrades } from '@features/grades/services/grades';
|
import { CoreGrades } from '@features/grades/services/grades';
|
||||||
import { CoreUserProfile } from '@features/user/services/user';
|
import { CoreUserProfile } from '@features/user/services/user';
|
||||||
|
@ -29,6 +28,7 @@ import { CoreNavigator } from '@services/navigator';
|
||||||
import { CoreSites } from '@services/sites';
|
import { CoreSites } from '@services/sites';
|
||||||
import { CoreUtils } from '@services/utils/utils';
|
import { CoreUtils } from '@services/utils/utils';
|
||||||
import { makeSingleton } from '@singletons';
|
import { makeSingleton } from '@singletons';
|
||||||
|
import { GRADES_PAGE_NAME } from '../grades-helper';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Profile grades handler.
|
* Profile grades handler.
|
||||||
|
|
|
@ -73,11 +73,11 @@
|
||||||
<h3 class="item-heading">{{ 'core.login.potentialidps' | translate }}</h3>
|
<h3 class="item-heading">{{ 'core.login.potentialidps' | translate }}</h3>
|
||||||
</ion-label>
|
</ion-label>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
<ion-item button *ngFor="let provider of identityProviders" class="ion-text-wrap core-oauth-icon"
|
<ion-button fill="outline" *ngFor="let provider of identityProviders" class="ion-text-wrap core-oauth-provider"
|
||||||
(click)="oauthClicked(provider)" [attr.aria-label]="provider.name" detail="false">
|
(click)="oauthClicked(provider)" [attr.aria-label]="provider.name" expand="block">
|
||||||
<img [src]="provider.iconurl" alt="" width="32" height="32" slot="start">
|
<img [src]="provider.iconurl" alt="" width="32" height="32" slot="start">
|
||||||
<ion-label>{{provider.name}}</ion-label>
|
<ion-label>{{provider.name}}</ion-label>
|
||||||
</ion-item>
|
</ion-button>
|
||||||
</ion-list>
|
</ion-list>
|
||||||
|
|
||||||
<ion-list *ngIf="canSignup" class="ion-padding-top core-login-sign-up">
|
<ion-list *ngIf="canSignup" class="ion-padding-top core-login-sign-up">
|
||||||
|
|
|
@ -87,11 +87,11 @@
|
||||||
<h3 class="item-heading">{{ 'core.login.potentialidps' | translate }}</h3>
|
<h3 class="item-heading">{{ 'core.login.potentialidps' | translate }}</h3>
|
||||||
</ion-label>
|
</ion-label>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
<ion-item button *ngFor="let provider of identityProviders" class="ion-text-wrap core-oauth-icon" (click)="oauthClicked(provider)"
|
<ion-button fill="outline" *ngFor="let provider of identityProviders" class="ion-text-wrap core-oauth-provider"
|
||||||
detail="false">
|
(click)="oauthClicked(provider)" [attr.aria-label]="provider.name" expand="block">
|
||||||
<img [src]="provider.iconurl" alt="" role="presentation" width="32" height="32" slot="start">
|
<img [src]="provider.iconurl" alt="" width="32" height="32" slot="start">
|
||||||
<ion-label>{{provider.name}}</ion-label>
|
<ion-label>{{provider.name}}</ion-label>
|
||||||
</ion-item>
|
</ion-button>
|
||||||
</ion-list>
|
</ion-list>
|
||||||
|
|
||||||
<!-- If OAuth, display cancel button since the form isn't displayed. -->
|
<!-- If OAuth, display cancel button since the form isn't displayed. -->
|
||||||
|
|
|
@ -15,7 +15,7 @@
|
||||||
import { Component, OnChanges, Input, ViewChild, Output, EventEmitter } from '@angular/core';
|
import { Component, OnChanges, Input, ViewChild, Output, EventEmitter } from '@angular/core';
|
||||||
import { IonRefresher } from '@ionic/angular';
|
import { IonRefresher } from '@ionic/angular';
|
||||||
|
|
||||||
import { CoreCourseFormatComponent } from '@features/course/components/format/format';
|
import { CoreCourseFormatComponent } from '@features/course/components/course-format/course-format';
|
||||||
import { CoreCourseModuleCompletionData, CoreCourseSection } from '@features/course/services/course-helper';
|
import { CoreCourseModuleCompletionData, CoreCourseSection } from '@features/course/services/course-helper';
|
||||||
import { CoreCourseFormatDelegate } from '@features/course/services/format-delegate';
|
import { CoreCourseFormatDelegate } from '@features/course/services/format-delegate';
|
||||||
import { CoreCourseAnyCourseData } from '@features/courses/services/courses';
|
import { CoreCourseAnyCourseData } from '@features/courses/services/courses';
|
||||||
|
|
|
@ -1,28 +1,8 @@
|
||||||
<!-- Buttons to add to the header. -->
|
<!-- Buttons to add to the header. -->
|
||||||
<core-navbar-buttons slot="end">
|
<core-navbar-buttons slot="end">
|
||||||
<core-context-menu>
|
<ion-button fill="clear" (click)="openModuleSummary()" [attr.aria-label]="'core.info' | translate">
|
||||||
<core-context-menu-item [hidden]="!displayOpenInBrowser || !externalUrl || (
|
<ion-icon name="fas-info-circle" slot="icon-only" aria-hidden="true"></ion-icon>
|
||||||
content?.compileComponent?.componentInstance?.displayOpenInBrowser === false)" [priority]="900"
|
</ion-button>
|
||||||
[content]="'core.openinbrowser' | translate" [href]="externalUrl" iconAction="fas-external-link-alt">
|
|
||||||
</core-context-menu-item>
|
|
||||||
<core-context-menu-item [hidden]="!displayDescription || !description || (
|
|
||||||
content?.compileComponent?.componentInstance?.displayDescription === false)" [priority]="800"
|
|
||||||
[content]="'core.moduleintro' | translate" (action)="expandDescription()" iconAction="fas-arrow-right">
|
|
||||||
</core-context-menu-item>
|
|
||||||
<core-context-menu-item [hidden]="!displayRefresh || (
|
|
||||||
content?.compileComponent?.componentInstance?.displayRefresh === false)" [priority]="700"
|
|
||||||
[content]="'core.refresh' | translate" (action)="doRefresh(null, $event)" [iconAction]="refreshIcon" [closeOnClick]="false">
|
|
||||||
</core-context-menu-item>
|
|
||||||
<core-context-menu-item [hidden]="!displayPrefetch || !prefetchStatusIcon || (
|
|
||||||
content?.compileComponent?.componentInstance?.displayPrefetch === false)" [priority]="600" [content]="prefetchText"
|
|
||||||
(action)="prefetch()" [iconAction]="prefetchStatusIcon" [closeOnClick]="false">
|
|
||||||
</core-context-menu-item>
|
|
||||||
<core-context-menu-item [hidden]="!displaySize || !size || (
|
|
||||||
content?.compileComponent?.componentInstance?.displaySize === false)" [priority]="500"
|
|
||||||
[content]="'core.clearstoreddata' | translate:{$a: size}" [iconDescription]="'fas-archive'" (action)="removeFiles()"
|
|
||||||
iconAction="fas-trash" [closeOnClick]="false">
|
|
||||||
</core-context-menu-item>
|
|
||||||
</core-context-menu>
|
|
||||||
</core-navbar-buttons>
|
</core-navbar-buttons>
|
||||||
|
|
||||||
<core-site-plugins-plugin-content *ngIf="component && method" [component]="component" [method]="method" [args]="args"
|
<core-site-plugins-plugin-content *ngIf="component && method" [component]="component" [method]="method" [args]="args"
|
||||||
|
|
|
@ -14,8 +14,13 @@
|
||||||
|
|
||||||
import { CoreConstants } from '@/core/constants';
|
import { CoreConstants } from '@/core/constants';
|
||||||
import { Component, OnInit, OnDestroy, Input, ViewChild } from '@angular/core';
|
import { Component, OnInit, OnDestroy, Input, ViewChild } from '@angular/core';
|
||||||
|
import { CoreIonLoadingElement } from '@classes/ion-loading';
|
||||||
|
|
||||||
import { CoreSiteWSPreSets } from '@classes/site';
|
import { CoreSiteWSPreSets } from '@classes/site';
|
||||||
|
import {
|
||||||
|
CoreCourseModuleSummaryResult,
|
||||||
|
CoreCourseModuleSummaryComponent,
|
||||||
|
} from '@features/course/components/module-summary/module-summary';
|
||||||
import { CoreCourseHelper, CoreCourseModuleData } from '@features/course/services/course-helper';
|
import { CoreCourseHelper, CoreCourseModuleData } from '@features/course/services/course-helper';
|
||||||
import {
|
import {
|
||||||
CoreCourseModuleDelegate,
|
CoreCourseModuleDelegate,
|
||||||
|
@ -28,10 +33,8 @@ import {
|
||||||
CoreSitePluginsCourseModuleHandlerData,
|
CoreSitePluginsCourseModuleHandlerData,
|
||||||
} from '@features/siteplugins/services/siteplugins';
|
} from '@features/siteplugins/services/siteplugins';
|
||||||
import { IonRefresher } from '@ionic/angular';
|
import { IonRefresher } from '@ionic/angular';
|
||||||
import { CoreTextUtils } from '@services/utils/text';
|
import { CoreDomUtils } from '@services/utils/dom';
|
||||||
import { CoreUtils } from '@services/utils/utils';
|
import { CoreUtils } from '@services/utils/utils';
|
||||||
import { Translate } from '@singletons';
|
|
||||||
import { CoreEventObserver } from '@singletons/events';
|
|
||||||
import { CoreSitePluginsPluginContentComponent } from '../plugin-content/plugin-content';
|
import { CoreSitePluginsPluginContentComponent } from '../plugin-content/plugin-content';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -55,22 +58,42 @@ export class CoreSitePluginsModuleIndexComponent implements OnInit, OnDestroy, C
|
||||||
args?: Record<string, unknown>;
|
args?: Record<string, unknown>;
|
||||||
initResult?: CoreSitePluginsContent | null;
|
initResult?: CoreSitePluginsContent | null;
|
||||||
preSets?: CoreSiteWSPreSets;
|
preSets?: CoreSiteWSPreSets;
|
||||||
|
|
||||||
// Data for context menu.
|
|
||||||
externalUrl?: string;
|
|
||||||
description?: string;
|
description?: string;
|
||||||
refreshIcon?: string;
|
|
||||||
|
/**
|
||||||
|
* @deprecated since 4.0, use module.url instead.
|
||||||
|
*/
|
||||||
|
externalUrl?: string;
|
||||||
|
/**
|
||||||
|
* @deprecated since 4.0. It won't be populated anymore.
|
||||||
|
*/
|
||||||
|
refreshIcon = CoreConstants.ICON_REFRESH;
|
||||||
|
/**
|
||||||
|
* @deprecated since 4.0.. It won't be populated anymore.
|
||||||
|
*/
|
||||||
prefetchStatus?: string;
|
prefetchStatus?: string;
|
||||||
|
/**
|
||||||
|
* @deprecated since 4.0. It won't be populated anymore.
|
||||||
|
*/
|
||||||
prefetchStatusIcon?: string;
|
prefetchStatusIcon?: string;
|
||||||
|
/**
|
||||||
|
* @deprecated since 4.0. It won't be populated anymore.
|
||||||
|
*/
|
||||||
prefetchText?: string;
|
prefetchText?: string;
|
||||||
|
/**
|
||||||
|
* @deprecated since 4.0. It won't be populated anymore.
|
||||||
|
*/
|
||||||
size?: string;
|
size?: string;
|
||||||
contextMenuStatusObserver?: CoreEventObserver;
|
|
||||||
contextFileStatusObserver?: CoreEventObserver;
|
|
||||||
displayOpenInBrowser = true;
|
displayOpenInBrowser = true;
|
||||||
displayDescription = true;
|
displayDescription = true;
|
||||||
displayRefresh = true;
|
displayRefresh = true;
|
||||||
displayPrefetch = true;
|
displayPrefetch = true;
|
||||||
displaySize = true;
|
displaySize = true;
|
||||||
|
displayGrades = false;
|
||||||
|
// @TODO: // Currently display blogs is not an option since it may change soon adding new summary handlers.
|
||||||
|
displayBlog = false;
|
||||||
|
|
||||||
ptrEnabled = true;
|
ptrEnabled = true;
|
||||||
isDestroyed = false;
|
isDestroyed = false;
|
||||||
|
|
||||||
|
@ -80,8 +103,6 @@ export class CoreSitePluginsModuleIndexComponent implements OnInit, OnDestroy, C
|
||||||
* Component being initialized.
|
* Component being initialized.
|
||||||
*/
|
*/
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.refreshIcon = CoreConstants.ICON_LOADING;
|
|
||||||
|
|
||||||
if (!this.module) {
|
if (!this.module) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -110,6 +131,7 @@ export class CoreSitePluginsModuleIndexComponent implements OnInit, OnDestroy, C
|
||||||
this.displayRefresh = !CoreUtils.isFalseOrZero(handlerSchema.displayrefresh);
|
this.displayRefresh = !CoreUtils.isFalseOrZero(handlerSchema.displayrefresh);
|
||||||
this.displayPrefetch = !CoreUtils.isFalseOrZero(handlerSchema.displayprefetch);
|
this.displayPrefetch = !CoreUtils.isFalseOrZero(handlerSchema.displayprefetch);
|
||||||
this.displaySize = !CoreUtils.isFalseOrZero(handlerSchema.displaysize);
|
this.displaySize = !CoreUtils.isFalseOrZero(handlerSchema.displaysize);
|
||||||
|
this.displayGrades = CoreUtils.isTrueOrOne(handlerSchema.displaygrades); // False by default.
|
||||||
this.ptrEnabled = !CoreUtils.isFalseOrZero(handlerSchema.ptrenabled);
|
this.ptrEnabled = !CoreUtils.isFalseOrZero(handlerSchema.ptrenabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -122,71 +144,114 @@ export class CoreSitePluginsModuleIndexComponent implements OnInit, OnDestroy, C
|
||||||
* Refresh the data.
|
* Refresh the data.
|
||||||
*
|
*
|
||||||
* @param refresher Refresher.
|
* @param refresher Refresher.
|
||||||
* @param done Function to call when done.
|
|
||||||
* @return Promise resolved when done.
|
* @return Promise resolved when done.
|
||||||
*/
|
*/
|
||||||
async doRefresh(refresher?: IonRefresher | null, done?: () => void): Promise<void> {
|
async doRefresh(refresher?: IonRefresher | null): Promise<void> {
|
||||||
if (this.content) {
|
|
||||||
this.refreshIcon = CoreConstants.ICON_LOADING;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await this.content?.refreshContent(false);
|
await this.content?.refreshContent(false);
|
||||||
} finally {
|
} finally {
|
||||||
refresher?.complete();
|
refresher?.complete();
|
||||||
done && done();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Function called when the data of the site plugin content is loaded.
|
* Function called when the data of the site plugin content is loaded.
|
||||||
*/
|
*/
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
contentLoaded(refresh: boolean): void {
|
contentLoaded(refresh: boolean): void {
|
||||||
this.refreshIcon = CoreConstants.ICON_REFRESH;
|
return;
|
||||||
|
|
||||||
// Check if there is a prefetch handler for this type of module.
|
|
||||||
if (CoreCourseModulePrefetchDelegate.getPrefetchHandlerFor(this.module.modname)) {
|
|
||||||
CoreCourseHelper.fillContextMenu(this, this.module, this.courseId, refresh, this.component);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Function called when starting to load the data of the site plugin content.
|
* Function called when starting to load the data of the site plugin content.
|
||||||
*/
|
*/
|
||||||
contentLoading(): void {
|
contentLoading(): void {
|
||||||
this.refreshIcon = CoreConstants.ICON_LOADING;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Expand the description.
|
* Expand the description.
|
||||||
|
*
|
||||||
|
* @deprecated since 4.0
|
||||||
*/
|
*/
|
||||||
expandDescription(): void {
|
expandDescription(): void {
|
||||||
if (!this.description) {
|
this.openModuleSummary();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Opens a module summary page.
|
||||||
|
*/
|
||||||
|
async openModuleSummary(): Promise<void> {
|
||||||
|
if (!this.module) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
CoreTextUtils.viewText(Translate.instant('core.description'), this.description, {
|
const data = await CoreDomUtils.openSideModal<CoreCourseModuleSummaryResult>({
|
||||||
|
component: CoreCourseModuleSummaryComponent,
|
||||||
|
componentProps: {
|
||||||
|
moduleId: this.module.id,
|
||||||
|
module: this.module,
|
||||||
|
description: this.description,
|
||||||
component: this.component,
|
component: this.component,
|
||||||
componentId: this.module.id,
|
|
||||||
filter: true,
|
|
||||||
contextLevel: 'module',
|
|
||||||
instanceId: this.module.id,
|
|
||||||
courseId: this.courseId,
|
courseId: this.courseId,
|
||||||
|
displayOptions: {
|
||||||
|
displayOpenInBrowser: this.displayOpenInBrowser,
|
||||||
|
displayDescription: this.displayDescription,
|
||||||
|
displayRefresh: this.displayRefresh,
|
||||||
|
displayPrefetch: this.displayPrefetch,
|
||||||
|
displaySize: this.displaySize,
|
||||||
|
displayBlog: this.displayBlog,
|
||||||
|
displayGrades: this.displayGrades,
|
||||||
|
},
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (data && data.action == 'refresh' && this.content?.dataLoaded) {
|
||||||
|
this.content?.refreshContent(true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Prefetch the module.
|
* Prefetch the module.
|
||||||
|
*
|
||||||
|
* @deprecated since 4.0
|
||||||
*/
|
*/
|
||||||
prefetch(): void {
|
async prefetch(): Promise<void> {
|
||||||
CoreCourseHelper.contextMenuPrefetch(this, this.module, this.courseId);
|
try {
|
||||||
|
// We need to call getDownloadSize, the package might have been updated.
|
||||||
|
const size = await CoreCourseModulePrefetchDelegate.getModuleDownloadSize(this.module, this.courseId, true);
|
||||||
|
|
||||||
|
await CoreDomUtils.confirmDownloadSize(size);
|
||||||
|
|
||||||
|
await CoreCourseModulePrefetchDelegate.prefetchModule(this.module, this.courseId, true);
|
||||||
|
} catch (error) {
|
||||||
|
if (!this.isDestroyed) {
|
||||||
|
CoreDomUtils.showErrorModalDefault(error, 'core.errordownloading', true);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Confirm and remove downloaded files.
|
* Confirm and remove downloaded files.
|
||||||
|
*
|
||||||
|
* @deprecated since 4.0
|
||||||
*/
|
*/
|
||||||
removeFiles(): void {
|
async removeFiles(): Promise<void> {
|
||||||
CoreCourseHelper.confirmAndRemoveFiles(this.module, this.courseId);
|
let modal: CoreIonLoadingElement | undefined;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await CoreDomUtils.showDeleteConfirm('addon.storagemanager.confirmdeletedatafrom', { name: this.module.name });
|
||||||
|
|
||||||
|
modal = await CoreDomUtils.showModalLoading();
|
||||||
|
|
||||||
|
await CoreCourseHelper.removeModuleStoredData(this.module, this.courseId);
|
||||||
|
} catch (error) {
|
||||||
|
if (error) {
|
||||||
|
CoreDomUtils.showErrorModal(error);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
modal?.dismiss();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -871,6 +871,7 @@ export type CoreSitePluginsCourseModuleHandlerData = CoreSitePluginsHandlerCommo
|
||||||
displayrefresh?: boolean;
|
displayrefresh?: boolean;
|
||||||
displayprefetch?: boolean;
|
displayprefetch?: boolean;
|
||||||
displaysize?: boolean;
|
displaysize?: boolean;
|
||||||
|
displaygrades?: boolean;
|
||||||
coursepagemethod?: string;
|
coursepagemethod?: string;
|
||||||
ptrenabled?: boolean;
|
ptrenabled?: boolean;
|
||||||
supportedfeatures?: Record<string, unknown>;
|
supportedfeatures?: Record<string, unknown>;
|
||||||
|
|
|
@ -297,6 +297,21 @@ button,
|
||||||
|
|
||||||
ion-button {
|
ion-button {
|
||||||
margin: 4px 8px;
|
margin: 4px 8px;
|
||||||
|
|
||||||
|
ion-spinner[slot=start],
|
||||||
|
img[slot=start] {
|
||||||
|
@include margin-horizontal(-0.3em, 0.3em);
|
||||||
|
}
|
||||||
|
|
||||||
|
ion-spinner[slot=end],
|
||||||
|
img[slot=end] {
|
||||||
|
@include margin-horizontal(-0.3em, 0.3em);
|
||||||
|
}
|
||||||
|
|
||||||
|
ion-spinner[slot] {
|
||||||
|
width: 20px;
|
||||||
|
color: inherit;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ion-button.button-outline {
|
ion-button.button-outline {
|
||||||
|
@ -460,6 +475,14 @@ ion-alert {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ion-loading {
|
||||||
|
--border-radius: var(--huge-radius);
|
||||||
|
|
||||||
|
.loading-wrapper {
|
||||||
|
border-radius: var(--border-radius) !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Ionic list.
|
// Ionic list.
|
||||||
ion-list {
|
ion-list {
|
||||||
padding: 0 !important;
|
padding: 0 !important;
|
||||||
|
@ -1465,6 +1488,16 @@ ion-grid.core-no-grid > ion-row {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ion-header.no-title {
|
||||||
|
--core-header-toolbar-border-width: 0;
|
||||||
|
--core-header-toolbar-background: transparent;
|
||||||
|
|
||||||
|
ion-toolbar .button.button-clear,
|
||||||
|
ion-toolbar .button.button-solid {
|
||||||
|
--background: var(--ion-background-color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ion-header[collapsible] {
|
ion-header[collapsible] {
|
||||||
@include core-transition(all, 500ms);
|
@include core-transition(all, 500ms);
|
||||||
|
|
||||||
|
|
|
@ -233,6 +233,9 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
--core-loading-spinner: var(--brand);
|
--core-loading-spinner: var(--brand);
|
||||||
|
ion-loading {
|
||||||
|
--spinner-color: var(--core-loading-spinner);
|
||||||
|
}
|
||||||
ion-spinner, ion-refresher {
|
ion-spinner, ion-refresher {
|
||||||
--ion-color-base: var(--core-loading-spinner);
|
--ion-color-base: var(--core-loading-spinner);
|
||||||
--ion-color-primary: var(--core-loading-spinner);
|
--ion-color-primary: var(--core-loading-spinner);
|
||||||
|
|
|
@ -20,6 +20,10 @@ information provided here is intended especially for developers.
|
||||||
- Most of the functions or callbacks that handle redirects/deeplinks have been modified to accept an object instead of just path + options. E.g.: CoreLoginHelper.isSiteLoggedOut, CoreLoginHelper.openBrowserForSSOLogin, CoreLoginHelper.openBrowserForOAuthLogin, CoreLoginHelper.prepareForSSOLogin, CoreApp.storeRedirect, CoreSites.loadSite.
|
- Most of the functions or callbacks that handle redirects/deeplinks have been modified to accept an object instead of just path + options. E.g.: CoreLoginHelper.isSiteLoggedOut, CoreLoginHelper.openBrowserForSSOLogin, CoreLoginHelper.openBrowserForOAuthLogin, CoreLoginHelper.prepareForSSOLogin, CoreApp.storeRedirect, CoreSites.loadSite.
|
||||||
- Course preview page route has changed from course/:courseId/preview to course/:courseId/summary to match with the page name and characteristics.
|
- Course preview page route has changed from course/:courseId/preview to course/:courseId/summary to match with the page name and characteristics.
|
||||||
- The parameters of the following functions in CoreCourseHelper have changed: navigateToModuleByInstance, navigateToModule, openModule.
|
- The parameters of the following functions in CoreCourseHelper have changed: navigateToModuleByInstance, navigateToModule, openModule.
|
||||||
|
- fillContextMenu, expandDescription, gotoBlog, prefetch and removeFiles functions have been removed from CoreCourseModuleMainResourceComponent.
|
||||||
|
- contextMenuPrefetch and fillContextMenu have been removed from CoreCourseHelper.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
=== 3.9.5 ===
|
=== 3.9.5 ===
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue