UBER-486: updated people avatars. (#3720)

Signed-off-by: Alexander Platov <sas_lord@mail.ru>
This commit is contained in:
Alexander Platov
2023-09-21 00:01:09 +07:00
committed by GitHub
parent d9d47846cf
commit a07f88033f
43 changed files with 322 additions and 111 deletions
+1
View File
@@ -947,6 +947,7 @@ a.no-line {
.content-color { color: var(--theme-content-color); }
.caption-color { color: var(--theme-caption-color); }
.content-accented-color { color: var(--accented-button-color); }
.red-color { color: var(--highlight-red); }
.error-color { color: var(--theme-error-color); }
+9 -4
View File
@@ -194,7 +194,9 @@
&.accented, &.brand, &.positive, &.negative {
&:hover, &:active, &:focus {
color: var(--accented-button-color);
.btn-icon { color: var(--accented-button-color); }
.btn-icon,
.btn-right-icon { color: var(--accented-button-color); }
}
}
&.regular, &.ghost {
@@ -208,7 +210,8 @@
color: var(--accented-button-content-color);
border-color: var(--accented-button-border);
.btn-icon { color: var(--accented-button-content-color); }
.btn-icon,
.btn-right-icon { color: var(--accented-button-content-color); }
}
&.accented {
background-color: var(--accented-button-default);
@@ -257,7 +260,8 @@
background-color: var(--theme-button-contrast-enabled);
border-color: var(--theme-button-contrast-border);
.btn-icon { color: var(--theme-button-contrast-color); }
.btn-icon,
.btn-right-icon { color: var(--theme-button-contrast-color); }
&:hover { background-color: var(--theme-button-contrast-hovered); }
&:active { background-color: var(--theme-button-contrast-pressed); }
@@ -298,7 +302,8 @@
border-color: transparent;
cursor: not-allowed;
.btn-icon { opacity: .5; }
.btn-icon,
.btn-right-icon { opacity: .5; }
}
.resetIconSize { font-size: 16px; }
@@ -93,7 +93,10 @@
/>
</span>
<svelte:fragment slot="iconRight">
<DropdownIcon size={'small'} fill={'var(--theme-dark-color)'} />
<DropdownIcon
size={'small'}
fill={kind === 'accented' && !disabled ? 'var(--accented-button-content-color)' : 'var(--theme-dark-color)'}
/>
</svelte:fragment>
</Button>
</div>
@@ -191,7 +191,10 @@
{#if showIcon}
{#if withAvatar}
<div class="msgactivity-avatar">
<Component is={contact.component.Avatar} props={{ avatar: person?.avatar, size: 'medium' }} />
<Component
is={contact.component.Avatar}
props={{ avatar: person?.avatar, size: 'medium', name: person?.name }}
/>
</div>
{:else}
<div class="msgactivity-icon">
@@ -45,7 +45,7 @@
on:click={() => onClick(p)}
>
<div class="icon">
<Avatar size={'x-small'} avatar={p.avatar} />
<Avatar size={'x-small'} avatar={p.avatar} name={p.name} />
</div>
</div>
{/each}
@@ -28,7 +28,7 @@
</script>
<div class="flex-nowrap">
<div class="avatar"><Avatar size={'medium'} /></div>
<div class="avatar"><Avatar size={'medium'} avatar={user.avatar} name={user.name} /></div>
<div class="flex-col-stretch message">
<div class="header">{getName(client.getHierarchy(), user)}<span>{message.createDate}</span></div>
<div class="text">{message.text}</div>
@@ -74,7 +74,7 @@
<div class="flex-row-top">
{#await getEmployee(value, $personByIdStore, $personAccountByIdStore) then employee}
<div class="avatar">
<Avatar size={'medium'} avatar={employee?.avatar} />
<Avatar size={'medium'} avatar={employee?.avatar} name={employee?.name} />
</div>
<div class="flex-grow flex-col select-text">
<div class="header">
@@ -204,7 +204,9 @@
</script>
<div class="container clear-mins" class:highlighted={isHighlighted} id={message._id}>
<div class="avatar"><Avatar size={'medium'} avatar={employee?.avatar} /></div>
<div class="avatar">
<Avatar size={'medium'} avatar={employee?.avatar} name={employee?.name} />
</div>
<div class="message clear-mins">
<div class="header clear-mins">
{#if employee}
@@ -52,7 +52,7 @@
<div class="message">
<div class="header">
<div class="avatar">
<Avatar size={'medium'} avatar={employee?.avatar} />
<Avatar size={'medium'} avatar={employee?.avatar} name={employee?.name} />
</div>
<span class="name">
{employee ? getName(client.getHierarchy(), employee) : ''}
@@ -59,7 +59,7 @@
<div class="flex-row-center container cursor-pointer" on:click>
<div class="flex-row-center">
{#each showReplies as reply}
<div class="reply"><Avatar size={'x-small'} avatar={reply.avatar} /></div>
<div class="reply"><Avatar size={'x-small'} avatar={reply.avatar} name={reply.name} /></div>
{/each}
{#if employees.size > shown}
<div class="reply"><span>+{employees.size - shown}</span></div>
@@ -27,7 +27,13 @@
</script>
<script lang="ts">
import contact, { AvatarProvider, AvatarType } from '@hcengineering/contact'
import contact, {
AvatarProvider,
AvatarType,
getAvatarColorForId,
getFirstName,
getLastName
} from '@hcengineering/contact'
import { Client, Ref } from '@hcengineering/core'
import { Asset, getResource } from '@hcengineering/platform'
import { getBlobURL, getClient } from '@hcengineering/presentation'
@@ -36,14 +42,21 @@
import AvatarIcon from './icons/Avatar.svelte'
export let avatar: string | null | undefined = undefined
export let name: string | null | undefined = undefined
export let direct: Blob | undefined = undefined
export let size: IconSize
export let icon: Asset | AnySvelteComponent | undefined = undefined
let url: string[] | undefined
let avatarProvider: AvatarProvider | undefined
let color: string | undefined = undefined
async function update (size: IconSize, avatar?: string | null, direct?: Blob) {
$: fname = getFirstName(name ?? '')
$: lname = getLastName(name ?? '')
$: displayName =
name != null ? (lname.length > 1 ? lname.trim()[0] : lname) + (fname.length > 1 ? fname.trim()[0] : fname) : ''
async function update (size: IconSize, avatar?: string | null, direct?: Blob, name?: string | null) {
if (direct !== undefined) {
getBlobURL(direct).then((blobURL) => {
url = [blobURL]
@@ -55,32 +68,46 @@
if (!avatarProvider || avatarProvider.type === AvatarType.COLOR) {
url = undefined
color = avatar.split('://')[1]
} else if (avatarProvider?.type === AvatarType.IMAGE) {
url = (await getResource(avatarProvider.getUrl))(avatar, size)
} else {
const uri = avatar.split('://')[1]
url = (await getResource(avatarProvider.getUrl))(uri, size)
}
} else if (name != null) {
color = getAvatarColorForId(name)
url = undefined
avatarProvider = undefined
} else {
url = undefined
avatarProvider = undefined
}
}
$: update(size, avatar, direct)
$: update(size, avatar, direct, name)
let imageElement: HTMLImageElement | undefined = undefined
$: srcset = url?.slice(1)?.join(', ')
</script>
<div class="ava-{size} flex-center avatar-container" class:no-img={!url}>
<div
class="ava-{size} flex-center avatar-container"
class:no-img={!url && color}
class:bordered={!url && color === undefined}
style:background-color={url ? 'var(--theme-button-default)' : color}
>
{#if url}
{#if size === 'large' || size === 'x-large' || size === '2x-large'}
<img class="ava-{size} ava-blur" src={url[0]} {srcset} alt={''} bind:this={imageElement} />
{/if}
<img class="ava-{size} ava-mask" src={url[0]} {srcset} alt={''} bind:this={imageElement} />
{:else if name && displayName && displayName !== ''}
<div class="ava-text" data-name={displayName.toLocaleUpperCase()} />
{:else}
<Icon icon={icon ?? AvatarIcon} size={size === 'card' ? 'x-small' : size} />
<div class="icon">
<Icon icon={icon ?? AvatarIcon} size={'full'} />
</div>
{/if}
</div>
@@ -89,60 +116,139 @@
flex-shrink: 0;
position: relative;
overflow: hidden;
background-color: var(--avatar-bg-color);
background-color: var(--theme-button-default);
border-radius: 50%;
pointer-events: none;
&.no-img {
color: var(--accented-button-color);
border-color: transparent;
}
&.bordered {
color: var(--theme-dark-color);
border: 1px solid var(--theme-button-border);
}
img {
object-fit: cover;
border: 1px solid var(--avatar-border-color);
}
&.no-img {
border-color: transparent;
.icon,
.ava-text::after {
position: absolute;
top: 50%;
left: 50%;
}
.icon {
width: 100%;
height: 100%;
color: inherit;
transform-origin: center;
transform: translate(-50%, -50%) scale(0.6);
}
.ava-text::after {
content: attr(data-name);
transform: translate(-50%, -50%);
}
}
.ava-inline {
width: 0.875rem; // 24
height: 0.875rem;
.ava-text {
font-weight: 500;
font-size: 0.525rem;
letter-spacing: -0.05em;
}
}
.ava-tiny {
width: 1.13rem; // ~18
height: 1.13rem;
.ava-text {
font-weight: 500;
font-size: 0.625rem;
letter-spacing: -0.05em;
}
}
.ava-card {
width: 1.25rem; // 20
height: 1.25rem;
.ava-text {
font-weight: 500;
font-size: 0.625rem;
letter-spacing: -0.05em;
}
}
.ava-x-small {
width: 1.5rem; // 24
height: 1.5rem;
.ava-text {
font-weight: 500;
font-size: 0.75rem;
letter-spacing: -0.05em;
}
}
.ava-smaller {
width: 1.75rem; // 28
height: 1.75rem;
.ava-text {
font-weight: 500;
font-size: 0.8125rem;
letter-spacing: -0.05em;
}
}
.ava-small {
width: 2rem; // 32
height: 2rem;
.ava-text {
font-weight: 500;
font-size: 0.875rem;
letter-spacing: -0.05em;
}
}
.ava-medium {
width: 2.25rem; // 36
height: 2.25rem;
.ava-text {
font-weight: 500;
font-size: 0.875rem;
letter-spacing: -0.05em;
}
}
.ava-large {
width: 4.5rem; // 72
height: 4.5rem;
.ava-text {
font-weight: 500;
font-size: 2rem;
}
}
.ava-x-large {
width: 7.5rem; // 120
height: 7.5rem;
.ava-text {
font-weight: 500;
font-size: 3.5rem;
}
}
.ava-2x-large {
width: 10rem; // 120
height: 10rem;
.ava-text {
font-weight: 500;
font-size: 4.75rem;
}
}
.ava-blur {
@@ -56,7 +56,7 @@
{/if}
{#each persons as person, i}
<div class="combine-avatar {size}" data-over={getDataOver(persons.length === i + 1, items)}>
<Avatar avatar={person.avatar} {size} />
<Avatar avatar={person.avatar} {size} name={person.name} />
</div>
{/each}
</div>
@@ -169,7 +169,13 @@
<slot name="extraControls" />
</div>
<div class="ml-4">
<EditableAvatar avatar={object.avatar} {email} {id} size={'large'} bind:this={avatarEditor} />
<EditableAvatar
avatar={object.avatar}
name={combineName(firstName, lastName)}
{email}
size={'large'}
bind:this={avatarEditor}
/>
</div>
</div>
<svelte:fragment slot="pool">
@@ -115,7 +115,12 @@
</div>
</div>
<div class="ml-4">
<EditableAvatar avatar={object.avatar} {id} size={'large'} bind:this={avatarEditor} />
<EditableAvatar
avatar={object.avatar}
name={combineName(firstName, lastName)}
size={'large'}
bind:this={avatarEditor}
/>
</div>
</div>
<svelte:fragment slot="pool">
@@ -110,13 +110,13 @@
<EditableAvatar
avatar={object.avatar}
{email}
id={object._id}
size={'x-large'}
name={object.name}
bind:this={avatarEditor}
on:done={onAvatarDone}
/>
{:else}
<Avatar avatar={object.avatar} size={'x-large'} />
<Avatar avatar={object.avatar} size={'x-large'} name={object.name} />
{/if}
{/key}
</div>
@@ -85,8 +85,8 @@
{#key object}
<EditableAvatar
avatar={object.avatar}
id={object._id}
size={'x-large'}
name={object.name}
bind:this={avatarEditor}
on:done={onAvatarDone}
/>
@@ -131,7 +131,7 @@
.name {
font-weight: 500;
font-size: 1.25rem;
color: var(--caption-color);
color: var(--theme-caption-color);
}
.location {
margin-top: 0.25rem;
@@ -141,6 +141,6 @@
.separator {
margin: 1rem 0;
height: 1px;
background-color: var(--divider-color);
background-color: var(--theme-divider-color);
}
</style>
@@ -16,24 +16,33 @@
import { createEventDispatcher } from 'svelte'
import attachment from '@hcengineering/attachment'
import { AnySvelteComponent, IconSize, showPopup } from '@hcengineering/ui'
import { AvatarType } from '@hcengineering/contact'
import { AvatarType, getAvatarColorForId } from '@hcengineering/contact'
import { Asset, getResource } from '@hcengineering/platform'
import AvatarComponent from './Avatar.svelte'
import SelectAvatarPopup from './SelectAvatarPopup.svelte'
export let avatar: string | null | undefined
export let name: string | null | undefined = undefined
export let email: string | undefined = undefined
export let id: string
export let size: IconSize
export let direct: Blob | undefined = undefined
export let icon: Asset | AnySvelteComponent | undefined = undefined
export let disabled: boolean = false
const [schema, uri] = avatar?.split('://') || []
$: [schema, uri] = avatar?.split('://') || []
let selectedAvatarType: AvatarType | undefined = avatar?.includes('://') ? (schema as AvatarType) : AvatarType.IMAGE
let selectedAvatar: string | null | undefined = selectedAvatarType === AvatarType.IMAGE ? avatar : uri
let selectedAvatarType: AvatarType | undefined
let selectedAvatar: string | null | undefined
$: selectedAvatarType = avatar?.includes('://')
? (schema as AvatarType)
: avatar === undefined
? AvatarType.COLOR
: AvatarType.IMAGE
$: selectedAvatar = selectedAvatarType === AvatarType.IMAGE ? avatar : uri
$: if (selectedAvatar === undefined && selectedAvatarType === AvatarType.COLOR) {
selectedAvatar = getAvatarColorForId(name)
}
export async function createAvatar (): Promise<string | undefined> {
if (selectedAvatarType === AvatarType.IMAGE && direct !== undefined) {
@@ -58,13 +67,26 @@
selectedAvatarType = submittedAvatarType
selectedAvatar = submittedAvatar
direct = submittedDirect
avatar = selectedAvatarType === AvatarType.IMAGE ? selectedAvatar : `${selectedAvatarType}://${selectedAvatar}`
dispatch('done')
}
const dispatch = createEventDispatcher()
async function showSelectionPopup (e: MouseEvent) {
if (!disabled) {
showPopup(SelectAvatarPopup, { avatar, email, id, file: direct, icon, onSubmit: handlePopupSubmit })
showPopup(SelectAvatarPopup, {
avatar:
selectedAvatarType === AvatarType.IMAGE
? selectedAvatar
: selectedAvatarType === AvatarType.COLOR && avatar == null
? undefined
: `${selectedAvatarType}://${selectedAvatar}`,
email,
name,
file: direct,
icon,
onSubmit: handlePopupSubmit
})
}
}
</script>
@@ -72,9 +94,14 @@
<!-- svelte-ignore a11y-click-events-have-key-events -->
<div class="cursor-pointer" on:click|self={showSelectionPopup}>
<AvatarComponent
avatar={selectedAvatarType === AvatarType.IMAGE ? selectedAvatar : `${selectedAvatarType}://${selectedAvatar}`}
{direct}
{size}
{icon}
avatar={selectedAvatarType === AvatarType.IMAGE
? selectedAvatar
: selectedAvatarType === AvatarType.COLOR && avatar == null
? undefined
: `${selectedAvatarType}://${selectedAvatar}`}
{name}
/>
</div>
@@ -58,7 +58,7 @@
>
{#if employee}
<div class="flex-col-center pb-2">
<Avatar size="x-large" avatar={employee.avatar} />
<Avatar size={'x-large'} avatar={employee.avatar} name={employee.name} />
</div>
<div class="pb-2">{getName(client.getHierarchy(), employee)}</div>
<DocNavLink object={employee}>
@@ -79,6 +79,7 @@
</div>
</div>
{:else if editable}
<!-- svelte-ignore a11y-click-events-have-key-events -->
<div class="flex-row-stretch over-underline pb-2" on:click={onEdit}>
<Label label={contact.string.SetStatus} />
</div>
@@ -359,7 +359,7 @@
selected={update.avatar !== undefined}
>
<svelte:fragment slot="item" let:item>
<Avatar avatar={item.avatar} size={'x-large'} icon={contact.icon.Person} />
<Avatar avatar={item.avatar} size={'x-large'} icon={contact.icon.Person} name={item.name} />
</svelte:fragment>
</MergeComparer>
<MergeComparer
@@ -42,7 +42,7 @@
<div class="antiContactCard">
<div class="label uppercase"><Label label={contact.string.Person} /></div>
<div class="flex-center logo">
<Avatar avatar={object.avatar} size={'large'} icon={contact.icon.Company} />
<Avatar avatar={object.avatar} size={'large'} icon={contact.icon.Company} name={object.name} />
</div>
{#if object}
<!-- svelte-ignore a11y-click-events-have-key-events -->
@@ -87,7 +87,7 @@
class:mr-2={shouldShowName && !enlargedText}
class:mr-3={shouldShowName && enlargedText}
>
<Avatar size={avatarSize} avatar={value.avatar} />
<Avatar size={avatarSize} avatar={value.avatar} name={value.name} />
</span>
{/if}
{#if shouldShowName}
@@ -15,24 +15,33 @@
<script lang="ts">
import { createEventDispatcher } from 'svelte'
import { AvatarType, buildGravatarId, checkHasGravatar, getAvatarColorForId } from '@hcengineering/contact'
import {
AvatarType,
buildGravatarId,
checkHasGravatar,
getAvatarColorForId,
getAvatarColors,
getAvatarColorName
} from '@hcengineering/contact'
import { Asset } from '@hcengineering/platform'
import { AnySvelteComponent, Label, showPopup, TabList } from '@hcengineering/ui'
import { AnySvelteComponent, Label, showPopup, TabList, eventToHTMLElement } from '@hcengineering/ui'
import { ColorsPopup } from '@hcengineering/view-resources'
import presentation, { Card, getFileUrl } from '@hcengineering/presentation'
import contact from '../plugin'
import { getAvatarTypeDropdownItems } from '../utils'
import AvatarComponent from './Avatar.svelte'
import EditAvatarPopup from './EditAvatarPopup.svelte'
export let avatar: string | undefined
export let avatar: string | null | undefined = undefined
export let name: string | null | undefined = undefined
export let email: string | undefined
export let id: string
export let file: Blob | undefined
export let icon: Asset | AnySvelteComponent | undefined = undefined
export let onSubmit: (avatarType?: AvatarType, avatar?: string, file?: Blob) => void
const [schema, uri] = avatar?.split('://') || []
const colors = getAvatarColors()
let color: string | undefined = (schema as AvatarType) === AvatarType.COLOR ? uri : undefined
const initialSelectedType = (() => {
if (file) {
@@ -47,7 +56,7 @@
const initialSelectedAvatar = (() => {
if (!avatar) {
return getAvatarColorForId(id)
return getAvatarColorForId(name)
}
return avatar.includes('://') ? uri : avatar
@@ -87,7 +96,7 @@
inputRef.click()
}
} else {
selectedAvatar = getAvatarColorForId(id)
selectedAvatar = color ?? getAvatarColorForId(name)
}
}
@@ -110,13 +119,13 @@
if (blob === undefined) {
if (!selectedFile && (!avatar || avatar.includes('://'))) {
selectedAvatarType = AvatarType.COLOR
selectedAvatar = getAvatarColorForId(id)
selectedAvatar = getAvatarColorForId(name)
}
return
}
if (blob === null) {
selectedAvatarType = AvatarType.COLOR
selectedAvatar = getAvatarColorForId(id)
selectedAvatar = getAvatarColorForId(name)
selectedFile = undefined
} else {
selectedFile = blob
@@ -142,10 +151,23 @@
if (!inputRef.value.length) {
if (!selectedFile) {
selectedAvatarType = AvatarType.COLOR
selectedAvatar = getAvatarColorForId(id)
selectedAvatar = getAvatarColorForId(name)
}
}
}
const showColorPopup = (event: MouseEvent) => {
showPopup(
ColorsPopup,
{ colors, columns: 6, selected: getAvatarColorName(selectedAvatar) },
eventToHTMLElement(event),
(col) => {
if (col != null) {
color = selectedAvatar = colors[col].color
}
}
)
}
</script>
<Card
@@ -164,14 +186,26 @@
on:changeContent
>
<div class="flex-col-center gapV-4 mx-6">
{#if selectedAvatarType === AvatarType.IMAGE}
<!-- svelte-ignore a11y-click-events-have-key-events -->
<div class="cursor-pointer" on:click|self={handleImageAvatarClick}>
<AvatarComponent avatar={selectedAvatar} direct={selectedFile} size={'2x-large'} {icon} />
</div>
{:else}
<AvatarComponent avatar={`${selectedAvatarType}://${selectedAvatar}`} size={'2x-large'} {icon} />
{/if}
<!-- svelte-ignore a11y-click-events-have-key-events -->
<div
class="cursor-pointer"
on:click|self={(e) => {
if (selectedAvatarType === AvatarType.IMAGE) handleImageAvatarClick()
else if (selectedAvatarType === AvatarType.COLOR) showColorPopup(e)
}}
>
<AvatarComponent
avatar={selectedAvatarType === AvatarType.IMAGE
? selectedAvatar === ''
? `${AvatarType.COLOR}://${color}`
: selectedAvatar
: `${selectedAvatarType}://${selectedAvatar}`}
direct={selectedAvatarType === AvatarType.IMAGE ? selectedFile : undefined}
size={'2x-large'}
{icon}
{name}
/>
</div>
<TabList
items={getAvatarTypeDropdownItems(hasGravatar)}
kind={'separated-free'}
@@ -31,7 +31,7 @@
<!-- svelte-ignore a11y-click-events-have-key-events -->
<div class="flex-row-center" on:click>
<Avatar avatar={value.avatar} {size} {icon} on:accent-color />
<Avatar avatar={value.avatar} {size} {icon} name={value.name} on:accent-color />
<div class="flex-col min-w-0 {size === 'tiny' || size === 'inline' ? 'ml-1' : 'ml-2'}" class:max-w-20={short}>
{#if subtitle}<div class="content-dark-color text-sm">{subtitle}</div>{/if}
<div class="label overflow-label text-left">{getName(client.getHierarchy(), value)}</div>
@@ -18,33 +18,29 @@
export let size: IconSize
export let fill: string = 'var(--caption-color)'
export let fill: string = 'var(--theme-caption-color)'
</script>
<svg class="svg-avatar avaicon-{size}" {fill} viewBox="0 0 40 40" xmlns="http://www.w3.org/2000/svg">
<circle class="op" cx="20" cy="13.6" r="6.4" />
<svg class="svg-avatar avaicon-{size}" {fill} viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
<path
d="M33.1,33.3c-0.8-2.2-2.5-4.2-4.9-5.5c-2.3-1.3-5.2-2.1-8.2-2.1s-5.8,0.7-8.2,2.1c-2.4,1.4-4.1,3.3-4.9,5.5 c-0.1,0.4,0.1,0.8,0.5,1c0.4,0.1,0.8-0.1,1-0.5c0.7-1.9,2.2-3.5,4.2-4.7c2.1-1.2,4.7-1.9,7.4-1.9c2.7,0,5.3,0.7,7.4,1.9 c2.1,1.2,3.6,2.9,4.2,4.7c0.1,0.3,0.4,0.5,0.7,0.5c0.1,0,0.2,0,0.3,0C33,34.1,33.2,33.7,33.1,33.3z"
fill-rule="evenodd"
clip-rule="evenodd"
d="M10 9.99988C12.0711 9.99988 13.75 8.32095 13.75 6.24988C13.75 4.17881 12.0711 2.49988 10 2.49988C7.92893 2.49988 6.25 4.17881 6.25 6.24988C6.25 8.32095 7.92893 9.99988 10 9.99988ZM10 11.2499C12.7614 11.2499 15 9.0113 15 6.24988C15 3.48845 12.7614 1.24988 10 1.24988C7.23858 1.24988 5 3.48845 5 6.24988C5 9.0113 7.23858 11.2499 10 11.2499Z"
/>
<path
d="M20,20.8c3.9,0,7.1-3.2,7.1-7.1S23.9,6.5,20,6.5c-3.9,0-7.1,3.2-7.1,7.1S16.1,20.8,20,20.8z M20,8 c3.1,0,5.6,2.5,5.6,5.6s-2.5,5.6-5.6,5.6c-3.1,0-5.6-2.5-5.6-5.6S16.9,8,20,8z"
d="M8.125 12.4999C5.70875 12.4999 3.75 14.4586 3.75 16.8749V18.1249C3.75 18.4701 4.02982 18.7499 4.375 18.7499C4.72018 18.7499 5 18.4701 5 18.1249V16.8749C5 15.149 6.39911 13.7499 8.125 13.7499H11.875C13.6009 13.7499 15 15.149 15 16.8749V18.1249C15 18.4701 15.2798 18.7499 15.625 18.7499C15.9702 18.7499 16.25 18.4701 16.25 18.1249V16.8749C16.25 14.4586 14.2912 12.4999 11.875 12.4999H8.125Z"
/>
</svg>
<style lang="scss">
.svg-avatar {
.op {
opacity: 0.05;
}
}
.avaicon-inline {
width: 0.75rem;
height: 0.75rem;
width: 0.6125rem;
height: 0.6125rem;
}
.avaicon-tiny {
width: 0.875rem;
height: 0.875rem;
width: 0.8125rem;
height: 0.8125rem;
}
.avaicon-x-small {
@@ -64,15 +60,15 @@
height: 1.5rem;
}
.avaicon-large {
width: 1.75rem;
height: 1.75rem;
width: 2.5rem;
height: 2.5rem;
}
.avaicon-x-large {
width: 3rem;
height: 3rem;
}
.avaicon-2x-large {
width: 4rem;
height: 4rem;
}
.avaicon-2x-large {
width: 6rem;
height: 6rem;
}
</style>
+15 -13
View File
@@ -13,6 +13,8 @@
// limitations under the License.
//
import { ColorDefinition } from '@hcengineering/ui'
/**
* @public
*/
@@ -29,17 +31,17 @@ export type GravatarPlaceholderType =
/**
* @public
*/
export const AVATAR_COLORS = [
'#4674ca', // blue
'#315cac', // blue_dark
'#57be8c', // green
'#3fa372', // green_dark
'#f9a66d', // yellow_orange
'#ec5e44', // red
'#e63717', // red_dark
'#f868bc', // pink
'#6c5fc7', // purple
'#4e3fb4', // purple_dark
'#57b1be', // teal
'#847a8c' // gray
export const AVATAR_COLORS: ColorDefinition[] = [
{ name: 'blue', color: '#4674ca' }, // blue
{ name: 'blue_dark', color: '#315cac' }, // blue_dark
{ name: 'green', color: '#57be8c' }, // green
{ name: 'green_dark', color: '#3fa372' }, // green_dark
{ name: 'yellow_orange', color: '#f9a66d' }, // yellow_orange
{ name: 'red', color: '#ec5e44' }, // red
{ name: 'red_dark', color: '#e63717' }, // red_dark
{ name: 'pink', color: '#f868bc' }, // pink
{ name: 'purple', color: '#6c5fc7' }, // purple
{ name: 'purple_dark', color: '#4e3fb4' }, // purple_dark
{ name: 'teal', color: '#57b1be' }, // teal
{ name: 'gray', color: '#847a8c' } // gray
]
+18 -3
View File
@@ -14,7 +14,7 @@
//
import { AttachedData, Class, Client, Doc, FindResult, Ref, Hierarchy } from '@hcengineering/core'
import { IconSize } from '@hcengineering/ui'
import { IconSize, ColorDefinition } from '@hcengineering/ui'
import { MD5 } from 'crypto-js'
import { Channel, Contact, contactPlugin, Person } from '.'
import { AVATAR_COLORS, GravatarPlaceholderType } from './types'
@@ -22,14 +22,29 @@ import { AVATAR_COLORS, GravatarPlaceholderType } from './types'
/**
* @public
*/
export function getAvatarColorForId (id: string): string {
export function getAvatarColorForId (id: string | null | undefined): string {
if (id == null) return AVATAR_COLORS[0].color
let hash = 0
for (let i = 0; i < id.length; i++) {
hash += id.charCodeAt(i)
}
return AVATAR_COLORS[hash % AVATAR_COLORS.length]
return AVATAR_COLORS[hash % AVATAR_COLORS.length].color
}
/**
* @public
*/
export function getAvatarColors (): readonly ColorDefinition[] {
return AVATAR_COLORS
}
/**
* @public
*/
export function getAvatarColorName (color: string): string {
return AVATAR_COLORS.find((col) => col.color === color)?.name ?? AVATAR_COLORS[0].name
}
/**
@@ -126,7 +126,7 @@
<div class="mr-2">
<Button icon={IconAdd} kind={'list'} on:click={createChild} />
</div>
<Avatar size={'medium'} avatar={value.avatar} icon={hr.icon.Department} />
<Avatar size={'medium'} avatar={value.avatar} icon={hr.icon.Department} name={value.name} />
<div class="flex-row ml-2 mr-4">
<div class="fs-title">
{value.name}
@@ -74,7 +74,6 @@
{#key object}
<EditableAvatar
avatar={object.avatar}
id={object._id}
size={'x-large'}
icon={hr.icon.Department}
bind:this={avatarEditor}
@@ -34,7 +34,7 @@
<DocNavLink object={value}>
<div class="flex-row-center">
<div class="member-icon mr-2">
<Avatar size={'medium'} avatar={value.avatar} />
<Avatar size={'medium'} avatar={value.avatar} name={value.name} />
</div>
<div class="flex-col">
<div class="member-title fs-title">
@@ -189,8 +189,8 @@
<div class="ml-4 flex">
<EditableAvatar
avatar={object.avatar}
id={customerId}
size={'large'}
name={object.name}
bind:this={avatarEditor}
bind:direct={avatar}
/>
@@ -184,7 +184,7 @@
<div class="flex-between header bottom-divider">
<div class="flex-row-center">
{#if employee}
<Avatar size="smaller" avatar={employee.avatar} />
<Avatar size={'smaller'} avatar={employee.avatar} name={employee.name} />
<span class="font-medium mx-2">{getName(client.getHierarchy(), employee)}</span>
{/if}
{#if newTxes > 0}
@@ -88,7 +88,7 @@
<!-- svelte-ignore a11y-click-events-have-key-events -->
<div class="inbox-activity__content shrink flex-grow clear-mins" class:read={newTxes === 0}>
<div class="flex-row-center gap-2">
<Avatar avatar={employee?.avatar} size="small" />
<Avatar avatar={employee?.avatar} size={'small'} name={employee?.name} />
{#if employee}
<span class="font-medium">{getName(client.getHierarchy(), employee)}</span>
{:else}
@@ -118,7 +118,7 @@
<div class="msgactivity-container">
{#if withAvatar}
<div class="msgactivity-avatar">
<Avatar avatar={employee?.avatar} size="x-small" />
<Avatar avatar={employee?.avatar} size={'x-small'} name={employee?.name} />
</div>
{:else}
<div class="msgactivity-icon">
@@ -46,7 +46,7 @@
<div class="antiContactCard">
<div class="label uppercase"><Label label={recruit.string.Talent} /></div>
<Avatar avatar={candidate?.avatar} size={'large'} />
<Avatar avatar={candidate?.avatar} size={'large'} name={candidate?.name} />
{#if candidate}
<DocNavLink object={candidate} {disabled}>
<div class="name lines-limit-2">
@@ -562,8 +562,8 @@
bind:this={avatarEditor}
bind:direct={object.avatar}
avatar={undefined}
id={object._id}
size={'large'}
name={combineName(object?.firstName?.trim() ?? '', object?.lastName?.trim() ?? '')}
/>
</div>
</div>
@@ -61,7 +61,7 @@
{/if}
<div class="flex-between mb-1">
<div class="flex-row-center">
<Avatar avatar={object.$lookup?.attachedTo?.avatar} size={'medium'} />
<Avatar avatar={object.$lookup?.attachedTo?.avatar} size={'medium'} name={object.$lookup?.attachedTo?.name} />
<div class="flex-grow flex-col min-w-0 ml-2">
<div class="fs-title over-underline lines-limit-2">
{object.$lookup?.attachedTo ? getName(client.getHierarchy(), object.$lookup.attachedTo) : ''}
@@ -37,6 +37,7 @@
{#if value}
<div class="flex persons">
{#each persons as p}
<!-- svelte-ignore a11y-click-events-have-key-events -->
<div
class="flex-presenter"
class:inline-presenter={inline}
@@ -44,7 +45,7 @@
on:click={() => onClick(p)}
>
<div class="icon">
<Avatar size={'x-small'} avatar={p.avatar} />
<Avatar size={'x-small'} avatar={p.avatar} name={p.name} />
</div>
</div>
{/each}
@@ -95,8 +95,8 @@
<EditableAvatar
avatar={employee.avatar}
email={account.email}
id={employee._id}
size={'x-large'}
name={employee.name}
bind:this={avatarEditor}
on:done={onAvatarDone}
/>
@@ -233,7 +233,7 @@
class="ml-2"
use:tooltip={{ label: getEmbeddedLabel(getContactName(client.getHierarchy(), participant)) }}
>
<Avatar size="small" avatar={participant.avatar} />
<Avatar size={'small'} avatar={participant.avatar} name={participant.name} />
</div>
{/each}
</div>
@@ -23,7 +23,7 @@
<div class="root">
<div class="icon">
<Avatar avatar={lead.avatar} size="medium" />
<Avatar avatar={lead.avatar} size={'medium'} name={lead.name} />
</div>
<div class="textContainer">
<div class="title">
@@ -38,7 +38,6 @@
>
<div class="flex-center ml-2">
<div class="flex-no-shrink circles-mark" class:isDraggable><IconCircles size={'small'} /></div>
!!!
</div>
<div class="root flex flex-between items-center w-full p-2">
@@ -163,7 +163,10 @@
}}
>
{#if employee}
<Component is={contact.component.Avatar} props={{ avatar: employee.avatar, size: 'medium' }} />
<Component
is={contact.component.Avatar}
props={{ avatar: employee.avatar, size: 'medium', name: employee.name }}
/>
{/if}
<div class="ml-2 flex-col">
{#if account}
@@ -707,7 +707,10 @@
class="cursor-pointer"
on:click|stopPropagation={() => showPopup(AccountPopup, {}, popupPosition)}
>
<Component is={contact.component.Avatar} props={{ avatar: employee?.avatar, size: 'small' }} />
<Component
is={contact.component.Avatar}
props={{ avatar: employee?.avatar, size: 'small', name: employee?.name }}
/>
</div>
</div>
</div>