diff --git a/src/addons/mod/scorm/components/components.module.ts b/src/addons/mod/scorm/components/components.module.ts new file mode 100644 index 000000000..0ec070895 --- /dev/null +++ b/src/addons/mod/scorm/components/components.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 { NgModule } from '@angular/core'; +import { AddonModScormIndexComponent } from './index/index'; +import { CoreSharedModule } from '@/core/shared.module'; +import { CoreCourseComponentsModule } from '@features/course/components/components.module'; + +@NgModule({ + declarations: [ + AddonModScormIndexComponent, + ], + imports: [ + CoreSharedModule, + CoreCourseComponentsModule, + ], + providers: [ + ], + exports: [ + AddonModScormIndexComponent, + ], +}) +export class AddonModScormComponentsModule {} diff --git a/src/addons/mod/scorm/components/index/addon-mod-scorm-index.html b/src/addons/mod/scorm/components/index/addon-mod-scorm-index.html new file mode 100644 index 000000000..61d17c466 --- /dev/null +++ b/src/addons/mod/scorm/components/index/addon-mod-scorm-index.html @@ -0,0 +1,246 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {{ scorm.warningMessage }} + + + + + + + + {{ 'addon.mod_scorm.attempts' | translate }} + + + + + +

{{ 'addon.mod_scorm.noattemptsallowed' | translate }}

+
+

+ {{ 'core.unlimited' | translate }} + {{ scorm.maxattempt }} +

+
+ + +

{{ 'addon.mod_scorm.noattemptsmade' | translate }}

+
+

{{ numAttempts }}

+
+ + +

{{ 'addon.mod_scorm.gradeforattempt' | translate }} {{attempt.num}}

+
+

+ {{ attempt.gradeFormatted }} + {{ 'addon.mod_scorm.cannotcalculategrade' | translate }} +

+
+
+ + +

{{ 'addon.mod_scorm.gradeforattempt' | translate }} {{attempt.num}}

+

+ {{ 'addon.mod_scorm.offlineattemptnote' | translate }} +

+

+ {{ 'addon.mod_scorm.offlineattemptovermax' | translate }} +

+
+

+ {{ attempt.gradeFormatted }} + {{ 'addon.mod_scorm.cannotcalculategrade' | translate }} +

+
+ + +

{{ 'addon.mod_scorm.grademethod' | translate }}

+
+

{{ gradeMethodReadable }}

+
+ + +

{{ 'addon.mod_scorm.gradereported' | translate }}

+
+

+ {{ gradeFormatted }} + {{ 'addon.mod_scorm.cannotcalculategrade' | translate }} +

+
+ + +

{{ 'core.lastsync' | translate }}

+

{{ syncTime }}

+
+
+
+
+ + + + + + {{ 'core.hasdatatosync' | translate: {$a: moduleName} }} + + + + + + + {{ 'addon.mod_scorm.contents' | translate }} + + + + {{ 'addon.mod_scorm.organizations' | translate }} + + + {{ org.title }} + + + + + + + + + + + +

+ {{ 'addon.mod_scorm.dataattemptshown' | translate:{number: attemptToContinue} }} +

+

{{ currentOrganization.title }}

+
+

+ + + + + + + + + + + + ({{ 'addon.mod_scorm.score' | translate }}: {{sco.scoreraw}}) + +

+
+
+
+
+
+ + + + + +

{{ errorMessage | translate }}

+
+
+ + {{ 'core.openinbrowser' | translate }} + + +
+ + + + + +

{{ 'addon.mod_scorm.exceededmaxattempts' | translate }}

+
+
+
+ + + + + + + + {{ 'addon.mod_scorm.newattempt' | translate }} + + + + + + +

{{ statusMessage | translate }}

+
+
+ + + + + + + {{ 'addon.mod_scorm.browse' | translate }} + + + + + + {{ 'addon.mod_scorm.enter' | translate }} + + + + + +
+ + + + + +

{{ progressMessage | translate }}

