Skip to content

Commit

Permalink
New feature for setting specific album header image LycheeOrg#2202 (L…
Browse files Browse the repository at this point in the history
  • Loading branch information
aSouchereau committed Apr 17, 2024
1 parent 74d672a commit 0778248
Show file tree
Hide file tree
Showing 38 changed files with 562 additions and 44 deletions.
2 changes: 2 additions & 0 deletions app/Actions/Photo/Delete.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
use App\Exceptions\Internal\QueryBuilderException;
use App\Exceptions\ModelDBException;
use App\Image\FileDeleter;
use App\Models\Album;
use App\Models\Photo;
use App\Models\SizeVariant;
use App\Models\SymLink;
Expand Down Expand Up @@ -84,6 +85,7 @@ public function do(array $photoIDs, array $albumIDs = []): FileDeleter
throw ModelDBException::create('photos', 'deleting', $e);
}
// @codeCoverageIgnoreEnd
Album::query()->whereIn('header_id', $photoIDs)->update(['header_id' => null]);

return $this->fileDeleter;
}
Expand Down
42 changes: 42 additions & 0 deletions app/Actions/Photo/Move.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<?php

namespace App\Actions\Photo;

use App\Actions\User\Notify;
use App\Exceptions\ModelDBException;
use App\Models\Album;
use App\Models\Photo;
use Illuminate\Database\Eloquent\Collection;

class Move
{
/**
* Duplicates a set of photos.
*
* @param Collection<int,Photo> $photos the source photos
* @param Album|null $album the destination album; `null` means root album
*
* @return void
*
* @throws ModelDBException
*/
public function do(Collection $photos, ?Album $album): void
{
$notify = new Notify();

/** @var Photo $photo */
foreach ($photos as $photo) {
$photo->album_id = $album?->id;
// Avoid unnecessary DB request, when we access the album of a
// photo later (e.g. when a notification is sent).
$photo->setRelation('album', $album);
if ($album !== null) {
$photo->owner_id = $album->owner_id;
}
$photo->save();
$notify->do($photo);
}

Album::query()->whereIn('header_id', $photos->map(fn (Photo $p) => $p->id))->update(['header_id' => null]);
}
}
14 changes: 14 additions & 0 deletions app/Http/Controllers/AlbumController.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
use App\Http\Requests\Album\MoveAlbumsRequest;
use App\Http\Requests\Album\SetAlbumCoverRequest;
use App\Http\Requests\Album\SetAlbumDescriptionRequest;
use App\Http\Requests\Album\SetAlbumHeaderRequest;
use App\Http\Requests\Album\SetAlbumLicenseRequest;
use App\Http\Requests\Album\SetAlbumNSFWRequest;
use App\Http\Requests\Album\SetAlbumProtectionPolicyRequest;
Expand Down Expand Up @@ -216,6 +217,19 @@ public function setCover(SetAlbumCoverRequest $request): void
$request->album()->save();
}

/**
* Set header image of the album.
*
* @param SetAlbumHeaderRequest $request
*
* @return void
*/
public function setHeader(SetAlbumHeaderRequest $request): void
{
$request->album()->header_id = $request->photo()?->id;
$request->album()->save();
}

/**
* Set the license of the Album.
*
Expand Down
21 changes: 4 additions & 17 deletions app/Http/Controllers/PhotoController.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
use App\Actions\Photo\Archive;
use App\Actions\Photo\Delete;
use App\Actions\Photo\Duplicate;
use App\Actions\User\Notify;
use App\Actions\Photo\Move;
use App\Contracts\Exceptions\InternalLycheeException;
use App\Contracts\Exceptions\LycheeException;
use App\Exceptions\MediaFileOperationException;
Expand Down Expand Up @@ -229,28 +229,15 @@ public function setTags(SetPhotosTagsRequest $request): void
* Moves the photos to an album.
*
* @param MovePhotosRequest $request
* @param Move $move
*
* @return void
*
* @throws LycheeException
*/
public function setAlbum(MovePhotosRequest $request): void
public function setAlbum(MovePhotosRequest $request, Move $move): void
{
$notify = new Notify();
$album = $request->album();

/** @var Photo $photo */
foreach ($request->photos() as $photo) {
$photo->album_id = $album?->id;
// Avoid unnecessary DB request, when we access the album of a
// photo later (e.g. when a notification is sent).
$photo->setRelation('album', $album);
if ($album !== null) {
$photo->owner_id = $album->owner_id;
}
$photo->save();
$notify->do($photo);
}
$move->do($request->photos(), $request->album());
}

