mirror of
https://github.com/filamentphp/filament.git
synced 2026-09-24 15:42:09 +08:00
do it
This commit is contained in:
@@ -87,7 +87,9 @@
|
||||
<div class="flex items-center shrink-0">
|
||||
@livewire('filament.core.global-search')
|
||||
|
||||
@livewire('filament.core.notifications')
|
||||
@if (filament()->hasDatabaseNotifications())
|
||||
@livewire('filament.core.database-notifications')
|
||||
@endif
|
||||
|
||||
<x-filament::user-menu />
|
||||
|
||||
|
||||
@@ -86,6 +86,8 @@
|
||||
|
||||
{{ $slot }}
|
||||
|
||||
@livewire('filament.core.notifications')
|
||||
|
||||
{{ filament()->renderHook('scripts.start') }}
|
||||
|
||||
@livewireScripts
|
||||
|
||||
@@ -54,14 +54,10 @@
|
||||
|
||||
@if (filament()->auth()->check())
|
||||
<div class="absolute top-0 end-0 flex items-center justify-end p-2 w-full">
|
||||
@livewire('filament.core.notifications')
|
||||
@livewire('filament.core.database-notifications')
|
||||
|
||||
<x-filament::user-menu />
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
@if (! filament()->auth()->check())
|
||||
@livewire('filament.core.notifications')
|
||||
@endif
|
||||
</x-filament::layouts.base>
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace Filament\Context\Concerns;
|
||||
|
||||
use Filament\Http\Livewire\DatabaseNotifications;
|
||||
use Filament\Http\Livewire\GlobalSearch;
|
||||
use Filament\Http\Livewire\Notifications;
|
||||
use Filament\Pages\Page;
|
||||
@@ -262,6 +263,7 @@ trait HasComponents
|
||||
|
||||
public function registerLivewireComponents(): void
|
||||
{
|
||||
$this->queueLivewireComponentForRegistration(DatabaseNotifications::class);
|
||||
$this->queueLivewireComponentForRegistration(GlobalSearch::class);
|
||||
$this->queueLivewireComponentForRegistration(Notifications::class);
|
||||
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
namespace Filament\Http\Livewire;
|
||||
|
||||
use Filament\Facades\Filament;
|
||||
use Filament\Notifications\Http\Livewire\DatabaseNotifications as BaseComponent;
|
||||
use Illuminate\Contracts\Auth\Authenticatable;
|
||||
use Illuminate\Contracts\View\View;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class DatabaseNotifications extends BaseComponent
|
||||
{
|
||||
public function getUser(): Model | Authenticatable | null
|
||||
{
|
||||
return Filament::auth()->user();
|
||||
}
|
||||
|
||||
public function getPollingInterval(): ?string
|
||||
{
|
||||
return Filament::getDatabaseNotificationsPollingInterval();
|
||||
}
|
||||
|
||||
public function getTrigger(): View
|
||||
{
|
||||
return view('filament::components.layouts.app.topbar.database-notifications-trigger');
|
||||
}
|
||||
|
||||
public static function getName(): string
|
||||
{
|
||||
return 'filament.core.database-notifications';
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,6 @@ namespace Filament\Http\Livewire;
|
||||
use Filament\Facades\Filament;
|
||||
use Filament\Notifications\Http\Livewire\Notifications as BaseComponent;
|
||||
use Illuminate\Contracts\Auth\Authenticatable;
|
||||
use Illuminate\Contracts\View\View;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class Notifications extends BaseComponent
|
||||
@@ -15,21 +14,6 @@ class Notifications extends BaseComponent
|
||||
return Filament::auth()->user();
|
||||
}
|
||||
|
||||
public function hasDatabaseNotifications(): bool
|
||||
{
|
||||
return $this->getUser() && Filament::hasDatabaseNotifications();
|
||||
}
|
||||
|
||||
public function getPollingInterval(): ?string
|
||||
{
|
||||
return Filament::getDatabaseNotificationsPollingInterval();
|
||||
}
|
||||
|
||||
public function getDatabaseNotificationsTrigger(): View
|
||||
{
|
||||
return view('filament::components.layouts.app.topbar.database-notifications-trigger');
|
||||
}
|
||||
|
||||
public static function getName(): string
|
||||
{
|
||||
return 'filament.core.notifications';
|
||||
|
||||
@@ -4,8 +4,6 @@ title: Database notifications
|
||||
|
||||
## Overview
|
||||
|
||||
> To start, make sure the package is [installed](installation) - `@livewire('notifications')` should be in your Blade layout somewhere.
|
||||
|
||||
Before we start, make sure that the [Laravel notifications table](https://laravel.com/docs/notifications#database-prerequisites) is added to your database:
|
||||
|
||||
```bash
|
||||
@@ -16,12 +14,10 @@ php artisan notifications:table
|
||||
|
||||
> If you're using UUIDs for your `User` model, make sure that your `notifiable` column is using `uuidMorphs()`: `$table->uuidMorphs('notifiable')`.
|
||||
|
||||
To enable database notifications, add the following to a service provider:
|
||||
To add database notifications to your app, you must add a new Livewire component to your Blade layout:
|
||||
|
||||
```php
|
||||
use Filament\Notifications\Http\Livewire\Notifications;
|
||||
|
||||
Notifications::databaseNotifications();
|
||||
```blade
|
||||
@livewire('database-notifications')
|
||||
```
|
||||
|
||||
Database notifications will be rendered within a modal. To open this modal, you must have a "trigger" button in your view. Create a new trigger button component in your app, for instance at `/resources/views/notifications/database-notifications-trigger.blade.php`:
|
||||
@@ -37,13 +33,12 @@ Database notifications will be rendered within a modal. To open this modal, you
|
||||
In the service provider, point to this new trigger view:
|
||||
|
||||
```php
|
||||
use Filament\Notifications\Http\Livewire\Notifications;
|
||||
use Filament\Notifications\Http\Livewire\DatabaseNotifications;
|
||||
|
||||
Notifications::databaseNotifications();
|
||||
Notifications::databaseNotificationsTrigger('filament-notifications.database-notifications-trigger');
|
||||
DatabaseNotifications::trigger('filament-notifications.database-notifications-trigger');
|
||||
```
|
||||
|
||||
Now, simply move the `@livewire('notifications')` component to the position in your HTML that you wish to render the database notifications trigger button. It should appear, and open the database notifications modal when clicked!
|
||||
Now, click on the trigger button that is rendered in your view. A modal should appear containing your database notifications when clicked!
|
||||
|
||||
## Sending database notifications
|
||||
|
||||
@@ -100,19 +95,19 @@ Polling is the practice of periodically making a request to the server to check
|
||||
By default, Livewire polls for new notifications every 30 seconds:
|
||||
|
||||
```php
|
||||
use Filament\Notifications\Http\Livewire\Notifications;
|
||||
use Filament\Notifications\Http\Livewire\DatabaseNotifications;
|
||||
|
||||
Notifications::databaseNotifications();
|
||||
Notifications::databaseNotificationsPollingInterval('30s');
|
||||
DatabaseNotifications::databaseNotifications();
|
||||
DatabaseNotifications::databaseNotificationsPollingInterval('30s');
|
||||
```
|
||||
|
||||
You may completely disable polling if you wish:
|
||||
|
||||
```php
|
||||
use Filament\Notifications\Http\Livewire\Notifications;
|
||||
use Filament\Notifications\Http\Livewire\DatabaseNotifications;
|
||||
|
||||
Notifications::databaseNotifications();
|
||||
Notifications::databaseNotificationsPollingInterval(null);
|
||||
DatabaseNotifications::databaseNotifications();
|
||||
DatabaseNotifications::databaseNotificationsPollingInterval(null);
|
||||
```
|
||||
|
||||
### Using Echo to receive new database notifications with websockets
|
||||
|
||||
@@ -107,7 +107,7 @@ export default (Alpine) => {
|
||||
this.isShown = false
|
||||
|
||||
setTimeout(
|
||||
() => Livewire.emit('notificationClosed', notification.id),
|
||||
() => Livewire.emitUp('notificationClosed', notification.id),
|
||||
this.getTransitionDuration(),
|
||||
)
|
||||
},
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
@props([
|
||||
'channel',
|
||||
])
|
||||
|
||||
<div
|
||||
x-data="{}"
|
||||
x-init="
|
||||
window.addEventListener('EchoLoaded', () => {
|
||||
window.Echo.private(@js($channel))
|
||||
.listen('.database-notifications.sent', () => {
|
||||
setTimeout(() => $wire.call('$refresh'), 500)
|
||||
})
|
||||
})
|
||||
"
|
||||
{{ $attributes }}
|
||||
></div>
|
||||
@@ -8,11 +8,11 @@
|
||||
<div class="mt-2 text-sm">
|
||||
@if ($unreadNotificationsCount)
|
||||
<x-filament::link
|
||||
wire:click="markAllDatabaseNotificationsAsRead"
|
||||
wire:click="markAllNotificationsAsRead"
|
||||
color="gray"
|
||||
tag="button"
|
||||
tabindex="-1"
|
||||
wire:target="markAllDatabaseNotificationsAsRead"
|
||||
wire:target="markAllNotificationsAsRead"
|
||||
wire:loading.attr="disabled"
|
||||
class="disabled:opacity-70 disabled:pointer-events-none"
|
||||
>
|
||||
@@ -25,12 +25,12 @@
|
||||
@endif
|
||||
|
||||
<x-filament::link
|
||||
wire:click="clearDatabaseNotifications"
|
||||
wire:click="clearNotifications"
|
||||
x-on:click="close()"
|
||||
color="gray"
|
||||
tag="button"
|
||||
tabindex="-1"
|
||||
wire:target="clearDatabaseNotifications"
|
||||
wire:target="clearNotifications"
|
||||
wire:loading.attr="disabled"
|
||||
class="disabled:opacity-70 disabled:pointer-events-none"
|
||||
>
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
'py-2 ps-4 pe-2',
|
||||
'bg-primary-50 -mb-px dark:bg-gray-700' => $notification->unread(),
|
||||
])>
|
||||
{{ $this->getNotificationFromDatabaseRecord($notification)->inline() }}
|
||||
{{ $this->getNotification($notification)->inline() }}
|
||||
</div>
|
||||
</div>
|
||||
@endforeach
|
||||
|
||||
@@ -10,9 +10,6 @@
|
||||
.notification((notification) => {
|
||||
setTimeout(() => $wire.handleBroadcastNotification(notification), 500)
|
||||
})
|
||||
.listen('.database-notifications.sent', () => {
|
||||
setTimeout(() => $wire.call('$refresh'), 500)
|
||||
})
|
||||
})
|
||||
|
||||
if (window.Echo) {
|
||||
|
||||
+8
-4
@@ -1,6 +1,6 @@
|
||||
@php
|
||||
$notifications = $this->getDatabaseNotifications();
|
||||
$unreadNotificationsCount = $this->getUnreadDatabaseNotificationsCount();
|
||||
$notifications = $this->getNotifications();
|
||||
$unreadNotificationsCount = $this->getUnreadNotificationsCount();
|
||||
@endphp
|
||||
|
||||
<div
|
||||
@@ -9,9 +9,9 @@
|
||||
@endif
|
||||
class="flex items-center"
|
||||
>
|
||||
@if ($databaseNotificationsTrigger = $this->getDatabaseNotificationsTrigger())
|
||||
@if ($trigger = $this->getTrigger())
|
||||
<x-filament-notifications::database.trigger>
|
||||
{{ $databaseNotificationsTrigger->with(['unreadNotificationsCount' => $unreadNotificationsCount]) }}
|
||||
{{ $trigger->with(['unreadNotificationsCount' => $unreadNotificationsCount]) }}
|
||||
</x-filament-notifications::database.trigger>
|
||||
@endif
|
||||
|
||||
@@ -19,4 +19,8 @@
|
||||
:notifications="$notifications"
|
||||
:unread-notifications-count="$unreadNotificationsCount"
|
||||
/>
|
||||
|
||||
@if ($broadcastChannel = $this->getBroadcastChannel())
|
||||
<x-filament-notifications::database.echo :channel="$broadcastChannel" />
|
||||
@endif
|
||||
</div>
|
||||
@@ -1,3 +1,4 @@
|
||||
@php use Filament\Notifications\Http\Livewire\Notifications; @endphp
|
||||
<x-filament-notifications::notification
|
||||
:notification="$notification"
|
||||
@class([
|
||||
@@ -6,7 +7,7 @@
|
||||
])
|
||||
:x-transition:enter-start="\Illuminate\Support\Arr::toCssClasses([
|
||||
'opacity-0',
|
||||
match (static::$horizontalAlignment) {
|
||||
($this instanceof Notifications) ? match (static::$horizontalAlignment) {
|
||||
'left' => '-translate-x-12',
|
||||
'right' => 'translate-x-12',
|
||||
'center' => match (static::$verticalAlignment) {
|
||||
@@ -14,7 +15,7 @@
|
||||
'bottom' => 'translate-y-12',
|
||||
'center' => null,
|
||||
},
|
||||
},
|
||||
} : null,
|
||||
])"
|
||||
x-transition:leave-end="scale-95 opacity-0"
|
||||
>
|
||||
|
||||
@@ -20,10 +20,6 @@
|
||||
@endforeach
|
||||
</div>
|
||||
|
||||
@if ($this->hasDatabaseNotifications())
|
||||
<x-filament-notifications::database />
|
||||
@endif
|
||||
|
||||
@if ($broadcastChannel = $this->getBroadcastChannel())
|
||||
<x-filament-notifications::echo :channel="$broadcastChannel" />
|
||||
@endif
|
||||
|
||||
@@ -0,0 +1,134 @@
|
||||
<?php
|
||||
|
||||
namespace Filament\Notifications\Http\Livewire;
|
||||
|
||||
use Carbon\CarbonInterface;
|
||||
use Filament\Notifications\Collection;
|
||||
use Filament\Notifications\Notification;
|
||||
use Illuminate\Contracts\Auth\Authenticatable;
|
||||
use Illuminate\Contracts\View\View;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\Relation;
|
||||
use Illuminate\Notifications\DatabaseNotification;
|
||||
use Illuminate\Notifications\DatabaseNotificationCollection;
|
||||
use Illuminate\Notifications\NotificationCollection;
|
||||
use Livewire\Component;
|
||||
|
||||
class DatabaseNotifications extends Component
|
||||
{
|
||||
/**
|
||||
* @var array<string, string>
|
||||
*/
|
||||
protected $listeners = [
|
||||
'notificationClosed' => 'removeNotification',
|
||||
];
|
||||
|
||||
public static ?string $trigger = null;
|
||||
|
||||
public static ?string $pollingInterval = '30s';
|
||||
|
||||
public function removeNotification(string $id): void
|
||||
{
|
||||
$this->getNotificationsQuery()
|
||||
->where('id', $id)
|
||||
->delete();
|
||||
}
|
||||
|
||||
public function clearNotifications(): void
|
||||
{
|
||||
$this->getNotificationsQuery()->delete();
|
||||
}
|
||||
|
||||
public function markAllNotificationsAsRead(): void
|
||||
{
|
||||
$this->getUnreadNotificationsQuery()->update(['read_at' => now()]);
|
||||
}
|
||||
|
||||
public function getNotifications(): DatabaseNotificationCollection
|
||||
{
|
||||
/** @phpstan-ignore-next-line */
|
||||
return $this->getNotificationsQuery()->get();
|
||||
}
|
||||
|
||||
public function getNotificationsQuery(): Builder | Relation
|
||||
{
|
||||
/** @phpstan-ignore-next-line */
|
||||
return $this->getUser()->notifications()->where('data->format', 'filament');
|
||||
}
|
||||
|
||||
public function getUnreadNotificationsQuery(): Builder | Relation
|
||||
{
|
||||
/** @phpstan-ignore-next-line */
|
||||
return $this->getNotificationsQuery()->unread();
|
||||
}
|
||||
|
||||
public function getUnreadNotificationsCount(): int
|
||||
{
|
||||
return $this->getUnreadNotificationsQuery()->count();
|
||||
}
|
||||
|
||||
public function getPollingInterval(): ?string
|
||||
{
|
||||
return static::$pollingInterval;
|
||||
}
|
||||
|
||||
public function getTrigger(): ?View
|
||||
{
|
||||
$viewPath = static::$trigger;
|
||||
|
||||
if (blank($viewPath)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return view($viewPath);
|
||||
}
|
||||
|
||||
public function getUser(): Model | Authenticatable | null
|
||||
{
|
||||
return auth()->user();
|
||||
}
|
||||
|
||||
public function getBroadcastChannel(): ?string
|
||||
{
|
||||
$user = $this->getUser();
|
||||
|
||||
if (! $user) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (method_exists($user, 'receivesBroadcastNotificationsOn')) {
|
||||
return $user->receivesBroadcastNotificationsOn();
|
||||
}
|
||||
|
||||
$userClass = str_replace('\\', '.', $user::class);
|
||||
|
||||
return "{$userClass}.{$user->getKey()}";
|
||||
}
|
||||
|
||||
public function getNotification(DatabaseNotification $notification): Notification
|
||||
{
|
||||
return Notification::fromDatabase($notification)
|
||||
->date($this->formatNotificationDate($notification->getAttributeValue('created_at')));
|
||||
}
|
||||
|
||||
protected function formatNotificationDate(CarbonInterface $date): string
|
||||
{
|
||||
return $date->diffForHumans();
|
||||
}
|
||||
|
||||
public static function trigger(?string $trigger): void
|
||||
{
|
||||
static::$trigger = $trigger;
|
||||
}
|
||||
|
||||
public static function pollingInterval(?string $interval): void
|
||||
{
|
||||
static::$pollingInterval = $interval;
|
||||
}
|
||||
|
||||
public function render(): View
|
||||
{
|
||||
return view('filament-notifications::database-notifications');
|
||||
}
|
||||
}
|
||||
@@ -30,12 +30,6 @@ class Notifications extends Component
|
||||
'notificationClosed' => 'removeNotification',
|
||||
];
|
||||
|
||||
public static bool $hasDatabaseNotifications = false;
|
||||
|
||||
public static ?string $databaseNotificationsTrigger = null;
|
||||
|
||||
public static ?string $databaseNotificationsPollingInterval = '30s';
|
||||
|
||||
public static string $horizontalAlignment = 'right';
|
||||
|
||||
public static string $verticalAlignment = 'top';
|
||||
@@ -67,50 +61,11 @@ class Notifications extends Component
|
||||
|
||||
public function removeNotification(string $id): void
|
||||
{
|
||||
if ($this->notifications->has($id)) {
|
||||
$this->notifications->forget($id);
|
||||
}
|
||||
|
||||
if (! $this->hasDatabaseNotifications()) {
|
||||
if (! $this->notifications->has($id)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->getDatabaseNotificationsQuery()
|
||||
->where('id', $id)
|
||||
->delete();
|
||||
}
|
||||
|
||||
public function clearDatabaseNotifications(): void
|
||||
{
|
||||
$this->getDatabaseNotificationsQuery()->delete();
|
||||
}
|
||||
|
||||
public function markAllDatabaseNotificationsAsRead(): void
|
||||
{
|
||||
$this->getUnreadDatabaseNotificationsQuery()->update(['read_at' => now()]);
|
||||
}
|
||||
|
||||
public function getDatabaseNotifications(): DatabaseNotificationCollection
|
||||
{
|
||||
/** @phpstan-ignore-next-line */
|
||||
return $this->getDatabaseNotificationsQuery()->get();
|
||||
}
|
||||
|
||||
public function getDatabaseNotificationsQuery(): Builder | Relation
|
||||
{
|
||||
/** @phpstan-ignore-next-line */
|
||||
return $this->getUser()->notifications()->where('data->format', 'filament');
|
||||
}
|
||||
|
||||
public function getUnreadDatabaseNotificationsQuery(): Builder | Relation
|
||||
{
|
||||
/** @phpstan-ignore-next-line */
|
||||
return $this->getDatabaseNotificationsQuery()->unread();
|
||||
}
|
||||
|
||||
public function getUnreadDatabaseNotificationsCount(): int
|
||||
{
|
||||
return $this->getUnreadDatabaseNotificationsQuery()->count();
|
||||
$this->notifications->forget($id);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -133,27 +88,6 @@ class Notifications extends Component
|
||||
);
|
||||
}
|
||||
|
||||
public function hasDatabaseNotifications(): bool
|
||||
{
|
||||
return $this->getUser() && static::$hasDatabaseNotifications;
|
||||
}
|
||||
|
||||
public function getPollingInterval(): ?string
|
||||
{
|
||||
return static::$databaseNotificationsPollingInterval;
|
||||
}
|
||||
|
||||
public function getDatabaseNotificationsTrigger(): ?View
|
||||
{
|
||||
$viewPath = static::$databaseNotificationsTrigger;
|
||||
|
||||
if (blank($viewPath)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return view($viewPath);
|
||||
}
|
||||
|
||||
public function getUser(): Model | Authenticatable | null
|
||||
{
|
||||
return auth()->user();
|
||||
@@ -176,32 +110,6 @@ class Notifications extends Component
|
||||
return "{$userClass}.{$user->getKey()}";
|
||||
}
|
||||
|
||||
public function getNotificationFromDatabaseRecord(DatabaseNotification $notification): Notification
|
||||
{
|
||||
return Notification::fromDatabase($notification)
|
||||
->date($this->formatNotificationDate($notification->getAttributeValue('created_at')));
|
||||
}
|
||||
|
||||
protected function formatNotificationDate(CarbonInterface $date): string
|
||||
{
|
||||
return $date->diffForHumans();
|
||||
}
|
||||
|
||||
public static function databaseNotifications(bool $condition = true): void
|
||||
{
|
||||
static::$hasDatabaseNotifications = $condition;
|
||||
}
|
||||
|
||||
public static function databaseNotificationsTrigger(?string $trigger): void
|
||||
{
|
||||
static::$databaseNotificationsTrigger = $trigger;
|
||||
}
|
||||
|
||||
public static function databaseNotificationsPollingInterval(?string $interval): void
|
||||
{
|
||||
static::$databaseNotificationsPollingInterval = $interval;
|
||||
}
|
||||
|
||||
public static function horizontalAlignment(string $alignment): void
|
||||
{
|
||||
static::$horizontalAlignment = $alignment;
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace Filament\Notifications;
|
||||
|
||||
use Filament\Notifications\Http\Livewire\DatabaseNotifications;
|
||||
use Filament\Notifications\Http\Livewire\Notifications;
|
||||
use Filament\Notifications\Testing\TestsNotifications;
|
||||
use Filament\Support\Assets\AssetManager;
|
||||
@@ -35,6 +36,8 @@ class NotificationsServiceProvider extends PackageServiceProvider
|
||||
|
||||
public function packageBooted(): void
|
||||
{
|
||||
Livewire::component('database-notifications', DatabaseNotifications::class);
|
||||
|
||||
Livewire::component('notifications', Notifications::class);
|
||||
|
||||
Livewire::listen('component.dehydrate', function (Component $component, Response $response): Response {
|
||||
|
||||
Reference in New Issue
Block a user