From 379a193f60088ce4da420f3dec3fa2fa11fc7a55 Mon Sep 17 00:00:00 2001 From: Christoph Reiter <reiter.christoph@gmail.com> Date: Tue, 23 Jun 2020 15:58:00 +0200 Subject: [PATCH] Split vpu-auth into vpu-auth and vpu-auth-button vpu-auth listens to login/logout events which the button emits and the button shows data from the auth-update event. --- packages/auth/src/auth-button.js | 327 +++++++++++++++++++++++++++ packages/auth/src/auth.js | 349 ++++------------------------- packages/auth/src/index.js | 3 +- packages/auth/src/util.js | 7 + packages/auth/src/vpu-auth-demo.js | 6 +- packages/auth/src/vpu-auth.js | 6 +- packages/auth/test/unit.js | 18 ++ 7 files changed, 401 insertions(+), 315 deletions(-) create mode 100644 packages/auth/src/auth-button.js create mode 100644 packages/auth/src/util.js diff --git a/packages/auth/src/auth-button.js b/packages/auth/src/auth-button.js new file mode 100644 index 00000000..3b2608cb --- /dev/null +++ b/packages/auth/src/auth-button.js @@ -0,0 +1,327 @@ +import {i18n} from './i18n.js'; +import {html, css} from 'lit-element'; +import {unsafeHTML} from 'lit-html/directives/unsafe-html.js'; +import {ScopedElementsMixin} from '@open-wc/scoped-elements'; +import * as commonStyles from 'vpu-common/styles'; +import {LitElement} from "lit-element"; +import {Icon, EventBus} from 'vpu-common'; +import {LoginStatus} from './util.js'; + +export class AuthButton extends ScopedElementsMixin(LitElement) { + + constructor() { + super(); + this.lang = 'de'; + this.showProfile = false; + this.showImage = false; + this._loginData = {}; + + this.closeDropdown = this.closeDropdown.bind(this); + this.onWindowResize = this.onWindowResize.bind(this); + } + + static get scopedElements() { + return { + 'vpu-icon': Icon, + }; + } + + static get properties() { + return { + lang: { type: String }, + showProfile: { type: Boolean, attribute: 'show-profile' }, + showImage: { type: Boolean, attribute: 'show-image' }, + _loginData: { type: Object, attribute: false }, + }; + } + + onWindowResize() { + this.updateDropdownWidth(); + } + + connectedCallback() { + super.connectedCallback(); + + this._bus = new EventBus(); + this._bus.subscribe('auth-update', (data) => { + this._loginData = data; + }); + + window.addEventListener('resize', this.onWindowResize); + document.addEventListener('click', this.closeDropdown); + } + + disconnectedCallback() { + window.removeEventListener('resize', this.onWindowResize); + this._bus.close(); + document.removeEventListener('click', this.closeDropdown); + super.disconnectedCallback(); + } + + /** + * Set the dropdown width to almost the width of the web component + * We need to set the width manually because a percent width is in relation to the viewport + */ + updateDropdownWidth() { + const dropdown = this.shadowRoot.querySelector("div.dropdown-menu"); + + if (!dropdown) { + return; + } + + let viewportOffset = this.getBoundingClientRect(); + let spaceToRIght = window.innerWidth - viewportOffset.left; + dropdown.setAttribute("style", `width: ${spaceToRIght - 20}px`); + } + + onLoginClicked(e) { + this._bus.publish('auth-login'); + e.preventDefault(); + } + + onLogoutClicked(e) { + this._bus.publish('auth-logout'); + } + + update(changedProperties) { + changedProperties.forEach((oldValue, propName) => { + if (propName === "lang") { + i18n.changeLanguage(this.lang); + } + }); + + super.update(changedProperties); + } + + static get styles() { + // language=css + return css` + ${commonStyles.getThemeCSS()} + + :host { + display: inline-block; + } + + a { + color: currentColor; + cursor: pointer; + text-decoration: none; + } + + img { + border-width: var(--vpu-border-width); + border-color: var(--vpu-dark); + border-style: solid; + } + + .dropdown.is-active .dropdown-menu, .dropdown.is-hoverable:hover .dropdown-menu { + display: block; + } + + .dropdown-menu { + display: none; + min-width: 5em; + max-width: 25em; + position: absolute; + z-index: 20; + border: solid 1px black; + border-radius: var(--vpu-border-radius); + overflow: hidden; + background-color: white; + } + + .dropdown-content { + background-color: white; + padding-bottom: 0.5rem; + padding-top: 0.5rem; + } + + .dropdown-content img { + max-width: 120px; + } + + .menu a { + /*padding: 0.3em;*/ + font-weight: 400; + color: #000; + display: block; + text-decoration: none; + } + + .menu a:hover { + color: #E4154B; + } + + .menu a.selected { color: white; background-color: black; } + + .dropdown-item { + color: #4a4a4a; + display: block; + font-size: 0.875rem; + line-height: 1.5; + padding: 0.375rem 1rem; + position: relative; + } + + .dropdown, img.login { + cursor: pointer; + } + + a.dropdown-item { + width: initial !important; + } + + .main-button { + min-width: 150px; + } + + .menu-icon { + height: 1em; + width: 1em; + vertical-align: -0.1rem; + } + + .login-box svg { + width: 1.1em; + height: 1.1em; + display: flex; + } + + .login-button { + padding: 0.3em 0.4em; + transition: background-color 0.15s, color 0.15s; + } + + .login-button:hover { + background-color: var(--vpu-dark); + color: var(--vpu-light); + cursor: pointer; + transition: none; + } + + .login-box { + display: flex; + align-items: center; + } + + .login-box:hover svg path { + fill: var(--vpu-light); + } + + .login-box .label { + padding-left: 0.2em; + } + + .dropdown-trigger { + display: flex; + align-items: center; + } + + .dropdown-trigger .name { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + min-width: 0; + margin-right: 0.5em + } + `; + } + + setChevron(name) { + const chevron = this.shadowRoot.querySelector("#menu-chevron-icon"); + if (chevron !== null) { + chevron.name = name; + } + } + + onDropdownClick(event) { + event.stopPropagation(); + event.currentTarget.classList.toggle('is-active'); + this.setChevron(event.currentTarget.classList.contains('is-active') ? 'chevron-up' : 'chevron-down'); + this.updateDropdownWidth(); + } + + closeDropdown() { + var dropdowns = this.shadowRoot.querySelectorAll('.dropdown'); + dropdowns.forEach(function (el) { + el.classList.remove('is-active'); + }); + this.setChevron('chevron-down'); + } + + onProfileClicked(event) { + event.preventDefault(); + const profileEvent = new CustomEvent("vpu-auth-profile", { + "detail": "Profile event", + bubbles: true, + composed: true, + }); + this.dispatchEvent(profileEvent); + } + + renderLoggedIn() { + const person = this._loginData.person; + const imageURL = (this.showImage && person && person.image) ? person.image : null; + + return html` + <div class="dropdown" @click="${this.onDropdownClick}"> + <a href="#"> + <div class="dropdown-trigger login-button"> + <div class="name">${this._loginData.name}</div> + <vpu-icon class="menu-icon" name="chevron-down" id="menu-chevron-icon"></vpu-icon> + </div> + </a> + <div class="dropdown-menu" id="dropdown-menu2" role="menu"> + <div class="dropdown-content" @blur="${this.closeDropdown}"> + ${imageURL ? html`<div class="dropdown-item"><img alt="" src="${imageURL}"></div>` : ''} + <div class="menu"> + ${this.showProfile ? html`<a href="#" @click="${this.onProfileClicked}" class="dropdown-item">${i18n.t('profile')}</a>` :''} + <a href="#" @click="${this.onLogoutClicked}" class="dropdown-item">${i18n.t('logout')}</a> + </div> + </div> + </div> + </div> + `; + } + + renderLoggedOut() { + let loginSVG = ` + <svg + viewBox="0 0 100 100" + y="0px" + x="0px" + id="icon" + role="img" + version="1.1"> + <g + id="g6"> + <path + style="stroke-width:1.33417916" + id="path2" + d="m 42.943908,38.894934 5.885859,6.967885 H 5.4215537 c -1.8393311,0 -3.4334181,1.741972 -3.4334181,4.064599 0,2.322628 1.4714649,4.064599 3.4334181,4.064599 H 48.829767 L 42.943908,60.9599 c -1.348843,1.596808 -1.348843,4.064599 0,5.661406 1.348843,1.596808 3.433418,1.596808 4.782261,0 L 61.705085,49.927418 47.726169,33.378693 c -1.348843,-1.596806 -3.433418,-1.596806 -4.782261,0 -1.348843,1.596807 -1.348843,4.064599 0,5.516241 z" /> + <path + id="path4" + d="m 50,2.3007812 c -18.777325,0 -35.049449,10.9124408 -42.8261719,26.7246098 H 13.390625 C 20.672112,16.348362 34.336876,7.8007812 50,7.8007812 73.3,7.8007812 92.300781,26.7 92.300781,50 92.300781,73.3 73.3,92.300781 50,92.300781 c -15.673389,0 -29.345175,-8.60579 -36.623047,-21.326172 H 7.1640625 C 14.942553,86.8272 31.242598,97.800781 50.099609,97.800781 76.399609,97.800781 97.900391,76.4 97.900391,50 97.800391,23.7 76.3,2.3007812 50,2.3007812 Z" /> + </g> + </svg> + `; + + return html` + <a href="#" @click="${this.onLoginClicked}"> + <div class="login-box login-button"> + <div class="icon">${unsafeHTML(loginSVG)}</div> + <div class="label">${i18n.t('login')}</div> + </div> + </a> + `; + } + + render() { + const loggedIn = (this._loginData.status === LoginStatus.LOGGED_IN); + return html` + <div class="authbox"> + ${loggedIn ? this.renderLoggedIn() : this.renderLoggedOut()} + </div> + `; + } +} \ No newline at end of file diff --git a/packages/auth/src/auth.js b/packages/auth/src/auth.js index f33a6c7c..647efec0 100644 --- a/packages/auth/src/auth.js +++ b/packages/auth/src/auth.js @@ -1,23 +1,12 @@ import {i18n} from './i18n.js'; -import {html, css} from 'lit-element'; -import {unsafeHTML} from 'lit-html/directives/unsafe-html.js'; -import {ScopedElementsMixin} from '@open-wc/scoped-elements'; import JSONLD from 'vpu-common/jsonld'; import * as commonUtils from 'vpu-common/utils'; -import * as commonStyles from 'vpu-common/styles'; -import {Icon, EventBus} from 'vpu-common'; -import VPULitElement from 'vpu-common/vpu-lit-element'; +import {EventBus} from 'vpu-common'; import {KeycloakWrapper} from './keycloak.js'; +import {LitElement} from "lit-element"; +import {LoginStatus} from './util.js'; -const LoginStatus = Object.freeze({ - UNKNOWN: 'unknown', - LOGGING_IN: 'logging-in', - LOGGED_IN: 'logged-in', - LOGGING_OUT: 'logging-out', - LOGGED_OUT: 'logged-out', -}); - /** * Keycloak auth web component * https://www.keycloak.org/docs/latest/securing_apps/index.html#_javascript_adapter @@ -31,14 +20,12 @@ const LoginStatus = Object.freeze({ * window.VPUPerson: Person json object of the user (optional, enable by setting the `load-person` attribute, * which will dispatch a `vpu-auth-person-init` event when loaded) */ -export class Auth extends ScopedElementsMixin(VPULitElement) { +export class Auth extends LitElement { constructor() { super(); this.lang = 'de'; this.forceLogin = false; this.loadPerson = false; - this.showProfile = false; - this.showImage = false; this.token = ""; this.tokenParsed = null; this.subject = ""; @@ -53,17 +40,19 @@ export class Auth extends ScopedElementsMixin(VPULitElement) { // Create the events this.initEvent = new CustomEvent("vpu-auth-init", { "detail": "KeyCloak init event", bubbles: true, composed: true }); this.personInitEvent = new CustomEvent("vpu-auth-person-init", { "detail": "KeyCloak person init event", bubbles: true, composed: true }); - this.profileEvent = new CustomEvent("vpu-auth-profile", { "detail": "Profile event", bubbles: true, composed: true }); this.keycloakDataUpdateEvent = new CustomEvent("vpu-auth-keycloak-data-update", { "detail": "KeyCloak data was updated", bubbles: true, composed: true }); - this.closeDropdown = this.closeDropdown.bind(this); this._onKCChanged = this._onKCChanged.bind(this); - } + } - static get scopedElements() { - return { - 'vpu-icon': Icon, - }; + update(changedProperties) { + changedProperties.forEach((oldValue, propName) => { + if (propName === "lang") { + i18n.changeLanguage(this.lang); + } + }); + + super.update(changedProperties); } _onKCChanged(event) { @@ -91,7 +80,7 @@ export class Auth extends ScopedElementsMixin(VPULitElement) { window.VPUPersonId = this.personId; window.VPUPerson = this.person; - this._setLoginStatus(LoginStatus.LOGGED_IN, tokenChanged); + this._setLoginStatus(LoginStatus.LOGGED_IN, tokenChanged || newPerson); } else { if (this._loginStatus === LoginStatus.LOGGED_IN) { this._setLoginStatus(LoginStatus.LOGGING_OUT); @@ -154,22 +143,19 @@ export class Auth extends ScopedElementsMixin(VPULitElement) { this._bus.publish('auth-update', { status: this._loginStatus, token: this.token, + name: this.name, + person: this.person, }, { retain: true, }); } - /** - * See: https://lit-element.polymer-project.org/guide/properties#initialize - */ static get properties() { return { lang: { type: String }, forceLogin: { type: Boolean, attribute: 'force-login' }, tryLogin: { type: Boolean, attribute: 'try-login' }, loadPerson: { type: Boolean, attribute: 'load-person' }, - showProfile: { type: Boolean, attribute: 'show-profile' }, - showImage: { type: Boolean, attribute: 'show-image' }, entryPointUrl: { type: String, attribute: 'entry-point-url' }, keycloakConfig: { type: Object, attribute: 'keycloak-config' }, name: { type: String, attribute: false }, @@ -211,6 +197,26 @@ export class Auth extends ScopedElementsMixin(VPULitElement) { this._kcwrapper = new KeycloakWrapper(baseURL, realm, clientId, silentCheckSsoRedirectUri); this._kcwrapper.addEventListener('changed', this._onKCChanged); + + + this._bus.subscribe('auth-login', () => { + this._kcwrapper.login({lang: this.lang, scope: this._getScope()}); + }); + + this._bus.subscribe('auth-logout', () => { + // Keycloak will redirect right away without emitting events, so we have + // to do this manually here + if (this._loginStatus === LoginStatus.LOGGED_IN) { + this._setLoginStatus(LoginStatus.LOGGING_OUT); + } + this._kcwrapper.logout(); + // In case logout was aborted, for example with beforeunload, + // revert back to being logged in + if (this._loginStatus === LoginStatus.LOGGING_OUT) { + this._setLoginStatus(LoginStatus.LOGGED_IN); + } + }); + const handleLogin = async () => { if (this.forceLogin || this._kcwrapper.isLoggingIn()) { this._setLoginStatus(LoginStatus.LOGGING_IN); @@ -226,291 +232,12 @@ export class Auth extends ScopedElementsMixin(VPULitElement) { }; handleLogin(); - - this.updateComplete.then(() => { - window.onresize = () => { - this.updateDropdownWidth(); - }; - }); - - document.addEventListener('click', this.closeDropdown); - } - - /** - * Set the dropdown width to almost the width of the web component - * We need to set the width manually because a percent width is in relation to the viewport - */ - updateDropdownWidth() { - const dropdown = this._("div.dropdown-menu"); - - if (!dropdown) { - return; - } - - let viewportOffset = this.getBoundingClientRect(); - let spaceToRIght = window.innerWidth - viewportOffset.left; - dropdown.setAttribute("style", `width: ${spaceToRIght - 20}px`); } disconnectedCallback() { - this._bus.close(); this._kcwrapper.removeEventListener('changed', this._onKCChanged); - document.removeEventListener('click', this.closeDropdown); - super.disconnectedCallback(); - } - - onLoginClicked(e) { - this._kcwrapper.login({lang: this.lang, scope: this._getScope()}); - e.preventDefault(); - } - - onLogoutClicked(e) { - // Keycloak will redirect right away without emitting events, so we have - // to do this manually here - if (this._loginStatus === LoginStatus.LOGGED_IN) { - this._setLoginStatus(LoginStatus.LOGGING_OUT); - } - this._kcwrapper.logout(); - // In case logout was aborted, for example with beforeunload, - // revert back to being logged in - if (this._loginStatus === LoginStatus.LOGGING_OUT) { - this._setLoginStatus(LoginStatus.LOGGED_IN); - } - } - - update(changedProperties) { - changedProperties.forEach((oldValue, propName) => { - if (propName === "lang") { - i18n.changeLanguage(this.lang); - } - }); - - super.update(changedProperties); - } - - static get styles() { - // language=css - return css` - ${commonStyles.getThemeCSS()} - - :host { - display: inline-block; - } - - a { - color: currentColor; - cursor: pointer; - text-decoration: none; - } - - img { - border-width: var(--vpu-border-width); - border-color: var(--vpu-dark); - border-style: solid; - } - - .dropdown.is-active .dropdown-menu, .dropdown.is-hoverable:hover .dropdown-menu { - display: block; - } - - .dropdown-menu { - display: none; - min-width: 5em; - max-width: 25em; - position: absolute; - z-index: 20; - border: solid 1px black; - border-radius: var(--vpu-border-radius); - overflow: hidden; - background-color: white; - } - - .dropdown-content { - background-color: white; - padding-bottom: 0.5rem; - padding-top: 0.5rem; - } - - .dropdown-content img { - max-width: 120px; - } - - .menu a { - /*padding: 0.3em;*/ - font-weight: 400; - color: #000; - display: block; - text-decoration: none; - } - - .menu a:hover { - color: #E4154B; - } - - .menu a.selected { color: white; background-color: black; } - - .dropdown-item { - color: #4a4a4a; - display: block; - font-size: 0.875rem; - line-height: 1.5; - padding: 0.375rem 1rem; - position: relative; - } - - .dropdown, img.login { - cursor: pointer; - } - - a.dropdown-item { - width: initial !important; - } - - .main-button { - min-width: 150px; - } - - .menu-icon { - height: 1em; - width: 1em; - vertical-align: -0.1rem; - } - - .login-box svg { - width: 1.1em; - height: 1.1em; - display: flex; - } - - .login-button { - padding: 0.3em 0.4em; - transition: background-color 0.15s, color 0.15s; - } - - .login-button:hover { - background-color: var(--vpu-dark); - color: var(--vpu-light); - cursor: pointer; - transition: none; - } - - .login-box { - display: flex; - align-items: center; - } - - .login-box:hover svg path { - fill: var(--vpu-light); - } - - .login-box .label { - padding-left: 0.2em; - } - - .dropdown-trigger { - display: flex; - align-items: center; - } - - .dropdown-trigger .name { - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - min-width: 0; - margin-right: 0.5em - } - `; - } - - setChevron(name) { - const chevron = this.shadowRoot.querySelector("#menu-chevron-icon"); - if (chevron !== null) { - chevron.name = name; - } - } - - onDropdownClick(event) { - event.stopPropagation(); - event.currentTarget.classList.toggle('is-active'); - this.setChevron(event.currentTarget.classList.contains('is-active') ? 'chevron-up' : 'chevron-down'); - this.updateDropdownWidth(); - } - - closeDropdown() { - var dropdowns = this.shadowRoot.querySelectorAll('.dropdown'); - dropdowns.forEach(function (el) { - el.classList.remove('is-active'); - }); - this.setChevron('chevron-down'); - } - - onProfileClicked(event) { - event.preventDefault(); - this.dispatchEvent(this.profileEvent); - } - - renderLoggedIn() { - const imageURL = (this.showImage && this.person && this.person.image) ? this.person.image : null; - - return html` - <div class="dropdown" @click="${this.onDropdownClick}"> - <a href="#"> - <div class="dropdown-trigger login-button"> - <div class="name">${this.name}</div> - <vpu-icon class="menu-icon" name="chevron-down" id="menu-chevron-icon"></vpu-icon> - </div> - </a> - <div class="dropdown-menu" id="dropdown-menu2" role="menu"> - <div class="dropdown-content" @blur="${this.closeDropdown}"> - ${imageURL ? html`<div class="dropdown-item"><img alt="" src="${imageURL}"></div>` : ''} - <div class="menu"> - ${this.showProfile ? html`<a href="#" @click="${this.onProfileClicked}" class="dropdown-item">${i18n.t('profile')}</a>` :''} - <a href="#" @click="${this.onLogoutClicked}" class="dropdown-item">${i18n.t('logout')}</a> - </div> - </div> - </div> - </div> - `; - } - - renderLoggedOut() { - let loginSVG = ` - <svg - viewBox="0 0 100 100" - y="0px" - x="0px" - id="icon" - role="img" - version="1.1"> - <g - id="g6"> - <path - style="stroke-width:1.33417916" - id="path2" - d="m 42.943908,38.894934 5.885859,6.967885 H 5.4215537 c -1.8393311,0 -3.4334181,1.741972 -3.4334181,4.064599 0,2.322628 1.4714649,4.064599 3.4334181,4.064599 H 48.829767 L 42.943908,60.9599 c -1.348843,1.596808 -1.348843,4.064599 0,5.661406 1.348843,1.596808 3.433418,1.596808 4.782261,0 L 61.705085,49.927418 47.726169,33.378693 c -1.348843,-1.596806 -3.433418,-1.596806 -4.782261,0 -1.348843,1.596807 -1.348843,4.064599 0,5.516241 z" /> - <path - id="path4" - d="m 50,2.3007812 c -18.777325,0 -35.049449,10.9124408 -42.8261719,26.7246098 H 13.390625 C 20.672112,16.348362 34.336876,7.8007812 50,7.8007812 73.3,7.8007812 92.300781,26.7 92.300781,50 92.300781,73.3 73.3,92.300781 50,92.300781 c -15.673389,0 -29.345175,-8.60579 -36.623047,-21.326172 H 7.1640625 C 14.942553,86.8272 31.242598,97.800781 50.099609,97.800781 76.399609,97.800781 97.900391,76.4 97.900391,50 97.800391,23.7 76.3,2.3007812 50,2.3007812 Z" /> - </g> - </svg> - `; - - return html` - <a href="#" @click="${this.onLoginClicked}"> - <div class="login-box login-button"> - <div class="icon">${unsafeHTML(loginSVG)}</div> - <div class="label">${i18n.t('login')}</div> - </div> - </a> - `; - } + this._bus.close(); - render() { - const loggedIn = (this._loginStatus === LoginStatus.LOGGED_IN); - return html` - <div class="authbox"> - ${loggedIn ? this.renderLoggedIn() : this.renderLoggedOut()} - </div> - `; + super.disconnectedCallback(); } } \ No newline at end of file diff --git a/packages/auth/src/index.js b/packages/auth/src/index.js index f8748296..9de95a6d 100644 --- a/packages/auth/src/index.js +++ b/packages/auth/src/index.js @@ -1,3 +1,4 @@ import {Auth} from './auth.js'; +import {AuthButton} from './auth-button.js'; -export {Auth}; \ No newline at end of file +export {Auth, AuthButton}; \ No newline at end of file diff --git a/packages/auth/src/util.js b/packages/auth/src/util.js new file mode 100644 index 00000000..2227557d --- /dev/null +++ b/packages/auth/src/util.js @@ -0,0 +1,7 @@ +export const LoginStatus = Object.freeze({ + UNKNOWN: 'unknown', + LOGGING_IN: 'logging-in', + LOGGED_IN: 'logged-in', + LOGGING_OUT: 'logging-out', + LOGGED_OUT: 'logged-out', +}); diff --git a/packages/auth/src/vpu-auth-demo.js b/packages/auth/src/vpu-auth-demo.js index 44898619..c9371ad7 100644 --- a/packages/auth/src/vpu-auth-demo.js +++ b/packages/auth/src/vpu-auth-demo.js @@ -2,6 +2,7 @@ import {i18n} from './i18n.js'; import {html, LitElement} from 'lit-element'; import {ScopedElementsMixin} from '@open-wc/scoped-elements'; import {Auth} from './auth.js'; +import {AuthButton} from './auth-button.js'; import * as commonUtils from 'vpu-common/utils'; class AuthDemo extends ScopedElementsMixin(LitElement) { @@ -13,6 +14,7 @@ class AuthDemo extends ScopedElementsMixin(LitElement) { static get scopedElements() { return { 'vpu-auth': Auth, + 'vpu-auth-button': AuthButton, }; } @@ -88,7 +90,9 @@ class AuthDemo extends ScopedElementsMixin(LitElement) { <h1 class="title">Auth-Demo</h1> </div> <div class="container"> - <vpu-auth lang="${this.lang}" keycloak-config='{"url": "https://auth-dev.tugraz.at/auth", "realm": "tugraz", "clientId": "auth-dev-mw-frontend-local", "silentCheckSsoRedirectUri": "${silentCheckSsoUri}", "scope": "optional-test-scope"}' load-person try-login show-image></vpu-auth> + <vpu-auth lang="${this.lang}" keycloak-config='{"url": "https://auth-dev.tugraz.at/auth", "realm": "tugraz", "clientId": "auth-dev-mw-frontend-local", "silentCheckSsoRedirectUri": "${silentCheckSsoUri}", "scope": "optional-test-scope"}' load-person try-login></vpu-auth> + + <vpu-auth-button lang="${this.lang}" show-image></vpu-auth-button> </div> </section> diff --git a/packages/auth/src/vpu-auth.js b/packages/auth/src/vpu-auth.js index c6be6253..50461dcb 100644 --- a/packages/auth/src/vpu-auth.js +++ b/packages/auth/src/vpu-auth.js @@ -1,4 +1,6 @@ -import * as commonUtils from 'vpu-common/utils'; +import {defineCustomElement} from 'vpu-common/utils'; import {Auth} from './auth.js'; +import {AuthButton} from './auth-button.js'; -commonUtils.defineCustomElement('vpu-auth', Auth); +defineCustomElement('vpu-auth', Auth); +defineCustomElement('vpu-auth-button', AuthButton); diff --git a/packages/auth/test/unit.js b/packages/auth/test/unit.js index 3a3c63e7..4f913340 100644 --- a/packages/auth/test/unit.js +++ b/packages/auth/test/unit.js @@ -26,6 +26,24 @@ suite('vpu-auth basics', () => { }); }); +suite('vpu-auth-button', () => { + let node; + + suiteSetup(async () => { + node = document.createElement('vpu-auth-button'); + document.body.appendChild(node); + await node.updateComplete; + }); + + suiteTeardown(() => { + node.remove(); + }); + + test('should render', () => { + expect(node).to.have.property('shadowRoot'); + }); + }); + suite('vpu-auth-demo basics', () => { let node; -- GitLab