/**
Expand Down
51 changes: 51 additions & 0 deletions app/Http/Requests/Album/SetAlbumHeaderRequest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
<?php

namespace App\Http\Requests\Album;

use App\Contracts\Http\Requests\HasAlbum;
use App\Contracts\Http\Requests\HasPhoto;
use App\Contracts\Http\Requests\RequestAttribute;
use App\Contracts\Models\AbstractAlbum;
use App\Http\Requests\BaseApiRequest;
use App\Http\Requests\Traits\HasAlbumTrait;
use App\Http\Requests\Traits\HasPhotoTrait;
use App\Http\RuleSets\Album\SetAlbumHeaderRuleSet;
use App\Models\Album;
use App\Models\Photo;
use App\Policies\AlbumPolicy;
use App\Policies\PhotoPolicy;
use Illuminate\Support\Facades\Gate;

class SetAlbumHeaderRequest extends BaseApiRequest implements HasAlbum, HasPhoto
{
use HasAlbumTrait;
use HasPhotoTrait;

/**
* {@inheritDoc}
*/
public function authorize(): bool
{
return Gate::check(AlbumPolicy::CAN_EDIT, [AbstractAlbum::class, $this->album]) &&
($this->photo === null || Gate::check(PhotoPolicy::CAN_SEE, $this->photo));
}

/**
* {@inheritDoc}
*/
public function rules(): array
{
return SetAlbumHeaderRuleSet::rules();
}

/**
* {@inheritDoc}
*/
protected function processValidatedValues(array $values, array $files): void
{
$this->album = Album::query()->findOrFail($values[RequestAttribute::ALBUM_ID_ATTRIBUTE]);
/** @var ?string $photoID */
$photoID = $values[RequestAttribute::PHOTO_ID_ATTRIBUTE];
$this->photo = $photoID === null ? null : Photo::query()->findOrFail($photoID);
}
}
1 change: 1 addition & 0 deletions app/Http/Resources/Models/AlbumResource.php
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ public function toArray($request)
'track_url' => $this->resource->track_url,
'license' => $this->resource->license->localization(),
'sorting' => $this->resource->photo_sorting,
'header_id' => $this->resource->header_id,

// children
'parent_id' => $this->resource->parent_id,
Expand Down
24 changes: 24 additions & 0 deletions app/Http/RuleSets/Album/SetAlbumHeaderRuleSet.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?php

namespace App\Http\RuleSets\Album;

use App\Contracts\Http\Requests\RequestAttribute;
use App\Contracts\Http\RuleSet;
use App\Rules\RandomIDRule;

/**
* Rules applied when Setting the header of an album.
*/
class SetAlbumHeaderRuleSet implements RuleSet
{
/**
* {@inheritDoc}
*/
public static function rules(): array
{
return [
RequestAttribute::ALBUM_ID_ATTRIBUTE => ['required', new RandomIDRule(true)],
RequestAttribute::PHOTO_ID_ATTRIBUTE => ['present', new RandomIDRule(true)],
];
}
}
99 changes: 99 additions & 0 deletions app/Livewire/Components/Forms/Album/SetHeader.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
<?php

namespace App\Livewire\Components\Forms\Album;

use App\Contracts\Models\AbstractAlbum;
use App\Livewire\Traits\Notify;
use App\Livewire\Traits\UseValidator;
use App\Models\Album;
use App\Models\Photo;
use App\Policies\AlbumPolicy;
use Illuminate\Contracts\View\View;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Support\Facades\Gate;
use Illuminate\Support\Facades\URL;
use Illuminate\Support\Str;
use Livewire\Attributes\Locked;
use Livewire\Component;

