MOBILE-3644 glossary: Migrate index page
This commit is contained in:
		
							parent
							
								
									010475b790
								
							
						
					
					
						commit
						184a7b561b
					
				
							
								
								
									
										43
									
								
								src/addons/mod/glossary/components/components.module.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								src/addons/mod/glossary/components/components.module.ts
									
									
									
									
									
										Normal 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 { NgModule } from '@angular/core'; | ||||
| import { AddonModGlossaryIndexComponent } from './index/index'; | ||||
| import { AddonModGlossaryModePickerPopoverComponent } from './mode-picker/mode-picker'; | ||||
| import { CoreSharedModule } from '@/core/shared.module'; | ||||
| import { CoreCourseComponentsModule } from '@features/course/components/components.module'; | ||||
| import { CoreSearchComponentsModule } from '@features/search/components/components.module'; | ||||
| 
 | ||||
| @NgModule({ | ||||
|     declarations: [ | ||||
|         AddonModGlossaryIndexComponent, | ||||
|         AddonModGlossaryModePickerPopoverComponent, | ||||
|     ], | ||||
|     imports: [ | ||||
|         CoreSharedModule, | ||||
|         CoreCourseComponentsModule, | ||||
|         CoreSearchComponentsModule, | ||||
|     ], | ||||
|     providers: [ | ||||
|     ], | ||||
|     exports: [ | ||||
|         AddonModGlossaryIndexComponent, | ||||
|         AddonModGlossaryModePickerPopoverComponent, | ||||
|     ], | ||||
|     entryComponents: [ | ||||
|         AddonModGlossaryIndexComponent, | ||||
|         AddonModGlossaryModePickerPopoverComponent, | ||||
|     ], | ||||
| }) | ||||
| export class AddonModGlossaryComponentsModule {} | ||||
| @ -0,0 +1,107 @@ | ||||
| <!-- Buttons to add to the header. --> | ||||
| <core-navbar-buttons slot="end"> | ||||
|     <ion-button *ngIf="glossary && glossary.browsemodes && glossary.browsemodes.length > 1" (click)="openModePicker($event)" | ||||
|         [attr.aria-label]="'addon.mod_glossary.browsemode' | translate"> | ||||
|         <ion-icon name="fas-sort"></ion-icon> | ||||
|     </ion-button> | ||||
| 
 | ||||
|     <ion-button *ngIf="glossary" (click)="toggleSearch()" [attr.aria-label]="'addon.mod_glossary.bysearch' | translate"> | ||||
|         <ion-icon name="fas-search"></ion-icon> | ||||
|     </ion-button> | ||||
| 
 | ||||
|     <core-context-menu> | ||||
|         <core-context-menu-item *ngIf="externalUrl" [priority]="900" [content]="'core.openinbrowser' | translate" | ||||
|             [href]="externalUrl" iconAction="fas-external-link-alt"> | ||||
|         </core-context-menu-item> | ||||
|         <core-context-menu-item *ngIf="description" [priority]="800" [content]="'core.moduleintro' | translate" | ||||
|             (action)="expandDescription()" iconAction="fas-arrow-right"> | ||||
|         </core-context-menu-item> | ||||
|         <core-context-menu-item *ngIf="blog" [priority]="750" content="{{'addon.blog.blog' | translate}}" | ||||
|             iconAction="far-newspaper" (action)="gotoBlog()"> | ||||
|         </core-context-menu-item> | ||||
|         <core-context-menu-item *ngIf="loaded && !(hasOffline || hasOfflineRatings) && isOnline" [priority]="700" | ||||
|             [content]="'core.refresh' | translate" (action)="doRefresh(null, $event)" [iconAction]="refreshIcon" | ||||
|             [closeOnClick]="false"> | ||||
|         </core-context-menu-item> | ||||
|         <core-context-menu-item *ngIf="loaded && (hasOffline || hasOfflineRatings) && isOnline" [priority]="600" | ||||
|             (action)="doRefresh(null, $event, true)" [content]="'core.settings.synchronizenow' | translate" [iconAction]="syncIcon" | ||||
|             [closeOnClick]="false"> | ||||
|         </core-context-menu-item> | ||||
|         <core-context-menu-item *ngIf="canAdd" [priority]="550" [content]="'addon.mod_glossary.addentry' | translate" | ||||
|             (action)="openNewEntry()" iconAction="fas-plus"> | ||||
|         </core-context-menu-item> | ||||
|         <core-context-menu-item *ngIf="prefetchStatusIcon" [priority]="500" [content]="prefetchText" (action)="prefetch($event)" | ||||
|             [iconAction]="prefetchStatusIcon" [closeOnClick]="false"> | ||||
|         </core-context-menu-item> | ||||
|         <core-context-menu-item *ngIf="size" [priority]="400" [content]="'core.clearstoreddata' | translate:{$a: size}" | ||||
|             iconDescription="fas-archive" (action)="removeFiles($event)" iconAction="fas-trash" [closeOnClick]="false"> | ||||
|         </core-context-menu-item> | ||||
|     </core-context-menu> | ||||
| </core-navbar-buttons> | ||||
| 
 | ||||
| <!-- Content. --> | ||||
| <core-split-view> | ||||
|     <ion-refresher slot="fixed" [disabled]="!loaded" (ionRefresh)="doRefresh($event.target)"> | ||||
|         <ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content> | ||||
|     </ion-refresher> | ||||
| 
 | ||||
