Show Ladill profile avatars in the meeting participants UI.
Deploy Ladill Meet / deploy (push) Successful in 1m10s
Deploy Ladill Meet / deploy (push) Successful in 1m10s
Resolve participant photos from linked user records for the header host chip and participants panel, with initials as fallback. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -7,6 +7,7 @@ use App\Models\Participant;
|
||||
use App\Models\Session;
|
||||
use App\Services\Meet\ChatService;
|
||||
use App\Services\Meet\Media\MediaProviderInterface;
|
||||
use App\Services\Meet\ParticipantPresenter;
|
||||
use App\Services\Meet\RecordingService;
|
||||
use App\Services\Meet\SecurityPolicyService;
|
||||
use App\Services\Meet\SessionService;
|
||||
@@ -146,6 +147,8 @@ class MeetingRoomController extends Controller
|
||||
|
||||
$session->load(['participants' => fn ($q) => $q->where('status', 'joined')]);
|
||||
|
||||
$avatars = ParticipantPresenter::avatarMap($session->participants, $session->room->host_user_ref);
|
||||
|
||||
$messages = $this->chat->recentMessages($session, 50);
|
||||
$reactions = $session->reactions()->where('created_at', '>=', now()->subMinutes(5))->latest()->limit(20)->get();
|
||||
$qa = app(\App\Services\Meet\QaService::class)->forSession($session, true);
|
||||
@@ -161,15 +164,9 @@ class MeetingRoomController extends Controller
|
||||
->get();
|
||||
|
||||
return response()->json([
|
||||
'participants' => $session->participants->map(fn ($p) => [
|
||||
'uuid' => $p->uuid,
|
||||
'display_name' => $p->display_name,
|
||||
'role' => $p->role,
|
||||
'hand_raised' => $p->hand_raised,
|
||||
'is_muted' => $p->is_muted,
|
||||
'is_video_off' => $p->is_video_off,
|
||||
'breakout_room_id' => $p->breakout_room_id,
|
||||
]),
|
||||
'participants' => $session->participants->map(
|
||||
fn ($p) => ParticipantPresenter::toArray($p, $avatars, $session->room->host_user_ref)
|
||||
),
|
||||
'messages' => $messages->map(fn ($m) => [
|
||||
'uuid' => $m->uuid,
|
||||
'sender_name' => $m->sender_name,
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\Meet;
|
||||
|
||||
use App\Models\Participant;
|
||||
use App\Models\User;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
class ParticipantPresenter
|
||||
{
|
||||
/**
|
||||
* @param Collection<int, Participant> $participants
|
||||
*/
|
||||
public static function avatarMap(Collection $participants, ?string $roomHostUserRef = null): Collection
|
||||
{
|
||||
$refs = $participants->pluck('user_ref')->filter();
|
||||
|
||||
if ($roomHostUserRef) {
|
||||
$refs = $refs->push($roomHostUserRef)->unique();
|
||||
}
|
||||
|
||||
if ($refs->isEmpty()) {
|
||||
return collect();
|
||||
}
|
||||
|
||||
return User::query()
|
||||
->whereIn('public_id', $refs->all())
|
||||
->get()
|
||||
->keyBy('public_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public static function toArray(Participant $participant, Collection $avatars, ?string $roomHostUserRef = null): array
|
||||
{
|
||||
return [
|
||||
'uuid' => $participant->uuid,
|
||||
'display_name' => $participant->display_name,
|
||||
'role' => $participant->role,
|
||||
'hand_raised' => $participant->hand_raised,
|
||||
'is_muted' => $participant->is_muted,
|
||||
'is_video_off' => $participant->is_video_off,
|
||||
'breakout_room_id' => $participant->breakout_room_id,
|
||||
'avatar_url' => self::avatarUrl($participant, $avatars, $roomHostUserRef),
|
||||
];
|
||||
}
|
||||
|
||||
public static function avatarUrl(Participant $participant, Collection $avatars, ?string $roomHostUserRef = null): ?string
|
||||
{
|
||||
if ($participant->user_ref) {
|
||||
$url = $avatars->get($participant->user_ref)?->avatarUrl();
|
||||
if ($url) {
|
||||
return $url;
|
||||
}
|
||||
}
|
||||
|
||||
if (in_array($participant->role, ['host', 'co_host'], true) && $roomHostUserRef) {
|
||||
return $avatars->get($roomHostUserRef)?->avatarUrl();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -10,16 +10,12 @@
|
||||
</head>
|
||||
<body class="h-full overflow-hidden bg-slate-950 font-sans text-white" x-data="meetRoom()" x-init="init()">
|
||||
@php
|
||||
$joinedParticipants = $session->participants
|
||||
->where('status', 'joined')
|
||||
->map(fn ($p) => [
|
||||
'uuid' => $p->uuid,
|
||||
'display_name' => $p->display_name,
|
||||
'role' => $p->role,
|
||||
'hand_raised' => $p->hand_raised,
|
||||
'is_muted' => $p->is_muted,
|
||||
'is_video_off' => $p->is_video_off,
|
||||
])
|
||||
use App\Services\Meet\ParticipantPresenter;
|
||||
|
||||
$joined = $session->participants->where('status', 'joined');
|
||||
$participantAvatars = ParticipantPresenter::avatarMap($joined, $room->host_user_ref);
|
||||
$joinedParticipants = $joined
|
||||
->map(fn ($p) => ParticipantPresenter::toArray($p, $participantAvatars, $room->host_user_ref))
|
||||
->values();
|
||||
@endphp
|
||||
<div id="meet-config"
|
||||
@@ -86,10 +82,16 @@
|
||||
<button @click="toggleParticipants()" type="button"
|
||||
class="inline-flex items-center gap-2 rounded-lg bg-slate-900 px-2 py-1 text-xs font-medium text-slate-200 transition-colors hover:bg-slate-800"
|
||||
title="View participants">
|
||||
<span x-show="hostParticipant()"
|
||||
class="flex h-7 w-7 shrink-0 items-center justify-center rounded-full text-[11px] font-semibold text-white"
|
||||
:style="avatarStyle(hostParticipant()?.display_name)"
|
||||
x-text="participantInitials(hostParticipant()?.display_name || '')"></span>
|
||||
<span x-show="hostParticipant()" class="relative flex h-7 w-7 shrink-0">
|
||||
<img x-show="hostParticipant()?.avatar_url"
|
||||
:src="hostParticipant()?.avatar_url"
|
||||
alt=""
|
||||
class="h-7 w-7 rounded-full object-cover">
|
||||
<span x-show="hostParticipant() && !hostParticipant()?.avatar_url"
|
||||
class="flex h-7 w-7 items-center justify-center rounded-full text-[11px] font-semibold text-white"
|
||||
:style="avatarStyle(hostParticipant()?.display_name)"
|
||||
x-text="participantInitials(hostParticipant()?.display_name || '')"></span>
|
||||
</span>
|
||||
<span x-text="participantCount() + ' in call'"></span>
|
||||
</button>
|
||||
</div>
|
||||
@@ -375,9 +377,16 @@
|
||||
<div class="flex min-h-0 flex-1 flex-col overflow-y-auto rounded-2xl bg-slate-900/90 p-2">
|
||||
<template x-for="person in sortedParticipants()" :key="person.uuid">
|
||||
<div class="flex items-center gap-3 rounded-xl px-2 py-2.5">
|
||||
<span class="flex h-10 w-10 shrink-0 items-center justify-center rounded-full text-sm font-semibold text-white"
|
||||
:style="avatarStyle(person.display_name)"
|
||||
x-text="participantInitials(person.display_name)"></span>
|
||||
<span class="relative flex h-10 w-10 shrink-0">
|
||||
<img x-show="person.avatar_url"
|
||||
:src="person.avatar_url"
|
||||
alt=""
|
||||
class="h-10 w-10 rounded-full object-cover">
|
||||
<span x-show="!person.avatar_url"
|
||||
class="flex h-10 w-10 items-center justify-center rounded-full text-sm font-semibold text-white"
|
||||
:style="avatarStyle(person.display_name)"
|
||||
x-text="participantInitials(person.display_name)"></span>
|
||||
</span>
|
||||
<span class="min-w-0 flex-1">
|
||||
<span class="block truncate text-sm font-medium text-white" x-text="person.display_name"></span>
|
||||
<span class="block text-xs text-slate-400" x-text="roleLabel(person.role)"></span>
|
||||
|
||||
Reference in New Issue
Block a user