implement uploading file

This commit is contained in:
2026-05-01 02:02:34 +03:30
parent 4776af5c2a
commit 12b6bb4d76
11 changed files with 589 additions and 24 deletions

View File

@@ -4,6 +4,7 @@ namespace App\Livewire\Chat;
use App\Models\Conversation;
use App\Models\ConversationParticipant;
use App\Models\Message;
use App\Models\User;
use Flux\Flux;
use Illuminate\Contracts\View\View;
@@ -25,6 +26,8 @@ class ConversationDetailsPanel extends Component
public bool $showAddMembersModal = false;
public bool $showFilesModal = false;
public string $memberSearch = '';
/**
@@ -47,6 +50,14 @@ class ConversationDetailsPanel extends Component
}
}
#[On('message-created')]
public function refreshFiles(int $conversationId): void
{
if ($conversationId === $this->conversationId) {
unset($this->conversation, $this->files);
}
}
#[Computed]
public function conversation(): Conversation
{
@@ -57,7 +68,10 @@ class ConversationDetailsPanel extends Component
->select(['id', 'conversation_id', 'user_id', 'role', 'joined_at'])
->with('user:id,name,email'),
])
->withCount('messages')
->withCount([
'messages',
'messages as files_count' => fn (Builder $messages) => $messages->where('type', Message::TypeFile),
])
->findOrFail($this->conversationId);
Gate::authorize('view', $conversation);
@@ -104,6 +118,15 @@ class ConversationDetailsPanel extends Component
$this->showAddMembersModal = true;
}
public function openFiles(): void
{
Gate::authorize('view', $this->conversation);
unset($this->files);
$this->showFilesModal = true;
}
public function toggleMember(int $userId): void
{
if ($userId === Auth::id()) {
@@ -233,6 +256,23 @@ class ConversationDetailsPanel extends Component
->values();
}
/**
* @return Collection<int, Message>
*/
#[Computed]
public function files(): Collection
{
Gate::authorize('view', $this->conversation);
return Message::query()
->where('conversation_id', $this->conversationId)
->where('type', Message::TypeFile)
->with('sender:id,name,email')
->latest()
->limit(50)
->get();
}
private function suggestedGroupName(?Collection $newMemberIds = null): string
{
$participantNames = $this->conversation->participants

View File

@@ -199,7 +199,7 @@ class ConversationList extends Component
->select(['id', 'conversation_id', 'user_id', 'role', 'last_read_at'])
->with('user:id,name,email'),
'latestMessage' => fn ($query) => $query
->select(['messages.id', 'messages.conversation_id', 'messages.user_id', 'messages.body', 'messages.created_at']),
->select(['messages.id', 'messages.conversation_id', 'messages.user_id', 'messages.type', 'messages.body', 'messages.metadata', 'messages.created_at']),
'latestMessage.sender:id,name,email',
])
->withMax('messages', 'created_at')
@@ -298,7 +298,11 @@ class ConversationList extends Component
? __('You: ')
: ($conversation->isGroup() ? $conversation->latestMessage->sender?->name.': ' : '');
return str($prefix.$conversation->latestMessage->body)
$preview = $conversation->latestMessage->isFile()
? __('Sent a file: :name', ['name' => $conversation->latestMessage->attachmentName()])
: $conversation->latestMessage->body;
return str($prefix.$preview)
->squish()
->limit(86)
->toString();

View File

@@ -7,17 +7,27 @@ use App\Models\ConversationParticipant;
use App\Models\Message;
use Illuminate\Contracts\View\View;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Gate;
use Livewire\Attributes\Locked;
use Livewire\Component;
use Livewire\Features\SupportFileUploads\TemporaryUploadedFile;
use Livewire\WithFileUploads;
class MessageComposer extends Component
{
use WithFileUploads;
#[Locked]
public int $conversationId;
public string $body = '';
/**
* @var array<int, TemporaryUploadedFile>
*/
public array $attachments = [];
public function mount(int $conversationId): void
{
$this->conversationId = $conversationId;
@@ -28,37 +38,90 @@ class MessageComposer extends Component
public function sendMessage(): void
{
$this->body = trim($this->body);
$hasAttachments = $this->hasAttachments();
$validated = $this->validate([
'body' => ['required', 'string', 'max:4000'],
'body' => [$hasAttachments ? 'nullable' : 'required', 'string', 'max:4000'],
'attachments' => ['array', 'max:5'],
'attachments.*' => ['file', 'max:10240'],
], [
'body.required' => __('Write a message before sending.'),
'body.required' => __('Write a message or attach a file before sending.'),
'attachments.max' => __('Attach up to :max files at a time.'),
'attachments.*.file' => __('Each attachment must be a valid file.'),
'attachments.*.max' => __('Each attachment must be 10 MB or smaller.'),
]);
$conversation = $this->conversation();
Gate::authorize('sendMessage', $conversation);
$message = Message::query()->create([
'conversation_id' => $conversation->id,
'user_id' => Auth::id(),
'type' => Message::TypeText,
'body' => $validated['body'],
]);
$message = DB::transaction(function () use ($conversation, $validated): Message {
$latestMessage = null;
$body = trim((string) ($validated['body'] ?? ''));
$conversation->touch();
if ($body !== '') {
$latestMessage = Message::query()->create([
'conversation_id' => $conversation->id,
'user_id' => Auth::id(),
'type' => Message::TypeText,
'body' => $body,
]);
}
ConversationParticipant::query()
->where('conversation_id', $conversation->id)
->where('user_id', Auth::id())
->update(['last_read_at' => now()]);
foreach ($this->attachments as $attachment) {
$metadata = [
'disk' => 'local',
'original_name' => $attachment->getClientOriginalName(),
'mime_type' => $attachment->getMimeType(),
'size' => $attachment->getSize(),
];
$this->reset('body');
$path = $attachment->store(
path: "chat-attachments/{$conversation->id}",
options: 'local',
);
$metadata['path'] = $path;
$latestMessage = Message::query()->create([
'conversation_id' => $conversation->id,
'user_id' => Auth::id(),
'type' => Message::TypeFile,
'body' => $metadata['original_name'],
'metadata' => $metadata,
]);
}
$conversation->touch();
ConversationParticipant::query()
->where('conversation_id', $conversation->id)
->where('user_id', Auth::id())
->update(['last_read_at' => now()]);
return $latestMessage;
});
$this->reset('body', 'attachments');
$this->resetValidation();
$this->dispatch('message-created', conversationId: $conversation->id, messageId: $message->id);
}
public function removeAttachment(int $index): void
{
if (! array_key_exists($index, $this->attachments)) {
return;
}
unset($this->attachments[$index]);
$this->attachments = array_values($this->attachments);
$this->resetValidation('attachments');
$this->resetValidation("attachments.{$index}");
}
private function conversation(): Conversation
{
$conversation = Conversation::query()
@@ -70,6 +133,13 @@ class MessageComposer extends Component
return $conversation;
}
private function hasAttachments(): bool
{
return collect($this->attachments)
->filter()
->isNotEmpty();
}
public function render(): View
{
return view('livewire.chat.message-composer');