@orion-studios/payload-studio 0.5.0-beta.63 → 0.5.0-beta.64

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -691,6 +691,64 @@ function MediaListItem({
691
691
  // src/admin-app/components/MediaUploadForm.tsx
692
692
  var import_react5 = require("react");
693
693
  var import_navigation = require("next/navigation");
694
+
695
+ // src/shared/clientImageUploadOptimization.ts
696
+ var MAX_DIRECT_UPLOAD_BYTES = 4e6;
697
+ async function optimizeImageForUpload(file) {
698
+ if (!file.type.startsWith("image/")) {
699
+ return file;
700
+ }
701
+ if (file.size <= MAX_DIRECT_UPLOAD_BYTES) {
702
+ return file;
703
+ }
704
+ const objectURL = URL.createObjectURL(file);
705
+ try {
706
+ const image = await new Promise((resolve, reject) => {
707
+ const nextImage = new Image();
708
+ nextImage.onload = () => resolve(nextImage);
709
+ nextImage.onerror = () => reject(new Error("Could not read image for upload optimization."));
710
+ nextImage.src = objectURL;
711
+ });
712
+ const canvas = document.createElement("canvas");
713
+ canvas.width = Math.max(1, image.width);
714
+ canvas.height = Math.max(1, image.height);
715
+ const context = canvas.getContext("2d");
716
+ if (!context) {
717
+ return file;
718
+ }
719
+ context.drawImage(image, 0, 0, canvas.width, canvas.height);
720
+ const outputMime = file.type === "image/webp" ? "image/webp" : "image/jpeg";
721
+ const qualityPasses = [0.82, 0.74, 0.66, 0.58, 0.5, 0.42, 0.36];
722
+ let bestFile = null;
723
+ for (const quality of qualityPasses) {
724
+ const blob = await new Promise((resolve) => {
725
+ canvas.toBlob((value) => resolve(value), outputMime, quality);
726
+ });
727
+ if (!blob) {
728
+ continue;
729
+ }
730
+ const optimizedName = file.name.replace(/\.[^/.]+$/, outputMime === "image/webp" ? ".webp" : ".jpg");
731
+ const optimized = new File([blob], optimizedName, {
732
+ lastModified: Date.now(),
733
+ type: outputMime
734
+ });
735
+ if (!bestFile || optimized.size < bestFile.size) {
736
+ bestFile = optimized;
737
+ }
738
+ if (optimized.size <= MAX_DIRECT_UPLOAD_BYTES) {
739
+ break;
740
+ }
741
+ }
742
+ if (!bestFile) {
743
+ return file;
744
+ }
745
+ return bestFile.size < file.size ? bestFile : file;
746
+ } finally {
747
+ URL.revokeObjectURL(objectURL);
748
+ }
749
+ }
750
+
751
+ // src/admin-app/components/MediaUploadForm.tsx
694
752
  var import_jsx_runtime6 = require("react/jsx-runtime");
695
753
  var MEDIA_LIBRARY_SYNC_EVENT = "orion-media-library-updated";
696
754
  var notifyMediaLibraryUpdated = () => {
@@ -773,12 +831,16 @@ function MediaUploadForm() {
773
831
  setSubmitting(true);
774
832
  setError(null);
775
833
  try {
834
+ const optimizedFile = await optimizeImageForUpload(file);
835
+ if (optimizedFile.size > MAX_DIRECT_UPLOAD_BYTES) {
836
+ throw new Error("Image is too large. Use an image under 4MB or lower-resolution export.");
837
+ }
776
838
  const formData = new FormData();
777
839
  const fallbackAlt = file.name.replace(/\.[^/.]+$/, "").trim();
778
840
  const resolvedAlt = alt.trim() || fallbackAlt || "Uploaded image";
779
841
  formData.set("_payload", JSON.stringify({ alt: resolvedAlt }));
780
842
  formData.set("alt", resolvedAlt);
781
- formData.set("file", file);
843
+ formData.set("file", optimizedFile);
782
844
  const response = await fetch("/api/media", {
783
845
  method: "POST",
784
846
  credentials: "include",
@@ -1,4 +1,8 @@
1
1
  'use client';
2
+ import {
3
+ MAX_DIRECT_UPLOAD_BYTES,
4
+ optimizeImageForUpload
5
+ } from "../chunk-I522K6BZ.mjs";
2
6
 
3
7
  // src/admin-app/components/AdminShellClient.tsx
4
8
  import { useEffect, useState } from "react";
@@ -742,12 +746,16 @@ function MediaUploadForm() {
742
746
  setSubmitting(true);
743
747
  setError(null);
744
748
  try {
749
+ const optimizedFile = await optimizeImageForUpload(file);
750
+ if (optimizedFile.size > MAX_DIRECT_UPLOAD_BYTES) {
751
+ throw new Error("Image is too large. Use an image under 4MB or lower-resolution export.");
752
+ }
745
753
  const formData = new FormData();
746
754
  const fallbackAlt = file.name.replace(/\.[^/.]+$/, "").trim();
747
755
  const resolvedAlt = alt.trim() || fallbackAlt || "Uploaded image";
748
756
  formData.set("_payload", JSON.stringify({ alt: resolvedAlt }));
749
757
  formData.set("alt", resolvedAlt);
750
- formData.set("file", file);
758
+ formData.set("file", optimizedFile);
751
759
  const response = await fetch("/api/media", {
752
760
  method: "POST",
753
761
  credentials: "include",
@@ -0,0 +1,62 @@
1
+ 'use client';
2
+
3
+ // src/shared/clientImageUploadOptimization.ts
4
+ var MAX_DIRECT_UPLOAD_BYTES = 4e6;
5
+ async function optimizeImageForUpload(file) {
6
+ if (!file.type.startsWith("image/")) {
7
+ return file;
8
+ }
9
+ if (file.size <= MAX_DIRECT_UPLOAD_BYTES) {
10
+ return file;
11
+ }
12
+ const objectURL = URL.createObjectURL(file);
13
+ try {
14
+ const image = await new Promise((resolve, reject) => {
15
+ const nextImage = new Image();
16
+ nextImage.onload = () => resolve(nextImage);
17
+ nextImage.onerror = () => reject(new Error("Could not read image for upload optimization."));
18
+ nextImage.src = objectURL;
19
+ });
20
+ const canvas = document.createElement("canvas");
21
+ canvas.width = Math.max(1, image.width);
22
+ canvas.height = Math.max(1, image.height);
23
+ const context = canvas.getContext("2d");
24
+ if (!context) {
25
+ return file;
26
+ }
27
+ context.drawImage(image, 0, 0, canvas.width, canvas.height);
28
+ const outputMime = file.type === "image/webp" ? "image/webp" : "image/jpeg";
29
+ const qualityPasses = [0.82, 0.74, 0.66, 0.58, 0.5, 0.42, 0.36];
30
+ let bestFile = null;
31
+ for (const quality of qualityPasses) {
32
+ const blob = await new Promise((resolve) => {
33
+ canvas.toBlob((value) => resolve(value), outputMime, quality);
34
+ });
35
+ if (!blob) {
36
+ continue;
37
+ }
38
+ const optimizedName = file.name.replace(/\.[^/.]+$/, outputMime === "image/webp" ? ".webp" : ".jpg");
39
+ const optimized = new File([blob], optimizedName, {
40
+ lastModified: Date.now(),
41
+ type: outputMime
42
+ });
43
+ if (!bestFile || optimized.size < bestFile.size) {
44
+ bestFile = optimized;
45
+ }
46
+ if (optimized.size <= MAX_DIRECT_UPLOAD_BYTES) {
47
+ break;
48
+ }
49
+ }
50
+ if (!bestFile) {
51
+ return file;
52
+ }
53
+ return bestFile.size < file.size ? bestFile : file;
54
+ } finally {
55
+ URL.revokeObjectURL(objectURL);
56
+ }
57
+ }
58
+
59
+ export {
60
+ MAX_DIRECT_UPLOAD_BYTES,
61
+ optimizeImageForUpload
62
+ };
@@ -263,6 +263,62 @@ var layoutToStudioDocument = (layout, title, metadata) => {
263
263
  };
264
264
  };
265
265
 
266
+ // src/shared/clientImageUploadOptimization.ts
267
+ var MAX_DIRECT_UPLOAD_BYTES = 4e6;
268
+ async function optimizeImageForUpload(file) {
269
+ if (!file.type.startsWith("image/")) {
270
+ return file;
271
+ }
272
+ if (file.size <= MAX_DIRECT_UPLOAD_BYTES) {
273
+ return file;
274
+ }
275
+ const objectURL = URL.createObjectURL(file);
276
+ try {
277
+ const image = await new Promise((resolve, reject) => {
278
+ const nextImage = new Image();
279
+ nextImage.onload = () => resolve(nextImage);
280
+ nextImage.onerror = () => reject(new Error("Could not read image for upload optimization."));
281
+ nextImage.src = objectURL;
282
+ });
283
+ const canvas = document.createElement("canvas");
284
+ canvas.width = Math.max(1, image.width);
285
+ canvas.height = Math.max(1, image.height);
286
+ const context = canvas.getContext("2d");
287
+ if (!context) {
288
+ return file;
289
+ }
290
+ context.drawImage(image, 0, 0, canvas.width, canvas.height);
291
+ const outputMime = file.type === "image/webp" ? "image/webp" : "image/jpeg";
292
+ const qualityPasses = [0.82, 0.74, 0.66, 0.58, 0.5, 0.42, 0.36];
293
+ let bestFile = null;
294
+ for (const quality of qualityPasses) {
295
+ const blob = await new Promise((resolve) => {
296
+ canvas.toBlob((value) => resolve(value), outputMime, quality);
297
+ });
298
+ if (!blob) {
299
+ continue;
300
+ }
301
+ const optimizedName = file.name.replace(/\.[^/.]+$/, outputMime === "image/webp" ? ".webp" : ".jpg");
302
+ const optimized = new File([blob], optimizedName, {
303
+ lastModified: Date.now(),
304
+ type: outputMime
305
+ });
306
+ if (!bestFile || optimized.size < bestFile.size) {
307
+ bestFile = optimized;
308
+ }
309
+ if (optimized.size <= MAX_DIRECT_UPLOAD_BYTES) {
310
+ break;
311
+ }
312
+ }
313
+ if (!bestFile) {
314
+ return file;
315
+ }
316
+ return bestFile.size < file.size ? bestFile : file;
317
+ } finally {
318
+ URL.revokeObjectURL(objectURL);
319
+ }
320
+ }
321
+
266
322
  // src/studio-pages/builder/presets.ts
267
323
  var sectionPresets = [
268
324
  {
@@ -1030,7 +1086,6 @@ async function parsePayloadErrorMessage(response, fallbackMessage) {
1030
1086
  }
1031
1087
  return fallbackMessage;
1032
1088
  }
1033
- var MAX_DIRECT_UPLOAD_BYTES = 4e6;
1034
1089
  var MEDIA_LIBRARY_SYNC_EVENT = "orion-media-library-updated";
1035
1090
  var notifyMediaLibraryUpdated = () => {
1036
1091
  if (typeof window === "undefined") {
@@ -1049,59 +1104,6 @@ var notifyMediaLibraryUpdated = () => {
1049
1104
  } catch {
1050
1105
  }
1051
1106
  };
1052
- async function optimizeImageForUpload(file) {
1053
- if (!file.type.startsWith("image/")) {
1054
- return file;
1055
- }
1056
- if (file.size <= MAX_DIRECT_UPLOAD_BYTES) {
1057
- return file;
1058
- }
1059
- const objectURL = URL.createObjectURL(file);
1060
- try {
1061
- const image = await new Promise((resolve, reject) => {
1062
- const nextImage = new Image();
1063
- nextImage.onload = () => resolve(nextImage);
1064
- nextImage.onerror = () => reject(new Error("Could not read image for upload optimization."));
1065
- nextImage.src = objectURL;
1066
- });
1067
- const canvas = document.createElement("canvas");
1068
- canvas.width = Math.max(1, image.width);
1069
- canvas.height = Math.max(1, image.height);
1070
- const context = canvas.getContext("2d");
1071
- if (!context) {
1072
- return file;
1073
- }
1074
- context.drawImage(image, 0, 0, canvas.width, canvas.height);
1075
- const outputMime = file.type === "image/webp" ? "image/webp" : "image/jpeg";
1076
- const qualityPasses = [0.82, 0.74, 0.66, 0.58, 0.5, 0.42, 0.36];
1077
- let bestFile = null;
1078
- for (const quality of qualityPasses) {
1079
- const blob = await new Promise((resolve) => {
1080
- canvas.toBlob((value) => resolve(value), outputMime, quality);
1081
- });
1082
- if (!blob) {
1083
- continue;
1084
- }
1085
- const optimizedName = file.name.replace(/\.[^/.]+$/, outputMime === "image/webp" ? ".webp" : ".jpg");
1086
- const optimized = new File([blob], optimizedName, {
1087
- lastModified: Date.now(),
1088
- type: outputMime
1089
- });
1090
- if (!bestFile || optimized.size < bestFile.size) {
1091
- bestFile = optimized;
1092
- }
1093
- if (optimized.size <= MAX_DIRECT_UPLOAD_BYTES) {
1094
- break;
1095
- }
1096
- }
1097
- if (!bestFile) {
1098
- return file;
1099
- }
1100
- return bestFile.size < file.size ? bestFile : file;
1101
- } finally {
1102
- URL.revokeObjectURL(objectURL);
1103
- }
1104
- }
1105
1107
  function InlineText({
1106
1108
  as = "p",
1107
1109
  className,
@@ -1,5 +1,9 @@
1
1
  'use client';
2
2
  "use client";
3
+ import {
4
+ MAX_DIRECT_UPLOAD_BYTES,
5
+ optimizeImageForUpload
6
+ } from "../chunk-I522K6BZ.mjs";
3
7
 
4
8
  // src/studio-pages/builder/BuilderPageEditor.tsx
5
9
  import { useCallback, useEffect, useMemo, useRef, useState } from "react";
@@ -1002,7 +1006,6 @@ async function parsePayloadErrorMessage(response, fallbackMessage) {
1002
1006
  }
1003
1007
  return fallbackMessage;
1004
1008
  }
1005
- var MAX_DIRECT_UPLOAD_BYTES = 4e6;
1006
1009
  var MEDIA_LIBRARY_SYNC_EVENT = "orion-media-library-updated";
1007
1010
  var notifyMediaLibraryUpdated = () => {
1008
1011
  if (typeof window === "undefined") {
@@ -1021,59 +1024,6 @@ var notifyMediaLibraryUpdated = () => {
1021
1024
  } catch {
1022
1025
  }
1023
1026
  };
1024
- async function optimizeImageForUpload(file) {
1025
- if (!file.type.startsWith("image/")) {
1026
- return file;
1027
- }
1028
- if (file.size <= MAX_DIRECT_UPLOAD_BYTES) {
1029
- return file;
1030
- }
1031
- const objectURL = URL.createObjectURL(file);
1032
- try {
1033
- const image = await new Promise((resolve, reject) => {
1034
- const nextImage = new Image();
1035
- nextImage.onload = () => resolve(nextImage);
1036
- nextImage.onerror = () => reject(new Error("Could not read image for upload optimization."));
1037
- nextImage.src = objectURL;
1038
- });
1039
- const canvas = document.createElement("canvas");
1040
- canvas.width = Math.max(1, image.width);
1041
- canvas.height = Math.max(1, image.height);
1042
- const context = canvas.getContext("2d");
1043
- if (!context) {
1044
- return file;
1045
- }
1046
- context.drawImage(image, 0, 0, canvas.width, canvas.height);
1047
- const outputMime = file.type === "image/webp" ? "image/webp" : "image/jpeg";
1048
- const qualityPasses = [0.82, 0.74, 0.66, 0.58, 0.5, 0.42, 0.36];
1049
- let bestFile = null;
1050
- for (const quality of qualityPasses) {
1051
- const blob = await new Promise((resolve) => {
1052
- canvas.toBlob((value) => resolve(value), outputMime, quality);
1053
- });
1054
- if (!blob) {
1055
- continue;
1056
- }
1057
- const optimizedName = file.name.replace(/\.[^/.]+$/, outputMime === "image/webp" ? ".webp" : ".jpg");
1058
- const optimized = new File([blob], optimizedName, {
1059
- lastModified: Date.now(),
1060
- type: outputMime
1061
- });
1062
- if (!bestFile || optimized.size < bestFile.size) {
1063
- bestFile = optimized;
1064
- }
1065
- if (optimized.size <= MAX_DIRECT_UPLOAD_BYTES) {
1066
- break;
1067
- }
1068
- }
1069
- if (!bestFile) {
1070
- return file;
1071
- }
1072
- return bestFile.size < file.size ? bestFile : file;
1073
- } finally {
1074
- URL.revokeObjectURL(objectURL);
1075
- }
1076
- }
1077
1027
  function InlineText({
1078
1028
  as = "p",
1079
1029
  className,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@orion-studios/payload-studio",
3
- "version": "0.5.0-beta.63",
3
+ "version": "0.5.0-beta.64",
4
4
  "description": "Unified Payload CMS toolkit for Orion Studios",
5
5
  "types": "./dist/index.d.ts",
6
6
  "main": "./dist/index.js",