diff --git a/src/core/components/components.module.ts b/src/core/components/components.module.ts index aeef5fea2..feb716d63 100644 --- a/src/core/components/components.module.ts +++ b/src/core/components/components.module.ts @@ -32,9 +32,13 @@ import { CoreEmptyBoxComponent } from './empty-box/empty-box'; import { CoreTabsComponent } from './tabs/tabs'; import { CoreInfiniteLoadingComponent } from './infinite-loading/infinite-loading'; import { CoreProgressBarComponent } from './progress-bar/progress-bar'; +import { CoreContextMenuComponent } from './context-menu/context-menu'; +import { CoreContextMenuItemComponent } from './context-menu/context-menu-item'; +import { CoreContextMenuPopoverComponent } from './context-menu/context-menu-popover'; import { CoreDirectivesModule } from '@directives/directives.module'; import { CorePipesModule } from '@pipes/pipes.module'; +import { CoreNavBarButtonsComponent } from './navbar-buttons/navbar-buttons'; @NgModule({ declarations: [ @@ -53,6 +57,10 @@ import { CorePipesModule } from '@pipes/pipes.module'; CoreTabsComponent, CoreInfiniteLoadingComponent, CoreProgressBarComponent, + CoreContextMenuComponent, + CoreContextMenuItemComponent, + CoreContextMenuPopoverComponent, + CoreNavBarButtonsComponent, ], imports: [ CommonModule, @@ -77,6 +85,10 @@ import { CorePipesModule } from '@pipes/pipes.module'; CoreTabsComponent, CoreInfiniteLoadingComponent, CoreProgressBarComponent, + CoreContextMenuComponent, + CoreContextMenuItemComponent, + CoreContextMenuPopoverComponent, + CoreNavBarButtonsComponent, ], }) export class CoreComponentsModule {} diff --git a/src/core/components/context-menu/context-menu-item.ts b/src/core/components/context-menu/context-menu-item.ts new file mode 100644 index 000000000..0fb926e2a --- /dev/null +++ b/src/core/components/context-menu/context-menu-item.ts @@ -0,0 +1,106 @@ +// (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, Output, OnInit, OnDestroy, EventEmitter, OnChanges, SimpleChange } from '@angular/core'; +import { CoreContextMenuComponent } from './context-menu'; + +/** + * This directive adds a item to the Context Menu popover. + * + * @description + * This directive defines and item to be added to the popover generated in CoreContextMenu. + * + * It is required to place this tag inside a core-context-menu tag. + * + * + * + * + */ +@Component({ + selector: 'core-context-menu-item', + template: '', +}) +export class CoreContextMenuItemComponent implements OnInit, OnDestroy, OnChanges { + + @Input() content?: string; // Content of the item. + @Input() iconDescription?: string; // Name of the icon to be shown on the left side of the item. + @Input() iconAction?: string; // Name of the icon to show on the right side of the item. Represents the action to do on click. + // If is "spinner" an spinner will be shown. + // If no icon or spinner is selected, no action or link will work. + // If href but no iconAction is provided arrow-right will be used. + @Input() iconSlash?: boolean; // Display a red slash over the icon. + @Input() ariaDescription?: string; // Aria label to add to iconDescription. + @Input() ariaAction?: string; // Aria label to add to iconAction. If not set, it will be equal to content. + @Input() href?: string; // Link to go if no action provided. + @Input() captureLink?: boolean | string; // Whether the link needs to be captured by the app. + @Input() autoLogin?: string; // Whether the link needs to be opened using auto-login. + @Input() closeOnClick = true; // Whether to close the popover when the item is clicked. + @Input() priority?: number; // Used to sort items. The highest priority, the highest position. + @Input() badge?: string; // A badge to show in the item. + @Input() badgeClass?: number; // A class to set in the badge. + @Input() hidden?: boolean; // Whether the item should be hidden. + @Output() action?: EventEmitter<() => void>; // Will emit an event when the item clicked. + @Output() onClosed?: EventEmitter<() => void>; // Will emit an event when the popover is closed because the item was clicked. + + protected hasAction = false; + protected destroyed = false; + + constructor( + protected ctxtMenu: CoreContextMenuComponent, + ) { + this.action = new EventEmitter(); + this.onClosed = new EventEmitter(); + } + + /** + * Component being initialized. + */ + ngOnInit(): void { + // Initialize values. + this.priority = this.priority || 1; + this.hasAction = !!this.action && this.action.observers.length > 0; + this.ariaAction = this.ariaAction || this.content; + + if (this.hasAction) { + this.href = ''; + } + + // Navigation help if href provided. + this.captureLink = this.href && this.captureLink ? this.captureLink : false; + this.autoLogin = this.autoLogin || 'check'; + + if (!this.destroyed) { + this.ctxtMenu.addItem(this); + } + } + + /** + * Component destroyed. + */ + ngOnDestroy(): void { + this.destroyed = true; + this.ctxtMenu.removeItem(this); + } + + /** + * Detect changes on input properties. + */ + ngOnChanges(changes: { [name: string]: SimpleChange }): void { + if (changes.hidden && !changes.hidden.firstChange) { + this.ctxtMenu.itemsChanged(); + } + } + +} diff --git a/src/core/components/context-menu/context-menu-popover.scss b/src/core/components/context-menu/context-menu-popover.scss new file mode 100644 index 000000000..4efe68f89 --- /dev/null +++ b/src/core/components/context-menu/context-menu-popover.scss @@ -0,0 +1,5 @@ +:host { + ion-list { + padding: 0; + } +} diff --git a/src/core/components/context-menu/context-menu-popover.ts b/src/core/components/context-menu/context-menu-popover.ts new file mode 100644 index 000000000..89e3861df --- /dev/null +++ b/src/core/components/context-menu/context-menu-popover.ts @@ -0,0 +1,77 @@ +// (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 } from '@angular/core'; +import { NavParams, PopoverController } from '@ionic/angular'; +import { CoreContextMenuItemComponent } from './context-menu-item'; + +/** + * Component to display a list of items received by param in a popover. + */ +@Component({ + selector: 'core-context-menu-popover', + templateUrl: 'core-context-menu-popover.html', + styleUrls: ['context-menu-popover.scss'], +}) +export class CoreContextMenuPopoverComponent { + + title: string; + uniqueId: string; + items: CoreContextMenuItemComponent[]; + + constructor( + navParams: NavParams, + protected popoverCtrl: PopoverController, + ) { + this.title = navParams.get('title'); + this.items = navParams.get('items') || []; + this.uniqueId = navParams.get('id'); + } + + /** + * Close the popover. + */ + closeMenu(item?: CoreContextMenuItemComponent): void { + this.popoverCtrl.dismiss(item); + } + + /** + * Function called when an item is clicked. + * + * @param event Click event. + * @param item Item clicked. + * @return Return true if success, false if error. + */ + itemClicked(event: Event, item: CoreContextMenuItemComponent): boolean { + if (!!item.action && item.action.observers.length > 0) { + event.preventDefault(); + event.stopPropagation(); + + if (item.iconAction == 'spinner') { + return false; + } + + if (item.closeOnClick) { + this.closeMenu(item); + } + + item.action.emit(this.closeMenu.bind(this, item)); + } else if (item.closeOnClick && (item.href || (!!item.onClosed && item.onClosed.observers.length > 0))) { + this.closeMenu(item); + } + + return true; + } + +} diff --git a/src/core/components/context-menu/context-menu.ts b/src/core/components/context-menu/context-menu.ts new file mode 100644 index 000000000..86c5940bd --- /dev/null +++ b/src/core/components/context-menu/context-menu.ts @@ -0,0 +1,213 @@ +// (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, OnDestroy, ElementRef } from '@angular/core'; +import { Subject } from 'rxjs'; +import { auditTime } from 'rxjs/operators'; +import { PopoverController } from '@ionic/angular'; +import { CoreDomUtils } from '@services/utils/dom'; +import { CoreUtils } from '@services/utils/utils'; +import { Translate } from '@singletons/core.singletons'; +import { CoreContextMenuItemComponent } from './context-menu-item'; +import { CoreContextMenuPopoverComponent } from './context-menu-popover'; + +/** + * This component adds a button (usually in the navigation bar) that displays a context menu popover. + */ +@Component({ + selector: 'core-context-menu', + templateUrl: 'core-context-menu.html', +}) +export class CoreContextMenuComponent implements OnInit, OnDestroy { + + @Input() icon?: string; // Icon to be shown on the navigation bar. Default: Kebab menu icon. + @Input() title?: string; // Text to be shown on the top of the popover. + @Input('aria-label') ariaLabel?: string; // Aria label to be shown on the top of the popover. + + hideMenu = true; // It will be unhidden when items are added. + expanded = false; + protected items: CoreContextMenuItemComponent[] = []; + protected itemsMovedToParent: CoreContextMenuItemComponent[] = []; + protected itemsChangedStream: Subject; // Stream to update the hideMenu boolean when items change. + protected instanceId: string; + protected parentContextMenu?: CoreContextMenuComponent; + protected uniqueId: string; + + constructor( + protected popoverCtrl: PopoverController, + elementRef: ElementRef, + ) { + // Create the stream and subscribe to it. We ignore successive changes during 250ms. + this.itemsChangedStream = new Subject(); + this.itemsChangedStream.pipe(auditTime(250)); + this.itemsChangedStream.subscribe(() => { + // Hide the menu if all items are hidden. + this.hideMenu = !this.items.some((item) => !item.hidden); + + // Sort the items by priority. + this.items.sort((a, b) => (a.priority || 0) <= (b.priority || 0) ? 1 : -1); + }); + + // Calculate the unique ID. + this.uniqueId = 'core-context-menu-' + CoreUtils.instance.getUniqueId('CoreContextMenuComponent'); + + this.instanceId = CoreDomUtils.instance.storeInstanceByElement(elementRef.nativeElement, this); + } + + /** + * Component being initialized. + */ + ngOnInit(): void { + this.icon = this.icon || 'ellipsis-vertical'; + this.ariaLabel = this.ariaLabel || this.title || Translate.instance.instant('core.displayoptions'); + } + + /** + * Add a context menu item. + * + * @param item The item to add. + */ + addItem(item: CoreContextMenuItemComponent): void { + if (this.parentContextMenu) { + // All items were moved to the "parent" menu. Add the item in there. + this.parentContextMenu.addItem(item); + + if (this.itemsMovedToParent.indexOf(item) == -1) { + this.itemsMovedToParent.push(item); + } + } else if (this.items.indexOf(item) == -1) { + this.items.push(item); + this.itemsChanged(); + } + } + + /** + * Function called when the items change. + */ + itemsChanged(): void { + if (this.parentContextMenu) { + // All items were moved to the "parent" menu, call the function in there. + this.parentContextMenu.itemsChanged(); + } else { + this.itemsChangedStream.next(); + } + } + + /** + * Merge the current context menu with the one passed as parameter. All the items in this menu will be moved to the + * one passed as parameter. + * + * @param contextMenu The context menu where to move the items. + */ + mergeContextMenus(contextMenu: CoreContextMenuComponent): void { + this.parentContextMenu = contextMenu; + + // Add all the items to the other menu. + for (let i = 0; i < this.items.length; i++) { + const item = this.items[i]; + contextMenu.addItem(item); + this.itemsMovedToParent.push(item); + } + + // Remove all items from the current menu. + this.items = []; + this.itemsChanged(); + } + + /** + * Remove an item from the context menu. + * + * @param item The item to remove. + */ + removeItem(item: CoreContextMenuItemComponent): void { + if (this.parentContextMenu) { + // All items were moved to the "parent" menu. Remove the item from there. + this.parentContextMenu.removeItem(item); + + const index = this.itemsMovedToParent.indexOf(item); + if (index >= 0) { + this.itemsMovedToParent.splice(index, 1); + } + } else { + const index = this.items.indexOf(item); + if (index >= 0) { + this.items.splice(index, 1); + } + this.itemsChanged(); + } + } + + /** + * Remove the items that were merged to a parent context menu. + */ + removeMergedItems(): void { + if (this.parentContextMenu) { + for (let i = 0; i < this.itemsMovedToParent.length; i++) { + this.parentContextMenu.removeItem(this.itemsMovedToParent[i]); + } + } + } + + /** + * Restore the items that were merged to a parent context menu. + */ + restoreMergedItems(): void { + if (this.parentContextMenu) { + for (let i = 0; i < this.itemsMovedToParent.length; i++) { + this.parentContextMenu.addItem(this.itemsMovedToParent[i]); + } + } + } + + /** + * Show the context menu. + * + * @param event Event. + */ + async showContextMenu(event: MouseEvent): Promise { + if (!this.expanded) { + const popover = await this.popoverCtrl.create( + { + event, + component: CoreContextMenuPopoverComponent, + componentProps: { + title: this.title, + items: this.items, + }, + showBackdrop: true, + id: this.uniqueId, + }, + ); + await popover.present(); + this.expanded = true; + + const data = await popover.onDidDismiss(); + this.expanded = false; + + if (data.data) { + data.data.onClosed?.emit(); + } + + } + } + + /** + * Component destroyed. + */ + ngOnDestroy(): void { + CoreDomUtils.instance.removeInstanceById(this.instanceId); + this.removeMergedItems(); + } + +} diff --git a/src/core/components/context-menu/core-context-menu-popover.html b/src/core/components/context-menu/core-context-menu-popover.html new file mode 100644 index 000000000..36cfcacdd --- /dev/null +++ b/src/core/components/context-menu/core-context-menu-popover.html @@ -0,0 +1,15 @@ + + {{title}} + + + + + + + + {{item.badge}} + + diff --git a/src/core/components/context-menu/core-context-menu.html b/src/core/components/context-menu/core-context-menu.html new file mode 100644 index 000000000..adac51104 --- /dev/null +++ b/src/core/components/context-menu/core-context-menu.html @@ -0,0 +1,6 @@ + + + + + diff --git a/src/core/components/navbar-buttons/navbar-buttons.scss b/src/core/components/navbar-buttons/navbar-buttons.scss new file mode 100644 index 000000000..5d9e00932 --- /dev/null +++ b/src/core/components/navbar-buttons/navbar-buttons.scss @@ -0,0 +1,3 @@ +:host { + display: none !important; +} diff --git a/src/core/components/navbar-buttons/navbar-buttons.ts b/src/core/components/navbar-buttons/navbar-buttons.ts new file mode 100644 index 000000000..7697224a4 --- /dev/null +++ b/src/core/components/navbar-buttons/navbar-buttons.ts @@ -0,0 +1,269 @@ +// (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, OnDestroy, ElementRef } from '@angular/core'; +import { CoreLogger } from '@singletons/logger'; +import { CoreDomUtils } from '@services/utils/dom'; +import { CoreContextMenuComponent } from '../context-menu/context-menu'; + +const BUTTON_HIDDEN_CLASS = 'core-navbar-button-hidden'; + +/** + * Component to add buttons to the app's header without having to place them inside the header itself. This is meant for + * pages that are loaded inside a sub ion-nav, so they don't have a header. + * + * If this component indicates a position (start/end), the buttons will only be added if the header has some buttons in that + * position. If no start/end is specified, then the buttons will be added to the first found in the header. + * + * If this component has a "prepend" attribute, the buttons will be added before other existing buttons in the header. + * + * You can use the [hidden] input to hide all the inner buttons if a certain condition is met. + * + * IMPORTANT: Do not use *ngIf in the buttons inside this component, it can cause problems. Please use [hidden] instead. + * + * Example usage: + * + * + * + * + * + * + */ +@Component({ + selector: 'core-navbar-buttons', + template: '', + styleUrls: ['navbar-buttons.scss'], +}) +export class CoreNavBarButtonsComponent implements OnInit, OnDestroy { + + // If the hidden input is true, hide all buttons. + // eslint-disable-next-line @angular-eslint/no-input-rename + @Input('hidden') set hidden(value: boolean) { + if (typeof value == 'string' && value == '') { + value = true; + } + this.allButtonsHidden = value; + this.showHideAllElements(); + } + + protected element: HTMLElement; + protected allButtonsHidden = false; + protected forceHidden = false; + protected logger: CoreLogger; + protected movedChildren?: Node[]; + protected instanceId: string; + protected mergedContextMenu?: CoreContextMenuComponent; + + constructor(element: ElementRef) { + this.element = element.nativeElement; + this.logger = CoreLogger.getInstance('CoreNavBarButtonsComponent'); + this.instanceId = CoreDomUtils.instance.storeInstanceByElement(this.element, this); + } + + /** + * Component being initialized. + */ + async ngOnInit(): Promise { + try { + const header = await this.searchHeader(); + if (header) { + // Search the right buttons container (start, end or any). + let selector = 'ion-buttons'; + + let slot = this.element.getAttribute('slot'); + // Take the slot from the parent if it has. + if (!slot && this.element.parentElement) { + slot = this.element.parentElement.getAttribute('slot'); + } + if (slot) { + selector += '[slot="' + slot + '"]'; + } + + const buttonsContainer = header.querySelector(selector); + if (buttonsContainer) { + this.mergeContextMenus(buttonsContainer); + + const prepend = this.element.hasAttribute('prepend'); + + this.movedChildren = CoreDomUtils.instance.moveChildren(this.element, buttonsContainer, prepend); + this.showHideAllElements(); + + } else { + this.logger.warn('The header was found, but it didn\'t have the right ion-buttons.', selector); + } + } + } catch (error) { + // Header not found. + this.logger.warn(error); + } + } + + /** + * Force or unforce hiding all buttons. If this is true, it will override the "hidden" input. + * + * @param value The value to set. + */ + forceHide(value: boolean): void { + this.forceHidden = value; + + this.showHideAllElements(); + } + + /** + * If both button containers have a context menu, merge them into a single one. + * + * @param buttonsContainer The container where the buttons will be moved. + * @todo + */ + protected mergeContextMenus(buttonsContainer: HTMLElement): void { + // Check if both button containers have a context menu. + const mainContextMenu = buttonsContainer.querySelector('core-context-menu'); + if (!mainContextMenu) { + return; + } + + const secondaryContextMenu = this.element.querySelector('core-context-menu'); + if (!secondaryContextMenu) { + return; + } + + // Both containers have a context menu. Merge them to prevent having 2 menus at the same time. + const mainContextMenuInstance: CoreContextMenuComponent = CoreDomUtils.instance.getInstanceByElement(mainContextMenu); + const secondaryContextMenuInstance: CoreContextMenuComponent = + CoreDomUtils.instance.getInstanceByElement(secondaryContextMenu); + + // Check that both context menus belong to the same core-tab. We shouldn't merge menus from different tabs. + if (mainContextMenuInstance && secondaryContextMenuInstance) { + this.mergedContextMenu = secondaryContextMenuInstance; + + this.mergedContextMenu.mergeContextMenus(mainContextMenuInstance); + + // Remove the empty context menu from the DOM. + secondaryContextMenu.parentElement?.removeChild(secondaryContextMenu); + } + } + + /** + * Search the ion-header where the buttons should be added. + * + * @param retries Number of retries so far. + * @return Promise resolved with the header element. + */ + protected async searchHeader(retries: number = 0): Promise { + let parentPage: HTMLElement = this.element; + + while (parentPage) { + if (!parentPage.parentElement) { + // No parent, stop. + break; + } + + // Get the next parent page. + parentPage = CoreDomUtils.instance.closest(parentPage.parentElement, '.ion-page'); + if (parentPage) { + // Check if the page has a header. If it doesn't, search the next parent page. + const header = this.searchHeaderInPage(parentPage); + if (header && getComputedStyle(header, null).display != 'none') { + return header; + } + } + } + + // Header not found. + if (retries < 5) { + // If the component or any of its parent is inside a ng-content or similar it can be detached when it's initialized. + // Try again after a while. + return new Promise((resolve, reject): void => { + setTimeout(() => { + // eslint-disable-next-line promise/catch-or-return + this.searchHeader(retries + 1).then(resolve, reject); + }, 200); + }); + } + + // We've waited enough time, reject. + throw Error('Header not found.'); + } + + /** + * Search ion-header inside a page. The header should be a direct child. + * + * @param page Page to search in. + * @return Header element. Undefined if not found. + */ + protected searchHeaderInPage(page: HTMLElement): HTMLElement | undefined { + for (let i = 0; i < page.children.length; i++) { + const child = page.children[i]; + if (child.tagName == 'ION-HEADER') { + return child; + } + } + } + + /** + * Show or hide all the elements. + */ + protected showHideAllElements(): void { + // Show or hide all moved children. + if (this.movedChildren) { + this.movedChildren.forEach((child: Node) => { + this.showHideElement(child); + }); + } + + // Show or hide all the context menu items that were merged to another context menu. + if (this.mergedContextMenu) { + if (this.forceHidden || this.allButtonsHidden) { + this.mergedContextMenu.removeMergedItems(); + } else { + this.mergedContextMenu.restoreMergedItems(); + } + } + } + + /** + * Show or hide an element. + * + * @param element Element to show or hide. + */ + protected showHideElement(element: Node): void { + // Check if it's an HTML Element + if (element instanceof Element) { + element.classList.toggle(BUTTON_HIDDEN_CLASS, !!this.forceHidden || !!this.allButtonsHidden); + } + } + + /** + * Component destroyed. + */ + ngOnDestroy(): void { + CoreDomUtils.instance.removeInstanceById(this.instanceId); + + // This component was destroyed, remove all the buttons that were moved. + // The buttons can be moved outside of the current page, that's why we need to manually destroy them. + // There's no need to destroy context menu items that were merged because they weren't moved from their DOM position. + if (this.movedChildren) { + this.movedChildren.forEach((child) => { + if (child.parentElement) { + child.parentElement.removeChild(child); + } + }); + } + + if (this.mergedContextMenu) { + this.mergedContextMenu.removeMergedItems(); + } + } + +} diff --git a/src/core/features/mainmenu/pages/home/home.html b/src/core/features/mainmenu/pages/home/home.html index 71b4ffdad..d3248c03a 100644 --- a/src/core/features/mainmenu/pages/home/home.html +++ b/src/core/features/mainmenu/pages/home/home.html @@ -8,9 +8,15 @@ - - + + + + + + diff --git a/src/core/features/mainmenu/pages/home/home.page.ts b/src/core/features/mainmenu/pages/home/home.page.ts index 4a9d1c418..a8a1887f7 100644 --- a/src/core/features/mainmenu/pages/home/home.page.ts +++ b/src/core/features/mainmenu/pages/home/home.page.ts @@ -12,10 +12,14 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { CoreSites } from '@services/sites'; import { Component, OnInit } from '@angular/core'; +import { NavController } from '@ionic/angular'; import { Subscription } from 'rxjs'; + +import { CoreSites } from '@services/sites'; import { CoreHomeDelegate, CoreHomeHandlerToDisplay } from '../../services/home.delegate'; +import { CoreCourses } from '@features/courses/services/courses'; +import { CoreEventObserver, CoreEvents } from '@singletons/events'; /** * Page that displays the Home. @@ -31,11 +35,16 @@ export class CoreHomePage implements OnInit { tabs: CoreHomeHandlerToDisplay[] = []; loaded = false; selectedTab?: number; + searchEnabled = false; + downloadCourseEnabled = false; + downloadCoursesEnabled = false; protected subscription?: Subscription; + protected updateSiteObserver?: CoreEventObserver; constructor( protected homeDelegate: CoreHomeDelegate, + protected navCtrl: NavController, ) { this.loadSiteName(); } @@ -44,9 +53,22 @@ export class CoreHomePage implements OnInit { * Initialize the component. */ ngOnInit(): void { + this.searchEnabled = !CoreCourses.instance.isSearchCoursesDisabledInSite(); + this.downloadCourseEnabled = !CoreCourses.instance.isDownloadCourseDisabledInSite(); + this.downloadCoursesEnabled = !CoreCourses.instance.isDownloadCoursesDisabledInSite(); + this.subscription = this.homeDelegate.getHandlersObservable().subscribe((handlers) => { handlers && this.initHandlers(handlers); }); + + // Refresh the enabled flags if site is updated. + this.updateSiteObserver = CoreEvents.on(CoreEvents.SITE_UPDATED, () => { + this.searchEnabled = !CoreCourses.instance.isSearchCoursesDisabledInSite(); + this.downloadCourseEnabled = !CoreCourses.instance.isDownloadCourseDisabledInSite(); + this.downloadCoursesEnabled = !CoreCourses.instance.isDownloadCoursesDisabledInSite(); + + this.loadSiteName(); + }, CoreSites.instance.getCurrentSiteId()); } /** @@ -91,4 +113,18 @@ export class CoreHomePage implements OnInit { this.siteName = CoreSites.instance.getCurrentSite()!.getSiteName(); } + /** + * Open page to manage courses storage. + */ + manageCoursesStorage(): void { + // @todo this.navCtrl.navigateForward(['/courses/storage']); + } + + /** + * Go to search courses. + */ + openSearch(): void { + this.navCtrl.navigateForward(['/courses/search']); + } + } diff --git a/src/core/features/settings/pages/space-usage/space-usage.html b/src/core/features/settings/pages/space-usage/space-usage.html index ad08d1c8d..8d9294c40 100644 --- a/src/core/features/settings/pages/space-usage/space-usage.html +++ b/src/core/features/settings/pages/space-usage/space-usage.html @@ -5,10 +5,11 @@ {{ 'core.settings.spaceusage' | translate }} - - - - + + + + + diff --git a/src/core/features/settings/pages/synchronization/synchronization.html b/src/core/features/settings/pages/synchronization/synchronization.html index 587cf2b4a..3ac2cd268 100644 --- a/src/core/features/settings/pages/synchronization/synchronization.html +++ b/src/core/features/settings/pages/synchronization/synchronization.html @@ -5,10 +5,11 @@ {{ 'core.settings.synchronization' | translate }} - - - - + + + + + diff --git a/src/theme/app.scss b/src/theme/app.scss index 45ee03c21..f48f74a42 100644 --- a/src/theme/app.scss +++ b/src/theme/app.scss @@ -10,6 +10,10 @@ ion-toolbar .in-toolbar.button-clear { --color: var(--ion-color-primary-contrast); } +ion-toolbar .core-navbar-button-hidden { + display: none !important; +} + // Ionic icon. ion-icon { &.icon-slash::after,