Merge pull request #3087 from crazyserver/MOBILE-3915

MOBILE-3915 course: Align icons on course index
main
Dani Palou 2022-02-02 11:55:39 +01:00 committed by GitHub
commit b2eb5e25d9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 147 additions and 123 deletions

View File

@ -26,7 +26,7 @@ import { CoreSite } from '@classes/site';
import { CoreUtils } from '@services/utils/utils';
import { CoreDomUtils } from '@services/utils/dom';
import { CoreTextUtils } from '@services/utils/text';
import { AddonCourseCompletion } from '@/addons/coursecompletion/services/coursecompletion';
import { AddonCourseCompletion } from '@addons/coursecompletion/services/coursecompletion';
import { AddonBlockMyOverviewFilterOptionsComponent } from '../filteroptions/filteroptions';
import { IonSearchbar } from '@ionic/angular';
import moment from 'moment';

View File

@ -27,7 +27,7 @@ import {
CoreEnrolledCourseDataWithOptions,
} from '@features/courses/services/courses-helper';
import { CoreCourseOptionsDelegate } from '@features/course/services/course-options-delegate';
import { AddonCourseCompletion } from '@/addons/coursecompletion/services/coursecompletion';
import { AddonCourseCompletion } from '@addons/coursecompletion/services/coursecompletion';
import { CoreBlockBaseComponent } from '@features/block/classes/base-block-component';
import { CoreUtils } from '@services/utils/utils';
import { CoreSite } from '@classes/site';

View File

@ -21,7 +21,7 @@ import {
CoreEnrolledCourseDataWithOptions,
} from '@features/courses/services/courses-helper';
import { CoreCourseOptionsDelegate } from '@features/course/services/course-options-delegate';
import { AddonCourseCompletion } from '@/addons/coursecompletion/services/coursecompletion';
import { AddonCourseCompletion } from '@addons/coursecompletion/services/coursecompletion';
import { CoreBlockBaseComponent } from '@features/block/classes/base-block-component';
import { CoreUtils } from '@services/utils/utils';
import { CoreSite } from '@classes/site';

View File

