import {colors, theme} from 'app/client/ui2018/cssVars'; import {UserProfile} from 'app/common/LoginSessionAPI'; import {dom, DomElementArg, styled} from 'grainjs'; import {icon} from 'app/client/ui2018/icons'; export type Size = 'small' | 'medium' | 'large'; interface OrgProperties { name: string; domain: string|null; } /** Helper wrapper around OrgProfile that converts it to UserProfile */ function OrgUser(org: OrgProperties): UserProfile { return {name: org.name, email: org.domain || ''}; } /** * Returns a DOM element showing a circular icon with a user's picture, or the user's initials if * picture is missing. Also varies the color of the circle when using initials. */ export function createUserImage( user: Partial|'exampleUser'|null, size: Size, ...args: DomElementArg[] ): HTMLElement { return cssUserImage( cssUserImage.cls('-' + size), ...(function*() { if (user === 'exampleUser') { yield [cssUserImage.cls('-example'), cssExampleUserIcon('EyeShow')]; } else if (!user || user.anonymous) { yield cssUserImage.cls('-anon'); } else { if (user.picture) { yield cssUserPicture({src: user.picture}, dom.on('error', (ev, el) => dom.hideElem(el, true))); } yield dom.style('background-color', pickColor(user)); const initials = getInitials(user); if (initials.length > 1) { yield cssUserImage.cls('-reduced'); } yield initials; } })(), ...args, ); } /** * Returns a DOM element showing team's initials as a circular icon. */ export function createTeamImage(org: OrgProperties, size: Size, ...args: DomElementArg[]): HTMLElement { return createUserImage(OrgUser(org), size, ...args); } /** * Extracts initials from a user, e.g. a FullUser. E.g. "Foo Bar" is turned into "FB", and * "foo@example.com" into just "f". * * Exported for testing. */ export function getInitials(user: Partial) { const source = (user.name && user.name.trim()) || (user.email && user.email.trim()) || ''; return source.split(/\s+/, 2).map(p => p.slice(0, 1)).join(''); } /** * Hashes the username to return a color. */ function pickColor(user: Partial): string { let c = hashCode(user.name + ':' + user.email) % someColors.length; if (c < 0) { c += someColors.length; } return someColors[c]; } /** * Hash a string into an integer. From https://stackoverflow.com/a/7616484/328565. */ function hashCode(str: string): number { let hash: number = 0; for (let i = 0; i < str.length; i++) { // tslint:disable-next-line:no-bitwise hash = ((hash << 5) - hash + str.charCodeAt(i)) | 0; } return hash; } // These mostly come from https://clrs.cc/ const someColors = [ '#0B437D', '#0074D9', '#7FDBFF', '#39CCCC', '#16DD6D', '#2ECC40', '#16B378', '#EFCC00', '#FF851B', '#FF4136', '#85144b', '#F012BE', '#B10DC9', ]; export const cssUserImage = styled('div', ` position: relative; text-align: center; text-transform: uppercase; user-select: none; -moz-user-select: none; color: white; border-radius: 100px; display: flex; align-items: center; justify-content: center; --border-size: 0px; width: calc(var(--icon-size, 24px) - var(--border-size)); height: calc(var(--icon-size, 24px) - var(--border-size)); line-height: 1em; &-small { --icon-size: 24px; font-size: 13.5px; --reduced-font-size: 12px; } &-medium { --icon-size: 32px; font-size: 18px; --reduced-font-size: 16px; } &-border { --border-size: 2px; } &-large { --icon-size: 40px; font-size: 22.5px; --reduced-font-size: 20px; } &-anon { border: 1px solid ${colors.slate}; color: ${colors.slate}; } &-anon::before { content: "?" } &-reduced { font-size: var(--reduced-font-size); } &-square { border-radius: 0px; } &-example { background-color: ${colors.slate}; border: 1px solid ${colors.slate}; } `); const cssUserPicture = styled('img', ` position: absolute; width: 100%; height: 100%; object-fit: cover; background-color: ${theme.menuBg}; border-radius: inherit; box-sizing: content-box; /* keep the border outside of the size of the image */ `); const cssExampleUserIcon = styled(icon, ` background-color: white; width: 45px; height: 45px; transform: scaleY(0.75); `);