Merge pull request #1376 from dpalou/MOBILE-2431

Mobile 2431
main
Juan Leyva 2018-06-26 10:41:24 +02:00 committed by GitHub
commit 29388cdc47
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 158 additions and 110 deletions

View File

@ -18,12 +18,12 @@
</ion-item-divider> </ion-item-divider>
<ng-container *ngFor="let contact of contacts[contactType]"> <ng-container *ngFor="let contact of contacts[contactType]">
<!-- Don't show deleted users --> <!-- Don't show deleted users -->
<ion-item text-wrap *ngIf="contact.profileimageurl || contact.profileimageurlsmall" [title]="contact.fullname" (click)="gotoDiscussion(contact.id)" [class.core-split-item-selected]="contact.id == discussionUserId" detail-none> <a ion-item text-wrap *ngIf="contact.profileimageurl || contact.profileimageurlsmall" [title]="contact.fullname" (click)="gotoDiscussion(contact.id)" [class.core-split-item-selected]="contact.id == discussionUserId" detail-none>
<ion-avatar item-start> <ion-avatar item-start>
<img src="{{contact.profileimageurl || contact.profileimageurlsmall}}" [alt]="'core.pictureof' | translate:{$a: contact.fullname}" core-external-content onError="this.src='assets/img/user-avatar.png'"> <img src="{{contact.profileimageurl || contact.profileimageurlsmall}}" [alt]="'core.pictureof' | translate:{$a: contact.fullname}" core-external-content onError="this.src='assets/img/user-avatar.png'">
</ion-avatar> </ion-avatar>
<h2><core-format-text [text]="contact.fullname"></core-format-text></h2> <h2><core-format-text [text]="contact.fullname"></core-format-text></h2>
</ion-item> </a>
</ng-container> </ng-container>
</ng-container> </ng-container>
</ion-list> </ion-list>

View File

@ -16,17 +16,17 @@
<h2>{{ 'core.searchresults' | translate }}</h2> <h2>{{ 'core.searchresults' | translate }}</h2>
<ion-note item-end>{{ search.results.length }}</ion-note> <ion-note item-end>{{ search.results.length }}</ion-note>
</ion-item-divider> </ion-item-divider>
<ion-item text-wrap *ngFor="let result of search.results" [title]="result.fullname" (click)="gotoDiscussion(result.userid, result.messageid)" [class.core-split-item-selected]="result.userid == discussionUserId" detail-none> <a ion-item text-wrap *ngFor="let result of search.results" [title]="result.fullname" (click)="gotoDiscussion(result.userid, result.messageid)" [class.core-split-item-selected]="result.userid == discussionUserId" detail-none>
<ion-avatar item-start> <ion-avatar item-start>
<img src="{{result.profileimageurl}}" [alt]="'core.pictureof' | translate:{$a: result.fullname}" core-external-content onError="this.src='assets/img/user-avatar.png'"> <img src="{{result.profileimageurl}}" [alt]="'core.pictureof' | translate:{$a: result.fullname}" core-external-content onError="this.src='assets/img/user-avatar.png'">
</ion-avatar> </ion-avatar>
<h2><core-format-text [text]="result.fullname"></core-format-text></h2> <h2><core-format-text [text]="result.fullname"></core-format-text></h2>
<p><core-format-text clean="true" singleLine="true" [text]="result.lastmessage"></core-format-text></p> <p><core-format-text clean="true" singleLine="true" [text]="result.lastmessage"></core-format-text></p>
</ion-item> </a>
</ion-list> </ion-list>
<ion-list *ngIf="!search.showResults" no-margin> <ion-list *ngIf="!search.showResults" no-margin>
<ion-item text-wrap *ngFor="let discussion of discussions" [title]="discussion.fullname" (click)="gotoDiscussion(discussion.message.user)" [class.core-split-item-selected]="discussion.message.user == discussionUserId" detail-none> <a ion-item text-wrap *ngFor="let discussion of discussions" [title]="discussion.fullname" (click)="gotoDiscussion(discussion.message.user)" [class.core-split-item-selected]="discussion.message.user == discussionUserId" detail-none>
<ion-avatar item-start> <ion-avatar item-start>
<img src="{{discussion.profileimageurl}}" [alt]="'core.pictureof' | translate:{$a: discussion.fullname}" core-external-content onError="this.src='assets/img/user-avatar.png'"> <img src="{{discussion.profileimageurl}}" [alt]="'core.pictureof' | translate:{$a: discussion.fullname}" core-external-content onError="this.src='assets/img/user-avatar.png'">
</ion-avatar> </ion-avatar>
@ -38,7 +38,7 @@
</ion-note> </ion-note>
</h2> </h2>
<p><core-format-text clean="true" singleLine="true" [text]="discussion.message.message"></core-format-text></p> <p><core-format-text clean="true" singleLine="true" [text]="discussion.message.message"></core-format-text></p>
</ion-item> </a>
</ion-list> </ion-list>
</core-loading> </core-loading>
</ion-content> </ion-content>

View File

