diff --git a/app/Support/ArtworkFeaturedImagePath.php b/app/Support/ArtworkFeaturedImagePath.php new file mode 100644 index 00000000..7917f7dd --- /dev/null +++ b/app/Support/ArtworkFeaturedImagePath.php @@ -0,0 +1,102 @@ +> + */ + public function variants(): array + { + /** @var array> $variants */ + $variants = config('uploads.featured_variants', []); + + return $variants; + } + + /** + * @return list + */ + public function variantNames(): array + { + return array_keys($this->variants()); + } + + public function defaultVariant(): string + { + return 'desktop'; + } + + public function featuredPrefix(): string + { + return trim((string) config('uploads.featured_prefix', 'artworks/featured'), '/'); + } + + public function objectPath(Artwork $artwork, string $variant): string + { + $hash = $this->normalizedHash($artwork); + $variantName = $this->normalizeVariant($variant); + + return sprintf( + '%s/%s/%s/%s/%s.webp', + $this->featuredPrefix(), + $variantName, + substr($hash, 0, 2), + substr($hash, 2, 2), + $hash, + ); + } + + public function url(Artwork $artwork, string $variant): string + { + return rtrim((string) config('cdn.files_url', 'https://files.skinbase.org'), '/').'/'.$this->objectPath($artwork, $variant); + } + + /** + * @return list + */ + public function preferredVariantOrder(string $variant): array + { + $variantName = $this->normalizeVariant($variant); + + $orders = [ + 'xs' => ['xs', 'mobile_sm', 'mobile', 'tablet', 'desktop', 'desktop_xl'], + 'mobile_sm' => ['mobile_sm', 'mobile', 'tablet', 'desktop', 'desktop_xl'], + 'mobile' => ['mobile', 'mobile_sm', 'xs', 'tablet', 'desktop', 'desktop_xl'], + 'tablet' => ['tablet', 'desktop', 'desktop_xl', 'mobile', 'mobile_sm', 'xs'], + 'desktop' => ['desktop', 'desktop_xl', 'tablet', 'mobile', 'mobile_sm', 'xs'], + 'desktop_xl' => ['desktop_xl', 'desktop', 'tablet', 'mobile', 'mobile_sm', 'xs'], + ]; + + return $orders[$variantName] ?? [$this->defaultVariant()]; + } + + /** + * @return array|null + */ + public function variantConfig(string $variant): ?array + { + return $this->variants()[$this->normalizeVariant($variant)] ?? null; + } + + public function normalizeVariant(string $variant): string + { + return array_key_exists($variant, $this->variants()) ? $variant : $this->defaultVariant(); + } + + private function normalizedHash(Artwork $artwork): string + { + $hash = trim((string) $artwork->hash); + + if ($hash === '') { + throw new \InvalidArgumentException('Artwork hash is required for featured thumbnail paths.'); + } + + return $hash; + } +} diff --git a/config/uploads.php b/config/uploads.php index e14cef72..5f61a0f3 100644 --- a/config/uploads.php +++ b/config/uploads.php @@ -14,6 +14,8 @@ return [ 'prefix' => env('ARTWORKS_OBJECT_PREFIX', 'artworks'), ], + 'featured_prefix' => env('ARTWORKS_FEATURED_OBJECT_PREFIX', 'artworks/featured'), + 'paths' => [ 'tmp' => 'tmp', 'quarantine' => 'quarantine', @@ -59,6 +61,19 @@ return [ 'sq' => ['size' => 512], ], + 'featured_variants' => [ + 'xs' => ['width' => 400, 'height' => 512, 'quality' => 76, 'media' => '(max-width: 479px)', 'sizes' => '100vw'], + 'mobile_sm' => ['width' => 640, 'height' => 640, 'quality' => 78, 'media' => '(max-width: 639px)', 'sizes' => '100vw'], + 'mobile' => ['width' => 900, 'height' => 900, 'quality' => 80, 'media' => '(max-width: 767px)', 'sizes' => '100vw'], + 'tablet' => ['width' => 1280, 'height' => 900, 'quality' => 82, 'media' => '(max-width: 1279px)', 'sizes' => '100vw'], + 'desktop' => ['width' => 1600, 'height' => 900, 'quality' => 84, 'media' => '(max-width: 1599px)', 'sizes' => '100vw'], + 'desktop_xl' => ['width' => 2200, 'height' => 1238, 'quality' => 86, 'media' => '(min-width: 1600px)', 'sizes' => '100vw'], + ], + + 'featured_thumbnails' => [ + 'queue' => env('ARTWORKS_FEATURED_THUMBNAIL_QUEUE', 'default'), + ], + 'square_thumbnails' => [ 'width' => env('UPLOAD_SQ_WIDTH', 512), 'height' => env('UPLOAD_SQ_HEIGHT', 512),