CommonUserAvatar.vue 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121
  1. <!-- Copyright (C) 2012-2024 Zammad Foundation, https://zammad-foundation.org/ -->
  2. <script setup lang="ts">
  3. import { computed } from 'vue'
  4. import { useApplicationStore } from '#shared/stores/application.ts'
  5. import { getInitials } from '#shared/utils/formatter.ts'
  6. import { i18n } from '#shared/i18n.ts'
  7. import {
  8. SYSTEM_USER_ID,
  9. SYSTEM_USER_INTERNAL_ID,
  10. } from '#shared/utils/constants.ts'
  11. import { getIdFromGraphQLId } from '#shared/graphql/utils.ts'
  12. import CommonAvatar from '../CommonAvatar/CommonAvatar.vue'
  13. import type { AvatarSize } from '../CommonAvatar/index.ts'
  14. import type { AvatarUser } from './types.ts'
  15. import logo from './assets/logo.svg'
  16. export interface Props {
  17. entity: AvatarUser
  18. size?: AvatarSize
  19. personal?: boolean
  20. decorative?: boolean
  21. }
  22. const props = defineProps<Props>()
  23. const initials = computed(() => {
  24. const { lastname, firstname, email } = props.entity
  25. return getInitials(firstname, lastname, email)
  26. })
  27. const colors = [
  28. 'bg-gray',
  29. 'bg-red-bright',
  30. 'bg-yellow',
  31. 'bg-blue',
  32. 'bg-green',
  33. 'bg-pink',
  34. 'bg-orange',
  35. ]
  36. const fullName = computed(() => {
  37. const { lastname, firstname } = props.entity
  38. return [firstname, lastname].filter(Boolean).join(' ')
  39. })
  40. const colorClass = computed(() => {
  41. const { id } = props.entity
  42. const internalId = getIdFromGraphQLId(id)
  43. if (internalId === SYSTEM_USER_INTERNAL_ID) return 'bg-white'
  44. // get color based on mod of the fullname length
  45. // so it stays consistent between different interfaces and logins
  46. return colors[internalId % (colors.length - 1)]
  47. })
  48. const sources = ['facebook', 'twitter']
  49. const icon = computed(() => {
  50. const { source } = props.entity
  51. if (source && sources.includes(source)) return source
  52. return null
  53. })
  54. const application = useApplicationStore()
  55. const image = computed(() => {
  56. if (icon.value) return null
  57. if (props.entity.id === SYSTEM_USER_ID) return logo
  58. if (!props.entity.image) return null
  59. // Support the inline data URI as an image source.
  60. if (props.entity.image.startsWith('data:')) return props.entity.image
  61. // we're using the REST api here to get the image and to also use the browser image cache
  62. // TODO: this should be re-evaluated when the desktop app is going to be implemented
  63. const apiUrl = String(application.config.api_path)
  64. return `${apiUrl}/users/image/${props.entity.image}`
  65. })
  66. const isVip = computed(() => {
  67. return !props.personal && props.entity.vip
  68. })
  69. const className = computed(() => {
  70. const classes = [colorClass.value]
  71. if (props.entity.outOfOffice) {
  72. classes.push('opacity-100 grayscale-[70%]')
  73. } else if (props.entity.active === false) {
  74. classes.push('opacity-20 grayscale')
  75. }
  76. return classes
  77. })
  78. const label = computed(() => {
  79. let label = i18n.t('Avatar')
  80. const name = fullName.value || props.entity.email
  81. if (name) label += ` (${name})`
  82. if (isVip.value) label += ` (${i18n.t('VIP')})`
  83. return label
  84. })
  85. </script>
  86. <template>
  87. <CommonAvatar
  88. :initials="initials"
  89. :size="size"
  90. :icon="icon"
  91. :class="className"
  92. :image="image"
  93. :vip-icon="isVip ? 'vip-user' : undefined"
  94. :decorative="decorative"
  95. :aria-label="label"
  96. />
  97. </template>