@ -201,14 +201,14 @@
</ion-item> </ion-item>
<!-- Data about the grader (teacher who graded). --> <!-- Data about the grader (teacher who graded). -->
<ion-item text-wrap *ngIf="grader" (click)="openUserProfile(grader.id)" [title]="grader.fullname" detail-push> <a ion-item text-wrap *ngIf="grader" (click)="openUserProfile(grader.id)" [title]="grader.fullname" detail-push>
<ion-avatar item-start> <ion-avatar item-start>
<img [src]="grader.profileimageurl" core-external-content [alt]="'core.pictureof' | translate:{$a: grader.fullname}" role="presentation" onError="this.src='assets/img/user-avatar.png'"> <img [src]="grader.profileimageurl" core-external-content [alt]="'core.pictureof' | translate:{$a: grader.fullname}" role="presentation" onError="this.src='assets/img/user-avatar.png'">
</ion-avatar> </ion-avatar>
<h2>{{ 'addon.mod_assign.gradedby' | translate }}</h2> <h2>{{ 'addon.mod_assign.gradedby' | translate }}</h2>
<h2>{{ grader.fullname }}</h2> <h2>{{ grader.fullname }}</h2>
<p *ngIf="feedback.gradeddate">{{ feedback.gradeddate * 1000 | coreFormatDate:"dfmediumdate" }}</p> <p *ngIf="feedback.gradeddate">{{ feedback.gradeddate * 1000 | coreFormatDate:"dfmediumdate" }}</p>
</ion-item> </a>
<!-- Warning message if cannot save grades. --> <!-- Warning message if cannot save grades. -->
<div *ngIf="isGrading && !canSaveGrades" class="core-warning-card" icon-start> <div *ngIf="isGrading && !canSaveGrades" class="core-warning-card" icon-start>

View File

@ -1,4 +1,4 @@
<ion-item text-wrap (click)="showComments()"> <a ion-item text-wrap (click)="showComments()" detail-none>
<h2>{{plugin.name}}</h2> <h2>{{plugin.name}}</h2>
<core-comments contextLevel="module" [instanceId]="assign.cmid" component="assignsubmission_comments" [itemId]="submission.id" area="submission_comments" [title]="plugin.name"></core-comments> <core-comments contextLevel="module" [instanceId]="assign.cmid" component="assignsubmission_comments" [itemId]="submission.id" area="submission_comments" [title]="plugin.name"></core-comments>
</ion-item> </a>

View File

@ -51,13 +51,13 @@
<ion-option *ngFor="let groupOpt of groupInfo.groups" [value]="groupOpt.id">{{groupOpt.name}}</ion-option> <ion-option *ngFor="let groupOpt of groupInfo.groups" [value]="groupOpt.id">{{groupOpt.name}}</ion-option>
</ion-select> </ion-select>
</ion-item> </ion-item>
<ion-item text-wrap *ngIf="access.canviewreports || access.canedititems" (click)="access.canviewreports && openFeature('Respondents')" [attr.detail-push]="access.canviewreports ? true : null"> <a ion-item text-wrap *ngIf="access.canviewreports || access.canedititems" (click)="access.canviewreports && openFeature('Respondents')" [attr.detail-none]="access.canviewreports ? null : true">
<h2>{{ 'addon.mod_feedback.completed_feedbacks' | translate }}</h2> <h2>{{ 'addon.mod_feedback.completed_feedbacks' | translate }}</h2>
<ion-badge item-end>{{feedback.completedCount}}</ion-badge> <ion-badge item-end>{{feedback.completedCount}}</ion-badge>
</ion-item> </a>
<ion-item text-wrap *ngIf="!access.isanonymous && access.canviewreports" (click)="openFeature('NonRespondents')" detail-push> <a ion-item text-wrap *ngIf="!access.isanonymous && access.canviewreports" (click)="openFeature('NonRespondents')" detail-push>
<h2>{{ 'addon.mod_feedback.show_nonrespondents' | translate }}</h2> <h2>{{ 'addon.mod_feedback.show_nonrespondents' | translate }}</h2>
</ion-item> </a>
<ion-item text-wrap *ngIf="access.canedititems"> <ion-item text-wrap *ngIf="access.canedititems">
<h2>{{ 'addon.mod_feedback.questions' | translate }}</h2> <h2>{{ 'addon.mod_feedback.questions' | translate }}</h2>
<ion-badge item-end>{{feedback.itemsCount}}</ion-badge> <ion-badge item-end>{{feedback.itemsCount}}</ion-badge>

View File

@ -36,9 +36,9 @@
<ion-item-divider color="light"> <ion-item-divider color="light">
{{ 'addon.mod_glossary.entriestobesynced' | translate }} {{ 'addon.mod_glossary.entriestobesynced' | translate }}
</ion-item-divider> </ion-item-divider>
<ion-item *ngFor="let entry of offlineEntries" (click)="openNewEntry(entry)"> <a ion-item *ngFor="let entry of offlineEntries" (click)="openNewEntry(entry)" detail-none>
<p>{{entry.concept}}</p> <p>{{entry.concept}}</p>
</ion-item> </a>
</ion-list> </ion-list>
<ion-list *ngIf="entries.length > 0"> <ion-list *ngIf="entries.length > 0">
@ -48,9 +48,9 @@
{{getDivider(entry)}} {{getDivider(entry)}}
</ion-item-divider> </ion-item-divider>
<ion-item (click)="openEntry(entry.id)" [class.core-split-item-selected]="entry.id == selectedEntry"> <a ion-item (click)="openEntry(entry.id)" [class.core-split-item-selected]="entry.id == selectedEntry" detail-none>
<p>{{entry.concept}}</p> <p>{{entry.concept}}</p>
</ion-item> </a>
</ng-container> </ng-container>
</ion-list> </ion-list>

View File

