diff --git a/app/Conductors/Conductor.php b/app/Conductors/Conductor.php index 7750dba..a79e907 100644 --- a/app/Conductors/Conductor.php +++ b/app/Conductors/Conductor.php @@ -593,7 +593,7 @@ class Conductor $requestIncludes = []; $modelFields = $conductor->fields(new $conductor->class()); - + // Limit fields $limitFields = $modelFields; if ($fields instanceof Request) { diff --git a/app/Conductors/MediaJobConductor.php b/app/Conductors/MediaJobConductor.php index 9c50c76..a55ef30 100644 --- a/app/Conductors/MediaJobConductor.php +++ b/app/Conductors/MediaJobConductor.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; diff --git a/app/Providers/RouteServiceProvider.php b/app/Providers/RouteServiceProvider.php index 3499f12..692cc89 100644 --- a/app/Providers/RouteServiceProvider.php +++ b/app/Providers/RouteServiceProvider.php @@ -42,7 +42,7 @@ class RouteServiceProvider extends ServiceProvider if ($rateLimitEnabled === true) { RateLimiter::for('api', function (Request $request) { - return Limit::perMinute(180)->by($request->user()?->id ?: $request->ip()); + return Limit::perMinute(800)->by($request->user()?->id ?: $request->ip()); }); } else { RateLimiter::for('api', function () { diff --git a/database/migrations/2023_09_10_085850_add_progress_max_to_media_jobs_table.php b/database/migrations/2023_09_10_085850_add_progress_max_to_media_jobs_table.php new file mode 100644 index 0000000..74aec2b --- /dev/null +++ b/database/migrations/2023_09_10_085850_add_progress_max_to_media_jobs_table.php @@ -0,0 +1,28 @@ +integer('progress_max')->default(0); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('media_jobs', function (Blueprint $table) { + $table->dropColumn('progress_max'); + }); + } +}; diff --git a/resources/js/components/dialogs/SMDialogMedia.vue b/resources/js/components/dialogs/SMDialogMedia.vue index ceda1c5..939e9c9 100644 --- a/resources/js/components/dialogs/SMDialogMedia.vue +++ b/resources/js/components/dialogs/SMDialogMedia.vue @@ -151,8 +151,8 @@ {{ + class="bg-white bg-op-90 w-full h-full"> + {{ getMediaStatusText(item) }} @@ -197,6 +197,27 @@ width: `${computedUploadProgress}%`, }"> +

+ {{ computedUploadMediaStatus }} +

+ +
+

+ {{ computedProcessingMediaTitle }} +

+
+
+
+

+ {{ computedProcessingMediaStatus }} +