class SetHeader extends Component
{
use AuthorizesRequests;
use UseValidator;
use Notify;
public const COMPACT_HEADER = 'compact';

#[Locked] public string $albumID;
public ?string $search = null; // ! wired
/** @var array<int,array{id:string,title:string,thumb:string}> */
#[Locked] public array $photoListSaved;
public ?string $header_id;
public ?string $title;

/**
* This is the equivalent of the constructor for Livewire Components.
*
* @param string $album_id to update the attributes of
*
* @return void
*/
public function mount(string $album_id): void
{
/** @var Album $album */
$album = Album::with(['header'])->findOrFail($album_id);

Gate::authorize(AlbumPolicy::CAN_EDIT, [AbstractAlbum::class, $album]);

$this->albumID = $album_id;

$this->photoListSaved = Photo::with('size_variants')->where('album_id', '=', $album_id)->get()->map(fn (Photo $photo) => [
'id' => $photo->id,
'title' => $photo->title,
'thumb' => $photo->size_variants->getThumb()?->url ?? URL::asset('img/no_images.svg'),
])->all();

$this->header_id = $album->header_id;
$this->title = $album->header?->title;
}

/**
* Simply render the form.
*
* @return View
*/
public function render(): View
{
return view('livewire.forms.album.set-header');
}

/**
* Give the tree of albums owned by the user.
*
* @return array<int,array{id:string,title:string,thumb:string}>
*/
public function getPhotoListProperty(): array
{
$filtered = collect($this->photoListSaved);
if ($this->search !== null && trim($this->search) !== '') {
return $filtered->filter(function (array $photo) {
return Str::contains($photo['title'], ltrim($this->search), true);
})->all();
}

return $filtered->all();
}

public function select(?string $photoId, ?string $title): void
{
Gate::authorize(AlbumPolicy::CAN_EDIT_ID, [AbstractAlbum::class, [$this->albumID]]);

Album::where('id', '=', $this->albumID)->update(['header_id' => $photoId]);
$this->header_id = $photoId;
$this->title = $title;
}

public function clearHeaderId(): void
{
$this->select(null, null);
}
}
28 changes: 13 additions & 15 deletions app/Livewire/Components/Forms/Photo/Move.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

namespace App\Livewire\Components\Forms\Photo;

use App\Actions\User\Notify as UserNotify;
use App\Actions\Photo\Move as PhotoMove;
use App\Contracts\Livewire\Params;
use App\Enum\SmartAlbumType;
use App\Http\RuleSets\Photo\MovePhotosRuleSet;
Expand All @@ -21,6 +21,7 @@ class Move extends Component
{
use InteractWithModal;
use AuthorizesRequests;
private PhotoMove $move;

#[Locked] public string $parent_id;
/** @var array<int,string> */
Expand All @@ -30,6 +31,16 @@ class Move extends Component
// Destination
#[Locked] public ?string $albumID = null;
#[Locked] public ?string $albumTitle = null;
/**
* Initialize private properties of component.
*
* @return void
*/
public function boot(): void
{
$this->move = new PhotoMove();
}

/**
* This is the equivalent of the constructor for Livewire Components.
*
Expand Down Expand Up @@ -77,20 +88,7 @@ public function setAlbum(string $id, string $title): void

$photos = Photo::query()->findOrFail($this->photoIDs);

$notify = new UserNotify();

/** @var Photo $photo */
foreach ($photos as $photo) {
$photo->album_id = $album?->id;
// Avoid unnecessary DB request, when we access the album of a
// photo later (e.g. when a notification is sent).
$photo->setRelation('album', $album);
if ($album !== null) {
$photo->owner_id = $album->owner_id;
}
$photo->save();
$notify->do($photo);
}
$this->move->do($photos, $album);

// We stay in current album.
$this->redirect(route('livewire-gallery-album', ['albumId' => $this->parent_id]), true);
Expand Down
Loading

0 comments on commit 0778248

Please sign in to comment.