refactor: finalize bootstrap-driven layout and widget runtime
This commit is contained in:
246
src/app/core/config/footer-resolver.service.ts
Normal file
246
src/app/core/config/footer-resolver.service.ts
Normal file
@@ -0,0 +1,246 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { map, Observable } from 'rxjs';
|
||||
import { ConfigService } from './config.service';
|
||||
import {
|
||||
BootstrapConfig,
|
||||
FooterConfig,
|
||||
FooterNavigationGroupConfig,
|
||||
FooterNavigationItemConfig,
|
||||
NavigationConfig,
|
||||
NavigationItemConfig
|
||||
} from '../../shared/models/config';
|
||||
import { StaticPageResolverService } from './static-page-resolver.service';
|
||||
import { LanguageService } from '../../services/language.service';
|
||||
|
||||
export interface FooterResolvedItem {
|
||||
id: string;
|
||||
label: string;
|
||||
route: string;
|
||||
}
|
||||
|
||||
export interface FooterResolvedGroup {
|
||||
id: string;
|
||||
title: string;
|
||||
items: FooterResolvedItem[];
|
||||
}
|
||||
|
||||
export interface FooterPaymentIcon {
|
||||
src: string;
|
||||
alt: string;
|
||||
width: number;
|
||||
height: number;
|
||||
}
|
||||
|
||||
export interface FooterResolvedModel {
|
||||
groups: FooterResolvedGroup[];
|
||||
paymentIcons: FooterPaymentIcon[];
|
||||
copyrightText: string;
|
||||
}
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class FooterResolverService {
|
||||
constructor(
|
||||
private readonly configService: ConfigService,
|
||||
private readonly staticPageResolver: StaticPageResolverService,
|
||||
private readonly languageService: LanguageService
|
||||
) {}
|
||||
|
||||
resolveFooterModel(): Observable<FooterResolvedModel> {
|
||||
return this.configService.loadBootstrap().pipe(
|
||||
map((bootstrap) => ({
|
||||
groups: this.resolveFooterGroups(bootstrap),
|
||||
paymentIcons: this.resolvePaymentIcons(bootstrap.footer),
|
||||
copyrightText: this.resolveCopyrightText(bootstrap)
|
||||
}))
|
||||
);
|
||||
}
|
||||
|
||||
private resolveFooterGroups(bootstrap: BootstrapConfig): FooterResolvedGroup[] {
|
||||
const footer = bootstrap.navigation?.footer ?? [];
|
||||
const lang = this.languageService.currentLanguage();
|
||||
const legalKeys = bootstrap.footer?.legalPageKeys ?? [];
|
||||
const legalItems = legalKeys
|
||||
.map(key => {
|
||||
const page = this.staticPageResolver.resolveByKeyFromBootstrap(bootstrap, key, lang);
|
||||
if (!page) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
id: `legal-${key}`,
|
||||
label: page.title,
|
||||
route: page.route
|
||||
} as FooterResolvedItem;
|
||||
})
|
||||
.filter((item): item is FooterResolvedItem => item != null);
|
||||
|
||||
if (this.isGroupedFooter(footer)) {
|
||||
const groups = footer
|
||||
.map((group, index) => this.resolveGroupFromConfig(group, bootstrap, lang, index))
|
||||
.filter((group): group is FooterResolvedGroup => group != null && group.items.length > 0);
|
||||
|
||||
if (legalItems.length > 0) {
|
||||
groups.push({
|
||||
id: 'footer-group-legal',
|
||||
title: 'Legal',
|
||||
items: legalItems
|
||||
});
|
||||
}
|
||||
|
||||
return groups;
|
||||
}
|
||||
|
||||
const items = (footer as NavigationItemConfig[])
|
||||
.map(item => this.resolveLegacyItem(item, bootstrap, lang))
|
||||
.filter((item): item is FooterResolvedItem => item != null);
|
||||
|
||||
if (!items.length) {
|
||||
return legalItems.length
|
||||
? [{ id: 'footer-group-legal', title: 'Legal', items: legalItems }]
|
||||
: [];
|
||||
}
|
||||
|
||||
const groups: FooterResolvedGroup[] = [{
|
||||
id: 'footer-group-default',
|
||||
title: '',
|
||||
items
|
||||
}];
|
||||
|
||||
if (legalItems.length > 0) {
|
||||
groups.push({
|
||||
id: 'footer-group-legal',
|
||||
title: 'Legal',
|
||||
items: legalItems
|
||||
});
|
||||
}
|
||||
|
||||
return groups;
|
||||
}
|
||||
|
||||
private resolveGroupFromConfig(
|
||||
group: FooterNavigationGroupConfig,
|
||||
bootstrap: BootstrapConfig,
|
||||
lang: string,
|
||||
index: number
|
||||
): FooterResolvedGroup | null {
|
||||
const items = (group.items ?? [])
|
||||
.map(item => this.resolveGroupItem(item, bootstrap, lang))
|
||||
.filter((item): item is FooterResolvedItem => item != null);
|
||||
|
||||
if (!items.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
id: `footer-group-${index}`,
|
||||
title: this.staticPageResolver.resolveFooterTitle(group.groupTitle, lang),
|
||||
items
|
||||
};
|
||||
}
|
||||
|
||||
private resolveGroupItem(
|
||||
item: FooterNavigationItemConfig,
|
||||
bootstrap: BootstrapConfig,
|
||||
lang: string
|
||||
): FooterResolvedItem | null {
|
||||
if (item.visible === false) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (item.type === 'staticPage' && item.key) {
|
||||
const staticPage = this.staticPageResolver.resolveByKeyFromBootstrap(bootstrap, item.key, lang);
|
||||
if (!staticPage) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
id: `static-${item.key}`,
|
||||
label: staticPage.title,
|
||||
route: staticPage.route
|
||||
};
|
||||
}
|
||||
|
||||
const label = typeof item.label === 'string'
|
||||
? item.label
|
||||
: this.staticPageResolver.resolveFooterTitle(item.label, lang);
|
||||
|
||||
if (!item.route || !label) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
id: `${item.type}-${label}`,
|
||||
label,
|
||||
route: item.route
|
||||
};
|
||||
}
|
||||
|
||||
private resolveLegacyItem(item: NavigationItemConfig, bootstrap: BootstrapConfig, lang: string): FooterResolvedItem | null {
|
||||
if (item.visible === false) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (item.type === 'staticPage' && item.key) {
|
||||
const staticPage = this.staticPageResolver.resolveByKeyFromBootstrap(bootstrap, item.key, lang);
|
||||
if (!staticPage) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
id: item.id,
|
||||
label: staticPage.title,
|
||||
route: staticPage.route
|
||||
};
|
||||
}
|
||||
|
||||
if (!item.route) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
id: item.id,
|
||||
label: item.labelKey ?? item.id,
|
||||
route: item.route
|
||||
};
|
||||
}
|
||||
|
||||
private isGroupedFooter(footer: NavigationConfig['footer']): footer is FooterNavigationGroupConfig[] {
|
||||
return Array.isArray(footer) && footer.length > 0 && 'items' in footer[0];
|
||||
}
|
||||
|
||||
private resolvePaymentIcons(footerConfig: FooterConfig | undefined): FooterPaymentIcon[] {
|
||||
const fallback: FooterPaymentIcon[] = [
|
||||
{ src: '/assets/images/mir-logo.svg', alt: 'MIR', width: 40, height: 28 },
|
||||
{ src: '/assets/images/visa-logo.svg', alt: 'Visa', width: 40, height: 28 },
|
||||
{ src: '/assets/images/mastercard-logo.svg', alt: 'Mastercard', width: 40, height: 28 }
|
||||
];
|
||||
|
||||
if (!footerConfig?.paymentIcons?.length) {
|
||||
return fallback;
|
||||
}
|
||||
|
||||
return footerConfig.paymentIcons
|
||||
.filter(icon => !!icon?.src)
|
||||
.map(icon => ({
|
||||
src: icon.src,
|
||||
alt: icon.alt || 'Payment icon',
|
||||
width: icon.width ?? 40,
|
||||
height: icon.height ?? 28
|
||||
}));
|
||||
}
|
||||
|
||||
private resolveCopyrightText(bootstrap: BootstrapConfig): string {
|
||||
const configured = bootstrap.footer?.copyrightText;
|
||||
const lang = this.languageService.currentLanguage();
|
||||
|
||||
if (typeof configured === 'string' && configured.trim()) {
|
||||
return configured;
|
||||
}
|
||||
|
||||
if (configured && typeof configured === 'object') {
|
||||
return this.staticPageResolver.resolveFooterTitle(configured, lang);
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user