{ const handleFilesUpload = (files: FileList) => { const fileList = []; let count = 0; + let maxCount = 15; let warnedUser = false; fileList.push(...Array.from(files)); @@ -826,7 +848,7 @@ const handleFilesUpload = (files: FileList) => { if (mimeMatches(props.mime, file.type) == true) { count = getUploadingMediaItems().length; - if (count < 5) { + if (count <= maxCount) { const uploadId = generateRandomId("upload_", 8, (s) => { return getMediaItemById(s) != null; }); @@ -834,18 +856,19 @@ const handleFilesUpload = (files: FileList) => { mediaItems.value.unshift( createMediaItem({ id: uploadId, + name: convertFileNameToTitle(file.name), }), ); window.setTimeout(() => { uploadFileById(uploadId, file); }, 50); - } else if (count >= 5 && warnedUser == false) { + } else if (count > maxCount && warnedUser == false) { warnedUser = true; useToastStore().addToast({ title: "Maximum Files", type: "warning", - content: "You cannot upload more than 5 files at a time", + content: `You cannot upload more than ${maxCount} files at a time`, }); return; @@ -861,18 +884,33 @@ const handleFilesUpload = (files: FileList) => { }; const getUploadingMediaItems = (): Media[] => { - return mediaItems.value.filter((item) => item.id.startsWith("upload_")); + return mediaItems.value.filter((item) => { + return ( + item.id.startsWith("upload_") && + item.jobs.length > 0 && + item.jobs[0].status === "uploading" + ); + }); }; const computedUploadMediaTitle = computed(() => { const items = getUploadingMediaItems(); - let prefix = "Uploading"; + return `Uploading ${items.length} File${items.length == 1 ? "" : "s"}`; +}); - if (computedUploadProgress.value >= 100) { - prefix = "Processing"; - } +const computedUploadMediaStatus = computed(() => { + const items = getUploadingMediaItems(); + let bytes = 0; + let maxBytes = 0; - return `${prefix} ${items.length} File${items.length == 1 ? "" : "s"}`; + items.forEach((item) => { + if (item.jobs.length > 0) { + bytes += item.jobs[0].progress; + maxBytes += item.jobs[0].progress_max; + } + }); + + return `${bytesReadable(bytes)} of ${bytesReadable(maxBytes)}`; }); const computedUploadProgress = computed(() => { @@ -883,7 +921,10 @@ const computedUploadProgress = computed(() => { const totalProgress = items.reduce((accumulator, item) => { if (item.jobs.length > 0) { - accumulator += item.jobs[0].progress || 100; + accumulator += + Math.floor( + (item.jobs[0].progress / item.jobs[0].progress_max) * 100, + ) || 100; } return accumulator; }, 0); @@ -891,6 +932,65 @@ const computedUploadProgress = computed(() => { return Math.floor(totalProgress / items.length); }); +const getProcessingMediaItems = (): Media[] => { + return mediaItems.value.filter((item) => { + return ( + item.id.startsWith("upload_") && + item.jobs.length > 0 && + item.jobs[0].status !== "uploading" + ); + }); +}; + +const computedProcessingMediaTitle = computed(() => { + const items = getProcessingMediaItems(); + return `Processing ${items.length} File${items.length == 1 ? "" : "s"}`; +}); + +const computedProcessingProgress = computed(() => { + const items = getProcessingMediaItems(); + if (items.length === 0) { + return 100; + } + + const totalProgress = items.reduce((accumulator, item) => { + if (item.jobs.length > 0) { + if (item.jobs[0].progress_max != 0) { + accumulator += + Math.floor( + (item.jobs[0].progress / item.jobs[0].progress_max) * + 100, + ) || 100; + } + } + return accumulator; + }, 0); + + return Math.floor(totalProgress / items.length); +}); + +const computedProcessingMediaStatus = computed(() => { + const items = getProcessingMediaItems(); + let status = ""; + + items.every((item) => { + let itemStatus = getMediaStatusText(item); + if (status == "" || (itemStatus != "" && itemStatus != "Queued")) { + const endLoop = !(status == ""); + status = `${item.name}: ${itemStatus}`; + + if (endLoop == true) { + return false; + } + } + + return true; + }); + + return status; + // return `${bytesReadable(bytes)} of ${bytesReadable(maxBytes)}`; +}); + /** * Upload a File to the server. * @param {string} uploadId The ID of the new media item. @@ -909,14 +1009,14 @@ const uploadFileById = (uploadId: string, file: File): void => { headers: { "Content-Type": "multipart/form-data", }, + chunk: "file", progress: (progressEvent) => { const mediaItem = getMediaItemById(uploadId); if (mediaItem != null) { mediaItem.jobs[0] = createMediaJobItem({ status: "uploading", - progress: Math.floor( - (progressEvent.loaded / progressEvent.total) * 100, - ), + progress: progressEvent.loaded, + progress_max: progressEvent.total, }); } }, @@ -934,10 +1034,20 @@ const uploadFileById = (uploadId: string, file: File): void => { } }) .catch((error) => { - // let errorString = "A server error occurred"; - // if (error.status == 413) { - // errorString = `The file is larger than ${max_upload_size.value}`; - // } + if (error.status == 413) { + useToastStore().addToast({ + title: "File too large", + type: "danger", + content: `Cannot upload the file ${file.name} as it larger than ${max_upload_size.value}.`, + }); + } else { + useToastStore().addToast({ + title: "File upload error", + type: "danger", + content: `Cannot upload the file ${file.name} as a server error occurred.`, + }); + } + console.log(error); }); }; @@ -949,7 +1059,7 @@ const uploadFileById = (uploadId: string, file: File): void => { */ const updateMediaItem = (id: string): void => { let media = getMediaItemById(id); - let timeout = 50; + let timeout = 200; if (media != null && media.jobs.length > 0) { if (id.startsWith("upload_")) { @@ -964,10 +1074,13 @@ const updateMediaItem = (id: string): void => { if (data.media_job.media_id != null) { media.id = data.media_job.media_id; } + if (data.media_job.id == media.jobs[0].id) { + media.jobs[0] = data.media_job; + } }) .catch((error) => { if (error.status == 429) { - timeout = 500; + timeout = 1000; } /* error */ @@ -1398,7 +1511,7 @@ const postUpdate = (data: MediaUpdate): void => { description: data.description, }, }).catch((error) => { - console.log(error); + console.log("postupdate: " + error); }); }; diff --git a/resources/js/helpers/api.ts b/resources/js/helpers/api.ts index b1c0b91..e2f6ffa 100644 --- a/resources/js/helpers/api.ts +++ b/resources/js/helpers/api.ts @@ -134,8 +134,6 @@ export const api = { for (const header in options.headers) { xhr.setRequestHeader(header, options.headers[header]); } - xhr.send(options.body as XMLHttpRequestBodyInit); - xhr.onload = function () { const result = { status: xhr.status, @@ -188,6 +186,12 @@ export const api = { return; } }; + + try { + xhr.send(options.body as XMLHttpRequestBodyInit); + } catch (e) { + console.log(e); + } } else { const fetchOptions: RequestInit = { method: options.method.toUpperCase() || "GET", @@ -379,7 +383,7 @@ export const api = { const file = apiOptions.body.get(apiOptions.chunk); if (file instanceof File) { - const chunkSize = 50 * 1024 * 1024; + const chunkSize = 2 * 1024 * 1024; let chunk = 0; let chunkCount = 1; let job_id = -1; diff --git a/resources/js/helpers/api.types.ts b/resources/js/helpers/api.types.ts index 7237ce0..f341e92 100644 --- a/resources/js/helpers/api.types.ts +++ b/resources/js/helpers/api.types.ts @@ -95,6 +95,7 @@ export interface MediaJob { status: string; status_text: string; progress: number; + progress_max: number; } export interface MediaJobResponse { diff --git a/resources/js/helpers/media.ts b/resources/js/helpers/media.ts index 0dd192b..6dd99b1 100644 --- a/resources/js/helpers/media.ts +++ b/resources/js/helpers/media.ts @@ -193,15 +193,15 @@ export const getMediaStatusText = (media: Media): string => { ) { if (media.jobs[0].status_text != "") { status = toTitleCase(media.jobs[0].status_text); - if ( - media.jobs[0].status == "processing" && - media.jobs[0].progress > -1 - ) { - status += ` ${media.jobs[0].progress}%`; - } } else { status = toTitleCase(media.jobs[0].status); } + + if (media.jobs[0].progress_max != 0) { + status += ` ${Math.floor( + (media.jobs[0].progress / media.jobs[0].progress_max) * 100, + )}%`; + } } } @@ -234,6 +234,7 @@ export interface MediaJobParams { status?: string; status_text?: string; progress?: number; + progress_max?: number; } export const createMediaItem = (params?: MediaParams): Media => { @@ -267,6 +268,7 @@ export const createMediaJobItem = (params?: MediaJobParams): MediaJob => { status: params.status || "", status_text: params.status_text || "", progress: params.progress || 0, + progress_max: params.progress_max || 0, }; return job;