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

Signed-off-by: Alexander Platov <sas_lord@mail.ru>
This commit is contained in:
Alexander Platov 2023-09-20 20:01:09 +03:00 committed by GitHub
parent d9d47846cf
commit a07f88033f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
43 changed files with 322 additions and 111 deletions

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); }

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; }

View File

@ -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>

View File

@ -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">

View File

@ -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}

View File

@ -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>

View File

@ -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">

View File

@ -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}

View File

@ -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) : ''}

View File

@ -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>

View File

@ -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 {

View File

@ -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>

View File

@ -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">

View File

@ -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">

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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

View File

@ -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 -->

View File

@ -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}

View File

@ -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
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>
{:else}
<AvatarComponent avatar={`${selectedAvatarType}://${selectedAvatar}`} size={'2x-large'} {icon} />
{/if}
<TabList
items={getAvatarTypeDropdownItems(hasGravatar)}
kind={'separated-free'}

View File

@ -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>

View File

@ -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>

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
]

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
}
/**

View File

@ -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}

View File

@ -74,7 +74,6 @@
{#key object}
<EditableAvatar
avatar={object.avatar}
id={object._id}
size={'x-large'}
icon={hr.icon.Department}
bind:this={avatarEditor}

View File

@ -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">

View File

@ -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}
/>

View File

@ -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}

View File

@ -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}

View File

@ -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">

View File

@ -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">

View File

@ -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>

View File

@ -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) : ''}

View File

@ -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}

View File

@ -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}
/>

View File

@ -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>

View File

@ -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">

View File

@ -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">

View File

@ -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}

View File

@ -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>