|     <core-search-box *ngIf="isSearch" (onSubmit)="search($event)" [placeholder]="'addon.mod_glossary.searchquery' | translate" | ||||
|         [autoFocus]="true" [lengthCheck]="2" (onClear)="toggleSearch()" searchArea="AddonModGlossary-{{module.id}}"> | ||||
|     </core-search-box> | ||||
| 
 | ||||
|     <core-loading [hideUntil]="entries.loaded" class="core-loading-center"> | ||||
|         <core-course-module-description [description]="description" [component]="component" [componentId]="componentId" | ||||
|             contextLevel="module" [contextInstanceId]="module.id" [courseId]="courseId"> | ||||
|         </core-course-module-description> | ||||
| 
 | ||||
|         <!-- Has offline data to be synchronized --> | ||||
|         <ion-card class="core-warning-card" *ngIf="hasOffline || hasOfflineRatings"> | ||||
|             <ion-item> | ||||
|                 <ion-icon name="fas-exclamation-triangle" slot="start"></ion-icon> | ||||
|                 <ion-label>{{ 'core.hasdatatosync' | translate:{$a: moduleName} }}</ion-label> | ||||
|             </ion-item> | ||||
|         </ion-card> | ||||
| 
 | ||||
|         <ion-list *ngIf="!isSearch && entries.offlineEntries.length > 0"> | ||||
|             <ion-item-divider> | ||||
|                 <ion-label>{{ 'addon.mod_glossary.entriestobesynced' | translate }}</ion-label> | ||||
|             </ion-item-divider> | ||||
|             <ion-item *ngFor="let entry of entries.offlineEntries" (click)="entries.select(entry)" detail="false" | ||||
|                 [class.core-selected-item]="entries.isSelected(entry)"> | ||||
|                 <ion-label> | ||||
|                     <core-format-text [text]="entry.concept" contextLevel="module" [contextInstanceId]="glossary!.coursemodule" | ||||
|                         [courseId]="courseId"> | ||||
|                     </core-format-text> | ||||
|                 </ion-label> | ||||
|             </ion-item> | ||||
|         </ion-list> | ||||
| 
 | ||||
|         <ion-list *ngIf="entries.onlineEntries.length > 0"> | ||||
|             <ng-container *ngFor="let entry of entries.onlineEntries; let index = index"> | ||||
|                 <ion-item-divider *ngIf="getDivider && showDivider(entry, entries.onlineEntries[index - 1])"> | ||||
|                     {{ getDivider!(entry) }} | ||||
|                 </ion-item-divider> | ||||
| 
 | ||||
|                 <ion-item (click)="entries.select(entry)" [class.core-selected-item]="entries.isSelected(entry)" detail="false"> | ||||
|                     <ion-label> | ||||
|                         <core-format-text [text]="entry.concept" contextLevel="module" [contextInstanceId]="glossary!.coursemodule" | ||||
|                             [courseId]="courseId"> | ||||
|                         </core-format-text> | ||||
|                     </ion-label> | ||||
|                 </ion-item> | ||||
|             </ng-container> | ||||
|         </ion-list> | ||||
| 
 | ||||
|         <core-empty-box *ngIf="entries.empty" icon="fas-list" [message]="'addon.mod_glossary.noentriesfound' | translate"> | ||||
|         </core-empty-box> | ||||
| 
 | ||||
|         <core-infinite-loading [enabled]="!entries.completed" [error]="loadMoreError" (action)="loadMoreEntries($event)"> | ||||
|         </core-infinite-loading> | ||||
|     </core-loading> | ||||
| 
 | ||||
|     <ion-fab slot="fixed" core-fab vertical="bottom" horizontal="end" *ngIf="canAdd"> | ||||
|         <ion-fab-button (click)="openNewEntry()" [attr.aria-label]="'addon.mod_glossary.addentry' | translate"> | ||||
|             <ion-icon name="fas-plus"></ion-icon> | ||||
|         </ion-fab-button> | ||||
|     </ion-fab> | ||||
| </core-split-view> | ||||
							
								
								
									
										646
									
								
								src/addons/mod/glossary/components/index/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										646
									
								
								src/addons/mod/glossary/components/index/index.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,646 @@ | ||||
| // (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 { ContextLevel } from '@/core/constants'; | ||||
| import { AfterViewInit, Component, OnDestroy, OnInit, Optional, ViewChild } from '@angular/core'; | ||||
| import { ActivatedRoute, Params } from '@angular/router'; | ||||
| import { CorePageItemsListManager } from '@classes/page-items-list-manager'; | ||||
| import { CoreSplitViewComponent } from '@components/split-view/split-view'; | ||||
| 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 { CoreRatingProvider } from '@features/rating/services/rating'; | ||||
| import { CoreRatingOffline } from '@features/rating/services/rating-offline'; | ||||
| import { CoreRatingSyncProvider } from '@features/rating/services/rating-sync'; | ||||
| import { IonContent } from '@ionic/angular'; | ||||
| import { CoreSites } from '@services/sites'; | ||||
| import { CoreDomUtils } from '@services/utils/dom'; | ||||
| import { CoreTextUtils } from '@services/utils/text'; | ||||
| import { PopoverController, Translate } from '@singletons'; | ||||
| import { CoreEventObserver, CoreEvents } from '@singletons/events'; | ||||
| import { | ||||
|     AddonModGlossary, | ||||
|     AddonModGlossaryEntry, | ||||
|     AddonModGlossaryEntryWithCategory, | ||||
|     AddonModGlossaryGetEntriesOptions, | ||||
|     AddonModGlossaryGetEntriesWSResponse, | ||||
|     AddonModGlossaryGlossary, | ||||
|     AddonModGlossaryProvider, | ||||
| } from '../../services/glossary'; | ||||
| import { AddonModGlossaryOffline, AddonModGlossaryOfflineEntry } from '../../services/glossary-offline'; | ||||
| import { | ||||
|     AddonModGlossaryAutoSyncData, | ||||
|     AddonModGlossarySyncProvider, | ||||
|     AddonModGlossarySyncResult, | ||||
| } from '../../services/glossary-sync'; | ||||
| import { AddonModGlossaryPrefetchHandler } from '../../services/handlers/prefetch'; | ||||
| import { AddonModGlossaryModePickerPopoverComponent } from '../mode-picker/mode-picker'; | ||||
| 
 | ||||
