From 8cd89ead85db55fd7c4fe5d472bee15ba15ecbb0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= <crazyserver@gmail.com> Date: Fri, 12 Nov 2021 16:32:54 +0100 Subject: [PATCH 1/3] MOBILE-3914 course: Add side blocks component and replace it --- scripts/langindex.json | 2 + src/core/components/empty-box/empty-box.scss | 4 - .../block/components/components.module.ts | 9 +- .../core-block-course-blocks.html | 15 -- .../course-blocks/course-blocks.scss | 61 ------ .../side-blocks-button.html | 3 + .../side-blocks-button.scss | 29 +++ .../side-blocks-button/side-blocks-button.ts | 45 ++++ .../components/side-blocks/side-blocks.html | 26 +++ .../side-blocks.ts} | 62 ++---- src/core/features/block/lang.json | 6 +- .../features/block/services/block-helper.ts | 16 ++ .../components/format/core-course-format.html | 203 +++++++++--------- .../course/components/format/format.ts | 23 +- .../features/sitehome/pages/index/index.html | 95 ++++---- .../features/sitehome/pages/index/index.ts | 35 ++- src/theme/theme.light.scss | 7 - 17 files changed, 328 insertions(+), 313 deletions(-) delete mode 100644 src/core/features/block/components/course-blocks/core-block-course-blocks.html delete mode 100644 src/core/features/block/components/course-blocks/course-blocks.scss create mode 100644 src/core/features/block/components/side-blocks-button/side-blocks-button.html create mode 100644 src/core/features/block/components/side-blocks-button/side-blocks-button.scss create mode 100644 src/core/features/block/components/side-blocks-button/side-blocks-button.ts create mode 100644 src/core/features/block/components/side-blocks/side-blocks.html rename src/core/features/block/components/{course-blocks/course-blocks.ts => side-blocks/side-blocks.ts} (61%) diff --git a/scripts/langindex.json b/scripts/langindex.json index 1df9a198e..94f7fe43e 100644 --- a/scripts/langindex.json +++ b/scripts/langindex.json @@ -1399,6 +1399,8 @@ "core.areyousure": "moodle", "core.back": "moodle", "core.block.blocks": "moodle", + "core.block.noblocks": "error", + "core.block.opendrawerblocks": "moodle", "core.browser": "local_moodlemobileapp", "core.cancel": "moodle", "core.cannotconnect": "local_moodlemobileapp", diff --git a/src/core/components/empty-box/empty-box.scss b/src/core/components/empty-box/empty-box.scss index 99ffdc978..b0b3c2254 100644 --- a/src/core/components/empty-box/empty-box.scss +++ b/src/core/components/empty-box/empty-box.scss @@ -72,7 +72,3 @@ height: auto; } } - -:host-context(core-block-course-blocks) .core-empty-box { - position: relative; -} diff --git a/src/core/features/block/components/components.module.ts b/src/core/features/block/components/components.module.ts index 7d64e292d..ab8bf883c 100644 --- a/src/core/features/block/components/components.module.ts +++ b/src/core/features/block/components/components.module.ts @@ -16,15 +16,17 @@ import { NgModule } from '@angular/core'; import { CoreBlockComponent } from './block/block'; import { CoreBlockOnlyTitleComponent } from './only-title-block/only-title-block'; import { CoreBlockPreRenderedComponent } from './pre-rendered-block/pre-rendered-block'; -import { CoreBlockCourseBlocksComponent } from './course-blocks/course-blocks'; import { CoreSharedModule } from '@/core/shared.module'; +import { CoreBlockSideBlocksComponent } from './side-blocks/side-blocks'; +import { CoreBlockSideBlocksButtonComponent } from './side-blocks-button/side-blocks-button'; @NgModule({ declarations: [ CoreBlockComponent, CoreBlockOnlyTitleComponent, CoreBlockPreRenderedComponent, - CoreBlockCourseBlocksComponent, + CoreBlockSideBlocksComponent, + CoreBlockSideBlocksButtonComponent, ], imports: [ CoreSharedModule, @@ -33,7 +35,8 @@ import { CoreSharedModule } from '@/core/shared.module'; CoreBlockComponent, CoreBlockOnlyTitleComponent, CoreBlockPreRenderedComponent, - CoreBlockCourseBlocksComponent, + CoreBlockSideBlocksComponent, + CoreBlockSideBlocksButtonComponent, ], }) export class CoreBlockComponentsModule {} diff --git a/src/core/features/block/components/course-blocks/core-block-course-blocks.html b/src/core/features/block/components/course-blocks/core-block-course-blocks.html deleted file mode 100644 index 120e60749..000000000 --- a/src/core/features/block/components/course-blocks/core-block-course-blocks.html +++ /dev/null @@ -1,15 +0,0 @@ -<div class="core-course-blocks-content"> - <ng-content></ng-content> -</div> - -<div *ngIf="blocks && blocks.length > 0 && !hideBlocks" [class.core-hide-blocks]="hideBottomBlocks" class="core-course-blocks-side"> - <core-loading [hideUntil]="dataLoaded" [fullscreen]="false"> - <ion-list> - <!-- Course expand="block"s. --> - <ng-container *ngFor="let block of blocks"> - <core-block *ngIf="block.visible" [block]="block" contextLevel="course" [instanceId]="courseId" - [extraData]="{'downloadEnabled': downloadEnabled}"></core-block> - </ng-container> - </ion-list> - </core-loading> -</div> diff --git a/src/core/features/block/components/course-blocks/course-blocks.scss b/src/core/features/block/components/course-blocks/course-blocks.scss deleted file mode 100644 index c7ba5b65e..000000000 --- a/src/core/features/block/components/course-blocks/course-blocks.scss +++ /dev/null @@ -1,61 +0,0 @@ -:host { - --side-blocks-box-shadow: var(--core-menu-box-shadow-start); - - &.core-no-blocks .core-course-blocks-content { - height: auto; - } - - &.core-has-blocks { - @media (min-width: 768px) { - display: flex; - - flex-direction: row; - flex-wrap: nowrap; - - .core-course-blocks-content { - box-shadow: none !important; - flex-grow: 1; - max-width: 100%; - - --ion-safe-area-right: 0px; - } - - div.core-course-blocks-side { - max-width: var(--side-blocks-max-width); - min-width: var(--side-blocks-min-width); - box-shadow: var(--side-blocks-box-shadow); - z-index: 2; - } - - .core-course-blocks-content, - div.core-course-blocks-side { - position: relative; - height: 100%; - - .core-loading-center, - core-loading.core-loading-loaded { - position: initial; - } - } - } - - @media (max-width: 767.98px) { - // Disable scroll on individual columns. - div.core-course-blocks-side { - height: auto; - - &.core-hide-blocks { - display: none; - } - } - } - } -} - -:host-context([dir="rtl"]).core-has-blocks { - @media (min-width: 768px) { - div.core-course-blocks-side { - box-shadow: var(--side-blocks-box-shadow); - } - } -} diff --git a/src/core/features/block/components/side-blocks-button/side-blocks-button.html b/src/core/features/block/components/side-blocks-button/side-blocks-button.html new file mode 100644 index 000000000..b4d634937 --- /dev/null +++ b/src/core/features/block/components/side-blocks-button/side-blocks-button.html @@ -0,0 +1,3 @@ +<ion-button (click)="openBlocks()" [attr.aria-label]="'core.block.opendrawerblocks' | translate"> + <ion-icon name="fas-cubes" slot="icon-only" aria-hidden="true"></ion-icon> +</ion-button> diff --git a/src/core/features/block/components/side-blocks-button/side-blocks-button.scss b/src/core/features/block/components/side-blocks-button/side-blocks-button.scss new file mode 100644 index 000000000..3e9277bfc --- /dev/null +++ b/src/core/features/block/components/side-blocks-button/side-blocks-button.scss @@ -0,0 +1,29 @@ +@import "~theme/globals"; + +:host { + @include position(50%, 0px, null, null); + position: fixed; + z-index: 10; + + ion-button { + margin: 0; + --padding-start: 0.5em; + --padding-end: 0; + --border-radius: 2em 0 0 2em; + + &::part(native) { + @include core-transition(padding, 200ms); + } + + &:hover { + --padding-end: 1.2em; + --padding-start: 1em; + } + } +} + +:host-context([dir=rtl]) { + ion-button { + --border-radius: 0 2em 2em 0; + } +} diff --git a/src/core/features/block/components/side-blocks-button/side-blocks-button.ts b/src/core/features/block/components/side-blocks-button/side-blocks-button.ts new file mode 100644 index 000000000..71f3e4898 --- /dev/null +++ b/src/core/features/block/components/side-blocks-button/side-blocks-button.ts @@ -0,0 +1,45 @@ +// (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 } from '@angular/core'; +import { CoreDomUtils } from '@services/utils/dom'; +import { CoreBlockSideBlocksComponent } from '../side-blocks/side-blocks'; + +/** + * Component that displays a button to open blocks. + */ +@Component({ + selector: 'core-block-side-blocks-button', + templateUrl: 'side-blocks-button.html', + styleUrls: ['side-blocks-button.scss'], +}) +export class CoreBlockSideBlocksButtonComponent { + + @Input() courseId!: number; + @Input() downloadEnabled = false; + + /** + * Open side blocks. + */ + openBlocks(): void { + CoreDomUtils.openSideModal({ + component: CoreBlockSideBlocksComponent, + componentProps: { + courseId: this.courseId, + downloadEnabled: this.downloadEnabled, + }, + }); + } + +} diff --git a/src/core/features/block/components/side-blocks/side-blocks.html b/src/core/features/block/components/side-blocks/side-blocks.html new file mode 100644 index 000000000..627139e3c --- /dev/null +++ b/src/core/features/block/components/side-blocks/side-blocks.html @@ -0,0 +1,26 @@ +<ion-header> + <ion-toolbar> + <h1>{{ 'core.block.blocks' | translate }}</h1> + <ion-buttons slot="end"> + <ion-button fill="clear" (click)="closeModal()" [attr.aria-label]="'core.close' | translate"> + <ion-icon name="fas-times" slot="icon-only" aria-hidden=true></ion-icon> + </ion-button> + </ion-buttons> + </ion-toolbar> +</ion-header> +<ion-content> + <ion-refresher slot="fixed" [disabled]="!loaded" (ionRefresh)="doRefresh($event.target)"> + <ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content> + </ion-refresher> + <core-loading [hideUntil]="loaded"> + <ion-list *ngIf="blocks.length > 0"> + <ng-container *ngFor="let block of blocks"> + <core-block *ngIf="block.visible" [block]="block" contextLevel="course" [instanceId]="courseId" + [extraData]="{'downloadEnabled': downloadEnabled}"></core-block> + </ng-container> + </ion-list> + + <core-empty-box *ngIf="blocks.length == 0" icon="fas-cubes" [message]="'core.block.noblocks' | translate"> + </core-empty-box> + </core-loading> +</ion-content> diff --git a/src/core/features/block/components/course-blocks/course-blocks.ts b/src/core/features/block/components/side-blocks/side-blocks.ts similarity index 61% rename from src/core/features/block/components/course-blocks/course-blocks.ts rename to src/core/features/block/components/side-blocks/side-blocks.ts index ad8b01d5f..18dc3c9ca 100644 --- a/src/core/features/block/components/course-blocks/course-blocks.ts +++ b/src/core/features/block/components/side-blocks/side-blocks.ts @@ -12,50 +12,38 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { Component, ViewChildren, Input, OnInit, QueryList, ElementRef } from '@angular/core'; -import { IonContent } from '@ionic/angular'; +import { Component, ViewChildren, Input, OnInit, QueryList } from '@angular/core'; +import { ModalController } from '@singletons'; import { CoreDomUtils } from '@services/utils/dom'; import { CoreCourse, CoreCourseBlock } from '@features/course/services/course'; import { CoreBlockHelper } from '../../services/block-helper'; import { CoreBlockComponent } from '../block/block'; import { CoreUtils } from '@services/utils/utils'; +import { IonRefresher } from '@ionic/angular'; /** - * Component that displays the list of course blocks. + * Component that displays the list of side blocks. */ @Component({ - selector: 'core-block-course-blocks', - templateUrl: 'core-block-course-blocks.html', - styleUrls: ['course-blocks.scss'], + selector: 'core-block-side-blocks', + templateUrl: 'side-blocks.html', }) -export class CoreBlockCourseBlocksComponent implements OnInit { +export class CoreBlockSideBlocksComponent implements OnInit { @Input() courseId!: number; - @Input() hideBlocks = false; - @Input() hideBottomBlocks = false; @Input() downloadEnabled = false; @ViewChildren(CoreBlockComponent) blocksComponents?: QueryList<CoreBlockComponent>; - dataLoaded = false; + loaded = false; blocks: CoreCourseBlock[] = []; - protected element: HTMLElement; - - constructor( - element: ElementRef, - protected content: IonContent, - ) { - this.element = element.nativeElement; - } - /** - * Component being initialized. + * @inheritdoc */ async ngOnInit(): Promise<void> { - this.element.classList.add('core-no-blocks'); this.loadContent().finally(() => { - this.dataLoaded = true; + this.loaded = true; }); } @@ -87,7 +75,6 @@ export class CoreBlockCourseBlocksComponent implements OnInit { * @return Promise resolved when done. */ async loadContent(): Promise<void> { - try { this.blocks = await CoreBlockHelper.getCourseBlocks(this.courseId); } catch (error) { @@ -95,29 +82,26 @@ export class CoreBlockCourseBlocksComponent implements OnInit { this.blocks = []; } - - const scrollElement = await this.content.getScrollElement(); - if (!this.hideBlocks && this.blocks.length > 0) { - this.element.classList.add('core-has-blocks'); - this.element.classList.remove('core-no-blocks'); - - scrollElement.classList.add('core-course-block-with-blocks'); - } else { - this.element.classList.remove('core-has-blocks'); - this.element.classList.add('core-no-blocks'); - scrollElement.classList.remove('core-course-block-with-blocks'); - } } /** - * Refresh data. + * Refresh the data. * - * @return Promise resolved when done. + * @param refresher Refresher. */ - async doRefresh(): Promise<void> { + async doRefresh(refresher?: IonRefresher): Promise<void> { await CoreUtils.ignoreErrors(this.invalidateBlocks()); - await this.loadContent(); + await this.loadContent().finally(() => { + refresher?.complete(); + }); + } + + /** + * Close modal. + */ + closeModal(): void { + ModalController.dismiss(); } } diff --git a/src/core/features/block/lang.json b/src/core/features/block/lang.json index 9b136b8ee..cc3f3c95a 100644 --- a/src/core/features/block/lang.json +++ b/src/core/features/block/lang.json @@ -1,3 +1,5 @@ { - "blocks": "Blocks" -} \ No newline at end of file + "blocks": "Blocks", + "noblocks": "No blocks found!", + "opendrawerblocks": "Open block drawer" +} diff --git a/src/core/features/block/services/block-helper.ts b/src/core/features/block/services/block-helper.ts index c946bd75b..00b33cd23 100644 --- a/src/core/features/block/services/block-helper.ts +++ b/src/core/features/block/services/block-helper.ts @@ -54,6 +54,22 @@ export class CoreBlockHelperProvider { return blocks; } + /** + * Returns if the course has any block. + * + * @param courseId Course ID. + * @return Wether course has blocks. + */ + async hasCourseBlocks(courseId: number): Promise<boolean> { + try { + const blocks = await this.getCourseBlocks(courseId); + + return blocks.length > 0; + } catch { + return false; + } + } + } export const CoreBlockHelper = makeSingleton(CoreBlockHelperProvider); diff --git a/src/core/features/course/components/format/core-course-format.html b/src/core/features/course/components/format/core-course-format.html index def9a3009..e4b211c67 100644 --- a/src/core/features/course/components/format/core-course-format.html +++ b/src/core/features/course/components/format/core-course-format.html @@ -6,126 +6,115 @@ </core-context-menu-item> </core-context-menu> </core-navbar-buttons> +<core-dynamic-component [component]="courseFormatComponent" [data]="data"> + <!-- Default course format. --> + <core-loading [hideUntil]="loaded"> + <!-- Section selector. --> + <core-dynamic-component [component]="sectionSelectorComponent" [data]="data"> -<core-block-course-blocks *ngIf="loaded" [courseId]="course!.id" [hideBlocks]="!displayBlocks" [downloadEnabled]="downloadEnabled" - [hideBottomBlocks]="selectedSection && selectedSection.id == allSectionsId && canLoadMore"> + <div *ngIf="displaySectionSelector && sections && hasSeveralSections" + class="ion-text-wrap ion-justify-content-between ion-align-items-center core-button-selector-row" + [class.core-section-download]="downloadEnabled"> + <core-combobox [modalOptions]="sectionSelectorModalOptions" interface="modal" listboxId="core-course-section-button" + icon="fas-folder" [label]="'core.course.section' | translate" + [selection]="selectedSection ? selectedSection.name : 'core.course.sections' | translate" + (onChange)="sectionChanged($event)"> + <span slot="text"> + <core-format-text *ngIf="selectedSection" [text]="selectedSection.name" contextLevel="course" + [contextInstanceId]="course?.id" [clean]="true" [singleLine]="true"> + </core-format-text> + <ng-container *ngIf="!selectedSection">{{ 'core.course.sections' | translate }}</ng-container> + </span> + </core-combobox> + <!-- Section download. --> + <ng-container *ngTemplateOutlet="sectionDownloadTemplate; context: {section: selectedSection}"></ng-container> + </div> + </core-dynamic-component> - <core-dynamic-component [component]="courseFormatComponent" [data]="data"> - <!-- Default course format. --> - <core-loading [hideUntil]="loaded"> - <!-- Section selector. --> - <core-dynamic-component [component]="sectionSelectorComponent" [data]="data"> - - <div *ngIf="displaySectionSelector && sections && hasSeveralSections" - class="ion-text-wrap ion-justify-content-between ion-align-items-center core-button-selector-row" - [class.core-section-download]="downloadEnabled"> - <core-combobox - [modalOptions]="sectionSelectorModalOptions" - interface="modal" - listboxId="core-course-section-button" - icon="fas-folder" - [label]="'core.course.section' | translate" - [selection]="selectedSection ? selectedSection.name : 'core.course.sections' | translate" - (onChange)="sectionChanged($event)" - > - <span slot="text"> - <core-format-text *ngIf="selectedSection" [text]="selectedSection.name" contextLevel="course" - [contextInstanceId]="course?.id" [clean]="true" [singleLine]="true"> - </core-format-text> - <ng-container *ngIf="!selectedSection">{{ 'core.course.sections' | translate }}</ng-container> - </span> - </core-combobox> - <!-- Section download. --> - <ng-container *ngTemplateOutlet="sectionDownloadTemplate; context: {section: selectedSection}"></ng-container> + <!-- Course summary. By default we only display the course progress. --> + <core-dynamic-component [component]="courseSummaryComponent" [data]="data"> + <ion-list lines="none" class="core-format-progress-list" *ngIf="imageThumb || (selectedSection?.id == allSectionsId && progress !== undefined) || + (selectedSection && selectedSection.id != allSectionsId && + (selectedSection.availabilityinfo || selectedSection.visible === 0))"> + <div *ngIf="imageThumb" class="core-course-thumb"> + <img [src]="imageThumb" core-external-content alt="" /> </div> + <ng-container *ngIf="selectedSection"> + <ion-item class="core-course-progress" *ngIf="selectedSection?.id == allSectionsId && progress !== undefined"> + <core-progress-bar [progress]="progress" a11yText="core.course.aria:sectionprogress"> + </core-progress-bar> + </ion-item> + <ion-item *ngIf="selectedSection && selectedSection.id != allSectionsId && + (selectedSection.availabilityinfo || selectedSection.visible === 0)"> + <ion-badge color="info" class="ion-text-wrap" + *ngIf="selectedSection.visible === 0 && selectedSection.uservisible !== false"> + {{ 'core.course.hiddenfromstudents' | translate }} + </ion-badge> + <ion-badge color="info" class="ion-text-wrap" + *ngIf="selectedSection.visible === 0 && selectedSection.uservisible === false"> + {{ 'core.notavailable' | translate }} + </ion-badge> + <ion-badge color="info" class="ion-text-wrap" *ngIf="selectedSection.availabilityinfo"> + <core-format-text [text]="selectedSection.availabilityinfo" contextLevel="course" + [contextInstanceId]="course?.id"> + </core-format-text> + </ion-badge> + </ion-item> + </ng-container> + </ion-list> + </core-dynamic-component> + + <!-- Single section. --> + <div *ngIf="selectedSection && selectedSection.id != allSectionsId"> + <core-dynamic-component [component]="singleSectionComponent" [data]="data"> + <ng-container *ngTemplateOutlet="sectionTemplate; context: {section: selectedSection}"></ng-container> + <core-empty-box *ngIf="!selectedSection.hasContent" icon="fas-th-large" + [message]="'core.course.nocontentavailable' | translate"> + </core-empty-box> + </core-dynamic-component> + </div> + + <!-- Multiple sections. --> + <div *ngIf="selectedSection && selectedSection.id == allSectionsId"> + <core-dynamic-component [component]="allSectionsComponent" [data]="data"> + <ng-container *ngFor="let section of sections; index as i"> + <ng-container *ngIf="i <= showSectionId"> + <ng-container *ngTemplateOutlet="sectionTemplate; context: {section: section}"></ng-container> + </ng-container> + </ng-container> </core-dynamic-component> - <!-- Course summary. By default we only display the course progress. --> - <core-dynamic-component [component]="courseSummaryComponent" [data]="data"> - <ion-list lines="none" class="core-format-progress-list" - *ngIf="imageThumb || (selectedSection?.id == allSectionsId && progress !== undefined) || - (selectedSection && selectedSection.id != allSectionsId && - (selectedSection.availabilityinfo || selectedSection.visible === 0))"> - <div *ngIf="imageThumb" class="core-course-thumb"> - <img [src]="imageThumb" core-external-content alt=""/> - </div> - <ng-container *ngIf="selectedSection"> - <ion-item class="core-course-progress" - *ngIf="selectedSection?.id == allSectionsId && progress !== undefined"> - <core-progress-bar [progress]="progress" a11yText="core.course.aria:sectionprogress"> - </core-progress-bar> - </ion-item> - <ion-item *ngIf="selectedSection && selectedSection.id != allSectionsId && - (selectedSection.availabilityinfo || selectedSection.visible === 0)"> - <ion-badge color="info" class="ion-text-wrap" - *ngIf="selectedSection.visible === 0 && selectedSection.uservisible !== false"> - {{ 'core.course.hiddenfromstudents' | translate }} - </ion-badge> - <ion-badge color="info" class="ion-text-wrap" - *ngIf="selectedSection.visible === 0 && selectedSection.uservisible === false"> - {{ 'core.notavailable' | translate }} - </ion-badge> - <ion-badge color="info" class="ion-text-wrap" *ngIf="selectedSection.availabilityinfo"> - <core-format-text [text]="selectedSection.availabilityinfo" contextLevel="course" - [contextInstanceId]="course?.id"> - </core-format-text> - </ion-badge> - </ion-item> - </ng-container> - </ion-list> - </core-dynamic-component> + <core-infinite-loading [enabled]="canLoadMore" (action)="showMoreActivities($event)"></core-infinite-loading> + </div> - <!-- Single section. --> - <div *ngIf="selectedSection && selectedSection.id != allSectionsId"> - <core-dynamic-component [component]="singleSectionComponent" [data]="data"> - <ng-container *ngTemplateOutlet="sectionTemplate; context: {section: selectedSection}"></ng-container> - <core-empty-box *ngIf="!selectedSection.hasContent" icon="fas-th-large" - [message]="'core.course.nocontentavailable' | translate"> - </core-empty-box> - </core-dynamic-component> - </div> + <ion-buttons class="ion-padding core-course-section-nav-buttons safe-area-padding-horizontal" + *ngIf="displaySectionSelector && sections?.length"> + <ion-button *ngIf="previousSection" (click)="sectionChanged(previousSection)" fill="outline" color="primary" + [attr.aria-label]="('core.previous' | translate) + ': ' + previousSection.name"> + <ion-icon name="fas-chevron-left" slot="icon-only" aria-hidden="true"></ion-icon> + <core-format-text class="sr-only" [text]="previousSection.name" contextLevel="course" [contextInstanceId]="course?.id"> + </core-format-text> + </ion-button> + <ion-button *ngIf="nextSection" (click)="sectionChanged(nextSection)" fill="solid" color="primary" + [attr.aria-label]="('core.next' | translate) + ': ' + nextSection.name"> + <core-format-text class="sr-only" [text]="nextSection.name" contextLevel="course" [contextInstanceId]="course?.id"> + </core-format-text> + <ion-icon name="fas-chevron-right" slot="icon-only" aria-hidden="true"></ion-icon> + </ion-button> + </ion-buttons> - <!-- Multiple sections. --> - <div *ngIf="selectedSection && selectedSection.id == allSectionsId"> - <core-dynamic-component [component]="allSectionsComponent" [data]="data"> - <ng-container *ngFor="let section of sections; index as i"> - <ng-container *ngIf="i <= showSectionId"> - <ng-container *ngTemplateOutlet="sectionTemplate; context: {section: section}"></ng-container> - </ng-container> - </ng-container> - </core-dynamic-component> - - <core-infinite-loading [enabled]="canLoadMore" (action)="showMoreActivities($event)"></core-infinite-loading> - </div> - - <ion-buttons class="ion-padding core-course-section-nav-buttons safe-area-padding-horizontal" - *ngIf="displaySectionSelector && sections?.length"> - <ion-button *ngIf="previousSection" (click)="sectionChanged(previousSection)" fill="outline" color="primary" - [attr.aria-label]="('core.previous' | translate) + ': ' + previousSection.name"> - <ion-icon name="fas-chevron-left" slot="icon-only" aria-hidden="true"></ion-icon> - <core-format-text class="sr-only" [text]="previousSection.name" contextLevel="course" - [contextInstanceId]="course?.id"> - </core-format-text> - </ion-button> - <ion-button *ngIf="nextSection" (click)="sectionChanged(nextSection)" fill="solid" color="primary" - [attr.aria-label]="('core.next' | translate) + ': ' + nextSection.name"> - <core-format-text class="sr-only" [text]="nextSection.name" contextLevel="course" - [contextInstanceId]="course?.id"> - </core-format-text> - <ion-icon name="fas-chevron-right" slot="icon-only" aria-hidden="true"></ion-icon> - </ion-button> - </ion-buttons> - </core-loading> - </core-dynamic-component> -</core-block-course-blocks> + <core-block-side-blocks-button *ngIf="course && displayBlocks && hasBlocks" [courseId]="course.id" + [downloadEnabled]="downloadEnabled"> + </core-block-side-blocks-button> + </core-loading> +</core-dynamic-component> <!-- Template to render a section. --> <ng-template #sectionTemplate let-section="section"> <section *ngIf="!section.hiddenbynumsections && section.id != allSectionsId && section.id != stealthModulesSectionId"> <!-- Title is only displayed when viewing all sections. --> <ion-item-divider *ngIf="selectedSection?.id == allSectionsId && section.name" class="ion-text-wrap" color="light" - [class.core-section-download]="downloadEnabled" - [class.item-dimmed]="section.visible === 0 || section.uservisible === false"> + [class.core-section-download]="downloadEnabled" [class.item-dimmed]="section.visible === 0 || section.uservisible === false"> <ion-label> <h2> <core-format-text [text]="section.name" contextLevel="course" [contextInstanceId]="course?.id"> diff --git a/src/core/features/course/components/format/format.ts b/src/core/features/course/components/format/format.ts index a76e10358..6e8ae36bc 100644 --- a/src/core/features/course/components/format/format.ts +++ b/src/core/features/course/components/format/format.ts @@ -24,7 +24,6 @@ import { ViewChildren, QueryList, Type, - ViewChild, ElementRef, } from '@angular/core'; import { ModalOptions } from '@ionic/core'; @@ -48,8 +47,8 @@ import { CoreEventObserver, CoreEvents } from '@singletons/events'; import { IonContent, IonRefresher } from '@ionic/angular'; import { CoreUtils } from '@services/utils/utils'; import { CoreCourseModulePrefetchDelegate } from '@features/course/services/module-prefetch-delegate'; -import { CoreBlockCourseBlocksComponent } from '@features/block/components/course-blocks/course-blocks'; import { CoreCourseSectionSelectorComponent } from '../section-selector/section-selector'; +import { CoreBlockHelper } from '@features/block/services/block-helper'; /** * Component to display course contents using a certain format. If the format isn't found, use default one. @@ -79,7 +78,6 @@ export class CoreCourseFormatComponent implements OnInit, OnChanges, OnDestroy { @Output() completionChanged = new EventEmitter<CoreCourseModuleCompletionData>(); // Notify when any module completion changes. @ViewChildren(CoreDynamicComponent) dynamicComponents?: QueryList<CoreDynamicComponent>; - @ViewChild(CoreBlockCourseBlocksComponent) courseBlocksComponent?: CoreBlockCourseBlocksComponent; // All the possible component classes. courseFormatComponent?: Type<unknown>; @@ -92,8 +90,9 @@ export class CoreCourseFormatComponent implements OnInit, OnChanges, OnDestroy { showSectionId = 0; data: Record<string, unknown> = {}; // Data to pass to the components. - displaySectionSelector?: boolean; - displayBlocks?: boolean; + displaySectionSelector = false; + displayBlocks = false; + hasBlocks = false; selectedSection?: CoreCourseSection; previousSection?: CoreCourseSection; nextSection?: CoreCourseSection; @@ -180,7 +179,7 @@ export class CoreCourseFormatComponent implements OnInit, OnChanges, OnDestroy { /** * Detect changes on input properties. */ - ngOnChanges(changes: { [name: string]: SimpleChange }): void { + async ngOnChanges(changes: { [name: string]: SimpleChange }): Promise<void> { this.setInputData(); this.sectionSelectorModalOptions.componentProps!.course = this.course; this.sectionSelectorModalOptions.componentProps!.sections = this.sections; @@ -191,6 +190,9 @@ export class CoreCourseFormatComponent implements OnInit, OnChanges, OnDestroy { this.displaySectionSelector = CoreCourseFormatDelegate.displaySectionSelector(this.course); this.displayBlocks = CoreCourseFormatDelegate.displayBlocks(this.course); + + this.hasBlocks = await CoreBlockHelper.hasCourseBlocks(this.course.id); + this.updateProgress(); if ('overviewfiles' in this.course) { @@ -498,8 +500,13 @@ export class CoreCourseFormatComponent implements OnInit, OnChanges, OnDestroy { await component.callComponentFunction('doRefresh', [refresher, done, afterCompletionChange]); }) || []; - if (this.courseBlocksComponent) { - promises.push(this.courseBlocksComponent.doRefresh()); + if (this.course) { + const courseId = this.course.id; + promises.push(CoreCourse.invalidateCourseBlocks(courseId).then(async () => { + this.hasBlocks = await CoreBlockHelper.hasCourseBlocks(courseId); + + return; + })); } await Promise.all(promises); diff --git a/src/core/features/sitehome/pages/index/index.html b/src/core/features/sitehome/pages/index/index.html index f96e2ea06..9220391ae 100644 --- a/src/core/features/sitehome/pages/index/index.html +++ b/src/core/features/sitehome/pages/index/index.html @@ -3,11 +3,9 @@ <ion-icon name="fas-search" slot="icon-only" aria-hidden="true"></ion-icon> </ion-button> <core-context-menu> - <core-context-menu-item [priority]="1000" *ngIf="displayEnableDownload" - [content]="'core.settings.showdownloadoptions' | translate" (action)="switchDownload()" - iconAction="toggle" [(toggle)]="downloadEnabled"></core-context-menu-item> - <core-context-menu-item [priority]="500" - [content]="'addon.storagemanager.managestorage' | translate" + <core-context-menu-item [priority]="1000" *ngIf="displayEnableDownload" [content]="'core.settings.showdownloadoptions' | translate" + (action)="switchDownload()" iconAction="toggle" [(toggle)]="downloadEnabled"></core-context-menu-item> + <core-context-menu-item [priority]="500" [content]="'addon.storagemanager.managestorage' | translate" (action)="manageCoursesStorage()" iconAction="fas-archive"></core-context-menu-item> </core-context-menu> </core-navbar-buttons> @@ -15,49 +13,52 @@ <ion-refresher slot="fixed" [disabled]="!dataLoaded" (ionRefresh)="doRefresh($event.target)"> <ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content> </ion-refresher> - <core-block-course-blocks [courseId]="siteHomeId" [downloadEnabled]="downloadEnabled"> - <core-loading [hideUntil]="dataLoaded"> - <ion-list> - <!-- Site home main contents. --> - <ng-container *ngIf="section && section.hasContent"> - <ion-item class="ion-text-wrap" *ngIf="section.summary"> - <ion-label><core-format-text [text]="section.summary" contextLevel="course" [contextInstanceId]="siteHomeId"> - </core-format-text></ion-label> - </ion-item> + <core-loading [hideUntil]="dataLoaded"> + <ion-list> + <!-- Site home main contents. --> + <ng-container *ngIf="section && section.hasContent"> + <ion-item class="ion-text-wrap" *ngIf="section.summary"> + <ion-label> + <core-format-text [text]="section.summary" contextLevel="course" [contextInstanceId]="siteHomeId"> + </core-format-text> + </ion-label> + </ion-item> - <core-course-module *ngFor="let module of section.modules" [module]="module" [courseId]="siteHomeId" - [downloadEnabled]="downloadEnabled" [section]="section"></core-course-module> - </ng-container> + <core-course-module *ngFor="let module of section.modules" [module]="module" [courseId]="siteHomeId" + [downloadEnabled]="downloadEnabled" [section]="section"></core-course-module> + </ng-container> - <!-- Site home items: news, categories, courses, etc. --> - <ng-container *ngIf="items.length > 0"> - <core-spacer *ngIf="section && section!.hasContent"></core-spacer> - <ng-container *ngFor="let item of items"> - <ng-container [ngSwitch]="item"> - <ng-container *ngSwitchCase="'LIST_OF_COURSE'"> - <ng-template *ngTemplateOutlet="allCourseList"></ng-template> - </ng-container> - <ng-container *ngSwitchCase="'LIST_OF_CATEGORIES'"> - <ng-template *ngTemplateOutlet="categories"></ng-template> - </ng-container> - <ng-container *ngSwitchCase="'COURSE_SEARCH_BOX'"> - <ng-template *ngTemplateOutlet="courseSearch"></ng-template> - </ng-container> - <ng-container *ngSwitchCase="'ENROLLED_COURSES'"> - <ng-template *ngTemplateOutlet="enrolledCourseList"></ng-template> - </ng-container> - <ng-container *ngSwitchCase="'NEWS_ITEMS'"> - <ng-template *ngTemplateOutlet="news"></ng-template> - </ng-container> - </ng-container> + <!-- Site home items: news, categories, courses, etc. --> + <ng-container *ngIf="items.length > 0"> + <core-spacer *ngIf="section && section!.hasContent"></core-spacer> + <ng-container *ngFor="let item of items"> + <ng-container [ngSwitch]="item"> + <ng-container *ngSwitchCase="'LIST_OF_COURSE'"> + <ng-template *ngTemplateOutlet="allCourseList"></ng-template> + </ng-container> + <ng-container *ngSwitchCase="'LIST_OF_CATEGORIES'"> + <ng-template *ngTemplateOutlet="categories"></ng-template> + </ng-container> + <ng-container *ngSwitchCase="'COURSE_SEARCH_BOX'"> + <ng-template *ngTemplateOutlet="courseSearch"></ng-template> + </ng-container> + <ng-container *ngSwitchCase="'ENROLLED_COURSES'"> + <ng-template *ngTemplateOutlet="enrolledCourseList"></ng-template> + </ng-container> + <ng-container *ngSwitchCase="'NEWS_ITEMS'"> + <ng-template *ngTemplateOutlet="news"></ng-template> </ng-container> </ng-container> - </ion-list> - <core-empty-box *ngIf="!hasContent" icon="fas-box-open" [message]="'core.course.nocontentavailable' | translate"> + </ng-container> + </ng-container> + </ion-list> + <core-block-side-blocks-button *ngIf="hasBlocks" [courseId]="siteHomeId" [downloadEnabled]="downloadEnabled"> + </core-block-side-blocks-button> - </core-empty-box> - </core-loading> - </core-block-course-blocks> + <core-empty-box *ngIf="!hasContent" icon="fas-box-open" [message]="'core.course.nocontentavailable' | translate"> + + </core-empty-box> + </core-loading> </ion-content> <ng-template #allCourseList> @@ -88,13 +89,17 @@ <ion-item button class="ion-text-wrap" (click)="openMyCourses()" detail="true"> <ion-icon name="fas-graduation-cap" fixed-width slot="start" aria-hidden="true"> </ion-icon> - <ion-label><h2>{{ 'core.courses.mycourses' | translate}}</h2></ion-label> + <ion-label> + <h2>{{ 'core.courses.mycourses' | translate}}</h2> + </ion-label> </ion-item> </ng-template> <ng-template #courseSearch> <ion-item button class="ion-text-wrap" (click)="openSearch()" detail="true"> <ion-icon name="fas-search" slot="start" aria-hidden="true"></ion-icon> - <ion-label><h2>{{ 'core.courses.searchcourses' | translate}}</h2></ion-label> + <ion-label> + <h2>{{ 'core.courses.searchcourses' | translate}}</h2> + </ion-label> </ion-item> </ng-template> diff --git a/src/core/features/sitehome/pages/index/index.ts b/src/core/features/sitehome/pages/index/index.ts index 777d6ce9c..eae6406f8 100644 --- a/src/core/features/sitehome/pages/index/index.ts +++ b/src/core/features/sitehome/pages/index/index.ts @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { Component, OnDestroy, OnInit, ViewChild } from '@angular/core'; +import { Component, OnDestroy, OnInit } from '@angular/core'; import { IonRefresher } from '@ionic/angular'; import { Params } from '@angular/router'; @@ -24,10 +24,11 @@ import { CoreSiteHome } from '@features/sitehome/services/sitehome'; import { CoreCourses, CoreCoursesProvider } from '@features//courses/services/courses'; import { CoreEventObserver, CoreEvents } from '@singletons/events'; import { CoreCourseHelper, CoreCourseModule } from '@features/course/services/course-helper'; -import { CoreBlockCourseBlocksComponent } from '@features/block/components/course-blocks/course-blocks'; import { CoreCourseModuleDelegate, CoreCourseModuleHandlerData } from '@features/course/services/module-delegate'; import { CoreCourseModulePrefetchDelegate } from '@features/course/services/module-prefetch-delegate'; import { CoreNavigator } from '@services/navigator'; +import { CoreBlockHelper } from '@features/block/services/block-helper'; +import { CoreUtils } from '@services/utils/utils'; /** * Page that displays site home index. @@ -38,14 +39,13 @@ import { CoreNavigator } from '@services/navigator'; }) export class CoreSiteHomeIndexPage implements OnInit, OnDestroy { - @ViewChild(CoreBlockCourseBlocksComponent) courseBlocksComponent?: CoreBlockCourseBlocksComponent; - dataLoaded = false; section?: CoreCourseWSSection & { hasContent?: boolean; }; hasContent = false; + hasBlocks = false; items: string[] = []; siteHomeId = 1; currentSite!: CoreSite; @@ -106,8 +106,8 @@ export class CoreSiteHomeIndexPage implements OnInit, OnDestroy { this.items = await CoreSiteHome.getFrontPageItems(config.frontpageloggedin); this.hasContent = this.items.length > 0; - if (this.items.some((item) => item == 'NEWS_ITEMS')) { - // Get the news forum. + // Get the news forum. + if (this.items.includes('NEWS_ITEMS')) { try { const forum = await CoreSiteHome.getNewsForum(this.siteHomeId); this.newsForumModule = await CoreCourse.getModule(forum.cmid, forum.course); @@ -140,17 +140,17 @@ export class CoreSiteHomeIndexPage implements OnInit, OnDestroy { } // Add log in Moodle. - CoreCourse.logView( + CoreUtils.ignoreErrors(CoreCourse.logView( this.siteHomeId, undefined, undefined, this.currentSite.getInfo()?.sitename, - ).catch(() => { - // Ignore errors. - }); + )); } catch (error) { CoreDomUtils.showErrorModalDefault(error, 'core.course.couldnotloadsectioncontent', true); } + + this.hasBlocks = await CoreBlockHelper.hasCourseBlocks(this.siteHomeId); } /** @@ -170,24 +170,15 @@ export class CoreSiteHomeIndexPage implements OnInit, OnDestroy { return; })); + promises.push(CoreCourse.invalidateCourseBlocks(this.siteHomeId)); + if (this.section && this.section.modules) { // Invalidate modules prefetch data. promises.push(CoreCourseModulePrefetchDelegate.invalidateModules(this.section.modules, this.siteHomeId)); } - if (this.courseBlocksComponent) { - promises.push(this.courseBlocksComponent.invalidateBlocks()); - } - Promise.all(promises).finally(async () => { - const p2: Promise<unknown>[] = []; - - p2.push(this.loadContent()); - if (this.courseBlocksComponent) { - p2.push(this.courseBlocksComponent.loadContent()); - } - - await Promise.all(p2).finally(() => { + await this.loadContent().finally(() => { refresher?.complete(); }); }); diff --git a/src/theme/theme.light.scss b/src/theme/theme.light.scss index 56998e1eb..fa001efb9 100644 --- a/src/theme/theme.light.scss +++ b/src/theme/theme.light.scss @@ -197,13 +197,6 @@ --background: var(--core-progressbar-background); } - --core-side-blocks-max-width: 30%; - --core-side-blocks-min-width: 280px; - core-block-course-blocks { - --side-blocks-max-width: var(--core-side-blocks-max-width); - --side-blocks-min-width: var(--core-side-blocks-min-width); - } - --ion-item-background: #{$ion-item-background}; --ion-item-detail-icon-color: var(--gray-darker); --ion-item-detail-icon-font-size: 20px; From 0bfe870af1dffed5f9b1447c65e28bea8e36861e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= <crazyserver@gmail.com> Date: Fri, 12 Nov 2021 16:32:54 +0100 Subject: [PATCH 2/3] MOBILE-3914 dashboard: Add side blocks on dashboard --- .../components/side-blocks/side-blocks.ts | 15 ++++-- .../courses/pages/dashboard/dashboard.html | 13 ++--- .../courses/pages/dashboard/dashboard.ts | 8 +++- .../features/courses/services/dashboard.ts | 47 ++++++++++++++++++- .../services/handlers/dashboard-home.ts | 2 +- 5 files changed, 72 insertions(+), 13 deletions(-) diff --git a/src/core/features/block/components/side-blocks/side-blocks.ts b/src/core/features/block/components/side-blocks/side-blocks.ts index 18dc3c9ca..eefc8f527 100644 --- a/src/core/features/block/components/side-blocks/side-blocks.ts +++ b/src/core/features/block/components/side-blocks/side-blocks.ts @@ -20,6 +20,7 @@ import { CoreBlockHelper } from '../../services/block-helper'; import { CoreBlockComponent } from '../block/block'; import { CoreUtils } from '@services/utils/utils'; import { IonRefresher } from '@ionic/angular'; +import { CoreCoursesDashboard } from '@features/courses/services/dashboard'; /** * Component that displays the list of side blocks. @@ -30,7 +31,7 @@ import { IonRefresher } from '@ionic/angular'; }) export class CoreBlockSideBlocksComponent implements OnInit { - @Input() courseId!: number; + @Input() courseId?: number; @Input() downloadEnabled = false; @ViewChildren(CoreBlockComponent) blocksComponents?: QueryList<CoreBlockComponent>; @@ -55,8 +56,10 @@ export class CoreBlockSideBlocksComponent implements OnInit { async invalidateBlocks(): Promise<void> { const promises: Promise<void>[] = []; - if (CoreBlockHelper.canGetCourseBlocks()) { + if (this.courseId) { promises.push(CoreCourse.invalidateCourseBlocks(this.courseId)); + } else { + promises.push(CoreCoursesDashboard.invalidateDashboardBlocks()); } // Invalidate the blocks. @@ -76,7 +79,13 @@ export class CoreBlockSideBlocksComponent implements OnInit { */ async loadContent(): Promise<void> { try { - this.blocks = await CoreBlockHelper.getCourseBlocks(this.courseId); + if (this.courseId) { + this.blocks = await CoreBlockHelper.getCourseBlocks(this.courseId); + } else { + const blocks = await CoreCoursesDashboard.getDashboardBlocks(); + + this.blocks = blocks.sideBlocks; + } } catch (error) { CoreDomUtils.showErrorModal(error); diff --git a/src/core/features/courses/pages/dashboard/dashboard.html b/src/core/features/courses/pages/dashboard/dashboard.html index c79854f7a..ee5fb7421 100644 --- a/src/core/features/courses/pages/dashboard/dashboard.html +++ b/src/core/features/courses/pages/dashboard/dashboard.html @@ -4,10 +4,9 @@ </ion-button> <core-context-menu> <core-context-menu-item *ngIf="(downloadCourseEnabled || downloadCoursesEnabled)" [priority]="1000" - [content]="'core.settings.showdownloadoptions' | translate" (action)="switchDownload()" - iconAction="toggle" [(toggle)]="downloadEnabled"></core-context-menu-item> - <core-context-menu-item [priority]="500" - [content]="'addon.storagemanager.managestorage' | translate" + [content]="'core.settings.showdownloadoptions' | translate" (action)="switchDownload()" iconAction="toggle" + [(toggle)]="downloadEnabled"></core-context-menu-item> + <core-context-menu-item [priority]="500" [content]="'addon.storagemanager.managestorage' | translate" (action)="manageCoursesStorage()" iconAction="fas-archive"></core-context-menu-item> </core-context-menu> </core-navbar-buttons> @@ -20,11 +19,13 @@ <ion-list> <ng-container *ngFor="let block of blocks"> <core-block *ngIf="block.visible" [block]="block" contextLevel="user" [instanceId]="userId" - [extraData]="{'downloadEnabled': downloadEnabled}"></core-block> + [extraData]="{'downloadEnabled': downloadEnabled}"></core-block> </ng-container> </ion-list> - <core-empty-box *ngIf="blocks.length == 0" icon="fas-th-large" [message]="'core.course.nocontentavailable' | translate"> + <core-block-side-blocks-button *ngIf="hasSideBlocks" [downloadEnabled]="downloadEnabled"></core-block-side-blocks-button> + + <core-empty-box *ngIf="blocks.length == 0" icon="fas-cubes" [message]="'core.course.nocontentavailable' | translate"> </core-empty-box> </core-loading> </ion-content> diff --git a/src/core/features/courses/pages/dashboard/dashboard.ts b/src/core/features/courses/pages/dashboard/dashboard.ts index 5102614cc..2b7448a0b 100644 --- a/src/core/features/courses/pages/dashboard/dashboard.ts +++ b/src/core/features/courses/pages/dashboard/dashboard.ts @@ -23,6 +23,7 @@ import { CoreDomUtils } from '@services/utils/dom'; import { CoreCourseBlock } from '@features/course/services/course'; import { CoreBlockComponent } from '@features/block/components/block/block'; import { CoreNavigator } from '@services/navigator'; +import { CoreBlockDelegate } from '@features/block/services/block-delegate'; /** * Page that displays the dashboard page. @@ -36,6 +37,7 @@ export class CoreCoursesDashboardPage implements OnInit, OnDestroy { @ViewChildren(CoreBlockComponent) blocksComponents?: QueryList<CoreBlockComponent>; + hasSideBlocks = false; searchEnabled = false; downloadEnabled = false; downloadCourseEnabled = false; @@ -88,7 +90,11 @@ export class CoreCoursesDashboardPage implements OnInit, OnDestroy { this.userId = CoreSites.getCurrentSiteUserId(); try { - this.blocks = await CoreCoursesDashboard.getDashboardBlocks(); + const blocks = await CoreCoursesDashboard.getDashboardBlocks(); + + this.blocks = blocks.mainBlocks; + + this.hasSideBlocks = CoreBlockDelegate.hasSupportedBlock(blocks.sideBlocks); } catch (error) { CoreDomUtils.showErrorModal(error); diff --git a/src/core/features/courses/services/dashboard.ts b/src/core/features/courses/services/dashboard.ts index d592f2e2d..76823a0b2 100644 --- a/src/core/features/courses/services/dashboard.ts +++ b/src/core/features/courses/services/dashboard.ts @@ -38,14 +38,14 @@ export class CoreCoursesDashboardProvider { } /** - * Get dashboard blocks. + * Get dashboard blocks from WS. * * @param userId User ID. Default, current user. * @param siteId Site ID. If not defined, current site. * @return Promise resolved with the list of blocks. * @since 3.6 */ - async getDashboardBlocks(userId?: number, siteId?: string): Promise<CoreCourseBlock[]> { + protected async getDashboardBlocksFromWS(userId?: number, siteId?: string): Promise<CoreCourseBlock[]> { const site = await CoreSites.getSite(siteId); const params: CoreBlockGetDashboardBlocksWSParams = { @@ -63,6 +63,44 @@ export class CoreCoursesDashboardProvider { return result.blocks || []; } + /** + * Get dashboard blocks. + * + * @param userId User ID. Default, current user. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with the list of blocks. + */ + async getDashboardBlocks(userId?: number, siteId?: string): Promise<CoreCoursesDashboardBlocks> { + const blocks = await CoreCoursesDashboard.getDashboardBlocksFromWS(userId, siteId); + + let mainBlocks: CoreCourseBlock[] = []; + let sideBlocks: CoreCourseBlock[] = []; + + blocks.forEach((block) => { + if (block.region == 'content' || block.region == 'main') { + mainBlocks.push(block); + } else { + sideBlocks.push(block); + } + }); + + if (mainBlocks.length == 0) { + mainBlocks = []; + sideBlocks = []; + + blocks.forEach((block) => { + if (block.region.match('side')) { + sideBlocks.push(block); + } else { + mainBlocks.push(block); + } + }); + } + + return { mainBlocks, sideBlocks }; + + } + /** * Invalidates dashboard blocks WS call. * @@ -122,6 +160,11 @@ export class CoreCoursesDashboardProvider { export const CoreCoursesDashboard = makeSingleton(CoreCoursesDashboardProvider); +export type CoreCoursesDashboardBlocks = { + mainBlocks: CoreCourseBlock[]; + sideBlocks: CoreCourseBlock[]; +}; + /** * Params of core_block_get_dashboard_blocks WS. */ diff --git a/src/core/features/courses/services/handlers/dashboard-home.ts b/src/core/features/courses/services/handlers/dashboard-home.ts index 998a6fac7..0076031a8 100644 --- a/src/core/features/courses/services/handlers/dashboard-home.ts +++ b/src/core/features/courses/services/handlers/dashboard-home.ts @@ -68,7 +68,7 @@ export class CoreDashboardHomeHandlerService implements CoreMainMenuHomeHandler if (dashboardAvailable && blocksEnabled) { const blocks = await CoreCoursesDashboard.getDashboardBlocks(undefined, siteId); - return CoreBlockDelegate.hasSupportedBlock(blocks); + return CoreBlockDelegate.hasSupportedBlock(blocks.mainBlocks) || CoreBlockDelegate.hasSupportedBlock(blocks.sideBlocks); } // Check if my overview is enabled. If it's enabled we will fake enabled blocks. From d990fa44f97993b3260b0e6d8eb6aa1f2a7a1e3a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= <crazyserver@gmail.com> Date: Tue, 16 Nov 2021 13:12:36 +0100 Subject: [PATCH 3/3] MOBILE-3914 utils: Close modal before navigating --- src/core/services/utils/dom.ts | 53 +++++++++++++++++++++++++++------- 1 file changed, 43 insertions(+), 10 deletions(-) diff --git a/src/core/services/utils/dom.ts b/src/core/services/utils/dom.ts index e4b6deb5a..610b1753b 100644 --- a/src/core/services/utils/dom.ts +++ b/src/core/services/utils/dom.ts @@ -29,7 +29,15 @@ import { CoreIonLoadingElement } from '@classes/ion-loading'; import { CoreCanceledError } from '@classes/errors/cancelederror'; import { CoreAnyError, CoreError } from '@classes/errors/error'; import { CoreSilentError } from '@classes/errors/silenterror'; -import { makeSingleton, Translate, AlertController, ToastController, PopoverController, ModalController } from '@singletons'; +import { + makeSingleton, + Translate, + AlertController, + ToastController, + PopoverController, + ModalController, + Router, +} from '@singletons'; import { CoreLogger } from '@singletons/logger'; import { CoreFileSizeSum } from '@services/plugin-file-delegate'; import { CoreNetworkError } from '@classes/errors/network-error'; @@ -41,6 +49,9 @@ import { CoreZoomLevel } from '@features/settings/services/settings-helper'; import { CoreErrorWithTitle } from '@classes/errors/errorwithtitle'; import { AddonFilterMultilangHandler } from '@addons/filter/multilang/services/handlers/multilang'; import { CoreSites } from '@services/sites'; +import { NavigationStart } from '@angular/router'; +import { filter } from 'rxjs/operators'; +import { Subscription } from 'rxjs'; /* * "Utils" service with helper functions for UI, DOM elements and HTML code. @@ -1651,18 +1662,32 @@ export class CoreDomUtilsProvider { /** * Opens a Modal. * - * @param modalOptions Modal Options. + * @param options Modal Options. */ async openModal<T = unknown>( - modalOptions: ModalOptions, + options: OpenModalOptions, ): Promise<T | undefined> { + const { waitForDismissCompleted, closeOnNavigate, ...modalOptions } = options; + const listenCloseEvents = closeOnNavigate ?? true; // Default to true. + const modal = await ModalController.create(modalOptions); + let navSubscription: Subscription | undefined; + if (listenCloseEvents) { + // Listen navigation events to close modals. + navSubscription = Router.events + .pipe(filter(event => event instanceof NavigationStart)) + .subscribe(async () => { + modal.dismiss(); + }); + } + await modal.present(); - // If onDidDismiss is nedded we can add a new param to the function to wait one function or the other. - const result = await modal.onWillDismiss<T>(); + const result = waitForDismissCompleted ? await modal.onDidDismiss<T>() : await modal.onWillDismiss<T>(); + navSubscription?.unsubscribe(); + if (result?.data) { return result?.data; } @@ -1671,21 +1696,21 @@ export class CoreDomUtilsProvider { /** * Opens a side Modal. * - * @param modalOptions Modal Options. + * @param options Modal Options. */ async openSideModal<T = unknown>( - modalOptions: ModalOptions, + options: OpenModalOptions, ): Promise<T | undefined> { - modalOptions = Object.assign({ + options = Object.assign({ cssClass: 'core-modal-lateral', showBackdrop: true, backdropDismiss: true, enterAnimation: CoreModalLateralTransitionEnter, leaveAnimation: CoreModalLateralTransitionLeave, - }, modalOptions); + }, options); - return await this.openModal<T>(modalOptions); + return await this.openModal<T>(options); } /** @@ -2012,3 +2037,11 @@ export const CoreDomUtils = makeSingleton(CoreDomUtilsProvider); export type OpenPopoverOptions = PopoverOptions & { waitForDismissCompleted?: boolean; }; + +/** + * Options for the openModal function. + */ +export type OpenModalOptions = ModalOptions & { + waitForDismissCompleted?: boolean; + closeOnNavigate?: boolean; // Default true. +};