MOBILE-3708 core: Fix and implement some missing todos
parent
ad4953f396
commit
400bc6840f
|
@ -0,0 +1,29 @@
|
||||||
|
// (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';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Component to display a Bootstrap Tooltip in a popover.
|
||||||
|
*/
|
||||||
|
@Component({
|
||||||
|
selector: 'core-bs-tooltip',
|
||||||
|
templateUrl: 'core-bs-tooltip.html',
|
||||||
|
})
|
||||||
|
export class CoreBSTooltipComponent {
|
||||||
|
|
||||||
|
@Input() content = '';
|
||||||
|
@Input() html?: boolean;
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
<ion-item class="ion-text-wrap">
|
||||||
|
<ion-label>
|
||||||
|
<p *ngIf="html" [innerHTML]="content"></p>
|
||||||
|
<p *ngIf="!html">{{content}}</p>
|
||||||
|
</ion-label>
|
||||||
|
</ion-item>
|
|
@ -51,6 +51,7 @@ import { CorePipesModule } from '@pipes/pipes.module';
|
||||||
import { CoreAttachmentsComponent } from './attachments/attachments';
|
import { CoreAttachmentsComponent } from './attachments/attachments';
|
||||||
import { CoreFilesComponent } from './files/files';
|
import { CoreFilesComponent } from './files/files';
|
||||||
import { CoreLocalFileComponent } from './local-file/local-file';
|
import { CoreLocalFileComponent } from './local-file/local-file';
|
||||||
|
import { CoreBSTooltipComponent } from './bs-tooltip/bs-tooltip';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
declarations: [
|
declarations: [
|
||||||
|
@ -84,6 +85,7 @@ import { CoreLocalFileComponent } from './local-file/local-file';
|
||||||
CoreAttachmentsComponent,
|
CoreAttachmentsComponent,
|
||||||
CoreFilesComponent,
|
CoreFilesComponent,
|
||||||
CoreLocalFileComponent,
|
CoreLocalFileComponent,
|
||||||
|
CoreBSTooltipComponent,
|
||||||
],
|
],
|
||||||
imports: [
|
imports: [
|
||||||
CommonModule,
|
CommonModule,
|
||||||
|
@ -124,6 +126,7 @@ import { CoreLocalFileComponent } from './local-file/local-file';
|
||||||
CoreAttachmentsComponent,
|
CoreAttachmentsComponent,
|
||||||
CoreFilesComponent,
|
CoreFilesComponent,
|
||||||
CoreLocalFileComponent,
|
CoreLocalFileComponent,
|
||||||
|
CoreBSTooltipComponent,
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
export class CoreComponentsModule {}
|
export class CoreComponentsModule {}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
ion-app.app-root core-iframe {
|
@import "~theme/globals";
|
||||||
|
|
||||||
|
:host {
|
||||||
> div {
|
> div {
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
max-height: 100%;
|
max-height: 100%;
|
||||||
|
@ -8,7 +9,7 @@ ion-app.app-root core-iframe {
|
||||||
border: 0;
|
border: 0;
|
||||||
display: block;
|
display: block;
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
background-color: $gray-light;
|
background-color: var(--gray-light);
|
||||||
}
|
}
|
||||||
|
|
||||||
.core-loading-container {
|
.core-loading-container {
|
||||||
|
|
|
@ -27,6 +27,7 @@ import { CoreLogger } from '@singletons/logger';
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'core-iframe',
|
selector: 'core-iframe',
|
||||||
templateUrl: 'core-iframe.html',
|
templateUrl: 'core-iframe.html',
|
||||||
|
styleUrls: ['iframe.scss'],
|
||||||
})
|
})
|
||||||
export class CoreIframeComponent implements OnChanges {
|
export class CoreIframeComponent implements OnChanges {
|
||||||
|
|
||||||
|
|
|
@ -22,10 +22,10 @@
|
||||||
</core-course-module-completion>
|
</core-course-module-completion>
|
||||||
|
|
||||||
<div class="core-module-buttons-more">
|
<div class="core-module-buttons-more">
|
||||||
<!-- @todo <core-download-refresh [status]="downloadStatus" [enabled]="downloadEnabled"
|
<core-download-refresh [status]="downloadStatus" [enabled]="downloadEnabled"
|
||||||
[canTrustDownload]="canCheckUpdates" [loading]="spinner || module.handlerData.spinner"
|
[canTrustDownload]="canCheckUpdates" [loading]="spinner || module.handlerData.spinner"
|
||||||
(action)="download($event)">
|
(action)="download($event)">
|
||||||
</core-download-refresh> -->
|
</core-download-refresh>
|
||||||
|
|
||||||
<!-- Buttons defined by the module handler. -->
|
<!-- Buttons defined by the module handler. -->
|
||||||
<ion-button fill="clear" *ngFor="let button of module.handlerData.buttons" color="dark"
|
<ion-button fill="clear" *ngFor="let button of module.handlerData.buttons" color="dark"
|
||||||
|
|
|
@ -16,14 +16,14 @@ import { Component, Input, OnInit, OnDestroy } from '@angular/core';
|
||||||
import { CoreEventCourseStatusChanged, CoreEventObserver, CoreEvents } from '@singletons/events';
|
import { CoreEventCourseStatusChanged, CoreEventObserver, CoreEvents } from '@singletons/events';
|
||||||
import { CoreSites } from '@services/sites';
|
import { CoreSites } from '@services/sites';
|
||||||
import { CoreDomUtils } from '@services/utils/dom';
|
import { CoreDomUtils } from '@services/utils/dom';
|
||||||
// import { CoreUser } from '@core/user/services/user';
|
import { CoreCourses, CoreCoursesMyCoursesUpdatedEventData, CoreCoursesProvider } from '@features/courses/services/courses';
|
||||||
import { CoreCourses } from '@features/courses/services/courses';
|
|
||||||
import { CoreCourse, CoreCourseProvider } from '@features/course/services/course';
|
import { CoreCourse, CoreCourseProvider } from '@features/course/services/course';
|
||||||
import { CoreCourseHelper, CorePrefetchStatusInfo } from '@features/course/services/course-helper';
|
import { CoreCourseHelper, CorePrefetchStatusInfo } from '@features/course/services/course-helper';
|
||||||
import { PopoverController, Translate } from '@singletons';
|
import { PopoverController, Translate } from '@singletons';
|
||||||
import { CoreConstants } from '@/core/constants';
|
import { CoreConstants } from '@/core/constants';
|
||||||
import { CoreEnrolledCourseDataWithExtraInfoAndOptions } from '../../services/courses-helper';
|
import { CoreEnrolledCourseDataWithExtraInfoAndOptions } from '../../services/courses-helper';
|
||||||
import { CoreCoursesCourseOptionsMenuComponent } from '../course-options-menu/course-options-menu';
|
import { CoreCoursesCourseOptionsMenuComponent } from '../course-options-menu/course-options-menu';
|
||||||
|
import { CoreUser } from '@features/user/services/user';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This component is meant to display a course for a list of courses with progress.
|
* This component is meant to display a course for a list of courses with progress.
|
||||||
|
@ -193,7 +193,6 @@ export class CoreCoursesCourseProgressComponent implements OnInit, OnDestroy {
|
||||||
* Show the context menu.
|
* Show the context menu.
|
||||||
*
|
*
|
||||||
* @param e Click Event.
|
* @param e Click Event.
|
||||||
* @todo
|
|
||||||
*/
|
*/
|
||||||
async showCourseOptionsMenu(e: Event): Promise<void> {
|
async showCourseOptionsMenu(e: Event): Promise<void> {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
@ -247,22 +246,62 @@ export class CoreCoursesCourseProgressComponent implements OnInit, OnDestroy {
|
||||||
* Hide/Unhide the course from the course list.
|
* Hide/Unhide the course from the course list.
|
||||||
*
|
*
|
||||||
* @param hide True to hide and false to show.
|
* @param hide True to hide and false to show.
|
||||||
* @todo CoreUser
|
|
||||||
*/
|
*/
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
protected async setCourseHidden(hide: boolean): Promise<void> {
|
||||||
protected setCourseHidden(hide: boolean): void {
|
this.showSpinner = true;
|
||||||
return;
|
|
||||||
|
// We should use null to unset the preference.
|
||||||
|
try {
|
||||||
|
await CoreUser.updateUserPreference(
|
||||||
|
'block_myoverview_hidden_course_' + this.course.id,
|
||||||
|
hide ? '1' : undefined,
|
||||||
|
);
|
||||||
|
|
||||||
|
this.course.hidden = hide;
|
||||||
|
CoreEvents.trigger<CoreCoursesMyCoursesUpdatedEventData>(CoreCoursesProvider.EVENT_MY_COURSES_UPDATED, {
|
||||||
|
courseId: this.course.id,
|
||||||
|
course: this.course,
|
||||||
|
action: CoreCoursesProvider.ACTION_STATE_CHANGED,
|
||||||
|
state: CoreCoursesProvider.STATE_HIDDEN,
|
||||||
|
value: hide,
|
||||||
|
}, CoreSites.getCurrentSiteId());
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
if (!this.isDestroyed) {
|
||||||
|
CoreDomUtils.showErrorModalDefault(error, 'Error changing course visibility.');
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
this.showSpinner = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Favourite/Unfavourite the course from the course list.
|
* Favourite/Unfavourite the course from the course list.
|
||||||
*
|
*
|
||||||
* @param favourite True to favourite and false to unfavourite.
|
* @param favourite True to favourite and false to unfavourite.
|
||||||
* @todo CoreUser
|
|
||||||
*/
|
*/
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
protected async setCourseFavourite(favourite: boolean): Promise<void> {
|
||||||
protected setCourseFavourite(favourite: boolean): void {
|
this.showSpinner = true;
|
||||||
return;
|
|
||||||
|
try {
|
||||||
|
await CoreCourses.setFavouriteCourse(this.course.id, favourite);
|
||||||
|
|
||||||
|
this.course.isfavourite = favourite;
|
||||||
|
CoreEvents.trigger<CoreCoursesMyCoursesUpdatedEventData>(CoreCoursesProvider.EVENT_MY_COURSES_UPDATED, {
|
||||||
|
courseId: this.course.id,
|
||||||
|
course: this.course,
|
||||||
|
action: CoreCoursesProvider.ACTION_STATE_CHANGED,
|
||||||
|
state: CoreCoursesProvider.STATE_FAVOURITE,
|
||||||
|
value: favourite,
|
||||||
|
}, CoreSites.getCurrentSiteId());
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
if (!this.isDestroyed) {
|
||||||
|
CoreDomUtils.showErrorModalDefault(error, 'Error changing course favourite attribute.');
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
this.showSpinner = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -450,11 +450,11 @@ export class CoreCoursesCoursePreviewPage implements OnInit, OnDestroy {
|
||||||
* Prefetch the course.
|
* Prefetch the course.
|
||||||
*/
|
*/
|
||||||
prefetchCourse(): void {
|
prefetchCourse(): void {
|
||||||
/* @todo CoreCourseHelper.confirmAndPrefetchCourse(this.prefetchCourseData, this.course).catch((error) => {
|
CoreCourseHelper.confirmAndPrefetchCourse(this.prefetchCourseData, this.course!).catch((error) => {
|
||||||
if (!this.pageDestroyed) {
|
if (!this.pageDestroyed) {
|
||||||
CoreDomUtils.showErrorModalDefault(error, 'core.course.errordownloadingcourse', true);
|
CoreDomUtils.showErrorModalDefault(error, 'core.course.errordownloadingcourse', true);
|
||||||
}
|
}
|
||||||
});*/
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -20,6 +20,8 @@ import { CoreSiteBasicInfo, CoreSites } from '@services/sites';
|
||||||
import { CoreLogger } from '@singletons/logger';
|
import { CoreLogger } from '@singletons/logger';
|
||||||
import { CoreLoginHelper } from '@features/login/services/login-helper';
|
import { CoreLoginHelper } from '@features/login/services/login-helper';
|
||||||
import { CoreNavigator } from '@services/navigator';
|
import { CoreNavigator } from '@services/navigator';
|
||||||
|
import { CorePushNotifications } from '@features/pushnotifications/services/pushnotifications';
|
||||||
|
import { CoreFilter } from '@features/filter/services/filter';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Page that displays a "splash screen" while the app is being initialized.
|
* Page that displays a "splash screen" while the app is being initialized.
|
||||||
|
@ -48,13 +50,12 @@ export class CoreLoginSitesPage implements OnInit {
|
||||||
const sites = await CoreUtils.ignoreErrors(CoreSites.getSortedSites(), [] as CoreSiteBasicInfo[]);
|
const sites = await CoreUtils.ignoreErrors(CoreSites.getSortedSites(), [] as CoreSiteBasicInfo[]);
|
||||||
|
|
||||||
// Remove protocol from the url to show more url text.
|
// Remove protocol from the url to show more url text.
|
||||||
this.sites = sites.map((site) => {
|
this.sites = await Promise.all(sites.map(async (site) => {
|
||||||
site.siteUrl = site.siteUrl.replace(/^https?:\/\//, '');
|
site.siteUrl = site.siteUrl.replace(/^https?:\/\//, '');
|
||||||
site.badge = 0;
|
site.badge = await CoreUtils.ignoreErrors(CorePushNotifications.getSiteCounter(site.id)) || 0;
|
||||||
// @todo: getSiteCounter.
|
|
||||||
|
|
||||||
return site;
|
return site;
|
||||||
});
|
}));
|
||||||
|
|
||||||
this.showDelete = false;
|
this.showDelete = false;
|
||||||
}
|
}
|
||||||
|
@ -76,9 +77,9 @@ export class CoreLoginSitesPage implements OnInit {
|
||||||
async deleteSite(e: Event, site: CoreSiteBasicInfo): Promise<void> {
|
async deleteSite(e: Event, site: CoreSiteBasicInfo): Promise<void> {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
|
||||||
const siteName = site.siteName || '';
|
let siteName = site.siteName || '';
|
||||||
|
|
||||||
// @todo: Format text: siteName.
|
siteName = await CoreFilter.formatText(siteName, { clean: true, singleLine: true, filter: false }, [], site.id);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await CoreDomUtils.showDeleteConfirm('core.login.confirmdeletesite', { sitename: siteName });
|
await CoreDomUtils.showDeleteConfirm('core.login.confirmdeletesite', { sitename: siteName });
|
||||||
|
|
|
@ -146,10 +146,7 @@ export class CoreMainMenuMorePage implements OnInit, OnDestroy {
|
||||||
* @param item Item to open.
|
* @param item Item to open.
|
||||||
*/
|
*/
|
||||||
openItem(item: CoreMainMenuCustomItem): void {
|
openItem(item: CoreMainMenuCustomItem): void {
|
||||||
// @todo CoreNavigator.navigateToSitePath('CoreViewerIframePage', {title: item.label, url: item.url});
|
CoreNavigator.navigateToSitePath('viewer/iframe', { params: { title: item.label, url: item.url } });
|
||||||
|
|
||||||
// eslint-disable-next-line no-console
|
|
||||||
console.error('openItem not implemented', item);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -819,7 +819,6 @@ export class CoreQuestionHelperProvider {
|
||||||
if (span.innerHTML) {
|
if (span.innerHTML) {
|
||||||
// There's a hidden feedback. Mark the icon as tappable.
|
// There's a hidden feedback. Mark the icon as tappable.
|
||||||
// The click listener is only added if treatCorrectnessIconsClicks is called.
|
// The click listener is only added if treatCorrectnessIconsClicks is called.
|
||||||
// @todo: Check if another attribute needs to be used now instead of tappable.
|
|
||||||
icon.setAttribute('tappable', '');
|
icon.setAttribute('tappable', '');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -843,9 +842,7 @@ export class CoreQuestionHelperProvider {
|
||||||
contextInstanceId?: number,
|
contextInstanceId?: number,
|
||||||
courseId?: number,
|
courseId?: number,
|
||||||
): void {
|
): void {
|
||||||
|
const icons = <HTMLElement[]> Array.from(element.querySelectorAll('ion-icon.questioncorrectnessicon[tappable]'));
|
||||||
// @todo: Check if another attribute needs to be used now instead of tappable.
|
|
||||||
const icons = <HTMLElement[]> Array.from(element.querySelectorAll('i.icon.questioncorrectnessicon[tappable]'));
|
|
||||||
const title = Translate.instant('core.question.feedback');
|
const title = Translate.instant('core.question.feedback');
|
||||||
|
|
||||||
icons.forEach((icon) => {
|
icons.forEach((icon) => {
|
||||||
|
|
|
@ -153,8 +153,6 @@ export class CoreSettingsGeneralPage {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when the analytics setting is enabled or disabled.
|
* Called when the analytics setting is enabled or disabled.
|
||||||
*
|
|
||||||
* @todo
|
|
||||||
*/
|
*/
|
||||||
async analyticsEnabledChanged(): Promise<void> {
|
async analyticsEnabledChanged(): Promise<void> {
|
||||||
await CorePushNotifications.enableAnalytics(this.analyticsEnabled);
|
await CorePushNotifications.enableAnalytics(this.analyticsEnabled);
|
||||||
|
|
|
@ -37,7 +37,6 @@ export interface CoreTagAreaHandler extends CoreDelegateHandler {
|
||||||
* Get the component to use to display items.
|
* Get the component to use to display items.
|
||||||
*
|
*
|
||||||
* @return The component (or promise resolved with component) to use, undefined if not found.
|
* @return The component (or promise resolved with component) to use, undefined if not found.
|
||||||
* @todo, check return types.
|
|
||||||
*/
|
*/
|
||||||
getComponent(): Type<unknown> | Promise<Type<unknown>>;
|
getComponent(): Type<unknown> | Promise<Type<unknown>>;
|
||||||
}
|
}
|
||||||
|
@ -85,7 +84,6 @@ export class CoreTagAreaDelegateService extends CoreDelegate<CoreTagAreaHandler>
|
||||||
* @param component Component name.
|
* @param component Component name.
|
||||||
* @param itemType Item type.
|
* @param itemType Item type.
|
||||||
* @return The component (or promise resolved with component) to use, undefined if not found.
|
* @return The component (or promise resolved with component) to use, undefined if not found.
|
||||||
* @todo, check return types.
|
|
||||||
*/
|
*/
|
||||||
async getComponent(component: string, itemType: string): Promise<Type<unknown> | undefined> {
|
async getComponent(component: string, itemType: string): Promise<Type<unknown> | undefined> {
|
||||||
const type = component + '/' + itemType;
|
const type = component + '/' + itemType;
|
||||||
|
|
|
@ -35,6 +35,7 @@ import { CoreFileUploaderHelper } from '@features/fileuploader/services/fileuplo
|
||||||
import { CoreIonLoadingElement } from '@classes/ion-loading';
|
import { CoreIonLoadingElement } from '@classes/ion-loading';
|
||||||
import { CoreUtils } from '@services/utils/utils';
|
import { CoreUtils } from '@services/utils/utils';
|
||||||
import { CoreNavigator } from '@services/navigator';
|
import { CoreNavigator } from '@services/navigator';
|
||||||
|
import { CoreCourses } from '@features/courses/services/courses';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'page-core-user-profile',
|
selector: 'page-core-user-profile',
|
||||||
|
@ -239,8 +240,8 @@ export class CoreUserProfilePage implements OnInit, OnDestroy {
|
||||||
async refreshUser(event?: CustomEvent<IonRefresher>): Promise<void> {
|
async refreshUser(event?: CustomEvent<IonRefresher>): Promise<void> {
|
||||||
await CoreUtils.ignoreErrors(Promise.all([
|
await CoreUtils.ignoreErrors(Promise.all([
|
||||||
CoreUser.invalidateUserCache(this.userId),
|
CoreUser.invalidateUserCache(this.userId),
|
||||||
// @todo this.coursesProvider.invalidateUserNavigationOptions(),
|
CoreCourses.invalidateUserNavigationOptions(),
|
||||||
// this.coursesProvider.invalidateUserAdministrationOptions()
|
CoreCourses.invalidateUserAdministrationOptions(),
|
||||||
]));
|
]));
|
||||||
|
|
||||||
await this.fetchUser();
|
await this.fetchUser();
|
||||||
|
|
|
@ -20,7 +20,8 @@ import { CoreUtils } from '@services/utils/utils';
|
||||||
import { CoreEvents } from '@singletons/events';
|
import { CoreEvents } from '@singletons/events';
|
||||||
import { CoreUserProfile } from './user';
|
import { CoreUserProfile } from './user';
|
||||||
import { makeSingleton } from '@singletons';
|
import { makeSingleton } from '@singletons';
|
||||||
import { CoreCourseUserAdminOrNavOptionIndexed } from '@features/courses/services/courses';
|
import { CoreCourses, CoreCourseUserAdminOrNavOptionIndexed } from '@features/courses/services/courses';
|
||||||
|
import { CoreSites } from '@services/sites';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Interface that all user profile handlers must implement.
|
* Interface that all user profile handlers must implement.
|
||||||
|
@ -241,9 +242,22 @@ export class CoreUserDelegateService extends CoreDelegate<CoreUserProfileHandler
|
||||||
* @return Promise resolved when done.
|
* @return Promise resolved when done.
|
||||||
*/
|
*/
|
||||||
protected async calculateUserHandlers(user: CoreUserProfile, courseId?: number): Promise<void> {
|
protected async calculateUserHandlers(user: CoreUserProfile, courseId?: number): Promise<void> {
|
||||||
// @todo: Get Course admin/nav options.
|
let navOptions: CoreCourseUserAdminOrNavOptionIndexed | undefined;
|
||||||
let navOptions;
|
let admOptions: CoreCourseUserAdminOrNavOptionIndexed | undefined;
|
||||||
let admOptions;
|
|
||||||
|
if (CoreCourses.canGetAdminAndNavOptions()) {
|
||||||
|
// Get course options.
|
||||||
|
const courses = await CoreCourses.getUserCourses(true);
|
||||||
|
const courseIds = courses.map((course) => course.id);
|
||||||
|
|
||||||
|
const options = await CoreCourses.getCoursesAdminAndNavOptions(courseIds);
|
||||||
|
|
||||||
|
// For backwards compatibility we don't modify the courseId.
|
||||||
|
const courseIdForOptions = courseId || CoreSites.getCurrentSiteHomeId();
|
||||||
|
|
||||||
|
navOptions = options.navOptions[courseIdForOptions];
|
||||||
|
admOptions = options.admOptions[courseIdForOptions];
|
||||||
|
}
|
||||||
|
|
||||||
const userData = this.userHandlers[user.id];
|
const userData = this.userHandlers[user.id];
|
||||||
userData.handlers = [];
|
userData.handlers = [];
|
||||||
|
|
|
@ -759,7 +759,7 @@ export class CoreUserProvider {
|
||||||
* @param siteId Site ID. If not defined, current site.
|
* @param siteId Site ID. If not defined, current site.
|
||||||
* @return Promise resolved if success.
|
* @return Promise resolved if success.
|
||||||
*/
|
*/
|
||||||
updateUserPreference(name: string, value: string, userId?: number, siteId?: string): Promise<void> {
|
updateUserPreference(name: string, value: string | undefined, userId?: number, siteId?: string): Promise<void> {
|
||||||
const preferences = [
|
const preferences = [
|
||||||
{
|
{
|
||||||
type: name,
|
type: name,
|
||||||
|
@ -780,7 +780,7 @@ export class CoreUserProvider {
|
||||||
* @return Promise resolved if success.
|
* @return Promise resolved if success.
|
||||||
*/
|
*/
|
||||||
async updateUserPreferences(
|
async updateUserPreferences(
|
||||||
preferences: { type: string; value: string }[],
|
preferences: { type: string; value: string | undefined }[],
|
||||||
disableNotifications?: boolean,
|
disableNotifications?: boolean,
|
||||||
userId?: number,
|
userId?: number,
|
||||||
siteId?: string,
|
siteId?: string,
|
||||||
|
|
|
@ -15,16 +15,19 @@
|
||||||
import { NgModule } from '@angular/core';
|
import { NgModule } from '@angular/core';
|
||||||
|
|
||||||
import { CoreSharedModule } from '@/core/shared.module';
|
import { CoreSharedModule } from '@/core/shared.module';
|
||||||
|
import { CoreViewerImageComponent } from './image/image';
|
||||||
import { CoreViewerTextComponent } from './text/text';
|
import { CoreViewerTextComponent } from './text/text';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
declarations: [
|
declarations: [
|
||||||
|
CoreViewerImageComponent,
|
||||||
CoreViewerTextComponent,
|
CoreViewerTextComponent,
|
||||||
],
|
],
|
||||||
imports: [
|
imports: [
|
||||||
CoreSharedModule,
|
CoreSharedModule,
|
||||||
],
|
],
|
||||||
exports: [
|
exports: [
|
||||||
|
CoreViewerImageComponent,
|
||||||
CoreViewerTextComponent,
|
CoreViewerTextComponent,
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
|
|
|
@ -0,0 +1,14 @@
|
||||||
|
<ion-header>
|
||||||
|
<ion-toolbar>
|
||||||
|
<ion-title>{{ title }}</ion-title>
|
||||||
|
<ion-buttons slot="end">
|
||||||
|
<ion-button (click)="closeModal()" [attr.aria-label]="'core.close' | translate">
|
||||||
|
<ion-icon name="fas-times" slot="icon-only"></ion-icon>
|
||||||
|
</ion-button>
|
||||||
|
</ion-buttons>
|
||||||
|
</ion-toolbar>
|
||||||
|
</ion-header>
|
||||||
|
<!-- @todo: zoom="true" maxZoom="2" class="core-zoom-pane". Now we need to use ionSlider? -->
|
||||||
|
<ion-content [scrollX]="true" [scrollY]="true">
|
||||||
|
<img [src]="image" [alt]="title" core-external-content [component]="component" [componentId]="componentId">
|
||||||
|
</ion-content>
|
|
@ -0,0 +1,9 @@
|
||||||
|
:host {
|
||||||
|
.core-zoom-pane {
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
|
img {
|
||||||
|
max-width: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,44 @@
|
||||||
|
// (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, OnInit } from '@angular/core';
|
||||||
|
|
||||||
|
import { ModalController, Translate } from '@singletons';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Modal component to view an image.
|
||||||
|
*/
|
||||||
|
@Component({
|
||||||
|
selector: 'core-viewer-image',
|
||||||
|
templateUrl: 'image.html',
|
||||||
|
})
|
||||||
|
export class CoreViewerImageComponent implements OnInit {
|
||||||
|
|
||||||
|
@Input() title?: string; // Modal title.
|
||||||
|
@Input() image?: string; // Image URL.
|
||||||
|
@Input() component?: string; // Component to use in external-content.
|
||||||
|
@Input() componentId?: string | number; // Component ID to use in external-content.
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.title = this.title || Translate.instant('core.imageviewer');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Close modal.
|
||||||
|
*/
|
||||||
|
closeModal(): void {
|
||||||
|
ModalController.dismiss();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
<ion-header>
|
||||||
|
<ion-toolbar>
|
||||||
|
<ion-buttons slot="start">
|
||||||
|
<ion-back-button [attr.aria-label]="'core.back' | translate"></ion-back-button>
|
||||||
|
</ion-buttons>
|
||||||
|
<ion-title>{{ title }}</ion-title>
|
||||||
|
</ion-toolbar>
|
||||||
|
</ion-header>
|
||||||
|
<ion-content>
|
||||||
|
<core-loading [hideUntil]="finalUrl">
|
||||||
|
<core-iframe *ngIf="finalUrl" [src]="finalUrl"></core-iframe>
|
||||||
|
</core-loading>
|
||||||
|
</ion-content>
|
|
@ -0,0 +1,38 @@
|
||||||
|
// (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 { RouterModule, Routes } from '@angular/router';
|
||||||
|
|
||||||
|
import { CoreSharedModule } from '@/core/shared.module';
|
||||||
|
import { CoreViewerIframePage } from './iframe';
|
||||||
|
|
||||||
|
const routes: Routes = [
|
||||||
|
{
|
||||||
|
path: '',
|
||||||
|
component: CoreViewerIframePage,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
imports: [
|
||||||
|
RouterModule.forChild(routes),
|
||||||
|
CoreSharedModule,
|
||||||
|
],
|
||||||
|
declarations: [
|
||||||
|
CoreViewerIframePage,
|
||||||
|
],
|
||||||
|
exports: [RouterModule],
|
||||||
|
})
|
||||||
|
export class CoreViewerIframePageModule {}
|
|
@ -0,0 +1,57 @@
|
||||||
|
// (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 { CoreNavigator } from '@services/navigator';
|
||||||
|
|
||||||
|
import { CoreSites } from '@services/sites';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Page to display a URL in an iframe.
|
||||||
|
*/
|
||||||
|
@Component({
|
||||||
|
selector: 'core-viewer-iframe',
|
||||||
|
templateUrl: 'iframe.html',
|
||||||
|
})
|
||||||
|
export class CoreViewerIframePage implements OnInit {
|
||||||
|
|
||||||
|
title?: string; // Page title.
|
||||||
|
url?: string; // Iframe URL.
|
||||||
|
/* Whether the URL should be open with auto-login. Accepts the following values:
|
||||||
|
"yes" -> Always auto-login.
|
||||||
|
"no" -> Never auto-login.
|
||||||
|
"check" -> Auto-login only if it points to the current site. Default value. */
|
||||||
|
autoLogin?: string;
|
||||||
|
finalUrl?: string;
|
||||||
|
|
||||||
|
async ngOnInit(): Promise<void> {
|
||||||
|
this.title = CoreNavigator.getRouteParam('title');
|
||||||
|
this.url = CoreNavigator.getRouteParam('url');
|
||||||
|
this.autoLogin = CoreNavigator.getRouteParam('autoLogin') || 'check';
|
||||||
|
|
||||||
|
if (!this.url) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const currentSite = CoreSites.getCurrentSite();
|
||||||
|
|
||||||
|
if (currentSite && (this.autoLogin == 'yes' || (this.autoLogin == 'check' && currentSite.containsUrl(this.url)))) {
|
||||||
|
// Format the URL to add auto-login.
|
||||||
|
this.finalUrl = await currentSite.getAutoLoginUrl(this.url, false);
|
||||||
|
} else {
|
||||||
|
this.finalUrl = this.url;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,28 @@
|
||||||
|
// (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 { RouterModule, Routes } from '@angular/router';
|
||||||
|
|
||||||
|
const routes: Routes = [
|
||||||
|
{
|
||||||
|
path: 'iframe',
|
||||||
|
loadChildren: () => import('./pages/iframe/iframe.module').then( m => m.CoreViewerIframePageModule),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
imports: [RouterModule.forChild(routes)],
|
||||||
|
})
|
||||||
|
export class CoreViewerLazyModule {}
|
|
@ -13,11 +13,21 @@
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
import { NgModule } from '@angular/core';
|
import { NgModule } from '@angular/core';
|
||||||
|
import { Routes } from '@angular/router';
|
||||||
|
|
||||||
|
import { CoreMainMenuTabRoutingModule } from '@features/mainmenu/mainmenu-tab-routing.module';
|
||||||
import { CoreViewerComponentsModule } from './components/components.module';
|
import { CoreViewerComponentsModule } from './components/components.module';
|
||||||
|
|
||||||
|
const routes: Routes = [
|
||||||
|
{
|
||||||
|
path: 'viewer',
|
||||||
|
loadChildren: () => import('./viewer-lazy.module').then(m => m.CoreViewerLazyModule),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
|
CoreMainMenuTabRoutingModule.forChild(routes),
|
||||||
CoreViewerComponentsModule,
|
CoreViewerComponentsModule,
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
|
|
|
@ -19,6 +19,7 @@ import { CoreSite, CoreSiteWSPreSets } from '@classes/site';
|
||||||
import { CoreError } from '@classes/errors/error';
|
import { CoreError } from '@classes/errors/error';
|
||||||
import { makeSingleton, Translate } from '@singletons';
|
import { makeSingleton, Translate } from '@singletons';
|
||||||
import { CoreWSExternalWarning } from '@services/ws';
|
import { CoreWSExternalWarning } from '@services/ws';
|
||||||
|
import { CoreCourses } from '@features/courses/services/courses';
|
||||||
|
|
||||||
const ROOT_CACHE_KEY = 'mmGroups:';
|
const ROOT_CACHE_KEY = 'mmGroups:';
|
||||||
|
|
||||||
|
@ -242,8 +243,11 @@ export class CoreGroupsProvider {
|
||||||
return this.getUserGroupsInCourse(0, siteId);
|
return this.getUserGroupsInCourse(0, siteId);
|
||||||
}
|
}
|
||||||
|
|
||||||
// @todo Get courses.
|
const courses = <CoreCourseBase[]> await CoreCourses.getUserCourses(false, siteId);
|
||||||
return <CoreGroup[]>[];
|
|
||||||
|
courses.push({ id: site.getSiteHomeId() }); // Add site home.
|
||||||
|
|
||||||
|
return this.getUserGroups(courses, siteId);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -18,6 +18,7 @@ import { CoreConfig } from '@services/config';
|
||||||
import { CoreConstants } from '@/core/constants';
|
import { CoreConstants } from '@/core/constants';
|
||||||
import { CoreLogger } from '@singletons/logger';
|
import { CoreLogger } from '@singletons/logger';
|
||||||
import { makeSingleton } from '@singletons';
|
import { makeSingleton } from '@singletons';
|
||||||
|
import { CoreH5P } from '@features/h5p/services/h5p';
|
||||||
|
|
||||||
const VERSION_APPLIED = 'version_applied';
|
const VERSION_APPLIED = 'version_applied';
|
||||||
|
|
||||||
|
@ -42,13 +43,13 @@ export class CoreUpdateManagerProvider {
|
||||||
* @return Promise resolved when the update process finishes.
|
* @return Promise resolved when the update process finishes.
|
||||||
*/
|
*/
|
||||||
async load(): Promise<void> {
|
async load(): Promise<void> {
|
||||||
const promises = [];
|
const promises: Promise<unknown>[] = [];
|
||||||
const versionCode = CoreConstants.CONFIG.versioncode;
|
const versionCode = CoreConstants.CONFIG.versioncode;
|
||||||
|
|
||||||
const versionApplied = await CoreConfig.get<number>(VERSION_APPLIED, 0);
|
const versionApplied = await CoreConfig.get<number>(VERSION_APPLIED, 0);
|
||||||
|
|
||||||
if (versionCode >= 3900 && versionApplied < 3900 && versionApplied > 0) {
|
if (versionCode >= 3900 && versionApplied < 3900 && versionApplied > 0) {
|
||||||
// @todo: H5P update.
|
promises.push(CoreH5P.h5pPlayer.deleteAllContentIndexes());
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -31,11 +31,20 @@ import { CoreIonLoadingElement } from '@classes/ion-loading';
|
||||||
import { CoreCanceledError } from '@classes/errors/cancelederror';
|
import { CoreCanceledError } from '@classes/errors/cancelederror';
|
||||||
import { CoreError } from '@classes/errors/error';
|
import { CoreError } from '@classes/errors/error';
|
||||||
import { CoreSilentError } from '@classes/errors/silenterror';
|
import { CoreSilentError } from '@classes/errors/silenterror';
|
||||||
|
import {
|
||||||
import { makeSingleton, Translate, AlertController, LoadingController, ToastController } from '@singletons';
|
makeSingleton,
|
||||||
|
Translate,
|
||||||
|
AlertController,
|
||||||
|
LoadingController,
|
||||||
|
ToastController,
|
||||||
|
PopoverController,
|
||||||
|
ModalController,
|
||||||
|
} from '@singletons';
|
||||||
import { CoreLogger } from '@singletons/logger';
|
import { CoreLogger } from '@singletons/logger';
|
||||||
import { CoreFileSizeSum } from '@services/plugin-file-delegate';
|
import { CoreFileSizeSum } from '@services/plugin-file-delegate';
|
||||||
import { CoreNetworkError } from '@classes/errors/network-error';
|
import { CoreNetworkError } from '@classes/errors/network-error';
|
||||||
|
import { CoreBSTooltipComponent } from '@components/bs-tooltip/bs-tooltip';
|
||||||
|
import { CoreViewerImageComponent } from '@features/viewer/components/image/image';
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* "Utils" service with helper functions for UI, DOM elements and HTML code.
|
* "Utils" service with helper functions for UI, DOM elements and HTML code.
|
||||||
|
@ -810,8 +819,18 @@ export class CoreDomUtilsProvider {
|
||||||
el.setAttribute('data-original-title', content);
|
el.setAttribute('data-original-title', content);
|
||||||
el.setAttribute('title', '');
|
el.setAttribute('title', '');
|
||||||
|
|
||||||
el.addEventListener('click', () => {
|
el.addEventListener('click', async (ev: Event) => {
|
||||||
// @todo
|
const html = el.getAttribute('data-html');
|
||||||
|
|
||||||
|
const popover = await PopoverController.create({
|
||||||
|
component: CoreBSTooltipComponent,
|
||||||
|
componentProps: {
|
||||||
|
content,
|
||||||
|
html: html === 'true',
|
||||||
|
},
|
||||||
|
event: ev,
|
||||||
|
});
|
||||||
|
await popover.present();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -1554,12 +1573,32 @@ export class CoreDomUtilsProvider {
|
||||||
* @param message Modal message.
|
* @param message Modal message.
|
||||||
* @param buttons Buttons to pass to the modal.
|
* @param buttons Buttons to pass to the modal.
|
||||||
* @param placeholder Placeholder of the input element if any.
|
* @param placeholder Placeholder of the input element if any.
|
||||||
* @return Promise resolved when modal presented.
|
* @return Promise resolved with the entered text if any.
|
||||||
*/
|
*/
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
async showTextareaPrompt(
|
||||||
showTextareaPrompt(title: string, message: string, buttons: (string | unknown)[], placeholder?: string): Promise<unknown> {
|
title: string,
|
||||||
// @todo
|
message: string,
|
||||||
return Promise.resolve();
|
buttons: AlertButton[],
|
||||||
|
placeholder?: string,
|
||||||
|
): Promise<string | undefined> {
|
||||||
|
const alert = await AlertController.create({
|
||||||
|
header: title,
|
||||||
|
message,
|
||||||
|
inputs: [
|
||||||
|
{
|
||||||
|
name: 'textarea-prompt',
|
||||||
|
type: 'textarea',
|
||||||
|
placeholder: placeholder,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
buttons,
|
||||||
|
});
|
||||||
|
|
||||||
|
await alert.present();
|
||||||
|
|
||||||
|
const result = await alert.onWillDismiss();
|
||||||
|
|
||||||
|
return result.data?.values?.['textarea-prompt'];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1671,9 +1710,30 @@ export class CoreDomUtilsProvider {
|
||||||
* @param componentId An ID to use in conjunction with the component.
|
* @param componentId An ID to use in conjunction with the component.
|
||||||
* @param fullScreen Whether the modal should be full screen.
|
* @param fullScreen Whether the modal should be full screen.
|
||||||
*/
|
*/
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
async viewImage(
|
||||||
viewImage(image: string, title?: string | null, component?: string, componentId?: string | number, fullScreen?: boolean): void {
|
image: string,
|
||||||
// @todo
|
title?: string | null,
|
||||||
|
component?: string,
|
||||||
|
componentId?: string | number,
|
||||||
|
fullScreen?: boolean,
|
||||||
|
): Promise<void> {
|
||||||
|
if (!image) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const modal = await ModalController.create({
|
||||||
|
component: CoreViewerImageComponent,
|
||||||
|
componentProps: {
|
||||||
|
title,
|
||||||
|
image,
|
||||||
|
component,
|
||||||
|
componentId,
|
||||||
|
},
|
||||||
|
cssClass: fullScreen ? 'core-modal-fullscreen' : '',
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
await modal.present();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
Loading…
Reference in New Issue