diff --git a/src/core/features/course/components/components.module.ts b/src/core/features/course/components/components.module.ts new file mode 100644 index 000000000..4350c88e3 --- /dev/null +++ b/src/core/features/course/components/components.module.ts @@ -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 {} diff --git a/src/core/features/course/components/module-description/core-course-module-description.html b/src/core/features/course/components/module-description/core-course-module-description.html new file mode 100644 index 000000000..bd8df6e14 --- /dev/null +++ b/src/core/features/course/components/module-description/core-course-module-description.html @@ -0,0 +1,11 @@ + + + + + + + {{ note }} + + \ No newline at end of file diff --git a/src/core/features/course/components/module-description/course-module-description.scss b/src/core/features/course/components/module-description/course-module-description.scss new file mode 100644 index 000000000..ca2d02d73 --- /dev/null +++ b/src/core/features/course/components/module-description/course-module-description.scss @@ -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); +// } +// } +// } +// } +// } \ No newline at end of file diff --git a/src/core/features/course/components/module-description/module-description.ts b/src/core/features/course/components/module-description/module-description.ts new file mode 100644 index 000000000..d5fcf1139 --- /dev/null +++ b/src/core/features/course/components/module-description/module-description.ts @@ -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.whoops' | translate }}

+

{{ 'core.uhoh' | translate }}

+ +

{{ 'core.course.activitydisabled' | translate }}

+

+ {{ 'core.course.activitynotyetviewablesiteupgradeneeded' | translate }} +

+

+ {{ 'core.course.activitynotyetviewableremoteaddon' | translate }} +

+

{{ 'core.course.askadmintosupport' | translate }}

+ +
+

{{ 'core.course.useactivityonbrowser' | translate }}

+ + {{ 'core.openinbrowser' | translate }} + + +
+ \ No newline at end of file diff --git a/src/core/features/course/components/unsupported-module/unsupported-module.ts b/src/core/features/course/components/unsupported-module/unsupported-module.ts new file mode 100644 index 000000000..46f2136cb --- /dev/null +++ b/src/core/features/course/components/unsupported-module/unsupported-module.ts @@ -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); + } + +} diff --git a/src/core/features/course/course.module.ts b/src/core/features/course/course.module.ts index a064ab67e..6960585bc 100644 --- a/src/core/features/course/course.module.ts +++ b/src/core/features/course/course.module.ts @@ -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, diff --git a/src/core/features/course/format/formats.module.ts b/src/core/features/course/format/formats.module.ts new file mode 100644 index 000000000..7da1f733a --- /dev/null +++ b/src/core/features/course/format/formats.module.ts @@ -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 { } diff --git a/src/core/features/course/format/singleactivity/components/core-course-format-single-activity.html b/src/core/features/course/format/singleactivity/components/core-course-format-single-activity.html new file mode 100644 index 000000000..1f3a37007 --- /dev/null +++ b/src/core/features/course/format/singleactivity/components/core-course-format-single-activity.html @@ -0,0 +1 @@ + diff --git a/src/core/features/course/format/singleactivity/components/singleactivity.ts b/src/core/features/course/format/singleactivity/components/singleactivity.ts new file mode 100644 index 000000000..379cff7ef --- /dev/null +++ b/src/core/features/course/format/singleactivity/components/singleactivity.ts @@ -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; // Will emit an event when any module completion changes. + + @ViewChild(CoreDynamicComponent) dynamicComponent?: CoreDynamicComponent; + + componentClass?: Type; // The class of the component to render. + data: Record = {}; // Data to pass to the component. + + /** + * Detect changes on input properties. + */ + async ngOnChanges(changes: { [name: string]: SimpleChange }): Promise { + 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, done?: () => void, afterCompletionChange?: boolean): Promise { + 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'); + } + +} diff --git a/src/core/features/course/format/singleactivity/services/handlers/singleactivity-format.ts b/src/core/features/course/format/singleactivity/services/handlers/singleactivity-format.ts new file mode 100644 index 000000000..203d4910f --- /dev/null +++ b/src/core/features/course/format/singleactivity/services/handlers/singleactivity-format.ts @@ -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 { + 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> { + 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 { + return false; + } + +} + +export class CoreCourseFormatSingleActivityHandler extends makeSingleton(CoreCourseFormatSingleActivityHandlerService) {} diff --git a/src/core/features/course/format/singleactivity/singleactivity.module.ts b/src/core/features/course/format/singleactivity/singleactivity.module.ts new file mode 100644 index 000000000..be619a5ab --- /dev/null +++ b/src/core/features/course/format/singleactivity/singleactivity.module.ts @@ -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 {} diff --git a/src/core/features/course/format/social/services/handlers/social-format.ts b/src/core/features/course/format/social/services/handlers/social-format.ts new file mode 100644 index 000000000..dde6c8711 --- /dev/null +++ b/src/core/features/course/format/social/services/handlers/social-format.ts @@ -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) {} diff --git a/src/core/features/course/format/social/social.module.ts b/src/core/features/course/format/social/social.module.ts new file mode 100644 index 000000000..7455034b7 --- /dev/null +++ b/src/core/features/course/format/social/social.module.ts @@ -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 {} diff --git a/src/core/features/course/format/topics/services/handlers/topics-format.ts b/src/core/features/course/format/topics/services/handlers/topics-format.ts new file mode 100644 index 000000000..78d305273 --- /dev/null +++ b/src/core/features/course/format/topics/services/handlers/topics-format.ts @@ -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 { + return true; + } + +} + +export class CoreCourseFormatTopicsHandler extends makeSingleton(CoreCourseFormatTopicsHandlerService) {} diff --git a/src/core/features/course/format/topics/topics.module.ts b/src/core/features/course/format/topics/topics.module.ts new file mode 100644 index 000000000..01c8518c4 --- /dev/null +++ b/src/core/features/course/format/topics/topics.module.ts @@ -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 {} diff --git a/src/core/features/course/format/weeks/services/handlers/weeks-format.ts b/src/core/features/course/format/weeks/services/handlers/weeks-format.ts new file mode 100644 index 000000000..e668a96d8 --- /dev/null +++ b/src/core/features/course/format/weeks/services/handlers/weeks-format.ts @@ -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 { + 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 { + 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) {} diff --git a/src/core/features/course/format/weeks/weeks.module.ts b/src/core/features/course/format/weeks/weeks.module.ts new file mode 100644 index 000000000..3ad1b0f19 --- /dev/null +++ b/src/core/features/course/format/weeks/weeks.module.ts @@ -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 {} diff --git a/src/core/features/course/services/database/log.ts b/src/core/features/course/services/database/log.ts index 1031ff3d4..20eac30c2 100644 --- a/src/core/features/course/services/database/log.ts +++ b/src/core/features/course/services/database/log.ts @@ -44,7 +44,7 @@ export const SITE_SCHEMA: CoreSiteSchema = { { name: 'time', type: 'INTEGER', - } + }, ], primaryKeys: ['component', 'componentid', 'ws', 'time'], },