diff --git a/src/addons/mod/assign/components/index/addon-mod-assign-index.html b/src/addons/mod/assign/components/index/addon-mod-assign-index.html index 1f7633bd4..a11a4b319 100644 --- a/src/addons/mod/assign/components/index/addon-mod-assign-index.html +++ b/src/addons/mod/assign/components/index/addon-mod-assign-index.html @@ -127,6 +127,6 @@ - diff --git a/src/addons/mod/bigbluebuttonbn/components/index/index.html b/src/addons/mod/bigbluebuttonbn/components/index/index.html index 911bc6123..f17bc8bf5 100644 --- a/src/addons/mod/bigbluebuttonbn/components/index/index.html +++ b/src/addons/mod/bigbluebuttonbn/components/index/index.html @@ -112,6 +112,6 @@ - diff --git a/src/addons/mod/book/components/index/addon-mod-book-index.html b/src/addons/mod/book/components/index/addon-mod-book-index.html index 410dd43cc..cdd809546 100644 --- a/src/addons/mod/book/components/index/addon-mod-book-index.html +++ b/src/addons/mod/book/components/index/addon-mod-book-index.html @@ -41,6 +41,6 @@ - diff --git a/src/addons/mod/book/pages/contents/contents.html b/src/addons/mod/book/pages/contents/contents.html index a25501544..5089297cf 100644 --- a/src/addons/mod/book/pages/contents/contents.html +++ b/src/addons/mod/book/pages/contents/contents.html @@ -45,7 +45,7 @@ - diff --git a/src/addons/mod/chat/components/index/addon-mod-chat-index.html b/src/addons/mod/chat/components/index/addon-mod-chat-index.html index 7296cddb5..52c2a5cc4 100644 --- a/src/addons/mod/chat/components/index/addon-mod-chat-index.html +++ b/src/addons/mod/chat/components/index/addon-mod-chat-index.html @@ -30,6 +30,6 @@ - diff --git a/src/addons/mod/choice/components/index/addon-mod-choice-index.html b/src/addons/mod/choice/components/index/addon-mod-choice-index.html index 1eebfa01c..6ef6a30cc 100644 --- a/src/addons/mod/choice/components/index/addon-mod-choice-index.html +++ b/src/addons/mod/choice/components/index/addon-mod-choice-index.html @@ -135,7 +135,7 @@ - diff --git a/src/addons/mod/data/components/index/addon-mod-data-index.html b/src/addons/mod/data/components/index/addon-mod-data-index.html index 6f15f5132..40b51ef5a 100644 --- a/src/addons/mod/data/components/index/addon-mod-data-index.html +++ b/src/addons/mod/data/components/index/addon-mod-data-index.html @@ -120,7 +120,7 @@ - diff --git a/src/addons/mod/feedback/components/index/addon-mod-feedback-index.html b/src/addons/mod/feedback/components/index/addon-mod-feedback-index.html index 3abdff768..4ffa61d9a 100644 --- a/src/addons/mod/feedback/components/index/addon-mod-feedback-index.html +++ b/src/addons/mod/feedback/components/index/addon-mod-feedback-index.html @@ -35,7 +35,7 @@ - diff --git a/src/addons/mod/folder/components/index/addon-mod-folder-index.html b/src/addons/mod/folder/components/index/addon-mod-folder-index.html index 82cb66284..fd09820a8 100644 --- a/src/addons/mod/folder/components/index/addon-mod-folder-index.html +++ b/src/addons/mod/folder/components/index/addon-mod-folder-index.html @@ -32,6 +32,6 @@ - diff --git a/src/addons/mod/forum/components/index/index.html b/src/addons/mod/forum/components/index/index.html index 61ff3501f..e49167d13 100644 --- a/src/addons/mod/forum/components/index/index.html +++ b/src/addons/mod/forum/components/index/index.html @@ -114,7 +114,7 @@ - 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 index c973a95a3..d391f6172 100644 --- a/src/addons/mod/glossary/components/index/addon-mod-glossary-index.html +++ b/src/addons/mod/glossary/components/index/addon-mod-glossary-index.html @@ -72,7 +72,7 @@ - diff --git a/src/addons/mod/h5pactivity/components/index/addon-mod-h5pactivity-index.html b/src/addons/mod/h5pactivity/components/index/addon-mod-h5pactivity-index.html index deed6960a..0ef0f7b2d 100644 --- a/src/addons/mod/h5pactivity/components/index/addon-mod-h5pactivity-index.html +++ b/src/addons/mod/h5pactivity/components/index/addon-mod-h5pactivity-index.html @@ -68,6 +68,6 @@ - diff --git a/src/addons/mod/imscp/components/index/addon-mod-imscp-index.html b/src/addons/mod/imscp/components/index/addon-mod-imscp-index.html index ea00f00ce..608162e22 100644 --- a/src/addons/mod/imscp/components/index/addon-mod-imscp-index.html +++ b/src/addons/mod/imscp/components/index/addon-mod-imscp-index.html @@ -29,8 +29,9 @@ - + - diff --git a/src/addons/mod/lesson/components/index/addon-mod-lesson-index.html b/src/addons/mod/lesson/components/index/addon-mod-lesson-index.html index d79ecd5a1..188c0ca97 100644 --- a/src/addons/mod/lesson/components/index/addon-mod-lesson-index.html +++ b/src/addons/mod/lesson/components/index/addon-mod-lesson-index.html @@ -279,6 +279,6 @@ - diff --git a/src/addons/mod/lti/components/index/addon-mod-lti-index.html b/src/addons/mod/lti/components/index/addon-mod-lti-index.html index 1452e0c18..217ee88b2 100644 --- a/src/addons/mod/lti/components/index/addon-mod-lti-index.html +++ b/src/addons/mod/lti/components/index/addon-mod-lti-index.html @@ -21,6 +21,6 @@ - diff --git a/src/addons/mod/page/components/index/addon-mod-page-index.html b/src/addons/mod/page/components/index/addon-mod-page-index.html index 83536200d..e995cc5a0 100644 --- a/src/addons/mod/page/components/index/addon-mod-page-index.html +++ b/src/addons/mod/page/components/index/addon-mod-page-index.html @@ -32,6 +32,6 @@ - diff --git a/src/addons/mod/quiz/components/index/addon-mod-quiz-index.html b/src/addons/mod/quiz/components/index/addon-mod-quiz-index.html index 59961b31c..5b80f4744 100644 --- a/src/addons/mod/quiz/components/index/addon-mod-quiz-index.html +++ b/src/addons/mod/quiz/components/index/addon-mod-quiz-index.html @@ -206,6 +206,6 @@ - diff --git a/src/addons/mod/resource/components/index/addon-mod-resource-index.html b/src/addons/mod/resource/components/index/addon-mod-resource-index.html index 1efbca3f9..0fd7713b7 100644 --- a/src/addons/mod/resource/components/index/addon-mod-resource-index.html +++ b/src/addons/mod/resource/components/index/addon-mod-resource-index.html @@ -97,6 +97,6 @@ - diff --git a/src/addons/mod/scorm/components/index/addon-mod-scorm-index.html b/src/addons/mod/scorm/components/index/addon-mod-scorm-index.html index 96329ba19..49c22490a 100644 --- a/src/addons/mod/scorm/components/index/addon-mod-scorm-index.html +++ b/src/addons/mod/scorm/components/index/addon-mod-scorm-index.html @@ -217,6 +217,6 @@ - diff --git a/src/addons/mod/scorm/pages/player/player.html b/src/addons/mod/scorm/pages/player/player.html index f50283787..895ae27f0 100644 --- a/src/addons/mod/scorm/pages/player/player.html +++ b/src/addons/mod/scorm/pages/player/player.html @@ -28,6 +28,7 @@

