mirror of
https://github.com/hcengineering/platform.git
synced 2024-12-22 11:01:54 +03:00
UBER-486: updated people avatars. (#3720)
Signed-off-by: Alexander Platov <sas_lord@mail.ru>
This commit is contained in:
parent
d9d47846cf
commit
a07f88033f
@ -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); }
|
||||
|
||||
|
@ -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
|
||||
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'}
|
||||
|
@ -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>
|
||||
|
@ -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
|
||||
]
|
||||
|
@ -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>
|
||||
|
Loading…
Reference in New Issue
Block a user