From eacf33b642084350f727b45d1f1b3ed14a738d3e Mon Sep 17 00:00:00 2001 From: James Collins Date: Fri, 1 Sep 2023 16:51:46 +1000 Subject: [PATCH] updates --- app/Conductors/Conductor.php | 1 + app/Conductors/MediaConductor.php | 8 +- app/Http/Controllers/Api/MediaController.php | 6 +- app/Models/Media.php | 6 + .../js/components/dialogs/SMDialogMedia.vue | 198 ++++++++---------- resources/js/helpers/api.types.ts | 2 +- resources/js/helpers/media.ts | 82 +++++++- resources/js/views/dashboard/MediaList.vue | 2 + 8 files changed, 188 insertions(+), 117 deletions(-) diff --git a/app/Conductors/Conductor.php b/app/Conductors/Conductor.php index 27d6b40..7750dba 100644 --- a/app/Conductors/Conductor.php +++ b/app/Conductors/Conductor.php @@ -591,6 +591,7 @@ class Conductor $conductor_class = get_called_class(); $conductor = new $conductor_class(); + $requestIncludes = []; $modelFields = $conductor->fields(new $conductor->class()); // Limit fields diff --git a/app/Conductors/MediaConductor.php b/app/Conductors/MediaConductor.php index b5dd98b..ebabc90 100644 --- a/app/Conductors/MediaConductor.php +++ b/app/Conductors/MediaConductor.php @@ -2,6 +2,7 @@ namespace App\Conductors; +use App\Models\MediaJob; use App\Models\User; use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Model; @@ -26,7 +27,7 @@ class MediaConductor extends Conductor * * @var string[] */ - protected $includes = ['user']; + protected $includes = ['user', 'jobs']; /** * The default filters to use in a request. @@ -156,4 +157,9 @@ class MediaConductor extends Conductor return UserConductor::includeModel(request(), 'user', $user); } + + public function includeJobs(Model $model) { + $jobs = $model->jobs()->select(['id','created_at','updated_at','user_id','status','status_text','progress'])->orderBy('created_at', 'desc')->get(); + return $jobs; + } } diff --git a/app/Http/Controllers/Api/MediaController.php b/app/Http/Controllers/Api/MediaController.php index 4319a41..6fa0484 100644 --- a/app/Http/Controllers/Api/MediaController.php +++ b/app/Http/Controllers/Api/MediaController.php @@ -165,19 +165,15 @@ class MediaController extends ApiController if ($request->has('transform') === true) { $transform = []; - foreach ($request->get('transform') as $key => $value) { + foreach (explode(',', $request->get('transform', '')) as $value) { if (is_string($value) === true) { if (preg_match('/^rotate-(-?\d+)$/', $value, $matches) !== false) { - unset($transform[$key]); $transform['rotate'] = $matches[1]; } elseif (preg_match('/^flip-([vh]|vh|hv)$/', $value, $matches) !== false) { - unset($transform[$key]); $transform['flip'] = $matches[1]; } elseif (preg_match('/^crop-(\d+)-(\d+)$/', $value, $matches) !== false) { - unset($transform[$key]); $transform['crop'] = ['width' => $matches[1], 'height' => $matches[2]]; } elseif (preg_match('/^crop-(\d+)-(\d+)-(\d+)-(\d+)$/', $value, $matches) !== false) { - unset($transform[$key]); $transform['crop'] = ['width' => $matches[1], 'height' => $matches[2], 'x' => $matches[3], 'y' => $matches[4]]; } } diff --git a/app/Models/Media.php b/app/Models/Media.php index a462d02..34189c6 100644 --- a/app/Models/Media.php +++ b/app/Models/Media.php @@ -12,6 +12,7 @@ use Illuminate\Contracts\Container\BindingResolutionException; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsTo; +use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Foundation\Bus\DispatchesJobs; use Illuminate\Http\JsonResponse; use Illuminate\Support\Facades\Cache; @@ -794,6 +795,7 @@ class Media extends Model if (strpos($this->mime_type, 'image/') === 0) { $image = Image::make($filePath); + $image->orientate(); $image->resize($thumbnailWidth, $thumbnailHeight, function ($constraint) { $constraint->aspectRatio(); }); @@ -1034,4 +1036,8 @@ class Media extends Model // $this->status = "Info: " . $status; $this->save(); } + + public function jobs(): HasMany { + return $this->hasMany(MediaJob::class, 'media_id'); + } } diff --git a/resources/js/components/dialogs/SMDialogMedia.vue b/resources/js/components/dialogs/SMDialogMedia.vue index b669e11..fa09aa0 100644 --- a/resources/js/components/dialogs/SMDialogMedia.vue +++ b/resources/js/components/dialogs/SMDialogMedia.vue @@ -140,24 +140,22 @@ 'bg-center', 'bg-no-repeat', 'relative', - { 'mb-6': showMediaName(item) }, + 'mb-6', ]" :style="{ backgroundImage: `url('${mediaGetThumbnail( item, )}')`, - backgroundColor: 'initial', }">
{{ item.title }}
NONE{{ item.status }} @@ -257,17 +255,13 @@

