This commit is contained in:
Dan Harrin
2023-04-22 15:31:08 +01:00
parent 25272882ee
commit 1dafe346d5
18 changed files with 224 additions and 152 deletions
@@ -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) {
@@ -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 {