From 4b6a7a58f5452d6d9588c75677b32874353a2808 Mon Sep 17 00:00:00 2001 From: Rares Munteanu Date: Tue, 21 Mar 2023 17:00:42 +0100 Subject: [PATCH] [MWPW-128550] Profile block fixes (#581) * [MWPW-128550] Profile block fixes * [MWPW-128550] Profile block fixes PR feedback implementation --------- Co-authored-by: Rares Munteanu --- .../blocks/appLauncher/appLauncher.js | 3 - .../blocks/navDropdown/dropdown.css | 1 + .../profile/{milo-wrapper.css => button.css} | 0 .../blocks/profile/button.js | 26 +++ .../profile/{profile.css => dropdown.css} | 71 +++---- .../blocks/profile/dropdown.js | 188 ++++++++++++++++++ .../blocks/profile/milo-wrapper.js | 57 ------ .../blocks/profile/profile.js | 103 ---------- .../blocks/profile/signIn.css | 27 ++- .../blocks/profile/signIn.js | 50 +++-- .../blocks/profile/standalone-wrapper.css | 2 +- .../blocks/search/gnav-search.js | 2 +- .../global-navigation/global-navigation.css | 6 + .../global-navigation/global-navigation.js | 117 ++++++----- 14 files changed, 370 insertions(+), 283 deletions(-) rename libs/blocks/global-navigation/blocks/profile/{milo-wrapper.css => button.css} (100%) create mode 100644 libs/blocks/global-navigation/blocks/profile/button.js rename libs/blocks/global-navigation/blocks/profile/{profile.css => dropdown.css} (67%) create mode 100644 libs/blocks/global-navigation/blocks/profile/dropdown.js delete mode 100644 libs/blocks/global-navigation/blocks/profile/milo-wrapper.js delete mode 100644 libs/blocks/global-navigation/blocks/profile/profile.js diff --git a/libs/blocks/global-navigation/blocks/appLauncher/appLauncher.js b/libs/blocks/global-navigation/blocks/appLauncher/appLauncher.js index 43a7ebc473f..e54e2ca1e70 100644 --- a/libs/blocks/global-navigation/blocks/appLauncher/appLauncher.js +++ b/libs/blocks/global-navigation/blocks/appLauncher/appLauncher.js @@ -47,9 +47,6 @@ function decorateAppsMenu(profileEl, appsDom, toggle) { } async function appLauncher(profileEl, appLauncherBlock, toggle) { - const gnav = profileEl.closest('nav.gnav'); - gnav.classList.add('has-apps'); - const appsLink = appLauncherBlock.querySelector('a'); appsLink.href = localizeLink(appsLink.href); diff --git a/libs/blocks/global-navigation/blocks/navDropdown/dropdown.css b/libs/blocks/global-navigation/blocks/navDropdown/dropdown.css index 26674686206..f1ad317b631 100644 --- a/libs/blocks/global-navigation/blocks/navDropdown/dropdown.css +++ b/libs/blocks/global-navigation/blocks/navDropdown/dropdown.css @@ -120,6 +120,7 @@ left: 0; padding: 0; z-index: 1; + box-shadow: 0 3px 3px 0 rgb(0 0 0 / 20%); } [dir = "rtl"] .feds-popup { diff --git a/libs/blocks/global-navigation/blocks/profile/milo-wrapper.css b/libs/blocks/global-navigation/blocks/profile/button.css similarity index 100% rename from libs/blocks/global-navigation/blocks/profile/milo-wrapper.css rename to libs/blocks/global-navigation/blocks/profile/button.css diff --git a/libs/blocks/global-navigation/blocks/profile/button.js b/libs/blocks/global-navigation/blocks/profile/button.js new file mode 100644 index 00000000000..bb0fd430688 --- /dev/null +++ b/libs/blocks/global-navigation/blocks/profile/button.js @@ -0,0 +1,26 @@ +import { toFragment, getFedsPlaceholderConfig } from '../../utilities/utilities.js'; +import { replaceKey } from '../../../../features/placeholders.js'; + +const decorateButton = async ({ avatar }) => { + const label = await replaceKey( + 'profile-button', + getFedsPlaceholderConfig(), + ); + + const buttonElem = toFragment` + + `; + + return buttonElem; +}; + +export default decorateButton; diff --git a/libs/blocks/global-navigation/blocks/profile/profile.css b/libs/blocks/global-navigation/blocks/profile/dropdown.css similarity index 67% rename from libs/blocks/global-navigation/blocks/profile/profile.css rename to libs/blocks/global-navigation/blocks/profile/dropdown.css index 5d72a9008b7..a380b4d43e3 100644 --- a/libs/blocks/global-navigation/blocks/profile/profile.css +++ b/libs/blocks/global-navigation/blocks/profile/dropdown.css @@ -1,7 +1,7 @@ :root { --feds-color-profile-heading: #707070; --feds-color-profile: #4b4b4b; - --feds-color-profile-emphasis: #2c2c2c; + --feds-color-profile--emphasis: #2c2c2c; --feds-border-profile: 1px solid #e1e1e1; } @@ -18,32 +18,18 @@ display: none; line-height: 1; white-space: nowrap; + z-index: 1; } -[dir='rtl'] .feds-profile-menu { +[dir = "rtl"] .feds-profile-menu { right: initial; left: 0; } -.feds-profile.is-open .feds-profile-menu { +.feds-profile-button[aria-expanded = "true"] + .feds-profile-menu { display: block; } -.feds-profile li a { - color: inherit; - overflow: hidden; - text-overflow: ellipsis; - display: block; -} - -/* TODO be removable with refactoring the menu CSS */ -.feds-profile.gnav-navitem.has-menu.is-open .gnav-navitem-menu { - position: absolute; - top: 100%; - left: auto; - margin-top: 0; -} - .feds-profile-header { padding: 20px; display: flex; @@ -61,22 +47,23 @@ overflow: hidden; } +.feds-profile-name, +.feds-profile-email { + text-overflow: ellipsis; + overflow: hidden; +} + .feds-profile-name { - margin: 0; - padding: 0 0 4px; + margin: 0 0 4px; font-size: 18px; font-weight: 700; - text-overflow: ellipsis; - overflow: hidden; - color: var(--feds-color-profile-emphasis); + color: var(--feds-color-profile--emphasis); } .feds-profile-email { margin: 0 0 12px; font-size: 14px; color: var(--feds-color-profile-heading); - text-overflow: ellipsis; - overflow: hidden; } .feds-profile-account { @@ -91,24 +78,35 @@ padding: 6px 0; } -.feds-local-menu ul { - margin: 0; - padding: 0; -} - .feds-local-menu h5 { margin: 0; - padding: 5px 20px; + padding: 8px 20px; color: var(--feds-color-profile-heading); font-size: 11px; font-weight: 600; + line-height: 1.5; text-transform: uppercase; } +.feds-local-menu p { + margin: 0; +} + +.feds-local-menu a, +.feds-profile-actions a { + display: block; + color: var(--feds-color-link--light); +} + +.feds-local-menu a:hover, +.feds-profile-actions a:hover { + color: var(--feds-color-link--hover--light); + background-color: var(--feds-background-link--hover--light); +} + .feds-local-menu a { - font-size: 14px; - padding: 9px 18px; - color: var(--feds-color-profile); + padding: 6px 20px; + line-height: 1.4; outline-offset: -1px; } @@ -121,8 +119,3 @@ padding: 14px 20px; border-top: var(--feds-border-profile); } - -.feds-profile-menu li:hover { - color: var(--feds-color-profile-emphasis); - background-color: #f5f5f5; -} diff --git a/libs/blocks/global-navigation/blocks/profile/dropdown.js b/libs/blocks/global-navigation/blocks/profile/dropdown.js new file mode 100644 index 00000000000..9da56e9cfdd --- /dev/null +++ b/libs/blocks/global-navigation/blocks/profile/dropdown.js @@ -0,0 +1,188 @@ +import { getConfig } from '../../../../utils/utils.js'; +import { toFragment, getFedsPlaceholderConfig } from '../../utilities/utilities.js'; +import { replaceKeyArray } from '../../../../features/placeholders.js'; + +const getLanguage = (ietfLocale) => { + if (!ietfLocale.length) return 'en'; + + const nonStandardLocaleMap = { 'no-NO': 'nb' }; + + if (nonStandardLocaleMap[ietfLocale]) { + return nonStandardLocaleMap[ietfLocale]; + } + + return ietfLocale.split('-')[0]; +}; + +const decorateProfileLink = (service, path = '') => { + const defaultServiceUrls = { + adminconsole: 'https://adminconsole.adobe.com', + account: 'https://account.adobe.com', + }; + + if (!service.length || !defaultServiceUrls[service]) return ''; + + let serviceUrl; + const { env } = getConfig(); + + if (!env?.[service]) { + serviceUrl = defaultServiceUrls[service]; + } else { + serviceUrl = new URL(defaultServiceUrls[service]); + serviceUrl.hostname = env[service]; + } + + return `${serviceUrl}${path}`; +}; + +const decorateAction = (label, path) => toFragment`
  • ${label}
  • `; + +class ProfileDropdown { + constructor({ + rawElem, + decoratedElem, + avatar, + sections, + buttonElem, + openOnInit, + } = {}) { + this.placeholders = {}; + this.profileData = {}; + this.avatar = avatar; + this.buttonElem = buttonElem; + this.decoratedElem = decoratedElem; + this.sections = sections; + this.openOnInit = openOnInit; + this.localMenu = rawElem.querySelector('h5')?.parentElement; + this.init(); + } + + async init() { + await this.getData(); + this.setButtonLabel(); + this.dropdown = this.decorateDropdown(); + this.addEventListeners(); + + if (this.openOnInit) this.toggleDropdown(); + + this.decoratedElem.append(this.dropdown); + } + + async getData() { + [ + [ + this.placeholders.profileButton, + this.placeholders.signOut, + this.placeholders.viewAccount, + this.placeholders.manageTeams, + this.placeholders.manageEnterprise, + ], + // TODO: sanity checks if the user is logged in and mandatory properties are set. + // If not, add logs providing guidance for developers + { displayName: this.profileData.displayName, email: this.profileData.email }, + ] = await Promise.all([ + replaceKeyArray( + ['profile-button', 'sign-out', 'view-account', 'manage-teams', 'manage-enterprise'], + getFedsPlaceholderConfig(), + ), + window.adobeIMS.getProfile(), + ]); + } + + setButtonLabel() { + if (this.buttonElem) this.buttonElem.setAttribute('aria-label', this.profileData.displayName); + } + + decorateDropdown() { + const { locale } = getConfig(); + const lang = getLanguage(locale.ietf); + + // TODO: the account name and email might need a bit of adaptive behavior; + // historically we shrunk the font size and displayed the account name on two lines; + // the email had some special logic as well; + // for MVP, we took a simpler approach ("Some very long name, very l...") + // TODO: historically, clicking the avatar lead to '/profile', + // but clicking the 'View account link' let to the account page; + // we need to check whether this is still needed + return toFragment` +
    + + +
    +

    ${this.profileData.displayName}

    +

    ${this.decorateEmail(this.profileData.email)}

    + +
    +
    + ${this.localMenu ? this.decorateLocalMenu() : ''} +
      + ${this.sections?.manage?.items?.team?.id ? decorateAction(this.placeholders.manageTeams, '/team') : ''} + ${this.sections?.manage?.items?.enterprise?.id ? decorateAction(this.placeholders.manageEnterprise) : ''} + ${this.decorateSignOut()} +
    +
    + `; + } + + decorateEmail() { + const maxCharacters = 12; + const emailParts = this.profileData.email.split('@'); + const username = emailParts[0].length <= maxCharacters + ? emailParts[0] + : `${emailParts[0].slice(0, maxCharacters)}…`; + const domainArr = emailParts[1].split('.'); + const tld = domainArr.pop(); + let domain = domainArr.join('.'); + domain = domain.length <= maxCharacters + ? domain + : `${domain.slice(0, maxCharacters)}…`; + + return `${username}@${domain}.${tld}`; + } + + decorateLocalMenu() { + if (this.localMenu) this.localMenu.classList.add('feds-local-menu'); + + return this.localMenu; + } + + decorateSignOut() { + const signOutLink = toFragment` +
  • + ${this.placeholders.signOut} +
  • + `; + + // TODO consumers might want to execute their own logic before a sign out + // we might want to provide them a way to do so here + signOutLink.addEventListener('click', (e) => { + e.preventDefault(); + window.adobeIMS.signOut(); + }); + + return signOutLink; + } + + addEventListeners() { + this.buttonElem.addEventListener('click', () => { + this.toggleDropdown(); + }); + } + + toggleDropdown() { + const isOpen = this.buttonElem.getAttribute('aria-expanded') === 'true'; + + if (isOpen) { + this.buttonElem.setAttribute('aria-expanded', 'false'); + } else { + this.buttonElem.setAttribute('aria-expanded', 'true'); + } + } +} + +export default ProfileDropdown; diff --git a/libs/blocks/global-navigation/blocks/profile/milo-wrapper.js b/libs/blocks/global-navigation/blocks/profile/milo-wrapper.js deleted file mode 100644 index 599dafa27a5..00000000000 --- a/libs/blocks/global-navigation/blocks/profile/milo-wrapper.js +++ /dev/null @@ -1,57 +0,0 @@ -import { toFragment, getFedsPlaceholderConfig } from '../../utilities/utilities.js'; -import { - replaceKey, - replaceKeyArray, -} from '../../../../features/placeholders.js'; - -const initProfileButton = async ({ blockEl, decoratedEl, avatar }) => { - const profileButtonLabel = await replaceKey( - 'profile-button', - getFedsPlaceholderConfig(), - ); - if (blockEl.children.length > 1) decoratedEl.classList.add('has-menu'); - decoratedEl.closest('nav.gnav')?.classList.add('signed-in'); - - const profileButtonEl = toFragment` - - `; - profileButtonEl.addEventListener('click', () => window.dispatchEvent(new Event('feds:profileButton:clicked'))); - return profileButtonEl; -}; - -const initProfileMenu = async ({ blockEl, ProfileClass, ...rest }) => { - const placeholders = {}; - [ - placeholders.profileButton, - placeholders.signOut, - placeholders.viewAccount, - placeholders.manageTeams, - placeholders.manageEnterprise, - ] = await replaceKeyArray( - [ - 'profile-button', - 'sign-out', - 'view-account', - 'manage-teams', - 'manage-enterprise', - ], - getFedsPlaceholderConfig(), - ); - const profile = new ProfileClass({ - localMenu: blockEl.querySelector('h5')?.parentElement, - placeholders, - ...rest, - }); - return profile; -}; - -export default { initProfileButton, initProfileMenu }; diff --git a/libs/blocks/global-navigation/blocks/profile/profile.js b/libs/blocks/global-navigation/blocks/profile/profile.js deleted file mode 100644 index 05d04e508ec..00000000000 --- a/libs/blocks/global-navigation/blocks/profile/profile.js +++ /dev/null @@ -1,103 +0,0 @@ -import { getConfig } from '../../../../utils/utils.js'; -import { toFragment } from '../../utilities/utilities.js'; - -const decorateEmail = (email) => { - const MAX_CHAR = 12; - const emailParts = email.split('@'); - const username = emailParts[0].length <= MAX_CHAR ? emailParts[0] : `${emailParts[0].slice(0, MAX_CHAR)}…`; - const domainArr = emailParts[1].split('.'); - const tld = domainArr.pop(); - let domain = domainArr.join('.'); - domain = domain.length <= MAX_CHAR ? domain : `${domain.slice(0, MAX_CHAR)}…`; - return `${username}@${domain}.${tld}`; -}; - -const decorateProfileLink = (href, service) => { - const env = getConfig(); - const name = env.name || 'prod'; - if (name === 'prod') return href; - const url = new URL(href); - url.hostname = env[service]; - return url.href; -}; - -const decorateAction = (label, href) => toFragment`
  • ${label}
  • `; - -class Profile { - constructor({ - decoratedEl, - avatar, - sections, - profileButtonEl, - localMenu, - placeholders, - }) { - this.sections = sections; - this.avatar = avatar; - this.profileButtonEl = profileButtonEl; - this.decoratedEl = decoratedEl; - this.localMenu = localMenu; - this.placeholders = placeholders; - if (localMenu) { - localMenu.classList.add('feds-local-menu'); - } - this.init(); - } - - async init() { - // TODO do some sanity checks if the user is logged in, the mandatory properties are set. - // If not there should be helpful logs providing guidance for developers - const { displayName, email } = await window.adobeIMS.getProfile(); - if (this.profileButtonEl) this.profileButtonEl.setAttribute('aria-label', displayName); - this.displayName = displayName; - this.email = email; - this.decoratedEl.append(this.menu()); - } - - decorateSignOut() { - const signOutLink = toFragment` -
  • - ${this.placeholders.signOut} -
  • - `; - - // TODO consumers might want to execute their own logic before a sign out - // we might want to provide them a way to do so here - signOutLink.addEventListener('click', (e) => { - e.preventDefault(); - window.adobeIMS.signOut(); - }); - return signOutLink; - } - - menu() { - // TODO the account name and email might need a bit of adaptive behaviour - // historically we shrunk the fontsize and displayed the account name on two lines - // the email had some special logic as well - // we took a simpler approach ("Some very long name, very l...") for MVP - return toFragment` -
    - - -
    -

    ${this.displayName}

    -

    ${decorateEmail(this.email)}

    - -
    -
    - ${this.localMenu} -
      - ${this.sections.manage.items.team?.id ? decorateAction(this.placeholders.manageTeams, 'https://adminconsole.adobe.com/team') : ''} - ${this.sections.manage.items.enterprise?.id ? decorateAction(this.placeholders.manageEnterprise, 'https://adminconsole.adobe.com') : ''} - ${this.decorateSignOut()} -
    -
    - `; - } -} -export default Profile; diff --git a/libs/blocks/global-navigation/blocks/profile/signIn.css b/libs/blocks/global-navigation/blocks/profile/signIn.css index 2a74e94709d..6ab21a57a8f 100644 --- a/libs/blocks/global-navigation/blocks/profile/signIn.css +++ b/libs/blocks/global-navigation/blocks/profile/signIn.css @@ -1,11 +1,11 @@ -.feds-signin { +.feds-signIn { padding: 11px var(--feds-spacing-utilityNav); border-radius: var(--feds-radius-utilityIcon); color: #4B4B4B; white-space: nowrap; } -.feds-signin-dropdown { +.feds-signIn-dropdown { position: absolute; display: none; right: 0; @@ -17,31 +17,30 @@ box-shadow: 0 2px 6px -1px rgb(0 0 0 / 10%); line-height: 1.4; white-space: nowrap; + z-index: 1; } -.feds-profile.is-open .feds-signin-dropdown { +.feds-signIn[aria-expanded = "true"] + .feds-signIn-dropdown { display: block; } -[dir='rtl'] .feds-signin-dropdown { +[dir = "rtl"] .feds-signIn-dropdown { right: initial; left: 0; } -.feds-signin-dropdown a { - color: inherit; -} - -.feds-signin-dropdown ul { +.feds-signIn-dropdown ul { margin: 0; padding: 12px 0; } -.feds-signin-dropdown li { - padding: 4px 32px; +.feds-signIn-dropdown li > a { + display: block; + color: var(--feds-color-link--light); + padding: 6px 32px; } -.feds-signin-dropdown li:hover { - color: var(--link-color); - background-color: #f5f5f5; +.feds-signIn-dropdown li > a:hover { + color: var(--feds-color-link--hover--light); + background-color: var(--feds-background-link--hover--light);; } diff --git a/libs/blocks/global-navigation/blocks/profile/signIn.js b/libs/blocks/global-navigation/blocks/profile/signIn.js index e8a54c38f7c..0193c8b1371 100644 --- a/libs/blocks/global-navigation/blocks/profile/signIn.js +++ b/libs/blocks/global-navigation/blocks/profile/signIn.js @@ -1,35 +1,49 @@ import { toFragment, getFedsPlaceholderConfig } from '../../utilities/utilities.js'; import { replaceKey } from '../../../../features/placeholders.js'; -const decorateSignIn = async ({ blockEl, decoratedEl }) => { - const dropDown = blockEl.querySelector(':scope > div:nth-child(2)'); +const decorateSignIn = async ({ rawElem, decoratedElem }) => { + const dropdownElem = rawElem.querySelector(':scope > div:nth-child(2)'); const signInLabel = await replaceKey('sign-in', getFedsPlaceholderConfig()); - if (!dropDown) { - const signIn = toFragment``; - signIn.addEventListener('click', (e) => { + let signInElem; + + if (!dropdownElem) { + signInElem = toFragment``; + + signInElem.addEventListener('click', (e) => { e.preventDefault(); // TODO confirm adobeIMS is always set // e.g. when it's consumed as NPM package or when we are on a consumer page window.adobeIMS.signIn(); }); - return signIn; - } + } else { + signInElem = toFragment``; - const signInWithDropDown = toFragment``; - dropDown.classList.add('feds-signin-dropdown'); - decoratedEl.insertAdjacentElement('beforeend', dropDown); + signInElem.addEventListener('click', () => { + const isOpen = signInElem.getAttribute('aria-expanded') === 'true'; - // TODO we don't have a good way of adding attributes to links - const dropDownSignIn = dropDown.querySelector('[href="https://adobe.com?sign-in=true"]'); - if (dropDownSignIn) { - dropDownSignIn.addEventListener('click', (e) => { - e.preventDefault(); - window.adobeIMS.signIn(); + if (isOpen) { + signInElem.setAttribute('aria-expanded', 'false'); + } else { + signInElem.setAttribute('aria-expanded', 'true'); + } }); + + dropdownElem.classList.add('feds-signIn-dropdown'); + + // TODO we don't have a good way of adding attributes to links + const dropdownSignIn = dropdownElem.querySelector('[href="https://adobe.com?sign-in=true"]'); + + if (dropdownSignIn) { + dropdownSignIn.addEventListener('click', (e) => { + e.preventDefault(); + window.adobeIMS.signIn(); + }); + } + + decoratedElem.append(dropdownElem); } - signInWithDropDown.addEventListener('click', () => window.dispatchEvent(new Event('feds:profileSignIn:clicked'))); - return signInWithDropDown; + decoratedElem.prepend(signInElem); }; export default decorateSignIn; diff --git a/libs/blocks/global-navigation/blocks/profile/standalone-wrapper.css b/libs/blocks/global-navigation/blocks/profile/standalone-wrapper.css index 10983927f94..425ab157cd3 100644 --- a/libs/blocks/global-navigation/blocks/profile/standalone-wrapper.css +++ b/libs/blocks/global-navigation/blocks/profile/standalone-wrapper.css @@ -8,7 +8,7 @@ justify-content: end; } -.feds-signin { +.feds-signIn { padding: 4px var(--feds-spacing-utilityNav); border-radius: var(--feds-radius-utilityIcon); } diff --git a/libs/blocks/global-navigation/blocks/search/gnav-search.js b/libs/blocks/global-navigation/blocks/search/gnav-search.js index bf2a2178440..2facaf79fa0 100644 --- a/libs/blocks/global-navigation/blocks/search/gnav-search.js +++ b/libs/blocks/global-navigation/blocks/search/gnav-search.js @@ -281,4 +281,4 @@ class Search { } } -export default { Search }; +export default Search; diff --git a/libs/blocks/global-navigation/global-navigation.css b/libs/blocks/global-navigation/global-navigation.css index a2df65cdb2a..06cc3939b65 100644 --- a/libs/blocks/global-navigation/global-navigation.css +++ b/libs/blocks/global-navigation/global-navigation.css @@ -76,6 +76,11 @@ background-color: var(--feds-background-nav--light); } +[dir = "rtl"] .feds-nav-wrapper { + left: 20px; + right: 0; +} + .global-navigation.is-open .feds-nav-wrapper { display: flex; } @@ -426,6 +431,7 @@ right: 0; justify-content: center; border-bottom: unset; + box-shadow: 0 3px 2px rgb(142 142 142 / 30%); } .feds-breadcrumbs { diff --git a/libs/blocks/global-navigation/global-navigation.js b/libs/blocks/global-navigation/global-navigation.js index f35640e8d79..f26a2a5f57d 100644 --- a/libs/blocks/global-navigation/global-navigation.js +++ b/libs/blocks/global-navigation/global-navigation.js @@ -39,9 +39,8 @@ class Gnav { constructor(body, el) { this.blocks = { profile: { - blockEl: body.querySelector('.profile'), - decoratedEl: toFragment`
    `, - config: {}, + rawElem: body.querySelector('.profile'), + decoratedElem: toFragment`
    `, }, search: { config: { icon: CONFIG.icons.search } }, }; @@ -74,7 +73,7 @@ class Gnav { ${this.decorateBrand()} ${this.elements.navWrapper} - ${this.blocks.profile.blockEl && this.blocks.profile.decoratedEl} + ${this.blocks.profile.rawElem ? this.blocks.profile.decoratedElem : ''} ${this.decorateLogo()} `; @@ -127,22 +126,22 @@ class Gnav { { MenuControls }, decorateDropdown, { appLauncher }, - Profile, - { Search }, + ProfileDropdown, + Search, ] = await Promise.all([ loadBlock('./utilities/delayed-utilities.js'), loadBlock('./blocks/navDropdown/dropdown.js'), loadBlock('./blocks/appLauncher/appLauncher.js'), - loadBlock('./blocks/profile/profile.js'), + loadBlock('./blocks/profile/dropdown.js'), loadBlock('./blocks/search/gnav-search.js'), - loadStyles('./blocks/profile/profile.css'), + loadStyles('./blocks/profile/dropdown.css'), loadStyles('./blocks/navDropdown/dropdown.css'), loadStyles('./blocks/search/gnav-search.css'), ]); this.menuControls = new MenuControls(this.curtain); this.decorateDropdown = decorateDropdown; + this.ProfileDropdown = ProfileDropdown; this.appLauncher = appLauncher; - this.blocks.profile.ProfileClass = Profile; this.Search = Search; resolve(); }); @@ -162,7 +161,10 @@ class Gnav { autoValidateToken: true, environment: env.ims, useLocalStorage: false, - onReady: () => this.decorateProfile(), + onReady: () => { + this.decorateProfile(); + this.decorateAppLauncher(); + }, }; const imsScript = document.querySelector('script[src$="/imslib.min.js"]') instanceof HTMLElement; if (!imsScript && !window.adobeIMS) { @@ -172,57 +174,78 @@ class Gnav { }; decorateProfile = async () => { - const { blockEl, decoratedEl } = this.blocks.profile; - if (!blockEl) return; + const { rawElem, decoratedElem } = this.blocks.profile; + if (!rawElem) return; - const accessToken = window.adobeIMS.getAccessToken(); - const { env } = getConfig(); - this.blocks.profile.profileRes = accessToken - ? await fetch(`https://${env.adobeIO}/profile`, { headers: new Headers({ Authorization: `Bearer ${accessToken.token}` }) }) - : {}; + const isSignedInUser = window.adobeIMS.isSignedInUser(); - if (this.blocks.profile.profileRes.status !== 200) { + // If user is not signed in, decorate the 'Sign In' element + if (!isSignedInUser) { const [decorateSignIn] = await Promise.all([ loadBlock('./blocks/profile/signIn.js'), loadStyles('./blocks/profile/signIn.css'), ]); - const signInEl = await decorateSignIn({ blockEl, decoratedEl }); - decoratedEl.append(signInEl); - window.addEventListener('feds:profileSignIn:clicked', async () => { - await this.loadDelayed(); - this.menuControls.toggleMenu(decoratedEl); - }); + + decorateSignIn({ rawElem, decoratedElem }); + + return; + } + + // If user is signed in, decorate the profile avatar + const accessToken = window.adobeIMS.getAccessToken(); + const { env } = getConfig(); + const headers = new Headers({ Authorization: `Bearer ${accessToken.token}` }); + const profileData = await fetch(`https://${env.adobeIO}/profile`, { headers }); + + if (profileData.status !== 200) { return; } const [ { sections, user: { avatar } }, - { initProfileButton, initProfileMenu }, + decorateButton, ] = await Promise.all([ - this.blocks.profile.profileRes.json(), - loadBlock('./blocks/profile/milo-wrapper.js'), - loadStyles('./blocks/profile/milo-wrapper.css'), + profileData.json(), + loadBlock('./blocks/profile/button.js'), + loadStyles('./blocks/profile/button.css'), ]); - this.blocks.profile.sections = sections; - this.blocks.profile.avatar = avatar; - const profileButtonEl = await initProfileButton(this.blocks.profile); - decoratedEl.append(profileButtonEl); - window.addEventListener('feds:profileButton:clicked', async () => { - if (!this.blocks.profile.menu) { - await this.loadDelayed(); - this.blocks.profile.menu = initProfileMenu(this.blocks.profile); - const appLauncherBlock = this.body.querySelector('.app-launcher'); - if (appLauncherBlock) { - this.appLauncher( - decoratedEl, - appLauncherBlock, - this.menuControls.toggleMenu, - ); - } - } - this.menuControls.toggleMenu(decoratedEl); - }); + this.blocks.profile.buttonElem = await decorateButton({ avatar }); + decoratedElem.append(this.blocks.profile.buttonElem); + + // Decorate the profile dropdown + // after user interacts with button or after 3s have passed + let decorationTimeout; + + const decorateDropdown = async (e) => { + this.blocks.profile.buttonElem.removeEventListener('click', decorateDropdown); + clearTimeout(decorationTimeout); + await this.loadDelayed(); + this.blocks.profile.dropdownInstance = new this.ProfileDropdown({ + rawElem, + decoratedElem, + avatar, + sections, + buttonElem: this.blocks.profile.buttonElem, + // If the dropdown has been decorated due to a click, open it + openOnInit: e instanceof Event, + }); + }; + + this.blocks.profile.buttonElem.addEventListener('click', decorateDropdown); + decorationTimeout = setTimeout(decorateDropdown, 3000); + }; + + decorateAppLauncher = () => { + // TODO: review App Launcher component + // const appLauncherBlock = this.body.querySelector('.app-launcher'); + // if (appLauncherBlock) { + // await this.loadDelayed(); + // this.appLauncher( + // decoratedElem, + // appLauncherBlock, + // ); + // } }; loadSearch = () => {