@orion-studios/payload-studio 0.5.0-beta.0 → 0.5.0-beta.2

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.
package/dist/index.mjs CHANGED
@@ -4,18 +4,18 @@ import {
4
4
  import {
5
5
  admin_app_exports
6
6
  } from "./chunk-AAOHJDNS.mjs";
7
- import {
8
- blocks_exports
9
- } from "./chunk-PC5622T7.mjs";
10
7
  import {
11
8
  studio_exports
12
9
  } from "./chunk-N67KVM2S.mjs";
10
+ import {
11
+ studio_pages_exports
12
+ } from "./chunk-UJFU323N.mjs";
13
13
  import {
14
14
  nextjs_exports
15
15
  } from "./chunk-ZLLNO5FM.mjs";
16
16
  import {
17
- studio_pages_exports
18
- } from "./chunk-UJFU323N.mjs";
17
+ blocks_exports
18
+ } from "./chunk-PC5622T7.mjs";
19
19
  import "./chunk-ETRRXURT.mjs";
20
20
  import "./chunk-6BWS3CLP.mjs";
21
21
  export {
@@ -84,10 +84,12 @@ h4 {
84
84
  max-width: 100%;
85
85
  }
86
86
 
87
- .orion-builder-content > .section,
88
- .orion-builder-content > .hero {
89
- padding-top: var(--orion-shell-padding-y);
90
- padding-bottom: var(--orion-shell-padding-y);
87
+ .orion-builder-content .section {
88
+ padding: 0;
89
+ }
90
+
91
+ .orion-builder-content .hero {
92
+ margin: 0;
91
93
  }
92
94
 
93
95
  .section {
@@ -563,6 +563,11 @@ var gradientPresetPairs = {
563
563
  sand: ["#f3ead8", "#f8f5ed"],
564
564
  slate: ["#1a2f2a", "#34524a"]
565
565
  };
566
+ var sectionPaddingMap = {
567
+ sm: "1.4rem",
568
+ md: "2.6rem",
569
+ lg: "3.4rem"
570
+ };
566
571
  var quickAddBlockTypes = [
567
572
  "hero",
568
573
  "featureGrid",
@@ -711,10 +716,9 @@ function parseAngle(value, fallback = "135") {
711
716
  return String(Math.max(0, Math.min(360, parsed)));
712
717
  }
713
718
  var sectionStyleFromBlock = (block, pageDefaults) => {
714
- const sectionWidthRaw = normalizeText(block.sectionWidth, defaultSectionStyle.sectionWidth);
715
719
  const contentWidthRaw = normalizeText(block.contentWidth, defaultSectionStyle.contentWidth);
716
- const sectionPaddingY = normalizeText(block.sectionPaddingY, defaultSectionStyle.sectionPaddingY);
717
- const sectionWidth = sectionWidthRaw === "full" || sectionWidthRaw === "wide" || sectionWidthRaw === "content" ? sectionWidthRaw : pageDefaults.pageWidthDefault;
720
+ const sectionPaddingRaw = normalizeText(block.sectionPaddingY, defaultSectionStyle.sectionPaddingY);
721
+ const sectionPaddingY = sectionPaddingRaw === "sm" || sectionPaddingRaw === "lg" ? sectionPaddingRaw : "md";
718
722
  const contentWidth = contentWidthRaw === "narrow" || contentWidthRaw === "content" || contentWidthRaw === "wide" || contentWidthRaw === "full" || contentWidthRaw === "inherit" ? contentWidthRaw : "inherit";
719
723
  const resolvedContentWidth = contentWidth === "inherit" ? pageDefaults.pageWidthDefault : contentWidth;
720
724
  const sectionMode = normalizeText(block.sectionBackgroundMode, defaultSectionStyle.sectionBackgroundMode);
@@ -735,8 +739,20 @@ var sectionStyleFromBlock = (block, pageDefaults) => {
735
739
  const contentGradient = `linear-gradient(${contentAngle}deg, ${contentPresetPair?.[0] || contentFrom} 0%, ${contentPresetPair?.[1] || contentTo} 100%)`;
736
740
  return {
737
741
  contentClass: `orion-builder-content is-${resolvedContentWidth}`,
738
- contentStyle: contentMode === "color" ? { background: contentColor } : contentMode === "gradient" ? { background: contentGradient } : {},
739
- sectionClass: `orion-builder-shell is-${sectionWidth} pad-${sectionPaddingY}`,
742
+ contentStyle: contentMode === "color" ? {
743
+ background: contentColor,
744
+ borderRadius: 20,
745
+ boxShadow: "0 8px 20px rgba(13, 74, 55, 0.08)"
746
+ } : contentMode === "gradient" ? {
747
+ background: contentGradient,
748
+ borderRadius: 20,
749
+ boxShadow: "0 8px 20px rgba(13, 74, 55, 0.08)"
750
+ } : {},
751
+ sectionClass: `orion-builder-shell is-${pageDefaults.pageWidthDefault}`,
752
+ sectionInnerStyle: {
753
+ paddingBottom: sectionPaddingMap[sectionPaddingY],
754
+ paddingTop: sectionPaddingMap[sectionPaddingY]
755
+ },
740
756
  sectionStyle: sectionMode === "color" ? { background: sectionColor } : sectionMode === "gradient" ? { background: sectionGradient } : {}
741
757
  };
742
758
  };
@@ -784,6 +800,51 @@ function getUploadedMediaURL(value) {
784
800
  }
785
801
  return "";
786
802
  }
803
+ var MAX_DIRECT_UPLOAD_BYTES = 4e6;
804
+ async function optimizeImageForUpload(file) {
805
+ if (!file.type.startsWith("image/")) {
806
+ return file;
807
+ }
808
+ if (file.size <= MAX_DIRECT_UPLOAD_BYTES) {
809
+ return file;
810
+ }
811
+ const objectURL = URL.createObjectURL(file);
812
+ try {
813
+ const image = await new Promise((resolve, reject) => {
814
+ const nextImage = new Image();
815
+ nextImage.onload = () => resolve(nextImage);
816
+ nextImage.onerror = () => reject(new Error("Could not read image for upload optimization."));
817
+ nextImage.src = objectURL;
818
+ });
819
+ const maxDimension = 2200;
820
+ const scale = Math.min(1, maxDimension / Math.max(image.width, image.height));
821
+ const targetWidth = Math.max(1, Math.round(image.width * scale));
822
+ const targetHeight = Math.max(1, Math.round(image.height * scale));
823
+ const canvas = document.createElement("canvas");
824
+ canvas.width = targetWidth;
825
+ canvas.height = targetHeight;
826
+ const context = canvas.getContext("2d");
827
+ if (!context) {
828
+ return file;
829
+ }
830
+ context.drawImage(image, 0, 0, targetWidth, targetHeight);
831
+ const outputMime = file.type === "image/webp" ? "image/webp" : "image/jpeg";
832
+ const blob = await new Promise((resolve) => {
833
+ canvas.toBlob((value) => resolve(value), outputMime, 0.82);
834
+ });
835
+ if (!blob) {
836
+ return file;
837
+ }
838
+ const optimizedName = file.name.replace(/\.[^/.]+$/, outputMime === "image/webp" ? ".webp" : ".jpg");
839
+ const optimized = new File([blob], optimizedName, {
840
+ lastModified: Date.now(),
841
+ type: outputMime
842
+ });
843
+ return optimized.size < file.size ? optimized : file;
844
+ } finally {
845
+ URL.revokeObjectURL(objectURL);
846
+ }
847
+ }
787
848
  function InlineText({
788
849
  as = "p",
789
850
  className,
@@ -1223,11 +1284,16 @@ function BuilderPageEditor({ initialDoc, pageID }) {
1223
1284
  setUploadError("");
1224
1285
  setUploadMessage("");
1225
1286
  try {
1287
+ const optimizedFile = await optimizeImageForUpload(file);
1288
+ if (optimizedFile.size > MAX_DIRECT_UPLOAD_BYTES) {
1289
+ throw new Error("Image is too large. Use an image under 4MB or lower-resolution export.");
1290
+ }
1226
1291
  const formData = new FormData();
1227
1292
  const fallbackAlt = file.name.replace(/\.[^/.]+$/, "").trim();
1228
1293
  const resolvedAlt = uploadAltText.trim() || fallbackAlt || "Uploaded image";
1229
1294
  formData.set("_payload", JSON.stringify({ alt: resolvedAlt }));
1230
- formData.set("file", file);
1295
+ formData.set("alt", resolvedAlt);
1296
+ formData.set("file", optimizedFile);
1231
1297
  const response = await fetch("/api/media", {
1232
1298
  body: formData,
1233
1299
  credentials: "include",
@@ -1320,7 +1386,7 @@ function BuilderPageEditor({ initialDoc, pageID }) {
1320
1386
  });
1321
1387
  const renderWithSectionShell = (block, className, content) => {
1322
1388
  const shell = sectionStyleFromBlock(block, pageDefaults);
1323
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("section", { className: `${className} ${shell.sectionClass}`, style: shell.sectionStyle, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: shell.contentClass, style: shell.contentStyle, children: content }) });
1389
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("section", { className: `${className} ${shell.sectionClass}`, style: shell.sectionStyle, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: shell.contentClass, style: shell.contentStyle, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: shell.sectionInnerStyle, children: content }) }) });
1324
1390
  };
1325
1391
  const sidebarSectionStyle = {
1326
1392
  border: "1px solid rgba(13, 74, 55, 0.15)",
@@ -1376,8 +1442,12 @@ function BuilderPageEditor({ initialDoc, pageID }) {
1376
1442
  }),
1377
1443
  [layout, pageDefaults]
1378
1444
  );
1445
+ const snapshotKey = (snapshot) => JSON.stringify({
1446
+ layout: toPersistedLayout(snapshot.layout),
1447
+ pageDefaults: snapshot.pageDefaults
1448
+ });
1379
1449
  const isDirty = (0, import_react.useMemo)(
1380
- () => JSON.stringify(currentSnapshot) !== JSON.stringify(lastSavedRef.current),
1450
+ () => snapshotKey(currentSnapshot) !== snapshotKey(lastSavedRef.current),
1381
1451
  [currentSnapshot]
1382
1452
  );
1383
1453
  const canUndo = pastSnapshots.length > 0;
@@ -2852,21 +2922,10 @@ function BuilderPageEditor({ initialDoc, pageID }) {
2852
2922
  }
2853
2923
  )
2854
2924
  ] }),
