diff --git a/backend/src/main/java/ch/puzzle/okr/dto/ClientConfigDto.java b/backend/src/main/java/ch/puzzle/okr/dto/ClientConfigDto.java index 5501587c12..3648fce74d 100644 --- a/backend/src/main/java/ch/puzzle/okr/dto/ClientConfigDto.java +++ b/backend/src/main/java/ch/puzzle/okr/dto/ClientConfigDto.java @@ -8,6 +8,7 @@ public record ClientConfigDto( String clientId, String favicon, String logo, + String title, HashMap customStyles ) { } diff --git a/backend/src/main/java/ch/puzzle/okr/service/clientconfig/ClientConfigProperties.java b/backend/src/main/java/ch/puzzle/okr/service/clientconfig/ClientConfigProperties.java index c65580041b..3dc9860d71 100644 --- a/backend/src/main/java/ch/puzzle/okr/service/clientconfig/ClientConfigProperties.java +++ b/backend/src/main/java/ch/puzzle/okr/service/clientconfig/ClientConfigProperties.java @@ -1,18 +1,46 @@ package ch.puzzle.okr.service.clientconfig; -import ch.puzzle.okr.dto.ClientConfigDto; -import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.stereotype.Service; import java.util.HashMap; + @ConfigurationProperties("okr.clientconfig") public class ClientConfigProperties { private String favicon; - private String logo; + private String title; + private HashMap customStyles = new HashMap<>(); + + public void setCustomStyles(HashMap customStyles) { + this.customStyles = customStyles; + } + + public String getFavicon() { + return favicon; + } + + public void setFavicon(String favicon) { + this.favicon = favicon; + } + + public String getLogo() { + return logo; + } + + public void setLogo(String logo) { + this.logo = logo; + } + + public HashMap getCustomStyles() { + return customStyles; + } - private HashMap customStyles; + public String getTitle() { + return title; + } + public void setTitle(String title) { + this.title = title; + } } diff --git a/backend/src/main/java/ch/puzzle/okr/service/clientconfig/ClientConfigService.java b/backend/src/main/java/ch/puzzle/okr/service/clientconfig/ClientConfigService.java index 3754879b12..35725967c9 100644 --- a/backend/src/main/java/ch/puzzle/okr/service/clientconfig/ClientConfigService.java +++ b/backend/src/main/java/ch/puzzle/okr/service/clientconfig/ClientConfigService.java @@ -1,7 +1,9 @@ package ch.puzzle.okr.service.clientconfig; import ch.puzzle.okr.dto.ClientConfigDto; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.stereotype.Service; import java.util.HashMap; @@ -9,6 +11,7 @@ @Service public class ClientConfigService { + @Value("${spring.security.oauth2.resourceserver.jwt.issuer-uri}") private String issuer; @@ -17,18 +20,22 @@ public class ClientConfigService { @Value("${spring.security.oauth2.resourceserver.opaquetoken.client-id}") private String clientId; + private final ClientConfigProperties clientConfigProperties; - @Value("${okr.clientconfig.favicon}") - private String favicon; - - @Value("${okr.clientconfig.logo}") - private String logo; - - @Value("${okr.clientconfig.customStyles}") - private HashMap customStyles; + public ClientConfigService(ClientConfigProperties clientConfigProperties) { + this.clientConfigProperties = clientConfigProperties; + } public ClientConfigDto getConfigBasedOnActiveEnv() { - return new ClientConfigDto(activeProfile, issuer, clientId, favicon, logo, customStyles); + return new ClientConfigDto( + activeProfile, + issuer, + clientId, + this.clientConfigProperties.getFavicon(), + this.clientConfigProperties.getLogo(), + this.clientConfigProperties.getTitle(), + this.clientConfigProperties.getCustomStyles() + ); } } diff --git a/backend/src/main/resources/application-staging.properties b/backend/src/main/resources/application-staging.properties index 80c9a109ff..8aabb902fd 100644 --- a/backend/src/main/resources/application-staging.properties +++ b/backend/src/main/resources/application-staging.properties @@ -4,4 +4,4 @@ logging.level.org.springframework=debug spring.security.oauth2.resourceserver.opaquetoken.client-id=pitc_okr_staging okr.user.champion.usernames=peggimann -okr.clientconfig.customStyles.okr-topbar-background-color=#ab31ad \ No newline at end of file +okr.clientconfig.customstyles.okr-topbar-background-color=#ab31ad \ No newline at end of file diff --git a/backend/src/main/resources/application.properties b/backend/src/main/resources/application.properties index bbc811a955..528f21be38 100644 --- a/backend/src/main/resources/application.properties +++ b/backend/src/main/resources/application.properties @@ -53,4 +53,4 @@ okr.jwt.user.email=email okr.clientconfig.favicon=assets/favicon.png okr.clientconfig.logo=assets/images/okr-logo.svg -okr.clientconfig.customStyles.okr-topbar-background-color=#ff0000 \ No newline at end of file +okr.clientconfig.title=Puzzle OKR \ No newline at end of file diff --git a/frontend/src/app/app.module.ts b/frontend/src/app/app.module.ts index d11efee938..fa25ac45eb 100644 --- a/frontend/src/app/app.module.ts +++ b/frontend/src/app/app.module.ts @@ -71,6 +71,7 @@ import { ActionPlanComponent } from './action-plan/action-plan.component'; import { CdkDrag, CdkDropList } from '@angular/cdk/drag-drop'; import { TeamManagementComponent } from './shared/dialog/team-management/team-management.component'; import { KeyresultDialogComponent } from './shared/dialog/keyresult-dialog/keyresult-dialog.component'; +import { CustomizationService } from './shared/services/customization.service'; function initOauthFactory(configService: ConfigService, oauthService: OAuthService) { return async () => { @@ -202,4 +203,6 @@ export const MY_FORMATS = { bootstrap: [AppComponent], schemas: [CUSTOM_ELEMENTS_SCHEMA], }) -export class AppModule {} +export class AppModule { + constructor(customizationService: CustomizationService) {} +} diff --git a/frontend/src/app/application-top-bar/application-top-bar.component.html b/frontend/src/app/application-top-bar/application-top-bar.component.html index 27d2589865..9ceb0f491e 100644 --- a/frontend/src/app/application-top-bar/application-top-bar.component.html +++ b/frontend/src/app/application-top-bar/application-top-bar.component.html @@ -1,7 +1,7 @@
- okr-logo + okr-logo
diff --git a/frontend/src/app/application-top-bar/application-top-bar.component.scss b/frontend/src/app/application-top-bar/application-top-bar.component.scss index ae740966bf..63082ca229 100644 --- a/frontend/src/app/application-top-bar/application-top-bar.component.scss +++ b/frontend/src/app/application-top-bar/application-top-bar.component.scss @@ -12,7 +12,12 @@ z-index: 102; height: inherit; justify-content: space-between; - background-color: var(--okr-topbar-background-color) + background-color: var(--okr-topbar-background-color); + + img { + max-height: calc(100% - 16px); + width: auto; + } } .topBarEntry { diff --git a/frontend/src/app/application-top-bar/application-top-bar.component.ts b/frontend/src/app/application-top-bar/application-top-bar.component.ts index 1d771ec829..fa723334ff 100644 --- a/frontend/src/app/application-top-bar/application-top-bar.component.ts +++ b/frontend/src/app/application-top-bar/application-top-bar.component.ts @@ -21,8 +21,8 @@ export class ApplicationTopBarComponent implements OnInit { @Input() hasAdminAccess!: ReplaySubject; + logoSrc$ = new BehaviorSubject('assets/images/okr-logo.svg'); private dialogRef!: MatDialogRef | undefined; - logoSrc$ = new BehaviorSubject(undefined); constructor( private oauthService: OAuthService, @@ -36,7 +36,9 @@ export class ApplicationTopBarComponent implements OnInit { this.configService.config$ .pipe( map((config) => { - this.logoSrc$.next(config.logo); + if (config.logo) { + this.logoSrc$.next(config.logo); + } }), ) .subscribe(); @@ -45,6 +47,7 @@ export class ApplicationTopBarComponent implements OnInit { this.username.next(this.oauthService.getIdentityClaims()['name']); } } + logOut() { const currentUrlTree = this.router.createUrlTree([], { queryParams: {} }); this.router.navigateByUrl(currentUrlTree).then(() => { diff --git a/frontend/src/app/config.service.ts b/frontend/src/app/config.service.ts index bc2b71a370..27faea76e1 100644 --- a/frontend/src/app/config.service.ts +++ b/frontend/src/app/config.service.ts @@ -1,8 +1,7 @@ import { Injectable } from '@angular/core'; import { HttpClient } from '@angular/common/http'; import { Observable, shareReplay } from 'rxjs'; -import { ClientConfig } from "./shared/types/model/ClientConfig"; - +import { ClientConfig } from './shared/types/model/ClientConfig'; @Injectable({ providedIn: 'root', @@ -11,8 +10,6 @@ export class ConfigService { public config$: Observable; constructor(private httpClient: HttpClient) { - this.config$ = this.httpClient - .get('/config') - .pipe(shareReplay()); + this.config$ = this.httpClient.get('/config').pipe(shareReplay()); } } diff --git a/frontend/src/app/shared/services/customization.service.ts b/frontend/src/app/shared/services/customization.service.ts index d7dc70e991..1af88f3a3a 100644 --- a/frontend/src/app/shared/services/customization.service.ts +++ b/frontend/src/app/shared/services/customization.service.ts @@ -1,6 +1,7 @@ -import { Injectable } from '@angular/core'; -import { ConfigService } from "../../config.service"; -import { CustomizationConfig } from "../types/model/ClientConfig"; +import { Inject, Injectable } from '@angular/core'; +import { ConfigService } from '../../config.service'; +import { CustomizationConfig, CustomStyles } from '../types/model/ClientConfig'; +import { DOCUMENT } from '@angular/common'; @Injectable({ providedIn: 'root', @@ -8,14 +9,17 @@ import { CustomizationConfig } from "../types/model/ClientConfig"; export class CustomizationService { private currentConfig?: CustomizationConfig; - constructor(private configService: ConfigService) { - configService.config$.subscribe(config => { + constructor( + configService: ConfigService, + @Inject(DOCUMENT) private document: Document, + ) { + configService.config$.subscribe((config) => { this.updateCustomizations(config); - }) + }); } - private updateCustomizations(config: CustomizationConfig) { + this.setTitle(config.title); this.setFavicon(config.favicon); this.setStyleCustomizations(config.customStyles); @@ -27,46 +31,55 @@ export class CustomizationService { return; } - document.getElementById("favicon")?.setAttribute("src", favicon); + this.document.getElementById('favicon')?.setAttribute('href', favicon); + } + + private setTitle(title: string) { + if (!title || this.currentConfig?.title === title) { + return; + } + debugger; + this.document.querySelector('title')!.innerHTML = title; } - private setStyleCustomizations(customStylesMap: Map) { + private setStyleCustomizations(customStylesMap: CustomStyles) { if (!customStylesMap || this.areStylesTheSame(customStylesMap)) { return; } - this.removeStyles(this.currentConfig?.customStyles) + this.removeStyles(this.currentConfig?.customStyles); this.setStyles(customStylesMap); } - private areStylesTheSame(customStylesMap: Map) { + private areStylesTheSame(customStylesMap: CustomStyles) { return JSON.stringify(this.currentConfig?.customStyles) === JSON.stringify(customStylesMap); } - private removeStyles(customStylesMap: Map | undefined) { + private setStyles(customStylesMap: CustomStyles | undefined) { if (!customStylesMap) { return; } - const styles = document?.getElementById("html")!.style; + + const styles = this.document.querySelector('html')!.style; if (!styles) { - return + return; } - Array.from(customStylesMap.entries()).forEach(([varName, varValue]) => { + Object.entries(customStylesMap).forEach(([varName, varValue]) => { styles.setProperty(`--${varName}`, varValue); }); } - private setStyles(customStylesMap: Map) { + private removeStyles(customStylesMap: CustomStyles | undefined) { if (!customStylesMap) { return; } - const styles = document?.getElementById("html")!.style; + + const styles = this.document.querySelector('html')!.style; if (!styles) { - return + return; } - - Array.from(customStylesMap.keys()).forEach((varName) => { + Object.keys(customStylesMap).forEach((varName) => { styles.removeProperty(`--${varName}`); }); } diff --git a/frontend/src/app/shared/types/model/ClientConfig.ts b/frontend/src/app/shared/types/model/ClientConfig.ts index fadb7ae552..031e4bb55a 100644 --- a/frontend/src/app/shared/types/model/ClientConfig.ts +++ b/frontend/src/app/shared/types/model/ClientConfig.ts @@ -1,14 +1,17 @@ - export interface AuthConfig { activeProfile: string; issuer: string; clientId: string; } +export interface CustomStyles { + [key: string]: string; +} + export interface CustomizationConfig { + title: string; favicon: string; logo: string; - customStyles: Map; -} -export interface ClientConfig extends AuthConfig, CustomizationConfig { + customStyles: CustomStyles; } +export interface ClientConfig extends AuthConfig, CustomizationConfig {} diff --git a/frontend/src/index.html b/frontend/src/index.html index 929342f919..a368b64e9b 100644 --- a/frontend/src/index.html +++ b/frontend/src/index.html @@ -2,7 +2,7 @@ - Puzzle OKR + OKR Tool diff --git a/frontend/src/style/_variables.scss b/frontend/src/style/_variables.scss index 5b91985d73..8fe6a1340c 100644 --- a/frontend/src/style/_variables.scss +++ b/frontend/src/style/_variables.scss @@ -48,5 +48,5 @@ $pz-dark-blue-palette: ( --mdc-filled-button-label-text-tracking: normal; --mdc-outlined-button-label-text-tracking: normal; - --okr-topbar-background-color: $pz-dark-blue; + --okr-topbar-background-color: #1e5a96; }