diff --git a/src/addons/block/block.module.ts b/src/addons/block/block.module.ts
index e1f0a96c7..bc2bbb55a 100644
--- a/src/addons/block/block.module.ts
+++ b/src/addons/block/block.module.ts
@@ -37,6 +37,7 @@ import { AddonBlockSiteMainMenuModule } from './sitemainmenu/sitemainmenu.module
import { AddonBlockStarredCoursesModule } from './starredcourses/starredcourses.module';
import { AddonBlockTagsModule } from './tags/tags.module';
import { AddonBlockActivityModulesModule } from './activitymodules/activitymodules.module';
+import { AddonBlockRecentlyAccessedItemsModule } from './recentlyaccesseditems/recentlyaccesseditems.module';
@NgModule({
declarations: [],
@@ -64,6 +65,7 @@ import { AddonBlockActivityModulesModule } from './activitymodules/activitymodul
AddonBlockStarredCoursesModule,
AddonBlockTagsModule,
AddonBlockActivityModulesModule,
+ AddonBlockRecentlyAccessedItemsModule,
],
providers: [],
exports: [],
diff --git a/src/addons/block/recentlyaccesseditems/components/components.module.ts b/src/addons/block/recentlyaccesseditems/components/components.module.ts
new file mode 100644
index 000000000..9aa9ce8a5
--- /dev/null
+++ b/src/addons/block/recentlyaccesseditems/components/components.module.ts
@@ -0,0 +1,43 @@
+// (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 { NgModule } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { IonicModule } from '@ionic/angular';
+import { TranslateModule } from '@ngx-translate/core';
+
+import { CoreSharedModule } from '@/core/shared.module';
+import { CoreCoursesComponentsModule } from '@features/courses/components/components.module';
+
+import { AddonBlockRecentlyAccessedItemsComponent } from './recentlyaccesseditems/recentlyaccesseditems';
+
+@NgModule({
+ declarations: [
+ AddonBlockRecentlyAccessedItemsComponent,
+ ],
+ imports: [
+ CommonModule,
+ IonicModule,
+ TranslateModule.forChild(),
+ CoreSharedModule,
+ CoreCoursesComponentsModule,
+ ],
+ exports: [
+ AddonBlockRecentlyAccessedItemsComponent,
+ ],
+ entryComponents: [
+ AddonBlockRecentlyAccessedItemsComponent,
+ ],
+})
+export class AddonBlockRecentlyAccessedItemsComponentsModule {}
diff --git a/src/addons/block/recentlyaccesseditems/components/recentlyaccesseditems/addon-block-recentlyaccesseditems.html b/src/addons/block/recentlyaccesseditems/components/recentlyaccesseditems/addon-block-recentlyaccesseditems.html
new file mode 100644
index 000000000..2008fbce0
--- /dev/null
+++ b/src/addons/block/recentlyaccesseditems/components/recentlyaccesseditems/addon-block-recentlyaccesseditems.html
@@ -0,0 +1,29 @@
+
+ {{ 'addon.block_recentlyaccesseditems.pluginname' | translate }}
+
+
+
+
+
+
+
diff --git a/src/addons/block/recentlyaccesseditems/components/recentlyaccesseditems/recentlyaccesseditems.scss b/src/addons/block/recentlyaccesseditems/components/recentlyaccesseditems/recentlyaccesseditems.scss
new file mode 100644
index 000000000..fc9edbddf
--- /dev/null
+++ b/src/addons/block/recentlyaccesseditems/components/recentlyaccesseditems/recentlyaccesseditems.scss
@@ -0,0 +1,7 @@
+@import "~theme/globals";
+
+:host {
+ .core-horizontal-scroll > div {
+ @include horizontal_scroll_item(80%, 250px, 300px);
+ }
+}
diff --git a/src/addons/block/recentlyaccesseditems/components/recentlyaccesseditems/recentlyaccesseditems.ts b/src/addons/block/recentlyaccesseditems/components/recentlyaccesseditems/recentlyaccesseditems.ts
new file mode 100644
index 000000000..65c55f24e
--- /dev/null
+++ b/src/addons/block/recentlyaccesseditems/components/recentlyaccesseditems/recentlyaccesseditems.ts
@@ -0,0 +1,86 @@
+// (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, OnInit } from '@angular/core';
+import { CoreSites } from '@services/sites';
+import { CoreBlockBaseComponent } from '@features/block/classes/base-block-component';
+import {
+ AddonBlockRecentlyAccessedItems,
+ AddonBlockRecentlyAccessedItemsItem,
+} from '../../services/recentlyaccesseditems';
+import { CoreTextUtils } from '@services/utils/text';
+import { CoreDomUtils } from '@services/utils/dom';
+import { CoreContentLinksHelper } from '@features/contentlinks/services/contentlinks-helper';
+
+/**
+ * Component to render a recently accessed items block.
+ */
+@Component({
+ selector: 'addon-block-recentlyaccesseditems',
+ templateUrl: 'addon-block-recentlyaccesseditems.html',
+ styleUrls: ['recentlyaccesseditems.scss'],
+})
+export class AddonBlockRecentlyAccessedItemsComponent extends CoreBlockBaseComponent implements OnInit {
+
+ items: AddonBlockRecentlyAccessedItemsItem[] = [];
+
+ protected fetchContentDefaultError = 'Error getting recently accessed items data.';
+
+ constructor() {
+ super('AddonBlockRecentlyAccessedItemsComponent');
+ }
+
+
+ /**
+ * Perform the invalidate content function.
+ *
+ * @return Resolved when done.
+ */
+ protected async invalidateContent(): Promise {
+ await AddonBlockRecentlyAccessedItems.instance.invalidateRecentItems();
+ }
+
+ /**
+ * Fetch the data to render the block.
+ *
+ * @return Promise resolved when done.
+ */
+ protected async fetchContent(): Promise {
+ this.items = await AddonBlockRecentlyAccessedItems.instance.getRecentItems();
+ }
+
+ /**
+ * Event clicked.
+ *
+ * @param e Click event.
+ * @param item Activity item info.
+ */
+ async action(e: Event, item: AddonBlockRecentlyAccessedItemsItem): Promise {
+ e.preventDefault();
+ e.stopPropagation();
+
+ const url = CoreTextUtils.instance.decodeHTMLEntities(item.viewurl);
+ const modal = await CoreDomUtils.instance.showModalLoading();
+
+ try {
+ const treated = await CoreContentLinksHelper.instance.handleLink(url);
+ if (!treated) {
+ return CoreSites.instance.getCurrentSite()?.openInBrowserWithAutoLoginIfSameSite(url);
+ }
+ } finally {
+ modal.dismiss();
+ }
+ }
+
+}
diff --git a/src/addons/block/recentlyaccesseditems/lang.json b/src/addons/block/recentlyaccesseditems/lang.json
new file mode 100644
index 000000000..47311d281
--- /dev/null
+++ b/src/addons/block/recentlyaccesseditems/lang.json
@@ -0,0 +1,4 @@
+{
+ "noitems": "No recent items",
+ "pluginname": "Recently accessed items"
+}
diff --git a/src/addons/block/recentlyaccesseditems/recentlyaccesseditems.module.ts b/src/addons/block/recentlyaccesseditems/recentlyaccesseditems.module.ts
new file mode 100644
index 000000000..a25826da9
--- /dev/null
+++ b/src/addons/block/recentlyaccesseditems/recentlyaccesseditems.module.ts
@@ -0,0 +1,40 @@
+// (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 { APP_INITIALIZER, NgModule } from '@angular/core';
+import { IonicModule } from '@ionic/angular';
+import { TranslateModule } from '@ngx-translate/core';
+import { CoreSharedModule } from '@/core/shared.module';
+import { CoreBlockDelegate } from '@features/block/services/block-delegate';
+import { AddonBlockRecentlyAccessedItemsComponentsModule } from './components/components.module';
+import { AddonBlockRecentlyAccessedItemsHandler } from './services/block-handler';
+
+@NgModule({
+ imports: [
+ IonicModule,
+ CoreSharedModule,
+ AddonBlockRecentlyAccessedItemsComponentsModule,
+ TranslateModule.forChild(),
+ ],
+ providers: [
+ {
+ provide: APP_INITIALIZER,
+ multi: true,
+ useValue: () => {
+ CoreBlockDelegate.instance.registerHandler(AddonBlockRecentlyAccessedItemsHandler.instance);
+ },
+ },
+ ],
+})
+export class AddonBlockRecentlyAccessedItemsModule {}
diff --git a/src/addons/block/recentlyaccesseditems/services/block-handler.ts b/src/addons/block/recentlyaccesseditems/services/block-handler.ts
new file mode 100644
index 000000000..cfc9ddf00
--- /dev/null
+++ b/src/addons/block/recentlyaccesseditems/services/block-handler.ts
@@ -0,0 +1,50 @@
+// (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 { Injectable } from '@angular/core';
+import { CoreBlockHandlerData } from '@features/block/services/block-delegate';
+import { AddonBlockRecentlyAccessedItemsComponent } from '../components/recentlyaccesseditems/recentlyaccesseditems';
+import { CoreBlockBaseHandler } from '@features/block/classes/base-block-handler';
+import { makeSingleton } from '@singletons';
+
+/**
+ * Block handler.
+ */
+@Injectable( { providedIn: 'root' })
+export class AddonBlockRecentlyAccessedItemsHandlerService extends CoreBlockBaseHandler {
+
+ name = 'AddonBlockRecentlyAccessedItems';
+ blockName = 'recentlyaccesseditems';
+
+ /**
+ * Returns the data needed to render the block.
+ *
+ * @param injector Injector.
+ * @param block The block to render.
+ * @param contextLevel The context where the block will be used.
+ * @param instanceId The instance ID associated with the context level.
+ * @return Data or promise resolved with the data.
+ */
+ getDisplayData(): CoreBlockHandlerData{
+
+ return {
+ title: 'addon.block_recentlyaccesseditems.pluginname',
+ class: 'addon-block-recentlyaccesseditems',
+ component: AddonBlockRecentlyAccessedItemsComponent,
+ };
+ }
+
+}
+
+export class AddonBlockRecentlyAccessedItemsHandler extends makeSingleton(AddonBlockRecentlyAccessedItemsHandlerService) {}
diff --git a/src/addons/block/recentlyaccesseditems/services/recentlyaccesseditems.ts b/src/addons/block/recentlyaccesseditems/services/recentlyaccesseditems.ts
new file mode 100644
index 000000000..42376d2c5
--- /dev/null
+++ b/src/addons/block/recentlyaccesseditems/services/recentlyaccesseditems.ts
@@ -0,0 +1,102 @@
+// (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 { Injectable } from '@angular/core';
+import { CoreSites } from '@services/sites';
+import { CoreDomUtils } from '@services/utils/dom';
+import { CoreCourse } from '@features/course/services/course';
+import { CoreSiteWSPreSets } from '@classes/site';
+import { makeSingleton } from '@singletons';
+
+const ROOT_CACHE_KEY = 'AddonBlockRecentlyAccessedItems:';
+
+/**
+ * Service that provides some features regarding recently accessed items.
+ */
+@Injectable( { providedIn: 'root' })
+export class AddonBlockRecentlyAccessedItemsProvider {
+
+ /**
+ * Get cache key for get last accessed items value WS call.
+ *
+ * @return Cache key.
+ */
+ protected getRecentItemsCacheKey(): string {
+ return ROOT_CACHE_KEY + ':recentitems';
+ }
+
+ /**
+ * Get last accessed items.
+ *
+ * @param siteId Site ID. If not defined, use current site.
+ * @return Promise resolved when the info is retrieved.
+ */
+ async getRecentItems(siteId?: string): Promise {
+ const site = await CoreSites.instance.getSite(siteId);
+
+ const preSets: CoreSiteWSPreSets = {
+ cacheKey: this.getRecentItemsCacheKey(),
+ };
+
+ const items: AddonBlockRecentlyAccessedItemsItem[] =
+ await site.read('block_recentlyaccesseditems_get_recent_items', undefined, preSets);
+
+ return items.map((item) => {
+ const modicon = item.icon && CoreDomUtils.instance.getHTMLElementAttribute(item.icon, 'src');
+
+ item.iconUrl = CoreCourse.instance.getModuleIconSrc(item.modname, modicon || undefined);
+
+ return item;
+ });
+ }
+
+ /**
+ * Invalidates get last accessed items WS call.
+ *
+ * @param siteId Site ID to invalidate. If not defined, use current site.
+ * @return Promise resolved when the data is invalidated.
+ */
+ async invalidateRecentItems(siteId?: string): Promise {
+ const site = await CoreSites.instance.getSite(siteId);
+
+ await site.invalidateWsCacheForKey(this.getRecentItemsCacheKey());
+ }
+
+}
+export class AddonBlockRecentlyAccessedItems extends makeSingleton(AddonBlockRecentlyAccessedItemsProvider) {}
+
+
+/**
+ * Result of WS block_recentlyaccesseditems_get_recent_items.
+ */
+export type AddonBlockRecentlyAccessedItemsItem = {
+ id: number; // Id.
+ courseid: number; // Courseid.
+ cmid: number; // Cmid.
+ userid: number; // Userid.
+ modname: string; // Modname.
+ name: string; // Name.
+ coursename: string; // Coursename.
+ timeaccess: number; // Timeaccess.
+ viewurl: string; // Viewurl.
+ courseviewurl: string; // Courseviewurl.
+ icon: string; // Icon.
+} & AddonBlockRecentlyAccessedItemsItemCalculatedData;
+
+/**
+ * Calculated data for recently accessed item.
+ */
+export type AddonBlockRecentlyAccessedItemsItemCalculatedData = {
+ iconUrl: string; // Icon URL. Calculated by the app.
+};
diff --git a/src/core/features/courses/components/course-progress/course-progress.scss b/src/core/features/courses/components/course-progress/course-progress.scss
index 50dc956eb..f73f02035 100644
--- a/src/core/features/courses/components/course-progress/course-progress.scss
+++ b/src/core/features/courses/components/course-progress/course-progress.scss
@@ -1,3 +1,5 @@
+@import "~theme/globals";
+
:host {
ion-card {
display: flex;
@@ -107,21 +109,8 @@
}
}
-// @todo
:host-context(.core-horizontal-scroll) {
- flex: 0 0 80%;
- min-width: 250px;
- max-width: 300px;
- align-self: stretch;
- display: block;
-
- [text-wrap] .label {
- h2, p {
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
- }
- }
+ @include horizontal_scroll_item(80%, 250px, 300px);
ion-card {
.core-course-thumb {
diff --git a/src/theme/globals.mixins.scss b/src/theme/globals.mixins.scss
index 21c70a4eb..45233a3a1 100644
--- a/src/theme/globals.mixins.scss
+++ b/src/theme/globals.mixins.scss
@@ -185,3 +185,32 @@
}
}
}
+
+@mixin horizontal_scroll_item($width, $min-width, $max-width) {
+ flex: 0 0 $width;
+ min-width: $min-width;
+ max-width: $max-width;
+ align-self: stretch;
+ display: block;
+
+ ion-card {
+ height: calc(100% - 20px);
+ width: calc(100% - 20px);
+ margin-top: 10px;
+ margin-bottom: 10px;
+
+ @media (max-width: 360px) {
+ margin-left: 6px;
+ margin-right: 6px;
+ width: calc(100% - 12px);
+ }
+ }
+
+ .ion-text-wrap ion-label {
+ h2, p {
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ }
+ }
+}