# Avatar-System

Wie Profilbilder für Benutzer und Kontakte aufgelöst, gerendert und gespeichert werden.

# Übersicht

SeminarPlan zeigt Avatare an über 15 Stellen im Frontend (Tabellen, Chat, Taskbar, Popups, Wochenplan, ...). Das System unterscheidet zwei Entitäten mit Avataren:

Entität Upload-Feld Gravatar-Quelle Fallback
Benutzer (IUserEntity) media.avatar user.email Gravatar Mystery Person (d=mp)
Kontakt (IContactEntity) media.avatar Primäre E-Mail Statischer Gravatar-Hash

# Auflösungs-Reihenfolge

# Benutzer (userHelper.getUserAvatarUrl)

src/modules/helper/userHelper.ts
getUserAvatarUrl(user: IUser, size = 64) {
  // 1. Lokaler Upload → media.avatar
  if (user?.media?.avatar) {
    return pathHelper.getImagePath(user.media.avatar);
  }

  // 2. Gravatar via E-Mail
  return getEmailAvatarUrl(user.email, size);
}

getEmailAvatarUrl(email: string, size = 64) {
  const hash = md5(email.trim().toLowerCase());
  return `https://www.gravatar.com/avatar/${hash}/?s=${size}&d=mp`;
}

Fallback-Kette: media.avatar → Gravatar (E-Mail-Hash) → d=mp (Gravatar liefert generisches Personen-Icon)

# Kontakt (contactHelper.getAvatar)

src/composables/contactHelper.ts
getAvatar(contact: ContactReadable) {
  // 1. Lokaler Upload
  if (contact.media?.avatar) {
    return pathHelper.getImagePath(contact.media.avatar);
  }

  // 2. Gravatar via primäre E-Mail
  const primaryEmail = contact.primaryEmail
    || contact.emailAddresses?.find(e => e.isPrimary)?.value;

  if (primaryEmail) {
    return userHelper.getEmailAvatarUrl(primaryEmail);
  }

  // 3. Statischer Fallback (leerer Gravatar)
  return 'https://www.gravatar.com/avatar/adefa.../?s=64';
}

Fallback-Kette: media.avatar → Gravatar (primäre E-Mail) → statischer Gravatar-Platzhalter

# Datenmodell

# media.avatar in der Datenbank

Das Feld speichert einen relativen Pfad zum hochgeladenen Bild:

{
  "media": {
    "avatar": "uploads/avatars/64a1b2c3d4e5f6.png"
  }
}

pathHelper.getImagePath() löst diesen Pfad zur vollständigen URL auf:

  • Relativer Pfad: config.baseUrl + '/' + path
  • Absoluter Pfad (/...): unverändert
  • URL (http...): unverändert

# Type Definitions

// IUserEntity (src/api/types/types.ts)
interface IUser {
  media: {
    avatar?: string;    // Pfad zum Profilbild
    signature?: string; // Pfad zur Unterschrift
  };
  // ...
}

// IContactEntity (src/api/types/types.ts)
interface IContactEntity {
  media: {
    avatar: string;     // Pfad zum Profilbild
    logo: string;       // Pfad zum Firmenlogo
  };
  // ...
}

# Komponenten

# SemAvatar.vue - Zentrale Avatar-Komponente

src/components/UI/SemAvatar.vue

Die Haupt-Komponente, die überall verwendet wird. Akzeptiert drei Bild-Quellen (Priorität):

Prop Typ Beschreibung
src string Direkte Bild-URL (höchste Prio, überschreibt user/contact)
user IUserEntity \| string Benutzer-Objekt oder Benutzer-ID
contact IContactEntity Kontakt-Objekt
variant 'rounded' \| 'square' Form (Standard: rounded)
size string CSS-Klasse für die Größe (Standard: w-32)
showOnline boolean Online-Punkt anzeigen (Standard: true)

Interne Auflösung:

  1. src → direkt verwenden
  2. useruserHelper.getUserAvatarUrl(user)
  3. contactcontactHelper.getAvatar(contact)

Online-Indikator: Wenn showOnline aktiv ist und ein user übergeben wurde, prüft die Komponente über runtimeStore.onlineMembers, ob der Benutzer gerade verbunden ist und zeigt einen grünen Punkt.

# SemUserAvatarWithInfo.vue - Avatar mit Hover-Popup

src/components/UI/SemUserAvatarWithInfo.vue

Zeigt einen SemAvatar mit einem Hover-Popup (VMenu), das UserInfo anzeigt (Name, E-Mail, Telefon, Rolle).

# ContactAvatarCell.vue - Kontakt-Avatar in Tabellen

src/components/UI/DataTable/ContactAvatarCell.vue

Spezialisierte Tabellenzelle für Kontakt-Avatare. Unterstützt:

  • Store-Lookup: Wenn der contactStore geladen ist, wird der Kontakt dort gesucht
  • V2-API-Fallback: Wenn der Store nicht geladen ist, wird der Kontakt über getV2('/contacts/:id') geladen
  • Prop-Fallback: displayName, avatarUrl, primaryEmail als direkte Props (für denormalisierte V2-Listendaten)
  • Hover-Popup: Zeigt ContactInfo mit Adresse, E-Mail, Telefon und Quick-Actions

# AvatarCell.vue - Benutzer-Avatar in Tabellen

src/components/UI/DataTable/AvatarCell.vue

Zeigt SemUserAvatarWithInfo für eine Benutzer-ID. Wird in den Tabellenspalten "Erstellt von" und "Geändert von" verwendet.

# Upload-Mechanismus

Der Avatar-Upload nutzt die SemSelectImage-Komponente:

src/components/UI/Inputs/SemSelectImage.vue

Diese Komponente bietet zwei Wege:

  1. Medienmanager: Auswahl eines bereits hochgeladenen Bilds
  2. Neuer Upload: Datei direkt hochladen (wird im Medienmanager gespeichert)

Nach der Auswahl wird der Pfad über ein @image-selected-Event an den Parent emittiert. Bei Benutzern aktualisiert SettingsUserSection.vue das media.avatar-Feld via updateUserMedia-Event.

# Gravatar-Details

SeminarPlan nutzt die Gravatar-API (opens new window):

  • Hash: MD5 der getrimmten, lowercase E-Mail-Adresse (md5 via js-md5)
  • URL-Format: https://www.gravatar.com/avatar/{hash}/?s={size}&d=mp
  • Parameter d=mp: "Mystery Person" - ein generisches Personen-Icon als Fallback, wenn kein Gravatar für die E-Mail existiert
  • Parameter s: Bildgröße in Pixeln (Standard: 64)

Datenschutz

Gravatar-Requests senden einen MD5-Hash der E-Mail-Adresse an gravatar.com. Das ist ein externer Service. Wenn das ein Problem ist, solltest du lokale Uploads verwenden.

# Eigene Avatare in V2-Programmen verwenden

<template>
  <!-- Für Benutzer (mit Online-Status) -->
  <SemAvatar :user="userId" size="w-10" />

  <!-- Für Kontakte -->
  <SemAvatar :contact="contactObject" size="w-10" />

  <!-- Direkte URL -->
  <SemAvatar src="https://example.com/avatar.png" size="w-10" />

  <!-- Benutzer mit Hover-Info -->
  <SemUserAvatarWithInfo :user="userObject" size="w-10" />
</template>

# Siehe auch

Last Updated: 4/16/2026, 8:54:41 AM