diff --git a/geoportal/geomapfish_geoportal/static-ngeo/js/apps/Controllerdesktop_alt.js b/geoportal/geomapfish_geoportal/static-ngeo/js/apps/Controllerdesktop_alt.js index 4ce3f012c..e1842d967 100644 --- a/geoportal/geomapfish_geoportal/static-ngeo/js/apps/Controllerdesktop_alt.js +++ b/geoportal/geomapfish_geoportal/static-ngeo/js/apps/Controllerdesktop_alt.js @@ -42,6 +42,8 @@ import ngeoStreetviewModule from 'ngeo/streetview/module'; import ngeoRoutingModule from 'ngeo/routing/module'; import ngeoStatemanagerWfsPermalink from 'ngeo/statemanager/WfsPermalink'; +import {customCssFn} from '../customizationLoader'; + /** * @private */ @@ -65,6 +67,9 @@ class Controller extends AbstractDesktopController { const drawLidarprofilePanelActive = new ngeoMiscToolActivate(this, 'drawLidarprofilePanelActive'); this.ngeoToolActivateMgr.registerTool('mapTools', drawLidarprofilePanelActive, false); + + // CUSTOM Override style in the Shadow DOM scope. + customCssFn(); } /** diff --git a/geoportal/geomapfish_geoportal/static-ngeo/js/apps/desktop_alt.html.ejs b/geoportal/geomapfish_geoportal/static-ngeo/js/apps/desktop_alt.html.ejs index b50f2fc12..53633e0f4 100644 --- a/geoportal/geomapfish_geoportal/static-ngeo/js/apps/desktop_alt.html.ejs +++ b/geoportal/geomapfish_geoportal/static-ngeo/js/apps/desktop_alt.html.ejs @@ -22,6 +22,7 @@ +
@@ -57,6 +58,10 @@ + +
A custom auth panel under
+ + diff --git a/geoportal/geomapfish_geoportal/static-ngeo/js/customization/angularDrawPanel.ts b/geoportal/geomapfish_geoportal/static-ngeo/js/customization/angularDrawPanel.ts new file mode 100644 index 000000000..90d8a7bb5 --- /dev/null +++ b/geoportal/geomapfish_geoportal/static-ngeo/js/customization/angularDrawPanel.ts @@ -0,0 +1,12 @@ +import {Controller} from 'gmf/drawing/drawFeatureComponent'; + +/** + * Initialize interactions by setting them inactive and decorating them + */ +Controller.prototype.initializeInteractions_ = function () { + console.log('Quick test to test AngularJs overriding -> It works.'); + this.interactions_.forEach((interaction) => { + interaction.setActive(false); + ngeoMiscDecorateInteraction(interaction); + }); +}; diff --git a/geoportal/geomapfish_geoportal/static-ngeo/js/customization/authFormElement.ts b/geoportal/geomapfish_geoportal/static-ngeo/js/customization/authFormElement.ts new file mode 100644 index 000000000..07966609b --- /dev/null +++ b/geoportal/geomapfish_geoportal/static-ngeo/js/customization/authFormElement.ts @@ -0,0 +1,274 @@ +import {unsafeCSS, html, TemplateResult} from 'lit'; +import i18next from 'i18next'; +import authenticationService from 'ngeo/auth/service'; +import ngeoAuthForm from 'ngeo/auth/FormElement'; +import user from 'gmfapi/store/user'; + + + +/** + * CUSTOM + * Get custom special role from the user's model. + */ +ngeoAuthForm.prototype.getSpecialRole_ = function(): string { + return user.getSpecialRole(); +} + +/** + * Calls the authentication service login method. + * CUSTOM: manage special role. + * @param evt Event from the form submit action. + */ +ngeoAuthForm.prototype.login = function(evt: Event): void { + evt.preventDefault(); + + this.manualLoginLogout_(); + + this.isLoading = true; + const errors = []; + const form = evt.target as HTMLFormElement; + const loginVal = (form.login as HTMLInputElement).value; + const pwdVal = (form.password as HTMLInputElement).value; + + if (loginVal === '') { + errors.push(i18next.t('The username is required.')); + } + if (pwdVal === '') { + errors.push(i18next.t('The password is required.')); + } + if (errors.length) { + this.isLoading = false; + this.setError_(errors); + } else { + // CUSTOM + authenticationService.setSpecialRole(form.specialRole.value); + // CUSTOM END + authenticationService + .login(loginVal, pwdVal) + .then(() => { + this.resetError_(); + }) + .catch(() => { + this.setError_([i18next.t('Incorrect credentials or disabled account.')]); + }) + .finally(() => { + this.isLoading = false; + form.reset(); + }); + } +} + +/** + * Render the HTML + * CUSTOM: Manage special role (new input, new feedback once logged in). + */ +ngeoAuthForm.prototype.render = function(): TemplateResult { + // CUSTOM + this.customCSS_ = ` + ${this.customCSS_} + strong { + text-shadow: 1px 1px 2px black; + } + ` + // CUSTOM END + return html` + + + ${this.gmfUser.is_intranet + ? html` +
+ ${i18next.t('You are recognized as an intranet user.')} +
+ ` + : ''} + ${this.gmfUser.username !== null + ? html` +
+
+ ${i18next.t('Logged in as')} + ${this.gmfUser.username}. +
+ +
+ ${i18next.t('With special role')} + ${this.getSpecialRole_()}. +
+ + ${!this.changingPassword + ? html` +
this.logout(evt)}> +
+ +
+
+ (this.changingPassword = true)} + /> +
+
+ ` + : ''} +
+ ` + : ''} + ${this.loginInfoMessage + ? html` +
+ ${this.loginInfoMessage} +
+ ` + : ''} + ${this.disconnectedShown + ? html` +
+ ${i18next.t('You are not logged in any more. The Interface has been reloaded.')} +
+ ` + : ''} + ${this.gmfUser.username === null && !this.changingPassword + ? html` +
+
this.login(evt)}> +
+ +
+ +
+ +
+ +
+ +
+ ${this.twoFactorAuth + ? html` +
+ ${i18next.t('The following field should be kept empty on first login:')} + +
+ ` + : ''} +
+ +
+ ${this.isLoading + ? html` + + ` + : ''} + +
+ ${this.resetPasswordShown + ? html`
+ ${i18next.t('A new password has just been sent to you by e-mail.')} +
` + : ''} +
+ ` + : ''} + ${this.changingPassword + ? html` +
+ ${this.userMustChangeItsPassword + ? html`
${i18next.t('You must change your password')}
` + : ''} +
this.changePassword(evt)}> +
+ +
+
+ +
+
+ +
+ ${this.gmfUser.otp_uri + ? html` +
+ +
+
+ ` + : ''} + ${this.gmfUser.two_factor_totp_secret + ? html` +
+ + ${this.gmfUser.two_factor_totp_secret} +
+ ` + : ''} + ${this.twoFactorAuth + ? html` +
+ +
+ ` + : ''} +
+ +
+
+ this.changePasswordReset()} + /> +
+
+
+ ` + : ''} +
+ `; +} diff --git a/geoportal/geomapfish_geoportal/static-ngeo/js/customization/authService.ts b/geoportal/geomapfish_geoportal/static-ngeo/js/customization/authService.ts new file mode 100644 index 000000000..caf9e09e3 --- /dev/null +++ b/geoportal/geomapfish_geoportal/static-ngeo/js/customization/authService.ts @@ -0,0 +1,37 @@ +import * as Sentry from '@sentry/browser'; +import {AuthenticationService} from 'ngeo/auth/service'; +import user from 'gmfapi/store/user'; + +/** + * CUSTOM + * Set the special role to use it in the setUser_ methode. + */ +AuthenticationService.prototype.setSpecialRole = function(specialRole: string) { + this.specialRole_ = specialRole; +} + +/** + * CUSTOM + * Redefine setUser to add management of a specialRole; + * @param respData Response. + * @param userState state of the user. + * @private + */ +AuthenticationService.prototype.setUser_ = function(respData: User, userState: UserState): void { + Sentry.setUser({ + username: respData.username, + }); + + // CUSTOM part under + if (!respData.username) { + this.specialRole_ = null; + } + if (this.specialRole_ === undefined) { + this.specialRole_ = localStorage.getItem('gmfUserSpecialRole'); + } else { + localStorage.setItem('gmfUserSpecialRole', this.specialRole_); + } + // CUSTOM END + + user.setUser(respData, userState, this.specialRole_); +} diff --git a/geoportal/geomapfish_geoportal/static-ngeo/js/customization/helloWorld.ts b/geoportal/geomapfish_geoportal/static-ngeo/js/customization/helloWorld.ts new file mode 100644 index 000000000..187b74511 --- /dev/null +++ b/geoportal/geomapfish_geoportal/static-ngeo/js/customization/helloWorld.ts @@ -0,0 +1,32 @@ +// File ...static-ngeo/js/custom/helloWorld.ts; +import {html, TemplateResult, CSSResult, css} from 'lit'; +import {customElement, property} from 'lit/decorators.js'; +import GmfBaseElement from 'gmfapi/elements/BaseElement'; +import i18next from 'i18next'; + +@customElement('hello-world') +export default class GmfAuthForm extends GmfBaseElement { + @property({type: String}) name = 'Not set'; + + connectedCallback(): void { + super.connectedCallback(); + } + + static styles: CSSResult[] = [ + ...GmfBaseElement.styles, + css` + .name { + color: green; + } + `, + ]; + + protected render(): TemplateResult { + return html` +
+ ${i18next.t('Hello')}  + ${this.name} +
+ `; + } +} diff --git a/geoportal/geomapfish_geoportal/static-ngeo/js/customization/panelElement.ts b/geoportal/geomapfish_geoportal/static-ngeo/js/customization/panelElement.ts new file mode 100644 index 000000000..e5d859eeb --- /dev/null +++ b/geoportal/geomapfish_geoportal/static-ngeo/js/customization/panelElement.ts @@ -0,0 +1,66 @@ +// The MIT License (MIT) +// +// Copyright (c) 2021 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 {html, TemplateResult, unsafeCSS} from 'lit'; +import {customElement, property} from 'lit/decorators.js'; +import {unsafeSVG} from 'lit/directives/unsafe-svg.js'; +import loadingSvg from 'gmf/icons/spinner.svg'; +import i18next from 'i18next'; +import 'ngeo/auth/FormElement'; + +import GmfAuthPanel from 'ngeo/auth/PanelElement'; + +// CUSTOM element that inherit from GmfAuthPanel. +@customElement('demo-auth-panel') +export default class DemoAuthPanel extends GmfAuthPanel { + // CUSTOM property + @property({type: String}) customMessage: string = 'Not set'; + + protected render(): TemplateResult { + const spinnerTemplate = this.postLoading + ? html` +
+ ${ + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument + unsafeSVG(loadingSvg) + } + ${i18next.t('Loading themes, please wait...')} +
+ ` + : ''; + return html` + + +

With my custom message: ${this.customMessage}

+ + ${this.getTitle(i18next.t('Login'))} + + ${spinnerTemplate} + `; + } +} diff --git a/geoportal/geomapfish_geoportal/static-ngeo/js/customization/user.ts b/geoportal/geomapfish_geoportal/static-ngeo/js/customization/user.ts new file mode 100644 index 000000000..d35eb4e9a --- /dev/null +++ b/geoportal/geomapfish_geoportal/static-ngeo/js/customization/user.ts @@ -0,0 +1,27 @@ +import {UserModel} from 'gmfapi/store/user'; + +/** + * CUSTOM + * Set the current User's properties and state. + * @return the custom role. + */ +UserModel.prototype.getSpecialRole = function(): string { + return this.specialRole_; +}; + +/** + * Set the current User's properties and state. + * @param properties The new user + * @param state The new state + * @param specialRole A custom very special role. + */ +UserModel.prototype.setUser = function(properties: User, state: UserState, specialRole: string): void { + const isValid = this.checkUserProperties_(properties); + if (!isValid || state === null) { + return; + } + this.state_ = state; + this.properties_.next(properties); + // CUSTOM part under + this.specialRole_ = specialRole; +} diff --git a/geoportal/geomapfish_geoportal/static-ngeo/js/customizationLoader.ts b/geoportal/geomapfish_geoportal/static-ngeo/js/customizationLoader.ts new file mode 100644 index 000000000..dad371d96 --- /dev/null +++ b/geoportal/geomapfish_geoportal/static-ngeo/js/customizationLoader.ts @@ -0,0 +1,21 @@ +// Test to override a lit component and a rxjs "store". +import './customization/user.ts'; +import './customization/authService.ts'; +import './customization/authFormElement.ts'; +import './customization/panelElement.ts'; +// Create a small brand new web-component. +import './customization/helloWorld.ts'; +// Small test to override an AngularJS component. +import './customization/angularDrawPanel.ts'; + +/** + * CUSTOM custom function to override style in the Shadow DOM scope. + * Here we target the gmf-auth-form of the demo-auth-panel (a Shadow DOM + * in another shadow DOM). + */ +export const customCssFn = ()=> { + const style = document.createElement( 'style' ) + style.innerHTML = 'strong { color: darkblue; }' + const demoAuthPanel = document.querySelector('demo-auth-panel').shadowRoot; + demoAuthPanel.querySelector('gmf-auth-form').shadowRoot.appendChild(style); +} diff --git a/geoportal/geomapfish_geoportal/static-ngeo/js/geomapfishmodule.js b/geoportal/geomapfish_geoportal/static-ngeo/js/geomapfishmodule.js index 104cde736..bff195041 100644 --- a/geoportal/geomapfish_geoportal/static-ngeo/js/geomapfishmodule.js +++ b/geoportal/geomapfish_geoportal/static-ngeo/js/geomapfishmodule.js @@ -10,9 +10,9 @@ import {decodeQueryString} from 'ngeo/utils.js'; /** * @type {!angular.IModule} */ -const module = angular.module('geomapfish', []); +const myModule = angular.module('geomapfish', []); -module.config([ +myModule.config([ '$compileProvider', function ($compileProvider) { if (!('debug' in decodeQueryString(window.location.search))) { @@ -22,4 +22,4 @@ module.config([ }, ]); -export default module; +export default myModule; diff --git a/geoportal/vars.yaml b/geoportal/vars.yaml index 06d0b6ef1..ef4cae1c2 100644 --- a/geoportal/vars.yaml +++ b/geoportal/vars.yaml @@ -151,6 +151,8 @@ vars: extends: desktop redirect_interface: mobile_alt constants: + gmfCustomCSS: + authentication: 'strong {color: red;}' sentryOptions: <<: *sentryOptions tags: @@ -775,6 +777,7 @@ no_interpreted: - shortener.email_body - welcome_email.email_body - interfaces_config.mobile_alt.constants.gmfMobileMeasurePointOptions.format + - interfaces_config.desktop_alt.constants.gmfCustomCSS.authentication runtime_environment: - name: SMTP_USER