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 @@
-
-
-
-
- 0 && !hideBlocks" [class.core-hide-blocks]="hideBottomBlocks" class="core-course-blocks-side">
-
-
-
-
-
-
-
-
-
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 @@
+
+
+
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 @@
+
+
+ {{ 'core.block.blocks' | translate }}
+
+
+
+
+
+
+
+
+
+
+
+
+ 0">
+
+
+
+
+
+
+
+
+
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;
- 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 {
- 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 {
-
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 {
+ async doRefresh(refresher?: IonRefresher): Promise {
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 {
+ 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.course.sections' | translate }}
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
- {{ 'core.course.sections' | translate }}
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ 'core.course.hiddenfromstudents' | translate }}
+
+
+ {{ 'core.notavailable' | translate }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
- {{ 'core.course.hiddenfromstudents' | translate }}
-
-
- {{ 'core.notavailable' | translate }}
-
-
-
-
-
-
-
-
-
+
+
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+ [class.core-section-download]="downloadEnabled" [class.item-dimmed]="section.visible === 0 || section.uservisible === false">
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(); // Notify when any module completion changes.
@ViewChildren(CoreDynamicComponent) dynamicComponents?: QueryList;
- @ViewChild(CoreBlockCourseBlocksComponent) courseBlocksComponent?: CoreBlockCourseBlocksComponent;
// All the possible component classes.
courseFormatComponent?: Type;
@@ -92,8 +90,9 @@ export class CoreCourseFormatComponent implements OnInit, OnChanges, OnDestroy {
showSectionId = 0;
data: Record = {}; // 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 {
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 @@
-
-
+
@@ -15,49 +13,52 @@
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
-
-
+
+
-
- 0">
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+ 0">
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
+
+
+
+
+
-
-
-
+
+
+
+
@@ -88,13 +89,17 @@
- {{ 'core.courses.mycourses' | translate}}
+
+ {{ 'core.courses.mycourses' | translate}}
+
- {{ 'core.courses.searchcourses' | translate}}
+
+ {{ 'core.courses.searchcourses' | translate}}
+
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[] = [];
-
- 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;