From 184a7b561b194837dc93f661712de1aa287ffd35 Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Fri, 9 Apr 2021 12:34:51 +0200 Subject: [PATCH] MOBILE-3644 glossary: Migrate index page --- .../glossary/components/components.module.ts | 43 ++ .../index/addon-mod-glossary-index.html | 107 +++ .../mod/glossary/components/index/index.ts | 646 ++++++++++++++++++ .../addon-mod-glossary-mode-picker.html | 6 + .../components/mode-picker/mode-picker.ts | 64 ++ .../mod/glossary/glossary-lazy.module.ts | 39 ++ src/addons/mod/glossary/glossary.module.ts | 14 +- .../mod/glossary/pages/index/index.html | 18 + src/addons/mod/glossary/pages/index/index.ts | 30 + 9 files changed, 966 insertions(+), 1 deletion(-) create mode 100644 src/addons/mod/glossary/components/components.module.ts create mode 100644 src/addons/mod/glossary/components/index/addon-mod-glossary-index.html create mode 100644 src/addons/mod/glossary/components/index/index.ts create mode 100644 src/addons/mod/glossary/components/mode-picker/addon-mod-glossary-mode-picker.html create mode 100644 src/addons/mod/glossary/components/mode-picker/mode-picker.ts create mode 100644 src/addons/mod/glossary/glossary-lazy.module.ts create mode 100644 src/addons/mod/glossary/pages/index/index.html create mode 100644 src/addons/mod/glossary/pages/index/index.ts diff --git a/src/addons/mod/glossary/components/components.module.ts b/src/addons/mod/glossary/components/components.module.ts new file mode 100644 index 000000000..6937ea579 --- /dev/null +++ b/src/addons/mod/glossary/components/components.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 { 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 {} diff --git a/src/addons/mod/glossary/components/index/addon-mod-glossary-index.html b/src/addons/mod/glossary/components/index/addon-mod-glossary-index.html new file mode 100644 index 000000000..43416ef4a --- /dev/null +++ b/src/addons/mod/glossary/components/index/addon-mod-glossary-index.html @@ -0,0 +1,107 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {{ 'core.hasdatatosync' | translate:{$a: moduleName} }} + + + + + + {{ 'addon.mod_glossary.entriestobesynced' | translate }} + + + + + + + + + + + + + {{ getDivider!(entry) }} + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/addons/mod/glossary/components/index/index.ts b/src/addons/mod/glossary/components/index/index.ts new file mode 100644 index 000000000..3cf621c08 --- /dev/null +++ b/src/addons/mod/glossary/components/index/index.ts @@ -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; + 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 { + 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 { + 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 { + 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 { + 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 { + const promises: Promise[] = []; + + 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 { + 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 { + 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 { + 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(); + + 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 { + + 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(( this.offlineEntries).concat(onlineEntries), hasMoreItems); + this.onlineEntries.concat(onlineEntries); + } + + /** + * Update offline entries items. + * + * @param offlineEntries Offline entries. + */ + setOfflineEntries(offlineEntries: AddonModGlossaryOfflineEntry[]): void { + this.setItems(( 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'; diff --git a/src/addons/mod/glossary/components/mode-picker/addon-mod-glossary-mode-picker.html b/src/addons/mod/glossary/components/mode-picker/addon-mod-glossary-mode-picker.html new file mode 100644 index 000000000..747140252 --- /dev/null +++ b/src/addons/mod/glossary/components/mode-picker/addon-mod-glossary-mode-picker.html @@ -0,0 +1,6 @@ + + + {{ mode.langkey | translate }} + + + diff --git a/src/addons/mod/glossary/components/mode-picker/mode-picker.ts b/src/addons/mod/glossary/components/mode-picker/mode-picker.ts new file mode 100644 index 000000000..e3e08071b --- /dev/null +++ b/src/addons/mod/glossary/components/mode-picker/mode-picker.ts @@ -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); + } + +} diff --git a/src/addons/mod/glossary/glossary-lazy.module.ts b/src/addons/mod/glossary/glossary-lazy.module.ts new file mode 100644 index 000000000..03fe01535 --- /dev/null +++ b/src/addons/mod/glossary/glossary-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 { 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 {} diff --git a/src/addons/mod/glossary/glossary.module.ts b/src/addons/mod/glossary/glossary.module.ts index 21b6a7afb..5a114381d 100644 --- a/src/addons/mod/glossary/glossary.module.ts +++ b/src/addons/mod/glossary/glossary.module.ts @@ -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[] = [ AddonModGlossaryHelperProvider, ]; +const routes: Routes = [ + { + path: AddonModGlossaryModuleHandlerService.PAGE_NAME, + loadChildren: () => import('./glossary-lazy.module').then(m => m.AddonModGlossaryLazyModule), + }, +]; + @NgModule({ imports: [ + CoreMainMenuTabRoutingModule.forChild(routes), + AddonModGlossaryComponentsModule, ], providers: [ { diff --git a/src/addons/mod/glossary/pages/index/index.html b/src/addons/mod/glossary/pages/index/index.html new file mode 100644 index 000000000..190905078 --- /dev/null +++ b/src/addons/mod/glossary/pages/index/index.html @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/src/addons/mod/glossary/pages/index/index.ts b/src/addons/mod/glossary/pages/index/index.ts new file mode 100644 index 000000000..23b135fa3 --- /dev/null +++ b/src/addons/mod/glossary/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, 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 { + + @ViewChild(AddonModGlossaryIndexComponent) activityComponent?: AddonModGlossaryIndexComponent; + +}