MOBILE-3659 course: Implement core course formats

main
Dani Palou 2021-01-14 10:26:41 +01:00
parent 2906210242
commit a91c042cc2
19 changed files with 801 additions and 2 deletions

View File

@ -0,0 +1,42 @@
// (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 { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { IonicModule } from '@ionic/angular';
import { TranslateModule } from '@ngx-translate/core';
import { CoreSharedModule } from '@/core/shared.module';
import { CoreBlockComponentsModule } from '@features/block/components/components.module';
import { CoreCourseModuleDescriptionComponent } from './module-description/module-description';
import { CoreCourseUnsupportedModuleComponent } from './unsupported-module/unsupported-module';
@NgModule({
declarations: [
CoreCourseModuleDescriptionComponent,
CoreCourseUnsupportedModuleComponent,
],
imports: [
CoreBlockComponentsModule,
CommonModule,
IonicModule,
TranslateModule.forChild(),
CoreSharedModule,
],
exports: [
CoreCourseModuleDescriptionComponent,
CoreCourseUnsupportedModuleComponent,
],
})
export class CoreCourseComponentsModule {}

View File

@ -0,0 +1,11 @@
<ion-card *ngIf="description">
<ion-item class="ion-text-wrap">
<core-format-text [text]="description" [component]="component" [componentId]="componentId"
[maxHeight]="showFull && showFull !== 'false' ? 0 : 120" fullOnClick="true" [contextLevel]="contextLevel"
[contextInstanceId]="contextInstanceId" [courseId]="courseId">
</core-format-text>
</ion-item>
<ion-item class="ion-text-wrap" *ngIf="note">
<ion-note slot="end">{{ note }}</ion-note>
</ion-item>
</ion-card>

View File

@ -0,0 +1,16 @@
// ion-app.app-root {
// .safe-area-page,
// .safe-padding-horizontal {
// core-course-module-description {
// padding-left: 0 !important;
// padding-right: 0 !important;
// .item-ios.item-block {
// @include safe-area-padding-horizontal($item-ios-padding-end / 2, null);
// .item-inner {
// @include safe-area-padding-horizontal(null, $item-ios-padding-end / 2);
// }
// }
// }
// }
// }

View File

@ -0,0 +1,48 @@
// (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 { Component, Input } from '@angular/core';
/**
* Component to display the description of a module.
*
* This directive is meant to display a module description in a similar way throughout all the app.
*
* You can add a note at the right side of the description by using the 'note' attribute.
*
* You can also pass a component and componentId to be used in format-text.
*
* Module descriptions are shortened by default, allowing the user to see the full description by clicking in it.
* If you want the whole description to be shown you can use the 'showFull' attribute.
*
* Example usage:
*
* <core-course-module-description [description]="myDescription"></core-course-module-description
*/
@Component({
selector: 'core-course-module-description',
templateUrl: 'core-course-module-description.html',
})
export class CoreCourseModuleDescriptionComponent {
@Input() description?: string; // The description to display.
@Input() note?: string; // A note to display along with the description.
@Input() component?: string; // Component for format text directive.
@Input() componentId?: string | number; // Component ID to use in conjunction with the component.
@Input() showFull?: string | boolean; // Whether to always display the full description.
@Input() contextLevel?: string; // The context level.
@Input() contextInstanceId?: number; // The instance ID related to the context.
@Input() courseId?: number; // Course ID the text belongs to. It can be used to improve performance with filters.
}

View File

@ -0,0 +1,25 @@
<div class="ion-padding">
<core-course-module-description [description]="module && module.description" contextLevel="module"
[contextInstanceId]="module.id" [courseId]="courseId">
</core-course-module-description>
<h2 *ngIf="!isDisabledInSite && isSupportedByTheApp">{{ 'core.whoops' | translate }}</h2>
<h2 *ngIf="isDisabledInSite || !isSupportedByTheApp">{{ 'core.uhoh' | translate }}</h2>
<p class="core-big" *ngIf="isDisabledInSite">{{ 'core.course.activitydisabled' | translate }}</p>
<p class="core-big" *ngIf="!isDisabledInSite && isSupportedByTheApp">
{{ 'core.course.activitynotyetviewablesiteupgradeneeded' | translate }}
</p>
<p class="core-big" *ngIf="!isDisabledInSite && !isSupportedByTheApp">
{{ 'core.course.activitynotyetviewableremoteaddon' | translate }}
</p>
<p *ngIf="isDisabledInSite || !isSupportedByTheApp"><strong>{{ 'core.course.askadmintosupport' | translate }}</strong></p>
<div *ngIf="module && module.url">
<p><strong>{{ 'core.course.useactivityonbrowser' | translate }}</strong></p>
<ion-button expand="block" [href]="module.url" core-link>
{{ 'core.openinbrowser' | translate }}
<ion-icon name="open" slot="end"></ion-icon>
</ion-button>
</div>
</div>

