Merge pull request #4252 from crazyserver/MOBILE-4653

MOBILE-4653 core: Add site logo component
main
Dani Palou 2024-12-10 09:56:19 +01:00 committed by GitHub
commit d9a64ff99b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
30 changed files with 258 additions and 138 deletions

View File

@ -98,6 +98,7 @@
"enableonboarding": true, "enableonboarding": true,
"forceColorScheme": "", "forceColorScheme": "",
"forceLoginLogo": false, "forceLoginLogo": false,
"showTopLogo": "hidden",
"ioswebviewscheme": "moodleappfs", "ioswebviewscheme": "moodleappfs",
"appstores": { "appstores": {
"android": "com.moodle.moodlemobile", "android": "com.moodle.moodlemobile",

View File

@ -163,7 +163,7 @@ export class CoreUnauthenticatedSite {
} }
/** /**
* Check whether the app should use the local logo instead of the remote one. * Check whether the app should use the local logo instead or the remote one.
* *
* @returns Whether local logo is forced. * @returns Whether local logo is forced.
*/ */
@ -180,10 +180,34 @@ export class CoreUnauthenticatedSite {
getLogoUrl(config?: CoreSitePublicConfigResponse): string | undefined { getLogoUrl(config?: CoreSitePublicConfigResponse): string | undefined {
config = config ?? this.publicConfig; config = config ?? this.publicConfig;
if (!config || this.forcesLocalLogo()) { if (!config || this.forcesLocalLogo()) {
return 'assets/img/login_logo.png'; return;
} }
return config.logourl || config.compactlogourl || 'assets/img/login_logo.png'; return config.logourl || config.compactlogourl || undefined;
}
/**
* Check show top logo mode.
*
* @returns The top logo mode.
*/
getShowTopLogo(): 'online' | 'offline' | 'hidden' {
return this.isDemoModeSite() ? 'hidden' : CoreConstants.CONFIG.showTopLogo;
}
/**
* Get logo URL from a site public config.
*
* @param config Site public config.
* @returns Logo URL.
*/
getTopLogoUrl(config?: CoreSitePublicConfigResponse): string | undefined {
config = config ?? this.publicConfig;
if (!config || this.getShowTopLogo() !== 'online') {
return;
}
return config.logourl || config.compactlogourl || undefined;
} }
/** /**

View File

@ -0,0 +1,14 @@
<div class="core-logo-container" *ngIf="showLogo && logoLoaded">
<img *ngIf="siteLogo" [src]="siteLogo" class="core-logo" [alt]="siteName" core-external-content [siteId]="siteId"
(error)="imageError()">
<img *ngIf="!siteLogo" [src]="fallbackLogo" class="core-logo" [alt]="siteName">
</div>
<p *ngIf="showSiteName && siteNameMode === 'p' && siteName" class="ion-no-margin ion-no-padding core-logo-sitename">
<core-format-text [text]="siteName" contextLevel="system" [contextInstanceId]="0" [siteId]="siteId" />
</p>
<core-format-text *ngIf="showSiteName && siteNameMode === '' && siteName" [text]="siteName" contextLevel="system" [contextInstanceId]="0"
class="core-logo-sitename" [siteId]="siteId" />
<h2 *ngIf="showSiteName && siteNameMode === 'h2' && siteName" class="ion-margin-top ion-no-padding core-logo-sitename">
<core-format-text [text]="siteName" contextLevel="system" [contextInstanceId]="0" [siteId]="siteId" />
</h2>

View File

@ -0,0 +1,22 @@
:host {
display: block;
}
.core-logo-container {
margin-bottom: var(--core-site-logo-margin-bottom, 0px);
}
img.core-logo {
max-height: var(--core-site-logo-max-height);
max-width: var(--core-site-logo-max-width, 100%);
width: var(--core-site-logo-width, auto);
margin: var(--core-site-logo-margin, 0px);
}
.core-logo-sitename {
display: var(--core-site-logo-sitename-display, block);
font: var(--core-site-logo-sitename-font);
margin-bottom: var(--core-site-logo-sitename-margin-bottom, 0px);
}

View File

@ -0,0 +1,131 @@
// (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, OnDestroy, Input } from '@angular/core';
import { CoreSharedModule } from '@/core/shared.module';
import { CoreSites } from '@services/sites';
import { CoreEventObserver, CoreEvents } from '@singletons/events';
import { CoreSite } from '@classes/sites/site';
import { toBoolean } from '@/core/transforms/boolean';
import { CorePromiseUtils } from '@singletons/promise-utils';
import { CoreUnauthenticatedSite } from '@classes/sites/unauthenticated-site';
/**
* Component to render the current site logo.
*/
@Component({
selector: 'core-site-logo',
templateUrl: 'site-logo.html',
styleUrl: 'site-logo.scss',
standalone: true,
imports: [CoreSharedModule],
})
export class CoreSiteLogoComponent implements OnInit, OnDestroy {
@Input({ transform: toBoolean }) hideOnError = false;
@Input() siteNameMode: CoreSiteLogoSiteNameMode = CoreSiteLogoSiteNameMode.NOTAG;
@Input({ transform: toBoolean }) showLogo = true;
@Input() site?: CoreSite | CoreUnauthenticatedSite;
@Input() logoType: 'top' | 'login' = 'login';
siteName?: string;
siteId?: string;
siteLogo?: string;
logoLoaded = false;
fallbackLogo = '';
showSiteName = true;
protected updateSiteObserver?: CoreEventObserver;
/**
* @inheritdoc
*/
async ngOnInit(): Promise<void> {
this.loadSite();
this.updateSiteObserver = CoreEvents.on(CoreEvents.SITE_UPDATED, async () => {
await this.loadInfo();
}, this.siteId);
this.fallbackLogo = this.logoType === 'top' ? 'assets/img/top_logo.png' : 'assets/img/login_logo.png';
this.showSiteName = this.logoType !== 'top';
await this.loadInfo();
}
/**
* Function to handle the image error.
*/
imageError(): void {
if (this.hideOnError) {
this.showLogo = false;
}
this.siteLogo = undefined;
}
/**
* Load the site and siteId.
*
* @returns Site.
*/
protected loadSite(): CoreSite | CoreUnauthenticatedSite {
this.site = this.site ?? CoreSites.getRequiredCurrentSite();
// During login, the siteId could be not defined yet.
if (!this.siteId && this.site instanceof CoreSite) {
this.siteId = this.site.getId();
}
return this.site;
}
/**
* Load the site name and logo.
*/
protected async loadInfo(): Promise<void> {
const site = this.loadSite();
this.siteName = await site.getSiteName() || '';
this.showSiteName = this.logoType !== 'top' || site.getShowTopLogo() === 'hidden';
if (this.logoType === 'top' && site.getShowTopLogo() === 'hidden') {
this.showLogo = false;
} else {
// Get the public config to avoid race conditions when retrieving the logo.
const siteConfig = await CorePromiseUtils.ignoreErrors(site.getPublicConfig());
this.siteLogo = this.logoType === 'top'
? site.getTopLogoUrl(siteConfig)
: site.getLogoUrl(siteConfig);
}
this.logoLoaded = true;
}
/**
* @inheritdoc
*/
ngOnDestroy(): void {
this.updateSiteObserver?.off();
}
}
export const enum CoreSiteLogoSiteNameMode {
HEADING2 = 'h2',
PARAGRAPH = 'p',
NOTAG = '',
}

View File

@ -92,20 +92,25 @@ export class CoreFormatTextDirective implements OnChanges, OnDestroy, AsyncDirec
@Input({ transform: toBoolean }) wsNotFiltered = false; // If true it means the WS didn't filter the text for some reason. @Input({ transform: toBoolean }) wsNotFiltered = false; // If true it means the WS didn't filter the text for some reason.
@Input({ transform: toBoolean }) captureLinks = true; // Whether links should tried to be opened inside the app. @Input({ transform: toBoolean }) captureLinks = true; // Whether links should tried to be opened inside the app.
@Input({ transform: toBoolean }) openLinksInApp = false; // Whether links should be opened in InAppBrowser. @Input({ transform: toBoolean }) openLinksInApp = false; // Whether links should be opened in InAppBrowser.
@Input({ transform: toBoolean }) hideIfEmpty = false; // If true, the tag will contain nothing if text is empty.
@Input({ transform: toBoolean }) disabled = false; // If disabled, autoplay elements will be disabled. @Input({ transform: toBoolean }) disabled = false; // If disabled, autoplay elements will be disabled.
/**
* @deprecated since 5.0. Not used anymore.
*/
@Input() hideIfEmpty = false; // If true, the tag will contain nothing if text is empty.
@Output() afterRender = new EventEmitter<void>(); // Called when the data is rendered. @Output() afterRender = new EventEmitter<void>(); // Called when the data is rendered.
@Output() filterContentRenderingComplete = new EventEmitter<void>(); // Called when the filters have finished rendering content. @Output() filterContentRenderingComplete = new EventEmitter<void>(); // Called when the filters have finished rendering content.
@Output() onClick: EventEmitter<void> = new EventEmitter(); // Called when clicked. @Output() onClick: EventEmitter<void> = new EventEmitter(); // Called when clicked.
protected element: HTMLElement; protected element: HTMLElement;
protected elementControllers: ElementController[] = []; protected elementControllers: ElementController[] = [];
protected emptyText = '';
protected domPromises: CoreCancellablePromise<void>[] = []; protected domPromises: CoreCancellablePromise<void>[] = [];
protected domElementPromise?: CoreCancellablePromise<void>; protected domElementPromise?: CoreCancellablePromise<void>;
protected externalContentInstances: CoreExternalContentDirective[] = []; protected externalContentInstances: CoreExternalContentDirective[] = [];
protected static readonly EMPTY_TEXT = '&nbsp;';
constructor( constructor(
element: ElementRef, element: ElementRef,
protected viewContainerRef: ViewContainerRef, protected viewContainerRef: ViewContainerRef,
@ -116,18 +121,17 @@ export class CoreFormatTextDirective implements OnChanges, OnDestroy, AsyncDirec
this.element = element.nativeElement; this.element = element.nativeElement;
this.element.classList.add('core-loading'); // Hide contents until they're treated. this.element.classList.add('core-loading'); // Hide contents until they're treated.
this.emptyText = this.hideIfEmpty ? '' : '&nbsp;'; this.element.innerHTML = CoreFormatTextDirective.EMPTY_TEXT;
this.element.innerHTML = this.emptyText;
this.element.addEventListener('click', (event) => this.elementClicked(event)); this.element.addEventListener('click', (event) => this.elementClicked(event));
this.siteId = this.siteId || CoreSites.getCurrentSiteId();
} }
/** /**
* @inheritdoc * @inheritdoc
*/ */
ngOnChanges(changes: { [name: string]: SimpleChange }): void { ngOnChanges(changes: { [name: string]: SimpleChange }): void {
this.siteId = this.siteId || CoreSites.getCurrentSiteId();
if (changes.text || changes.filter || changes.contextLevel || changes.contextInstanceId) { if (changes.text || changes.filter || changes.contextLevel || changes.contextInstanceId) {
this.formatAndRenderContents(); this.formatAndRenderContents();
@ -365,7 +369,7 @@ export class CoreFormatTextDirective implements OnChanges, OnDestroy, AsyncDirec
this.externalContentInstances = []; this.externalContentInstances = [];
if (!this.text) { if (!this.text) {
this.element.innerHTML = this.emptyText; // Remove current contents. this.element.innerHTML = CoreFormatTextDirective.EMPTY_TEXT; // Remove current contents.
await this.finishRender(); await this.finishRender();
@ -450,7 +454,7 @@ export class CoreFormatTextDirective implements OnChanges, OnDestroy, AsyncDirec
let formatted: string; let formatted: string;
let filters: CoreFilterFilter[] = []; let filters: CoreFilterFilter[] = [];
if (filter) { if (filter && siteId) {
const filterResult = await CoreFilterHelper.getFiltersAndFormatText( const filterResult = await CoreFilterHelper.getFiltersAndFormatText(
this.text || '', this.text || '',
this.contextLevel || ContextLevel.SYSTEM, this.contextLevel || ContextLevel.SYSTEM,

View File

@ -96,6 +96,17 @@ describe('CoreFormatTextDirective', () => {
}); });
it('should get filters from server and format text', async () => { it('should get filters from server and format text', async () => {
// Arrange
const site = mock(new CoreSite('25', 'https://mysite.com', 'token'), {
getId: () => site.id,
});
mockSingleton(CoreSites, {
getSite: () => Promise.resolve(site),
getCurrentSite: () => site,
getCurrentSiteId: () => site.id,
});
// Arrange // Arrange
mockSingleton(CoreFilterHelper, { mockSingleton(CoreFilterHelper, {
getFiltersAndFormatText: () => Promise.resolve({ getFiltersAndFormatText: () => Promise.resolve({
@ -124,7 +135,7 @@ describe('CoreFormatTextDirective', () => {
ContextLevel.COURSE, ContextLevel.COURSE,
42, 42,
expect.anything(), expect.anything(),
undefined, '25',
); );
}); });

View File

@ -20,6 +20,7 @@ import { CoreBlockComponentsModule } from '@features/block/components/components
import { CoreMainMenuComponentsModule } from '@features/mainmenu/components/components.module'; import { CoreMainMenuComponentsModule } from '@features/mainmenu/components/components.module';
import { CoreCoursesMyPage } from '@features/courses/pages/my/my'; import { CoreCoursesMyPage } from '@features/courses/pages/my/my';
import { CoreSiteLogoComponent } from '@/core/components/site-logo/site-logo';
const routes: Routes = [ const routes: Routes = [
{ {
@ -34,6 +35,7 @@ const routes: Routes = [
CoreSharedModule, CoreSharedModule,
CoreBlockComponentsModule, CoreBlockComponentsModule,
CoreMainMenuComponentsModule, CoreMainMenuComponentsModule,
CoreSiteLogoComponent,
], ],
declarations: [ declarations: [
CoreCoursesMyPage, CoreCoursesMyPage,

View File

@ -5,8 +5,7 @@
</ion-buttons> </ion-buttons>
<ion-title> <ion-title>
<h1> <h1>
<core-format-text [text]="siteName" contextLevel="system" [contextInstanceId]="0" class="core-header-sitename" /> <core-site-logo logoType="top" />
<img src="assets/img/top_logo.png" class="core-header-logo" [alt]="siteName">
</h1> </h1>
</ion-title> </ion-title>
<ion-buttons slot="end"> <ion-buttons slot="end">

View File

@ -48,7 +48,6 @@ export class CoreCoursesMyPage implements OnInit, OnDestroy, AsyncDirective {
@ViewChild(CoreBlockComponent) block!: CoreBlockComponent; @ViewChild(CoreBlockComponent) block!: CoreBlockComponent;
siteName = '';
downloadCoursesEnabled = false; downloadCoursesEnabled = false;
userId: number; userId: number;
loadedBlock?: Partial<CoreCourseBlock>; loadedBlock?: Partial<CoreCourseBlock>;
@ -66,8 +65,6 @@ export class CoreCoursesMyPage implements OnInit, OnDestroy, AsyncDirective {
// Refresh the enabled flags if site is updated. // Refresh the enabled flags if site is updated.
this.updateSiteObserver = CoreEvents.on(CoreEvents.SITE_UPDATED, async () => { this.updateSiteObserver = CoreEvents.on(CoreEvents.SITE_UPDATED, async () => {
this.downloadCoursesEnabled = !CoreCourses.isDownloadCoursesDisabledInSite(); this.downloadCoursesEnabled = !CoreCourses.isDownloadCoursesDisabledInSite();
await this.loadSiteName();
}, CoreSites.getCurrentSiteId()); }, CoreSites.getCurrentSiteId());
this.userId = CoreSites.getCurrentSiteUserId(); this.userId = CoreSites.getCurrentSiteUserId();
@ -98,8 +95,6 @@ export class CoreCoursesMyPage implements OnInit, OnDestroy, AsyncDirective {
CoreSites.loginNavigationFinished(); CoreSites.loginNavigationFinished();
await this.loadSiteName();
this.loadContent(true); this.loadContent(true);
} }
@ -156,14 +151,6 @@ export class CoreCoursesMyPage implements OnInit, OnDestroy, AsyncDirective {
this.logView(); this.logView();
} }
/**
* Load the site name.
*/
protected async loadSiteName(): Promise<void> {
const site = CoreSites.getRequiredCurrentSite();
this.siteName = await site.getSiteName() || '';
}
/** /**
* Load fallback blocks. * Load fallback blocks.
*/ */

View File

@ -18,6 +18,7 @@ import { RouterModule, Routes } from '@angular/router';
import { CoreSharedModule } from '@/core/shared.module'; import { CoreSharedModule } from '@/core/shared.module';
import { CoreLoginComponentsModule } from '@features/login/components/components.module'; import { CoreLoginComponentsModule } from '@features/login/components/components.module';
import { CoreLoginCredentialsPage } from '@features/login/pages/credentials/credentials'; import { CoreLoginCredentialsPage } from '@features/login/pages/credentials/credentials';
import { CoreSiteLogoComponent } from '@/core/components/site-logo/site-logo';
const routes: Routes = [ const routes: Routes = [
{ {
@ -31,6 +32,7 @@ const routes: Routes = [
RouterModule.forChild(routes), RouterModule.forChild(routes),
CoreSharedModule, CoreSharedModule,
CoreLoginComponentsModule, CoreLoginComponentsModule,
CoreSiteLogoComponent,
], ],
declarations: [ declarations: [
CoreLoginCredentialsPage, CoreLoginCredentialsPage,

View File

@ -18,6 +18,7 @@ import { RouterModule, Routes } from '@angular/router';
import { CoreSharedModule } from '@/core/shared.module'; import { CoreSharedModule } from '@/core/shared.module';
import { CoreLoginComponentsModule } from '@features/login/components/components.module'; import { CoreLoginComponentsModule } from '@features/login/components/components.module';
import { CoreLoginReconnectPage } from '@features/login/pages/reconnect/reconnect'; import { CoreLoginReconnectPage } from '@features/login/pages/reconnect/reconnect';
import { CoreSiteLogoComponent } from '@/core/components/site-logo/site-logo';
const routes: Routes = [ const routes: Routes = [
{ {
@ -31,6 +32,7 @@ const routes: Routes = [
RouterModule.forChild(routes), RouterModule.forChild(routes),
CoreSharedModule, CoreSharedModule,
CoreLoginComponentsModule, CoreLoginComponentsModule,
CoreSiteLogoComponent,
], ],
declarations: [ declarations: [
CoreLoginReconnectPage, CoreLoginReconnectPage,

View File

@ -29,20 +29,14 @@
margin-bottom: 32px; margin-bottom: 32px;
.core-login-site { .core-login-site {
.core-login-site-logo { core-site-logo {
width: 90%; --core-site-logo-max-height: 104px;
max-width: 300px; --core-site-logo-sitename-margin-bottom: 8px;
margin: 0px auto; --core-site-logo-sitename-font: var(--mdl-typography-subtitle-font-lg);
img { --core-site-logo-max-width: 300px;
max-width: 100%; --core-site-logo-width: 90%;
max-height: 104px; --core-site-logo-margin: 0 auto;
}
}
.core-sitename {
font-size: 1.2rem;
margin-bottom: 8px;
} }
.core-siteurl { .core-siteurl {
@ -89,9 +83,12 @@
} }
@if ($core-fixed-url) { @if ($core-fixed-url) {
.core-sitename, .core-siteurl { .core-siteurl {
display: none; display: none;
} }
core-site-logo {
--core-site-logo-sitename-display: none;
}
} }
@if ($core-login-button-outline) { @if ($core-login-button-outline) {

View File

@ -23,16 +23,7 @@
<ng-container *ngIf="!siteCheckError && site && credForm"> <ng-container *ngIf="!siteCheckError && site && credForm">
<div class="ion-text-wrap ion-text-center core-login-info-box"> <div class="ion-text-wrap ion-text-center core-login-info-box">
<div class="core-login-site"> <div class="core-login-site">
<div class="core-login-site-logo"> <core-site-logo siteNameMode="h2" [site]="site" />
<!-- Show site logo or a default image. -->
<img *ngIf="logoUrl" [src]="logoUrl" role="presentation" alt="" core-external-content
onError="this.src='assets/img/login_logo.png'">
<img *ngIf="!logoUrl" src="assets/img/login_logo.png" role="presentation" alt="">
</div>
<h2 *ngIf="siteName" class="ion-margin-top ion-no-padding core-sitename">
<core-format-text [text]="siteName" [filter]="false" />
</h2>
<p class="core-siteurl" *ngIf="displaySiteUrl">{{site.siteUrl}}</p> <p class="core-siteurl" *ngIf="displaySiteUrl">{{site.siteUrl}}</p>
</div> </div>

View File

@ -57,8 +57,6 @@ export class CoreLoginCredentialsPage implements OnInit, OnDestroy {
credForm!: FormGroup; credForm!: FormGroup;
site!: CoreUnauthenticatedSite; site!: CoreUnauthenticatedSite;
siteName?: string;
logoUrl?: string;
authInstructions?: string; authInstructions?: string;
canSignup?: boolean; canSignup?: boolean;
pageLoaded = false; pageLoaded = false;
@ -104,11 +102,9 @@ export class CoreLoginCredentialsPage implements OnInit, OnDestroy {
} }
this.site = CoreSitesFactory.makeUnauthenticatedSite(siteUrl, this.siteConfig); this.site = CoreSitesFactory.makeUnauthenticatedSite(siteUrl, this.siteConfig);
this.logoUrl = this.site.getLogoUrl(this.siteConfig);
this.urlToOpen = CoreNavigator.getRouteParam('urlToOpen'); this.urlToOpen = CoreNavigator.getRouteParam('urlToOpen');
this.supportConfig = this.siteConfig && new CoreUserGuestSupportConfig(this.site, this.siteConfig); this.supportConfig = this.siteConfig && new CoreUserGuestSupportConfig(this.site, this.siteConfig);
this.displaySiteUrl = this.site.shouldDisplayInformativeLinks(); this.displaySiteUrl = this.site.shouldDisplayInformativeLinks();
this.siteName = await this.site.getSiteName();
} catch (error) { } catch (error) {
CoreDomUtils.showErrorModal(error); CoreDomUtils.showErrorModal(error);
@ -220,8 +216,6 @@ export class CoreLoginCredentialsPage implements OnInit, OnDestroy {
if (this.site.isDemoModeSite()) { if (this.site.isDemoModeSite()) {
this.showScanQR = false; this.showScanQR = false;
} else { } else {
this.siteName = this.siteConfig.sitename;
this.logoUrl = this.site.getLogoUrl(this.siteConfig);
this.showScanQR = await CoreLoginHelper.displayQRInCredentialsScreen(this.siteConfig.tool_mobile_qrcodetype); this.showScanQR = await CoreLoginHelper.displayQRInCredentialsScreen(this.siteConfig.tool_mobile_qrcodetype);
} }

View File

@ -31,16 +31,8 @@
<div class="ion-text-wrap ion-text-center core-login-info-box"> <div class="ion-text-wrap ion-text-center core-login-info-box">
<div class="core-login-site"> <div class="core-login-site">
<div class="core-login-site-logo" *ngIf="!showUserAvatar"> <core-site-logo siteNameMode="p" [showLogo]="!showUserAvatar" [site]="site" />
<!-- Show site logo or a default image. -->
<img *ngIf="logoUrl" [src]="logoUrl" role="presentation" onError="this.src='assets/img/login_logo.png'" alt=""
core-external-content [siteId]="siteId">
<img *ngIf="!logoUrl" src="assets/img/login_logo.png" role="presentation" alt="">
</div>
<p *ngIf="siteInfo?.siteName" class="ion-no-margin ion-no-padding core-sitename">
<core-format-text [text]="siteInfo?.siteName" [filter]="false" />
</p>
<p class="core-siteurl" *ngIf="displaySiteUrl">{{site.siteUrl}}</p> <p class="core-siteurl" *ngIf="displaySiteUrl">{{site.siteUrl}}</p>
</div> </div>

View File

@ -49,7 +49,6 @@ export class CoreLoginReconnectPage implements OnInit, OnDestroy {
credForm: FormGroup; credForm: FormGroup;
site!: CoreSite; site!: CoreSite;
logoUrl?: string;
displaySiteUrl = false; displaySiteUrl = false;
showForgottenPassword = true; showForgottenPassword = true;
showUserAvatar = false; showUserAvatar = false;
@ -202,7 +201,6 @@ export class CoreLoginReconnectPage implements OnInit, OnDestroy {
} }
this.isBrowserSSO = CoreLoginHelper.isSSOLoginNeeded(this.siteConfig.typeoflogin); this.isBrowserSSO = CoreLoginHelper.isSSOLoginNeeded(this.siteConfig.typeoflogin);
this.logoUrl = this.site.getLogoUrl();
await CoreSites.checkApplication(this.siteConfig); await CoreSites.checkApplication(this.siteConfig);
} }

View File

@ -48,7 +48,6 @@
.core-login-site-logo, .core-login-site-logo,
.core-login-site-list, .core-login-site-list,
.core-login-site-list-found { .core-login-site-list-found {
transition-delay: 0s;
visibility: visible; visibility: visible;
opacity: 1; opacity: 1;
transition: all 0.7s ease-in-out; transition: all 0.7s ease-in-out;
@ -61,6 +60,7 @@
margin-bottom: 0; margin-bottom: 0;
padding: 0; padding: 0;
max-height: 0; max-height: 0;
display: block !important; /** Avoid breaking the animation */
} }
} }

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 18 KiB

View File

@ -13,18 +13,14 @@
</ion-toolbar> </ion-toolbar>
</ion-header> </ion-header>
<ion-content> <ion-content>
<core-loading [hideUntil]="siteLogoLoaded && handlersLoaded"> <core-loading [hideUntil]="handlersLoaded">
<ion-list> <ion-list>
<!-- Site info with URL and clickable. --> <!-- Site info with URL and clickable. -->
<ion-item button class="core-usermenu-siteinfo ion-text-wrap" *ngIf="siteInfo && displaySiteUrl" lines="full" [detail]="false" <ion-item button class="core-usermenu-siteinfo ion-text-wrap" *ngIf="siteInfo && displaySiteUrl" lines="full" [detail]="false"
[href]="siteUrl" core-link> [href]="siteUrl" core-link>
<ion-label> <ion-label>
<!-- Show site logo. --> <!-- Show site logo. -->
<img class="core-usermenu-site-logo" *ngIf="siteLogo && siteLogoLoaded" [src]="siteLogo" role="presentation" alt="" <core-site-logo [hideOnError]="true" siteNameMode="p" />
core-external-content onError="this.class='image-not-found'">
<p class="core-usermenu-sitename">
<core-format-text [text]="siteName" contextLevel="system" [contextInstanceId]="0" [wsNotFiltered]="true" />
</p>
<a [href]="siteUrl" core-link class="core-usermenu-siteurl">{{ siteUrl }}</a> <a [href]="siteUrl" core-link class="core-usermenu-siteurl">{{ siteUrl }}</a>
</ion-label> </ion-label>
</ion-item> </ion-item>
@ -32,11 +28,7 @@
<ion-item class="core-usermenu-siteinfo ion-text-wrap" *ngIf="siteInfo && !displaySiteUrl" lines="full" detail="false"> <ion-item class="core-usermenu-siteinfo ion-text-wrap" *ngIf="siteInfo && !displaySiteUrl" lines="full" detail="false">
<ion-label> <ion-label>
<!-- Show site logo. --> <!-- Show site logo. -->
<img class="core-usermenu-site-logo" *ngIf="siteLogo && siteLogoLoaded" [src]="siteLogo" role="presentation" alt="" <core-site-logo [hideOnError]="true" siteNameMode="p" />
onError="this.class='image-not-found'">
<p class="core-usermenu-sitename">
<core-format-text [text]="siteName" contextLevel="system" [contextInstanceId]="0" [wsNotFiltered]="true" />
</p>
</ion-label> </ion-label>
</ion-item> </ion-item>

View File

@ -22,23 +22,17 @@
} }
} }
.core-usermenu-sitename { core-site-logo {
font: var(--mdl-typography-subtitle-font-lg); --core-site-logo-margin-bottom: 8px;
} --core-site-logo-max-height: var(--core-user-menu-site-logo-max-height);
img.core-usermenu-site-logo { --core-site-logo-sitename-font: var(--mdl-typography-subtitle-font-lg);
margin-bottom: 8px; --core-site-logo-sitename-margin-bottom: 2px;
max-height: var(--core-user-menu-site-logo-max-height);
} }
img.image-not-found {
display: none;
}
@if ($core-user-hide-sitename) { @if ($core-user-hide-sitename) {
.core-usermenu-sitename { core-site-logo {
display: none; --core-site-logo-sitename-display: none;
} }
} }

View File

@ -15,7 +15,6 @@
import { CoreConstants } from '@/core/constants'; import { CoreConstants } from '@/core/constants';
import { CoreSharedModule } from '@/core/shared.module'; import { CoreSharedModule } from '@/core/shared.module';
import { Component, OnDestroy, OnInit } from '@angular/core'; import { Component, OnDestroy, OnInit } from '@angular/core';
import { CoreSite } from '@classes/sites/site';
import { CoreSiteInfo } from '@classes/sites/unauthenticated-site'; import { CoreSiteInfo } from '@classes/sites/unauthenticated-site';
import { CoreFilter } from '@features/filter/services/filter'; import { CoreFilter } from '@features/filter/services/filter';
import { CoreUserAuthenticatedSupportConfig } from '@features/user/classes/support/authenticated-support-config'; import { CoreUserAuthenticatedSupportConfig } from '@features/user/classes/support/authenticated-support-config';
@ -31,10 +30,10 @@ import { CoreModals } from '@services/modals';
import { CoreNavigator } from '@services/navigator'; import { CoreNavigator } from '@services/navigator';
import { CoreSites } from '@services/sites'; import { CoreSites } from '@services/sites';
import { CoreDomUtils } from '@services/utils/dom'; import { CoreDomUtils } from '@services/utils/dom';
import { CorePromiseUtils } from '@singletons/promise-utils';
import { ModalController } from '@singletons'; import { ModalController } from '@singletons';
import { Subscription } from 'rxjs'; import { Subscription } from 'rxjs';
import { CoreLoginHelper } from '@features/login/services/login-helper'; import { CoreLoginHelper } from '@features/login/services/login-helper';
import { CoreSiteLogoComponent } from '@/core/components/site-logo/site-logo';
/** /**
* Component to display a user menu. * Component to display a user menu.
@ -46,6 +45,7 @@ import { CoreLoginHelper } from '@features/login/services/login-helper';
standalone: true, standalone: true,
imports: [ imports: [
CoreSharedModule, CoreSharedModule,
CoreSiteLogoComponent,
], ],
}) })
export class CoreMainMenuUserMenuComponent implements OnInit, OnDestroy { export class CoreMainMenuUserMenuComponent implements OnInit, OnDestroy {
@ -53,8 +53,6 @@ export class CoreMainMenuUserMenuComponent implements OnInit, OnDestroy {
siteId?: string; siteId?: string;
siteInfo?: CoreSiteInfo; siteInfo?: CoreSiteInfo;
siteName?: string; siteName?: string;
siteLogo?: string;
siteLogoLoaded = false;
siteUrl?: string; siteUrl?: string;
displaySiteUrl = false; displaySiteUrl = false;
handlers: CoreUserProfileHandlerData[] = []; handlers: CoreUserProfileHandlerData[] = [];
@ -81,8 +79,6 @@ export class CoreMainMenuUserMenuComponent implements OnInit, OnDestroy {
this.removeAccountOnLogout = !!CoreConstants.CONFIG.removeaccountonlogout; this.removeAccountOnLogout = !!CoreConstants.CONFIG.removeaccountonlogout;
this.displaySiteUrl = currentSite.shouldDisplayInformativeLinks(); this.displaySiteUrl = currentSite.shouldDisplayInformativeLinks();
this.loadSiteLogo(currentSite);
if (!this.siteInfo) { if (!this.siteInfo) {
return; return;
} }
@ -127,25 +123,6 @@ export class CoreMainMenuUserMenuComponent implements OnInit, OnDestroy {
}); });
} }
/**
* Load site logo from current site public config.
*
* @param currentSite Current site object.
* @returns Promise resolved when done.
*/
protected async loadSiteLogo(currentSite: CoreSite): Promise<void> {
if (currentSite.forcesLocalLogo()) {
this.siteLogo = currentSite.getLogoUrl();
this.siteLogoLoaded = true;
return;
}
const siteConfig = await CorePromiseUtils.ignoreErrors(currentSite.getPublicConfig());
this.siteLogo = currentSite.getLogoUrl(siteConfig);
this.siteLogoLoaded = true;
}
/** /**
* Opens User profile page. * Opens User profile page.
* *

View File

@ -22,6 +22,7 @@ import { CoreMainMenuHomeHandlerService } from '@features/mainmenu/services/hand
import { CoreMainMenuComponentsModule } from '@features/mainmenu/components/components.module'; import { CoreMainMenuComponentsModule } from '@features/mainmenu/components/components.module';
import { resolveHomeRoutes } from '@features/mainmenu/mainmenu-home-routing.module'; import { resolveHomeRoutes } from '@features/mainmenu/mainmenu-home-routing.module';
import { CoreMainMenuHomePage } from '@features/mainmenu/pages/home/home'; import { CoreMainMenuHomePage } from '@features/mainmenu/pages/home/home';
import { CoreSiteLogoComponent } from '@/core/components/site-logo/site-logo';
/** /**
* Build module routes. * Build module routes.
@ -49,6 +50,7 @@ function buildRoutes(injector: Injector): Routes {
imports: [ imports: [
CoreSharedModule, CoreSharedModule,
CoreMainMenuComponentsModule, CoreMainMenuComponentsModule,
CoreSiteLogoComponent,
], ],
providers: [ providers: [
{ provide: ROUTES, multi: true, useFactory: buildRoutes, deps: [Injector] }, { provide: ROUTES, multi: true, useFactory: buildRoutes, deps: [Injector] },

View File

@ -5,8 +5,7 @@
</ion-buttons> </ion-buttons>
<ion-title> <ion-title>
<h1> <h1>
<core-format-text [text]="siteName" contextLevel="system" [contextInstanceId]="0" class="core-header-sitename" /> <core-site-logo logoType="top" />
<img src="assets/img/top_logo.png" class="core-header-logo" [alt]="siteName">
</h1> </h1>
</ion-title> </ion-title>
<ion-buttons slot="end"> <ion-buttons slot="end">

View File

@ -69,15 +69,8 @@ ion-header.header-md {
display: inline !important; display: inline !important;
} }
h1 { h1 core-site-logo {
.core-header-logo { --core-site-logo-max-height: var(--core-mainpage-headerlogo-maxheight);
max-height: var(--core-mainpage-headerlogo-maxheight);
display: var( --core-mainpage-headerlogo-display);
}
.core-header-sitename {
display: var(--core-mainpage-sitename-display);
}
} }
h1, h2, .subheading { h1, h2, .subheading {

View File

@ -84,7 +84,6 @@ $core-dd-question-colors: #FFFFFF, #B0C4DE, #DCDCDC, #D8BFD8, #87CEFA, #DAA520,
$core-text-hightlight-background-color: lighten($blue, 40%) !default; $core-text-hightlight-background-color: lighten($blue, 40%) !default;
$core-fixed-url: false !default; $core-fixed-url: false !default;
$core-dashboard-logo: false !default;
$core-always-show-main-menu: false !default; $core-always-show-main-menu: false !default;
$core-format-text-never-shorten: false !default; $core-format-text-never-shorten: false !default;

View File

@ -231,15 +231,7 @@
--rotate-expandable: rotate(90deg); --rotate-expandable: rotate(90deg);
--core-mainpage-sitename-display: none;
--core-mainpage-headerlogo-display: none;
--core-mainpage-headerlogo-maxheight: calc(var(--core-header-toolbar-height) - 16px); --core-mainpage-headerlogo-maxheight: calc(var(--core-header-toolbar-height) - 16px);
@if ($core-dashboard-logo) {
--core-mainpage-headerlogo-display: inline;
} @else {
--core-mainpage-sitename-display: inline;
}
} }
/** @deprecated since 4.3 **/ /** @deprecated since 4.3 **/

View File

@ -53,6 +53,7 @@ export interface EnvironmentConfig {
enableonboarding: boolean; enableonboarding: boolean;
forceColorScheme: CoreColorScheme; forceColorScheme: CoreColorScheme;
forceLoginLogo: boolean; forceLoginLogo: boolean;
showTopLogo: 'online' | 'offline' | 'hidden';
ioswebviewscheme: string; ioswebviewscheme: string;
appstores: Record<string, string>; appstores: Record<string, string>;
displayqroncredentialscreen?: boolean; displayqroncredentialscreen?: boolean;