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; + } + } +}