@ -25,26 +25,26 @@ function buildRoutes(injector: Injector): Routes {
data: {
mainMenuTabRoot: AddonCalendarMainMenuHandlerService.PAGE_NAME,
},
loadChildren: () => import('@/addons/calendar/pages/index/index.module').then(m => m.AddonCalendarIndexPageModule),
loadChildren: () => import('@addons/calendar/pages/index/index.module').then(m => m.AddonCalendarIndexPageModule),
},
{
path: 'settings',
loadChildren: () =>
import('@/addons/calendar/pages/settings/settings.module').then(m => m.AddonCalendarSettingsPageModule),
import('@addons/calendar/pages/settings/settings.module').then(m => m.AddonCalendarSettingsPageModule),
},
{
path: 'day',
loadChildren: () =>
import('@/addons/calendar/pages/day/day.module').then(m => m.AddonCalendarDayPageModule),
import('@addons/calendar/pages/day/day.module').then(m => m.AddonCalendarDayPageModule),
},
{
path: 'event/:id',
loadChildren: () => import('@/addons/calendar/pages/event/event.module').then(m => m.AddonCalendarEventPageModule),
loadChildren: () => import('@addons/calendar/pages/event/event.module').then(m => m.AddonCalendarEventPageModule),
},
{
path: 'edit/:eventId',
loadChildren: () =>
import('@/addons/calendar/pages/edit-event/edit-event.module').then(m => m.AddonCalendarEditEventPageModule),
import('@addons/calendar/pages/edit-event/edit-event.module').then(m => m.AddonCalendarEditEventPageModule),
},
...buildTabMainRoutes(injector, {
redirectTo: 'index',

View File

@ -15,7 +15,7 @@
import { Injectable } from '@angular/core';
import { CoreError } from '@classes/errors/error';
import { CoreFileUploader, CoreFileUploaderStoreFilesResult } from '@features/fileuploader/services/fileuploader';
import { FileEntry } from '@ionic-native/file';
import { FileEntry } from '@ionic-native/file/ngx';
import { CoreFile } from '@services/file';
import { CoreFileEntry } from '@services/file-helper';
import { CoreSites } from '@services/sites';

View File

@ -37,7 +37,7 @@ export const ADDON_NOTIFICATIONS_SERVICES: Type<unknown>[] = [
const routes: Routes = [
{
path: AddonNotificationsMainMenuHandlerService.PAGE_NAME,
loadChildren: () => import('@/addons/notifications/notifications-lazy.module').then(m => m.AddonNotificationsLazyModule),
loadChildren: () => import('@addons/notifications/notifications-lazy.module').then(m => m.AddonNotificationsLazyModule),
},
];
const preferencesRoutes: Routes = [

View File

@ -28,8 +28,8 @@ import {
AddonPrivateFilesFile,
AddonPrivateFilesGetUserInfoWSResult,
AddonPrivateFilesGetFilesWSParams,
} from '@/addons/privatefiles/services/privatefiles';
import { AddonPrivateFilesHelper } from '@/addons/privatefiles/services/privatefiles-helper';
} from '@addons/privatefiles/services/privatefiles';
import { AddonPrivateFilesHelper } from '@addons/privatefiles/services/privatefiles-helper';
import { CoreUtils } from '@services/utils/utils';
import { CoreNavigator } from '@services/navigator';

View File

@ -30,7 +30,7 @@ export const ADDON_PRIVATEFILES_SERVICES: Type<unknown>[] = [
const routes: Routes = [
{
path: AddonPrivateFilesUserHandlerService.PAGE_NAME,
loadChildren: () => import('@/addons/privatefiles/privatefiles-lazy.module').then(m => m.AddonPrivateFilesLazyModule),
loadChildren: () => import('@addons/privatefiles/privatefiles-lazy.module').then(m => m.AddonPrivateFilesLazyModule),
},
];

View File

@ -14,7 +14,7 @@
import { Injectable } from '@angular/core';
import { AddonPrivateFiles } from '@/addons/privatefiles/services/privatefiles';
import { AddonPrivateFiles } from '@addons/privatefiles/services/privatefiles';
import { makeSingleton } from '@singletons';
import {
CoreUserDelegateContext,

View File

@ -24,7 +24,7 @@ import { TranslateHttpLoader } from '@ngx-translate/http-loader';
import { IonicModule, IonicRouteStrategy } from '@ionic/angular';
import { CoreModule } from '@/core/core.module';
import { AddonsModule } from '@/addons/addons.module';
import { AddonsModule } from '@addons/addons.module';
import { AppComponent } from './app.component';
import { AppRoutingModule } from './app-routing.module';

View File

@ -12,9 +12,9 @@
</ion-header>
<ion-content>
<ion-list id="core-course-section-selector" role="listbox" aria-labelledby="core-course-section-selector-label">
<ng-container *ngFor="let section of sections">
<ion-item *ngIf="allSectionId == section.id" class="ion-text-wrap divider" (click)="selectSection($event, section)" button
[class.item-current]="selectedId === section.id" detail="false">
<ng-container *ngFor="let section of sectionsToRender">
<ion-item *ngIf="allSectionId == section.id" class="ion-text-wrap divider core-course-index-all"
(click)="selectSectionOrModule($event, section.id)" button [class.item-current]="selectedId === section.id" detail="false">
<ion-label>
<p class="item-heading">
<core-format-text [text]="section.name" contextLevel="course" [contextInstanceId]="course?.id">
@ -22,16 +22,17 @@
</p>
</ion-label>
</ion-item>
<ng-container *ngIf="allSectionId != section.id && !section.hiddenbynumsections &&
section.id != stealthModulesSectionId && section.uservisible !== false">
<ion-item class="ion-text-wrap divider section" (click)="selectSection($event, section)"
[button]="section.visible !== 0 && section.uservisible !== false" [class.item-current]="selectedId === section.id"
[class.item-dimmed]="section.visible === 0" detail="false" sticky="true">
<ion-icon [name]="section.expanded ? 'fas-chevron-down' : 'fas-chevron-right'" flip-rtl slot="start"
class="expandable-status-icon" (click)="toggleExpand($event, section)"
<ng-container *ngIf="allSectionId != section.id">
<ion-item class="ion-text-wrap divider section" (click)="selectSectionOrModule($event, section.id)" button
[class.item-current]="selectedId === section.id" [class.item-dimmed]="section.visible === 0" detail="false"
sticky="true">
<ion-icon *ngIf="section.hasVisibleModules" [name]="section.expanded ? 'fas-chevron-down' : 'fas-chevron-right'"
flip-rtl slot="start" class="expandable-status-icon" (click)="toggleExpand($event, section)"
[attr.aria-label]="(section.expanded ? 'core.collapse' : 'core.expand') | translate"
[attr.aria-expanded]="section.expanded" [attr.aria-controls]="'core-course-index-section-' + section.id">
</ion-icon>
<ion-icon *ngIf="!section.hasVisibleModules" name="" slot="start" aria-hidden="true" class="expandable-status-icon">
</ion-icon>
<ion-label>
<p class="item-heading">
<core-format-text [text]="section.name" contextLevel="course" [contextInstanceId]="course?.id">
@ -42,37 +43,34 @@
<ion-icon name="fas-lock" *ngIf="section.availabilityinfo" slot="end" class="restricted"
[attr.aria-label]="'core.restricted' | translate"></ion-icon>
</ion-item>
<div [hidden]="!section.expanded" [id]="'core-course-index-section-' + section.id">
<ng-container *ngIf="section.expanded">
<ng-container *ngFor="let module of section.modules">
<ion-item [class.item-dimmed]="module.visible === 0"
*ngIf="module.visibleoncoursepage !== 0 && !module.noviewlink"
(click)="selectModule($event, section, module)" button>
<ion-icon class="completioninfo completion_none" name="" *ngIf="module.completionStatus === undefined"
slot="start" aria-hidden="true"></ion-icon>
<ion-icon class="completioninfo completion_incomplete" name="far-circle"
*ngIf="module.completionStatus === 0" slot="start" [attr.aria-label]="'core.course.todo' | translate">
</ion-icon>
<ion-icon class="completioninfo completion_complete" name="fas-circle"
*ngIf="module.completionStatus === 1 || module.completionStatus === 2" color="success" slot="start"
[attr.aria-label]="'core.course.done' | translate">
</ion-icon>
<ion-icon class="completioninfo completion_fail" name="fas-circle" *ngIf="module.completionStatus === 3"
color="danger" slot="start" [attr.aria-label]="'core.course.failed' | translate">
</ion-icon>
<ion-label>
<p class="item-heading">
<core-format-text [text]="module.name" contextLevel="module" [contextInstanceId]="module.id"
[courseId]="module.courseid">
</core-format-text>
</p>
</ion-label>
<ion-icon name="fas-lock" *ngIf="module.uservisible === false" slot="end" class="restricted"
[attr.aria-label]="'core.restricted' | translate"></ion-icon>
</ion-item>
</ng-container>
<ng-container *ngIf="section.expanded">
<ng-container *ngFor="let module of section.modules">
<ion-item [class.item-dimmed]="!module.visible" (click)="selectSectionOrModule($event, section.id, module.id)"
button>
<ion-icon class="completioninfo completion_none" name="" *ngIf="module.completionStatus === undefined"
slot="start" aria-hidden="true"></ion-icon>
<ion-icon class="completioninfo completion_incomplete" name="far-circle" *ngIf="module.completionStatus === 0"
slot="start" [attr.aria-label]="'core.course.todo' | translate">
</ion-icon>
<ion-icon class="completioninfo completion_complete" name="fas-circle"
*ngIf="module.completionStatus === 1 || module.completionStatus === 2" color="success" slot="start"
[attr.aria-label]="'core.course.done' | translate">
</ion-icon>
<ion-icon class="completioninfo completion_fail" name="fas-circle" *ngIf="module.completionStatus === 3"
color="danger" slot="start" [attr.aria-label]="'core.course.failed' | translate">
</ion-icon>
<ion-label>
<p class="item-heading">
<core-format-text [text]="module.name" contextLevel="module" [contextInstanceId]="module.id"
[courseId]="module.course">
</core-format-text>
</p>
</ion-label>
<ion-icon name="fas-lock" *ngIf="!module.uservisible" slot="end" class="restricted"
[attr.aria-label]="'core.restricted' | translate"></ion-icon>
</ion-item>
</ng-container>
</div>
</ng-container>
</ng-container>
</ng-container>
</ion-list>

View File

@ -16,18 +16,21 @@ ion-icon.completioninfo {
width: 18px;
}
ion-item.section::part(native) {
ion-item::part(native) {
--padding-start: 0;
}
ion-icon.expandable-status-icon {
ion-icon {
margin: 0;
@include padding(12px, 32px, 12px, 16px);
}
ion-item.core-course-index-all::part(native) {
--padding-start: 16px;
}
ion-item.item-current ion-icon.expandable-status-icon {
@include padding(null, null, null, 11px);
}
ion-icon.restricted {

View File

@ -13,19 +13,18 @@
// limitations under the License.
import { Component, ElementRef, Input, OnInit, ViewChild } from '@angular/core';
import { CoreCourseModuleData, CoreCourseSection } from '@features/course/services/course-helper';
import {
CoreCourseModuleCompletionStatus,
CoreCourseModuleCompletionTracking,
CoreCourseProvider,
} from '@features/course/services/course';
import { CoreCourseAnyCourseData } from '@features/courses/services/courses';
import { CoreUtils } from '@services/utils/utils';
import { ModalController } from '@singletons';
import { CoreCourseSection } from '@features/course/services/course-helper';
import { CoreCourseFormatDelegate } from '@features/course/services/format-delegate';
import { CoreCourseAnyCourseData } from '@features/courses/services/courses';
import { IonContent } from '@ionic/angular';
import { CoreDomUtils } from '@services/utils/dom';
import { CoreUtils } from '@services/utils/utils';
import { ModalController } from '@singletons';
/**
* Component to display course index modal.
@ -39,13 +38,13 @@ export class CoreCourseCourseIndexComponent implements OnInit {
@ViewChild(IonContent) content?: IonContent;
@Input() sections?: CourseIndexSection[];
@Input() sections: CoreCourseSection[] = [];
@Input() selectedId?: number;
@Input() course?: CoreCourseAnyCourseData;
stealthModulesSectionId = CoreCourseProvider.STEALTH_MODULES_SECTION_ID;
allSectionId = CoreCourseProvider.ALL_SECTIONS_ID;
highlighted?: string;
sectionsToRender: CourseIndexSection[] = [];
constructor(
protected elementRef: ElementRef,
@ -70,29 +69,47 @@ export class CoreCourseCourseIndexComponent implements OnInit {
return;
}
// Collapse all sections first.
this.sections.forEach((section) => section.expanded = false);
const currentSection = await CoreCourseFormatDelegate.getCurrentSection(this.course, this.sections);
currentSection.highlighted = true;
if (this.selectedId === undefined) {
currentSection.expanded = true;
// Highlight current section if none is selected.
this.selectedId = currentSection.id;
} else {
const selectedSection = this.sections.find((section) => section.id == this.selectedId);
if (selectedSection) {
selectedSection.expanded = true;
}
}
this.sections.forEach((section) => {
section.modules.forEach((module) => {
module.completionStatus = module.completiondata === undefined ||
module.completiondata.tracking == CoreCourseModuleCompletionTracking.COMPLETION_TRACKING_NONE
? undefined
: module.completiondata.state;
// Clone sections to add information.
this.sectionsToRender = this.sections
.filter((section) => !section.hiddenbynumsections &&
section.id != CoreCourseProvider.STEALTH_MODULES_SECTION_ID &&
section.uservisible !== false)
.map((section) => {
const modules = section.modules
.filter((module) => module.visibleoncoursepage !== 0 && !module.noviewlink)
.map((module) => {
const completionStatus = module.completiondata === undefined ||
module.completiondata.tracking == CoreCourseModuleCompletionTracking.COMPLETION_TRACKING_NONE
? undefined
: module.completiondata.state;
return {
id: module.id,
name: module.name,
course: module.course,
visible: !!module.visible,
uservisible: !!module.uservisible,
completionStatus,
};
});
return {
id: section.id,
name: section.name,
availabilityinfo: !!section.availabilityinfo,
expanded: section.id === this.selectedId,
highlighted: currentSection?.id === section.id,
hasVisibleModules: modules.length > 0,
modules: modules,
};
});
});
this.highlighted = CoreCourseFormatDelegate.getSectionHightlightedName(this.course);
@ -102,7 +119,7 @@ export class CoreCourseCourseIndexComponent implements OnInit {
this.content,
'.item.item-current',
);
}, 200);
}, 300);
}
/**
@ -128,39 +145,33 @@ export class CoreCourseCourseIndexComponent implements OnInit {
* Select a section.
*
* @param event Event.
* @param section Selected section object.
* @param sectionId Selected section id.
* @param moduleId Selected module id, if any.
*/
selectSection(event: Event, section: CoreCourseSection): void {
if (section.uservisible !== false) {
ModalController.dismiss({ event, section });
}
}
/**
* Select a section and open a module
*
* @param event Event.
* @param section Selected section object.
* @param module Selected module object.
*/
selectModule(event: Event,section: CoreCourseSection, module: CoreCourseModuleData): void {
if (module.uservisible !== false) {
ModalController.dismiss({ event, section, module });
}
selectSectionOrModule(event: Event, sectionId: number, moduleId?: number): void {
ModalController.dismiss({ event, sectionId, moduleId });
}
}
type CourseIndexSection = Omit<CoreCourseSection, 'modules'> & {
highlighted?: boolean;
expanded?: boolean;
modules: (CoreCourseModuleData & {
type CourseIndexSection = {
id: number;
name: string;
highlighted: boolean;
expanded: boolean;
hasVisibleModules: boolean;
availabilityinfo: boolean;
modules: {
id: number;
course: number;
visible: boolean;
uservisible: boolean;
completionStatus?: CoreCourseModuleCompletionStatus;
})[];
}[];
};
export type CoreCourseIndexSectionWithModule = {
event: Event;
section: CourseIndexSection;
module?: CoreCourseModuleData;
sectionId: number;
moduleId?: number;
};

View File

@ -69,7 +69,6 @@
class="section-wrapper" [id]="section.id">
<ion-item-divider class="course-section ion-text-wrap" color="light"
[class.item-dimmed]="section.visible === 0 || section.uservisible === false">
<ion-icon name="fas-folder" aria-label="hidden" slot="start"></ion-icon>
<ion-label>
<h2 *ngIf="section.name">
<core-format-text [text]="section.name" contextLevel="course" [contextInstanceId]="course.id">

View File

@ -312,20 +312,33 @@ export class CoreCourseFormatComponent implements OnInit, OnChanges, OnDestroy {
},
});
if (data) {
this.sectionChanged(data.section);
if (data.module) {
if (!data.module.handlerData) {
data.module.handlerData =
await CoreCourseModuleDelegate.getModuleDataFor(data.module.modname, data.module, this.course.id);
}
if (data.module.uservisible !== false && data.module.handlerData?.action) {
data.module.handlerData.action(data.event, data.module, data.module.course);
}
this.moduleId = data.module.id;
}
if (!data) {
return;
}
const section = this.sections.find((section) => section.id == data.sectionId);
if (!section) {
return;
}
this.sectionChanged(section);
if (!data.moduleId) {
return;
}
const module = section.modules.find((module) => module.id == data.moduleId);
if (!module) {
return;
}
if (!module.handlerData) {
module.handlerData =
await CoreCourseModuleDelegate.getModuleDataFor(module.modname, module, this.course.id);
}
if (module.uservisible !== false && module.handlerData?.action) {
module.handlerData.action(data.event, module, module.course);
}
this.moduleId = data.moduleId;
}
/**

View File

@ -24,7 +24,7 @@ import {
} from './courses';
import { makeSingleton, Translate } from '@singletons';
import { CoreWSExternalFile } from '@services/ws';
import { AddonCourseCompletion } from '@/addons/coursecompletion/services/coursecompletion';
import { AddonCourseCompletion } from '@addons/coursecompletion/services/coursecompletion';
/**
* Helper to gather some common courses functions.