Skip to content

Commit

Permalink
MOBILE-4653 core: Add site logo component
Browse files Browse the repository at this point in the history
  • Loading branch information
crazyserver committed Dec 9, 2024
1 parent 53baee7 commit 0cb8b3f
Show file tree
Hide file tree
Showing 30 changed files with 235 additions and 136 deletions.
1 change: 1 addition & 0 deletions moodle.config.json
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@
"enableonboarding": true,
"forceColorScheme": "",
"forceLoginLogo": false,
"showTopLogo": "hidden",
"ioswebviewscheme": "moodleappfs",
"appstores": {
"android": "com.moodle.moodlemobile",
Expand Down
30 changes: 27 additions & 3 deletions src/core/classes/sites/unauthenticated-site.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*/
Expand All @@ -180,10 +180,34 @@ export class CoreUnauthenticatedSite {
getLogoUrl(config?: CoreSitePublicConfigResponse): string | undefined {
config = config ?? this.publicConfig;
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;
}

/**
Expand Down
14 changes: 14 additions & 0 deletions src/core/components/site-logo/site-logo.html
Original file line number Diff line number Diff line change
@@ -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>
22 changes: 22 additions & 0 deletions src/core/components/site-logo/site-logo.scss
Original file line number Diff line number Diff line change
@@ -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);
}
114 changes: 114 additions & 0 deletions src/core/components/site-logo/site-logo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
// (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 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?: CoreUnauthenticatedSite | CoreSite;
@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.siteId = ((this.site && 'getId' in this.site && this.site.getId()) ?? CoreSites.getCurrentSiteId()) || undefined;

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 name and logo.
*/
protected async loadInfo(): Promise<void> {
const site = this.site ?? CoreSites.getRequiredCurrentSite();
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 = '',
}
9 changes: 4 additions & 5 deletions src/core/directives/format-text.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,18 +116,17 @@ export class CoreFormatTextDirective implements OnChanges, OnDestroy, AsyncDirec
this.element = element.nativeElement;
this.element.classList.add('core-loading'); // Hide contents until they're treated.

this.emptyText = this.hideIfEmpty ? '' : '&nbsp;';
this.element.innerHTML = this.emptyText;

this.element.addEventListener('click', (event) => this.elementClicked(event));

this.siteId = this.siteId || CoreSites.getCurrentSiteId();
}

/**
* @inheritdoc
*/
ngOnChanges(changes: { [name: string]: SimpleChange }): void {
this.emptyText = this.hideIfEmpty ? '' : '&nbsp;';
this.siteId = this.siteId || CoreSites.getCurrentSiteId();

if (changes.text || changes.filter || changes.contextLevel || changes.contextInstanceId) {
this.formatAndRenderContents();

Expand Down Expand Up @@ -450,7 +449,7 @@ export class CoreFormatTextDirective implements OnChanges, OnDestroy, AsyncDirec
let formatted: string;
let filters: CoreFilterFilter[] = [];

if (filter) {
if (filter && siteId) {
const filterResult = await CoreFilterHelper.getFiltersAndFormatText(
this.text || '',
this.contextLevel || ContextLevel.SYSTEM,
Expand Down
13 changes: 12 additions & 1 deletion src/core/directives/tests/format-text.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,17 @@ describe('CoreFormatTextDirective', () => {
});

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
mockSingleton(CoreFilterHelper, {
getFiltersAndFormatText: () => Promise.resolve({
Expand Down Expand Up @@ -124,7 +135,7 @@ describe('CoreFormatTextDirective', () => {
ContextLevel.COURSE,
42,
expect.anything(),
undefined,
'25',
);
});

Expand Down
2 changes: 2 additions & 0 deletions src/core/features/courses/courses-my-lazy.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { CoreBlockComponentsModule } from '@features/block/components/components

import { CoreMainMenuComponentsModule } from '@features/mainmenu/components/components.module';
import { CoreCoursesMyPage } from '@features/courses/pages/my/my';
import { CoreSiteLogoComponent } from '@/core/components/site-logo/site-logo';

const routes: Routes = [
{
Expand All @@ -34,6 +35,7 @@ const routes: Routes = [
CoreSharedModule,
CoreBlockComponentsModule,
CoreMainMenuComponentsModule,
CoreSiteLogoComponent,
],
declarations: [
CoreCoursesMyPage,
Expand Down
3 changes: 1 addition & 2 deletions src/core/features/courses/pages/my/my.html
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,7 @@
</ion-buttons>
<ion-title>
<h1>
<core-format-text [text]="siteName" contextLevel="system" [contextInstanceId]="0" class="core-header-sitename" />
<img src="assets/img/top_logo.png" class="core-header-logo" [alt]="siteName">
<core-site-logo logoType="top" />
</h1>
</ion-title>
<ion-buttons slot="end">
Expand Down
13 changes: 0 additions & 13 deletions src/core/features/courses/pages/my/my.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,6 @@ export class CoreCoursesMyPage implements OnInit, OnDestroy, AsyncDirective {

@ViewChild(CoreBlockComponent) block!: CoreBlockComponent;

siteName = '';
downloadCoursesEnabled = false;
userId: number;
loadedBlock?: Partial<CoreCourseBlock>;
Expand All @@ -66,8 +65,6 @@ export class CoreCoursesMyPage implements OnInit, OnDestroy, AsyncDirective {
// Refresh the enabled flags if site is updated.
this.updateSiteObserver = CoreEvents.on(CoreEvents.SITE_UPDATED, async () => {
this.downloadCoursesEnabled = !CoreCourses.isDownloadCoursesDisabledInSite();
await this.loadSiteName();

}, CoreSites.getCurrentSiteId());

this.userId = CoreSites.getCurrentSiteUserId();
Expand Down Expand Up @@ -98,8 +95,6 @@ export class CoreCoursesMyPage implements OnInit, OnDestroy, AsyncDirective {

CoreSites.loginNavigationFinished();

await this.loadSiteName();

this.loadContent(true);
}

Expand Down Expand Up @@ -156,14 +151,6 @@ export class CoreCoursesMyPage implements OnInit, OnDestroy, AsyncDirective {
this.logView();
}

/**
* Load the site name.
*/
protected async loadSiteName(): Promise<void> {
const site = CoreSites.getRequiredCurrentSite();
this.siteName = await site.getSiteName() || '';
}

/**
* Load fallback blocks.
*/
Expand Down
2 changes: 2 additions & 0 deletions src/core/features/login/login-credentials-lazy.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { RouterModule, Routes } from '@angular/router';
import { CoreSharedModule } from '@/core/shared.module';
import { CoreLoginComponentsModule } from '@features/login/components/components.module';
import { CoreLoginCredentialsPage } from '@features/login/pages/credentials/credentials';
import { CoreSiteLogoComponent } from '@/core/components/site-logo/site-logo';

const routes: Routes = [
{
Expand All @@ -31,6 +32,7 @@ const routes: Routes = [
RouterModule.forChild(routes),
CoreSharedModule,
CoreLoginComponentsModule,
CoreSiteLogoComponent,
],
declarations: [
CoreLoginCredentialsPage,
Expand Down
2 changes: 2 additions & 0 deletions src/core/features/login/login-reconnect-lazy.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { RouterModule, Routes } from '@angular/router';
import { CoreSharedModule } from '@/core/shared.module';
import { CoreLoginComponentsModule } from '@features/login/components/components.module';
import { CoreLoginReconnectPage } from '@features/login/pages/reconnect/reconnect';
import { CoreSiteLogoComponent } from '@/core/components/site-logo/site-logo';

const routes: Routes = [
{
Expand All @@ -31,6 +32,7 @@ const routes: Routes = [
RouterModule.forChild(routes),
CoreSharedModule,
CoreLoginComponentsModule,
CoreSiteLogoComponent,
],
declarations: [
CoreLoginReconnectPage,
Expand Down
Loading

0 comments on commit 0cb8b3f

Please sign in to comment.