@gip-recia/extended-uportal-header
Version:
a component to deport the uPortal menu
752 lines (711 loc) • 23.9 kB
text/typescript
/** runtime */
import 'regenerator-runtime';
/** Lit */
import {
html,
LitElement,
css,
unsafeCSS,
TemplateResult,
PropertyValueMap,
nothing,
} from 'lit';
import { unsafeHTML } from 'lit-html/directives/unsafe-html.js';
// eslint-disable-next-line @typescript-eslint/no-unused-vars
import { customElement, property, state } from 'lit/decorators.js';
import { msg, str, updateWhenLocaleChanges } from '@lit/localize';
import { createRef, Ref, ref } from 'lit/directives/ref.js';
/** Helpers */
// eslint-disable-next-line @typescript-eslint/no-unused-vars
import sizeHelper from '@helpers/sizeHelper';
import { setLocale } from '@helpers/localisation';
import langHelper from '@helpers/langHelper';
/** Services */
import openIdConnect, {
Response as OIDCResponse,
} from '@uportal/open-id-connect';
import userInfoService, { userInfo } from '@services/userInfoService';
import orgInfoService, { orgInfo } from '@services/orgInfoService';
import templateService, { template } from '@services/templateService';
import sessionService from '@services/sessionService';
/** Libraries */
import debounce from 'lodash/debounce';
import throttle from 'lodash/throttle';
/** Components */
import '@gip-recia/eyebrow-user-info-lit';
import '@gip-recia/esco-content-menu-lit';
/** Styles */
import scss from '@styles/extended-uportal-header.scss';
/** Images */
import defaultOrgIcon from '@images/default-org.icon.png';
import defaultOrgImage from '@images/default-org.png';
import defaultAvatar from '@images/default-avatar.svg';
/** Icons */
import { icon } from '@fortawesome/fontawesome-svg-core';
import { faRightToBracket } from '@fortawesome/free-solid-svg-icons/faRightToBracket';
import portletService from '@services/portletService';
interface properties {
messages: unknown;
domain: string;
portailPath: string;
serviceName: string;
favoritesPortletCardSize: string;
gridPortletCardSize: string;
defaultOrgLogoUrl: string;
defaultOrgLogoPath: string;
forceOrgLogoUrl: string;
forceOrgLogoPath: string;
defaultOrgIconUrl: string;
defaultOrgIconPath: string;
defaultAvatarUrl: string;
defaultAvatarPath: string;
contextApiUrl: string;
favoriteApiUrl: string;
layoutApiUrl: string;
portletApiUrl: string;
organizationApiUrl: string;
userInfoApiUrl: string;
sessionApiUrl: string;
templateApiUrl: string;
templateApiPath: string;
template: template | null;
signOutUrl: string;
userInfoPortletUrl: string;
switchOrgPortletUrl: string;
switchOrgEvent: boolean;
orgAttributeName: string;
orgLogoUrlAttributeName: string;
userAllOrgsIdAttributeName: string;
hideActionMode: 'auto' | 'always' | 'never';
showFavoritesInSlider: boolean;
iconType:
| 'hamburger'
| 'four-square'
| 'four-empty-square'
| 'nine-square'
| 'nine-empty-square'
| 'four-circle'
| 'four-empty-circle'
| 'nine-circle'
| 'nine-empty-circle';
returnHomeTarget: string;
returnHomeTitle: string | null;
height: string;
sessionRenewDisable: boolean;
portletInfoApiUrl: string;
fname: string;
debug: boolean;
}
type overridableProperties = Omit<
properties,
'templateApiUrl' | 'templateApiPath' | 'template'
>;
('extended-uportal-header')
// eslint-disable-next-line @typescript-eslint/no-unused-vars
export class ExtendedUportalHeader extends LitElement {
({ type: Object })
messages = [];
({ type: String })
domain = '';
({ type: String, attribute: 'portail-path' })
portailPath = '';
({ type: String, attribute: 'service-name' })
serviceName = '';
({
type: String,
hasChanged(newVal: string) {
return sizeHelper.validate(newVal, true, true);
},
attribute: 'favorites-portlet-card-size',
})
favoritesPortletCardSize = 'small';
({
type: String,
hasChanged(newVal: string) {
return sizeHelper.validate(newVal, true, true);
},
attribute: 'grid-portlet-card-size',
})
gridPortletCardSize = 'medium';
({ type: String, attribute: 'default-org-logo-url' })
defaultOrgLogoUrl = '';
({ type: String, attribute: 'default-org-logo-path' })
defaultOrgLogoPath = '';
({ type: String, attribute: 'force-org-logo-url' })
forceOrgLogoUrl = '';
({ type: String, attribute: 'force-org-logo-path' })
forceOrgLogoPath = '';
({ type: String, attribute: 'default-org-icon-url' })
defaultOrgIconUrl = '';
({ type: String, attribute: 'default-org-icon-path' })
defaultOrgIconPath = '';
({ type: String, attribute: 'default-avatar-url' })
defaultAvatarUrl = '';
({ type: String, attribute: 'default-avatar-path' })
defaultAvatarPath = '';
({ type: String, attribute: 'context-api-url' })
contextApiUrl = process.env.APP_PORTAL_CONTEXT ?? '';
({ type: String, attribute: 'favorite-api-url' })
favoriteApiUrl =
(process.env.APP_PORTAL_CONTEXT ?? '') +
(process.env.APP_FAVORITES_PORTLETS_URI ?? '');
({ type: String, attribute: 'layout-api-url' })
layoutApiUrl =
(process.env.APP_PORTAL_CONTEXT ?? '') +
(process.env.APP_FAVORITES_URI ?? '');
({ type: String, attribute: 'portlet-api-url' })
portletApiUrl =
(process.env.APP_PORTAL_CONTEXT ?? '') +
(process.env.APP_BROWSABLE_PORTLETS_URI ?? '');
({ type: String, attribute: 'organization-api-url' })
organizationApiUrl =
(process.env.APP_PORTAL_CONTEXT ?? '') +
(process.env.APP_ORG_API_URI ?? '');
({ type: String, attribute: 'user-info-api-url' })
userInfoApiUrl =
(process.env.APP_PORTAL_CONTEXT ?? '') +
(process.env.APP_USER_INFO_URI ?? '');
({ type: String, attribute: 'session-api-url' })
sessionApiUrl =
(process.env.APP_PORTAL_CONTEXT ?? '') +
(process.env.APP_SESSION_URI ?? '');
({ type: String, attribute: 'template-api-url' })
templateApiUrl = '';
({ type: String, attribute: 'template-api-path' })
templateApiPath = process.env.APP_TPL_API_PATH ?? '';
({ type: Object })
template: template | null = null;
({ type: String, attribute: 'sign-out-url' })
signOutUrl = process.env.APP_LOGOUT_URL ?? '';
({ type: String, attribute: 'sign-in-url' })
signInUrl = '';
({ type: String, attribute: 'user-info-portlet-url' })
userInfoPortletUrl = '';
({ type: String, attribute: 'switch-org-portlet-url' })
switchOrgPortletUrl = '';
({ type: Boolean, attribute: 'switch-org-event' })
switchOrgEvent = false;
({ type: String, attribute: 'user-org-id-attribute-name' })
orgAttributeName = 'ESCOSIRENCourant[0]';
({ type: String, attribute: 'org-logo-url-attribute-name' })
orgLogoUrlAttributeName = 'otherAttributes.ESCOStructureLogo[0]';
({ type: String, attribute: 'user-all-orgs-id-attribute-name' })
userAllOrgsIdAttributeName = 'ESCOSIREN';
({
type: String,
hasChanged(newVal: string) {
return ['auto', 'always', 'never'].includes(newVal);
},
attribute: 'hide-action-mode',
})
hideActionMode = 'auto';
({ type: Boolean, attribute: 'show-favorites-in-slider' })
showFavoritesInSlider = false;
({
type: String,
hasChanged(newVal: string) {
return [
'hamburger',
'four-square',
'four-empty-square',
'nine-square',
'nine-empty-square',
'four-circle',
'four-empty-circle',
'nine-circle',
'nine-empty-circle',
].includes(newVal);
},
attribute: 'icon-type',
})
iconType = 'hamburger';
({
type: String,
attribute: 'return-home-target',
hasChanged(newVal: string) {
return ['_self', '_blank'].includes(newVal);
},
})
returnHomeTarget = '_self';
({ type: String, attribute: 'return-home-title' })
returnHomeTitle: string | null = null;
({ type: String })
height = 'auto';
({ type: Boolean, attribute: 'disable-session-renew' })
sessionRenewDisable = false;
({ type: Object, attribute: 'dont-override' })
dontOverride: Array<keyof overridableProperties> | null = null;
({ type: String, attribute: 'portlet-info-api-url' })
portletInfoApiUrl = '';
({ type: String })
fname = '';
({ type: Boolean })
debug = false;
()
private _loaded: number | boolean = false;
()
private _userApiResult: OIDCResponse | null = null;
private _sessionTimeout = 300000;
private _tokenExpireTime = 0;
private _loadingToken = false;
private _userInfos: userInfo | null = null;
private _orgInfos: orgInfo | null = null;
private _loadingData = false;
private _loadingTemplate = false;
hamburgerRef: Ref<HTMLElement> = createRef();
constructor() {
super();
if (this.domain === '') {
this.domain = window.document.domain;
}
const lhOpts = {
languageCodeOnly: true,
availableLanguages: ['fr', 'en'],
defaultLanguage: 'en',
};
const lang = langHelper.getPageLang(lhOpts);
setLocale(lang);
langHelper.setLocale(lang);
updateWhenLocaleChanges(this);
}
connectedCallback(): void {
super.connectedCallback();
['mousemove', 'click', 'keypress'].every((event) =>
document.addEventListener(event, this._handleUserAction.bind(this))
);
window.addEventListener('eyebrow-user-info', (e: Event) => {
if ((e as CustomEvent).detail.type === 'change-etab') {
this.hamburgerRef.value?.dispatchEvent(new CustomEvent('switch-org'));
}
});
}
disconnectedCallback(): void {
super.disconnectedCallback();
['mousemove', 'click', 'keypress'].every((event) =>
document.removeEventListener(event, this._handleUserAction.bind(this))
);
}
protected shouldUpdate(
// eslint-disable-next-line @typescript-eslint/no-explicit-any
_changedProperties: PropertyValueMap<any> | Map<PropertyKey, unknown>
): boolean {
if (_changedProperties.has('messages')) {
langHelper.setReference(this.messages);
}
if (
!this.template &&
!this._loadingTemplate &&
!this._loaded &&
!this._loadingData
) {
this._firstLoad();
}
if (
_changedProperties.has('userInfoApiUrl') ||
_changedProperties.has('layoutApiUrl') ||
_changedProperties.has('organizationApiUrl')
) {
this._debounceLoad();
}
if (
_changedProperties.has('template') ||
_changedProperties.has('templateApiUrl') ||
_changedProperties.has('templateApiPath')
) {
this._debounceGetTemplate();
}
if (!this._loaded) {
return false;
}
return true;
}
private async _firstLoad() {
await this._getTemplate();
this._loadPortletInformations();
this._debounceLoad();
}
private _debounceLoad = debounce(this._load.bind(this), 500);
private async _load() {
this._loadingData = true;
await this._renewToken();
const previusStatus = this._userInfos;
this._userInfos = await userInfoService.get(
this._makeUrl(this.userInfoApiUrl),
this._makeUrl(this.layoutApiUrl),
this.orgAttributeName,
this.userAllOrgsIdAttributeName,
this._userApiResult,
this.debug
);
if (this._userInfos?.orgId) {
this._orgInfos = await orgInfoService.get(
this._makeUrl(this.userInfoApiUrl),
this._makeUrl(this.organizationApiUrl),
this._userInfos.orgId,
this._userApiResult,
this.debug
);
}
this._loadingData = false;
if (previusStatus != this._userInfos) {
this._loaded = Date.now();
return;
}
this._loaded = true;
}
private _debounceRenewToken = debounce(this._renewToken.bind(this), 500);
private async _renewToken() {
const currentTime = Math.ceil(Date.now() / 1000);
if (
!this.debug &&
!this._loadingToken &&
currentTime >= this._tokenExpireTime
) {
this._loadingToken = true;
this._userApiResult = await openIdConnect({
userInfoApiUrl: this._makeUrl(this.userInfoApiUrl),
});
this._tokenExpireTime = this._userApiResult.decoded.exp - 5 ?? 0;
this._loadingToken = false;
}
}
private _throttleRenewSession = throttle(
this._renewSession.bind(this),
this._sessionTimeout
);
private async _renewSession() {
if (this.sessionRenewDisable) {
this._debounceLoad();
return;
}
const session = await sessionService.get(this._makeUrl(this.sessionApiUrl));
if (session !== null) {
if (this._loaded && this._isConnected() && !session.isConnected)
this._debounceLoad();
}
}
private _debounceGetTemplate = debounce(this._getTemplate.bind(this), 500);
private async _getTemplate() {
if (this.template !== null) return;
this._loadingTemplate = true;
this.template = await templateService.get(this._tplApiUrl(), this.domain);
await this._overrideProperties();
this._loadingTemplate = false;
}
private _defaultProperties: properties = {
messages: [],
domain: '',
portailPath: '',
serviceName: '',
favoritesPortletCardSize: 'small',
gridPortletCardSize: 'medium',
defaultOrgLogoUrl: '',
defaultOrgLogoPath: '',
forceOrgLogoUrl: '',
forceOrgLogoPath: '',
defaultOrgIconUrl: '',
defaultOrgIconPath: '',
defaultAvatarUrl: '',
defaultAvatarPath: '',
contextApiUrl: '',
favoriteApiUrl: '',
layoutApiUrl: '',
portletApiUrl: '',
organizationApiUrl: '',
userInfoApiUrl: '',
sessionApiUrl: '',
templateApiUrl: '',
templateApiPath: '',
template: null,
signOutUrl: '',
userInfoPortletUrl: '',
switchOrgPortletUrl: '',
switchOrgEvent: false,
orgAttributeName: 'ESCOSIRENCourant[0]',
orgLogoUrlAttributeName: 'otherAttributes.ESCOStructureLogo[0]',
userAllOrgsIdAttributeName: 'ESCOSIREN',
hideActionMode: 'auto',
showFavoritesInSlider: false,
iconType: 'hamburger',
returnHomeTarget: '_self',
returnHomeTitle: null,
height: 'auto',
sessionRenewDisable: false,
portletInfoApiUrl: '',
fname: '',
debug: false,
};
private async _overrideProperties() {
let config = this.template?.config;
if (config) {
config = Object.fromEntries(
Object.entries(config).map(([key, value]) => [
key.replace(/-./g, (m) => m.toUpperCase()[1]),
value,
])
);
for (const [key, value] of Object.entries(config)) {
const currentValue = this[key as keyof overridableProperties];
const defaultValue =
this._defaultProperties[key as keyof overridableProperties];
const keepCurrent = !!this.dontOverride?.includes(
key as keyof overridableProperties
);
let override = false;
switch (typeof currentValue) {
case 'object':
if (
Array.isArray(currentValue) &&
Array.isArray(defaultValue) &&
currentValue.every((val) => defaultValue.includes(val))
)
override = true;
else if (defaultValue === null) {
if (currentValue === defaultValue || currentValue.length === 0)
override = true;
}
break;
default:
if (currentValue === defaultValue || currentValue === '')
override = true;
break;
}
this.debug &&
console.log({
key,
current: { value: currentValue, type: typeof currentValue },
default: { value: defaultValue, type: typeof defaultValue },
received: { value, type: typeof value },
override,
keepCurrent,
});
if (override && !keepCurrent) {
this[key as keyof overridableProperties] = value as never;
if (key === 'messages') langHelper.setReference(this.messages);
}
}
}
}
private async _loadPortletInformations() {
if (
this.portletInfoApiUrl === '' ||
this.fname === '' ||
this.serviceName !== ''
)
return;
const data = await portletService.get(
this._makeUrl(this.portletInfoApiUrl),
this.fname
);
if (data) this.serviceName = data.title;
}
private _handleUserAction() {
if (this._loaded) {
this._debounceRenewToken();
if (!this.sessionRenewDisable) this._throttleRenewSession();
}
}
private _makeUrl(path: string, domain = ''): string {
if (path.startsWith('http')) return path;
const protocol = this.debug ? 'http' : 'https';
return `${protocol}://${domain == '' ? this.domain : domain}${path}`;
}
private _defaultOrgLogo() {
return this.defaultOrgLogoUrl != ''
? this.defaultOrgLogoUrl
: this.defaultOrgLogoPath != ''
? this._makeUrl(this.defaultOrgLogoPath)
: defaultOrgImage;
}
private _forceOrgLogo() {
return this.forceOrgLogoUrl != ''
? this.forceOrgLogoUrl
: this.forceOrgLogoPath != ''
? this._makeUrl(this.forceOrgLogoPath)
: false;
}
private _picture() {
return this._userInfos?.picture
? this._makeUrl(this._userInfos.picture)
: this.defaultAvatarUrl != ''
? this.defaultAvatarUrl
: this.defaultAvatarPath != ''
? this._makeUrl(this.defaultAvatarPath)
: defaultAvatar;
}
private _orgIconUrl() {
return this.template?.iconPath
? this._makeUrl(this.template?.iconPath)
: this.defaultOrgIconUrl != ''
? this.defaultOrgIconUrl
: this.defaultOrgIconPath != ''
? this._makeUrl(this.defaultOrgIconPath)
: defaultOrgIcon;
}
private _tplApiUrl() {
return this.templateApiUrl != ''
? this.templateApiUrl
: this.templateApiPath != ''
? this._makeUrl(this.templateApiPath)
: '';
}
private _isConnected(): boolean {
return this._userInfos !== null && this._orgInfos !== null;
}
render(): TemplateResult {
this._preRender();
return this._isConnected() || this.signInUrl !== ''
? html`
<div id="extended-uportal-header-container">
${this._renderMenu()}
<div id="extended-uportal-header-org">
<slot name="brand">
<a
class="org-logo"
href="${this._makeUrl(this.portailPath)}"
target="${this.returnHomeTarget}"
title="${this.returnHomeTitle ??
this.template?.name ??
langHelper.localTranslation(
'message.header.gotoportal',
msg(str`go to the portal`)
)}"
>
<img
src="${this._orgIconUrl()}"
height="28px"
alt="${this.template?.name ??
langHelper.localTranslation(
'message.header.icon',
msg(str`portal icon`)
)}"
/>
</a>
<p class="org-display-name" tabindex="0">
${this._orgInfos?.displayName.toUpperCase()}
${this.serviceName != ''
? html`<span class="service-name">
> ${this.serviceName}</span
>`
: ''}
</p>
</slot>
</div>
${this._renderUser()}
</div>
`
: html` <slot name="not-loaded"></slot> `;
}
private _preRender() {
if (this.template?.color) {
this.style.setProperty(
'--ext-header-tpl-primary-color',
this.template?.color
);
}
if (this.template?.iconOpacity) {
this.style.setProperty(
'--ext-header-tpl-icon-opacity',
this.template.iconOpacity.toString()
);
}
if (this.height !== 'auto') {
this.style.setProperty('--ext-header-tpl-height', this.height);
}
}
private _renderMenu() {
const userApiResult = this._userApiResult
? JSON.stringify(this._userApiResult)
: nothing;
return this._userInfos && this._orgInfos
? html` <div id="extended-uportal-header-menu">
<slot name="menu">
<esco-hamburger-menu
${ref(this.hamburgerRef)}
.messages=${this.messages}
portal-base-url="${this.domain}"
favorites-portlet-card-size="${this.favoritesPortletCardSize}"
grid-portlet-card-size="${this.gridPortletCardSize}"
default-org-logo="${this._defaultOrgLogo()}"
?force-org-logo="${this._forceOrgLogo()}"
context-api-url="${this.contextApiUrl}"
favorite-api-url="${this.favoriteApiUrl}"
layout-api-url="${this.layoutApiUrl}"
portlet-api-url="${this.portletApiUrl}"
organization-api-url="${this.organizationApiUrl}"
user-info-api-url="${this.userInfoApiUrl}"
user-info=${userApiResult}
sign-out-url="${this.signOutUrl}"
user-info-portlet-url="${this.userInfoPortletUrl}"
switch-org-portlet-url="${this.switchOrgPortletUrl}"
?switch-org-event=${this.switchOrgEvent}
user-org-id-attribute-name="${this.orgAttributeName}"
org-logo-url-attribute-name="${this.orgLogoUrlAttributeName}"
user-all-orgs-id-attribute-name="${this
.userAllOrgsIdAttributeName}"
hide-action-mode="${this.hideActionMode}"
?show-favorites-in-slider=${this.showFavoritesInSlider}
icon-type=${this.iconType}
?debug=${this.debug}
>
</esco-hamburger-menu>
</slot>
</div>`
: html``;
}
private _renderUser() {
const signInIcon = `${icon(faRightToBracket).html}`;
return this._userInfos && this._orgInfos
? html`<div id="extended-uportal-header-user">
<slot name="user">
<eyebrow-user-info
.messages=${this.messages}
portal-base-url="${this.domain}"
:menu-is-dark="false"
display-name="${this._userInfos.displayName}"
picture="${this._picture()}"
avatar-size="28px"
config='{
"notification": false,
"settings": {
"link": "${this.userInfoPortletUrl}"
},
"info-etab": false,
"change-etab": ${this._userInfos.hasOtherOrgs
? !this.switchOrgPortletUrl.includes('/p/')
? '{ "link": null }'
: `{ "link": "${this._makeUrl(this.switchOrgPortletUrl)}" }`
: 'false'},
"starter": false,
"logout": {
"link": "${this.signOutUrl}"
}
}'
force-new-ui
></eyebrow-user-info>
</slot>
</div>`
: html`<div id="extended-uportal-header-login">
<slot name="login">
<a
href="${this.signInUrl}"
target="_self"
class="login-button"
title="${langHelper.localTranslation(
'message.header.login',
msg(str`login`)
)}"
>
<span class="sign-in-icon">${unsafeHTML(signInIcon)}</span>
${langHelper.localTranslation(
'message.header.login',
msg(str`login`)
)}
</a>
</slot>
</div>`;
}
static styles = css`
${unsafeCSS(scss)}
`;
}