@ -1,5 +1,5 @@
<core-loading [hideUntil]="loaded"> <core-loading [hideUntil]="loaded">
<ion-item *ngIf="summary" text-wrap [attr.detail-push]="canViewAssessment && !canSelfAssess? true : null" (click)="gotoAssessment()"> <a ion-item *ngIf="summary" text-wrap [attr.detail-none]="canViewAssessment && !canSelfAssess? null : true" (click)="gotoAssessment()">
<ion-avatar item-start> <ion-avatar item-start>
<img [src]="profile && profile.profileimageurl" core-external-content [alt]="'core.pictureof' | translate:{$a: profile && profile.fullname}" core-user-link [courseId]="courseId" [userId]="profile && profile.id" role="presentation" onError="this.src='assets/img/user-avatar.png'"> <img [src]="profile && profile.profileimageurl" core-external-content [alt]="'core.pictureof' | translate:{$a: profile && profile.fullname}" core-user-link [courseId]="courseId" [userId]="profile && profile.id" role="presentation" onError="this.src='assets/img/user-avatar.png'">
</ion-avatar> </ion-avatar>
@ -23,5 +23,5 @@
<ion-note item-end *ngIf="offline"> <ion-note item-end *ngIf="offline">
<ion-icon name="time"></ion-icon>{{ 'core.notsent' | translate }} <ion-icon name="time"></ion-icon>{{ 'core.notsent' | translate }}
</ion-note> </ion-note>
</ion-item> </a>
</core-loading> </core-loading>

View File

@ -15,11 +15,11 @@
<core-course-module-description *ngIf="description && selectedPhase == workshopPhases.PHASE_SETUP" [description]="description" [component]="component" [componentId]="componentId"></core-course-module-description> <core-course-module-description *ngIf="description && selectedPhase == workshopPhases.PHASE_SETUP" [description]="description" [component]="component" [componentId]="componentId"></core-course-module-description>
<ion-card class="with-borders" *ngIf="phases"> <ion-card class="with-borders" *ngIf="phases">
<ion-item (click)="selectPhase()"> <a ion-item (click)="selectPhase()" detail-none>
<h2 stacked text-wrap>{{ phases[selectedPhase].title }}</h2> <h2 stacked text-wrap>{{ phases[selectedPhase].title }}</h2>
<p text-wrap *ngIf="phases[selectedPhase].code == workshop.phase">{{ 'addon.mod_workshop.userplancurrentphase' | translate }}</p> <p text-wrap *ngIf="phases[selectedPhase].code == workshop.phase">{{ 'addon.mod_workshop.userplancurrentphase' | translate }}</p>
<ion-icon item-end name="arrow-dropdown"></ion-icon> <ion-icon item-end name="arrow-dropdown"></ion-icon>
</ion-item> </a>
<a ion-item text-wrap *ngIf="phases[selectedPhase].switchUrl" [href]="phases[selectedPhase].switchUrl" detail-none> <a ion-item text-wrap *ngIf="phases[selectedPhase].switchUrl" [href]="phases[selectedPhase].switchUrl" detail-none>
<ion-icon item-start name="swap"></ion-icon> <ion-icon item-start name="swap"></ion-icon>
{{ 'addon.mod_workshop.switchphase' + selectedPhase | translate }} {{ 'addon.mod_workshop.switchphase' + selectedPhase | translate }}
@ -28,7 +28,7 @@
</ion-card> </ion-card>
<ion-card class="with-borders" *ngIf="phases && phases[selectedPhase] && phases[selectedPhase].tasks && phases[selectedPhase].tasks.length"> <ion-card class="with-borders" *ngIf="phases && phases[selectedPhase] && phases[selectedPhase].tasks && phases[selectedPhase].tasks.length">
<ion-item text-wrap *ngFor="let task of phases[selectedPhase].tasks" [class.item-dimmed]="selectedPhase != workshop.phase" (click)="runTask(task)" detail-none> <a ion-item text-wrap *ngFor="let task of phases[selectedPhase].tasks" [class.item-dimmed]="selectedPhase != workshop.phase" (click)="runTask(task)" detail-none>
<ion-icon item-start name="radio-button-off" *ngIf="task.completed == null"></ion-icon> <ion-icon item-start name="radio-button-off" *ngIf="task.completed == null"></ion-icon>
<ion-icon item-start name="close-circle" color="danger" *ngIf="task.completed == ''"></ion-icon> <ion-icon item-start name="close-circle" color="danger" *ngIf="task.completed == ''"></ion-icon>
<ion-icon item-start name="information-circle" color="info" *ngIf="task.completed == 'info'"></ion-icon> <ion-icon item-start name="information-circle" color="info" *ngIf="task.completed == 'info'"></ion-icon>
@ -37,7 +37,7 @@
<h2>{{task.title}}</h2> <h2>{{task.title}}</h2>
<p *ngIf="task.details"><core-format-text [text]="task.details"></core-format-text></p> <p *ngIf="task.details"><core-format-text [text]="task.details"></core-format-text></p>
<ion-icon item-end *ngIf="task.link && !task.support" name="open"></ion-icon> <ion-icon item-end *ngIf="task.link && !task.support" name="open"></ion-icon>
</ion-item> </a>
</ion-card> </ion-card>
<!-- Has something offline. --> <!-- Has something offline. -->
@ -102,12 +102,12 @@
</ng-container> </ng-container>
<ion-card class="with-borders" *ngIf="!access.canviewallsubmissions && selectedPhase == workshop.phase && (canSubmit || canAssess) && selectedPhase == workshopPhases.PHASE_EVALUATION"> <ion-card class="with-borders" *ngIf="!access.canviewallsubmissions && selectedPhase == workshop.phase && (canSubmit || canAssess) && selectedPhase == workshopPhases.PHASE_EVALUATION">
<ion-item text-wrap *ngIf="submission" (click)="switchPhase(workshopPhases.PHASE_SUBMISSION)" detail-push> <a ion-item text-wrap *ngIf="submission" (click)="switchPhase(workshopPhases.PHASE_SUBMISSION)" detail-push>
<h2>{{ 'addon.mod_workshop.yoursubmission' | translate }}</h2> <h2>{{ 'addon.mod_workshop.yoursubmission' | translate }}</h2>
</ion-item> </a>
<ion-item text-wrap *ngIf="canAssess" (click)="switchPhase(workshopPhases.PHASE_ASSESSMENT)" detail-push> <a ion-item text-wrap *ngIf="canAssess" (click)="switchPhase(workshopPhases.PHASE_ASSESSMENT)" detail-push>
<h2>{{ 'addon.mod_workshop.assignedassessments' | translate }}</h2> <h2>{{ 'addon.mod_workshop.assignedassessments' | translate }}</h2>
</ion-item> </a>
</ion-card> </ion-card>
<!-- CLOSED PHASE --> <!-- CLOSED PHASE -->
@ -123,14 +123,14 @@
<ion-item-divider color="light" text-wrap> <ion-item-divider color="light" text-wrap>
<h2>{{ 'addon.mod_workshop.yourgrades' | translate }}</h2> <h2>{{ 'addon.mod_workshop.yourgrades' | translate }}</h2>
</ion-item-divider> </ion-item-divider>
<ion-item text-wrap *ngIf="userGrades.submissionlongstrgrade" (click)="switchPhase(workshopPhases.PHASE_SUBMISSION)" detail-push> <a ion-item text-wrap *ngIf="userGrades.submissionlongstrgrade" (click)="switchPhase(workshopPhases.PHASE_SUBMISSION)" detail-push>
<h2>{{ 'addon.mod_workshop.submissiongrade' | translate }}</h2> <h2>{{ 'addon.mod_workshop.submissiongrade' | translate }}</h2>
<core-format-text [text]="userGrades.submissionlongstrgrade"></core-format-text> <core-format-text [text]="userGrades.submissionlongstrgrade"></core-format-text>
</ion-item> </a>
<ion-item text-wrap *ngIf="userGrades.assessmentlongstrgrade" (click)="switchPhase(workshopPhases.PHASE_ASSESSMENT)" detail-push> <a ion-item text-wrap *ngIf="userGrades.assessmentlongstrgrade" (click)="switchPhase(workshopPhases.PHASE_ASSESSMENT)" detail-push>
<h2>{{ 'addon.mod_workshop.gradinggrade' | translate }}</h2> <h2>{{ 'addon.mod_workshop.gradinggrade' | translate }}</h2>
<core-format-text [text]="userGrades.assessmentlongstrgrade"></core-format-text> <core-format-text [text]="userGrades.assessmentlongstrgrade"></core-format-text>
</ion-item> </a>
</ion-card> </ion-card>
<ion-card class="with-borders" *ngIf="publishedSubmissions && publishedSubmissions.length"> <ion-card class="with-borders" *ngIf="publishedSubmissions && publishedSubmissions.length">

