From 8a3acc2dfdcd21ea43b6b8b86a8c65f70281d485 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= Date: Fri, 9 Aug 2019 13:16:02 +0200 Subject: [PATCH] MOBILE-3025 blocks: Merge blocks and content scroll --- src/app/app.scss | 1 + .../rich-text-editor/rich-text-editor.ts | 2 +- src/components/tabs/tab.ts | 18 ++- src/core/block/components/block/block.scss | 1 + .../core-block-course-blocks.html | 22 ++-- .../course-blocks/course-blocks.scss | 74 ++++++------ .../components/course-blocks/course-blocks.ts | 89 ++++++++++++-- .../components/format/core-course-format.html | 112 +++++++++--------- .../components/index/core-sitehome-index.html | 48 ++++---- src/providers/utils/dom.ts | 39 ++++++ src/theme/format-text.scss | 4 + 11 files changed, 258 insertions(+), 152 deletions(-) diff --git a/src/app/app.scss b/src/app/app.scss index 2d0a1baa0..d11387ef8 100644 --- a/src/app/app.scss +++ b/src/app/app.scss @@ -226,6 +226,7 @@ ion-app.app-root { user-select: text; word-break: break-word; word-wrap: break-word; + white-space: normal; &[maxHeight], &[ng-reflect-max-height] { diff --git a/src/components/rich-text-editor/rich-text-editor.ts b/src/components/rich-text-editor/rich-text-editor.ts index b0261ba75..08bf278b3 100644 --- a/src/components/rich-text-editor/rich-text-editor.ts +++ b/src/components/rich-text-editor/rich-text-editor.ts @@ -103,7 +103,7 @@ export class CoreRichTextEditorComponent implements AfterContentInit, OnDestroy } /** - * Init editor + * Init editor. */ ngAfterContentInit(): void { this.domUtils.isRichTextEditorEnabled().then((enabled) => { diff --git a/src/components/tabs/tab.ts b/src/components/tabs/tab.ts index 48b8a3dcb..9006b3011 100644 --- a/src/components/tabs/tab.ts +++ b/src/components/tabs/tab.ts @@ -121,18 +121,14 @@ export class CoreTabComponent implements OnInit, OnDestroy { this.showHideNavBarButtons(true); // Setup tab scrolling. - setTimeout(() => { - // Workaround to solve undefined this.scroll on tab change. - const scroll: HTMLElement = this.content ? this.content.getScrollElement() : - this.element.querySelector('ion-content > .scroll-content'); + this.domUtils.waitElementToExist(() => this.content ? this.content.getScrollElement() : + this.element.querySelector('ion-content > .scroll-content')).then((scroll) => { + scroll.addEventListener('scroll', (e): void => { + this.tabs.showHideTabs(e.target); + }); - if (scroll) { - scroll.onscroll = (e): void => { - this.tabs.showHideTabs(e.target); - }; - this.tabs.showHideTabs(scroll); - } - }, 1); + this.tabs.showHideTabs(scroll); + }); } /** diff --git a/src/core/block/components/block/block.scss b/src/core/block/components/block/block.scss index 765a266a8..4029d7ffa 100644 --- a/src/core/block/components/block/block.scss +++ b/src/core/block/components/block/block.scss @@ -1,5 +1,6 @@ ion-app.app-root core-block { position: relative; + display: block; core-loading.core-loading-center { display: block; diff --git a/src/core/block/components/course-blocks/core-block-course-blocks.html b/src/core/block/components/course-blocks/core-block-course-blocks.html index be7bb0c1d..ee6af5a26 100644 --- a/src/core/block/components/course-blocks/core-block-course-blocks.html +++ b/src/core/block/components/course-blocks/core-block-course-blocks.html @@ -2,13 +2,15 @@ - - - - - - - - - - +
+
+ + + + + + + + +
+
diff --git a/src/core/block/components/course-blocks/course-blocks.scss b/src/core/block/components/course-blocks/course-blocks.scss index e9f9e228a..cc2c9f3c0 100644 --- a/src/core/block/components/course-blocks/course-blocks.scss +++ b/src/core/block/components/course-blocks/course-blocks.scss @@ -1,8 +1,5 @@ -$core-side-blocks-max-small-width: 300px; -$core-side-blocks-min-small-width: 25%; - -$core-side-blocks-max-width: 320px; -$core-side-blocks-min-width: 30%; +$core-side-blocks-max-width: 30%; +$core-side-blocks-min-width: 280px; .core-course-block-with-blocks > .scroll-content { overflow-y: visible; @@ -10,15 +7,8 @@ $core-side-blocks-min-width: 30%; ion-app.app-root core-block-course-blocks { - &.core-no-blocks { - .core-course-blocks-content > ion-content { - height: auto; - - > .scroll-content { - overflow-y: visible; - position: relative; - } - } + &.core-no-blocks .core-course-blocks-content { + height: auto; } &.core-has-blocks { @@ -33,49 +23,51 @@ ion-app.app-root core-block-course-blocks { flex-wrap: nowrap; .core-course-blocks-content { - min-width: calc(100% - #{($core-side-blocks-max-small-width)}); - max-width: calc(100% - #{($core-side-blocks-min-small-width)}); - z-index: 0; - flex: 1; box-shadow: none !important; + flex-grow: 1; + max-width: 100%; } - ion-content.core-course-blocks-side { - transform: none !important; - position: sticky; - @include position(0, 0, 0, auto); - z-index: 30; - max-width: $core-side-blocks-max-small-width; - min-width: $core-side-blocks-min-small-width; - @include border-start(1px, solid, $list-md-border-color); - } - } - - @include media-breakpoint-up(lg) { - .core-course-blocks-content { - min-width: calc(100% - #{($core-side-blocks-max-width)}); - max-width: calc(100% - #{($core-side-blocks-min-width)}); - } - - ion-content.core-course-blocks-side { + div.core-course-blocks-side { + position: relative; + @include position(auto, 0, auto, auto); max-width: $core-side-blocks-max-width; min-width: $core-side-blocks-min-width; + @include border-start(1px, solid, $list-md-border-color); + + .core-course-blocks-side-scroll { + position: absolute; + top: 0; + max-width: 100%; + min-width: 100%; + + &.core-course-blocks-fixed-bottom { + position: fixed; + bottom: 0; + top: auto; + transform: none !important; + } + + core-block { + max-width: $core-side-blocks-max-width; + min-width: $core-side-blocks-min-width; + } + } } } @include media-breakpoint-down(sm) { // Disable scroll on individual columns. - .core-course-blocks-content > ion-content, - ion-content.core-course-blocks-side { + div.core-course-blocks-side { + transform: none !important; height: auto; &.core-hide-blocks { display: none; } - > .scroll-content { - overflow-y: visible; - position: relative; + .core-course-blocks-side-scroll { + transform: none !important; } } } diff --git a/src/core/block/components/course-blocks/course-blocks.ts b/src/core/block/components/course-blocks/course-blocks.ts index 744dd4326..574dc3c8b 100644 --- a/src/core/block/components/course-blocks/course-blocks.ts +++ b/src/core/block/components/course-blocks/course-blocks.ts @@ -12,9 +12,10 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { Component, ViewChildren, Input, OnInit, QueryList, ElementRef, Optional } from '@angular/core'; +import { Component, ViewChildren, Input, OnInit, QueryList, ElementRef, OnDestroy } from '@angular/core'; import { Content } from 'ionic-angular'; import { CoreDomUtilsProvider } from '@providers/utils/dom'; +import { CoreAppProvider } from '@providers/app'; import { CoreCourseProvider } from '@core/course/providers/course'; import { CoreBlockComponent } from '../block/block'; import { CoreBlockHelperProvider } from '../../providers/helper'; @@ -26,7 +27,7 @@ import { CoreBlockHelperProvider } from '../../providers/helper'; selector: 'core-block-course-blocks', templateUrl: 'core-block-course-blocks.html', }) -export class CoreBlockCourseBlocksComponent implements OnInit { +export class CoreBlockCourseBlocksComponent implements OnInit, OnDestroy { @Input() courseId: number; @Input() hideBlocks = false; @@ -38,13 +39,17 @@ export class CoreBlockCourseBlocksComponent implements OnInit { blocks = []; protected element: HTMLElement; - protected parentContent: HTMLElement; + protected lastScroll; + protected translationY = 0; + protected blocksScrollHeight = 0; + protected sideScroll: HTMLElement; + protected vpHeight = 0; // Viewport height. + protected scrollWorking = false; constructor(private domUtils: CoreDomUtilsProvider, private courseProvider: CoreCourseProvider, protected blockHelper: CoreBlockHelperProvider, element: ElementRef, - @Optional() content: Content) { + protected content: Content, protected appProvider: CoreAppProvider) { this.element = element.nativeElement; - this.parentContent = content.getElementRef().nativeElement; } /** @@ -53,9 +58,77 @@ export class CoreBlockCourseBlocksComponent implements OnInit { ngOnInit(): void { this.loadContent().finally(() => { this.dataLoaded = true; + + window.addEventListener('resize', this.initScroll.bind(this)); }); } + /** + * Setup scrolling. + */ + protected initScroll(): void { + const scroll: HTMLElement = this.content && this.content.getScrollElement(); + + this.domUtils.waitElementToExist(() => scroll && scroll.querySelector('.core-course-blocks-side')).then((sideElement) => { + const contentHeight = parseInt(this.content.getNativeElement().querySelector('.scroll-content').scrollHeight, 10); + + this.sideScroll = scroll.querySelector('.core-course-blocks-side-scroll'); + this.blocksScrollHeight = this.sideScroll.scrollHeight; + this.vpHeight = sideElement.clientHeight; + + // Check if needed and event was not init before. + if (this.appProvider.isWide() && this.vpHeight && contentHeight > this.vpHeight && + this.blocksScrollHeight > this.vpHeight) { + if (typeof this.lastScroll == 'undefined') { + this.lastScroll = 0; + scroll.addEventListener('scroll', this.scrollFunction.bind(this)); + } + this.scrollWorking = true; + } else { + this.sideScroll.style.transform = 'translate(0, 0)'; + this.sideScroll.classList.remove('core-course-blocks-fixed-bottom'); + this.scrollWorking = false; + } + }); + } + + /** + * Scroll function that moves the sidebar if needed. + * + * @param {Event} e Event to get the target from. + */ + protected scrollFunction(e: Event): void { + if (!this.scrollWorking) { + return; + } + + const target: any = e.target, + top = parseInt(target.scrollTop, 10), + goingUp = top < this.lastScroll; + if (goingUp) { + this.sideScroll.classList.remove('core-course-blocks-fixed-bottom'); + if (top <= this.translationY ) { + // Fixed to top. + this.translationY = top; + this.sideScroll.style.transform = 'translate(0, ' + this.translationY + 'px)'; + } + } else if (top - this.translationY >= (this.blocksScrollHeight - this.vpHeight)) { + // Fixed to bottom. + this.sideScroll.classList.add('core-course-blocks-fixed-bottom'); + this.translationY = top - (this.blocksScrollHeight - this.vpHeight); + this.sideScroll.style.transform = 'translate(0, ' + this.translationY + 'px)'; + } + + this.lastScroll = top; + } + + /** + * Component destroyed. + */ + ngOnDestroy(): void { + window.removeEventListener('resize', this.initScroll); + } + /** * Invalidate blocks data. * @@ -95,11 +168,13 @@ export class CoreBlockCourseBlocksComponent implements OnInit { this.element.classList.add('core-has-blocks'); this.element.classList.remove('core-no-blocks'); - this.parentContent.classList.add('core-course-block-with-blocks'); + this.content.getElementRef().nativeElement.classList.add('core-course-block-with-blocks'); + + this.initScroll(); } else { this.element.classList.remove('core-has-blocks'); this.element.classList.add('core-no-blocks'); - this.parentContent.classList.remove('core-course-block-with-blocks'); + this.content.getElementRef().nativeElement.classList.remove('core-course-block-with-blocks'); } }); } diff --git a/src/core/course/components/format/core-course-format.html b/src/core/course/components/format/core-course-format.html index 028dc2785..2d01ef5a1 100644 --- a/src/core/course/components/format/core-course-format.html +++ b/src/core/course/components/format/core-course-format.html @@ -6,69 +6,67 @@ - - - - - - -
- - - + + + + + +
+ + + +
+
+ + + + +
+
+ + + +
+
+ + +
+ + + +
- - - -
- -
- - - -
-
- - -
- - - - -
- - -
- - - - - + +
+ + + + - + + - -
- - - - - + +
+
+ + + + -
- + diff --git a/src/core/sitehome/components/index/core-sitehome-index.html b/src/core/sitehome/components/index/core-sitehome-index.html index 389a968ac..7bc8292fe 100644 --- a/src/core/sitehome/components/index/core-sitehome-index.html +++ b/src/core/sitehome/components/index/core-sitehome-index.html @@ -1,30 +1,28 @@ - - - - - - - - + + + + + + + - + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - + + diff --git a/src/providers/utils/dom.ts b/src/providers/utils/dom.ts index e0a99c94c..8776024e7 100644 --- a/src/providers/utils/dom.ts +++ b/src/providers/utils/dom.ts @@ -702,6 +702,45 @@ export class CoreDomUtilsProvider { return this.instances[id]; } + /** + * Wait an element to exists using the findFunction. + * + * @param {Function} findFunction The function used to find the element. + * @return {Promise} Resolved if found, rejected if too many tries. + */ + waitElementToExist(findFunction: Function): Promise { + const promiseInterval = { + promise: null, + resolve: null, + reject: null + }; + + let tries = 100; + + promiseInterval.promise = new Promise((resolve, reject): void => { + promiseInterval.resolve = resolve; + promiseInterval.reject = reject; + }); + + const clear = setInterval(() => { + const element: HTMLElement = findFunction(); + + if (element) { + clearInterval(clear); + promiseInterval.resolve(element); + } else { + tries--; + + if (tries <= 0) { + clearInterval(clear); + promiseInterval.reject(); + } + } + }, 100); + + return promiseInterval.promise; + } + /** * Handle bootstrap tooltips in a certain element. * diff --git a/src/theme/format-text.scss b/src/theme/format-text.scss index d14efd148..a14ee433e 100644 --- a/src/theme/format-text.scss +++ b/src/theme/format-text.scss @@ -9,6 +9,10 @@ ion-app.app-root core-rich-text-editor .core-rte-editor { margin-bottom: 1rem; } + .no-overflow { + overflow: auto; + } + // Fix lists styles in core-format-text. ul { padding-left: 1rem;