+ +
+
+
+
+
+
diff --git a/src/addons/mod/scorm/components/index/index.scss b/src/addons/mod/scorm/components/index/index.scss new file mode 100644 index 000000000..636b83653 --- /dev/null +++ b/src/addons/mod/scorm/components/index/index.scss @@ -0,0 +1,19 @@ +@import "~theme/globals"; + +:host { + .addon-mod_scorm-attempt-summary ion-item > p { + font-size: 14px; + } + + .addon-mod_scorm-toc { + // Hide all non sco icons using css to maintain padding. + ion-icon { + opacity: 0; + @include margin(5px, 8px, null, null); + } + + .addon-mod_scorm-type-sco ion-icon { + opacity: 1 + } + } +} diff --git a/src/addons/mod/scorm/components/index/index.ts b/src/addons/mod/scorm/components/index/index.ts new file mode 100644 index 000000000..efaf7b3bd --- /dev/null +++ b/src/addons/mod/scorm/components/index/index.ts @@ -0,0 +1,605 @@ +// (C) Copyright 2015 Moodle Pty Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { CoreConstants } from '@/core/constants'; +import { Component, OnInit, Optional } from '@angular/core'; +import { CoreCourseModuleMainActivityComponent } from '@features/course/classes/main-activity-component'; +import { CoreCourseContentsPage } from '@features/course/pages/contents/contents'; +import { CoreCourse } from '@features/course/services/course'; +import { IonContent } from '@ionic/angular'; +import { CoreNavigator } from '@services/navigator'; +import { CoreDomUtils } from '@services/utils/dom'; +import { CoreUtils } from '@services/utils/utils'; +import { Translate } from '@singletons'; +import { CoreEventObserver, CoreEvents } from '@singletons/events'; +import { AddonModScormPrefetchHandler } from '../../services/handlers/prefetch'; +import { + AddonModScorm, + AddonModScormAttemptCountResult, + AddonModScormGetScormAccessInformationWSResponse, + AddonModScormAttemptGrade, + AddonModScormOrganization, + AddonModScormProvider, + AddonModScormScorm, +} from '../../services/scorm'; +import { AddonModScormHelper, AddonModScormTOCScoWithIcon } from '../../services/scorm-helper'; +import { + AddonModScormAutoSyncEventData, + AddonModScormSync, + AddonModScormSyncProvider, + AddonModScormSyncResult, +} from '../../services/scorm-sync'; + +/** + * Component that displays a SCORM entry page. + */ +@Component({ + selector: 'addon-mod-scorm-index', + templateUrl: 'addon-mod-scorm-index.html', + styleUrls: ['index.scss'], +}) +export class AddonModScormIndexComponent extends CoreCourseModuleMainActivityComponent implements OnInit { + + component = AddonModScormProvider.COMPONENT; + moduleName = 'scorm'; + + scorm?: AddonModScormScorm; // The SCORM object. + currentOrganization: Partial = {}; // Selected organization. + startNewAttempt = false; + errorMessage?: string; // Error message. + syncTime?: string; // Last sync time. + hasOffline = false; // Whether the SCORM has offline data. + attemptToContinue?: number; // The attempt to continue or review. + statusMessage?: string; // Message about the status. + downloading = false; // Whether the SCORM is being downloaded. + percentage?: string; // Download/unzip percentage. + showPercentage = false; // Whether to show the percentage. + progressMessage?: string; // Message about download/unzip. + organizations?: AddonModScormOrganization[]; // List of organizations. + loadingToc = false; // Whether the TOC is being loaded. + toc?: AddonModScormTOCScoWithIcon[]; // Table of contents (structure). + accessInfo?: AddonModScormGetScormAccessInformationWSResponse; // Access information. + skip?: boolean; // Launch immediately. + incomplete = false; // Whether last attempt is incomplete. + numAttempts = -1; // Number of attempts. + grade?: number; // Grade. + gradeFormatted?: string; // Grade formatted. + gradeMethodReadable?: string; // Grade method in a readable format. + attemptsLeft = -1; // Number of attempts left. + onlineAttempts: AttemptGrade[] = []; // Grades for online attempts. + offlineAttempts: AttemptGrade[] = []; // Grades for offline attempts. + + protected fetchContentDefaultError = 'addon.mod_scorm.errorgetscorm'; // Default error to show when loading contents. + protected syncEventName = AddonModScormSyncProvider.AUTO_SYNCED; + protected attempts?: AddonModScormAttemptCountResult; // Data about online and offline attempts. + protected lastAttempt?: number; // Last attempt. + protected lastIsOffline = false; // Whether the last attempt is offline. + protected hasPlayed = false; // Whether the user has opened the player page. + protected dataSentObserver?: CoreEventObserver; // To detect data sent to server. + protected dataSent = false; // Whether some data was sent to server while playing the SCORM. + + constructor( + protected content?: IonContent, + @Optional() courseContentsPage?: CoreCourseContentsPage, + ) { + super('AddonModScormIndexComponent', content, courseContentsPage); + } + + /** + * Component being initialized. + */ + async ngOnInit(): Promise { + super.ngOnInit(); + + await this.loadContent(false, true); + + if (!this.scorm) { + return; + } + + if (this.skip) { + this.open(); + } + + try { + await AddonModScorm.logView(this.scorm.id, this.scorm.name); + + this.checkCompletion(); + } catch { + // Ignore errors. + } + } + + /** + * Check the completion. + */ + protected checkCompletion(): void { + CoreCourse.checkModuleCompletion(this.courseId, this.module.completiondata); + } + + /** + * Download a SCORM package or restores an ongoing download. + * + * @return Promise resolved when done. + */ + protected async downloadScormPackage(): Promise { + this.downloading = true; + + try { + await AddonModScormPrefetchHandler.download(this.module, this.courseId, undefined, (data) => { + if (!data) { + return; + } + + this.percentage = undefined; + this.showPercentage = false; + + if (data.downloading) { + // Downloading package. + if (this.scorm!.packagesize && data.progress) { + const percentageNumber = Number(data.progress.loaded / this.scorm!.packagesize) * 100; + this.percentage = percentageNumber.toFixed(1); + this.showPercentage = percentageNumber >= 0 && percentageNumber <= 100; + } + } else if (data.message) { + // Show a message. + this.progressMessage = data.message; + } else if (data.progress && data.progress.loaded && data.progress.total) { + // Unzipping package. + const percentageNumber = Number(data.progress.loaded / data.progress.total) * 100; + this.percentage = percentageNumber.toFixed(1); + this.showPercentage = percentageNumber >= 0 && percentageNumber <= 100; + } + }); + + } finally { + this.progressMessage = undefined; + this.percentage = undefined; + this.downloading = false; + } + } + + /** + * @inheritdoc + */ + protected async fetchContent(refresh: boolean = false, sync: boolean = false, showErrors: boolean = false): Promise { + try { + // Get the SCORM instance. + this.scorm = await AddonModScorm.getScorm(this.courseId, this.module.id, { moduleUrl: this.module.url }); + + this.dataRetrieved.emit(this.scorm); + this.description = this.scorm.intro || this.description; + this.errorMessage = AddonModScorm.isScormUnsupported(this.scorm); + + if (this.scorm.warningMessage) { + return; // SCORM is closed or not open yet, we can't get more data. + } + + if (sync) { + // Try to synchronize the SCORM. + await CoreUtils.ignoreErrors(this.syncActivity(showErrors)); + } + + const [syncTime, accessInfo] = await Promise.all([ + AddonModScormSync.getReadableSyncTime(this.scorm.id), + AddonModScorm.getAccessInformation(this.scorm.id, { cmId: this.module.id }), + this.fetchAttemptData(this.scorm), + ]); + + this.syncTime = syncTime; + this.accessInfo = accessInfo; + + // Check whether to launch the SCORM immediately. + if (typeof this.skip == 'undefined') { + this.skip = !this.hasOffline && !this.errorMessage && + (!this.scorm.lastattemptlock || this.attemptsLeft > 0) && + this.accessInfo.canskipview && !this.accessInfo.canviewreport && + this.scorm.skipview! >= AddonModScormProvider.SKIPVIEW_FIRST && + (this.scorm.skipview == AddonModScormProvider.SKIPVIEW_ALWAYS || this.lastAttempt == 0); + } + } finally { + this.fillContextMenu(refresh); + } + } + + /** + * Fetch attempt data. + * + * @param scorm Scorm. + * @return Promise resolved when done. + */ + protected async fetchAttemptData(scorm: AddonModScormScorm): Promise { + // Get the number of attempts. + this.attempts = await AddonModScorm.getAttemptCount(scorm.id, { cmId: this.module.id }); + this.hasOffline = !!this.attempts.offline.length; + + // Determine the attempt that will be continued or reviewed. + const attempt = await AddonModScormHelper.determineAttemptToContinue(scorm, this.attempts); + + this.lastAttempt = attempt.num; + this.lastIsOffline = attempt.offline; + + if (this.lastAttempt != this.attempts.lastAttempt.num) { + this.attemptToContinue = this.lastAttempt; + } else { + this.attemptToContinue = undefined; + } + + // Check if the last attempt is incomplete. + this.incomplete = await AddonModScorm.isAttemptIncomplete(scorm.id, this.lastAttempt, { + offline: this.lastIsOffline, + cmId: this.module.id, + }); + + this.numAttempts = this.attempts.total; + this.gradeMethodReadable = AddonModScorm.getScormGradeMethod(scorm); + this.attemptsLeft = AddonModScorm.countAttemptsLeft(scorm, this.attempts.lastAttempt.num); + + if (scorm.forcenewattempt == AddonModScormProvider.SCORM_FORCEATTEMPT_ALWAYS || + (scorm.forcenewattempt && !this.incomplete)) { + this.startNewAttempt = true; + } + + await Promise.all([ + this.getReportedGrades(scorm, this.attempts), + this.fetchStructure(scorm), + this.loadPackageSize(scorm), + this.setStatusListener(), + ]); + } + + /** + * Load SCORM package size if needed. + * + * @return Promise resolved when done. + */ + protected async loadPackageSize(scorm: AddonModScormScorm): Promise { + if (scorm.packagesize || this.errorMessage) { + return; + } + + // SCORM is supported but we don't have package size. Try to calculate it. + scorm.packagesize = await AddonModScorm.calculateScormSize(scorm); + } + + /** + * Fetch the structure of the SCORM (TOC). + * + * @return Promise resolved when done. + */ + protected async fetchStructure(scorm: AddonModScormScorm): Promise { + this.organizations = await AddonModScorm.getOrganizations(scorm.id, { cmId: this.module.id }); + + if (!this.currentOrganization.identifier) { + // Load first organization (if any). + if (this.organizations.length) { + this.currentOrganization.identifier = this.organizations[0].identifier; + } else { + this.currentOrganization.identifier = ''; + } + } + + return this.loadOrganizationToc(scorm, this.currentOrganization.identifier); + } + + /** + * Get the grade of an attempt and add it to the scorm attempts list. + * + * @param attempt The attempt number. + * @param offline Whether it's an offline attempt. + * @param attempts Object where to add the attempt. + * @return Promise resolved when done. + */ + protected async getAttemptGrade( + attempt: number, + offline: boolean, + attempts: Record, + ): Promise { + const grade = await AddonModScorm.getAttemptGrade(this.scorm!, attempt, offline); + + attempts[attempt] = { + num: attempt, + grade: grade, + }; + } + + /** + * Get the grades of each attempt and the grade of the SCORM. + * + * @return Promise resolved when done. + */ + protected async getReportedGrades(scorm: AddonModScormScorm, attempts: AddonModScormAttemptCountResult): Promise { + const promises: Promise[] = []; + const onlineAttempts: Record = {}; + const offlineAttempts: Record = {}; + + // Calculate the grade for each attempt. + attempts.online.forEach((attempt) => { + // Check that attempt isn't in offline to prevent showing the same attempt twice. Offline should be more recent. + if (attempts.offline.indexOf(attempt) == -1) { + promises.push(this.getAttemptGrade(attempt, false, onlineAttempts)); + } + }); + + attempts.offline.forEach((attempt) => { + promises.push(this.getAttemptGrade(attempt, true, offlineAttempts)); + }); + + await Promise.all(promises); + + // Calculate the grade of the whole SCORM. We only use online attempts to calculate this data. + this.grade = AddonModScorm.calculateScormGrade(scorm, onlineAttempts); + + // Add the attempts to the SCORM in array format in ASC order, and format the grades. + this.onlineAttempts = CoreUtils.objectToArray(onlineAttempts); + this.offlineAttempts = CoreUtils.objectToArray(offlineAttempts); + this.onlineAttempts.sort((a, b) => a.num - b.num); + this.offlineAttempts.sort((a, b) => a.num - b.num); + + // Now format the grades. + this.onlineAttempts.forEach((attempt) => { + attempt.gradeFormatted = AddonModScorm.formatGrade(scorm, attempt.grade); + }); + this.offlineAttempts.forEach((attempt) => { + attempt.gradeFormatted = AddonModScorm.formatGrade(scorm, attempt.grade); + }); + + this.gradeFormatted = AddonModScorm.formatGrade(scorm, this.grade); + } + + /** + * Checks if sync has succeed from result sync data. + * + * @param result Data returned on the sync function. + * @return If suceed or not. + */ + protected hasSyncSucceed(result: AddonModScormSyncResult): boolean { + if (result.updated || this.dataSent) { + // Check completion status if something was sent. + this.checkCompletion(); + } + + this.dataSent = false; + + return true; + } + + /** + * User entered the page that contains the component. + */ + ionViewDidEnter(): void { + super.ionViewDidEnter(); + + if (!this.hasPlayed) { + return; + } + + this.hasPlayed = false; + this.startNewAttempt = false; // Uncheck new attempt. + + // Add a delay to make sure the player has started the last writing calls so we can detect conflicts. + setTimeout(() => { + this.dataSentObserver?.off(); // Stop listening for changes. + this.dataSentObserver = undefined; + + // Refresh data. + this.showLoadingAndRefresh(true, false); + }, 500); + } + + /** + * Perform the invalidate content function. + * + * @return Resolved when done. + */ + protected async invalidateContent(): Promise { + const promises: Promise[] = []; + + promises.push(AddonModScorm.invalidateScormData(this.courseId)); + + if (this.scorm) { + promises.push(AddonModScorm.invalidateAllScormData(this.scorm.id)); + } + + await Promise.all(promises); + } + + /** + * Compares sync event data with current data to check if refresh content is needed. + * + * @param syncEventData Data receiven on sync observer. + * @return True if refresh is needed, false otherwise. + */ + protected isRefreshSyncNeeded(syncEventData: AddonModScormAutoSyncEventData): boolean { + if (syncEventData.updated && this.scorm && syncEventData.scormId == this.scorm.id) { + // Check completion status. + this.checkCompletion(); + + return true; + } + + return false; + } + + /** + * Load a organization's TOC. + */ + async loadOrganization(): Promise { + try { + await this.loadOrganizationToc(this.scorm!, this.currentOrganization.identifier!); + } catch (error) { + CoreDomUtils.showErrorModalDefault(error, this.fetchContentDefaultError, true); + } + } + + /** + * Load the TOC of a certain organization. + * + * @param organizationId The organization id. + * @return Promise resolved when done. + */ + protected async loadOrganizationToc(scorm: AddonModScormScorm, organizationId: string): Promise { + if (!scorm.displaycoursestructure) { + // TOC is not displayed, no need to load it. + return; + } + + this.loadingToc = true; + + try { + this.toc = await AddonModScormHelper.getToc(scorm.id, this.lastAttempt!, this.incomplete, { + organization: organizationId, + offline: this.lastIsOffline, + cmId: this.module.id, + }); + + // Search organization title. + this.organizations!.forEach((org) => { + if (org.identifier == organizationId) { + this.currentOrganization.title = org.title; + } + }); + } finally { + this.loadingToc = false; + } + } + + /** + * Open a SCORM. It will download the SCORM package if it's not downloaded or it has changed. + * + * @param event Event. + * @param scoId SCO that needs to be loaded when the SCORM is opened. If not defined, load first SCO. + */ + async open(event?: Event, preview: boolean = false, scoId?: number): Promise { + if (event) { + event.preventDefault(); + event.stopPropagation(); + } + + if (this.downloading) { + // Scope is being downloaded, abort. + return; + } + + const isOutdated = this.currentStatus == CoreConstants.OUTDATED; + const scorm = this.scorm!; + + if (!isOutdated && this.currentStatus != CoreConstants.NOT_DOWNLOADED) { + // Already downloaded, open it. + this.openScorm(scoId, preview); + + return; + } + + // SCORM needs to be downloaded. + await AddonModScormHelper.confirmDownload(scorm, isOutdated); + // Invalidate WS data if SCORM is outdated. + if (isOutdated) { + await CoreUtils.ignoreErrors(AddonModScorm.invalidateAllScormData(scorm.id)); + } + + try { + await this.downloadScormPackage(); + // Success downloading, open SCORM if user hasn't left the view. + if (!this.isDestroyed) { + this.openScorm(scoId, preview); + } + } catch (error) { + if (!this.isDestroyed) { + CoreDomUtils.showErrorModalDefault( + error, + Translate.instant('addon.mod_scorm.errordownloadscorm', { name: scorm.name }), + ); + } + } + } + + /** + * Open a SCORM package. + * + * @param scoId SCO ID. + */ + protected openScorm(scoId?: number, preview: boolean = false): void { + // Display the full page when returning to the page. + this.skip = false; + this.hasPlayed = true; + + // Detect if anything was sent to server. + this.dataSentObserver?.off(); + + this.dataSentObserver = CoreEvents.on(AddonModScormProvider.DATA_SENT_EVENT, (data) => { + if (data.scormId === this.scorm!.id) { + this.dataSent = true; + } + }, this.siteId); + + CoreNavigator.navigate('player', { + params: { + mode: preview ? AddonModScormProvider.MODEBROWSE : AddonModScormProvider.MODENORMAL, + moduleUrl: this.module.url, + newAttempt: !!this.startNewAttempt, + organizationId: this.currentOrganization.identifier, + scoId: scoId, + }, + }); + } + + /** + * @inheritdoc + */ + protected async showStatus(status: string): Promise { + + if (status == CoreConstants.OUTDATED && this.scorm) { + // Only show the outdated message if the file should be downloaded. + const download = await AddonModScorm.shouldDownloadMainFile(this.scorm, true); + + this.statusMessage = download ? 'addon.mod_scorm.scormstatusoutdated' : ''; + } else if (status == CoreConstants.NOT_DOWNLOADED) { + this.statusMessage = 'addon.mod_scorm.scormstatusnotdownloaded'; + } else if (status == CoreConstants.DOWNLOADING) { + if (!this.downloading) { + // It's being downloaded right now but the view isn't tracking it. "Restore" the download. + this.downloadScormPackage(); + } + } else { + this.statusMessage = ''; + } + } + + /** + * Performs the sync of the activity. + * + * @return Promise resolved when done. + */ + protected async sync(): Promise { + const result = await AddonModScormSync.syncScorm(this.scorm!); + + if (!result.updated && this.dataSent) { + // The user sent data to server, but not in the sync process. Check if we need to fetch data. + await CoreUtils.ignoreErrors( + AddonModScormSync.prefetchAfterUpdate(AddonModScormPrefetchHandler.instance, this.module, this.courseId), + ); + } + + return result; + } + +} + +/** + * Grade for an online attempt. + */ +export type AttemptGrade = AddonModScormAttemptGrade & { + gradeFormatted?: string; +}; diff --git a/src/addons/mod/scorm/pages/index/index.html b/src/addons/mod/scorm/pages/index/index.html new file mode 100644 index 000000000..b8f8eb0e3 --- /dev/null +++ b/src/addons/mod/scorm/pages/index/index.html @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/addons/mod/scorm/pages/index/index.ts b/src/addons/mod/scorm/pages/index/index.ts new file mode 100644 index 000000000..dfaa663ba --- /dev/null +++ b/src/addons/mod/scorm/pages/index/index.ts @@ -0,0 +1,30 @@ +// (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, OnInit, ViewChild } from '@angular/core'; +import { CoreCourseModuleMainActivityPage } from '@features/course/classes/main-activity-page'; +import { AddonModScormIndexComponent } from '../../components/index/index'; + +/** + * Page that displays the scorm entry page. + */ +@Component({ + selector: 'page-addon-mod-scorm-index', + templateUrl: 'index.html', +}) +export class AddonModScormIndexPage extends CoreCourseModuleMainActivityPage implements OnInit { + + @ViewChild(AddonModScormIndexComponent) activityComponent?: AddonModScormIndexComponent; + +} diff --git a/src/addons/mod/scorm/scorm-lazy.module.ts b/src/addons/mod/scorm/scorm-lazy.module.ts new file mode 100644 index 000000000..ae5feb451 --- /dev/null +++ b/src/addons/mod/scorm/scorm-lazy.module.ts @@ -0,0 +1,39 @@ +// (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 { RouterModule, Routes } from '@angular/router'; + +import { CoreSharedModule } from '@/core/shared.module'; +import { AddonModScormComponentsModule } from './components/components.module'; +import { AddonModScormIndexPage } from './pages/index/index'; + +const routes: Routes = [ + { + path: ':courseId/:cmId', + component: AddonModScormIndexPage, + }, +]; + +@NgModule({ + imports: [ + RouterModule.forChild(routes), + CoreSharedModule, + AddonModScormComponentsModule, + ], + declarations: [ + AddonModScormIndexPage, + ], +}) +export class AddonModScormLazyModule {} diff --git a/src/addons/mod/scorm/scorm.module.ts b/src/addons/mod/scorm/scorm.module.ts index c8303d57f..45d28649e 100644 --- a/src/addons/mod/scorm/scorm.module.ts +++ b/src/addons/mod/scorm/scorm.module.ts @@ -13,25 +13,44 @@ // limitations under the License. import { APP_INITIALIZER, NgModule, Type } from '@angular/core'; +import { Routes } from '@angular/router'; import { CoreContentLinksDelegate } from '@features/contentlinks/services/contentlinks-delegate'; import { CoreCourseModuleDelegate } from '@features/course/services/module-delegate'; import { CoreCourseModulePrefetchDelegate } from '@features/course/services/module-prefetch-delegate'; +import { CoreMainMenuTabRoutingModule } from '@features/mainmenu/mainmenu-tab-routing.module'; import { CoreCronDelegate } from '@services/cron'; import { CORE_SITE_SCHEMAS } from '@services/sites'; +import { AddonModScormComponentsModule } from './components/components.module'; import { OFFLINE_SITE_SCHEMA } from './services/database/scorm'; import { AddonModScormGradeLinkHandler } from './services/handlers/grade-link'; import { AddonModScormIndexLinkHandler } from './services/handlers/index-link'; import { AddonModScormListLinkHandler } from './services/handlers/list-link'; -import { AddonModScormModuleHandler } from './services/handlers/module'; +import { AddonModScormModuleHandler, AddonModScormModuleHandlerService } from './services/handlers/module'; import { AddonModScormPrefetchHandler } from './services/handlers/prefetch'; import { AddonModScormSyncCronHandler } from './services/handlers/sync-cron'; +import { AddonModScormProvider } from './services/scorm'; +import { AddonModScormHelperProvider } from './services/scorm-helper'; +import { AddonModScormOfflineProvider } from './services/scorm-offline'; +import { AddonModScormSyncProvider } from './services/scorm-sync'; export const ADDON_MOD_SCORM_SERVICES: Type[] = [ + AddonModScormProvider, + AddonModScormOfflineProvider, + AddonModScormHelperProvider, + AddonModScormSyncProvider, +]; +const routes: Routes = [ + { + path: AddonModScormModuleHandlerService.PAGE_NAME, + loadChildren: () => import('./scorm-lazy.module').then(m => m.AddonModScormLazyModule), + }, ]; @NgModule({ imports: [ + CoreMainMenuTabRoutingModule.forChild(routes), + AddonModScormComponentsModule, ], providers: [ { diff --git a/src/theme/theme.base.scss b/src/theme/theme.base.scss index 641e22700..af5fa751f 100644 --- a/src/theme/theme.base.scss +++ b/src/theme/theme.base.scss @@ -480,3 +480,10 @@ ion-button.core-button-select { .core-browser-copy-area { display: none; } + +// Different levels of padding. +@for $i from 0 through 15 { + .core-padding-#{$i} { + @include padding(null, null, null, 15px * $i + 16px); + } +}