View File

@ -47,7 +47,7 @@
</ion-item> </ion-item>
</div> </div>
<ion-item text-wrap *ngIf="summary" [attr.detail-push]="submission.timemodified? true : null" (click)="gotoSubmission()"> <a ion-item text-wrap *ngIf="summary" [attr.detail-none]="submission.timemodified ? null : true" (click)="gotoSubmission()">
<ion-avatar item-start> <ion-avatar item-start>
<img [src]="profile && profile.profileimageurl" core-external-content [alt]="'core.pictureof' | translate:{$a: profile && profile.fullname}" core-user-link [courseId]="courseId" [userId]="profile && profile.id" role="presentation" onError="this.src='assets/img/user-avatar.png'"> <img [src]="profile && profile.profileimageurl" core-external-content [alt]="'core.pictureof' | translate:{$a: profile && profile.fullname}" core-user-link [courseId]="courseId" [userId]="profile && profile.id" role="presentation" onError="this.src='assets/img/user-avatar.png'">
</ion-avatar> </ion-avatar>
@ -78,5 +78,5 @@
<div *ngIf="offline"><ion-icon name="time"></ion-icon> {{ 'core.notsent' | translate }}</div> <div *ngIf="offline"><ion-icon name="time"></ion-icon> {{ 'core.notsent' | translate }}</div>
<div *ngIf="submission.deleted"><ion-icon name="trash"></ion-icon> {{ 'core.deletedoffline' | translate }}</div> <div *ngIf="submission.deleted"><ion-icon name="trash"></ion-icon> {{ 'core.deletedoffline' | translate }}</div>
</ion-note> </ion-note>
</ion-item> </a>
</core-loading> </core-loading>

View File

