diff --git a/app/Http/Controllers/AssetController.php b/app/Http/Controllers/AssetController.php index ed72475..7a3aa2e 100644 --- a/app/Http/Controllers/AssetController.php +++ b/app/Http/Controllers/AssetController.php @@ -12,7 +12,7 @@ public function __invoke(Request $request, string $slug) { return Storage::disk('s3') ->response(Asset::where('slug', $slug)->firstOrFail()->path, null, [ - 'Cache-Control' => 'public, max-age=31536000' + 'Cache-Control' => 'public, max-age=31536000', ]); } } diff --git a/app/Http/Controllers/Prezet/ImageController.php b/app/Http/Controllers/Prezet/ImageController.php index 39911c2..f836f9e 100644 --- a/app/Http/Controllers/Prezet/ImageController.php +++ b/app/Http/Controllers/Prezet/ImageController.php @@ -4,14 +4,16 @@ use Illuminate\Http\Request; use Illuminate\Http\Response; +use Illuminate\Support\Facades\Storage; +use Prezet\Prezet\Models\Document; use Prezet\Prezet\Prezet; class ImageController { public function __invoke(Request $request, string $path): Response { - $file = Prezet::getImage($path); - $size = strlen($file); + /* resolve img path from sub dir, trim off 'images/' & pass to prezet */ + $file = Prezet::getImage(ltrim($this->resolveImagePath($path), 'images/')); return response($file, 200, [ 'Content-Type' => match (pathinfo($path, PATHINFO_EXTENSION)) { @@ -19,9 +21,29 @@ public function __invoke(Request $request, string $path): Response 'png' => 'image/png', default => 'image/webp' }, - 'Content-Length' => $size, + 'Content-Length' => strlen($file), 'Accept-Ranges' => 'bytes', 'Cache-Control' => 'public, max-age=31536000', ]); } + + private function resolveImagePath(string $filename): ?string + { + /* remove any appended size version when looking up path */ + $originalFilename = preg_replace('/-\d+w\./', '.', $filename); + + $slugs = Document::pluck('slug')->toArray(); + + /* loop through the articles' slugs to get dir & file name from prezet disk */ + foreach ($slugs as $slug) { + $imagePath = "images/{$slug}/{$originalFilename}"; + + if (Storage::disk('prezet')->exists($imagePath)) { + return $imagePath; + } + } + + /* images should always be in their article's sub dir, so we should really never reach here. */ + return null; + } } diff --git a/app/Livewire/Blog.php b/app/Livewire/Blog.php index 8c70f1c..0e089c2 100644 --- a/app/Livewire/Blog.php +++ b/app/Livewire/Blog.php @@ -24,6 +24,9 @@ class Blog extends Component #[Validate('required|file|mimes:md')] public $file; + #[Validate(['images' => 'nullable|array', 'images.*' => 'image|max:4096'])] + public $images = []; + public function render() { return view('livewire.blog'); @@ -39,9 +42,26 @@ public function store() options: ['disk' => 'prezet'] ); + /* index in prezet disk */ Artisan::call('prezet:index'); + $document = Document::query()->latest()->first(); + + if ($document && $this->images) { + foreach ($this->images as $image) { + $image->storeAs( + path: "images/{$document->slug}", + name: $image->getClientOriginalName(), + options: ['disk' => 'prezet'] + ); + } + + /* index in prezet disk again (for the images) */ + Artisan::call('prezet:index'); + } + $this->file = null; + $this->images = []; Flux::toast(variant: 'success', text: 'Blog Post Uploaded!', duration: 3000); } @@ -53,11 +73,18 @@ public function destroy($id) if ($document) { try { /* delete the file */ - Storage::disk('prezet')->delete('content/'.$document->slug.'.md'); + Storage::disk('prezet')->delete("content/{$document->slug}.md"); /* delete the tags */ DB::connection('prezet')->table('document_tags')->where('document_id', $id)->delete(); + /* delete images if there are any */ + $imageDirectory = "images/{$document->slug}"; + + if (Storage::disk('prezet')->exists($imageDirectory)) { + Storage::disk('prezet')->deleteDirectory($imageDirectory); + } + /* delete the reference */ Document::where('id', $id)->delete(); @@ -90,4 +117,11 @@ public function posts() return $documents; } + + public function clearUploads() + { + $this->file = null; + $this->images = []; + $this->resetValidation(); + } } diff --git a/app/Livewire/Forms/PhotoForm.php b/app/Livewire/Forms/PhotoForm.php index b0a2ecd..dc14d0b 100644 --- a/app/Livewire/Forms/PhotoForm.php +++ b/app/Livewire/Forms/PhotoForm.php @@ -90,9 +90,9 @@ private function getStorageDisk(): string private function processImage(): void { - $manager = new ImageManager(new Driver()); + $manager = new ImageManager(new Driver); $image = $manager->read($this->photo->getRealPath()); - + if ($image->width() > 2000 || $image->height() > 1500) { $image->scaleDown(width: 2000, height: 1500); $image->save($this->photo->getRealPath()); diff --git a/config/prezet.php b/config/prezet.php index c56aca0..923d270 100644 --- a/config/prezet.php +++ b/config/prezet.php @@ -105,7 +105,7 @@ 'sizes' => '92vw, (max-width: 1024px) 92vw, 768px', - 'zoomable' => true, + 'zoomable' => false, ], /* diff --git a/resources/css/app.css b/resources/css/app.css index fa7e11f..4a1201a 100644 --- a/resources/css/app.css +++ b/resources/css/app.css @@ -156,132 +156,6 @@ white-space: nowrap; width: 1px; } - - /* - |-------------------------------------------------------------------------- - | Alpinejs Zoomable - |-------------------------------------------------------------------------- - | https://github.com/benbjurstrom/alpinejs-zoomable - | - */ - - img[x-zoomable]:hover { - cursor: zoom-in; - } - - .zoomable-fullscreen-container { - position: fixed; - top: 0; - left: 0; - width: 100%; - height: 100vh; - background: rgba(255, 255, 255, 0.9); - display: none; - z-index: 998; - cursor: zoom-out; - overflow: hidden; - touch-action: none; - } - - .zoomable-fullscreen-image { - position: absolute; - max-height: none; - max-width: none; - cursor: grab; - transition: transform 0.2s ease-out; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); - touch-action: none; - -webkit-user-select: none; - user-select: none; - } - - .zoomable-fullscreen-image.dragging { - cursor: grabbing; - transition: none; - } - - .zoomable-controls-panel { - position: fixed; - top: 20px; - right: 20px; - display: flex; - gap: 10px; - z-index: 999; - } - - .zoomable-control-button { - background: #a1a1aa; - border: none; - color: white; - width: 40px; - height: 40px; - border-radius: 50%; - cursor: pointer; - display: flex; - align-items: center; - justify-content: center; - transition: background-color 0.2s; - } - - .zoomable-control-button > svg { - width: 20px; - height: 20px; - } - - .zoomable-control-button:hover, - .zoomable-control-button:focus { - background: #71717a; - outline: none; - } - - @media (max-width: 768px) { - .zoomable-controls-panel { - top: 10px; - right: 10px; - } - - .zoomable-control-button { - width: 44px; - height: 44px; - -webkit-tap-highlight-color: transparent; /* Prevents gray flash on iOS */ - touch-action: manipulation; /* Optimize for touch */ - } - } - - .zoomable-loading-indicator { - position: absolute; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); - z-index: 1000; - display: flex; - flex-direction: column; - align-items: center; - color: #737373; - font-size: 1rem; - } - - .zoomable-spinner { - width: 40px; - height: 40px; - border: 4px solid rgba(255, 255, 255, 0.3); - border-top: 4px solid #737373; - border-radius: 50%; - animation: spin 1s linear infinite; - margin-bottom: 10px; - } - - @keyframes spin { - to { - transform: rotate(360deg); - } - } - - .zoomable-loading-indicator[hidden] { - display: none; - } } @theme { diff --git a/resources/views/livewire/blog.blade.php b/resources/views/livewire/blog.blade.php index 96e4e37..b43e61a 100644 --- a/resources/views/livewire/blog.blade.php +++ b/resources/views/livewire/blog.blade.php @@ -1,20 +1,62 @@