Skip to content

Enhanced Responsive Image Manager #4265

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 13 commits into from
May 29, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions app/Api/ApiDocsController.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,12 @@ public function json()

return response()->json($docs);
}

/**
* Redirect to the API docs page.
*/
public function redirect()
{
return redirect('/api/docs');
}
}
2 changes: 1 addition & 1 deletion app/Uploads/Controllers/GalleryImageController.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ public function list(Request $request)
$uploadedToFilter = $request->get('uploaded_to', null);
$parentTypeFilter = $request->get('filter_type', null);

$imgData = $this->imageRepo->getEntityFiltered('gallery', $parentTypeFilter, $page, 24, $uploadedToFilter, $searchTerm);
$imgData = $this->imageRepo->getEntityFiltered('gallery', $parentTypeFilter, $page, 30, $uploadedToFilter, $searchTerm);

return view('pages.parts.image-manager-list', [
'images' => $imgData['images'],
Expand Down
34 changes: 27 additions & 7 deletions app/Uploads/Controllers/ImageController.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,10 @@

class ImageController extends Controller
{
protected ImageRepo $imageRepo;
protected ImageService $imageService;

public function __construct(ImageRepo $imageRepo, ImageService $imageService)
{
$this->imageRepo = $imageRepo;
$this->imageService = $imageService;
public function __construct(
protected ImageRepo $imageRepo,
protected ImageService $imageService
) {
}

/**
Expand Down Expand Up @@ -65,6 +62,29 @@ public function update(Request $request, string $id)
]);
}

/**
* Update the file for an existing image.
*/
public function updateFile(Request $request, string $id)
{
$this->validate($request, [
'file' => ['required', 'file', ...$this->getImageValidationRules()],
]);

$image = $this->imageRepo->getById($id);
$this->checkImagePermission($image);
$this->checkOwnablePermission('image-update', $image);
$file = $request->file('file');

try {
$this->imageRepo->updateImageFile($image, $file);
} catch (ImageUploadException $exception) {
return $this->jsonError($exception->getMessage());
}

return response('');
}

/**
* Get the form for editing the given image.
*
Expand Down
7 changes: 6 additions & 1 deletion app/Uploads/Controllers/ImageGalleryApiController.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ protected function rules(): array
],
'update' => [
'name' => ['string', 'max:180'],
'image' => ['file', ...$this->getImageValidationRules()],
]
];
}
Expand Down Expand Up @@ -89,7 +90,8 @@ public function read(string $id)

/**
* Update the details of an existing image in the system.
* Only allows updating of the image name at this time.
* Since "image" is expected to be a file, this needs to be a 'multipart/form-data' type request if providing a
* new image file. Updated image files should be of the same file type as the original image.
*/
public function update(Request $request, string $id)
{
Expand All @@ -99,6 +101,9 @@ public function update(Request $request, string $id)
$this->checkOwnablePermission('image-update', $image);

$this->imageRepo->updateImageDetails($image, $data);
if (isset($data['image'])) {
$this->imageRepo->updateImageFile($image, $data['image']);
}

return response()->json($this->formatForSingleResponse($image));
}
Expand Down
42 changes: 27 additions & 15 deletions app/Uploads/ImageRepo.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,10 @@

class ImageRepo
{
protected ImageService $imageService;
protected PermissionApplicator $permissions;

/**
* ImageRepo constructor.
*/
public function __construct(ImageService $imageService, PermissionApplicator $permissions)
{
$this->imageService = $imageService;
$this->permissions = $permissions;
public function __construct(
protected ImageService $imageService,
protected PermissionApplicator $permissions
) {
}

/**
Expand Down Expand Up @@ -164,12 +158,30 @@ public function saveDrawing(string $base64Uri, int $uploadedTo): Image
public function updateImageDetails(Image $image, $updateDetails): Image
{
$image->fill($updateDetails);
$image->updated_by = user()->id;
$image->save();
$this->loadThumbs($image);

return $image;
}

/**
* Update the image file of an existing image in the system.
* @throws ImageUploadException
*/
public function updateImageFile(Image $image, UploadedFile $file): void
{
if ($file->getClientOriginalExtension() !== pathinfo($image->path, PATHINFO_EXTENSION)) {
throw new ImageUploadException(trans('errors.image_upload_replace_type'));
}

$image->refresh();
$image->updated_by = user()->id;
$image->save();
$this->imageService->replaceExistingFromUpload($image->path, $image->type, $file);
$this->loadThumbs($image, true);
}

/**
* Destroys an Image object along with its revisions, files and thumbnails.
*
Expand Down Expand Up @@ -202,11 +214,11 @@ public function destroyByUrlAndType(string $url, string $imageType): void
/**
* Load thumbnails onto an image object.
*/
public function loadThumbs(Image $image): void
public function loadThumbs(Image $image, bool $forceCreate = false): void
{
$image->setAttribute('thumbs', [
'gallery' => $this->getThumbnail($image, 150, 150, false),
'display' => $this->getThumbnail($image, 1680, null, true),
'gallery' => $this->getThumbnail($image, 150, 150, false, $forceCreate),
'display' => $this->getThumbnail($image, 1680, null, true, $forceCreate),
]);
}

Expand All @@ -215,10 +227,10 @@ public function loadThumbs(Image $image): void
* If $keepRatio is true only the width will be used.
* Checks the cache then storage to avoid creating / accessing the filesystem on every check.
*/
protected function getThumbnail(Image $image, ?int $width, ?int $height, bool $keepRatio): ?string
protected function getThumbnail(Image $image, ?int $width, ?int $height, bool $keepRatio, bool $forceCreate): ?string
{
try {
return $this->imageService->getThumbnail($image, $width, $height, $keepRatio);
return $this->imageService->getThumbnail($image, $width, $height, $keepRatio, $forceCreate);
} catch (Exception $exception) {
return null;
}
Expand Down
14 changes: 11 additions & 3 deletions app/Uploads/ImageService.php
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,14 @@ public function saveNew(string $imageName, string $imageData, string $type, int
return $image;
}

public function replaceExistingFromUpload(string $path, string $type, UploadedFile $file): void
{
$imageData = file_get_contents($file->getRealPath());
$storage = $this->getStorageDisk($type);
$adjustedPath = $this->adjustPathForStorageDisk($path, $type);
$storage->put($adjustedPath, $imageData);
}

/**
* Save image data for the given path in the public space, if possible,
* for the provided storage mechanism.
Expand Down Expand Up @@ -262,7 +270,7 @@ protected function isApngData(Image $image, string &$imageData): bool
* @throws Exception
* @throws InvalidArgumentException
*/
public function getThumbnail(Image $image, ?int $width, ?int $height, bool $keepRatio = false): string
public function getThumbnail(Image $image, ?int $width, ?int $height, bool $keepRatio = false, bool $forceCreate = false): string
{
// Do not resize GIF images where we're not cropping
if ($keepRatio && $this->isGif($image)) {
Expand All @@ -277,13 +285,13 @@ public function getThumbnail(Image $image, ?int $width, ?int $height, bool $keep

// Return path if in cache
$cachedThumbPath = $this->cache->get($thumbCacheKey);
if ($cachedThumbPath) {
if ($cachedThumbPath && !$forceCreate) {
return $this->getPublicUrl($cachedThumbPath);
}

// If thumbnail has already been generated, serve that and cache path
$storage = $this->getStorageDisk($image->type);
if ($storage->exists($this->adjustPathForStorageDisk($thumbFilePath, $image->type))) {
if (!$forceCreate && $storage->exists($this->adjustPathForStorageDisk($thumbFilePath, $image->type))) {
$this->cache->put($thumbCacheKey, $thumbFilePath, 60 * 60 * 72);

return $this->getPublicUrl($thumbFilePath);
Expand Down
1 change: 1 addition & 0 deletions lang/en/common.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

// Buttons
'cancel' => 'Cancel',
'close' => 'Close',
'confirm' => 'Confirm',
'back' => 'Back',
'save' => 'Save',
Expand Down
7 changes: 7 additions & 0 deletions lang/en/components.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@

// Image Manager
'image_select' => 'Image Select',
'image_list' => 'Image List',
'image_details' => 'Image Details',
'image_upload' => 'Upload Image',
'image_intro' => 'Here you can select and manage images that have been previously uploaded to the system.',
'image_intro_upload' => 'Upload a new image by dragging an image file into this window, or by using the "Upload Image" button above.',
Expand All @@ -15,6 +17,9 @@
'image_page_title' => 'View images uploaded to this page',
'image_search_hint' => 'Search by image name',
'image_uploaded' => 'Uploaded :uploadedDate',
'image_uploaded_by' => 'Uploaded by :userName',
'image_uploaded_to' => 'Uploaded to :pageLink',
'image_updated' => 'Updated :updateDate',
'image_load_more' => 'Load More',
'image_image_name' => 'Image Name',
'image_delete_used' => 'This image is used in the pages below.',
Expand All @@ -27,6 +32,8 @@
'image_upload_success' => 'Image uploaded successfully',
'image_update_success' => 'Image details successfully updated',
'image_delete_success' => 'Image successfully deleted',
'image_replace' => 'Replace Image',
'image_replace_success' => 'Image file successfully updated',

// Code Editor
'code_editor' => 'Edit Code',
Expand Down
1 change: 1 addition & 0 deletions lang/en/errors.php
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
// Drawing & Images
'image_upload_error' => 'An error occurred uploading the image',
'image_upload_type_error' => 'The image type being uploaded is invalid',
'image_upload_replace_type' => 'Image file replacements must be of the same type',
'drawing_data_not_found' => 'Drawing data could not be loaded. The drawing file might no longer exist or you may not have permission to access it.',

// Attachments
Expand Down
4 changes: 4 additions & 0 deletions resources/js/components/dropzone.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export class Dropzone extends Component {
this.isActive = true;

this.url = this.$opts.url;
this.method = (this.$opts.method || 'post').toUpperCase();
this.successMessage = this.$opts.successMessage;
this.errorMessage = this.$opts.errorMessage;
this.uploadLimitMb = Number(this.$opts.uploadLimit);
Expand Down Expand Up @@ -167,6 +168,9 @@ export class Dropzone extends Component {
startXhrForUpload(upload) {
const formData = new FormData();
formData.append('file', upload.file, upload.file.name);
if (this.method !== 'POST') {
formData.append('_method', this.method);
}
const component = this;

const req = window.$http.createXMLHttpRequest('POST', this.url, {
Expand Down
Loading