MOBILE-3592 user: Implement profile page
parent
d733488963
commit
c608432d24
|
@ -0,0 +1,11 @@
|
||||||
|
<img *ngIf="avatarUrl" [src]="avatarUrl" [alt]="'core.pictureof' | translate:{$a: fullname}" core-external-content
|
||||||
|
onError="this.src='assets/img/user-avatar.png'" role="presentation" (click)="gotoProfile($event)">
|
||||||
|
|
||||||
|
<img *ngIf="!avatarUrl" src="assets/img/user-avatar.png" [alt]="'core.pictureof' | translate:{$a: fullname}" role="presentation"
|
||||||
|
(click)="gotoProfile($event)">
|
||||||
|
|
||||||
|
<span *ngIf="checkOnline && isOnline()" class="contact-status online"></span>
|
||||||
|
|
||||||
|
<img *ngIf="extraIcon" [src]="extraIcon" alt="" role="presentation" class="core-avatar-extra-icon">
|
||||||
|
|
||||||
|
<ng-content></ng-content>
|
|
@ -0,0 +1,33 @@
|
||||||
|
:host {
|
||||||
|
position: relative;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
.contact-status {
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
width: 14px;
|
||||||
|
height: 14px;
|
||||||
|
border-radius: 50%;
|
||||||
|
&.online {
|
||||||
|
border: 1px solid white;
|
||||||
|
background-color: var(--core-online-color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.core-avatar-extra-icon {
|
||||||
|
margin: 0 !important;
|
||||||
|
border-radius: 0 !important;
|
||||||
|
background: none;
|
||||||
|
position: absolute;
|
||||||
|
right: -4px;
|
||||||
|
bottom: -4px;
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
:host-context(.toolbar) .contact-status {
|
||||||
|
width: 10px;
|
||||||
|
height: 10px;
|
||||||
|
}
|
|
@ -0,0 +1,179 @@
|
||||||
|
// (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, OnChanges, OnDestroy, SimpleChange } from '@angular/core';
|
||||||
|
import { ActivatedRoute } from '@angular/router';
|
||||||
|
import { NavController } from '@ionic/angular';
|
||||||
|
|
||||||
|
import { CoreApp } from '@services/app';
|
||||||
|
import { CoreSites } from '@services/sites';
|
||||||
|
import { CoreUtils } from '@services/utils/utils';
|
||||||
|
import { CoreEventObserver, CoreEvents } from '@singletons/events';
|
||||||
|
import { CoreObject } from '@singletons/object';
|
||||||
|
import { CoreUserProvider, CoreUserBasicData, CoreUserProfilePictureUpdatedData } from '@core/user/services/user';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Component to display a "user avatar".
|
||||||
|
*
|
||||||
|
* Example: <ion-avatar core-user-avatar [user]="participant"></ion-avatar>
|
||||||
|
*/
|
||||||
|
@Component({
|
||||||
|
selector: 'core-user-avatar',
|
||||||
|
templateUrl: 'core-user-avatar.html',
|
||||||
|
styleUrls: ['user-avatar.scss'],
|
||||||
|
})
|
||||||
|
export class CoreUserAvatarComponent implements OnInit, OnChanges, OnDestroy {
|
||||||
|
|
||||||
|
@Input() user?: CoreUserWithAvatar;
|
||||||
|
// The following params will override the ones in user object.
|
||||||
|
@Input() profileUrl?: string;
|
||||||
|
@Input() protected linkProfile = true; // Avoid linking to the profile if wanted.
|
||||||
|
@Input() fullname?: string;
|
||||||
|
@Input() protected userId?: number; // If provided or found it will be used to link the image to the profile.
|
||||||
|
@Input() protected courseId?: number;
|
||||||
|
@Input() checkOnline = false; // If want to check and show online status.
|
||||||
|
@Input() extraIcon?: string; // Extra icon to show near the avatar.
|
||||||
|
|
||||||
|
avatarUrl?: string;
|
||||||
|
|
||||||
|
// Variable to check if we consider this user online or not.
|
||||||
|
// @TODO: Use setting when available (see MDL-63972) so we can use site setting.
|
||||||
|
protected timetoshowusers = 300000; // Miliseconds default.
|
||||||
|
protected currentUserId: number;
|
||||||
|
protected pictureObserver: CoreEventObserver;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
protected navCtrl: NavController,
|
||||||
|
protected route: ActivatedRoute,
|
||||||
|
) {
|
||||||
|
this.currentUserId = CoreSites.instance.getCurrentSiteUserId();
|
||||||
|
|
||||||
|
this.pictureObserver = CoreEvents.on<CoreUserProfilePictureUpdatedData>(
|
||||||
|
CoreUserProvider.PROFILE_PICTURE_UPDATED,
|
||||||
|
(data) => {
|
||||||
|
if (data.userId == this.userId) {
|
||||||
|
this.avatarUrl = data.picture;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
CoreSites.instance.getCurrentSiteId(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Component being initialized.
|
||||||
|
*/
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.setFields();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Listen to changes.
|
||||||
|
*/
|
||||||
|
ngOnChanges(changes: { [name: string]: SimpleChange }): void {
|
||||||
|
// If something change, update the fields.
|
||||||
|
if (changes) {
|
||||||
|
this.setFields();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set fields from user.
|
||||||
|
*/
|
||||||
|
protected setFields(): void {
|
||||||
|
const profileUrl = this.profileUrl || (this.user && (this.user.profileimageurl || this.user.userprofileimageurl ||
|
||||||
|
this.user.userpictureurl || this.user.profileimageurlsmall || (this.user.urls && this.user.urls.profileimage)));
|
||||||
|
|
||||||
|
if (typeof profileUrl == 'string') {
|
||||||
|
this.avatarUrl = profileUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.fullname = this.fullname || (this.user && (this.user.fullname || this.user.userfullname));
|
||||||
|
|
||||||
|
this.userId = this.userId || (this.user && (this.user.userid || this.user.id));
|
||||||
|
this.courseId = this.courseId || (this.user && this.user.courseid);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper function for checking the time meets the 'online' condition.
|
||||||
|
*
|
||||||
|
* @return boolean
|
||||||
|
*/
|
||||||
|
isOnline(): boolean {
|
||||||
|
if (!this.user) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (CoreUtils.instance.isFalseOrZero(this.user.isonline)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.user.lastaccess) {
|
||||||
|
// If the time has passed, don't show the online status.
|
||||||
|
const time = new Date().getTime() - this.timetoshowusers;
|
||||||
|
|
||||||
|
return this.user.lastaccess * 1000 >= time;
|
||||||
|
} else {
|
||||||
|
// You have to have Internet access first.
|
||||||
|
return !!this.user.isonline && CoreApp.instance.isOnline();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Go to user profile.
|
||||||
|
*
|
||||||
|
* @param event Click event.
|
||||||
|
*/
|
||||||
|
gotoProfile(event: Event): void {
|
||||||
|
if (!this.linkProfile || !this.userId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
|
||||||
|
// @todo Decide which navCtrl to use. If this component is inside a split view, use the split view's master nav.
|
||||||
|
this.navCtrl.navigateForward(['user'], {
|
||||||
|
relativeTo: this.route,
|
||||||
|
queryParams: CoreObject.removeUndefined({
|
||||||
|
userId: this.userId,
|
||||||
|
courseId: this.courseId,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Component destroyed.
|
||||||
|
*/
|
||||||
|
ngOnDestroy(): void {
|
||||||
|
this.pictureObserver.off();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Type with all possible formats of user.
|
||||||
|
*/
|
||||||
|
type CoreUserWithAvatar = CoreUserBasicData & {
|
||||||
|
userpictureurl?: string;
|
||||||
|
userprofileimageurl?: string;
|
||||||
|
profileimageurlsmall?: string;
|
||||||
|
urls?: {
|
||||||
|
profileimage?: string;
|
||||||
|
};
|
||||||
|
userfullname?: string;
|
||||||
|
userid?: number;
|
||||||
|
isonline?: boolean;
|
||||||
|
courseid?: number;
|
||||||
|
lastaccess?: number;
|
||||||
|
};
|
|
@ -0,0 +1,27 @@
|
||||||
|
{
|
||||||
|
"address": "Address",
|
||||||
|
"city": "City/town",
|
||||||
|
"contact": "Contact",
|
||||||
|
"country": "Country",
|
||||||
|
"description": "Description",
|
||||||
|
"details": "Details",
|
||||||
|
"detailsnotavailable": "The details of this user are not available to you.",
|
||||||
|
"editingteacher": "Teacher",
|
||||||
|
"email": "Email address",
|
||||||
|
"emailagain": "Email (again)",
|
||||||
|
"errorloaduser": "Error loading user.",
|
||||||
|
"firstname": "First name",
|
||||||
|
"interests": "Interests",
|
||||||
|
"lastname": "Surname",
|
||||||
|
"manager": "Manager",
|
||||||
|
"newpicture": "New picture",
|
||||||
|
"noparticipants": "No participants found for this course",
|
||||||
|
"participants": "Participants",
|
||||||
|
"phone1": "Phone",
|
||||||
|
"phone2": "Mobile phone",
|
||||||
|
"roles": "Roles",
|
||||||
|
"sendemail": "Email",
|
||||||
|
"student": "Student",
|
||||||
|
"teacher": "Non-editing teacher",
|
||||||
|
"webpage": "Web page"
|
||||||
|
}
|
|
@ -0,0 +1,92 @@
|
||||||
|
<ion-header>
|
||||||
|
<ion-toolbar>
|
||||||
|
<ion-buttons slot="start">
|
||||||
|
<ion-back-button [attr.aria-label]="'core.back' | translate"></ion-back-button>
|
||||||
|
</ion-buttons>
|
||||||
|
<ion-title *ngIf="title">{{ title }}</ion-title>
|
||||||
|
</ion-toolbar>
|
||||||
|
</ion-header>
|
||||||
|
<ion-content>
|
||||||
|
<ion-refresher slot="fixed" [disabled]="!userLoaded" (ionRefresh)="refreshUser($event)">
|
||||||
|
<ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content>
|
||||||
|
</ion-refresher>
|
||||||
|
<core-loading [hideUntil]="userLoaded">
|
||||||
|
<ion-list *ngIf="user && !isDeleted && isEnrolled">
|
||||||
|
<ion-item class="ion-text-center item-avatar-center">
|
||||||
|
<ion-avatar>
|
||||||
|
<core-user-avatar [user]="user" [userId]="user.id" [linkProfile]="false"
|
||||||
|
[checkOnline]="true">
|
||||||
|
<ion-icon *ngIf="canChangeProfilePicture" name="fa-pen" class="core-icon-foreground"
|
||||||
|
(click)="changeProfilePicture()">
|
||||||
|
</ion-icon>
|
||||||
|
</core-user-avatar>
|
||||||
|
</ion-avatar>
|
||||||
|
<ion-label>
|
||||||
|
<h2>{{ user.fullname }}</h2>
|
||||||
|
<p *ngIf="user.address">{{ user.address }}</p>
|
||||||
|
<p *ngIf="rolesFormatted" class="ion-text-wrap">
|
||||||
|
<strong>{{ 'core.user.roles' | translate}}</strong>{{'core.labelsep' | translate}}
|
||||||
|
{{ rolesFormatted }}
|
||||||
|
</p>
|
||||||
|
</ion-label>
|
||||||
|
</ion-item>
|
||||||
|
|
||||||
|
<ion-grid class="core-user-communication-handlers"
|
||||||
|
*ngIf="(communicationHandlers && communicationHandlers.length) || isLoadingHandlers">
|
||||||
|
<ion-row class="ion-no-padding justify-content-between"
|
||||||
|
*ngIf="communicationHandlers && communicationHandlers.length">
|
||||||
|
<ion-col *ngFor="let handler of communicationHandlers" class="ion-align-self-center ion-text-center">
|
||||||
|
<a (click)="handlerClicked($event, handler)" [ngClass]="['core-user-profile-handler', handler.class]"
|
||||||
|
title="{{handler.title | translate}}">
|
||||||
|
<ion-icon [name]="handler.icon" slot="start"></ion-icon>
|
||||||
|
<p>{{handler.title | translate}}</p>
|
||||||
|
</a>
|
||||||
|
</ion-col>
|
||||||
|
</ion-row>
|
||||||
|
<ion-row class="ion-no-padding">
|
||||||
|
<ion-col class="ion-text-center core-loading-handlers" *ngIf="isLoadingHandlers">
|
||||||
|
<ion-spinner></ion-spinner>
|
||||||
|
</ion-col>
|
||||||
|
</ion-row>
|
||||||
|
</ion-grid>
|
||||||
|
|
||||||
|
<ion-item button class="ion-text-wrap core-user-profile-handler" (click)="openUserDetails()"
|
||||||
|
title="{{ 'core.user.details' | translate }}">
|
||||||
|
<ion-icon name="fa-user" slot="start"></ion-icon>
|
||||||
|
<ion-label>
|
||||||
|
<h2>{{ 'core.user.details' | translate }}</h2>
|
||||||
|
</ion-label>
|
||||||
|
</ion-item>
|
||||||
|
<ion-item class="ion-text-center core-loading-handlers" *ngIf="isLoadingHandlers">
|
||||||
|
<ion-spinner></ion-spinner>
|
||||||
|
</ion-item>
|
||||||
|
|
||||||
|
<ion-item button *ngFor="let handler of newPageHandlers" class="ion-text-wrap" (click)="handlerClicked($event, handler)"
|
||||||
|
[ngClass]="['core-user-profile-handler', handler.class]" [hidden]="handler.hidden"
|
||||||
|
title="{{ handler.title | translate }}">
|
||||||
|
<ion-label>
|
||||||
|
<ion-icon *ngIf="handler.icon" [name]="handler.icon" slot="start"></ion-icon>
|
||||||
|
<h2>{{ handler.title | translate }}</h2>
|
||||||
|
</ion-label>
|
||||||
|
</ion-item>
|
||||||
|
|
||||||
|
<ion-item *ngIf="actionHandlers && actionHandlers.length">
|
||||||
|
<ion-button *ngFor="let handler of actionHandlers" expand="block" fill="outline"
|
||||||
|
[ngClass]="['core-user-profile-handler', handler.class]" (click)="handlerClicked($event, handler)"
|
||||||
|
[hidden]="handler.hidden" title="{{ handler.title | translate }}" [disabled]="handler.spinner">
|
||||||
|
<ion-label>
|
||||||
|
<ion-icon *ngIf="handler.icon" [name]="handler.icon" slot="start"></ion-icon>
|
||||||
|
<span>{{ handler.title | translate }}</span>
|
||||||
|
</ion-label>
|
||||||
|
<ion-spinner *ngIf="handler.spinner"></ion-spinner>
|
||||||
|
</ion-button>
|
||||||
|
</ion-item>
|
||||||
|
</ion-list>
|
||||||
|
|
||||||
|
<core-empty-box *ngIf="!user && !isDeleted && isEnrolled" icon="fa-user"
|
||||||
|
[message]=" 'core.user.detailsnotavailable' | translate">
|
||||||
|
</core-empty-box>
|
||||||
|
<core-empty-box *ngIf="isDeleted" icon="fa-user" [message]="'core.userdeleted' | translate"></core-empty-box>
|
||||||
|
<core-empty-box *ngIf="!isEnrolled" icon="fa-user" [message]="'core.notenrolledprofile' | translate"></core-empty-box>
|
||||||
|
</core-loading>
|
||||||
|
</ion-content>
|
|
@ -0,0 +1,47 @@
|
||||||
|
// (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 { RouterModule, Routes } from '@angular/router';
|
||||||
|
import { IonicModule } from '@ionic/angular';
|
||||||
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
|
|
||||||
|
import { CoreComponentsModule } from '@components/components.module';
|
||||||
|
import { CoreDirectivesModule } from '@directives/directives.module';
|
||||||
|
|
||||||
|
import { CoreUserProfilePage } from './profile.page';
|
||||||
|
|
||||||
|
const routes: Routes = [
|
||||||
|
{
|
||||||
|
path: '',
|
||||||
|
component: CoreUserProfilePage,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
imports: [
|
||||||
|
RouterModule.forChild(routes),
|
||||||
|
CommonModule,
|
||||||
|
IonicModule,
|
||||||
|
TranslateModule.forChild(),
|
||||||
|
CoreComponentsModule,
|
||||||
|
CoreDirectivesModule,
|
||||||
|
],
|
||||||
|
declarations: [
|
||||||
|
CoreUserProfilePage,
|
||||||
|
],
|
||||||
|
exports: [RouterModule],
|
||||||
|
})
|
||||||
|
export class CoreUserProfilePageModule {}
|
|
@ -0,0 +1,289 @@
|
||||||
|
// (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, OnDestroy, OnInit } from '@angular/core';
|
||||||
|
import { ActivatedRoute } from '@angular/router';
|
||||||
|
import { IonRefresher, NavController } from '@ionic/angular';
|
||||||
|
import { Subscription } from 'rxjs';
|
||||||
|
|
||||||
|
import { CoreSite } from '@classes/site';
|
||||||
|
import { CoreSites } from '@services/sites';
|
||||||
|
import { CoreDomUtils } from '@services/utils/dom';
|
||||||
|
import { CoreMimetypeUtils } from '@services/utils/mimetype';
|
||||||
|
import { Translate } from '@singletons/core.singletons';
|
||||||
|
import { CoreEventObserver, CoreEvents } from '@singletons/events';
|
||||||
|
import {
|
||||||
|
CoreUser,
|
||||||
|
CoreUserProfile,
|
||||||
|
CoreUserProfilePictureUpdatedData,
|
||||||
|
CoreUserProfileRefreshedData,
|
||||||
|
CoreUserProvider,
|
||||||
|
} from '@core/user/services/user';
|
||||||
|
import { CoreUserHelper } from '@core/user/services/user.helper';
|
||||||
|
import { CoreUserDelegate, CoreUserProfileHandlerData } from '@core/user/services/user.delegate';
|
||||||
|
import { CoreFileUploaderHelper } from '@core/fileuploader/services/fileuploader.helper';
|
||||||
|
import { CoreIonLoadingElement } from '@classes/ion-loading';
|
||||||
|
import { CoreUtils } from '@/app/services/utils/utils';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'page-core-user-profile',
|
||||||
|
templateUrl: 'profile.html',
|
||||||
|
styleUrls: ['profile.scss'],
|
||||||
|
})
|
||||||
|
export class CoreUserProfilePage implements OnInit, OnDestroy {
|
||||||
|
|
||||||
|
protected courseId!: number;
|
||||||
|
protected userId!: number;
|
||||||
|
protected site?: CoreSite;
|
||||||
|
protected obsProfileRefreshed: CoreEventObserver;
|
||||||
|
protected subscription?: Subscription;
|
||||||
|
|
||||||
|
userLoaded = false;
|
||||||
|
isLoadingHandlers = false;
|
||||||
|
user?: CoreUserProfile;
|
||||||
|
title?: string;
|
||||||
|
isDeleted = false;
|
||||||
|
isEnrolled = true;
|
||||||
|
canChangeProfilePicture = false;
|
||||||
|
rolesFormatted?: string;
|
||||||
|
actionHandlers: CoreUserProfileHandlerData[] = [];
|
||||||
|
newPageHandlers: CoreUserProfileHandlerData[] = [];
|
||||||
|
communicationHandlers: CoreUserProfileHandlerData[] = [];
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
protected route: ActivatedRoute,
|
||||||
|
protected navCtrl: NavController,
|
||||||
|
protected userDelegate: CoreUserDelegate,
|
||||||
|
) {
|
||||||
|
|
||||||
|
this.obsProfileRefreshed = CoreEvents.on<CoreUserProfileRefreshedData>(CoreUserProvider.PROFILE_REFRESHED, (data) => {
|
||||||
|
if (!this.user || !data.user) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.user.email = data.user.email;
|
||||||
|
this.user.address = CoreUserHelper.instance.formatAddress('', data.user.city, data.user.country);
|
||||||
|
}, CoreSites.instance.getCurrentSiteId());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* On init.
|
||||||
|
*/
|
||||||
|
async ngOnInit(): Promise<void> {
|
||||||
|
this.site = CoreSites.instance.getCurrentSite();
|
||||||
|
this.userId = this.route.snapshot.queryParams['userId'];
|
||||||
|
this.courseId = this.route.snapshot.queryParams['courseId'];
|
||||||
|
|
||||||
|
if (!this.site) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allow to change the profile image only in the app profile page.
|
||||||
|
this.canChangeProfilePicture =
|
||||||
|
(!this.courseId || this.courseId == this.site.getSiteHomeId()) &&
|
||||||
|
this.userId == this.site.getUserId() &&
|
||||||
|
this.site.canUploadFiles() &&
|
||||||
|
CoreUser.instance.canUpdatePictureInSite(this.site) &&
|
||||||
|
!CoreUser.instance.isUpdatePictureDisabledInSite(this.site);
|
||||||
|
|
||||||
|
try {
|
||||||
|
await this.fetchUser();
|
||||||
|
|
||||||
|
try {
|
||||||
|
await CoreUser.instance.logView(this.userId, this.courseId, this.user!.fullname);
|
||||||
|
} catch (error) {
|
||||||
|
this.isDeleted = error?.errorcode === 'userdeleted';
|
||||||
|
this.isEnrolled = error?.errorcode !== 'notenrolledprofile';
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
this.userLoaded = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetches the user and updates the view.
|
||||||
|
*/
|
||||||
|
async fetchUser(): Promise<void> {
|
||||||
|
try {
|
||||||
|
const user = await CoreUser.instance.getProfile(this.userId, this.courseId);
|
||||||
|
|
||||||
|
user.address = CoreUserHelper.instance.formatAddress('', user.city, user.country);
|
||||||
|
this.rolesFormatted = 'roles' in user ? CoreUserHelper.instance.formatRoleList(user.roles) : '';
|
||||||
|
|
||||||
|
this.user = user;
|
||||||
|
this.title = user.fullname;
|
||||||
|
|
||||||
|
// If there's already a subscription, unsubscribe because we'll get a new one.
|
||||||
|
this.subscription?.unsubscribe();
|
||||||
|
|
||||||
|
this.subscription = this.userDelegate.getProfileHandlersFor(user, this.courseId).subscribe((handlers) => {
|
||||||
|
this.actionHandlers = [];
|
||||||
|
this.newPageHandlers = [];
|
||||||
|
this.communicationHandlers = [];
|
||||||
|
handlers.forEach((handler) => {
|
||||||
|
switch (handler.type) {
|
||||||
|
case CoreUserDelegate.TYPE_COMMUNICATION:
|
||||||
|
this.communicationHandlers.push(handler.data);
|
||||||
|
break;
|
||||||
|
case CoreUserDelegate.TYPE_ACTION:
|
||||||
|
this.actionHandlers.push(handler.data);
|
||||||
|
break;
|
||||||
|
case CoreUserDelegate.TYPE_NEW_PAGE:
|
||||||
|
default:
|
||||||
|
this.newPageHandlers.push(handler.data);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.isLoadingHandlers = !this.userDelegate.areHandlersLoaded(user.id);
|
||||||
|
});
|
||||||
|
|
||||||
|
await this.checkUserImageUpdated();
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
// Error is null for deleted users, do not show the modal.
|
||||||
|
CoreDomUtils.instance.showErrorModal(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if current user image has changed.
|
||||||
|
*
|
||||||
|
* @return Promise resolved when done.
|
||||||
|
*/
|
||||||
|
protected async checkUserImageUpdated(): Promise<void> {
|
||||||
|
if (!this.site || !this.site.getInfo() || !this.user) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.userId != this.site.getUserId() || this.user.profileimageurl == this.site.getInfo()!.userpictureurl) {
|
||||||
|
// Not current user or hasn't changed.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The current user image received is different than the one stored in site info. Assume the image was updated.
|
||||||
|
// Update the site info to get the right avatar in there.
|
||||||
|
try {
|
||||||
|
await CoreSites.instance.updateSiteInfo(this.site.getId());
|
||||||
|
} catch {
|
||||||
|
// Cannot update site info. Assume the profile image is the right one.
|
||||||
|
CoreEvents.trigger<CoreUserProfilePictureUpdatedData>(CoreUserProvider.PROFILE_PICTURE_UPDATED, {
|
||||||
|
userId: this.userId,
|
||||||
|
picture: this.user.profileimageurl,
|
||||||
|
}, this.site.getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.user.profileimageurl != this.site.getInfo()!.userpictureurl) {
|
||||||
|
// The image is still different, this means that the good one is the one in site info.
|
||||||
|
await this.refreshUser();
|
||||||
|
} else {
|
||||||
|
// Now they're the same, send event to use the right avatar in the rest of the app.
|
||||||
|
CoreEvents.trigger<CoreUserProfilePictureUpdatedData>(CoreUserProvider.PROFILE_PICTURE_UPDATED, {
|
||||||
|
userId: this.userId,
|
||||||
|
picture: this.user.profileimageurl,
|
||||||
|
}, this.site.getId());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Opens dialog to change profile picture.
|
||||||
|
*/
|
||||||
|
async changeProfilePicture(): Promise<void> {
|
||||||
|
const maxSize = -1;
|
||||||
|
const title = Translate.instance.instant('core.user.newpicture');
|
||||||
|
const mimetypes = CoreMimetypeUtils.instance.getGroupMimeInfo('image', 'mimetypes');
|
||||||
|
let modal: CoreIonLoadingElement | undefined;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result = await CoreFileUploaderHelper.instance.selectAndUploadFile(maxSize, title, mimetypes);
|
||||||
|
|
||||||
|
modal = await CoreDomUtils.instance.showModalLoading('core.sending', true);
|
||||||
|
|
||||||
|
const profileImageURL = await CoreUser.instance.changeProfilePicture(result.itemid, this.userId, this.site!.getId());
|
||||||
|
|
||||||
|
CoreEvents.trigger<CoreUserProfilePictureUpdatedData>(CoreUserProvider.PROFILE_PICTURE_UPDATED, {
|
||||||
|
userId: this.userId,
|
||||||
|
picture: profileImageURL,
|
||||||
|
}, this.site!.getId());
|
||||||
|
|
||||||
|
CoreSites.instance.updateSiteInfo(this.site!.getId());
|
||||||
|
|
||||||
|
this.refreshUser();
|
||||||
|
} catch (error) {
|
||||||
|
CoreDomUtils.instance.showErrorModal(error);
|
||||||
|
} finally {
|
||||||
|
modal?.dismiss();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Refresh the user.
|
||||||
|
*
|
||||||
|
* @param event Event.
|
||||||
|
* @return Promise resolved when done.
|
||||||
|
*/
|
||||||
|
async refreshUser(event?: CustomEvent<IonRefresher>): Promise<void> {
|
||||||
|
await CoreUtils.instance.ignoreErrors(Promise.all([
|
||||||
|
CoreUser.instance.invalidateUserCache(this.userId),
|
||||||
|
// @todo this.coursesProvider.invalidateUserNavigationOptions(),
|
||||||
|
// this.coursesProvider.invalidateUserAdministrationOptions()
|
||||||
|
]));
|
||||||
|
|
||||||
|
await this.fetchUser();
|
||||||
|
|
||||||
|
event?.detail.complete();
|
||||||
|
|
||||||
|
if (this.user) {
|
||||||
|
CoreEvents.trigger<CoreUserProfileRefreshedData>(CoreUserProvider.PROFILE_REFRESHED, {
|
||||||
|
courseId: this.courseId,
|
||||||
|
userId: this.userId,
|
||||||
|
user: this.user,
|
||||||
|
}, this.site?.getId());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Open the page with the user details.
|
||||||
|
*/
|
||||||
|
openUserDetails(): void {
|
||||||
|
// @todo: Navigate out of split view if this page is in the right pane.
|
||||||
|
this.navCtrl.navigateForward(['../about'], {
|
||||||
|
relativeTo: this.route,
|
||||||
|
queryParams: {
|
||||||
|
courseId: this.courseId,
|
||||||
|
userId: this.userId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A handler was clicked.
|
||||||
|
*
|
||||||
|
* @param event Click event.
|
||||||
|
* @param handler Handler that was clicked.
|
||||||
|
*/
|
||||||
|
handlerClicked(event: Event, handler: CoreUserProfileHandlerData): void {
|
||||||
|
// @todo: Pass the right navCtrl if this page is in the right pane of split view.
|
||||||
|
handler.action(event, this.navCtrl, this.user!, this.courseId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Page destroyed.
|
||||||
|
*/
|
||||||
|
ngOnDestroy(): void {
|
||||||
|
this.subscription?.unsubscribe();
|
||||||
|
this.obsProfileRefreshed.off();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,69 @@
|
||||||
|
:host {
|
||||||
|
// @todo
|
||||||
|
// .core-icon-foreground {
|
||||||
|
// position: absolute;
|
||||||
|
// @include position(null, 0, 0, null);
|
||||||
|
// font-size: 24px;
|
||||||
|
// line-height: 30px;
|
||||||
|
// text-align: center;
|
||||||
|
|
||||||
|
// width: 30px;
|
||||||
|
// height: 30px;
|
||||||
|
// border-radius: 50%;
|
||||||
|
// background-color: $white;
|
||||||
|
// @include darkmode() {
|
||||||
|
// background: $core-dark-item-bg-color;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// [core-user-avatar].item-avatar-center {
|
||||||
|
// display: inline-block;
|
||||||
|
// img {
|
||||||
|
// margin: 0;
|
||||||
|
// }
|
||||||
|
// .contact-status {
|
||||||
|
// width: 24px;
|
||||||
|
// height: 24px;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// .core-user-communication-handlers {
|
||||||
|
// background: $list-background-color;
|
||||||
|
// border-bottom: 1px solid $list-border-color;
|
||||||
|
|
||||||
|
// @include darkmode() {
|
||||||
|
// background: $core-dark-item-bg-color;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// .core-user-profile-handler {
|
||||||
|
// background: $list-background-color;
|
||||||
|
// border: 0;
|
||||||
|
// color: $core-user-profile-communication-icons-color;
|
||||||
|
|
||||||
|
// @include darkmode() {
|
||||||
|
// background: $core-dark-item-bg-color;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// p {
|
||||||
|
// margin: 0;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// .icon {
|
||||||
|
// border-radius: 50%;
|
||||||
|
// width: 32px;
|
||||||
|
// height: 32px;
|
||||||
|
// max-width: 32px;
|
||||||
|
// font-size: 22px;
|
||||||
|
// line-height: 32px;
|
||||||
|
// color: white;
|
||||||
|
// background-color: $core-user-profile-communication-icons-color;
|
||||||
|
// margin-bottom: 5px;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// .core-user-profile-handler {
|
||||||
|
// ion-spinner {
|
||||||
|
// @include margin(null, null, null, 0.3em);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
}
|
|
@ -810,7 +810,8 @@ export class CoreUser extends makeSingleton(CoreUserProvider) {}
|
||||||
*/
|
*/
|
||||||
export type CoreUserProfileRefreshedData = {
|
export type CoreUserProfileRefreshedData = {
|
||||||
courseId: number; // Course the user profile belongs to.
|
courseId: number; // Course the user profile belongs to.
|
||||||
user: CoreUserProfile; // User affected.
|
userId: number; // User ID.
|
||||||
|
user?: CoreUserProfile; // User affected.
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -0,0 +1,37 @@
|
||||||
|
// (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 { Routes } from '@angular/router';
|
||||||
|
|
||||||
|
import { CoreMainMenuRoutingModule } from '@core/mainmenu/mainmenu-routing.module';
|
||||||
|
|
||||||
|
const routes: Routes = [
|
||||||
|
{
|
||||||
|
path: 'user',
|
||||||
|
loadChildren: () => import('@core/user/user.module').then(m => m.CoreUserModule),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
imports: [
|
||||||
|
CoreMainMenuRoutingModule.forChild(routes),
|
||||||
|
],
|
||||||
|
exports: [
|
||||||
|
CoreMainMenuRoutingModule,
|
||||||
|
],
|
||||||
|
providers: [
|
||||||
|
],
|
||||||
|
})
|
||||||
|
export class CoreUserInitModule {}
|
|
@ -0,0 +1,34 @@
|
||||||
|
// (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: '',
|
||||||
|
redirectTo: 'profile',
|
||||||
|
pathMatch: 'full',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'profile',
|
||||||
|
loadChildren: () => import('./pages/profile/profile.page.module').then( m => m.CoreUserProfilePageModule),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
imports: [RouterModule.forChild(routes)],
|
||||||
|
exports: [RouterModule],
|
||||||
|
})
|
||||||
|
export class CoreUserRoutingModule {}
|
|
@ -0,0 +1,24 @@
|
||||||
|
// (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 { CoreUserRoutingModule } from './user-routing.module';
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
imports: [
|
||||||
|
CoreUserRoutingModule,
|
||||||
|
],
|
||||||
|
})
|
||||||
|
export class CoreUserModule {}
|
|
@ -0,0 +1,66 @@
|
||||||
|
// (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 { Directive, Input, OnInit, ElementRef } from '@angular/core';
|
||||||
|
import { ActivatedRoute } from '@angular/router';
|
||||||
|
import { NavController } from '@ionic/angular';
|
||||||
|
|
||||||
|
import { CoreObject } from '@singletons/object';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Directive to go to user profile on click.
|
||||||
|
*/
|
||||||
|
@Directive({
|
||||||
|
selector: '[core-user-link]',
|
||||||
|
})
|
||||||
|
export class CoreUserLinkDirective implements OnInit {
|
||||||
|
|
||||||
|
@Input() userId?: number; // User id to open the profile.
|
||||||
|
@Input() courseId?: number; // If set, course id to show the user info related to that course.
|
||||||
|
|
||||||
|
protected element: HTMLElement;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
element: ElementRef,
|
||||||
|
protected navCtrl: NavController,
|
||||||
|
protected route: ActivatedRoute,
|
||||||
|
) {
|
||||||
|
this.element = element.nativeElement;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Function executed when the component is initialized.
|
||||||
|
*/
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.element.addEventListener('click', (event) => {
|
||||||
|
// If the event prevented default action, do nothing.
|
||||||
|
if (event.defaultPrevented || !this.userId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
|
||||||
|
// @todo If this directive is inside a split view, use the split view's master nav.
|
||||||
|
this.navCtrl.navigateForward(['user'], {
|
||||||
|
relativeTo: this.route,
|
||||||
|
queryParams: CoreObject.removeUndefined({
|
||||||
|
userId: this.userId,
|
||||||
|
courseId: this.courseId,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -35,6 +35,7 @@ import { CoreProgressBarComponent } from './progress-bar/progress-bar';
|
||||||
import { CoreContextMenuComponent } from './context-menu/context-menu';
|
import { CoreContextMenuComponent } from './context-menu/context-menu';
|
||||||
import { CoreContextMenuItemComponent } from './context-menu/context-menu-item';
|
import { CoreContextMenuItemComponent } from './context-menu/context-menu-item';
|
||||||
import { CoreContextMenuPopoverComponent } from './context-menu/context-menu-popover';
|
import { CoreContextMenuPopoverComponent } from './context-menu/context-menu-popover';
|
||||||
|
import { CoreUserAvatarComponent } from './user-avatar/user-avatar';
|
||||||
|
|
||||||
import { CoreDirectivesModule } from '@directives/directives.module';
|
import { CoreDirectivesModule } from '@directives/directives.module';
|
||||||
import { CorePipesModule } from '@pipes/pipes.module';
|
import { CorePipesModule } from '@pipes/pipes.module';
|
||||||
|
@ -61,6 +62,7 @@ import { CoreNavBarButtonsComponent } from './navbar-buttons/navbar-buttons';
|
||||||
CoreContextMenuItemComponent,
|
CoreContextMenuItemComponent,
|
||||||
CoreContextMenuPopoverComponent,
|
CoreContextMenuPopoverComponent,
|
||||||
CoreNavBarButtonsComponent,
|
CoreNavBarButtonsComponent,
|
||||||
|
CoreUserAvatarComponent,
|
||||||
],
|
],
|
||||||
imports: [
|
imports: [
|
||||||
CommonModule,
|
CommonModule,
|
||||||
|
@ -89,6 +91,7 @@ import { CoreNavBarButtonsComponent } from './navbar-buttons/navbar-buttons';
|
||||||
CoreContextMenuItemComponent,
|
CoreContextMenuItemComponent,
|
||||||
CoreContextMenuPopoverComponent,
|
CoreContextMenuPopoverComponent,
|
||||||
CoreNavBarButtonsComponent,
|
CoreNavBarButtonsComponent,
|
||||||
|
CoreUserAvatarComponent,
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
export class CoreComponentsModule {}
|
export class CoreComponentsModule {}
|
||||||
|
|
|
@ -22,6 +22,7 @@ import { CoreLinkDirective } from './link';
|
||||||
import { CoreLongPressDirective } from './long-press';
|
import { CoreLongPressDirective } from './long-press';
|
||||||
import { CoreSupressEventsDirective } from './supress-events';
|
import { CoreSupressEventsDirective } from './supress-events';
|
||||||
import { CoreFaIconDirective } from './fa-icon';
|
import { CoreFaIconDirective } from './fa-icon';
|
||||||
|
import { CoreUserLinkDirective } from './user-link';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
declarations: [
|
declarations: [
|
||||||
|
@ -33,6 +34,7 @@ import { CoreFaIconDirective } from './fa-icon';
|
||||||
CoreSupressEventsDirective,
|
CoreSupressEventsDirective,
|
||||||
CoreFabDirective,
|
CoreFabDirective,
|
||||||
CoreFaIconDirective,
|
CoreFaIconDirective,
|
||||||
|
CoreUserLinkDirective,
|
||||||
],
|
],
|
||||||
imports: [],
|
imports: [],
|
||||||
exports: [
|
exports: [
|
||||||
|
@ -44,6 +46,7 @@ import { CoreFaIconDirective } from './fa-icon';
|
||||||
CoreSupressEventsDirective,
|
CoreSupressEventsDirective,
|
||||||
CoreFabDirective,
|
CoreFabDirective,
|
||||||
CoreFaIconDirective,
|
CoreFaIconDirective,
|
||||||
|
CoreUserLinkDirective,
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
export class CoreDirectivesModule {}
|
export class CoreDirectivesModule {}
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
</ion-header>
|
</ion-header>
|
||||||
<ion-content>
|
<ion-content>
|
||||||
<ion-list>
|
<ion-list>
|
||||||
<ion-item *ngIf="siteInfo" class="ion-text-wrap"> <!-- @todo core-user-link [userId]="siteInfo.userid" -->
|
<ion-item *ngIf="siteInfo" class="ion-text-wrap" core-user-link [userId]="siteInfo.userid">
|
||||||
<ion-avatar slot="start"></ion-avatar> <!-- @todo core-user-avatar [user]="siteInfo" -->
|
<ion-avatar slot="start"></ion-avatar> <!-- @todo core-user-avatar [user]="siteInfo" -->
|
||||||
<ion-label>
|
<ion-label>
|
||||||
<h2>{{siteInfo.fullname}}</h2>
|
<h2>{{siteInfo.fullname}}</h2>
|
||||||
|
|
|
@ -656,6 +656,10 @@ export class CoreDomUtilsProvider {
|
||||||
* @return Error message, null if no error should be displayed.
|
* @return Error message, null if no error should be displayed.
|
||||||
*/
|
*/
|
||||||
getErrorMessage(error: CoreError | CoreTextErrorObject | string, needsTranslate?: boolean): string | null {
|
getErrorMessage(error: CoreError | CoreTextErrorObject | string, needsTranslate?: boolean): string | null {
|
||||||
|
if (typeof error != 'string' && !error) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
let extraInfo = '';
|
let extraInfo = '';
|
||||||
let errorMessage: string | undefined;
|
let errorMessage: string | undefined;
|
||||||
|
|
||||||
|
@ -1332,21 +1336,21 @@ export class CoreDomUtilsProvider {
|
||||||
* @param autocloseTime Number of milliseconds to wait to close the modal. If not defined, modal won't be closed.
|
* @param autocloseTime Number of milliseconds to wait to close the modal. If not defined, modal won't be closed.
|
||||||
* @return Promise resolved with the alert modal.
|
* @return Promise resolved with the alert modal.
|
||||||
*/
|
*/
|
||||||
showErrorModal(
|
async showErrorModal(
|
||||||
error: CoreError | CoreTextErrorObject | string,
|
error: CoreError | CoreTextErrorObject | string,
|
||||||
needsTranslate?: boolean,
|
needsTranslate?: boolean,
|
||||||
autocloseTime?: number,
|
autocloseTime?: number,
|
||||||
): Promise<HTMLIonAlertElement | null> {
|
): Promise<HTMLIonAlertElement | null> {
|
||||||
if (this.isCanceledError(error)) {
|
if (this.isCanceledError(error)) {
|
||||||
// It's a canceled error, don't display an error.
|
// It's a canceled error, don't display an error.
|
||||||
return Promise.resolve(null);
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const message = this.getErrorMessage(error, needsTranslate);
|
const message = this.getErrorMessage(error, needsTranslate);
|
||||||
|
|
||||||
if (message === null) {
|
if (message === null) {
|
||||||
// Message doesn't need to be displayed, stop.
|
// Message doesn't need to be displayed, stop.
|
||||||
return Promise.resolve(null);
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const alertOptions: AlertOptions = {
|
const alertOptions: AlertOptions = {
|
||||||
|
|
|
@ -24,6 +24,7 @@
|
||||||
--purple: var(--custom-purple, #8e24aa); // Accent (never text).
|
--purple: var(--custom-purple, #8e24aa); // Accent (never text).
|
||||||
|
|
||||||
--core-color: var(--custom-main-color, var(--orange));
|
--core-color: var(--custom-main-color, var(--orange));
|
||||||
|
--core-online-color: #5cb85c;
|
||||||
|
|
||||||
--ion-color-primary: var(--core-color);
|
--ion-color-primary: var(--core-color);
|
||||||
--ion-color-primary-rgb: 249,128,18;
|
--ion-color-primary-rgb: 249,128,18;
|
||||||
|
|
Loading…
Reference in New Issue