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:
14
CHANGELOG.md
14
CHANGELOG.md
@@ -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
|
||||
|
||||
95
README.md
95
README.md
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user