View File

@ -0,0 +1,49 @@
// (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 { Component, Input, OnInit } from '@angular/core';
import { CoreCourse, CoreCourseModuleData } from '@features/course/services/course';
import { CoreCourseModuleDelegate } from '@features/course/services/module-delegate';
/**
* Component that displays info about an unsupported module.
*/
@Component({
selector: 'core-course-unsupported-module',
templateUrl: 'core-course-unsupported-module.html',
})
export class CoreCourseUnsupportedModuleComponent implements OnInit {
@Input() courseId?: number; // The course to module belongs to.
@Input() module?: CoreCourseModuleData; // The module to render.
isDisabledInSite?: boolean;
isSupportedByTheApp?: boolean;
moduleName?: string;
/**
* Component being initialized.
*/
ngOnInit(): void {
if (!this.module) {
return;
}
this.isDisabledInSite = CoreCourseModuleDelegate.instance.isModuleDisabledInSite(this.module.modname);
this.isSupportedByTheApp = CoreCourseModuleDelegate.instance.hasHandler(this.module.modname);
this.moduleName = CoreCourse.instance.translateModuleName(this.module.modname);
}
}

View File

@ -15,11 +15,16 @@
import { NgModule } from '@angular/core';
import { CORE_SITE_SCHEMAS } from '@services/sites';
import { CoreCourseComponentsModule } from './components/components.module';
import { CoreCourseFormatModule } from './format/formats.module';
import { SITE_SCHEMA, OFFLINE_SITE_SCHEMA } from './services/database/course';
import { SITE_SCHEMA as LOG_SITE_SCHEMA } from './services/database/log';
@NgModule({
imports: [
CoreCourseFormatModule,
CoreCourseComponentsModule,
],
providers: [
{
provide: CORE_SITE_SCHEMAS,

View File

@ -0,0 +1,33 @@
// (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 { NgModule } from '@angular/core';
import { CoreCourseFormatSingleActivityModule } from './singleactivity/singleactivity.module';
import { CoreCourseFormatSocialModule } from './social/social.module';
import { CoreCourseFormatTopicsModule } from './topics/topics.module';
import { CoreCourseFormatWeeksModule } from './weeks/weeks.module';
@NgModule({
declarations: [],
imports: [
CoreCourseFormatSingleActivityModule,
CoreCourseFormatSocialModule,
CoreCourseFormatTopicsModule,
CoreCourseFormatWeeksModule,
],
providers: [],
exports: [],
})
export class CoreCourseFormatModule { }

View File

@ -0,0 +1 @@
<core-dynamic-component [component]="componentClass" [data]="data"></core-dynamic-component>

View File

@ -0,0 +1,104 @@
// (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 { Component, Input, OnChanges, SimpleChange, ViewChild, Output, EventEmitter, Type } from '@angular/core';
import { CoreCourseModuleDelegate } from '@features/course/services/module-delegate';
import { CoreCourseUnsupportedModuleComponent } from '@features/course/components/unsupported-module/unsupported-module';
import { CoreDynamicComponent } from '@components/dynamic-component/dynamic-component';
import { CoreCourseAnyCourseData } from '@features/courses/services/courses';
import { CoreCourseSection } from '@features/course/services/course';
import { IonRefresher } from '@ionic/angular';
/**
* Component to display single activity format. It will determine the right component to use and instantiate it.
*
* The instantiated component will receive the course and the module as inputs.
*/
@Component({
selector: 'core-course-format-single-activity',
templateUrl: 'core-course-format-single-activity.html',
})
export class CoreCourseFormatSingleActivityComponent implements OnChanges {
@Input() course?: CoreCourseAnyCourseData; // The course to render.
@Input() sections?: CoreCourseSection[]; // List of course sections.
@Input() downloadEnabled?: boolean; // Whether the download of sections and modules is enabled.
@Input() initialSectionId?: number; // The section to load first (by ID).
@Input() initialSectionNumber?: number; // The section to load first (by number).
@Input() moduleId?: number; // The module ID to scroll to. Must be inside the initial selected section.
@Output() completionChanged?: EventEmitter<void>; // Will emit an event when any module completion changes.
@ViewChild(CoreDynamicComponent) dynamicComponent?: CoreDynamicComponent;
componentClass?: Type<unknown>; // The class of the component to render.
data: Record<string | number, unknown> = {}; // Data to pass to the component.
/**
* Detect changes on input properties.
*/
async ngOnChanges(changes: { [name: string]: SimpleChange }): Promise<void> {
if (!changes.course || !changes.sections) {
return;
}
if (!this.course || !this.sections || !this.sections.length) {
return;
}
// In single activity the module should only have 1 section and 1 module. Get the module.
const module = this.sections?.[0].modules?.[0];
this.data.courseId = this.course.id;
this.data.module = module;
if (module && !this.componentClass) {
// We haven't obtained the class yet. Get it now.
const component = await CoreCourseModuleDelegate.instance.getMainComponent(this.course, module);
this.componentClass = component || CoreCourseUnsupportedModuleComponent;
}
}
/**
* Refresh the data.
*
* @param refresher Refresher.
* @param done Function to call when done.
* @param afterCompletionChange Whether the refresh is due to a completion change.
* @return Promise resolved when done.
*/
async doRefresh(refresher?: CustomEvent<IonRefresher>, done?: () => void, afterCompletionChange?: boolean): Promise<void> {
if (afterCompletionChange) {
// Don't refresh the view after a completion change since completion isn't displayed.
return;
}
await this.dynamicComponent?.callComponentFunction('doRefresh', [refresher, done]);
}
/**
* User entered the page that contains the component.
*/
ionViewDidEnter(): void {
this.dynamicComponent?.callComponentFunction('ionViewDidEnter');
}
/**
* User left the page that contains the component.
*/
ionViewDidLeave(): void {
this.dynamicComponent?.callComponentFunction('ionViewDidLeave');
}
}

View File

@ -0,0 +1,153 @@
// (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 { Injectable, Type } from '@angular/core';
import { CoreCourseSection } from '@features/course/services/course';
import { CoreCourseFormatHandler } from '@features/course/services/format-delegate';
import { CoreCourseModuleDelegate } from '@features/course/services/module-delegate';
import { CoreCourseAnyCourseData } from '@features/courses/services/courses';
import { CoreCourseFormatSingleActivityComponent } from '../../components/singleactivity';
import { makeSingleton } from '@singletons';
/**
* Handler to support singleactivity course format.
*/
@Injectable({ providedIn: 'root' })
export class CoreCourseFormatSingleActivityHandlerService implements CoreCourseFormatHandler {
name = 'CoreCourseFormatSingleActivity';
format = 'singleactivity';
/**
* Whether or not the handler is enabled on a site level.
*
* @return True or promise resolved with true if enabled.
*/
async isEnabled(): Promise<boolean> {
return true;
}
/**
* Whether it allows seeing all sections at the same time. Defaults to true.
*
* @param course The course to check.
* @return Whether it can view all sections.
*/
// eslint-disable-next-line @typescript-eslint/no-unused-vars
canViewAllSections(course: CoreCourseAnyCourseData): boolean {
return false;
}
/**
* Whether the option blocks should be displayed. Defaults to true.
*
* @param course The course to check.
* @return Whether it can display blocks.
*/
// eslint-disable-next-line @typescript-eslint/no-unused-vars
displayBlocks(course: CoreCourseAnyCourseData): boolean {
return false;
}
/**
* Get the title to use in course page. If not defined, course displayname or fullname.
* This function will be called without sections first, and then call it again when the sections are retrieved.
*
* @param course The course.
* @param sections List of sections.
* @return Title.
*/
getCourseTitle(course: CoreCourseAnyCourseData, sections?: CoreCourseSection[]): string {
if (sections?.[0]?.modules?.[0]) {
return sections[0].modules[0].name;
}
if (course.displayname) {
return course.displayname;
} else if (course.fullname) {
return course.fullname;
}
return '';
}
/**
* Whether the option to enable section/module download should be displayed. Defaults to true.
*
* @param course The course to check.
* @return Whether the option to enable section/module download should be displayed
*/
// eslint-disable-next-line @typescript-eslint/no-unused-vars
displayEnableDownload(course: CoreCourseAnyCourseData): boolean {
return false;
}
/**
* Whether the default section selector should be displayed. Defaults to true.
*
* @param course The course to check.
* @return Whether the default section selector should be displayed.
*/
// eslint-disable-next-line @typescript-eslint/no-unused-vars
displaySectionSelector(course: CoreCourseAnyCourseData): boolean {
return false;
}
/**
* Whether the course refresher should be displayed. If it returns false, a refresher must be included in the course format,
* and the doRefresh method of CoreCourseSectionPage must be called on refresh. Defaults to true.
*
* @param course The course to check.
* @param sections List of course sections.
* @return Whether the refresher should be displayed.
*/
displayRefresher(course: CoreCourseAnyCourseData, sections: CoreCourseSection[]): boolean {
if (sections?.[0]?.modules?.[0]) {
return CoreCourseModuleDelegate.instance.displayRefresherInSingleActivity(sections[0].modules[0].modname);
} else {
return true;
}
}
/**
* Return the Component to use to display the course format instead of using the default one.
* Use it if you want to display a format completely different from the default one.
* If you want to customize the default format there are several methods to customize parts of it.
* It's recommended to return the class of the component, but you can also return an instance of the component.
*
* @param injector Injector.
* @param course The course to render.
* @return The component (or promise resolved with component) to use, undefined if not found.
*/
// eslint-disable-next-line @typescript-eslint/no-unused-vars
async getCourseFormatComponent(course: CoreCourseAnyCourseData): Promise<Type<unknown>> {
return CoreCourseFormatSingleActivityComponent;
}
/**
* Whether the view should be refreshed when completion changes. If your course format doesn't display
* activity completion then you should return false.
*
* @param course The course.
* @return Whether course view should be refreshed when an activity completion changes.
*/
// eslint-disable-next-line @typescript-eslint/no-unused-vars
async shouldRefreshWhenCompletionChanges(course: CoreCourseAnyCourseData): Promise<boolean> {
return false;
}
}
export class CoreCourseFormatSingleActivityHandler extends makeSingleton(CoreCourseFormatSingleActivityHandlerService) {}

View File

@ -0,0 +1,43 @@
// (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 { APP_INITIALIZER, NgModule } from '@angular/core';
import { CoreSharedModule } from '@/core/shared.module';
import { CoreCourseFormatDelegate } from '@features/course/services/format-delegate';
import { CoreCourseFormatSingleActivityComponent } from './components/singleactivity';
import { CoreCourseFormatSingleActivityHandler } from './services/handlers/singleactivity-format';
@NgModule({
declarations: [
CoreCourseFormatSingleActivityComponent,
],
imports: [
CoreSharedModule,
],
providers: [
{
provide: APP_INITIALIZER,
multi: true,
deps: [],
useFactory: () => () => {
CoreCourseFormatDelegate.instance.registerHandler(CoreCourseFormatSingleActivityHandler.instance);
},
},
],
exports: [
CoreCourseFormatSingleActivityComponent,
],
})
export class CoreCourseFormatSingleActivityModule {}

View File

@ -0,0 +1,32 @@
// (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 { Injectable } from '@angular/core';
import { makeSingleton } from '@singletons';
import { CoreCourseFormatSingleActivityHandlerService } from '../../../singleactivity/services/handlers/singleactivity-format';
/**
* Handler to support social course format.
* This format is like singleactivity in the mobile app, so we'll use the same implementation for both.
*/
@Injectable({ providedIn: 'root' })
export class CoreCourseFormatSocialHandlerService extends CoreCourseFormatSingleActivityHandlerService {
name = 'CoreCourseFormatSocial';
format = 'social';
}
export class CoreCourseFormatSocialHandler extends makeSingleton(CoreCourseFormatSocialHandlerService) {}

View File

@ -0,0 +1,34 @@
// (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 { APP_INITIALIZER, NgModule } from '@angular/core';
import { CoreCourseFormatDelegate } from '@features/course/services/format-delegate';
import { CoreCourseFormatSocialHandler } from './services/handlers/social-format';
@NgModule({
declarations: [],
imports: [],
providers: [
{
provide: APP_INITIALIZER,
multi: true,
deps: [],
useFactory: () => () => {
CoreCourseFormatDelegate.instance.registerHandler(CoreCourseFormatSocialHandler.instance);
},
},
],
})
export class CoreCourseFormatSocialModule {}

View File

@ -0,0 +1,40 @@
// (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 { Injectable } from '@angular/core';
import { CoreCourseFormatHandler } from '@features/course/services/format-delegate';
import { makeSingleton } from '@singletons';
/**
* Handler to support topics course format.
*/
@Injectable({ providedIn: 'root' })
export class CoreCourseFormatTopicsHandlerService implements CoreCourseFormatHandler {
name = 'CoreCourseFormatTopics';
format = 'topics';
/**
* Whether or not the handler is enabled on a site level.
*
* @return True or promise resolved with true if enabled.
*/
async isEnabled(): Promise<boolean> {
return true;
}
}
export class CoreCourseFormatTopicsHandler extends makeSingleton(CoreCourseFormatTopicsHandlerService) {}

View File

@ -0,0 +1,34 @@
// (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 { APP_INITIALIZER, NgModule } from '@angular/core';
import { CoreCourseFormatDelegate } from '@features/course/services/format-delegate';
import { CoreCourseFormatTopicsHandler } from './services/handlers/topics-format';
@NgModule({
declarations: [],
imports: [],
providers: [
{
provide: APP_INITIALIZER,
multi: true,
deps: [],
useFactory: () => () => {
CoreCourseFormatDelegate.instance.registerHandler(CoreCourseFormatTopicsHandler.instance);
},
},
],
})
export class CoreCourseFormatTopicsModule {}

View File

@ -0,0 +1,95 @@
// (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 { Injectable } from '@angular/core';
import { CoreTimeUtils } from '@services/utils/time';
import { CoreCourseFormatHandler } from '@features/course/services/format-delegate';
import { makeSingleton } from '@singletons';
import { CoreCourseAnyCourseData } from '@features/courses/services/courses';
import { CoreCourseSection } from '@features/course/services/course';
import { CoreConstants } from '@/core/constants';
/**
* Handler to support weeks course format.
*/
@Injectable({ providedIn: 'root' })
export class CoreCourseFormatWeeksHandlerService implements CoreCourseFormatHandler {
name = 'CoreCourseFormatWeeks';
format = 'weeks';
/**
* Whether or not the handler is enabled on a site level.
*
* @return True or promise resolved with true if enabled.
*/
async isEnabled(): Promise<boolean> {
return true;
}
/**
* Given a list of sections, get the "current" section that should be displayed first.
*
* @param course The course to get the title.
* @param sections List of sections.
* @return Current section (or promise resolved with current section).
*/
async getCurrentSection(course: CoreCourseAnyCourseData, sections: CoreCourseSection[]): Promise<CoreCourseSection> {
const now = CoreTimeUtils.instance.timestamp();
if ((course.startdate && now < course.startdate) || (course.enddate && now > course.enddate)) {
// Course hasn't started yet or it has ended already. Return all sections.
return sections[0];
}
for (let i = 0; i < sections.length; i++) {
const section = sections[i];
if (typeof section.section == 'undefined' || section.section < 1) {
continue;
}
const dates = this.getSectionDates(section, course.startdate || 0);
if (now >= dates.start && now < dates.end) {
return section;
}
}
// The section wasn't found, return all sections.
return sections[0];
}
/**
* Return the start and end date of a section.
*
* @param section The section to treat.
* @param startDate The course start date (in seconds).
* @return An object with the start and end date of the section.
*/
protected getSectionDates(section: CoreCourseSection, startDate: number): { start: number; end: number } {
// Hack alert. We add 2 hours to avoid possible DST problems. (e.g. we go into daylight savings and the date changes).
startDate = startDate + 7200;
const dates = {
start: startDate + (CoreConstants.SECONDS_WEEK * (section.section! - 1)),
end: 0,
};
dates.end = dates.start + CoreConstants.SECONDS_WEEK;
return dates;
}
}
export class CoreCourseFormatWeeksHandler extends makeSingleton(CoreCourseFormatWeeksHandlerService) {}

View File

@ -0,0 +1,34 @@
// (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 { APP_INITIALIZER, NgModule } from '@angular/core';
import { CoreCourseFormatDelegate } from '@features/course/services/format-delegate';
import { CoreCourseFormatWeeksHandler } from './services/handlers/weeks-format';
@NgModule({
declarations: [],
imports: [],
providers: [
{
provide: APP_INITIALIZER,
multi: true,
deps: [],
useFactory: () => () => {
CoreCourseFormatDelegate.instance.registerHandler(CoreCourseFormatWeeksHandler.instance);
},
},
],
})
export class CoreCourseFormatWeeksModule {}

View File

@ -44,7 +44,7 @@ export const SITE_SCHEMA: CoreSiteSchema = {
{
name: 'time',
type: 'INTEGER',
}
},
],
primaryKeys: ['component', 'componentid', 'ws', 'time'],
},