// The MIT License (MIT) // // Copyright (c) 2021-2025 Camptocamp SA // // Permission is hereby granted, free of charge, to any person obtaining a copy of // this software and associated documentation files (the "Software"), to deal in // the Software without restriction, including without limitation the rights to // use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of // the Software, and to permit persons to whom the Software is furnished to do so, // subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS // FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR // COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER // IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. import i18next from 'i18next'; import {LitElement, css, CSSResult} from 'lit'; import configuration, {Configuration} from 'gmfapi/store/config'; import {Subscription} from 'rxjs'; import cssReset from 'gmfapi/css/reset.css'; import cssBootstrap from 'gmfapi/css/bootstrap-custom.css'; import cssFontawesome from 'gmfapi/css/fontawesome.css'; /** * This is a base element. * * That includes: * - i18next initialization. * - Bootstrap (custom build + color in vars), Font Awesome, reset and some custom style. * - Configuration directly available by overriding the `initConfig` method. * - RXJS subscription added to subscriptions are unsubscribe on element removal. * Example: * ```js * import {html, css} from 'lit'; * import {customElement} from 'lit/decorators.js'; * * // @ts-ignore * @customElement('my-element') * export default class MyElement extent (window as any).gmfapi.elements.BaseElement { * static styles = [ * ...(window as any).gmfapi.elements.BaseElement.styles, * css`...` * ]; * * initConfig(configuration: Any): void { * ... * }; * * render(): TemplateResult { * return html`${this.getTitle(i18next.t('Title'))} * your template` * } * } * ``` */ export default class GmfBaseElement extends LitElement { /** * @private */ i18nLanguageChangedCallback_: () => void; protected subscriptions: Subscription[] = []; // bootstrap/font-awesome static resetStyle: CSSResult = cssReset; static bootstrapStyle: CSSResult = cssBootstrap; static fontawesomeStyle: CSSResult = cssFontawesome; // Make some Bootstrap values configurable static bootstrapVarStyle: CSSResult = css` body a, body .btn-link { color: var(--link-color); } body a:hover, body .btn-link:hover { color: var(--link-hover-color); } body .btn:focus, body .form-control:focus, body .btn.focus, body .form-control.focus { box-shadow: 0 0 0 0.2rem var(--input-btn-focus-color); border-color: var(--brand-primary); } body .modal-header { border-color: var(--link-color); } body .table tr td, body .table tr th { border-color: var(--table-border-color); } body .nav-pills .nav-link.active, body .nav-pills .show > .nav-link { background-color: var(--brand-primary); } `; // Common styles for components static commonStyle: CSSResult = css` .form-control, .btn.prime { border-radius: 0; } .btn.prime { background-color: var(--brand-primary); border-color: var(--input-border-focus); color: white; } .btn.prime.active { box-shadow: inset 0 0.37rem 0.75rem var(--light-box-shadow-color); } .btn.prime:hover, .btn.prime.active { background-color: var(--input-border-focus); border-color: var(--input-border-focus-darken); } a, .btn-link { color: var(--link-color); } a:hover, .btn-link:hover { color: var(--link-hover-color); } `; static styles: CSSResult[] = [ GmfBaseElement.resetStyle, GmfBaseElement.bootstrapStyle, GmfBaseElement.bootstrapVarStyle, GmfBaseElement.fontawesomeStyle, GmfBaseElement.commonStyle, ]; connectedCallback(): void { // i18n this.i18nLanguageChangedCallback_ = () => this.requestUpdate(); i18next.on('languageChanged', this.i18nLanguageChangedCallback_); super.connectedCallback(); // config this.subscriptions.push( configuration.getConfig().subscribe({ next: (configuration: Configuration) => { if (configuration) { this.initConfig(configuration); } }, }), ); } disconnectedCallback(): void { // i18n i18next.off('languageChanged', this.i18nLanguageChangedCallback_); super.disconnectedCallback(); // config this.subscriptions.forEach((sub) => sub.unsubscribe()); } /** * Init the config and allows to override it in the inherited class. * @param {Configuration} configuration The configuration object. * @abstract * @protected */ // eslint-disable-next-line @typescript-eslint/no-unused-vars initConfig(configuration: Configuration): void {} }