git commit -m "feat: Add disk support and fix Filament v4 compatibility

- Add disk() and visibility() for Storage integration
- Rename gap() to imageGap() to avoid Filament v4 conflict
- Add cursor-pointer on image hover
- Remove empty text display"
This commit is contained in:
al-saloul
2025-12-11 12:45:59 +03:00
parent 4084c97d92
commit 037294d62b
4 changed files with 115 additions and 89 deletions

View File

@@ -2,6 +2,20 @@
All notable changes to `filament-image-gallery` will be documented in this file.
## v2.0.1 - 2024-12-11
### Added
- `disk()` method for Storage integration (both Column and Entry)
- `visibility()` method for private files with temporary URLs
- Cursor pointer on image hover
### Changed
- Renamed `gap()` to `imageGap()` in Entry to avoid Filament v4 conflict
- Removed empty text display when no images
### Fixed
- Filament v4 compatibility for ImageGalleryEntry
## v2.0.0 - 2024-12-11
### Added

View File

@@ -18,9 +18,9 @@ A Filament plugin for displaying image galleries with zoom, rotate, flip, and fu
- 📋 **Infolist Entry** - Show image galleries in infolists with horizontal scrolling
- 🧩 **Blade Component** - Use standalone in any Blade view
- 🔍 **Viewer.js Integration** - Zoom, rotate, flip, and fullscreen image viewing
- 💾 **Storage Disk Support** - Works with any Laravel filesystem disk
- 🌙 **Dark Mode Support** - Works seamlessly with dark mode
- 🌐 **RTL Support** - Full right-to-left language support
- 🌍 **Translations** - English and Arabic translations included
## Installation
@@ -43,6 +43,7 @@ use Alsaloul\ImageGallery\Tables\Columns\ImageGalleryColumn;
ImageGalleryColumn::make('images')
->getStateUsing(fn ($record) => $record->images->pluck('image')->toArray())
->disk(config('filesystems.default'))
->circle()
->stacked(3)
->ring(2, '#3b82f6')
@@ -54,16 +55,17 @@ ImageGalleryColumn::make('images')
| Method | Description | Default |
|--------|-------------|---------|
| `disk(string)` | Storage disk for images | `null` |
| `visibility(string)` | `'public'` or `'private'` (for temporary URLs) | `'public'` |
| `thumbWidth(int)` | Thumbnail width in pixels | `40` |
| `thumbHeight(int)` | Thumbnail height in pixels | `40` |
| `limit(int\|null)` | Maximum images to show | `3` |
| `stacked(int\|bool)` | Stack thumbnails with overlap. Pass `int` for custom spacing (e.g., `stacked(5)`) | `false` |
| `stacked(int\|bool)` | Stack thumbnails. Pass `int` for custom spacing | `false` |
| `square(bool)` | Square shape with rounded corners | `false` |
| `circle(bool)` | Circular shape | `false` |
| `ring(int, string)` | Add border ring with width and optional color | `1, null` |
| `ring(int, string)` | Border ring with width and color | `1, null` |
| `ringColor(string)` | Set ring color separately | `null` |
| `limitedRemainingText(bool)` | Show "+N" badge for remaining | `true` |
| `emptyText(string)` | Text when no images | `'No images'` |
---
@@ -73,22 +75,22 @@ ImageGalleryColumn::make('images')
use Alsaloul\ImageGallery\Infolists\Entries\ImageGalleryEntry;
ImageGalleryEntry::make('images')
->disk(config('filesystems.default'))
->thumbWidth(128)
->thumbHeight(128)
->gap('gap-4')
->emptyText('No images available'),
->imageGap('gap-4'),
```
#### Available Methods
| Method | Description | Default |
|--------|-------------|---------|
| `disk(string)` | Storage disk for images | `null` |
| `visibility(string)` | `'public'` or `'private'` | `'public'` |
| `thumbWidth(int)` | Thumbnail width in pixels | `128` |
| `thumbHeight(int)` | Thumbnail height in pixels | `128` |
| `gap(string)` | Tailwind gap class | `'gap-4'` |
| `imageGap(string)` | Tailwind gap class | `'gap-4'` |
| `rounded(string)` | Tailwind rounded class | `'rounded-lg'` |
| `zoomCursor(bool)` | Show zoom cursor on hover | `true` |
| `emptyText(string)` | Text when no images | `'No images'` |
| `wrapperClass(string)` | Additional wrapper classes | `null` |
---
@@ -97,8 +99,7 @@ ImageGalleryEntry::make('images')
```blade
<x-image-gallery::image-gallery
:images="$trip->images"
empty-text="No images for this trip"
:images="$model->images"
:thumb-width="150"
:thumb-height="150"
rounded="rounded-xl"
@@ -106,25 +107,26 @@ ImageGalleryEntry::make('images')
/>
```
#### Available Props
| Prop | Description | Default |
|------|-------------|---------|
| `images` | Array of image URLs or objects with `image` property | `[]` |
| `emptyText` | Text when no images | Translated message |
| `thumbWidth` | Thumbnail width in pixels | `128` |
| `thumbHeight` | Thumbnail height in pixels | `128` |
| `rounded` | Tailwind rounded class | `'rounded-lg'` |
| `gap` | Tailwind gap class | `'gap-4'` |
| `wrapperClass` | Additional wrapper classes | `''` |
| `zoomCursor` | Show zoom cursor on hover | `true` |
| `id` | Custom gallery ID | Auto-generated |
---
## Examples
### Circular Stacked Images with Blue Ring
### With Storage Disk
```php
ImageGalleryColumn::make('images')
->disk('s3')
->circle()
->stacked(3)
->limit(3)
// For private files
ImageGalleryColumn::make('images')
->disk('s3')
->visibility('private') // Generates temporary URLs
->limit(3)
```
### Circular Stacked with Ring
```php
ImageGalleryColumn::make('images')
->circle()
@@ -133,50 +135,11 @@ ImageGalleryColumn::make('images')
->limit(3)
```
### Square Images Without Stacking
```php
ImageGalleryColumn::make('images')
->square()
->limit(5)
```
### Custom Overlap Spacing
```php
ImageGalleryColumn::make('images')
->circle()
->stacked(5) // -space-x-5
->limit(4)
```
---
## Image Data Format
All components accept images in multiple formats:
```php
// Array of URLs
$images = ['https://example.com/image1.jpg', 'https://example.com/image2.jpg'];
// Array of objects with 'image' property
$images = [['image' => 'https://example.com/image1.jpg']];
// Eloquent collection with 'image' attribute
$images = $trip->images;
```
## Upgrading
### From 1.x to 2.x
Version 2.x adds support for Filament v4 while maintaining backward compatibility with v3:
- PHP requirement updated to `^8.2`
- No breaking changes in API
## Changelog
Please see [CHANGELOG](CHANGELOG.md) for more information on what has changed recently.
Please see [CHANGELOG](CHANGELOG.md) for more information.
## Credits

View File

@@ -2,7 +2,7 @@
$urls = $getImageUrls();
$width = $getThumbWidth();
$height = $getThumbHeight();
$gap = $getGap();
$gap = $getImageGap();
$rounded = $getRounded();
$zoomCursor = $hasZoomCursor();
$wrapperClass = $getWrapperClass() ?? '';
@@ -15,22 +15,18 @@
class="image-gallery flex overflow-x-auto {{ $gap }} my-2 pb-2 select-none {{ $wrapperClass }}"
data-viewer-gallery
>
@forelse($urls as $src)
@foreach($urls as $src)
<img
src="{{ $src }}"
loading="lazy"
class="{{ $rounded }} shadow object-cover border border-gray-200 dark:border-gray-700 hover:scale-105 transition {{ $zoomCursor ? 'cursor-zoom-in' : '' }}"
class="{{ $rounded }} shadow object-cover border border-gray-200 dark:border-gray-700 hover:scale-105 transition cursor-pointer"
style="width: {{ $width }}px; height: {{ $height }}px; flex-shrink: 0;"
alt="image"
/>
@empty
<span class="text-gray-400 dark:text-gray-500">{{ $getEmptyText() }}</span>
@endforelse
@endforeach
</div>
</x-dynamic-component>
@once
@push('scripts')
<x-image-gallery::viewer-script />
@endpush
<x-image-gallery::viewer-script />
@endonce

View File

@@ -4,6 +4,7 @@ namespace Alsaloul\ImageGallery\Infolists\Entries;
use Closure;
use Filament\Infolists\Components\Entry;
use Illuminate\Support\Facades\Storage;
class ImageGalleryEntry extends Entry
{
@@ -13,7 +14,7 @@ class ImageGalleryEntry extends Entry
protected int | Closure $thumbHeight = 128;
protected string | Closure $gap = 'gap-4';
protected string | Closure $imageGap = 'gap-4';
protected string | Closure $rounded = 'rounded-lg';
@@ -23,6 +24,10 @@ class ImageGalleryEntry extends Entry
protected string | Closure | null $wrapperClass = null;
protected string | Closure | null $disk = null;
protected string | Closure $visibility = 'public';
public function thumbWidth(int | Closure $width): static
{
$this->thumbWidth = $width;
@@ -47,16 +52,16 @@ class ImageGalleryEntry extends Entry
return $this->evaluate($this->thumbHeight);
}
public function gap(string | Closure $gap): static
public function imageGap(string | Closure $gap): static
{
$this->gap = $gap;
$this->imageGap = $gap;
return $this;
}
public function getGap(): string
public function getImageGap(): string
{
return $this->evaluate($this->gap);
return $this->evaluate($this->imageGap);
}
public function rounded(string | Closure $rounded): static
@@ -107,6 +112,30 @@ class ImageGalleryEntry extends Entry
return $this->evaluate($this->wrapperClass);
}
public function disk(string | Closure | null $disk): static
{
$this->disk = $disk;
return $this;
}
public function getDisk(): ?string
{
return $this->evaluate($this->disk);
}
public function visibility(string | Closure $visibility): static
{
$this->visibility = $visibility;
return $this;
}
public function getVisibility(): string
{
return $this->evaluate($this->visibility);
}
/**
* Get normalized image URLs from state
*/
@@ -118,18 +147,42 @@ class ImageGalleryEntry extends Entry
return [];
}
return collect($state)->map(function ($item) {
$disk = $this->getDisk();
$visibility = $this->getVisibility();
return collect($state)->map(function ($item) use ($disk, $visibility) {
$path = null;
if (is_string($item)) {
return $item;
}
if (is_array($item)) {
return $item['image'] ?? $item['url'] ?? null;
}
if (is_object($item)) {
return $item->image ?? $item->url ?? null;
$path = $item;
} elseif (is_array($item)) {
$path = $item['image'] ?? $item['url'] ?? $item['path'] ?? null;
} elseif (is_object($item)) {
$path = $item->image ?? $item->url ?? $item->path ?? null;
}
return null;
if (empty($path)) {
return null;
}
// If it's already a full URL, return as-is
if (filter_var($path, FILTER_VALIDATE_URL)) {
return $path;
}
// If disk is specified, generate URL from storage
if ($disk) {
$storage = Storage::disk($disk);
if ($visibility === 'private') {
return $storage->temporaryUrl($path, now()->addMinutes(5));
}
return $storage->url($path);
}
// Default: return path as-is (might be relative URL)
return $path;
})->filter()->values()->toArray();
}
}