- {{ - lastSelected.status.split(":") - .length > 1 - ? lastSelected.status - .split(":")[1] - .trim() - : lastSelected.status - }} + {{ lastSelected.status }}

= ref([]); +const mediaItems: Ref = ref([]); /** * Selected media item id. */ -const selected: Ref = ref([]); -let lastSelected: Ref = ref(null); +const selected: Ref = ref([]); +let lastSelected: Ref = ref(null); /** * How many media items are we showing per page. @@ -621,10 +625,10 @@ const computedAccepts = computed(() => { /** * Get the media item by id. * @param {string} item_id The media item id. - * @returns {Media | null} The media object or null. + * @returns {ExtendedMedia | null} The media object or null. */ -const getMediaItem = (item_id: string): Media | null => { - let found: Media | null = null; +const getMediaItem = (item_id: string): ExtendedMedia | null => { + let found: ExtendedMedia | null = null; mediaItems.value.every((item) => { if (item.id == item_id) { @@ -638,14 +642,6 @@ const getMediaItem = (item_id: string): Media | null => { return found; }; -const showMediaName = (media: Media): boolean => { - return true; - // return !( - // media.mime_type.startsWith("image/") || - // media.mime_type.startsWith("video/") - // ); -}; - /** * Handle user clicking the cancel/close button. */ @@ -758,7 +754,7 @@ const handleClickItem = (item_id: string): void => { /** * Handle user double clicking a media item. - * @param item_id The media id. + * @param {string} item_id The media id. */ const handleDblClickItem = (item_id: string): void => { if (!props.multiple) { @@ -839,6 +835,7 @@ const handleFilesUpload = (files: FileList) => { variants: {}, created_at: "", updated_at: "", + jobs: [], }); }); @@ -868,7 +865,7 @@ const startFilesUpload = async () => { ); submitFormData.append("description", ""); try { - let result = await api.post({ + let result = await api.chunk({ url: "/media", body: submitFormData, headers: { @@ -895,16 +892,20 @@ const startFilesUpload = async () => { }); if (result.data) { const data = result.data as MediaResponse; + const extendedMedium: ExtendedMedia = { + ...data.medium, + status: "", + }; const currentUploadFileNumStr = currentUploadFileNum.value.toString(); mediaItems.value.every((item, index) => { if (item.id == currentUploadFileNumStr) { - mediaItems.value[index] = data.medium; + mediaItems.value[index] = extendedMedium; if (!selected.value) { - selected.value.push(data.medium); + selected.value.push(extendedMedium); } else if (props.multiple) { - selected.value.push(data.medium); + selected.value.push(extendedMedium); } return false; } @@ -955,72 +956,47 @@ const updateFiles = async () => { if (updateFilesNonce.value == null) { let remaining = false; - mediaItems.value.forEach((item, index) => { + for (const [index, item] of mediaItems.value.entries()) { if (isUUID(item.id)) { - remaining = true; - - api.get({ + let updateResult = await api.get({ url: "/media/{id}", params: { id: item.id, }, - }) - .then((updateResult) => { - if (updateResult.data) { - const updateData = - updateResult.data as MediaResponse; - mediaItems.value[index].status = - updateData.medium.status; - if (updateData.medium.status == "OK") { - mediaItems.value[index] = updateData.medium; - forceRefresh.push(updateData.medium.id); - if ( - lastSelected.value && - lastSelected.value.id == - updateData.medium.id - ) { - lastSelected.value = updateData.medium; - } - } else if ( - updateData.medium.status.startsWith("Error") === - true - ) { - mediaItems.value = mediaItems.value.filter( - (mediaItem) => - mediaItem.id !== updateData.medium.id, - ); - lastSelected.value = null; - totalItems.value--; + }); - useToastStore().addToast({ - title: "Upload failed", - type: "danger", - content: updateData.medium.status, - // content: `${item.name} failed to be processed by the server.`, - }); - } - } else { - throw "error"; - } - }) - .catch(() => { - /* error retreiving data */ - mediaItems.value = mediaItems.value.filter( - (mediaItem) => mediaItem.id !== item.id, - ); - }); + if (updateResult.data) { + const updateData = updateResult.data as MediaResponse; + const statusData = mediaStatus(updateData.medium); + console.log(item.id, statusData); + + if (updateData.medium.thumbnail != item.thumbnail) { + mediaItems.value[index] = { + ...updateData.medium, + status: "", + }; + } + + if (statusData.busy == false) { + mediaItems.value[index].status = ""; + } else { + remaining = true; + mediaItems.value[index].status = + statusData.status + + " " + + statusData.status_text + + " " + + statusData.progress; + } + } } - }); - - // mediaItems.value = mediaItems.value.filter( - // (item) => item.status.startsWith("Error") === false, - // ); + } if (remaining) { updateFilesNonce.value = setTimeout(() => { updateFilesNonce.value = null; updateFiles(); - }, 1000); + }, 500); } else { updateFilesNonce.value = null; } @@ -1084,15 +1060,18 @@ const handleLoad = async () => { if (result.data) { const data = result.data as MediaCollection; - const mediaIds = new Set( - mediaItems.value.map((item) => item.id), - ); - const filteredItems = data.media.filter( - (item) => !mediaIds.has(item.id), - ); + // const mediaIds = new Set( + // mediaItems.value.map((item) => item.id), + // ); + // const filteredItems = data.media.filter( + // (item) => !mediaIds.has(item.id), + // ); totalItems.value = data.total; - mediaItems.value.push(...filteredItems); + mediaItems.value = data.media.map((item) => ({ + ...item, + status: mediaStatus(item).status_text, + })); } }) .catch(() => { @@ -1220,7 +1199,8 @@ const formatDate = (date) => { const allowEditSelected = computed(() => { return ( lastSelected.value != null && - lastSelected.value.status === "OK" && + lastSelected.value.jobs.length > 0 && + lastSelected.value.jobs[0].status == "complete" && userStore.id && (userHasPermission("admin/media") || lastSelected.value.user_id == userStore.id) @@ -1259,13 +1239,13 @@ const handleRotateLeft = async (item: Media) => { .then((result) => { if (result.data) { const data = result.data as MediaResponse; - const index = mediaItems.value.findIndex( - (mediaItem) => mediaItem.id === item.id, - ); + // const index = mediaItems.value.findIndex( + // (mediaItem) => mediaItem.id === item.id, + // ); - if (index !== -1) { - mediaItems.value[index] = data.medium; - } + // if (index !== -1) { + // mediaItems.value[index] = data.medium; + // } updateFiles(); } @@ -1288,13 +1268,13 @@ const handleRotateRight = async (item: Media) => { .then((result) => { if (result.data) { const data = result.data as MediaResponse; - const index = mediaItems.value.findIndex( - (mediaItem) => mediaItem.id === item.id, - ); + // const index = mediaItems.value.findIndex( + // (mediaItem) => mediaItem.id === item.id, + // ); - if (index !== -1) { - mediaItems.value[index] = data.medium; - } + // if (index !== -1) { + // mediaItems.value[index] = data.medium; + // } updateFiles(); } diff --git a/resources/js/helpers/api.types.ts b/resources/js/helpers/api.types.ts index 3f35559..7237ce0 100644 --- a/resources/js/helpers/api.types.ts +++ b/resources/js/helpers/api.types.ts @@ -68,7 +68,6 @@ export interface Media { mime_type: string; permission: string; size: number; - status: string; storage: string; url: string; thumbnail: string; @@ -77,6 +76,7 @@ export interface Media { variants: { [key: string]: string }; created_at: string; updated_at: string; + jobs: Array; } export interface MediaResponse { diff --git a/resources/js/helpers/media.ts b/resources/js/helpers/media.ts index 9556f11..0a94034 100644 --- a/resources/js/helpers/media.ts +++ b/resources/js/helpers/media.ts @@ -24,6 +24,12 @@ export const mediaGetVariantUrl = ( : media.url; }; +/** + * Check if a mime matches. + * @param {string} mimeExpected The mime expected. + * @param {string} mimeToCheck The mime to check. + * @returns {boolean} The mimeToCheck matches mimeExpected. + */ export const mimeMatches = ( mimeExpected: string, mimeToCheck: string, @@ -38,10 +44,22 @@ export const mimeMatches = ( return regex.test(mimeToCheck); }; +/** + * MediaGetThumbnailCallback Type + */ +export type mediaGetThumbnailCallback = (url: string) => void; + +/** + * Get Media/File Thumbnail. + * @param {Media|File} media The Media/File object. + * @param {string|null} useVariant The variable to use. + * @param {mediaGetThumbnailCallback|null} callback Callback with the thumbnail. Required when passing File. + * @returns {string} The thumbnail url. + */ export const mediaGetThumbnail = ( media: Media | File, useVariant: string | null = "", - callback = null, + callback: mediaGetThumbnailCallback | null = null, ): string => { let url: string = ""; @@ -91,3 +109,65 @@ export const mediaGetThumbnail = ( return url; }; + +/** + * Check if the media is currently busy. + * @param {Media} media The media item to check. + * @returns {boolean} If the media is busy. + */ +export const mediaIsBusy = (media: Media): boolean => { + let busy = false; + + if (media.jobs) { + media.jobs.forEach((item) => { + if ( + item.status != "invalid" && + item.status != "complete" && + item.status != "failed" + ) { + busy = true; + } + }); + } + + return busy; +}; + +interface MediaStatus { + busy: boolean; + status: string; + status_text: string; + progress: number; +} + +/** + * Get the current Media status + * @param {Media} media The media item to check. + * @returns {MediaStatus} The media status. + */ +export const mediaStatus = (media: Media): MediaStatus => { + const status = { + busy: false, + status: "", + status_text: "", + progress: 0, + }; + + if (media.jobs) { + for (const item of media.jobs) { + if ( + item.status != "invalid" && + item.status != "complete" && + item.status != "failed" + ) { + status.busy = true; + status.status = item.status; + status.status_text = item.status_text; + status.progress = item.progress; + break; + } + } + } + + return status; +}; diff --git a/resources/js/views/dashboard/MediaList.vue b/resources/js/views/dashboard/MediaList.vue index e1fcabd..ccb8adb 100644 --- a/resources/js/views/dashboard/MediaList.vue +++ b/resources/js/views/dashboard/MediaList.vue @@ -370,6 +370,8 @@ const handleDeleteSelected = async () => { }); if (result == true) { + itemsLoading.value = true; + let errorCount = 0; let successCount = 0;