2855
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", { style: sidebarLabelStyle, children: [
2856
- "Section Width",
2857
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
2858
- "select",
2859
- {
2860
- onChange: (event) => setSelectedStyleField("sectionWidth", event.target.value),
2861
- style: sidebarInputStyle,
2862
- value: normalizeText(selectedSectionStyle.sectionWidth, "content"),
2863
- children: [
2864
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "content", children: "Content" }),
2865
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "wide", children: "Wide" }),
2866
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "full", children: "Full" })
2867
- ]
2868
- }
2869
- )
2925
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { color: "var(--ink-700)", fontSize: 11 }, children: [
2926
+ "Section width follows ",
2927
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("strong", { children: "Page Defaults" }),
2928
+ " for a consistent page layout."
2870
2929
  ] }),
2871
2930
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", { style: sidebarLabelStyle, children: [
2872
2931
  "Content Width",
@@ -535,6 +535,11 @@ var gradientPresetPairs = {
535
535
  sand: ["#f3ead8", "#f8f5ed"],
536
536
  slate: ["#1a2f2a", "#34524a"]
537
537
  };
538
+ var sectionPaddingMap = {
539
+ sm: "1.4rem",
540
+ md: "2.6rem",
541
+ lg: "3.4rem"
542
+ };
538
543
  var quickAddBlockTypes = [
539
544
  "hero",
540
545
  "featureGrid",
@@ -683,10 +688,9 @@ function parseAngle(value, fallback = "135") {
683
688
  return String(Math.max(0, Math.min(360, parsed)));
684
689
  }
685
690
  var sectionStyleFromBlock = (block, pageDefaults) => {
686
- const sectionWidthRaw = normalizeText(block.sectionWidth, defaultSectionStyle.sectionWidth);
687
691
  const contentWidthRaw = normalizeText(block.contentWidth, defaultSectionStyle.contentWidth);
688
- const sectionPaddingY = normalizeText(block.sectionPaddingY, defaultSectionStyle.sectionPaddingY);
689
- const sectionWidth = sectionWidthRaw === "full" || sectionWidthRaw === "wide" || sectionWidthRaw === "content" ? sectionWidthRaw : pageDefaults.pageWidthDefault;
692
+ const sectionPaddingRaw = normalizeText(block.sectionPaddingY, defaultSectionStyle.sectionPaddingY);
693
+ const sectionPaddingY = sectionPaddingRaw === "sm" || sectionPaddingRaw === "lg" ? sectionPaddingRaw : "md";
690
694
  const contentWidth = contentWidthRaw === "narrow" || contentWidthRaw === "content" || contentWidthRaw === "wide" || contentWidthRaw === "full" || contentWidthRaw === "inherit" ? contentWidthRaw : "inherit";
691
695
  const resolvedContentWidth = contentWidth === "inherit" ? pageDefaults.pageWidthDefault : contentWidth;
692
696
  const sectionMode = normalizeText(block.sectionBackgroundMode, defaultSectionStyle.sectionBackgroundMode);
@@ -707,8 +711,20 @@ var sectionStyleFromBlock = (block, pageDefaults) => {
707
711
  const contentGradient = `linear-gradient(${contentAngle}deg, ${contentPresetPair?.[0] || contentFrom} 0%, ${contentPresetPair?.[1] || contentTo} 100%)`;
708
712
  return {
709
713
  contentClass: `orion-builder-content is-${resolvedContentWidth}`,
710
- contentStyle: contentMode === "color" ? { background: contentColor } : contentMode === "gradient" ? { background: contentGradient } : {},
711
- sectionClass: `orion-builder-shell is-${sectionWidth} pad-${sectionPaddingY}`,
714
+ contentStyle: contentMode === "color" ? {
715
+ background: contentColor,
716
+ borderRadius: 20,
717
+ boxShadow: "0 8px 20px rgba(13, 74, 55, 0.08)"
718
+ } : contentMode === "gradient" ? {
719
+ background: contentGradient,
720
+ borderRadius: 20,
721
+ boxShadow: "0 8px 20px rgba(13, 74, 55, 0.08)"
722
+ } : {},
723
+ sectionClass: `orion-builder-shell is-${pageDefaults.pageWidthDefault}`,
724
+ sectionInnerStyle: {
725
+ paddingBottom: sectionPaddingMap[sectionPaddingY],
726
+ paddingTop: sectionPaddingMap[sectionPaddingY]
727
+ },
712
728
  sectionStyle: sectionMode === "color" ? { background: sectionColor } : sectionMode === "gradient" ? { background: sectionGradient } : {}
713
729
  };
714
730
  };
@@ -756,6 +772,51 @@ function getUploadedMediaURL(value) {
756
772
  }
757
773
  return "";
758
774
  }
775
+ var MAX_DIRECT_UPLOAD_BYTES = 4e6;
776
+ async function optimizeImageForUpload(file) {
777
+ if (!file.type.startsWith("image/")) {
778
+ return file;
779
+ }
780
+ if (file.size <= MAX_DIRECT_UPLOAD_BYTES) {
781
+ return file;
782
+ }
783
+ const objectURL = URL.createObjectURL(file);
784
+ try {
785
+ const image = await new Promise((resolve, reject) => {
786
+ const nextImage = new Image();
787
+ nextImage.onload = () => resolve(nextImage);
788
+ nextImage.onerror = () => reject(new Error("Could not read image for upload optimization."));
789
+ nextImage.src = objectURL;
790
+ });
791
+ const maxDimension = 2200;
792
+ const scale = Math.min(1, maxDimension / Math.max(image.width, image.height));
793
+ const targetWidth = Math.max(1, Math.round(image.width * scale));
794
+ const targetHeight = Math.max(1, Math.round(image.height * scale));
795
+ const canvas = document.createElement("canvas");
796
+ canvas.width = targetWidth;
797
+ canvas.height = targetHeight;
798
+ const context = canvas.getContext("2d");
799
+ if (!context) {
800
+ return file;
801
+ }
802
+ context.drawImage(image, 0, 0, targetWidth, targetHeight);
803
+ const outputMime = file.type === "image/webp" ? "image/webp" : "image/jpeg";
804
+ const blob = await new Promise((resolve) => {
805
+ canvas.toBlob((value) => resolve(value), outputMime, 0.82);
806
+ });
807
+ if (!blob) {
808
+ return file;
809
+ }
810
+ const optimizedName = file.name.replace(/\.[^/.]+$/, outputMime === "image/webp" ? ".webp" : ".jpg");
811
+ const optimized = new File([blob], optimizedName, {
812
+ lastModified: Date.now(),
813
+ type: outputMime
814
+ });
815
+ return optimized.size < file.size ? optimized : file;
816
+ } finally {
817
+ URL.revokeObjectURL(objectURL);
818
+ }
819
+ }
759
820
  function InlineText({
760
821
  as = "p",
761
822
  className,
@@ -1195,11 +1256,16 @@ function BuilderPageEditor({ initialDoc, pageID }) {
1195
1256
  setUploadError("");
1196
1257
  setUploadMessage("");
1197
1258
  try {
1259
+ const optimizedFile = await optimizeImageForUpload(file);
1260
+ if (optimizedFile.size > MAX_DIRECT_UPLOAD_BYTES) {
1261
+ throw new Error("Image is too large. Use an image under 4MB or lower-resolution export.");
1262
+ }
1198
1263
  const formData = new FormData();
1199
1264
  const fallbackAlt = file.name.replace(/\.[^/.]+$/, "").trim();
1200
1265
  const resolvedAlt = uploadAltText.trim() || fallbackAlt || "Uploaded image";
1201
1266
  formData.set("_payload", JSON.stringify({ alt: resolvedAlt }));
1202
- formData.set("file", file);
1267
+ formData.set("alt", resolvedAlt);
1268
+ formData.set("file", optimizedFile);
1203
1269
  const response = await fetch("/api/media", {
1204
1270
  body: formData,
1205
1271
  credentials: "include",
@@ -1292,7 +1358,7 @@ function BuilderPageEditor({ initialDoc, pageID }) {
1292
1358
  });
1293
1359
  const renderWithSectionShell = (block, className, content) => {
1294
1360
  const shell = sectionStyleFromBlock(block, pageDefaults);
1295
- return /* @__PURE__ */ jsx("section", { className: `${className} ${shell.sectionClass}`, style: shell.sectionStyle, children: /* @__PURE__ */ jsx("div", { className: shell.contentClass, style: shell.contentStyle, children: content }) });
1361
+ return /* @__PURE__ */ jsx("section", { className: `${className} ${shell.sectionClass}`, style: shell.sectionStyle, children: /* @__PURE__ */ jsx("div", { className: shell.contentClass, style: shell.contentStyle, children: /* @__PURE__ */ jsx("div", { style: shell.sectionInnerStyle, children: content }) }) });
1296
1362
  };
1297
1363
  const sidebarSectionStyle = {
1298
1364
  border: "1px solid rgba(13, 74, 55, 0.15)",
@@ -1348,8 +1414,12 @@ function BuilderPageEditor({ initialDoc, pageID }) {
1348
1414
  }),
1349
1415
  [layout, pageDefaults]
1350
1416
  );
1417
+ const snapshotKey = (snapshot) => JSON.stringify({
1418
+ layout: toPersistedLayout(snapshot.layout),
1419
+ pageDefaults: snapshot.pageDefaults
1420
+ });
1351
1421
  const isDirty = useMemo(
1352
- () => JSON.stringify(currentSnapshot) !== JSON.stringify(lastSavedRef.current),
1422
+ () => snapshotKey(currentSnapshot) !== snapshotKey(lastSavedRef.current),
1353
1423
  [currentSnapshot]
1354
1424
  );
1355
1425
  const canUndo = pastSnapshots.length > 0;
@@ -2824,21 +2894,10 @@ function BuilderPageEditor({ initialDoc, pageID }) {
2824
2894
  }
2825
2895
  )
2826
2896
  ] }),
2827
- /* @__PURE__ */ jsxs("label", { style: sidebarLabelStyle, children: [
2828
- "Section Width",
2829
- /* @__PURE__ */ jsxs(
2830
- "select",
2831
- {
2832
- onChange: (event) => setSelectedStyleField("sectionWidth", event.target.value),
2833
- style: sidebarInputStyle,
2834
- value: normalizeText(selectedSectionStyle.sectionWidth, "content"),
2835
- children: [
2836
- /* @__PURE__ */ jsx("option", { value: "content", children: "Content" }),
2837
- /* @__PURE__ */ jsx("option", { value: "wide", children: "Wide" }),
2838
- /* @__PURE__ */ jsx("option", { value: "full", children: "Full" })
2839
- ]
2840
- }
2841
- )
2897
+ /* @__PURE__ */ jsxs("div", { style: { color: "var(--ink-700)", fontSize: 11 }, children: [
2898
+ "Section width follows ",
2899
+ /* @__PURE__ */ jsx("strong", { children: "Page Defaults" }),
2900
+ " for a consistent page layout."
2842
2901
  ] }),
2843
2902
  /* @__PURE__ */ jsxs("label", { style: sidebarLabelStyle, children: [
2844
2903
  "Content Width",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@orion-studios/payload-studio",
3
- "version": "0.5.0-beta.0",
3
+ "version": "0.5.0-beta.2",
4
4
  "description": "Unified Payload CMS toolkit for Orion Studios",
5
5
  "types": "./dist/index.d.ts",
6
6
  "exports": {