@ -376,7 +376,10 @@ export class CoreSite {
* @param {any} Config. * @param {any} Config.
*/ */
setConfig(config: any): void { setConfig(config: any): void {
config.tool_mobile_disabledfeatures = this.textUtils.treatDisabledFeatures(config.tool_mobile_disabledfeatures); if (config) {
config.tool_mobile_disabledfeatures = this.textUtils.treatDisabledFeatures(config.tool_mobile_disabledfeatures);
}
this.config = config; this.config = config;
this.calculateOfflineDisabled(); this.calculateOfflineDisabled();
} }

View File

@ -1,3 +1,10 @@
<!-- Buttons to add to the header. *ngIf is needed, otherwise the component is executed too soon and doesn't find the header. -->
<core-navbar-buttons end *ngIf="loaded">
<core-context-menu>
<core-context-menu-item [hidden]="!displaySectionSelector || !sections || !sections.length" [priority]="500" [content]="'core.course.sections' | translate" (action)="showSectionSelector($event)" iconAction="menu"></core-context-menu-item>
</core-context-menu>
</core-navbar-buttons>
<!-- Default course format. --> <!-- Default course format. -->
<core-dynamic-component [component]="courseFormatComponent" [data]="data"> <core-dynamic-component [component]="courseFormatComponent" [data]="data">
<!-- Course summary. By default we only display the course progress. --> <!-- Course summary. By default we only display the course progress. -->

View File

@ -10,10 +10,10 @@
</ion-header> </ion-header>
<ion-content> <ion-content>
<ng-container *ngFor="let section of sections"> <ng-container *ngFor="let section of sections">
<ion-item *ngIf="sectionHasContent(section)" text-wrap (click)="selectSection(section)" [class.core-primary-item]="selected.id == section.id" [class.item-dimmed]="section.visible === 0 || section.uservisible === false"> <a ion-item *ngIf="sectionHasContent(section)" text-wrap (click)="selectSection(section)" [class.core-primary-item]="selected.id == section.id" [class.item-dimmed]="section.visible === 0 || section.uservisible === false" detail-none>
<h2><core-format-text [text]="section.formattedName || section.name"></core-format-text></h2> <h2><core-format-text [text]="section.formattedName || section.name"></core-format-text></h2>
<ion-badge color="secondary" *ngIf="section.visible === 0">{{ 'core.course.nocontentavailable' | translate }}</ion-badge> <ion-badge color="secondary" *ngIf="section.visible === 0">{{ 'core.course.nocontentavailable' | translate }}</ion-badge>
<ion-badge color="secondary" *ngIf="section.availabilityinfo"><core-format-text [text]=" section.availabilityinfo"></core-format-text></ion-badge> <ion-badge color="secondary" *ngIf="section.availabilityinfo"><core-format-text [text]=" section.availabilityinfo"></core-format-text></ion-badge>
</ion-item> </a>
</ng-container> </ng-container>
</ion-content> </ion-content>

View File

@ -209,7 +209,7 @@ export class CoreCourseSectionPage implements OnDestroy {
// Get the overview files. // Get the overview files.
if (this.course.overviewfiles) { if (this.course.overviewfiles) {
this.course.imageThumb = this.course.overviewfiles[0] && this.course.overviewfiles[0].fileurl; this.course.imageThumb = this.course.overviewfiles[0] && this.course.overviewfiles[0].fileurl;
} else { } else if (this.coursesProvider.isGetCoursesByFieldAvailable()) {
promises.push(this.coursesProvider.getCoursesByField('id', this.course.id).then((coursesInfo) => { promises.push(this.coursesProvider.getCoursesByField('id', this.course.id).then((coursesInfo) => {
if (coursesInfo[0] && coursesInfo[0].overviewfiles && coursesInfo[0].overviewfiles[0]) { if (coursesInfo[0] && coursesInfo[0].overviewfiles && coursesInfo[0].overviewfiles[0]) {
this.course.imageThumb = coursesInfo[0].overviewfiles[0].fileurl; this.course.imageThumb = coursesInfo[0].overviewfiles[0].fileurl;

View File

@ -97,6 +97,15 @@ export class CoreCourseFormatDefaultHandler implements CoreCourseFormatHandler {
* @return {any|Promise<any>} Current section (or promise resolved with current section). * @return {any|Promise<any>} Current section (or promise resolved with current section).
*/ */
getCurrentSection(course: any, sections: any[]): any | Promise<any> { getCurrentSection(course: any, sections: any[]): any | Promise<any> {
if (!this.coursesProvider.isGetCoursesByFieldAvailable()) {
// Cannot get the current section, return the first one.
if (sections[0].id != CoreCourseProvider.ALL_SECTIONS_ID) {
return sections[0];
}
return sections[1];
}
// We need the "marker" to determine the current section. // We need the "marker" to determine the current section.
return this.coursesProvider.getCoursesByField('id', course.id).catch(() => { return this.coursesProvider.getCoursesByField('id', course.id).catch(() => {
// Ignore errors. // Ignore errors.

View File

@ -1007,7 +1007,9 @@ export class CoreCourseHelperProvider {
}); });
// Prefetch other data needed to render the course. // Prefetch other data needed to render the course.
promises.push(this.coursesProvider.getCoursesByField('id', course.id)); if (this.coursesProvider.isGetCoursesByFieldAvailable()) {
promises.push(this.coursesProvider.getCoursesByField('id', course.id));
}
promises.push(this.courseProvider.getActivitiesCompletionStatus(course.id)); promises.push(this.courseProvider.getActivitiesCompletionStatus(course.id));
return this.utils.allPromises(promises); return this.utils.allPromises(promises);

View File

@ -395,7 +395,9 @@ export class CoreCourseOptionsDelegate extends CoreDelegate {
* @return {Promise<void>} Promise resolved when done. * @return {Promise<void>} Promise resolved when done.
*/ */
protected loadCourseOptions(course: any, refresh?: boolean): Promise<void> { protected loadCourseOptions(course: any, refresh?: boolean): Promise<void> {
if (typeof course.navOptions == 'undefined' || typeof course.admOptions == 'undefined' || refresh) { if (this.coursesProvider.canGetAdminAndNavOptions() &&
(typeof course.navOptions == 'undefined' || typeof course.admOptions == 'undefined' || refresh)) {
return this.coursesProvider.getCoursesAdminAndNavOptions([course.id]).then((options) => { return this.coursesProvider.getCoursesAdminAndNavOptions([course.id]).then((options) => {
course.navOptions = options.navOptions[course.id]; course.navOptions = options.navOptions[course.id];
course.admOptions = options.admOptions[course.id]; course.admOptions = options.admOptions[course.id];

View File

@ -96,7 +96,7 @@ export class CoreCoursesMyCoursesPage implements OnDestroy {
this.courseIds = courseIds.join(','); this.courseIds = courseIds.join(',');
if (this.courseIds) { if (this.courseIds && this.coursesProvider.isGetCoursesByFieldAvailable()) {
// Load course image of all the courses. // Load course image of all the courses.
promises.push(this.coursesProvider.getCoursesByField('ids', this.courseIds).then((coursesInfo) => { promises.push(this.coursesProvider.getCoursesByField('ids', this.courseIds).then((coursesInfo) => {
coursesInfo = this.utils.arrayToObject(coursesInfo, 'id'); coursesInfo = this.utils.arrayToObject(coursesInfo, 'id');
@ -111,12 +111,14 @@ export class CoreCoursesMyCoursesPage implements OnDestroy {
})); }));
} }
promises.push(this.coursesProvider.getCoursesAdminAndNavOptions(courseIds).then((options) => { if (this.coursesProvider.canGetAdminAndNavOptions()) {
courses.forEach((course) => { promises.push(this.coursesProvider.getCoursesAdminAndNavOptions(courseIds).then((options) => {
course.navOptions = options.navOptions[course.id]; courses.forEach((course) => {
course.admOptions = options.admOptions[course.id]; course.navOptions = options.navOptions[course.id];
}); course.admOptions = options.admOptions[course.id];
})); });
}));
}
return Promise.all(promises).then(() => { return Promise.all(promises).then(() => {
this.courses = courses; this.courses = courses;

View File

@ -223,17 +223,19 @@ export class CoreCoursesMyOverviewPage implements OnDestroy {
return course.id; return course.id;
}); });
// Load course options of the course. if (this.coursesProvider.canGetAdminAndNavOptions()) {
promises.push(this.coursesProvider.getCoursesAdminAndNavOptions(courseIds).then((options) => { // Load course options of the course.
courses.forEach((course) => { promises.push(this.coursesProvider.getCoursesAdminAndNavOptions(courseIds).then((options) => {
course.navOptions = options.navOptions[course.id]; courses.forEach((course) => {
course.admOptions = options.admOptions[course.id]; course.navOptions = options.navOptions[course.id];
}); course.admOptions = options.admOptions[course.id];
})); });
}));
}
this.courseIds = courseIds.join(','); this.courseIds = courseIds.join(',');
if (this.courseIds) { if (this.courseIds && this.coursesProvider.isGetCoursesByFieldAvailable()) {
// Load course image of all the courses. // Load course image of all the courses.
promises.push(this.coursesProvider.getCoursesByField('ids', this.courseIds).then((coursesInfo) => { promises.push(this.coursesProvider.getCoursesByField('ids', this.courseIds).then((coursesInfo) => {
coursesInfo = this.utils.arrayToObject(coursesInfo, 'id'); coursesInfo = this.utils.arrayToObject(coursesInfo, 'id');

View File

@ -33,6 +33,16 @@ export class CoreCoursesProvider {
this.logger = logger.getInstance('CoreCoursesProvider'); this.logger = logger.getInstance('CoreCoursesProvider');
} }
/**
* Whether current site supports getting course options.
*
* @return {boolean} Whether current site supports getting course options.
*/
canGetAdminAndNavOptions(): boolean {
return this.sitesProvider.wsAvailableInCurrentSite('core_course_get_user_navigation_options') &&
this.sitesProvider.wsAvailableInCurrentSite('core_course_get_user_administration_options');
}
/** /**
* Get categories. They can be filtered by id. * Get categories. They can be filtered by id.
* *

View File

@ -11,7 +11,7 @@
</ion-header> </ion-header>
<ion-content class="has-fab"> <ion-content class="has-fab">
<ion-list> <ion-list>
<ion-item (click)="login(site.id)" *ngFor="let site of sites; let idx = index"> <a ion-item (click)="login(site.id)" *ngFor="let site of sites; let idx = index" detail-none>
<ion-avatar item-start> <ion-avatar item-start>
<img [src]="site.avatar" core-external-content [siteId]="site.id" alt="{{ 'core.pictureof' | translate:{$a: site.fullname} }}" role="presentation" onError="this.src='assets/img/user-avatar.png'"> <img [src]="site.avatar" core-external-content [siteId]="site.id" alt="{{ 'core.pictureof' | translate:{$a: site.fullname} }}" role="presentation" onError="this.src='assets/img/user-avatar.png'">
</ion-avatar> </ion-avatar>
@ -22,7 +22,7 @@
<button *ngIf="showDelete" item-end ion-button icon-only clear color="danger" (click)="deleteSite($event, idx)" [attr.aria-label]="'core.delete' | translate"> <button *ngIf="showDelete" item-end ion-button icon-only clear color="danger" (click)="deleteSite($event, idx)" [attr.aria-label]="'core.delete' | translate">
<ion-icon name="trash"></ion-icon> <ion-icon name="trash"></ion-icon>
</button> </button>
</ion-item> </a>
</ion-list> </ion-list>
<ion-fab bottom right> <ion-fab bottom right>
<button ion-fab (click)="add()" [attr.aria-label]="'core.add' | translate"> <button ion-fab (click)="add()" [attr.aria-label]="'core.add' | translate">

View File

@ -15,12 +15,12 @@
<ion-item text-center *ngIf="(!handlers || !handlers.length) && !handlersLoaded"> <ion-item text-center *ngIf="(!handlers || !handlers.length) && !handlersLoaded">
<ion-spinner></ion-spinner> <ion-spinner></ion-spinner>
</ion-item> </ion-item>
<ion-item *ngFor="let handler of handlers" [ngClass]="['core-moremenu-handler', handler.class]" (click)="openHandler(handler)" title="{{ handler.title | translate }}" detail-push> <a ion-item *ngFor="let handler of handlers" [ngClass]="['core-moremenu-handler', handler.class]" (click)="openHandler(handler)" title="{{ handler.title | translate }}" detail-push>
<core-icon [name]="handler.icon" item-start></core-icon> <core-icon [name]="handler.icon" item-start></core-icon>
<p>{{ handler.title | translate}}</p> <p>{{ handler.title | translate}}</p>
<ion-badge item-end *ngIf="handler.showBadge" [hidden]="handler.loading || !handler.badge">{{handler.badge}}</ion-badge> <ion-badge item-end *ngIf="handler.showBadge" [hidden]="handler.loading || !handler.badge">{{handler.badge}}</ion-badge>
<ion-spinner item-end *ngIf="handler.showBadge && handler.loading"></ion-spinner> <ion-spinner item-end *ngIf="handler.showBadge && handler.loading"></ion-spinner>
</ion-item> </a>
<div *ngFor="let item of customItems" class="core-moremenu-customitem"> <div *ngFor="let item of customItems" class="core-moremenu-customitem">
<a ion-item *ngIf="item.type != 'embedded'" [href]="item.url" core-link [capture]="item.type == 'app'" [inApp]="item.type == 'inappbrowser'" title="{{item.label}}"> <a ion-item *ngIf="item.type != 'embedded'" [href]="item.url" core-link [capture]="item.type == 'app'" [inApp]="item.type == 'inappbrowser'" title="{{item.label}}">
<core-icon [name]="item.icon" item-start></core-icon> <core-icon [name]="item.icon" item-start></core-icon>

View File

@ -8,32 +8,32 @@
<core-split-view> <core-split-view>
<ion-content> <ion-content>
<ion-list> <ion-list>
<ion-item (click)="openHandler('CoreSettingsGeneralPage')" [title]="'core.settings.general' | translate" [class.core-split-item-selected]="'CoreSettingsGeneralPage' == selectedPage" detail-push> <a ion-item (click)="openHandler('CoreSettingsGeneralPage')" [title]="'core.settings.general' | translate" [class.core-split-item-selected]="'CoreSettingsGeneralPage' == selectedPage" detail-push>
<ion-icon name="construct" item-start></ion-icon> <ion-icon name="construct" item-start></ion-icon>
<p>{{ 'core.settings.general' | translate }}</p> <p>{{ 'core.settings.general' | translate }}</p>
</ion-item> </a>
<ion-item (click)="openHandler('CoreSettingsSpaceUsagePage')" [title]="'core.settings.spaceusage' | translate" [class.core-split-item-selected]="'CoreSettingsSpaceUsagePage' == selectedPage" detail-push> <a ion-item (click)="openHandler('CoreSettingsSpaceUsagePage')" [title]="'core.settings.spaceusage' | translate" [class.core-split-item-selected]="'CoreSettingsSpaceUsagePage' == selectedPage" detail-push>
<ion-icon name="stats" item-start></ion-icon> <ion-icon name="stats" item-start></ion-icon>
<p>{{ 'core.settings.spaceusage' | translate }}</p> <p>{{ 'core.settings.spaceusage' | translate }}</p>
</ion-item> </a>
<ion-item (click)="openHandler('CoreSettingsSynchronizationPage')" [title]="'core.settings.synchronization' | translate" [class.core-split-item-selected]="'CoreSettingsSynchronizationPage' == selectedPage" detail-push> <a ion-item (click)="openHandler('CoreSettingsSynchronizationPage')" [title]="'core.settings.synchronization' | translate" [class.core-split-item-selected]="'CoreSettingsSynchronizationPage' == selectedPage" detail-push>
<ion-icon name="sync" item-start></ion-icon> <ion-icon name="sync" item-start></ion-icon>
<p>{{ 'core.settings.synchronization' | translate }}</p> <p>{{ 'core.settings.synchronization' | translate }}</p>
</ion-item> </a>
<ion-item *ngIf="isIOS" (click)="openHandler('CoreSharedFilesListPage', {manage: true})" [title]="'core.sharedfiles.sharedfiles' | translate" [class.core-split-item-selected]="'CoreSharedFilesListPage' == selectedPage" detail-push> <a ion-item *ngIf="isIOS" (click)="openHandler('CoreSharedFilesListPage', {manage: true})" [title]="'core.sharedfiles.sharedfiles' | translate" [class.core-split-item-selected]="'CoreSharedFilesListPage' == selectedPage" detail-push>
<ion-icon name="folder" item-start></ion-icon> <ion-icon name="folder" item-start></ion-icon>
<p>{{ 'core.sharedfiles.sharedfiles' | translate }}</p> <p>{{ 'core.sharedfiles.sharedfiles' | translate }}</p>
</ion-item> </a>
<ion-item *ngFor="let handler of handlers" [ngClass]="['core-settings-handler', handler.class]" (click)="openHandler(handler.page, handler.params)" [title]="handler.title | translate" detail-push [class.core-split-item-selected]="handler.page == selectedPage"> <a ion-item *ngFor="let handler of handlers" [ngClass]="['core-settings-handler', handler.class]" (click)="openHandler(handler.page, handler.params)" [title]="handler.title | translate" detail-push [class.core-split-item-selected]="handler.page == selectedPage">
<core-icon [name]="handler.icon" item-start *ngIf="handler.icon"></core-icon> <core-icon [name]="handler.icon" item-start *ngIf="handler.icon"></core-icon>
<p>{{ handler.title | translate}}</p> <p>{{ handler.title | translate}}</p>
</ion-item> </a>
<ion-item (click)="openHandler('CoreSettingsAboutPage')" [title]="'core.settings.about' | translate" [class.core-split-item-selected]="'CoreSettingsAboutPage' == selectedPage" detail-push> <a ion-item (click)="openHandler('CoreSettingsAboutPage')" [title]="'core.settings.about' | translate" [class.core-split-item-selected]="'CoreSettingsAboutPage' == selectedPage" detail-push>
<ion-icon name="contacts" item-start></ion-icon> <ion-icon name="contacts" item-start></ion-icon>
<p>{{ 'core.settings.about' | translate }}</p> <p>{{ 'core.settings.about' | translate }}</p>
</ion-item> </a>
</ion-list> </ion-list>
</ion-content> </ion-content>
</core-split-view> </core-split-view>

View File

@ -213,51 +213,62 @@ export class CoreUserDelegate extends CoreDelegate {
* @return {Subject<CoreUserProfileHandlerToDisplay[]>} Resolved with the handlers. * @return {Subject<CoreUserProfileHandlerToDisplay[]>} Resolved with the handlers.
*/ */
getProfileHandlersFor(user: any, courseId: number): Subject<CoreUserProfileHandlerToDisplay[]> { getProfileHandlersFor(user: any, courseId: number): Subject<CoreUserProfileHandlerToDisplay[]> {
const promises = []; let promise,
navOptions,
admOptions;
if (this.coursesProvider.canGetAdminAndNavOptions()) {
// Get course options.
promise = this.coursesProvider.getUserCourses(true).then((courses) => {
const courseIds = courses.map((course) => {
return course.id;
});
return this.coursesProvider.getCoursesAdminAndNavOptions(courseIds).then((options) => {
// For backwards compatibility we don't modify the courseId.
const courseIdForOptions = courseId || this.sitesProvider.getCurrentSiteHomeId();
navOptions = options.navOptions[courseIdForOptions];
admOptions = options.admOptions[courseIdForOptions];
});
});
} else {
promise = Promise.resolve();
}
this.userHandlers = []; this.userHandlers = [];
// Retrieve course options forcing cache. promise.then(() => {
this.coursesProvider.getUserCourses(true).then((courses) => { const promises = [];
const courseIds = courses.map((course) => {
return course.id;
});
return this.coursesProvider.getCoursesAdminAndNavOptions(courseIds).then((options) => { for (const name in this.enabledHandlers) {
// For backwards compatibility we don't modify the courseId. // Checks if the handler is enabled for the user.
const courseIdForOptions = courseId || this.sitesProvider.getCurrentSiteHomeId(), const handler = <CoreUserProfileHandler> this.handlers[name],
navOptions = options.navOptions[courseIdForOptions], isEnabledForUser = handler.isEnabledForUser(user, courseId, navOptions, admOptions),
admOptions = options.admOptions[courseIdForOptions]; promise = Promise.resolve(isEnabledForUser).then((enabled) => {
if (enabled) {
for (const name in this.enabledHandlers) { this.userHandlers.push({
// Checks if the handler is enabled for the user. name: name,
const handler = <CoreUserProfileHandler> this.handlers[name], data: handler.getDisplayData(user, courseId),
isEnabledForUser = handler.isEnabledForUser(user, courseId, navOptions, admOptions), priority: handler.priority,
promise = Promise.resolve(isEnabledForUser).then((enabled) => { type: handler.type || CoreUserDelegate.TYPE_NEW_PAGE
if (enabled) { });
this.userHandlers.push({ } else {
name: name, return Promise.reject(null);
data: handler.getDisplayData(user, courseId), }
priority: handler.priority, }).catch(() => {
type: handler.type || CoreUserDelegate.TYPE_NEW_PAGE // Nothing to do here, it is not enabled for this user.
});
} else {
return Promise.reject(null);
}
}).catch(() => {
// Nothing to do here, it is not enabled for this user.
});
promises.push(promise);
}
return Promise.all(promises).then(() => {
// Sort them by priority.
this.userHandlers.sort((a, b) => {
return b.priority - a.priority;
}); });
this.loaded = true; promises.push(promise);
this.observableHandlers.next(this.userHandlers); }
return Promise.all(promises).then(() => {
// Sort them by priority.
this.userHandlers.sort((a, b) => {
return b.priority - a.priority;
}); });
this.loaded = true;
this.observableHandlers.next(this.userHandlers);
}); });
}).catch(() => { }).catch(() => {
// Never fails. // Never fails.