WIP
This commit is contained in:
@@ -37,9 +37,33 @@ class ImageController extends Controller
|
|||||||
/**
|
/**
|
||||||
* Display the specified resource.
|
* Display the specified resource.
|
||||||
*/
|
*/
|
||||||
public function show(Image $image) : BinaryFileResponse
|
public function show(Image $image, string $size = 'original') : BinaryFileResponse
|
||||||
{
|
{
|
||||||
return response()->file(Storage::disk('images')->path($image->album->id . '/thumbnail/' . $image->id . '.avif'));
|
return response()->file(Storage::disk('images')->path($image->album->id . '/' . $size . '/' . $image->id . '.avif'));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Display the thumbnail of the specified resource.
|
||||||
|
*/
|
||||||
|
public function thumbnail(Image $image) : BinaryFileResponse
|
||||||
|
{
|
||||||
|
return $this->show($image, 'thumbnail');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Display the lightbox of the specified resource.
|
||||||
|
*/
|
||||||
|
public function lightbox(Image $image) : BinaryFileResponse
|
||||||
|
{
|
||||||
|
return $this->show($image, 'lightbox');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Display the lightbox of the specified resource.
|
||||||
|
*/
|
||||||
|
public function download(Image $image)
|
||||||
|
{
|
||||||
|
return Storage::disk('images')->download($image->album_id . '/original/' . $image->id . '.avif', name: $image->album->name . '_' . $image->id . '.avif');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -33,16 +33,11 @@ class GenerateFullscreen implements ShouldQueue
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$image = InterventionImage::read($this->source);
|
$lightbox = InterventionImage::read($this->source);
|
||||||
if($image->width() >= $image->height()) {
|
$lightbox = $lightbox->scaleDown(height: config('gallery.image.fullscreen.height', 2000));
|
||||||
// landscape
|
|
||||||
$image->scaleDown(width: config('gallery.image.fullscreen.maxWidth', 2000));
|
|
||||||
} else {
|
|
||||||
// portrait
|
|
||||||
$image->scaleDown(height: config('gallery.image.fullscreen.maxHeight', 2000));
|
|
||||||
}
|
|
||||||
|
|
||||||
Storage::disk('images')->put($this->destination, $image->toAvif(config('gallery.image.quality', 80)));
|
Storage::disk('images')->put($this->destination, $lightbox->toAvif(config('gallery.image.quality', 80)));
|
||||||
|
$this->image->setLightboxSize($lightbox->width(), $lightbox->height());
|
||||||
}
|
}
|
||||||
|
|
||||||
public function failed(?Throwable $exception): void
|
public function failed(?Throwable $exception): void
|
||||||
|
|||||||
@@ -33,16 +33,10 @@ class GenerateThumbnail implements ShouldQueue
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$image = InterventionImage::read($this->source);
|
$thumbnail = InterventionImage::read($this->source);
|
||||||
if($image->width() >= $image->height()) {
|
$thumbnail = $thumbnail->scaleDown(height: config('gallery.image.thumbnail.height', 640), width: config('gallery.image.thumbnail.width', 640));
|
||||||
// landscape
|
|
||||||
$image->scaleDown(width: config('gallery.image.thumbnail.maxWidth', 150));
|
|
||||||
} else {
|
|
||||||
// portrait
|
|
||||||
$image->scaleDown(height: config('gallery.image.thumbnail.maxHeight', 150));
|
|
||||||
}
|
|
||||||
|
|
||||||
Storage::disk('images')->put($this->destination, $image->toAvif(config('gallery.image.quality', 80)));
|
Storage::disk('images')->put($this->destination, $thumbnail->toAvif(config('gallery.image.quality', 80)));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function failed(?Throwable $exception): void
|
public function failed(?Throwable $exception): void
|
||||||
|
|||||||
@@ -18,6 +18,8 @@ class Image extends Model implements HasThumbnail
|
|||||||
*/
|
*/
|
||||||
protected $attributes = [
|
protected $attributes = [
|
||||||
'isCover' => false,
|
'isCover' => false,
|
||||||
|
'lightboxWidth' => 0,
|
||||||
|
'lightboxHeight' => 0,
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -25,7 +27,7 @@ class Image extends Model implements HasThumbnail
|
|||||||
*
|
*
|
||||||
* @var array
|
* @var array
|
||||||
*/
|
*/
|
||||||
protected $fillable = ['album_id'];
|
protected $fillable = ['album_id', 'lightboxWidth', 'lightboxHeight'];
|
||||||
|
|
||||||
public function album(): BelongsTo
|
public function album(): BelongsTo
|
||||||
{
|
{
|
||||||
@@ -33,6 +35,24 @@ class Image extends Model implements HasThumbnail
|
|||||||
}
|
}
|
||||||
|
|
||||||
public function getThumbnail() : string {
|
public function getThumbnail() : string {
|
||||||
return route('image.show', $this);
|
return route('image.thumbnail', $this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getDownload() : string {
|
||||||
|
return route('image.download', $this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setLightboxSize(int $width, int $height) : void {
|
||||||
|
$this->lightboxWidth = $width;
|
||||||
|
$this->lightboxHeight = $height;
|
||||||
|
$this->save();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getLightboxAttribute() : array {
|
||||||
|
return [
|
||||||
|
'location' => route('image.lightbox', $this),
|
||||||
|
'width' => $this->lightboxWidth,
|
||||||
|
'height' => $this->lightboxHeight,
|
||||||
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,8 @@ return new class extends Migration
|
|||||||
$table->id();
|
$table->id();
|
||||||
$table->timestamps();
|
$table->timestamps();
|
||||||
$table->boolean('isCover');
|
$table->boolean('isCover');
|
||||||
|
$table->bigInteger(column: 'lightboxWidth', unsigned: true)->default(0);
|
||||||
|
$table->bigInteger(column: 'lightboxHeight', unsigned: true)->default(0);
|
||||||
$table->foreignIdFor(Album::class);
|
$table->foreignIdFor(Album::class);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
11
package-lock.json
generated
11
package-lock.json
generated
@@ -9,7 +9,8 @@
|
|||||||
"filepond-plugin-file-validate-size": "^2.2.8",
|
"filepond-plugin-file-validate-size": "^2.2.8",
|
||||||
"filepond-plugin-file-validate-type": "^1.2.9",
|
"filepond-plugin-file-validate-type": "^1.2.9",
|
||||||
"filepond-plugin-image-exif-orientation": "^1.0.11",
|
"filepond-plugin-image-exif-orientation": "^1.0.11",
|
||||||
"filepond-plugin-image-preview": "^4.6.12"
|
"filepond-plugin-image-preview": "^4.6.12",
|
||||||
|
"photoswipe": "^5.4.4"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@tailwindcss/forms": "^0.5.7",
|
"@tailwindcss/forms": "^0.5.7",
|
||||||
@@ -1662,6 +1663,14 @@
|
|||||||
"url": "https://github.com/sponsors/isaacs"
|
"url": "https://github.com/sponsors/isaacs"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/photoswipe": {
|
||||||
|
"version": "5.4.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/photoswipe/-/photoswipe-5.4.4.tgz",
|
||||||
|
"integrity": "sha512-WNFHoKrkZNnvFFhbHL93WDkW3ifwVOXSW3w1UuZZelSmgXpIGiZSNlZJq37rR8YejqME2rHs9EhH9ZvlvFH2NA==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.12.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/picocolors": {
|
"node_modules/picocolors": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
|
||||||
|
|||||||
@@ -19,6 +19,7 @@
|
|||||||
"filepond-plugin-file-validate-size": "^2.2.8",
|
"filepond-plugin-file-validate-size": "^2.2.8",
|
||||||
"filepond-plugin-file-validate-type": "^1.2.9",
|
"filepond-plugin-file-validate-type": "^1.2.9",
|
||||||
"filepond-plugin-image-exif-orientation": "^1.0.11",
|
"filepond-plugin-image-exif-orientation": "^1.0.11",
|
||||||
"filepond-plugin-image-preview": "^4.6.12"
|
"filepond-plugin-image-preview": "^4.6.12",
|
||||||
|
"photoswipe": "^5.4.4"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
@import 'filepond/dist/filepond.min.css';
|
@import 'filepond/dist/filepond.min.css';
|
||||||
@import 'filepond-plugin-image-preview/dist/filepond-plugin-image-preview.css';
|
@import 'filepond-plugin-image-preview/dist/filepond-plugin-image-preview.css';
|
||||||
|
@import 'photoswipe/dist/photoswipe.css';
|
||||||
|
|
||||||
@tailwind base;
|
@tailwind base;
|
||||||
@tailwind components;
|
@tailwind components;
|
||||||
@@ -31,3 +32,14 @@
|
|||||||
@apply text-gray-900 dark:text-white;
|
@apply text-gray-900 dark:text-white;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.pswp__bg {
|
||||||
|
@apply backdrop-blur-md bg-white/30 dark:bg-gray-800/30;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pswp__top-bar {
|
||||||
|
@apply fixed bottom-2 top-auto w-auto left-1/2 -translate-x-1/2 text-gray-500 bg-white rounded-lg shadow dark:text-gray-400 dark:bg-gray-800;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pswp__button {
|
||||||
|
width: 60px;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,4 +1,7 @@
|
|||||||
import './bootstrap';
|
import './bootstrap';
|
||||||
|
|
||||||
|
import PhotoSwipeLightbox from 'photoswipe/lightbox';
|
||||||
|
|
||||||
import * as FilePond from 'filepond';
|
import * as FilePond from 'filepond';
|
||||||
import FilePondPluginImageExifOrientation from 'filepond-plugin-image-exif-orientation';
|
import FilePondPluginImageExifOrientation from 'filepond-plugin-image-exif-orientation';
|
||||||
import FilePondPluginImagePreview from 'filepond-plugin-image-preview';
|
import FilePondPluginImagePreview from 'filepond-plugin-image-preview';
|
||||||
@@ -19,4 +22,59 @@ document.addEventListener('alpine:init', () => {
|
|||||||
this.states[state] = value;
|
this.states[state] = value;
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const lightbox = new PhotoSwipeLightbox({
|
||||||
|
gallery: '#album',
|
||||||
|
children: 'a',
|
||||||
|
bgOpacity: 1,
|
||||||
|
arrowPrevSVG: '<svg class="w-12 h-12 text-gray-800 dark:text-white" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" viewBox="0 0 24 24"><path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m15 19-7-7 7-7"/></svg>',
|
||||||
|
arrowNextSVG: '<svg class="w-12 h-12 text-gray-800 dark:text-white" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" viewBox="0 0 24 24"><path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m9 5 7 7-7 7"/></svg>',
|
||||||
|
closeSVG: '<div class="bg-transparent p-2 inline-flex items-center me-2 hover:bg-gray-100 rounded-lg dark:bg-gray-800 dark:hover:bg-gray-700"><svg class="w-6 h-6 text-gray-800 dark:text-white" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" viewBox="0 0 24 24"><path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18 17.94 6M18 18 6.06 6"/></svg></div>',
|
||||||
|
zoomSVG: '<div class="bg-transparent p-2 inline-flex items-center me-2 hover:bg-gray-100 rounded-lg dark:bg-gray-800 dark:hover:bg-gray-700"><svg class="w-6 h-6 text-gray-800 dark:text-white" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" viewBox="0 0 24 24"><path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M16.872 9.687 20 6.56 17.44 4 4 17.44 6.56 20 16.873 9.687Zm0 0-2.56-2.56M6 7v2m0 0v2m0-2H4m2 0h2m7 7v2m0 0v2m0-2h-2m2 0h2M8 4h.01v.01H8V4Zm2 2h.01v.01H10V6Zm2-2h.01v.01H12V4Zm8 8h.01v.01H20V12Zm-2 2h.01v.01H18V14Zm2 2h.01v.01H20V16Z"/></svg></div>',
|
||||||
|
pswpModule: () => import('photoswipe')
|
||||||
|
});
|
||||||
|
|
||||||
|
lightbox.on('uiRegister', function() {
|
||||||
|
lightbox.pswp.ui.registerElement({
|
||||||
|
name: 'download-button',
|
||||||
|
order: 9,
|
||||||
|
isButton: true,
|
||||||
|
tagName: 'button',
|
||||||
|
html: '<div class="bg-transparent p-2 inline-flex items-center me-2 hover:bg-gray-100 rounded-lg dark:bg-gray-800 dark:hover:bg-gray-700"><svg class="w-6 h-6 text-gray-800 dark:text-white" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" viewBox="0 0 24 24"><path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 13V4M7 14H5a1 1 0 0 0-1 1v4a1 1 0 0 0 1 1h14a1 1 0 0 0 1-1v-4a1 1 0 0 0-1-1h-2m-1-5-4 5-4-5m9 8h.01"/></svg></div>',
|
||||||
|
|
||||||
|
onInit: (el, pswp) => {
|
||||||
|
el.setAttribute('download', '');
|
||||||
|
el.setAttribute('target', '_blank');
|
||||||
|
el.setAttribute('rel', 'noopener');
|
||||||
|
|
||||||
|
pswp.on('change', () => {
|
||||||
|
el.href = pswp.currSlide.data.element.dataset.pswpDownload;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
lightbox.pswp.ui.registerElement({
|
||||||
|
name: 'rotate-button-clk',
|
||||||
|
ariaLabel: 'Rotate clockwise',
|
||||||
|
order: 8,
|
||||||
|
isButton: true,
|
||||||
|
html: '<div class="bg-transparent p-2 inline-flex items-center me-2 hover:bg-gray-100 rounded-lg dark:bg-gray-800 dark:hover:bg-gray-700"><svg xmlns="http://www.w3.org/2000/svg" fill="none" aria-hidden="true" class="w-6 h-6 text-gray-800 dark:text-white" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" viewBox="0 0 24 24"><path d="M19.407 11.376a7.552 7.552 0 1 1-2.212-5.34"/><path d="M17.555 2.612v4h-4"/></svg></div>',
|
||||||
|
onClick: (event, el) => {
|
||||||
|
// Rotate
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
lightbox.pswp.ui.registerElement({
|
||||||
|
name: 'rotate-button-cclk',
|
||||||
|
ariaLabel: 'Rotate counter-clockwise',
|
||||||
|
order: 7,
|
||||||
|
isButton: true,
|
||||||
|
html: '<div class="bg-transparent p-2 inline-flex items-center me-2 hover:bg-gray-100 rounded-lg dark:bg-gray-800 dark:hover:bg-gray-700"><svg xmlns="http://www.w3.org/2000/svg" fill="none" aria-hidden="true" class="w-6 h-6 text-gray-800 dark:text-white scale-x-[-1]" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" viewBox="0 0 24 24"><path d="M19.407 11.376a7.552 7.552 0 1 1-2.212-5.34"/><path d="M17.555 2.612v4h-4"/></svg></div>',
|
||||||
|
onClick: (event, el) => {
|
||||||
|
// Rotate
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
lightbox.init();
|
||||||
|
|||||||
@@ -28,9 +28,16 @@
|
|||||||
@if($album->mutations->count() > 0)
|
@if($album->mutations->count() > 0)
|
||||||
<livewire:drawer.album.progress-monitor :mutation="$album->mutations->first()"/>
|
<livewire:drawer.album.progress-monitor :mutation="$album->mutations->first()"/>
|
||||||
@else
|
@else
|
||||||
<div class="m-8 flex flex-wrap flex-row gap-4">
|
<div id="album" class="m-8 flex flex-wrap flex-row gap-4">
|
||||||
@foreach ($album->images as $image)
|
@foreach ($album->images as $image)
|
||||||
<figure class="relative group rounded-lg cursor-pointer h-80 flex-grow overflow-hidden">
|
<a
|
||||||
|
class="relative group rounded-lg cursor-pointer h-80 flex-grow overflow-hidden"
|
||||||
|
href="{{ $image->lightbox['location'] }}"
|
||||||
|
data-pswp-width="{{ $image->lightbox['width'] }}"
|
||||||
|
data-pswp-download="{{ $image->getDownload() }}"
|
||||||
|
data-pswp-height="{{ $image->lightbox['height'] }}"
|
||||||
|
data-cropped="true"
|
||||||
|
>
|
||||||
<img class="max-h-full min-w-full align-bottom object-cover"
|
<img class="max-h-full min-w-full align-bottom object-cover"
|
||||||
src="{{ $image->getThumbnail() }}" alt="{{ $album->name }} Bild">
|
src="{{ $image->getThumbnail() }}" alt="{{ $album->name }} Bild">
|
||||||
<div class="opacity-0 group-hover:opacity-40 absolute inset-0 w-full h-full bg-black flex items-center justify-center transition-opacity">
|
<div class="opacity-0 group-hover:opacity-40 absolute inset-0 w-full h-full bg-black flex items-center justify-center transition-opacity">
|
||||||
@@ -39,7 +46,7 @@
|
|||||||
<path stroke="currentColor" stroke-width="2" d="M15 12a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z"/>
|
<path stroke="currentColor" stroke-width="2" d="M15 12a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z"/>
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
</figure>
|
</a>
|
||||||
@endforeach
|
@endforeach
|
||||||
</div>
|
</div>
|
||||||
<x-drawer name="image-add" >
|
<x-drawer name="image-add" >
|
||||||
|
|||||||
@@ -8,4 +8,6 @@ use App\Http\Controllers\AlbumController;
|
|||||||
Route::get('/', [CategoryController::class, 'index'])->name('index');
|
Route::get('/', [CategoryController::class, 'index'])->name('index');
|
||||||
Route::get('/category/{category}', [CategoryController::class, 'show'])->name('category.show');
|
Route::get('/category/{category}', [CategoryController::class, 'show'])->name('category.show');
|
||||||
Route::get('/album/{album}', [AlbumController::class, 'show'])->name('album.show');
|
Route::get('/album/{album}', [AlbumController::class, 'show'])->name('album.show');
|
||||||
Route::get('/image/{image}', [ImageController::class, 'show'])->name('image.show');
|
Route::get('/image/{image}/thumbnail', [ImageController::class, 'thumbnail'])->name('image.thumbnail');
|
||||||
|
Route::get('/image/{image}/lightbox', [ImageController::class, 'lightbox'])->name('image.lightbox');
|
||||||
|
Route::get('/image/{image}/download', [ImageController::class, 'download'])->name('image.download');
|
||||||
|
|||||||
Reference in New Issue
Block a user