Files
nocodb/packages/nc-gui/components/general/UserIcon.vue
mertmit 69a29568c7 chore: sync
Signed-off-by: mertmit <mertmit99@gmail.com>
2026-01-10 00:21:02 +03:00

219 lines
5.8 KiB
Vue

<script lang="ts" setup>
import { IconType, type UserType } from 'nocodb-sdk'
import 'emoji-mart-vue-fast/css/emoji-mart.css'
import { Icon } from '@iconify/vue'
import { type IconMapKey, isColorDark, stringToColor } from '#imports'
const props = withDefaults(
defineProps<{
size?: 'small' | 'medium' | 'base' | 'large' | 'xlarge' | 'auto'
user?: Partial<UserType> | Partial<User> | null
disabled?: boolean
iconBgColor?: string
showPlaceholderIcon?: boolean
isDeleted?: boolean
initialsLength?: 1 | 2
placeholderIcon?: IconMapKey
}>(),
{
user: () => ({}),
size: 'medium',
name: '',
email: '',
disabled: false,
iconBgColor: 'var(--nc-bg-gray-light)',
showPlaceholderIcon: false,
isDeleted: false,
initialsLength: 2,
},
)
const { size } = toRefs(props)
const { getColor } = useTheme()
const { getPossibleAttachmentSrc } = useAttachment()
const user = computed(() => {
return {
...(props.user || {}),
email: props.user?.email?.trim() || '',
display_name: props.user?.display_name?.trim() || '',
meta: props.user?.meta || null,
}
})
const userIcon = computed<{
icon: any
iconType: IconType | string
}>(() => {
if (props.isDeleted) {
return {
icon: 'ncSlash',
iconType: IconType.ICON,
}
}
if (!user.value.meta) {
return {
icon: '',
iconType: '',
}
}
const icon = parseProp(user.value.meta).icon || ''
const iconType = parseProp(user.value.meta).iconType || ''
if ((!icon || !iconType) && props.placeholderIcon) {
return {
icon: props.placeholderIcon,
iconType: IconType.ICON,
}
}
return {
icon: iconType === IconType.IMAGE && ncIsObject(icon) ? getPossibleAttachmentSrc(icon) || '' : (icon as string),
iconType,
}
})
const backgroundColor = computed(() => {
if (props.disabled || props.isDeleted) {
return '#bbbbbb'
}
// in comments we need to generate user icon from email
const color = user.value.display_name
? stringToColor(user.value.display_name)
: user.value.email
? stringToColor(user.value.email)
: '#FFFFFF'
if (userIcon.value.icon) {
switch (userIcon.value.iconType) {
case IconType.IMAGE: {
return ''
}
case IconType.EMOJI: {
return props.iconBgColor
}
case IconType.ICON: {
return props.iconBgColor
}
default: {
return color || '#FFFFFF'
}
}
}
return color || '#FFFFFF'
})
const usernameInitials = computed(() => {
if (props.disabled || props.isDeleted) {
return ''
}
return getSafeInitials(user.value.display_name?.trim() || user.value.email?.split('@')[0], props.initialsLength, true)
})
</script>
<template>
<div
class="nc-user-avatar"
:class="{
'h-full min-h-5 aspect-square': size === 'auto',
'w-4 h-4': size === 'small',
'w-6 h-6': size === 'medium',
'w-8 h-8': size === 'base',
'w-20 h-20': size === 'large',
'w-26 h-26': size === 'xlarge',
}"
:style="{
backgroundColor:
showPlaceholderIcon || (userIcon.icon && userIcon.iconType === IconType.IMAGE) ? undefined : backgroundColor,
}"
>
<div v-if="showPlaceholderIcon" class="border-1 border-nc-border-gray-medium rounded-full overflow-hidden">
<img src="~assets/img/placeholder/avatar.png" class="!w-full h-full flex-none" />
</div>
<CellAttachmentPreviewImage
v-else-if="userIcon.icon && userIcon.iconType === IconType.IMAGE"
:srcs="userIcon.icon"
class="flex-none !object-contain max-h-full max-w-full !m-0"
:is-cell-preview="false"
/>
<div
v-else-if="userIcon.icon && userIcon.iconType === IconType.EMOJI"
class="flex items-center justify-center align-middle"
:class="{
'text-inherit': size === 'auto',
'text-white': isColorDark(getColor(backgroundColor)),
'text-black opacity-80': !isColorDark(getColor(backgroundColor)),
'text-tiny': size === 'small',
'text-base': size === 'medium',
'text-lg': size === 'base',
'text-4xl': size === 'large',
'text-5xl': size === 'xlarge',
}"
>
<div v-if="isUnicodeEmoji(userIcon.icon)">
{{ userIcon.icon }}
</div>
<Icon
v-else
:data-testid="`nc-icon-${userIcon.icon}`"
class="!text-inherit flex-none"
:class="{
'w-[75%] h-[75%]': size === 'auto',
'w-3 h-3': size === 'small',
'w-4 h-4': size === 'medium',
'w-5 h-5': size === 'base',
'w-12 h-12': size === 'large',
'w-14 h-14': size === 'xlarge',
}"
:icon="userIcon.icon"
></Icon>
</div>
<GeneralIcon
v-else-if="userIcon.icon && userIcon.iconType === IconType.ICON"
:icon="userIcon.icon"
class="flex-none"
:class="{
'w-[75%] h-[75%]': size === 'auto',
'text-white': isColorDark(getColor(backgroundColor)),
'text-black opacity-80': !isColorDark(getColor(backgroundColor)),
'w-3 h-3': size === 'small',
'w-4 h-4': size === 'medium',
'w-5 h-5': size === 'base',
'w-12 h-12': size === 'large',
'w-14 h-14': size === 'xlarge',
'!opacity-50': isDeleted,
}"
/>
<div
v-else-if="usernameInitials"
class="font-semibold"
:class="{
'!text-md': size === 'base',
'!text-3xl': size === 'large',
'!text-4xl': size === 'xlarge',
'text-white': isColorDark(getColor(backgroundColor)),
'text-black': !isColorDark(getColor(backgroundColor)),
}"
>
{{ usernameInitials }}
</div>
<div v-else>&nbsp;</div>
</div>
</template>
<style lang="scss" scoped>
.nc-user-avatar {
@apply flex-none rounded-full text-xs flex items-center justify-center uppercase overflow-hidden;
}
</style>