/**
 * -------------------------------------------------------------------------------------------
 * Copyright (c) Microsoft Corporation.  All Rights Reserved.  Licensed under the MIT License.
 * See License in the project root for license information.
 * -------------------------------------------------------------------------------------------
 */ import { customElement , html , internalProperty , property , TemplateResult } from 'lit-element' ; import { classMap } from 'lit-html/directives/class-map' ; import { MgtTemplatedComponent , Providers , ProviderState , TeamsHelper } from '@microsoft/mgt-element' ; import { Presence , User , Person } from '@microsoft/microsoft-graph-types' ; import { findPeople , getEmailFromGraphEntity } from '../../graph/graph.people' ; import { IDynamicPerson , ViewType } from '../../graph/types' ; import { getPersonImage } from '../../graph/graph.photos' ; import { getUserWithPhoto } from '../../graph/graph.userWithPhoto' ; import { getSvg , SvgIcon } from '../../utils/SvgHelper' ; import { getUserPresence } from '../../graph/graph.presence' ; import { getPersonCardGraphData } from './mgt-person-card.graph' ; import { MgtPerson } from '../mgt-person/mgt-person' ; import { styles } from './mgt-person-card-css' ; import { BasePersonCardSection } from './sections/BasePersonCardSection' ; import { MgtPersonCardContact } from './sections/mgt-person-card-contact/mgt-person-card-contact' ; import { MgtPersonCardFiles } from './sections/mgt-person-card-files/mgt-person-card-files' ; import { MgtPersonCardMessages } from './sections/mgt-person-card-messages/mgt-person-card-messages' ; import { MgtPersonCardOrganization } from './sections/mgt-person-card-organization/mgt-person-card-organization' ; import { MgtPersonCardProfile } from './sections/mgt-person-card-profile/mgt-person-card-profile' ; import { MgtPersonCardConfig , MgtPersonCardState } from './mgt-person-card.types' ; import { strings } from './strings' ; import '../sub-components/mgt-spinner/mgt-spinner' ; export * from './mgt-person-card.types' ; interface MgtPersonCardStateHistory { state : MgtPersonCardState ; personDetails : IDynamicPerson ; personImage : string ; } export class extends MgtTemplatedComponent fires cssprop cssprop cssprop cssprop cssprop cssprop cssprop cssprop cssprop cssprop cssprop @ customElement ( 'mgt-person-card' ) export class MgtPersonCard extends MgtTemplatedComponent { /**
     * Array of styles to apply to the element. The styles should be defined
     * using the `css` tag function.
     */ static get styles ( ) { return styles ; } protected get strings ( ) { return strings ; } static return { * } memberof public static get requiredScopes ( ) : string [ ] { return MgtPersonCard . getScopes ( ) ; } public static getScopes ( ) { const scopes = [ ] ; if ( this . config . sections . files ) { scopes . push ( 'Sites.Read.All' ) ; } if ( this . config . sections . mailMessages ) { scopes . push ( 'Mail.ReadBasic' ) ; } if ( this . config . sections . organization ) { scopes . push ( 'User.Read.All' ) ; if ( typeof this . config . sections . organization !== 'boolean' && this . config . sections . organization . showWorksWith ) { scopes . push ( 'People.Read.All' ) ; } } if ( this . config . sections . profile ) { scopes . push ( 'User.Read.All' ) ; } if ( this . config . useContactApis ) { scopes . push ( 'Contacts.Read' ) ; } if ( scopes . indexOf ( 'User.Read.All' ) < 0 ) { scopes . push ( 'User.ReadBasic.All' ) ; scopes . push ( 'User.Read' ) ; } if ( scopes . indexOf ( 'People.Read.All' ) < 0 ) { scopes . push ( 'People.Read' ) ; } return [ ... new Set ( scopes ) ] ; } static type { MgtPersonCardConfig } memberof public static get config ( ) { return this . _config ; } private static _config : MgtPersonCardConfig = { sections : { files : true , mailMessages : true , organization : { showWorksWith : true } , profile : true } , useContactApis : true } ; type { IDynamicPerson } memberof @ property ( { attribute : 'person-details' , type : Object } ) public get personDetails ( ) : IDynamicPerson { return this . _personDetails ; } public set personDetails ( value ) { if ( this . _personDetails === value ) { return ; } this . _personDetails = value ; this . personImage = this . getImage ( ) ; this . requestStateUpdate ( ) ; } type { string } @ property ( { attribute : 'person-query' } ) public personQuery : string ; type { string } @ property ( { attribute : 'user-id' } ) public get userId ( ) : string { return this . _userId ; } public set userId ( value ) { if ( value === this . _userId ) { return ; } this . _userId = value ; this . personDetails = null ; this . state = null ; this . requestStateUpdate ( ) ; } type { string } memberof @ property ( { attribute : 'person-image' , type : String } ) public personImage : string ; type { boolean } memberof @ property ( { attribute : 'fetch-image' , type : Boolean } ) public fetchImage : boolean ; type { boolean } memberof @ property ( { attribute : 'is-expanded' , type : Boolean } ) public isExpanded : boolean ; type { boolean } memberof @ property ( { attribute : 'inherit-details' , type : Boolean } ) public inheritDetails : boolean ; type { boolean } @ property ( { attribute : 'show-presence' , type : Boolean } ) public showPresence : boolean ; type { MicrosoftGraph . Presence } memberof @ property ( { attribute : 'person-presence' , type : Object } ) public personPresence : Presence ; protected type { BasePersonCardSection [ ] } memberof protected sections : BasePersonCardSection [ ] ; @ internalProperty ( ) private state : MgtPersonCardState ; @ internalProperty ( ) private isStateLoading : boolean ; private _history : MgtPersonCardStateHistory [ ] ; private _chatInput : string ; private _currentSection : BasePersonCardSection ; private _personDetails : IDynamicPerson ; private _me : User ; private _userId : string ; private get internalPersonDetails ( ) : IDynamicPerson { return ( this . state && this . state . person ) || this . personDetails ; } constructor ( ) { super ( ) ; this . _chatInput = '' ; this . _currentSection = null ; this . _history = [ ] ; this . sections = [ ] ; } param { * } name param { * } oldValue param { * } newValue memberof public attributeChangedCallback ( name , oldValue , newValue ) { super . attributeChangedCallback ( name , oldValue , newValue ) ; if ( oldValue === newValue ) { return ; } switch ( name ) { case 'person-query' : this . personDetails = null ; this . state = null ; this . requestStateUpdate ( ) ; break ; } } protected memberof public navigate ( person ) { this . _history . push ( { personDetails : this . personDetails , personImage : this . getImage ( ) , state : this . state } ) ; this . _personDetails = person ; this . state = null ; this . personImage = null ; this . _currentSection = null ; this . sections = [ ] ; this . requestStateUpdate ( ) ; } returns { void } memberof public goBack ( ) { if ( ! this . _history || ! this . _history . length ) { return ; } const historyState = this . _history . pop ( ) ; this . _currentSection = null ; this . state = historyState . state ; this . _personDetails = historyState . state ; this . personImage = historyState . personImage ; this . loadSections ( ) ; } returns { void } memberof public clearHistory ( ) { this . _currentSection = null ; if ( ! this . _history || ! this . _history . length ) { return ; } const historyState = this . _history [ 0 ] ; this . _history = [ ] ; this . state = historyState . state ; this . _personDetails = historyState . state ; this . personImage = historyState . personImage ; this . loadSections ( ) ; } /**
     * Invoked on each update to perform rendering tasks. This method must return
     * a lit-html TemplateResult. Setting properties inside this method will *not*
     * trigger the element to update.
     */ protected render ( ) { if ( ! this . internalPersonDetails ) { return this . renderNoData ( ) ; } const person = this . internalPersonDetails ; const image = this . getImage ( ) ; if ( this . hasTemplate ( 'default' ) ) { return this . renderTemplate ( 'default' , { person : this . internalPersonDetails , personImage : image } ) ; } const navigationTemplate = this . _history && this . _history . length ? html `
            <div class="nav">
              <div class="nav__back" @click=${ ( ) => this . goBack ( ) }>${ getSvg ( SvgIcon . Back ) }</div>
            </div>
          ` : null ; let personDetailsTemplate = this . renderTemplate ( 'person-details' , { person : this . internalPersonDetails , personImage : image } ) ; if ( ! personDetailsTemplate ) { const personTemplate = this . renderPerson ( ) ; const contactIconsTemplate = this . renderContactIcons ( person ) ; personDetailsTemplate = html `
        ${ personTemplate } ${ contactIconsTemplate }
      ` ; } const expandedDetailsTemplate = this . isExpanded ? this . renderExpandedDetails ( ) : this . renderExpandedDetailsButton ( ) ; return html `
      <div class="root" dir=${ this . direction }>
        ${ navigationTemplate }
        <div class="person-details-container">${ personDetailsTemplate }</div>
        <div class="expanded-details-container">${ expandedDetailsTemplate }</div>
      </div>
    ` ; } protected returns { TemplateResult } memberof protected renderNoData ( ) { return this . renderTemplate ( 'no-data' , null ) || html `` ; } protected param { * } image memberof protected renderPerson ( ) { const avatarSize = 'large' ; return html `
      <mgt-person
        class="person-image"
        .personDetails=${ this . internalPersonDetails }
        .personImage=${ this . getImage ( ) }
        .personPresence=${ this . personPresence }
        .showPresence=${ this . showPresence }
        .avatarSize=${ avatarSize }
        .view=${ ViewType . threelines }
        .line2Property=${ 'jobTitle' }
        .line3Property=${ 'department' }
      ></mgt-person>
    ` ; } protected param { IDynamicPerson } person returns { TemplateResult } memberof protected renderPersonSubtitle ( person ? ) { person = person || this . internalPersonDetails ; if ( ! person . department ) { return ; } return html `
      <div class="department">${ person . department }</div>
    ` ; } protected returns { TemplateResult } memberof protected renderContactIcons ( person ? ) { if ( this . isExpanded ) { return ; } person = person || this . internalPersonDetails ; const userPerson = person as User ; let email ; if ( getEmailFromGraphEntity ( person ) ) { email = html `
        <div class="icon" @click=${ ( ) => this . emailUser ( ) } tabindex=0>
          ${ getSvg ( SvgIcon . SmallEmail ) }
          <span>${ this . strings . sendEmailLinkSubtitle }</span>
        </div>
      ` ; } let chat ; if ( userPerson . userPrincipalName ) { chat = html `
        <div class="icon" @click=${ ( ) => this . chatUser ( ) } tabindex=0>
          ${ getSvg ( SvgIcon . SmallChat ) }
          <span>${ this . strings . startChatLinkSubtitle }</span>
        </div>
      ` ; } return html `
      <div class="base-icons">
        ${ email } ${ chat }
      </div>
    ` ; } protected returns { TemplateResult } memberof protected renderExpandedDetailsButton ( ) { return html `
      <div class="expanded-details-button" @click=${ ( ) => this . showExpandedDetails ( ) } @keydown=${ this . handleKeyDown } @ tabindex=0>
        ${ getSvg ( SvgIcon . ExpandDown ) }
      </div>
    ` ; } protected param { IDynamicPerson } person returns { TemplateResult } memberof protected renderExpandedDetails ( person ? ) { if ( ! this . state && this . isStateLoading ) { return html `
        <div class="loading">
          <mgt-spinner></mgt-spinner>
        </div>
      ` ; } const provider = Providers . globalProvider ; if ( provider . state === ProviderState . SignedOut ) { this . loadSections ( ) ; } person = person || this . internalPersonDetails ; const sectionNavTemplate = this . renderSectionNavigation ( ) ; const currentSectionTemplate = this . renderCurrentSection ( ) ; return html `
      <div class="section-nav">
        ${ sectionNavTemplate }
      </div>
      <div class="section-host" @wheel=${ ( e ) => this . handleSectionScroll ( e ) } tabindex=0>
        ${ currentSectionTemplate }
      </div>
    ` ; } protected returns { TemplateResult } memberof protected renderSectionNavigation ( ) { if ( ! this . sections || ( this . sections . length < 2 && ! this . hasTemplate ( 'additional-details' ) ) ) { return ; } const currentSectionIndex = this . _currentSection ? this . sections . indexOf ( this . _currentSection ) : - 1 ; const navIcons = this . sections . map ( ( section , i , a ) => { const classes = classMap ( { active : i === currentSectionIndex , 'section-nav__icon' : true } ) ; return html `
        <button tabindex=0 class=${ classes } @click=${ ( ) => this . updateCurrentSection ( section ) }>${ section . renderIcon ( ) }</button>
      ` ; } ) ; const overviewClasses = classMap ( { active : currentSectionIndex === - 1 , 'section-nav__icon' : true } ) ; return html `
      <button tabindex=0 class=${ overviewClasses } @click=${ ( ) => this . updateCurrentSection ( null ) }>
        ${ getSvg ( SvgIcon . Overview ) }
      </button>
      ${ navIcons }
    ` ; } protected returns { TemplateResult } memberof protected renderOverviewSection ( ) { const compactTemplates = this . sections . map ( section => html `
        <div class="section">
          <div class="section__header">
            <div class="section__title">${ section . displayName }</div>
            <a class="section__show-more" tabindex=0 @keydown=${ e => e . keyCode === 13 ? this . updateCurrentSection ( section ) : '' } @click=${ ( ) => this . updateCurrentSection ( section ) }
              >${ this . strings . showMoreSectionButton }</a
            >
          </div>
          <div class="section__content">${ section . asCompactView ( ) }</div>
        </div>
      ` ) ; const additionalDetails = this . renderTemplate ( 'additional-details' , { person : this . internalPersonDetails , personImage : this . getImage ( ) , state : this . state } ) ; if ( additionalDetails ) { compactTemplates . splice ( 1 , 0 , html `
          <div class="section">
            <div class="additional-details">${ additionalDetails }</div>
          </div>
        ` ) ; } return html `
      <div class="sections">
        ${ compactTemplates }
      </div>
    ` ; } protected returns { TemplateResult } memberof protected renderCurrentSection ( ) { if ( ( ! this . sections || ! this . sections . length ) && ! this . hasTemplate ( 'additional-details' ) ) { return ; } if ( this . sections . length === 1 && ! this . hasTemplate ( 'additional-details' ) ) { return html `
        ${ this . sections [ 0 ] . asFullView ( ) }
      ` ; } if ( ! this . _currentSection ) { return this . renderOverviewSection ( ) ; } return html `
      ${ this . _currentSection . asFullView ( ) }
    ` ; } protected returns memberof protected async loadState ( ) { if ( this . state ) { return ; } if ( ! this . personDetails && this . inheritDetails ) { let parent = this . parentElement ; while ( parent && parent . tagName !== 'MGT-PERSON' ) { parent = parent . parentElement ; } if ( parent && ( parent as MgtPerson ) . personDetails ) { this . personDetails = ( parent as MgtPerson ) . personDetails ; this . personImage = ( parent as MgtPerson ) . personImage ; } } const provider = Providers . globalProvider ; if ( ! provider || provider . state !== ProviderState . SignedIn ) { return ; } const graph = provider . graph . forComponent ( this ) ; this . isStateLoading = true ; if ( ! this . _me ) { this . _me = await Providers . me ( ) ; } if ( this . personDetails ) { const user = this . personDetails as User ; const id = user . userPrincipalName || user . id ; if ( id && ! getEmailFromGraphEntity ( user ) ) { const person = await getUserWithPhoto ( graph , id ) ; this . personDetails = person ; this . personImage = this . getImage ( ) ; } } else if ( this . userId || this . personQuery === 'me' ) { const person = await getUserWithPhoto ( graph , this . userId ) ; this . personDetails = person ; this . personImage = this . getImage ( ) ; } else if ( this . personQuery ) { const people = await findPeople ( graph , this . personQuery , 1 ) ; if ( people && people . length ) { this . personDetails = people [ 0 ] ; getPersonImage ( graph , this . personDetails , MgtPersonCard . config . useContactApis ) . then ( image => { if ( image ) { this . personDetails . personImage = image ; this . personImage = image ; } } ) ; } } const defaultPresence = { activity : 'Offline' , availability : 'Offline' , id : null } ; if ( ! this . personPresence && this . showPresence ) { try { if ( this . personDetails && this . personDetails . id ) { getUserPresence ( graph , this . personDetails . id ) . then ( presence => { this . personPresence = presence ; } ) ; } else { this . personPresence = defaultPresence ; } } catch ( _ ) { this . personPresence = defaultPresence ; } } if ( this . personDetails ?. id ) { this . state = await getPersonCardGraphData ( graph , this . personDetails , this . _me === this . personDetails . id , MgtPersonCard . config ) ; } this . loadSections ( ) ; this . isStateLoading = false ; } protected returns { void } memberof protected sendQuickMessage ( ) { const message = this . _chatInput . trim ( ) ; if ( ! message || ! message . length ) { return ; } this . chatUser ( message ) ; } protected memberof protected emailUser ( ) { const user = this . internalPersonDetails ; if ( user ) { const email = getEmailFromGraphEntity ( user ) ; if ( email ) { window . open ( 'mailto:' + email , '_blank' ) ; } } } protected memberof protected callUser ( ) { const user = this . personDetails as User ; const person = this . personDetails as microsoftgraph . Person ; if ( user && user . businessPhones && user . businessPhones . length ) { const phone = user . businessPhones [ 0 ] ; if ( phone ) { window . open ( 'tel:' + phone , '_blank' ) ; } } else if ( person && person . phones && person . phones . length ) { const businessPhones = this . getPersonBusinessPhones ( person ) ; const phone = businessPhones [ 0 ] ; if ( phone ) { window . open ( 'tel:' + phone , '_blank' ) ; } } } protected memberof protected chatUser ( message = null ) { const user = this . personDetails as User ; if ( user && user . userPrincipalName ) { const users = user . userPrincipalName ; let url = `https://teams.microsoft.com/l/chat/0/0?users=${ users }` ; if ( message && message . length ) { url += `&message=${ message }` ; } const openWindow = ( ) => window . open ( url , '_blank' ) ; if ( TeamsHelper . isAvailable ) { TeamsHelper . executeDeepLink ( url , ( status ) => { if ( ! status ) { openWindow ( ) ; } } ) ; } else { openWindow ( ) ; } } } protected memberof protected showExpandedDetails ( ) { const root = this . renderRoot . querySelector ( '.root' ) ; if ( root && root . animate ) { root . animate ( [ { height : 'auto' , transformOrigin : 'top left' } , { height : 'auto' , transformOrigin : 'top left' } ] , { duration : 1000 , easing : 'ease-in-out' , fill : 'both' } ) ; } this . isExpanded = true ; this . fireCustomEvent ( 'expanded' , null , true ) ; } private loadSections ( ) { this . sections = [ ] ; if ( ! this . internalPersonDetails ) { return ; } const contactSections = new MgtPersonCardContact ( this . internalPersonDetails as User ) ; if ( contactSections . hasData ) { this . sections . push ( contactSections ) ; } if ( ! this . state ) { return ; } const { person , directReports , messages , files , profile } = this . state ; if ( MgtPersonCard . config . sections . organization && ( ( person && person . manager ) || ( directReports && directReports . length ) ) ) { this . sections . push ( new MgtPersonCardOrganization ( this . state , this . _me ) ) ; } if ( MgtPersonCard . config . sections . mailMessages && messages && messages . length ) { this . sections . push ( new MgtPersonCardMessages ( messages ) ) ; } if ( MgtPersonCard . config . sections . files && files && files . length ) { this . sections . push ( new MgtPersonCardFiles ( files ) ) ; } if ( MgtPersonCard . config . sections . profile && profile ) { const profileSection = new MgtPersonCardProfile ( profile ) ; if ( profileSection . hasData ) { this . sections . push ( profileSection ) ; } } } private getImage ( ) { if ( this . personImage ) { return this . personImage ; } const person = this . personDetails ; return person && person . personImage ? person . personImage : null ; } private getPersonBusinessPhones ( person ) { const phones = person . phones ; const businessPhones = [ ] ; for ( const p of phones ) { if ( p . type === 'business' ) { businessPhones . push ( p . number ) ; } } return businessPhones ; } private updateCurrentSection ( section ) { const sectionHost = this . renderRoot . querySelector ( '.section-host' ) ; sectionHost . scrollTop = 0 ; this . _currentSection = section ; this . requestUpdate ( ) ; } private handleSectionScroll ( e ) { const target = this . renderRoot . querySelector ( '.section-host' ) as HTMLElement ; if ( target ) { if ( ! ( e . deltaY < 0 && target . scrollTop === 0 ) && ! ( e . deltaY > 0 && target . clientHeight + target . scrollTop >= target . scrollHeight - 1 ) ) { e . stopPropagation ( ) ; } } } private handleKeyDown ( e ) { if ( e ) { if ( e . keyCode === 13 ) { this . showExpandedDetails ( ) ; } } } }