| /** | ||||
|  * Component that displays a glossary entry page. | ||||
|  */ | ||||
| @Component({ | ||||
|     selector: 'addon-mod-glossary-index', | ||||
|     templateUrl: 'addon-mod-glossary-index.html', | ||||
| }) | ||||
| export class AddonModGlossaryIndexComponent extends CoreCourseModuleMainActivityComponent | ||||
|     implements OnInit, AfterViewInit, OnDestroy { | ||||
| 
 | ||||
|     @ViewChild(CoreSplitViewComponent) splitView!: CoreSplitViewComponent; | ||||
| 
 | ||||
|     component = AddonModGlossaryProvider.COMPONENT; | ||||
|     moduleName = 'glossary'; | ||||
| 
 | ||||
|     isSearch = false; | ||||
|     canAdd = false; | ||||
|     loadMoreError = false; | ||||
|     loadingMessage?: string; | ||||
|     entries: AddonModGlossaryEntriesManager; | ||||
|     hasOfflineRatings = false; | ||||
|     glossary?: AddonModGlossaryGlossary; | ||||
| 
 | ||||
|     protected syncEventName = AddonModGlossarySyncProvider.AUTO_SYNCED; | ||||
|     protected fetchFunction?: (options?: AddonModGlossaryGetEntriesOptions) => AddonModGlossaryGetEntriesWSResponse; | ||||
|     protected fetchInvalidate?: () => Promise<void>; | ||||
|     protected addEntryObserver?: CoreEventObserver; | ||||
|     protected fetchMode?: AddonModGlossaryFetchMode; | ||||
|     protected viewMode?: string; | ||||
|     protected fetchedEntriesCanLoadMore = false; | ||||
|     protected fetchedEntries: AddonModGlossaryEntry[] = []; | ||||
|     protected ratingOfflineObserver?: CoreEventObserver; | ||||
|     protected ratingSyncObserver?: CoreEventObserver; | ||||
| 
 | ||||
|     getDivider?: (entry: AddonModGlossaryEntry) => string; | ||||
|     showDivider: (entry: AddonModGlossaryEntry, previous?: AddonModGlossaryEntry) => boolean = () => false; | ||||
| 
 | ||||
|     constructor( | ||||
|         route: ActivatedRoute, | ||||
|         protected content?: IonContent, | ||||
|         @Optional() courseContentsPage?: CoreCourseContentsPage, | ||||
|     ) { | ||||
|         super('AddonModGlossaryIndexComponent', content, courseContentsPage); | ||||
| 
 | ||||
|         this.entries = new AddonModGlossaryEntriesManager( | ||||
|             route.component, | ||||
|         ); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @inheritdoc | ||||
|      */ | ||||
|     async ngOnInit(): Promise<void> { | ||||
|         super.ngOnInit(); | ||||
| 
 | ||||
|         this.loadingMessage = Translate.instant('core.loading'); | ||||
| 
 | ||||
|         // When an entry is added, we reload the data.
 | ||||
|         this.addEntryObserver = CoreEvents.on(AddonModGlossaryProvider.ADD_ENTRY_EVENT, (data) => { | ||||
|             if (this.glossary && this.glossary.id === data.glossaryId) { | ||||
|                 this.showLoadingAndRefresh(false); | ||||
| 
 | ||||
|                 // Check completion since it could be configured to complete once the user adds a new entry.
 | ||||
|                 CoreCourse.checkModuleCompletion(this.courseId, this.module.completiondata); | ||||
|             } | ||||
|         }); | ||||
| 
 | ||||
|         // Listen for offline ratings saved and synced.
 | ||||
|         this.ratingOfflineObserver = CoreEvents.on(CoreRatingProvider.RATING_SAVED_EVENT, (data) => { | ||||
|             if (this.glossary && data.component == 'mod_glossary' && data.ratingArea == 'entry' && data.contextLevel == 'module' | ||||
|                     && data.instanceId == this.glossary.coursemodule) { | ||||
|                 this.hasOfflineRatings = true; | ||||
|             } | ||||
|         }); | ||||
|         this.ratingSyncObserver = CoreEvents.on(CoreRatingSyncProvider.SYNCED_EVENT, (data) => { | ||||
|             if (this.glossary && data.component == 'mod_glossary' && data.ratingArea == 'entry' && data.contextLevel == 'module' | ||||
|                     && data.instanceId == this.glossary.coursemodule) { | ||||
|                 this.hasOfflineRatings = false; | ||||
|             } | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @inheritdoc | ||||
|      */ | ||||
|     async ngAfterViewInit(): Promise<void> { | ||||
|         await this.loadContent(false, true); | ||||
| 
 | ||||
|         if (!this.glossary) { | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         this.entries.start(this.splitView); | ||||
| 
 | ||||
|         try { | ||||
|             await AddonModGlossary.logView(this.glossary.id, this.viewMode!, this.glossary.name); | ||||
| 
 | ||||
|             CoreCourse.checkModuleCompletion(this.courseId, this.module.completiondata); | ||||
|         } catch (error) { | ||||
|             // Ignore errors.
 | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @inheritdoc | ||||
|      */ | ||||
|     protected async fetchContent(refresh: boolean = false, sync: boolean = false, showErrors: boolean = false): Promise<void> { | ||||
|         try { | ||||
|             this.glossary = await AddonModGlossary.getGlossary(this.courseId, this.module.id); | ||||
| 
 | ||||
|             this.description = this.glossary.intro || this.description; | ||||
|             this.canAdd = (AddonModGlossary.isPluginEnabledForEditing() && !!this.glossary.canaddentry) || false; | ||||
| 
 | ||||
|             this.dataRetrieved.emit(this.glossary); | ||||
| 
 | ||||
|             if (!this.fetchMode) { | ||||
|                 this.switchMode('letter_all'); | ||||
|             } | ||||
| 
 | ||||
|             if (sync) { | ||||
|                 // Try to synchronize the glossary.
 | ||||
|                 await this.syncActivity(showErrors); | ||||
|             } | ||||
| 
 | ||||
|             const [hasOfflineRatings] = await Promise.all([ | ||||
|                 CoreRatingOffline.hasRatings('mod_glossary', 'entry', ContextLevel.MODULE, this.glossary.coursemodule), | ||||
|                 this.fetchEntries(), | ||||
|             ]); | ||||
| 
 | ||||
|             this.hasOfflineRatings = hasOfflineRatings; | ||||
|         } finally { | ||||
|             this.fillContextMenu(refresh); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Convenience function to fetch entries. | ||||
|      * | ||||
|      * @param append True if fetched entries are appended to exsiting ones. | ||||
|      * @return Promise resolved when done. | ||||
|      */ | ||||
|     protected async fetchEntries(append: boolean = false): Promise<void> { | ||||
|         if (!this.fetchFunction) { | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         this.loadMoreError = false; | ||||
|         const from = append ? this.entries.onlineEntries.length : 0; | ||||
| 
 | ||||
|         const result = await this.fetchFunction({ | ||||
|             from: from, | ||||
|             cmId: this.module.id, | ||||
|         }); | ||||
| 
 | ||||
|         const hasMoreEntries = from + result.entries.length < result.count; | ||||
| 
 | ||||
|         if (append) { | ||||
|             this.entries.setItems(this.entries.items.concat(result.entries), hasMoreEntries); | ||||
|         } else { | ||||
|             this.entries.setOnlineEntries(result.entries, hasMoreEntries); | ||||
|         } | ||||
| 
 | ||||
|         // Now get the ofline entries.
 | ||||
|         // Check if there are responses stored in offline.
 | ||||
|         const offlineEntries = await AddonModGlossaryOffline.getGlossaryNewEntries(this.glossary!.id); | ||||
| 
 | ||||
|         offlineEntries.sort((a, b) => a.concept.localeCompare(b.concept)); | ||||
|         this.hasOffline = !!offlineEntries.length; | ||||
|         this.entries.setOfflineEntries(offlineEntries); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @inheritdoc | ||||
|      */ | ||||
|     protected async invalidateContent(): Promise<void> { | ||||
|         const promises: Promise<void>[] = []; | ||||
| 
 | ||||
|         if (this.fetchInvalidate) { | ||||
|             promises.push(this.fetchInvalidate()); | ||||
|         } | ||||
| 
 | ||||
|         promises.push(AddonModGlossary.invalidateCourseGlossaries(this.courseId)); | ||||
| 
 | ||||
|         if (this.glossary) { | ||||
|             promises.push(AddonModGlossary.invalidateCategories(this.glossary.id)); | ||||
|         } | ||||
| 
 | ||||
|         await Promise.all(promises); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Performs the sync of the activity. | ||||
|      * | ||||
|      * @return Promise resolved when done. | ||||
|      */ | ||||
|     protected sync(): Promise<AddonModGlossarySyncResult> { | ||||
|         return AddonModGlossaryPrefetchHandler.sync(this.module, this.courseId); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Checks if sync has succeed from result sync data. | ||||
|      * | ||||
|      * @param result Data returned on the sync function. | ||||
|      * @return Whether it succeed or not. | ||||
|      */ | ||||
|     protected hasSyncSucceed(result: AddonModGlossarySyncResult): boolean { | ||||
|         return result.updated; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * 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: AddonModGlossaryAutoSyncData): boolean { | ||||
|         return !!this.glossary && syncEventData.glossaryId == this.glossary.id && | ||||
|                 syncEventData.userId == CoreSites.getCurrentSiteUserId(); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Change fetch mode. | ||||
|      * | ||||
|      * @param mode New mode. | ||||
|      */ | ||||
|     protected switchMode(mode: AddonModGlossaryFetchMode): void { | ||||
|         this.fetchMode = mode; | ||||
|         this.isSearch = false; | ||||
| 
 | ||||
|         switch (mode) { | ||||
|             case 'author_all': | ||||
|                 // Browse by author.
 | ||||
|                 this.viewMode = 'author'; | ||||
|                 this.fetchFunction = AddonModGlossary.getEntriesByAuthor.bind( | ||||
|                     AddonModGlossary.instance, | ||||
|                     this.glossary!.id, | ||||
|                     'ALL', | ||||
|                     'LASTNAME', | ||||
|                     'ASC', | ||||
|                 ); | ||||
|                 this.fetchInvalidate = AddonModGlossary.invalidateEntriesByAuthor.bind( | ||||
|                     AddonModGlossary.instance, | ||||
|                     this.glossary!.id, | ||||
|                     'ALL', | ||||
|                     'LASTNAME', | ||||
|                     'ASC', | ||||
|                 ); | ||||
|                 this.getDivider = (entry) => entry.userfullname; | ||||
|                 this.showDivider = (entry, previous) => !previous || entry.userid != previous.userid; | ||||
|                 break; | ||||
| 
 | ||||
|             case 'cat_all': | ||||
|                 // Browse by category.
 | ||||
|                 this.viewMode = 'cat'; | ||||
|                 this.fetchFunction = AddonModGlossary.getEntriesByCategory.bind( | ||||
|                     AddonModGlossary.instance, | ||||
|                     this.glossary!.id, | ||||
|                     AddonModGlossaryProvider.SHOW_ALL_CATEGORIES, | ||||
|                 ); | ||||
|                 this.fetchInvalidate = AddonModGlossary.invalidateEntriesByCategory.bind( | ||||
|                     AddonModGlossary.instance, | ||||
|                     this.glossary!.id, | ||||
|                     AddonModGlossaryProvider.SHOW_ALL_CATEGORIES, | ||||
|                 ); | ||||
|                 this.getDivider = (entry: AddonModGlossaryEntryWithCategory) => entry.categoryname || ''; | ||||
|                 this.showDivider = (entry, previous) => !previous || this.getDivider!(entry) != this.getDivider!(previous); | ||||
|                 break; | ||||
| 
 | ||||
|             case 'newest_first': | ||||
|                 // Newest first.
 | ||||
|                 this.viewMode = 'date'; | ||||
|                 this.fetchFunction = AddonModGlossary.getEntriesByDate.bind( | ||||
|                     AddonModGlossary.instance, | ||||
|                     this.glossary!.id, | ||||
|                     'CREATION', | ||||
|                     'DESC', | ||||
|                 ); | ||||
|                 this.fetchInvalidate = AddonModGlossary.invalidateEntriesByDate.bind( | ||||
|                     AddonModGlossary.instance, | ||||
|                     this.glossary!.id, | ||||
|                     'CREATION', | ||||
|                     'DESC', | ||||
|                 ); | ||||
|                 this.getDivider = undefined; | ||||
|                 this.showDivider = () => false; | ||||
|                 break; | ||||
| 
 | ||||
|             case 'recently_updated': | ||||
|                 // Recently updated.
 | ||||
|                 this.viewMode = 'date'; | ||||
|                 this.fetchFunction = AddonModGlossary.getEntriesByDate.bind( | ||||
|                     AddonModGlossary.instance, | ||||
|                     this.glossary!.id, | ||||
|                     'UPDATE', | ||||
|                     'DESC', | ||||
|                 ); | ||||
|                 this.fetchInvalidate = AddonModGlossary.invalidateEntriesByDate.bind( | ||||
|                     AddonModGlossary.instance, | ||||
|                     this.glossary!.id, | ||||
|                     'UPDATE', | ||||
|                     'DESC', | ||||
|                 ); | ||||
|                 this.getDivider = undefined; | ||||
|                 this.showDivider = () => false; | ||||
|                 break; | ||||
| 
 | ||||
|             case 'letter_all': | ||||
|             default: | ||||
|                 // Consider it is 'letter_all'.
 | ||||
|                 this.viewMode = 'letter'; | ||||
|                 this.fetchMode = 'letter_all'; | ||||
|                 this.fetchFunction = AddonModGlossary.getEntriesByLetter.bind( | ||||
|                     AddonModGlossary.instance, | ||||
|                     this.glossary!.id, | ||||
|                     'ALL', | ||||
|                 ); | ||||
|                 this.fetchInvalidate = AddonModGlossary.invalidateEntriesByLetter.bind( | ||||
|                     AddonModGlossary.instance, | ||||
|                     this.glossary!.id, | ||||
|                     'ALL', | ||||
|                 ); | ||||
|                 this.getDivider = (entry) => { | ||||
|                     // Try to get the first letter without HTML tags.
 | ||||
|                     const noTags = CoreTextUtils.cleanTags(entry.concept); | ||||
| 
 | ||||
|                     return (noTags || entry.concept).substr(0, 1).toUpperCase(); | ||||
|                 }; | ||||
|                 this.showDivider = (entry, previous) => !previous || this.getDivider!(entry) != this.getDivider!(previous); | ||||
|                 break; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Convenience function to load more entries. | ||||
|      * | ||||
|      * @param infiniteComplete Infinite scroll complete function. Only used from core-infinite-loading. | ||||
|      * @return Promise resolved when done. | ||||
|      */ | ||||
|     async loadMoreEntries(infiniteComplete?: () => void): Promise<void> { | ||||
|         try { | ||||
|             await this.fetchEntries(true); | ||||
|         } catch (error) { | ||||
|             this.loadMoreError = true; | ||||
|             CoreDomUtils.showErrorModalDefault(error, 'addon.mod_glossary.errorloadingentries', true); | ||||
|         } finally { | ||||
|             infiniteComplete && infiniteComplete(); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Show the mode picker menu. | ||||
|      * | ||||
|      * @param event Event. | ||||
|      */ | ||||
|     async openModePicker(event: MouseEvent): Promise<void> { | ||||
|         const popover = await PopoverController.create({ | ||||
|             component: AddonModGlossaryModePickerPopoverComponent, | ||||
|             componentProps: { | ||||
|                 browseModes: this.glossary!.browsemodes, | ||||
|                 selectedMode: this.isSearch ? '' : this.fetchMode, | ||||
|             }, | ||||
|             event, | ||||
|         }); | ||||
| 
 | ||||
|         popover.present(); | ||||
| 
 | ||||
|         const result = await popover.onDidDismiss<AddonModGlossaryFetchMode>(); | ||||
| 
 | ||||
|         const mode = result.data; | ||||
|         if (mode) { | ||||
|             if (mode !== this.fetchMode) { | ||||
|                 this.changeFetchMode(mode); | ||||
|             } else if (this.isSearch) { | ||||
|                 this.toggleSearch(); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Toggles between search and fetch mode. | ||||
|      */ | ||||
|     toggleSearch(): void { | ||||
|         if (this.isSearch) { | ||||
|             this.isSearch = false; | ||||
|             this.entries.setOnlineEntries(this.fetchedEntries, this.fetchedEntriesCanLoadMore); | ||||
|             this.switchMode(this.fetchMode!); | ||||
|         } else { | ||||
|             // Search for entries. The fetch function will be set when searching.
 | ||||
|             this.getDivider = undefined; | ||||
|             this.showDivider = () => false; | ||||
|             this.isSearch = true; | ||||
| 
 | ||||
|             this.fetchedEntries = this.entries.onlineEntries; | ||||
|             this.fetchedEntriesCanLoadMore = !this.entries.completed; | ||||
|             this.entries.setItems([], false); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Change fetch mode. | ||||
|      * | ||||
|      * @param mode Mode. | ||||
|      */ | ||||
|     changeFetchMode(mode: AddonModGlossaryFetchMode): void { | ||||
|         this.isSearch = false; | ||||
|         this.loadingMessage = Translate.instant('core.loading'); | ||||
|         this.content?.scrollToTop(); | ||||
|         this.switchMode(mode); | ||||
|         this.loaded = false; | ||||
|         this.loadContent(); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Opens new entry editor. | ||||
|      */ | ||||
|     openNewEntry(): void { | ||||
|         this.entries.select({ newEntry: true }); | ||||
|         // @todo
 | ||||
|         // const params = {
 | ||||
|         //     courseId: this.courseId,
 | ||||
|         //     module: this.module,
 | ||||
|         //     glossary: this.glossary,
 | ||||
|         //     entry: entry,
 | ||||
|         // };
 | ||||
|         // this.splitviewCtrl.getMasterNav().push('AddonModGlossaryEditPage', params);
 | ||||
|         // this.selectedEntry = 0;
 | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Search entries. | ||||
|      * | ||||
|      * @param query Text entered on the search box. | ||||
|      */ | ||||
|     search(query: string): void { | ||||
|         this.loadingMessage = Translate.instant('core.searching'); | ||||
|         this.fetchFunction = AddonModGlossary.getEntriesBySearch.bind( | ||||
|             AddonModGlossary.instance, | ||||
|             this.glossary!.id, | ||||
|             query, | ||||
|             true, | ||||
|             'CONCEPT', | ||||
|             'ASC', | ||||
|         ); | ||||
|         this.fetchInvalidate = AddonModGlossary.invalidateEntriesBySearch.bind( | ||||
|             AddonModGlossary.instance, | ||||
|             this.glossary!.id, | ||||
|             query, | ||||
|             true, | ||||
|             'CONCEPT', | ||||
|             'ASC', | ||||
|         ); | ||||
|         this.loaded = false; | ||||
|         this.loadContent(); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @inheritdoc | ||||
|      */ | ||||
|     ngOnDestroy(): void { | ||||
|         super.ngOnDestroy(); | ||||
| 
 | ||||
|         this.addEntryObserver?.off(); | ||||
|         this.ratingOfflineObserver?.off(); | ||||
|         this.ratingSyncObserver?.off(); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * Type to select the new entry form. | ||||
|  */ | ||||
| type NewEntryForm = { newEntry: true }; | ||||
| 
 | ||||
| /** | ||||
|  * Type of items that can be held by the entries manager. | ||||
|  */ | ||||
|  type EntryItem = AddonModGlossaryEntry | AddonModGlossaryOfflineEntry | NewEntryForm; | ||||
| 
 | ||||
| /** | ||||
|  * Entries manager. | ||||
|  */ | ||||
| class AddonModGlossaryEntriesManager extends CorePageItemsListManager<EntryItem> { | ||||
| 
 | ||||
|     onlineEntries: AddonModGlossaryEntry[] = []; | ||||
|     offlineEntries: AddonModGlossaryOfflineEntry[] = []; | ||||
| 
 | ||||
|     constructor(pageComponent: unknown) { | ||||
|         super(pageComponent); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @inheritdoc | ||||
|      */ | ||||
|     getItemQueryParams(entry: EntryItem): Params { | ||||
|         // @todo
 | ||||
|         return { | ||||
|             // courseId: this.component.courseId,
 | ||||
|             // cmId: this.component.module.id,
 | ||||
|             // forumId: this.component.forum!.id,
 | ||||
|             // ...(this.isOnlineDiscussion(discussion) ? { discussion, trackPosts: this.component.trackPosts } : {}),
 | ||||
|         }; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Type guard to infer NewEntryForm objects. | ||||
|      * | ||||
|      * @param entry Item to check. | ||||
|      * @return Whether the item is a new entry form. | ||||
|      */ | ||||
|     isNewEntryForm(entry: EntryItem): entry is NewEntryForm { | ||||
|         return 'newEntry' in entry; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Type guard to infer entry objects. | ||||
|      * | ||||
|      * @param entry Item to check. | ||||
|      * @return Whether the item is an offline entry. | ||||
|      */ | ||||
|     isOfflineEntry(entry: EntryItem): entry is AddonModGlossaryOfflineEntry { | ||||
|         return !this.isNewEntryForm(entry) && !this.isOnlineEntry(entry); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Type guard to infer entry objects. | ||||
|      * | ||||
|      * @param entry Item to check. | ||||
|      * @return Whether the item is an offline entry. | ||||
|      */ | ||||
|     isOnlineEntry(entry: EntryItem): entry is AddonModGlossaryEntry { | ||||
|         return 'id' in entry; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Update online entries items. | ||||
|      * | ||||
|      * @param onlineEntries Online entries. | ||||
|      */ | ||||
|     setOnlineEntries(onlineEntries: AddonModGlossaryEntry[], hasMoreItems: boolean = false): void { | ||||
|         this.setItems((<EntryItem[]> this.offlineEntries).concat(onlineEntries), hasMoreItems); | ||||
|         this.onlineEntries.concat(onlineEntries); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Update offline entries items. | ||||
|      * | ||||
|      * @param offlineEntries Offline entries. | ||||
|      */ | ||||
|     setOfflineEntries(offlineEntries: AddonModGlossaryOfflineEntry[]): void { | ||||
|         this.setItems((<EntryItem[]> offlineEntries).concat(this.onlineEntries), this.hasMoreItems); | ||||
|         this.offlineEntries = offlineEntries; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @inheritdoc | ||||
|      */ | ||||
|     setItems(entries: EntryItem[], hasMoreItems: boolean = false): void { | ||||
|         super.setItems(entries, hasMoreItems); | ||||
| 
 | ||||
|         this.onlineEntries = []; | ||||
|         this.offlineEntries = []; | ||||
|         this.items.forEach(entry => { | ||||
|             if (this.isOfflineEntry(entry)) { | ||||
|                 this.offlineEntries.push(entry); | ||||
|             } else if (this.isOnlineEntry(entry)) { | ||||
|                 this.onlineEntries.push(entry); | ||||
|             } | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @inheritdoc | ||||
|      */ | ||||
|     resetItems(): void { | ||||
|         super.resetItems(); | ||||
|         this.onlineEntries = []; | ||||
|         this.offlineEntries = []; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @inheritdoc | ||||
|      */ | ||||
|     protected getItemPath(entry: EntryItem): string { | ||||
|         if (this.isOnlineEntry(entry)) { | ||||
|             return `entry/${entry.id}`; | ||||
|         } | ||||
| 
 | ||||
|         if (this.isOfflineEntry(entry)) { | ||||
|             return `edit/${entry.timecreated}`; | ||||
|         } | ||||
| 
 | ||||
|         return 'edit/0'; | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| export type AddonModGlossaryFetchMode = 'author_all' | 'cat_all' | 'newest_first' | 'recently_updated' | 'letter_all'; | ||||
| @ -0,0 +1,6 @@ | ||||
| <ion-radio-group [(ngModel)]="selectedMode" (ionChange)="modePicked()"> | ||||
|     <ion-item class="ion-text-wrap" *ngFor="let mode of modes"> | ||||
|         <ion-label>{{ mode.langkey | translate }}</ion-label> | ||||
|         <ion-radio slot="end" [value]="mode.key"></ion-radio> | ||||
|     </ion-item> | ||||
| </ion-radio-group> | ||||
| @ -0,0 +1,64 @@ | ||||
| // (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 { PopoverController } from '@singletons'; | ||||
| import { AddonModGlossaryFetchMode } from '../index'; | ||||
| 
 | ||||
| /** | ||||
|  * Component to display the mode picker. | ||||
|  */ | ||||
| @Component({ | ||||
|     selector: 'addon-mod-glossary-mode-picker-popover', | ||||
|     templateUrl: 'addon-mod-glossary-mode-picker.html', | ||||
| }) | ||||
| export class AddonModGlossaryModePickerPopoverComponent implements OnInit { | ||||
| 
 | ||||
|     @Input() browseModes: string[] = []; | ||||
|     @Input() selectedMode = ''; | ||||
| 
 | ||||
|     modes: { key: AddonModGlossaryFetchMode; langkey: string }[] = []; | ||||
| 
 | ||||
|     /** | ||||
|      * @inheritdoc | ||||
|      */ | ||||
|     ngOnInit(): void { | ||||
|         this.browseModes.forEach((mode) => { | ||||
|             switch (mode) { | ||||
|                 case 'letter' : | ||||
|                     this.modes.push({ key: 'letter_all', langkey: 'addon.mod_glossary.byalphabet' }); | ||||
|                     break; | ||||
|                 case 'cat' : | ||||
|                     this.modes.push({ key: 'cat_all', langkey: 'addon.mod_glossary.bycategory' }); | ||||
|                     break; | ||||
|                 case 'date' : | ||||
|                     this.modes.push({ key: 'newest_first', langkey: 'addon.mod_glossary.bynewestfirst' }); | ||||
|                     this.modes.push({ key: 'recently_updated', langkey: 'addon.mod_glossary.byrecentlyupdated' }); | ||||
|                     break; | ||||
|                 case 'author' : | ||||
|                     this.modes.push({ key: 'author_all', langkey: 'addon.mod_glossary.byauthor' }); | ||||
|                     break; | ||||
|                 default: | ||||
|             } | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Function called when a mode is clicked. | ||||
|      */ | ||||
|     modePicked(): void { | ||||
|         PopoverController.dismiss(this.selectedMode); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
							
								
								
									
										39
									
								
								src/addons/mod/glossary/glossary-lazy.module.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								src/addons/mod/glossary/glossary-lazy.module.ts
									
									
									
									
									
										Normal file
									
								
							| @ -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 { AddonModGlossaryComponentsModule } from './components/components.module'; | ||||
| import { AddonModGlossaryIndexPage } from './pages/index/index'; | ||||
| 
 | ||||
| const routes: Routes = [ | ||||
|     { | ||||
|         path: ':courseId/:cmId', | ||||
|         component: AddonModGlossaryIndexPage, | ||||
|     }, | ||||
| ]; | ||||
| 
 | ||||
| @NgModule({ | ||||
|     imports: [ | ||||
|         RouterModule.forChild(routes), | ||||
|         CoreSharedModule, | ||||
|         AddonModGlossaryComponentsModule, | ||||
|     ], | ||||
|     declarations: [ | ||||
|         AddonModGlossaryIndexPage, | ||||
|     ], | ||||
| }) | ||||
| export class AddonModGlossaryLazyModule {} | ||||
| @ -13,12 +13,15 @@ | ||||
| // 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 { CoreTagAreaDelegate } from '@features/tag/services/tag-area-delegate'; | ||||
| import { CoreCronDelegate } from '@services/cron'; | ||||
| import { CORE_SITE_SCHEMAS } from '@services/sites'; | ||||
| import { AddonModGlossaryComponentsModule } from './components/components.module'; | ||||
| import { SITE_SCHEMA, OFFLINE_SITE_SCHEMA } from './services/database/glossary'; | ||||
| import { AddonModGlossaryProvider } from './services/glossary'; | ||||
| import { AddonModGlossaryHelperProvider } from './services/glossary-helper'; | ||||
| @ -28,7 +31,7 @@ import { AddonModGlossaryEditLinkHandler } from './services/handlers/edit-link'; | ||||
| import { AddonModGlossaryEntryLinkHandler } from './services/handlers/entry-link'; | ||||
| import { AddonModGlossaryIndexLinkHandler } from './services/handlers/index-link'; | ||||
| import { AddonModGlossaryListLinkHandler } from './services/handlers/list-link'; | ||||
| import { AddonModGlossaryModuleHandler } from './services/handlers/module'; | ||||
| import { AddonModGlossaryModuleHandler, AddonModGlossaryModuleHandlerService } from './services/handlers/module'; | ||||
| import { AddonModGlossaryPrefetchHandler } from './services/handlers/prefetch'; | ||||
| import { AddonModGlossarySyncCronHandler } from './services/handlers/sync-cron'; | ||||
| import { AddonModGlossaryTagAreaHandler } from './services/handlers/tag-area'; | ||||
| @ -40,8 +43,17 @@ export const ADDON_MOD_GLOSSARY_SERVICES: Type<unknown>[] = [ | ||||
|     AddonModGlossaryHelperProvider, | ||||
| ]; | ||||
| 
 | ||||
| const routes: Routes = [ | ||||
|     { | ||||
|         path: AddonModGlossaryModuleHandlerService.PAGE_NAME, | ||||
|         loadChildren: () => import('./glossary-lazy.module').then(m => m.AddonModGlossaryLazyModule), | ||||
|     }, | ||||
| ]; | ||||
| 
 | ||||
| @NgModule({ | ||||
|     imports: [ | ||||
|         CoreMainMenuTabRoutingModule.forChild(routes), | ||||
|         AddonModGlossaryComponentsModule, | ||||
|     ], | ||||
|     providers: [ | ||||
|         { | ||||
|  | ||||
							
								
								
									
										18
									
								
								src/addons/mod/glossary/pages/index/index.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								src/addons/mod/glossary/pages/index/index.html
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,18 @@ | ||||
| <ion-header> | ||||
|     <ion-toolbar> | ||||
|         <ion-buttons slot="start"> | ||||
|             <ion-back-button [attr.aria-label]="'core.back' | translate"></ion-back-button> | ||||
|         </ion-buttons> | ||||
|         <ion-title> | ||||
|             <core-format-text [text]="title" contextLevel="module" [contextInstanceId]="module?.id" [courseId]="courseId"> | ||||
|             </core-format-text> | ||||
|         </ion-title> | ||||
|         <ion-buttons slot="end"> | ||||
|             <!-- The buttons defined by the component will be added in here. --> | ||||
|         </ion-buttons> | ||||
|     </ion-toolbar> | ||||
| </ion-header> | ||||
| <ion-content> | ||||
|     <addon-mod-glossary-index [module]="module" [courseId]="courseId" (dataRetrieved)="updateData($event)"> | ||||
|     </addon-mod-glossary-index> | ||||
| </ion-content> | ||||
							
								
								
									
										30
									
								
								src/addons/mod/glossary/pages/index/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								src/addons/mod/glossary/pages/index/index.ts
									
									
									
									
									
										Normal file
									
								
							| @ -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, ViewChild } from '@angular/core'; | ||||
| import { CoreCourseModuleMainActivityPage } from '@features/course/classes/main-activity-page'; | ||||
| import { AddonModGlossaryIndexComponent } from '../../components/index'; | ||||
| 
 | ||||
| /** | ||||
|  * Page that displays a glossary. | ||||
|  */ | ||||
| @Component({ | ||||
|     selector: 'page-addon-mod-glossary-index', | ||||
|     templateUrl: 'index.html', | ||||
| }) | ||||
| export class AddonModGlossaryIndexPage extends CoreCourseModuleMainActivityPage<AddonModGlossaryIndexComponent> { | ||||
| 
 | ||||
|     @ViewChild(AddonModGlossaryIndexComponent) activityComponent?: AddonModGlossaryIndexComponent; | ||||
| 
 | ||||
| } | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user