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 6e6334aab..5089297cf 100644 --- a/src/addons/mod/book/pages/contents/contents.html +++ b/src/addons/mod/book/pages/contents/contents.html @@ -21,9 +21,8 @@ - +
- @@ -31,11 +30,6 @@ - - -
@@ -50,4 +44,9 @@
+ + + diff --git a/src/addons/mod/book/pages/contents/contents.scss b/src/addons/mod/book/pages/contents/contents.scss index de654bcac..823126ed0 100644 --- a/src/addons/mod/book/pages/contents/contents.scss +++ b/src/addons/mod/book/pages/contents/contents.scss @@ -1,6 +1,12 @@ :host { + core-loading ::ng-deep .core-loading-content { + min-height: 100%; + display: flex; + flex-direction: column; + } + .core-swipe-slides-container { - ion-card, core-navigation-bar { + ion-card { flex: none; } } diff --git a/src/addons/mod/book/pages/contents/contents.ts b/src/addons/mod/book/pages/contents/contents.ts index 36af98196..21436d557 100644 --- a/src/addons/mod/book/pages/contents/contents.ts +++ b/src/addons/mod/book/pages/contents/contents.ts @@ -62,7 +62,6 @@ export class AddonModBookContentsPage implements OnInit, OnDestroy { warning = ''; displayNavBar = true; navigationItems: CoreNavigationBarItem[] = []; - displayTitlesInNavBar = false; slidesOpts: CoreSwipeSlidesOptions = { autoHeight: true, scrollOnChange: 'top', @@ -135,7 +134,6 @@ export class AddonModBookContentsPage implements OnInit, OnDestroy { const downloadResult = await this.downloadResourceIfNeeded(module, refresh); this.displayNavBar = book.navstyle != AddonModBookNavStyle.TOC_ONLY; - this.displayTitlesInNavBar = book.navstyle == AddonModBookNavStyle.TEXT; this.title = book.name; // Get contents. No need to refresh, it has been done in downloadResourceIfNeeded. 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 a3ce89920..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 @@ -10,7 +10,7 @@ - + @@ -24,12 +24,14 @@
- -
- + + + 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 73bb4de59..895ae27f0 100644 --- a/src/addons/mod/scorm/pages/player/player.html +++ b/src/addons/mod/scorm/pages/player/player.html @@ -20,13 +20,15 @@ - - - +

{{ 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/classes/tabs.ts b/src/core/classes/tabs.ts index 76527de07..ca644c64e 100644 --- a/src/core/classes/tabs.ts +++ b/src/core/classes/tabs.ts @@ -85,7 +85,8 @@ export class CoreTabsBaseComponent implements OnInit, Aft protected firstSelectedTab?: string; // ID of the first selected tab to control history. protected backButtonFunction: (event: BackButtonEvent) => void; protected languageChangedSubscription?: Subscription; - protected isInTransition = false; // Weather Slides is in transition. + // Swiper 6 documentation: https://swiper6.vercel.app/ + protected isInTransition = false; // Wether Slides is in transition. protected slidesSwiper: any; // eslint-disable-line @typescript-eslint/no-explicit-any protected slidesSwiperLoaded = false; protected scrollElements: Record = {}; // Scroll elements for each loaded tab. diff --git a/src/core/components/navigation-bar/core-navigation-bar.html b/src/core/components/navigation-bar/core-navigation-bar.html index 3f5b6df49..087ffbdf9 100644 --- a/src/core/components/navigation-bar/core-navigation-bar.html +++ b/src/core/components/navigation-bar/core-navigation-bar.html @@ -1,27 +1,17 @@ - - - + + - - + - - -

{{currentIndex + 1}} / {{items.length}}

-
+ + + - - - - + + +
diff --git a/src/core/components/navigation-bar/navigation-bar.scss b/src/core/components/navigation-bar/navigation-bar.scss index b79c274dd..6579ff761 100644 --- a/src/core/components/navigation-bar/navigation-bar.scss +++ b/src/core/components/navigation-bar/navigation-bar.scss @@ -1,15 +1,19 @@ -:host { - --background: var(--core-course-module-navigation-background); +@import "~theme/globals"; +:host { + --height: var(--core-navigation-max-height); + --background: var(--core-navigation-background); + --button-vertical-margin: 2px; + + height: var(--height); width: 100%; background-color: var(--background); display: block; + border-top: 1px solid var(--stroke); - .core-navigation-bar-arrow { - text-transform: none; - max-width: 100%; - ion-icon { - flex-shrink: 0; - } + ion-button, + ::ng-deep ion-button { + margin-top: var(--button-vertical-margin); + margin-bottom: var(--button-vertical-margin); } } diff --git a/src/core/components/navigation-bar/navigation-bar.ts b/src/core/components/navigation-bar/navigation-bar.ts index 92681ea06..c749dfa5f 100644 --- a/src/core/components/navigation-bar/navigation-bar.ts +++ b/src/core/components/navigation-bar/navigation-bar.ts @@ -16,10 +16,10 @@ import { Component, EventEmitter, Input, OnChanges, Output, SimpleChange } from import { Translate } from '@singletons'; /** - * 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: * @@ -32,7 +32,6 @@ import { Translate } from '@singletons'; export class CoreNavigationBarComponent implements OnChanges { @Input() items: CoreNavigationBarItem[] = []; // List of items. - @Input() showTitles = false; // Display titles on buttons. @Input() previousTranslate = 'core.previous'; // Previous translatable text, can admit $a variable. @Input() nextTranslate = 'core.next'; // Next translatable text, can admit $a variable. @Input() component?: string; // Component the bar belongs to. @@ -46,6 +45,8 @@ export class CoreNavigationBarComponent implements OnChanges { previousIndex = -1; // Previous item index. If -1, the previous arrow won't be shown. nextIndex = -1; // Next item index. If -1, the next arrow won't be shown. currentIndex = 0; + progress = 0; + progressText = ''; // Function to call when arrow is clicked. Will receive as a param the item to load. @Output() action: EventEmitter = new EventEmitter(); @@ -63,6 +64,9 @@ export class CoreNavigationBarComponent implements OnChanges { return; } + this.progress = ((this.currentIndex + 1) / this.items.length) * 100; + this.progressText = `${this.currentIndex + 1} / ${this.items.length}`; + this.nextIndex = this.items[this.currentIndex + 1]?.enabled ? this.currentIndex + 1 : -1; if (this.nextIndex >= 0) { this.nextTitle = Translate.instant(this.nextTranslate, { $a: this.items[this.nextIndex].title || '' }); @@ -88,22 +92,6 @@ export class CoreNavigationBarComponent implements OnChanges { this.action.emit(this.items[itemIndex].item); } - /** - * Navigate to an item with the range component. - * - * @param target: Element changed. - */ - navigateOnRange(target: HTMLIonRangeElement): void { - const selectedIndex = target.value as number; // Single value, use number. - if (!this.items[selectedIndex].enabled) { - target.value = this.currentIndex; - - return; - } - - this.navigate(selectedIndex); - } - } export type CoreNavigationBarItem = { diff --git a/src/core/components/progress-bar/core-progress-bar.html b/src/core/components/progress-bar/core-progress-bar.html index dd3335981..dc7dc744e 100644 --- a/src/core/components/progress-bar/core-progress-bar.html +++ b/src/core/components/progress-bar/core-progress-bar.html @@ -4,7 +4,7 @@
{{ a11yText | translate }} - {{ 'core.percentagenumber' | translate: {$a: text} }} + {{ text }}
diff --git a/src/core/components/progress-bar/progress-bar.ts b/src/core/components/progress-bar/progress-bar.ts index 78d74f287..357d948e5 100644 --- a/src/core/components/progress-bar/progress-bar.ts +++ b/src/core/components/progress-bar/progress-bar.ts @@ -86,7 +86,7 @@ export class CoreProgressBarComponent implements OnInit, OnChanges { this.progress = Math.floor(this.progress); if (!this.textSupplied) { - this.text = String(this.progress); + this.text = Translate.instant('core.percentagenumber', { $a: this.progress }); } this.width = DomSanitizer.bypassSecurityTrustStyle(this.progress + '%'); @@ -94,8 +94,7 @@ export class CoreProgressBarComponent implements OnInit, OnChanges { } if (changes.text || changes.progress || changes.a11yText) { - this.progressBarValueText = (this.a11yText ? Translate.instant(this.a11yText) + ' ' : '') + - Translate.instant('core.percentagenumber', { $a: this.text }); + this.progressBarValueText = (this.a11yText ? Translate.instant(this.a11yText) + ' ' : '') + this.text; } } 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/core-course-module-navigation.html b/src/core/features/course/components/module-navigation/core-course-module-navigation.html index 203fc8f2a..3beaf4af2 100644 --- a/src/core/features/course/components/module-navigation/core-course-module-navigation.html +++ b/src/core/features/course/components/module-navigation/core-course-module-navigation.html @@ -1,7 +1,7 @@ - @@ -14,7 +14,7 @@ - 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 767ea4da6..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,20 +1,16 @@ @import "~theme/globals"; :host { - --height: var(--core-course-module-navigation-height, var(--core-course-module-navigation-max-height)); - --background: var(--core-course-module-navigation-background); + --height: var(--core-navigation-max-height); + --background: var(--core-navigation-background); --button-vertical-margin: 2px; height: var(--height); 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); @@ -25,15 +21,6 @@ margin-top: var(--button-vertical-margin); margin-bottom: var(--button-vertical-margin); } - - .core-course-module-navigation-arrow { - min-width: 48px; - } -} - -:host-context(.core-iframe-fullscreen) { - opacity: 0 !important; - height: 0 !important; } :host-context(core-course-format.core-course-format-singleactivity) { 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 3a566c701..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-course-module-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-course-module-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 461393019..8261707ae 100644 --- a/src/theme/theme.base.scss +++ b/src/theme/theme.base.scss @@ -1124,8 +1124,8 @@ ion-fab[core-fab] { } } -ion-content.has-core-course-module-navigation ion-fab { - bottom: calc(var(--core-course-module-navigation-height, 0px) + 10px); +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; @@ -1540,9 +1564,19 @@ body.core-iframe-fullscreen ion-content { .core-swipe-slides-container { display: flex; flex-direction: column; - height: 100%; + flex-grow: 1; + min-height: 100%; core-swipe-slides { + display: flex; + flex-direction: column; flex-grow: 1; + + ion-slides { + flex-grow: 1; + max-width: 100%; + } } + + } diff --git a/src/theme/theme.dark.scss b/src/theme/theme.dark.scss index 6f0743f7c..358d341f1 100644 --- a/src/theme/theme.dark.scss +++ b/src/theme/theme.dark.scss @@ -129,6 +129,10 @@ --core-send-message-input-background: var(--gray-900); --core-send-message-input-color: var(--white); + --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 32b099920..34fa46c45 100644 --- a/src/theme/theme.light.scss +++ b/src/theme/theme.light.scss @@ -310,8 +310,10 @@ --core-courseimage-on-course-size: 72px; --core-courseimage-radius: var(--medium-radius); - --core-course-module-navigation-max-height: 48px; - --core-course-module-navigation-background: var(--contrast-background); + --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;