{{ errorMessage | translate }}

- + diff --git a/src/addons/mod/survey/components/index/addon-mod-survey-index.html b/src/addons/mod/survey/components/index/addon-mod-survey-index.html index 8c99699fc..6fd4d8906 100644 --- a/src/addons/mod/survey/components/index/addon-mod-survey-index.html +++ b/src/addons/mod/survey/components/index/addon-mod-survey-index.html @@ -131,6 +131,6 @@ - diff --git a/src/addons/mod/url/components/index/addon-mod-url-index.html b/src/addons/mod/url/components/index/addon-mod-url-index.html index 0153bcdf7..9bd9966eb 100644 --- a/src/addons/mod/url/components/index/addon-mod-url-index.html +++ b/src/addons/mod/url/components/index/addon-mod-url-index.html @@ -46,6 +46,6 @@ - diff --git a/src/addons/mod/wiki/components/index/addon-mod-wiki-index.html b/src/addons/mod/wiki/components/index/addon-mod-wiki-index.html index 342e83895..f7208d4ce 100644 --- a/src/addons/mod/wiki/components/index/addon-mod-wiki-index.html +++ b/src/addons/mod/wiki/components/index/addon-mod-wiki-index.html @@ -71,7 +71,7 @@ - diff --git a/src/addons/mod/workshop/components/index/addon-mod-workshop-index.html b/src/addons/mod/workshop/components/index/addon-mod-workshop-index.html index ebb5b73a3..adc5c72e4 100644 --- a/src/addons/mod/workshop/components/index/addon-mod-workshop-index.html +++ b/src/addons/mod/workshop/components/index/addon-mod-workshop-index.html @@ -233,6 +233,6 @@ - diff --git a/src/core/components/navigation-bar/navigation-bar.scss b/src/core/components/navigation-bar/navigation-bar.scss index 42a6491d7..6579ff761 100644 --- a/src/core/components/navigation-bar/navigation-bar.scss +++ b/src/core/components/navigation-bar/navigation-bar.scss @@ -1,7 +1,7 @@ @import "~theme/globals"; :host { - --height: var(--core-navigation-height, var(--core-navigation-max-height)); + --height: var(--core-navigation-max-height); --background: var(--core-navigation-background); --button-vertical-margin: 2px; @@ -9,20 +9,11 @@ width: 100%; background-color: var(--background); display: block; - bottom: 0; - z-index: 3; border-top: 1px solid var(--stroke); - @include core-transition(all, 200ms); - ion-button, ::ng-deep ion-button { margin-top: var(--button-vertical-margin); margin-bottom: var(--button-vertical-margin); } } - -:host-context(.core-iframe-fullscreen) { - opacity: 0 !important; - height: 0 !important; -} diff --git a/src/core/components/navigation-bar/navigation-bar.ts b/src/core/components/navigation-bar/navigation-bar.ts index 4fa88cfe6..c749dfa5f 100644 --- a/src/core/components/navigation-bar/navigation-bar.ts +++ b/src/core/components/navigation-bar/navigation-bar.ts @@ -12,18 +12,14 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { Component, ElementRef, EventEmitter, Input, OnChanges, OnDestroy, Output, SimpleChange } from '@angular/core'; -import { IonContent } from '@ionic/angular'; -import { ScrollDetail } from '@ionic/core'; -import { CoreUtils } from '@services/utils/utils'; +import { Component, EventEmitter, Input, OnChanges, Output, SimpleChange } from '@angular/core'; import { Translate } from '@singletons'; -import { CoreMath } from '@singletons/math'; /** - * Component to show a "bar" with arrows to navigate forward/backward and an slider to move around. + * Component to show a "bar" with arrows to navigate forward/backward and an progressbar to see the status. * * This directive will show two arrows at the left and right of the screen to navigate to previous/next item when clicked. - * If no previous/next item is defined, that arrow won't be shown. + * If no previous/next item is defined, that arrow will be disabled. * * Example usage: * @@ -33,7 +29,7 @@ import { CoreMath } from '@singletons/math'; templateUrl: 'core-navigation-bar.html', styleUrls: ['navigation-bar.scss'], }) -export class CoreNavigationBarComponent implements OnDestroy, OnChanges { +export class CoreNavigationBarComponent implements OnChanges { @Input() items: CoreNavigationBarItem[] = []; // List of items. @Input() previousTranslate = 'core.previous'; // Previous translatable text, can admit $a variable. @@ -52,118 +48,9 @@ export class CoreNavigationBarComponent implements OnDestroy, OnChanges { progress = 0; progressText = ''; - protected element: HTMLElement; - protected initialHeight = 0; - protected initialPaddingBottom = 0; - protected previousTop = 0; - protected previousHeight = 0; - protected stickTimeout?: number; - protected content?: HTMLIonContentElement | null; - // Function to call when arrow is clicked. Will receive as a param the item to load. @Output() action: EventEmitter = new EventEmitter(); - constructor(el: ElementRef, protected ionContent: IonContent) { - this.element = el.nativeElement; - this.element.setAttribute('slot', 'fixed'); // Just in case somebody forgets to add it. - } - - /** - * Setup scroll event listener. - * - * @param retries Number of retries left. - */ - protected async listenScrollEvents(retries = 3): Promise { - // Already initialized. - if (this.initialHeight > 0) { - return; - } - - this.initialHeight = this.element.getBoundingClientRect().height; - - if (this.initialHeight == 0 && retries > 0) { - await CoreUtils.nextTicks(50); - - this.listenScrollEvents(retries - 1); - - return; - } - // Set a minimum height value. - this.initialHeight = this.initialHeight || 48; - this.previousHeight = this.initialHeight; - - this.content = this.element.closest('ion-content'); - - if (!this.content) { - return; - } - - this.content.classList.add('has-core-navigation'); - - // Move element to the nearest ion-content if it's not the parent. - if (this.element.parentElement?.nodeName != 'ION-CONTENT') { - this.content.appendChild(this.element); - } - - // Set a padding to not overlap elements. - this.initialPaddingBottom = parseFloat(this.content.style.getPropertyValue('--padding-bottom') || '0'); - this.content.style.setProperty('--padding-bottom', this.initialPaddingBottom + this.initialHeight + 'px'); - const scroll = await this.content.getScrollElement(); - this.content.scrollEvents = true; - - this.setBarHeight(this.initialHeight); - this.content.addEventListener('ionScroll', (e: CustomEvent): void => { - if (!this.content) { - return; - } - - this.onScroll(e.detail, scroll); - }); - - } - - /** - * On scroll function. - * - * @param scrollDetail Scroll detail object. - * @param scrollElement Scroll element to calculate maxScroll. - */ - protected onScroll(scrollDetail: ScrollDetail, scrollElement: HTMLElement): void { - const maxScroll = scrollElement.scrollHeight - scrollElement.offsetHeight; - if (scrollDetail.scrollTop <= 0 || scrollDetail.scrollTop >= maxScroll) { - // Reset. - this.setBarHeight(this.initialHeight); - } else { - let newHeight = this.previousHeight - (scrollDetail.scrollTop - this.previousTop); - newHeight = CoreMath.clamp(newHeight, 0, this.initialHeight); - - this.setBarHeight(newHeight); - } - this.previousTop = scrollDetail.scrollTop; - } - - /** - * Sets the bar height. - * - * @param height The new bar height. - */ - protected setBarHeight(height: number): void { - if (this.stickTimeout) { - clearTimeout(this.stickTimeout); - } - - this.element.style.opacity = height <= 0 ? '0' : '1'; - this.content?.style.setProperty('--core-navigation-height', height + 'px'); - this.previousHeight = height; - - if (height > 0 && height < this.initialHeight) { - // Finish opening or closing the bar. - const newHeight = height < this.initialHeight / 2 ? 0 : this.initialHeight; - - this.stickTimeout = window.setTimeout(() => this.setBarHeight(newHeight), 500); - } - } - /** * @inheritdoc */ @@ -189,8 +76,6 @@ export class CoreNavigationBarComponent implements OnDestroy, OnChanges { if (this.previousIndex >= 0) { this.previousTitle = Translate.instant(this.previousTranslate, { $a: this.items[this.previousIndex].title || '' }); } - - this.listenScrollEvents(); } /** @@ -207,13 +92,6 @@ export class CoreNavigationBarComponent implements OnDestroy, OnChanges { this.action.emit(this.items[itemIndex].item); } - /** - * @inheritdoc - */ - async ngOnDestroy(): Promise { - this.content?.style.setProperty('--padding-bottom', this.initialPaddingBottom + 'px'); - } - } export type CoreNavigationBarItem = { diff --git a/src/core/directives/collapsible-footer.ts b/src/core/directives/collapsible-footer.ts new file mode 100644 index 000000000..71fd0b7fd --- /dev/null +++ b/src/core/directives/collapsible-footer.ts @@ -0,0 +1,158 @@ +// (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 { Directive, ElementRef, OnDestroy, OnInit } from '@angular/core'; +import { ScrollDetail } from '@ionic/core'; +import { IonContent } from '@ionic/angular'; +import { CoreUtils } from '@services/utils/utils'; +import { CoreMath } from '@singletons/math'; + +/** + * Directive to make an element fixed at the bottom collapsible when scrolling. + * + * Example usage: + * + *
+ */ +@Directive({ + selector: '[collapsible-footer]', +}) +export class CoreCollapsibleFooterDirective implements OnInit, OnDestroy { + + protected element: HTMLElement; + protected initialHeight = 0; + protected initialPaddingBottom = 0; + protected previousTop = 0; + protected previousHeight = 0; + protected stickTimeout?: number; + protected content?: HTMLIonContentElement | null; + + constructor(el: ElementRef, protected ionContent: IonContent) { + this.element = el.nativeElement; + this.element.setAttribute('slot', 'fixed'); // Just in case somebody forgets to add it. + } + + /** + * Setup scroll event listener. + * + * @param retries Number of retries left. + */ + protected async listenScrollEvents(retries = 5): Promise { + // Already initialized. + if (this.initialHeight > 0) { + return; + } + + this.initialHeight = this.element.getBoundingClientRect().height; + + if (this.initialHeight == 0 && retries > 0) { + await CoreUtils.nextTicks(50); + + this.listenScrollEvents(retries - 1); + + return; + } + + // Set a minimum height value. + this.initialHeight = this.initialHeight || 48; + this.previousHeight = this.initialHeight; + + this.content = this.element.closest('ion-content'); + + if (!this.content) { + return; + } + + this.content.classList.add('has-collapsible-footer'); + + // Move element to the nearest ion-content if it's not the parent. + if (this.element.parentElement?.nodeName != 'ION-CONTENT') { + this.content.appendChild(this.element); + } + + // Set a padding to not overlap elements. + this.initialPaddingBottom = parseFloat(this.content.style.getPropertyValue('--padding-bottom') || '0'); + this.content.style.setProperty('--padding-bottom', this.initialPaddingBottom + this.initialHeight + 'px'); + const scroll = await this.content.getScrollElement(); + this.content.scrollEvents = true; + + this.setBarHeight(this.initialHeight); + this.content.addEventListener('ionScroll', (e: CustomEvent): void => { + if (!this.content) { + return; + } + + this.onScroll(e.detail, scroll); + }); + + } + + /** + * On scroll function. + * + * @param scrollDetail Scroll detail object. + * @param scrollElement Scroll element to calculate maxScroll. + */ + protected onScroll(scrollDetail: ScrollDetail, scrollElement: HTMLElement): void { + const maxScroll = scrollElement.scrollHeight - scrollElement.offsetHeight; + if (scrollDetail.scrollTop <= 0 || scrollDetail.scrollTop >= maxScroll) { + // Reset. + this.setBarHeight(this.initialHeight); + } else { + let newHeight = this.previousHeight - (scrollDetail.scrollTop - this.previousTop); + newHeight = CoreMath.clamp(newHeight, 0, this.initialHeight); + + this.setBarHeight(newHeight); + } + this.previousTop = scrollDetail.scrollTop; + } + + /** + * Sets the bar height. + * + * @param height The new bar height. + */ + protected setBarHeight(height: number): void { + if (this.stickTimeout) { + clearTimeout(this.stickTimeout); + } + + this.element.classList.toggle('footer-collapsed', height <= 0); + this.element.classList.toggle('footer-expanded', height >= this.initialHeight); + this.content?.style.setProperty('--core-collapsible-footer-height', height + 'px'); + this.previousHeight = height; + + if (height > 0 && height < this.initialHeight) { + // Finish opening or closing the bar. + const newHeight = height < this.initialHeight / 2 ? 0 : this.initialHeight; + + this.stickTimeout = window.setTimeout(() => this.setBarHeight(newHeight), 500); + } + } + + /** + * @inheritdoc + */ + ngOnInit(): void { + this.listenScrollEvents(); + } + + /** + * @inheritdoc + */ + async ngOnDestroy(): Promise { + this.content?.style.setProperty('--padding-bottom', this.initialPaddingBottom + 'px'); + } + +} diff --git a/src/core/directives/directives.module.ts b/src/core/directives/directives.module.ts index 42a2b3c79..9f518532f 100644 --- a/src/core/directives/directives.module.ts +++ b/src/core/directives/directives.module.ts @@ -30,6 +30,7 @@ import { CoreDownloadFileDirective } from './download-file'; import { CoreCollapsibleHeaderDirective } from './collapsible-header'; import { CoreSwipeNavigationDirective } from './swipe-navigation'; import { CoreCollapsibleItemDirective } from './collapsible-item'; +import { CoreCollapsibleFooterDirective } from './collapsible-footer'; @NgModule({ declarations: [ @@ -49,6 +50,7 @@ import { CoreCollapsibleItemDirective } from './collapsible-item'; CoreCollapsibleHeaderDirective, CoreSwipeNavigationDirective, CoreCollapsibleItemDirective, + CoreCollapsibleFooterDirective, ], exports: [ CoreAutoFocusDirective, @@ -67,6 +69,7 @@ import { CoreCollapsibleItemDirective } from './collapsible-item'; CoreCollapsibleHeaderDirective, CoreSwipeNavigationDirective, CoreCollapsibleItemDirective, + CoreCollapsibleFooterDirective, ], }) export class CoreDirectivesModule {} diff --git a/src/core/features/course/components/module-navigation/module-navigation.scss b/src/core/features/course/components/module-navigation/module-navigation.scss index 48713703f..7ae45b850 100644 --- a/src/core/features/course/components/module-navigation/module-navigation.scss +++ b/src/core/features/course/components/module-navigation/module-navigation.scss @@ -1,7 +1,7 @@ @import "~theme/globals"; :host { - --height: var(--core-navigation-height, var(--core-navigation-max-height)); + --height: var(--core-navigation-max-height); --background: var(--core-navigation-background); --button-vertical-margin: 2px; @@ -9,12 +9,8 @@ width: 100%; background-color: var(--background); display: block; - bottom: 0; - z-index: 3; border-top: 1px solid var(--stroke); - @include core-transition(all, 200ms); - core-loading { text-align: center; --loading-inline-min-height: var(--height); @@ -27,11 +23,6 @@ } } -:host-context(.core-iframe-fullscreen) { - opacity: 0 !important; - height: 0 !important; -} - :host-context(core-course-format.core-course-format-singleactivity) { opacity: 0 !important; height: 0 !important; diff --git a/src/core/features/course/components/module-navigation/module-navigation.ts b/src/core/features/course/components/module-navigation/module-navigation.ts index 5b6a4062f..af20ac47e 100644 --- a/src/core/features/course/components/module-navigation/module-navigation.ts +++ b/src/core/features/course/components/module-navigation/module-navigation.ts @@ -17,13 +17,11 @@ import { CoreCourse, CoreCourseProvider, CoreCourseWSSection } from '@features/c import { CoreCourseModuleCompletionData, CoreCourseModuleData } from '@features/course/services/course-helper'; import { CoreCourseModuleDelegate } from '@features/course/services/module-delegate'; import { IonContent } from '@ionic/angular'; -import { ScrollDetail } from '@ionic/core'; import { CoreNavigationOptions, CoreNavigator } from '@services/navigator'; import { CoreSites, CoreSitesReadingStrategy } from '@services/sites'; import { CoreDomUtils } from '@services/utils/dom'; import { CoreUtils } from '@services/utils/utils'; import { CoreEventObserver, CoreEvents } from '@singletons/events'; -import { CoreMath } from '@singletons/math'; /** * Component to show a button to go to the next resource/activity. @@ -51,21 +49,11 @@ export class CoreCourseModuleNavigationComponent implements OnInit, OnDestroy { loaded = false; showCompletion = false; // Whether to show completion. - protected element: HTMLElement; - protected initialHeight = 0; - protected initialPaddingBottom = 0; - protected previousTop = 0; - protected previousHeight = 0; - protected stickTimeout?: number; - protected content?: HTMLIonContentElement | null; protected completionObserver: CoreEventObserver; constructor(el: ElementRef, protected ionContent: IonContent) { const siteId = CoreSites.getCurrentSiteId(); - this.element = el.nativeElement; - this.element.setAttribute('slot', 'fixed'); - this.completionObserver = CoreEvents.on(CoreEvents.COMPLETION_MODULE_VIEWED, async (data) => { if (data && data.courseId == this.courseId) { // Check if now there's a next module. @@ -88,76 +76,14 @@ export class CoreCourseModuleNavigationComponent implements OnInit, OnDestroy { await this.setNextAndPreviousModules(CoreSitesReadingStrategy.PREFER_CACHE); } finally { this.loaded = true; - - await CoreUtils.nextTicks(50); - this.listenScrollEvents(); } } - /** - * Setup scroll event listener. - * - * @param retries Number of retries left. - */ - protected async listenScrollEvents(retries = 3): Promise { - this.initialHeight = this.element.getBoundingClientRect().height; - - if (this.initialHeight == 0 && retries > 0) { - await CoreUtils.nextTicks(50); - - this.listenScrollEvents(retries - 1); - - return; - } - // Set a minimum height value. - this.initialHeight = this.initialHeight || 48; - this.previousHeight = this.initialHeight; - - this.content = this.element.closest('ion-content'); - - if (!this.content) { - return; - } - - // Special case where there's no navigation. - const courseFormat = this.element.closest('core-course-format.core-course-format-singleactivity'); - if (courseFormat) { - this.element.remove(); - this.ngOnDestroy(); - - return; - } - - this.content.classList.add('has-core-navigation'); - - // Move element to the nearest ion-content if it's not the parent. - if (this.element.parentElement?.nodeName != 'ION-CONTENT') { - this.content.appendChild(this.element); - } - - // Set a padding to not overlap elements. - this.initialPaddingBottom = parseFloat(this.content.style.getPropertyValue('--padding-bottom') || '0'); - this.content.style.setProperty('--padding-bottom', this.initialPaddingBottom + this.initialHeight + 'px'); - const scroll = await this.content.getScrollElement(); - this.content.scrollEvents = true; - - this.setBarHeight(this.initialHeight); - this.content.addEventListener('ionScroll', (e: CustomEvent): void => { - if (!this.content) { - return; - } - - this.onScroll(e.detail, scroll); - }); - - } - /** * @inheritdoc */ async ngOnDestroy(): Promise { this.completionObserver.off(); - this.content?.style.setProperty('--padding-bottom', this.initialPaddingBottom + 'px'); } /** @@ -316,46 +242,4 @@ export class CoreCourseModuleNavigationComponent implements OnInit, OnDestroy { } } - /** - * On scroll function. - * - * @param scrollDetail Scroll detail object. - * @param scrollElement Scroll element to calculate maxScroll. - */ - protected onScroll(scrollDetail: ScrollDetail, scrollElement: HTMLElement): void { - const maxScroll = scrollElement.scrollHeight - scrollElement.offsetHeight; - if (scrollDetail.scrollTop <= 0 || scrollDetail.scrollTop >= maxScroll) { - // Reset. - this.setBarHeight(this.initialHeight); - } else { - let newHeight = this.previousHeight - (scrollDetail.scrollTop - this.previousTop); - newHeight = CoreMath.clamp(newHeight, 0, this.initialHeight); - - this.setBarHeight(newHeight); - } - this.previousTop = scrollDetail.scrollTop; - } - - /** - * Sets the bar height. - * - * @param height The new bar height. - */ - protected setBarHeight(height: number): void { - if (this.stickTimeout) { - clearTimeout(this.stickTimeout); - } - - this.element.style.opacity = height <= 0 ? '0' : '1'; - this.content?.style.setProperty('--core-navigation-height', height + 'px'); - this.previousHeight = height; - - if (height > 0 && height < this.initialHeight) { - // Finish opening or closing the bar. - const newHeight = height < this.initialHeight / 2 ? 0 : this.initialHeight; - - this.stickTimeout = window.setTimeout(() => this.setBarHeight(newHeight), 500); - } - } - } diff --git a/src/core/features/course/pages/module-preview/module-preview.html b/src/core/features/course/pages/module-preview/module-preview.html index 010351d87..60efcbee6 100644 --- a/src/core/features/course/pages/module-preview/module-preview.html +++ b/src/core/features/course/pages/module-preview/module-preview.html @@ -44,6 +44,6 @@ - diff --git a/src/core/features/siteplugins/components/module-index/core-siteplugins-module-index.html b/src/core/features/siteplugins/components/module-index/core-siteplugins-module-index.html index 94574fcbe..b47edea6c 100644 --- a/src/core/features/siteplugins/components/module-index/core-siteplugins-module-index.html +++ b/src/core/features/siteplugins/components/module-index/core-siteplugins-module-index.html @@ -14,5 +14,5 @@ (onLoadingContent)="contentLoading()"> - + diff --git a/src/theme/theme.base.scss b/src/theme/theme.base.scss index 822b5b991..8261707ae 100644 --- a/src/theme/theme.base.scss +++ b/src/theme/theme.base.scss @@ -1124,7 +1124,7 @@ ion-fab[core-fab] { } } -ion-content.has-core-navigation ion-fab { +ion-content.has-collapsible-footer ion-fab { bottom: calc(var(--core-navigation-height, 0px) + 10px); @include core-transition(all, 200ms); } @@ -1442,6 +1442,30 @@ ion-grid.core-no-grid > ion-row { @include collapsible-item(); } +[collapsible-footer] { + &.footer-collapsed { + --core-collapsible-footer-height: 0; + opacity: 0; + } + &.footer-expanded { + --core-collapsible-footer-height: auto; + } + + width: 100%; + bottom: 0; + z-index: 3; + height: var(--core-collapsible-footer-height, auto); + background-color: var(--core-collapsible-footer-background); + display: block; + border-top: 1px solid var(--stroke); + @include core-transition(all, 200ms); +} + +.core-iframe-fullscreen [collapsible-footer] { + opacity: 0 !important; + height: 0 !important; +} + ion-header.no-title { --core-header-toolbar-border-width: 0; --core-header-toolbar-background: transparent; diff --git a/src/theme/theme.dark.scss b/src/theme/theme.dark.scss index b5be2494e..358d341f1 100644 --- a/src/theme/theme.dark.scss +++ b/src/theme/theme.dark.scss @@ -131,6 +131,8 @@ --core-navigation-background: var(--contrast-background); + --core-collapsible-footer-background: var(--contrast-background); + --addon-messages-message-bg: var(--gray-800); --addon-messages-message-activated-bg: var(--gray-700); --addon-messages-message-note-text: var(--subdued-text-color); diff --git a/src/theme/theme.light.scss b/src/theme/theme.light.scss index 58a0cc3a5..34fa46c45 100644 --- a/src/theme/theme.light.scss +++ b/src/theme/theme.light.scss @@ -310,9 +310,11 @@ --core-courseimage-on-course-size: 72px; --core-courseimage-radius: var(--medium-radius); - --core-navigation-max-height: 48px; + --core-navigation-height: 48px; --core-navigation-background: var(--contrast-background); + --core-collapsible-footer-background: var(--contrast-background); + --core-user-menu-site-logo-max-height: 32px; --addon-calendar-today-border-color: var(--primary);