MOBILE-4261 user: Use initials instead of default avatar when possible
parent
28d744f337
commit
c3e2cdf731
|
@ -1,6 +1,8 @@
|
|||
@import "~theme/globals";
|
||||
:host {
|
||||
--core-avatar-size: 30px;
|
||||
|
||||
:host .core-block-content ::ng-deep {
|
||||
.core-block-content ::ng-deep {
|
||||
max-height: 200px;
|
||||
overflow-y: auto;
|
||||
.item-inner,
|
||||
|
@ -25,11 +27,29 @@
|
|||
|
||||
.core-adapted-img-container {
|
||||
display: inline;
|
||||
@include margin-horizontal(0px, 8px);
|
||||
@include margin-horizontal(0px, 0.25rem);
|
||||
}
|
||||
|
||||
.userpicture {
|
||||
border-radius: 50%;
|
||||
border-radius: var(--core-avatar-radius);
|
||||
width: var(--core-avatar-size);
|
||||
height: var(--core-avatar-size);
|
||||
}
|
||||
|
||||
.userinitials {
|
||||
background-color: var(--gray-200);
|
||||
vertical-align: middle;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: var(--core-avatar-radius);
|
||||
color: var(--gray-800);
|
||||
font-weight: normal;
|
||||
width: var(--core-avatar-size);
|
||||
height: var(--core-avatar-size);
|
||||
font-size: 0.7rem;
|
||||
margin-right: 0.25rem;
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -48,4 +68,5 @@
|
|||
text-align: center;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -39,10 +39,8 @@
|
|||
<span *ngIf="action == 'timeadded'">{{ entry.timecreated * 1000 | coreFormatDate }}</span>
|
||||
<span *ngIf="action == 'timemodified'">{{ entry.timemodified * 1000 | coreFormatDate }}</span>
|
||||
|
||||
<a *ngIf="action == 'userpicture'" core-user-link [courseId]="database.course" [userId]="entry.userid" [title]="entry.fullname">
|
||||
<img class="avatar-round" [src]="userPicture" [alt]="'core.pictureof' | translate:{$a: entry.fullname}" core-external-content
|
||||
onError="this.src='assets/img/user-avatar.png'">
|
||||
</a>
|
||||
<core-user-avatar *ngIf="action == 'userpicture'" [user]="entry" slot="start" [courseId]="database.course" [userId]="entry.userid"
|
||||
[profileUrl]="userPicture"></core-user-avatar>
|
||||
|
||||
<a *ngIf="action == 'user' && entry" core-user-link [courseId]="database.course" [userId]="entry.userid" [title]="entry.fullname">
|
||||
{{entry.fullname}}
|
||||
|
|
|
@ -63,7 +63,7 @@ export class CoreSitePickerComponent implements OnInit {
|
|||
|
||||
site.fullNameAndSiteName = Translate.instant(
|
||||
'core.fullnameandsitename',
|
||||
{ fullname: site.fullName, sitename: siteName },
|
||||
{ fullname: site.fullname, sitename: siteName },
|
||||
);
|
||||
}));
|
||||
|
||||
|
|
|
@ -1,14 +1,26 @@
|
|||
<img *ngIf="avatarUrl && linkProfile" [src]="avatarUrl" [alt]="'core.pictureof' | translate:{$a: fullname}" core-external-content
|
||||
onError="this.src='assets/img/user-avatar.png'" (ariaButtonClick)="gotoProfile($event)">
|
||||
<ng-container *ngIf="avatarUrl">
|
||||
<img *ngIf="linkProfile" [src]="avatarUrl" [alt]="'core.pictureof' | translate:{$a: fullname}" core-external-content
|
||||
(error)="loadImageError()" (ariaButtonClick)="gotoProfile($event)" [siteId]="siteId">
|
||||
|
||||
<img *ngIf="avatarUrl && !linkProfile" [src]="avatarUrl" [alt]="'core.pictureof' | translate:{$a: fullname}" core-external-content
|
||||
onError="this.src='assets/img/user-avatar.png'" aria-hidden="true">
|
||||
<img *ngIf="!linkProfile" [src]="avatarUrl" [alt]="'core.pictureof' | translate:{$a: fullname}" core-external-content
|
||||
(error)="loadImageError()" aria-hidden="true" [siteId]="siteId">
|
||||
</ng-container>
|
||||
<ng-container *ngIf="!avatarUrl && initials">
|
||||
<div class="userinitials" *ngIf="linkProfile" [attr.aria-label]="'core.pictureof' | translate:{$a: fullname}"
|
||||
(ariaButtonClick)="gotoProfile($event)">
|
||||
{{ initials }}
|
||||
</div>
|
||||
|
||||
<img *ngIf="!avatarUrl && linkProfile" src="assets/img/user-avatar.png" [alt]="'core.pictureof' | translate:{$a: fullname}"
|
||||
<div class="userinitials" *ngIf="!linkProfile" [attr.aria-label]="'core.pictureof' | translate:{$a: fullname}" aria-hidden="true">
|
||||
{{ initials }}
|
||||
</div>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="!avatarUrl && !initials">
|
||||
<img *ngIf="linkProfile" src="assets/img/user-avatar.png" [alt]="'core.pictureof' | translate:{$a: fullname}"
|
||||
(ariaButtonClick)="gotoProfile($event)">
|
||||
|
||||
<img *ngIf="!avatarUrl && !linkProfile" src="assets/img/user-avatar.png" [alt]="'core.pictureof' | translate:{$a: fullname}"
|
||||
aria-hidden="true">
|
||||
<img *ngIf="!linkProfile" src="assets/img/user-avatar.png" [alt]="'core.pictureof' | translate:{$a: fullname}" aria-hidden="true">
|
||||
</ng-container>
|
||||
|
||||
<span *ngIf="checkOnline && isOnline()" class="contact-status online" role="status" [attr.aria-label]="'core.online' | translate">
|
||||
</span>
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
height: var(--core-avatar-size);
|
||||
|
||||
img {
|
||||
border-radius: 50%;
|
||||
border-radius: var(--core-avatar-radius);
|
||||
width: var(--core-avatar-size);
|
||||
height: var(--core-avatar-size);
|
||||
max-width: var(--core-avatar-size);
|
||||
|
@ -23,7 +23,7 @@
|
|||
display: inline-block;
|
||||
position: relative;
|
||||
&:after {
|
||||
border-radius: 50%;
|
||||
border-radius: var(--core-avatar-radius);
|
||||
display: block;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
|
@ -62,6 +62,24 @@
|
|||
background-color: var(--core-online-color);
|
||||
}
|
||||
}
|
||||
|
||||
.userinitials {
|
||||
background-color: var(--gray-200);
|
||||
vertical-align: middle;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: var(--core-avatar-radius);
|
||||
color: var(--gray-800);
|
||||
font-weight: normal;
|
||||
width: var(--core-avatar-size);
|
||||
height: var(--core-avatar-size);
|
||||
font-size: calc(var(--core-avatar-size)*0.3);
|
||||
}
|
||||
|
||||
&.large-avatar .userinitials {
|
||||
margin-top: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
:host-context(.toolbar) .contact-status {
|
||||
|
|
|
@ -20,6 +20,8 @@ import { CoreEventObserver, CoreEvents } from '@singletons/events';
|
|||
import { USER_PROFILE_PICTURE_UPDATED, CoreUserBasicData } from '@features/user/services/user';
|
||||
import { CoreNavigator } from '@services/navigator';
|
||||
import { CoreNetwork } from '@services/network';
|
||||
import { CoreUrl } from '@singletons/url';
|
||||
import { CoreUserHelper } from '@features/user/services/user-helper';
|
||||
|
||||
/**
|
||||
* Component to display a "user avatar".
|
||||
|
@ -41,8 +43,10 @@ export class CoreUserAvatarComponent implements OnInit, OnChanges, OnDestroy {
|
|||
@Input() userId?: number; // If provided or found it will be used to link the image to the profile.
|
||||
@Input() courseId?: number;
|
||||
@Input() checkOnline = false; // If want to check and show online status.
|
||||
@Input() siteId?: string;
|
||||
|
||||
avatarUrl?: string;
|
||||
initials = '';
|
||||
|
||||
// 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.
|
||||
|
@ -56,7 +60,7 @@ export class CoreUserAvatarComponent implements OnInit, OnChanges, OnDestroy {
|
|||
this.pictureObserver = CoreEvents.on(
|
||||
USER_PROFILE_PICTURE_UPDATED,
|
||||
(data) => {
|
||||
if (data.userId == this.userId) {
|
||||
if (data.userId === this.userId) {
|
||||
this.avatarUrl = data.picture;
|
||||
}
|
||||
},
|
||||
|
@ -68,6 +72,8 @@ export class CoreUserAvatarComponent implements OnInit, OnChanges, OnDestroy {
|
|||
* @inheritdoc
|
||||
*/
|
||||
ngOnInit(): void {
|
||||
this.siteId = this.siteId || CoreSites.getCurrentSiteId();
|
||||
|
||||
this.setFields();
|
||||
}
|
||||
|
||||
|
@ -81,6 +87,13 @@ export class CoreUserAvatarComponent implements OnInit, OnChanges, OnDestroy {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Avatar image loading error handler.
|
||||
*/
|
||||
loadImageError(): void {
|
||||
this.avatarUrl = undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set fields from user.
|
||||
*/
|
||||
|
@ -88,12 +101,20 @@ export class CoreUserAvatarComponent implements OnInit, OnChanges, OnDestroy {
|
|||
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') {
|
||||
if (typeof profileUrl === 'string') {
|
||||
this.avatarUrl = profileUrl;
|
||||
}
|
||||
|
||||
this.fullname = this.fullname || (this.user && (this.user.fullname || this.user.userfullname));
|
||||
|
||||
if (this.user) {
|
||||
this.initials = CoreUserHelper.getUserInitials(this.user);
|
||||
}
|
||||
|
||||
if (this.initials && this.avatarUrl && CoreUrl.parse(this.avatarUrl)?.path?.startsWith('/theme/image.php')) {
|
||||
this.avatarUrl = undefined;
|
||||
}
|
||||
|
||||
this.userId = this.userId || (this.user && (this.user.userid || this.user.id));
|
||||
this.courseId = this.courseId || (this.user && this.user.courseid);
|
||||
}
|
||||
|
|
|
@ -20,12 +20,10 @@
|
|||
</ion-label>
|
||||
</ion-item>
|
||||
<ion-item *ngFor="let site of sites" (click)="siteClicked(site.id)" detail="false" button>
|
||||
<ion-avatar slot="start">
|
||||
<img [src]="site.avatar" core-external-content [siteId]="site.id"
|
||||
alt="{{ 'core.pictureof' | translate:{$a: site.fullName} }}" onError="this.src='assets/img/user-avatar.png'">
|
||||
</ion-avatar>
|
||||
<core-user-avatar [user]="site" slot="start" [linkProfile]="false"></core-user-avatar>
|
||||
|
||||
<ion-label>
|
||||
<p class="item-heading">{{site.fullName}}</p>
|
||||
<p class="item-heading">{{site.fullname}}</p>
|
||||
<p>
|
||||
<core-format-text [text]="site.siteName" clean="true" [siteId]="site.id"></core-format-text>
|
||||
</p>
|
||||
|
|
|
@ -35,13 +35,11 @@
|
|||
</ion-item-divider>
|
||||
|
||||
<ion-item detail="false">
|
||||
<ion-avatar slot="start">
|
||||
<img [src]="accountsList.currentSite.avatar" core-external-content [siteId]="accountsList.currentSite.id"
|
||||
alt="{{ 'core.pictureof' | translate:{$a: accountsList.currentSite.fullName} }}"
|
||||
onError="this.src='assets/img/user-avatar.png'">
|
||||
</ion-avatar>
|
||||
<core-user-avatar [user]="accountsList.currentSite" slot="start" [linkProfile]="false"
|
||||
[siteId]="accountsList.currentSite.id"></core-user-avatar>
|
||||
|
||||
<ion-label>
|
||||
<p class="item-heading">{{accountsList.currentSite.fullName}}</p>
|
||||
<p class="item-heading">{{accountsList.currentSite.fullname}}</p>
|
||||
</ion-label>
|
||||
<ion-icon color="success" name="fas-check"></ion-icon>
|
||||
</ion-item>
|
||||
|
@ -75,12 +73,10 @@
|
|||
<!-- Template to render a list of sites. -->
|
||||
<ng-template #siteList let-sites="sites">
|
||||
<ion-item button *ngFor="let site of sites" (click)="login($event, site.id)" detail="true">
|
||||
<ion-avatar slot="start">
|
||||
<img [src]="site.avatar" core-external-content [siteId]="site.id" alt="{{ 'core.pictureof' | translate:{$a: site.fullName} }}"
|
||||
onError="this.src='assets/img/user-avatar.png'">
|
||||
</ion-avatar>
|
||||
<core-user-avatar [user]="site" slot="start" [linkProfile]="false" [siteId]="site.id"></core-user-avatar>
|
||||
|
||||
<ion-label>
|
||||
<p class="item-heading">{{site.fullName}}</p>
|
||||
<p class="item-heading">{{site.fullname}}</p>
|
||||
</ion-label>
|
||||
<ion-badge slot="end" *ngIf="!showDelete && site.badge" @coreShowHideAnimation>
|
||||
<span aria-hidden="true">{{site.badge}}</span>
|
||||
|
|
|
@ -75,6 +75,10 @@
|
|||
text-decoration: underline;
|
||||
}
|
||||
|
||||
core-user-avatar.large-avatar {
|
||||
--core-avatar-size: var(--core-large-avatar-size);
|
||||
}
|
||||
|
||||
@if ($core-login-hide-forgot-password) {
|
||||
.core-login-forgotten-password {
|
||||
display: none;
|
||||
|
|
|
@ -20,8 +20,8 @@
|
|||
<div class="list-item-limited-width">
|
||||
<div class="ion-text-wrap ion-text-center ion-margin-bottom" [ngClass]="{'item-avatar-center': showUserAvatar}">
|
||||
<!-- Show user avatar. -->
|
||||
<img *ngIf="showUserAvatar" [src]="userAvatar" class="large-avatar" core-external-content [siteId]="siteId"
|
||||
alt="{{ 'core.pictureof' | translate:{$a: userFullName} }}" onError="this.src='assets/img/user-avatar.png'">
|
||||
<core-user-avatar class="large-avatar" *ngIf="showUserAvatar" [user]="siteInfo" [linkProfile]="false"
|
||||
[siteId]="siteId"></core-user-avatar>
|
||||
|
||||
<div class="core-login-site-logo" *ngIf="!showUserAvatar">
|
||||
<!-- Show site logo or a default image. -->
|
||||
|
@ -29,8 +29,8 @@
|
|||
<img *ngIf="!logoUrl" src="assets/img/login_logo.png" role="presentation" alt="">
|
||||
</div>
|
||||
|
||||
<p *ngIf="siteName" class="ion-padding core-sitename">
|
||||
<core-format-text [text]="siteName" [filter]="false"></core-format-text>
|
||||
<p *ngIf="siteInfo?.siteName" class="ion-padding core-sitename">
|
||||
<core-format-text [text]="siteInfo?.siteName" [filter]="false"></core-format-text>
|
||||
</p>
|
||||
<p class="core-siteurl">{{siteUrl}}</p>
|
||||
|
||||
|
|
|
@ -17,7 +17,7 @@ import { FormBuilder, FormGroup, Validators } from '@angular/forms';
|
|||
|
||||
import { CoreApp } from '@services/app';
|
||||
import { CoreNetwork } from '@services/network';
|
||||
import { CoreSites, CoreSitesReadingStrategy } from '@services/sites';
|
||||
import { CoreSiteBasicInfo, CoreSites, CoreSitesReadingStrategy } from '@services/sites';
|
||||
import { CoreDomUtils } from '@services/utils/dom';
|
||||
import { CoreUtils } from '@services/utils/utils';
|
||||
import { CoreLoginHelper } from '@features/login/services/login-helper';
|
||||
|
@ -47,9 +47,6 @@ export class CoreLoginReconnectPage implements OnInit, OnDestroy {
|
|||
credForm: FormGroup;
|
||||
siteUrl!: string;
|
||||
username!: string;
|
||||
userFullName!: string;
|
||||
userAvatar?: string;
|
||||
siteName!: string;
|
||||
logoUrl?: string;
|
||||
identityProviders?: CoreSiteIdentityProvider[];
|
||||
showForgottenPassword = true;
|
||||
|
@ -58,6 +55,7 @@ export class CoreLoginReconnectPage implements OnInit, OnDestroy {
|
|||
isOAuth = false;
|
||||
isLoggedOut: boolean;
|
||||
siteId!: string;
|
||||
siteInfo?: CoreSiteBasicInfo;
|
||||
showScanQR = false;
|
||||
showLoading = true;
|
||||
reconnectAttempts = 0;
|
||||
|
@ -104,20 +102,30 @@ export class CoreLoginReconnectPage implements OnInit, OnDestroy {
|
|||
throw new CoreError('Invalid site');
|
||||
}
|
||||
|
||||
this.siteUrl = site.getURL();
|
||||
|
||||
this.siteInfo = {
|
||||
id: this.siteId,
|
||||
siteUrl: this.siteUrl,
|
||||
siteUrlWithoutProtocol: this.siteUrl.replace(/^https?:\/\//, '').toLowerCase(),
|
||||
fullname: site.infos.fullname,
|
||||
firstname: site.infos.firstname,
|
||||
lastname: site.infos.lastname,
|
||||
siteName: await site.getSiteName(),
|
||||
userpictureurl: site.infos.userpictureurl,
|
||||
loggedOut: true, // Not used.
|
||||
};
|
||||
|
||||
this.username = site.infos.username;
|
||||
this.userFullName = site.infos.fullname;
|
||||
this.userAvatar = site.infos.userpictureurl;
|
||||
this.siteUrl = site.infos.siteurl;
|
||||
this.siteName = await site.getSiteName();
|
||||
this.supportConfig = new CoreUserAuthenticatedSupportConfig(site);
|
||||
|
||||
// If login was OAuth we should only reach this page if the OAuth method ID has changed.
|
||||
this.isOAuth = site.isOAuth();
|
||||
|
||||
const sites = await CoreLoginHelper.getAvailableSites();
|
||||
const availableSites = await CoreLoginHelper.getAvailableSites();
|
||||
|
||||
// Show logo instead of avatar if it's a fixed site.
|
||||
this.showUserAvatar = !!this.userAvatar && !sites.length;
|
||||
this.showUserAvatar = !availableSites.length;
|
||||
|
||||
this.checkSiteConfig(site);
|
||||
|
||||
|
@ -130,7 +138,7 @@ export class CoreLoginReconnectPage implements OnInit, OnDestroy {
|
|||
}
|
||||
|
||||
/**
|
||||
* Component destroyed.
|
||||
* @inheritdoc
|
||||
*/
|
||||
ngOnDestroy(): void {
|
||||
this.viewLeft = true;
|
||||
|
@ -191,10 +199,6 @@ export class CoreLoginReconnectPage implements OnInit, OnDestroy {
|
|||
|
||||
await CoreSites.checkApplication(this.siteConfig);
|
||||
|
||||
// Check logoURL if user avatar is not set.
|
||||
if (this.userAvatar?.startsWith(this.siteUrl + '/theme/image.php')) {
|
||||
this.showUserAvatar = false;
|
||||
}
|
||||
this.logoUrl = CoreLoginHelper.getLogoUrl(this.siteConfig);
|
||||
}
|
||||
|
||||
|
|
|
@ -33,12 +33,10 @@
|
|||
</ion-item-divider>
|
||||
|
||||
<ion-item button *ngFor="let site of sites" (click)="login($event, site.id)" detail="true">
|
||||
<ion-avatar slot="start">
|
||||
<img [src]="site.avatar" core-external-content [siteId]="site.id"
|
||||
alt="{{ 'core.pictureof' | translate:{$a: site.fullName} }}" onError="this.src='assets/img/user-avatar.png'">
|
||||
</ion-avatar>
|
||||
<core-user-avatar [user]="site" slot="start" [linkProfile]="false" [siteId]="site.id"></core-user-avatar>
|
||||
|
||||
<ion-label>
|
||||
<p class="item-heading">{{site.fullName}}</p>
|
||||
<p class="item-heading">{{site.fullname}}</p>
|
||||
</ion-label>
|
||||
<ion-badge slot="end" *ngIf="!showDelete && site.badge" @coreShowHideAnimation>
|
||||
<span aria-hidden="true">{{site.badge}}</span>
|
||||
|
|
|
@ -69,12 +69,10 @@
|
|||
|
||||
<!-- Template to render a site space usage. -->
|
||||
<ng-template #siteUsage let-site="site">
|
||||
<ion-avatar slot="start">
|
||||
<img [src]="site.avatar" core-external-content [siteId]="site.id" alt="{{ 'core.pictureof' | translate:{$a: site.fullName} }}"
|
||||
onError="this.src='assets/img/user-avatar.png'">
|
||||
</ion-avatar>
|
||||
<core-user-avatar [user]="site" slot="start" [linkProfile]="false" [siteId]="site.id"></core-user-avatar>
|
||||
|
||||
<ion-label class="ion-text-wrap">
|
||||
<p class="item-heading">{{site.fullName}}</p>
|
||||
<p class="item-heading">{{site.fullname}}</p>
|
||||
<ion-badge color="light" *ngIf="site.spaceUsage !== undefined">{{ site.spaceUsage | coreBytesToSize }}</ion-badge>
|
||||
</ion-label>
|
||||
<ion-button fill="clear" color="danger" slot="end" (click)="deleteSiteStorage(site)"
|
||||
|
|
|
@ -70,7 +70,7 @@ export class CoreSettingsSpaceUsagePage implements OnInit, OnDestroy {
|
|||
|
||||
if (siteInfo) {
|
||||
siteEntry.siteUrl = siteInfo.siteurl;
|
||||
siteEntry.fullName = siteInfo.fullname;
|
||||
siteEntry.fullname = siteInfo.fullname;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -94,12 +94,10 @@
|
|||
|
||||
<!-- Template to render a site to sync. -->
|
||||
<ng-template #siteSync let-site="site">
|
||||
<ion-avatar slot="start">
|
||||
<img [src]="site.avatar" core-external-content [siteId]="site.id" alt="{{ 'core.pictureof' | translate:{$a: site.fullName} }}"
|
||||
onError="this.src='assets/img/user-avatar.png'">
|
||||
</ion-avatar>
|
||||
<core-user-avatar [user]="site" slot="start" [linkProfile]="false" [siteId]="site.id"></core-user-avatar>
|
||||
|
||||
<ion-label>
|
||||
<p class="item-heading">{{site.fullName}}</p>
|
||||
<p class="item-heading">{{site.fullname}}</p>
|
||||
<p class="text-danger" *ngIf="site.loggedOut">{{ 'core.settings.logintosync' | translate }}</p>
|
||||
</ion-label>
|
||||
<core-button-with-spinner [loading]="isSynchronizing(site.id)" slot="end" *ngIf="!site.loggedOut">
|
||||
|
|
|
@ -84,7 +84,7 @@ export class CoreSettingsSynchronizationPage implements OnInit, OnDestroy {
|
|||
|
||||
if (siteInfo) {
|
||||
siteEntry.siteUrl = siteInfo.siteurl;
|
||||
siteEntry.fullName = siteInfo.fullname;
|
||||
siteEntry.fullname = siteInfo.fullname;
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
@ -18,12 +18,10 @@
|
|||
</ion-label>
|
||||
</ion-item>
|
||||
<ion-item *ngFor="let site of sites" (click)="storeInSite(site.id)" detail="false" button>
|
||||
<ion-avatar slot="start" aria-hidden="true">
|
||||
<img [src]="site.avatar" core-external-content [siteId]="site.id"
|
||||
alt="{{ 'core.pictureof' | translate:{$a: site.fullname} }}" onError="this.src='assets/img/user-avatar.png'">
|
||||
</ion-avatar>
|
||||
<core-user-avatar [user]="site" slot="start" [linkProfile]="false" [siteId]="site.id"></core-user-avatar>
|
||||
|
||||
<ion-label>
|
||||
<p class="item-heading">{{site.fullName}}</p>
|
||||
<p class="item-heading">{{site.fullname}}</p>
|
||||
<p>
|
||||
<core-format-text clean="true" [text]="site.siteName" [siteId]="site.id"></core-format-text>
|
||||
</p>
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
<ion-card *ngFor="let item of items">
|
||||
<ion-item class="ion-text-wrap" [href]="item.url" core-link [capture]="true">
|
||||
<ion-avatar slot="start" *ngIf="item.avatarUrl">
|
||||
<img [src]="item.avatarUrl" core-external-content alt="" role="presentation" onError="this.src='assets/img/user-avatar.png'">
|
||||
</ion-avatar>
|
||||
<core-user-avatar *ngIf="item.avatarUrl" [profileUrl]="item.avatarUrl" slot="start" [linkProfile]="false"></core-user-avatar>
|
||||
|
||||
<core-mod-icon *ngIf="item.iconUrl" [modicon]="item.iconUrl" slot="start" [showAlt]="false">
|
||||
</core-mod-icon>
|
||||
<ion-label>
|
||||
|
|
|
@ -17,7 +17,7 @@ import { CoreNavigator } from '@services/navigator';
|
|||
import { CoreSites } from '@services/sites';
|
||||
|
||||
import { makeSingleton, Translate } from '@singletons';
|
||||
import { CoreUserRole } from './user';
|
||||
import { CoreUserProfile, CoreUserRole } from './user';
|
||||
|
||||
/**
|
||||
* Service that provides some features regarding users information.
|
||||
|
@ -83,6 +83,21 @@ export class CoreUserHelperProvider {
|
|||
await CoreNavigator.navigate('/user/completeprofile', { params: { siteId }, reset: true });
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the user initials.
|
||||
*
|
||||
* @param user User object.
|
||||
* @returns Promise resolved with the user data.
|
||||
*/
|
||||
getUserInitials(user: Partial<CoreUserProfile>): string {
|
||||
if (!user.firstname && !user.lastname) {
|
||||
// @TODO: Use local info or check WS to get initials from.
|
||||
return '';
|
||||
}
|
||||
|
||||
return (user.firstname?.charAt(0) || '') + (user.lastname?.charAt(0) || '');
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export const CoreUserHelper = makeSingleton(CoreUserHelperProvider);
|
||||
|
|
|
@ -27,6 +27,8 @@ import { CoreStatusWithWarningsWSResponse, CoreWSExternalWarning } from '@servic
|
|||
import { CoreError } from '@classes/errors/error';
|
||||
import { USERS_TABLE_NAME, CoreUserDBRecord } from './database/user';
|
||||
import { CorePushNotifications } from '@features/pushnotifications/services/pushnotifications';
|
||||
import { CoreUserHelper } from './user-helper';
|
||||
import { CoreUrl } from '@singletons/url';
|
||||
|
||||
const ROOT_CACHE_KEY = 'mmUser:';
|
||||
|
||||
|
@ -673,6 +675,14 @@ export class CoreUserProvider {
|
|||
return;
|
||||
}
|
||||
|
||||
// Do not prefetch when initials are set and image is default.
|
||||
if ('firstname' in entry || 'lastname' in entry) {
|
||||
const initials = CoreUserHelper.getUserInitials(entry);
|
||||
if (initials && imageUrl && CoreUrl.parse(imageUrl)?.path === '/theme/image.php') {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
treated[imageUrl] = true;
|
||||
|
||||
try {
|
||||
|
|
|
@ -1260,9 +1260,11 @@ export class CoreSitesProvider {
|
|||
id: site.id,
|
||||
siteUrl: site.siteUrl,
|
||||
siteUrlWithoutProtocol: site.siteUrl.replace(/^https?:\/\//, '').toLowerCase(),
|
||||
fullName: siteInfo?.fullname,
|
||||
fullname: siteInfo?.fullname,
|
||||
firstname: siteInfo?.firstname,
|
||||
lastname: siteInfo?.lastname,
|
||||
siteName: siteInfo?.sitename,
|
||||
avatar: siteInfo?.userpictureurl,
|
||||
userpictureurl: siteInfo?.userpictureurl,
|
||||
siteHomeId: siteInfo?.siteid || 1,
|
||||
loggedOut: !!site.loggedOut,
|
||||
};
|
||||
|
@ -1300,8 +1302,8 @@ export class CoreSitesProvider {
|
|||
}
|
||||
|
||||
// Finally use fullname.
|
||||
textA = a.fullName?.toLowerCase().trim() || '';
|
||||
textB = b.fullName?.toLowerCase().trim() || '';
|
||||
textA = a.fullname?.toLowerCase().trim() || '';
|
||||
textB = b.fullname?.toLowerCase().trim() || '';
|
||||
|
||||
return textA.localeCompare(textB);
|
||||
});
|
||||
|
@ -2016,9 +2018,11 @@ export type CoreSiteBasicInfo = {
|
|||
id: string; // Site ID.
|
||||
siteUrl: string; // Site URL.
|
||||
siteUrlWithoutProtocol: string; // Site URL without protocol.
|
||||
fullName?: string; // User's full name.
|
||||
fullname?: string; // User's full name.
|
||||
firstname?: string; // User's first name.
|
||||
lastname?: string; // User's last name.
|
||||
userpictureurl?: string; // User avatar.
|
||||
siteName?: string; // Site's name.
|
||||
avatar?: string; // User's avatar.
|
||||
badge?: number; // Badge to display in the site.
|
||||
siteHomeId?: number; // Site home ID.
|
||||
loggedOut: boolean; // If Site is logged out.
|
||||
|
|
|
@ -330,6 +330,7 @@ html {
|
|||
|
||||
--core-large-avatar-size: 90px;
|
||||
--core-avatar-size: var(--a11y-min-target-size);
|
||||
--core-avatar-radius: 50%;
|
||||
|
||||
--core-courseimage-on-course-size: 72px;
|
||||
--core-courseimage-radius: var(--medium-radius);
|
||||
|
|
|
@ -1,6 +1,10 @@
|
|||
This files describes API changes in the Moodle Mobile app,
|
||||
information provided here is intended especially for developers.
|
||||
|
||||
=== 4.3.0 ===
|
||||
|
||||
- CoreSiteBasicInfo fullName attribute has changed to fullname and avatar to userpictureurl to match user fields.
|
||||
|
||||
=== 4.2.0 ===
|
||||
|
||||
- CoreIconComponent has been removed after deprecation period: Use CoreFaIconDirective instead.
|
||||
|
|
Loading…
Reference in New Issue