@unicitylabs/sphere-ui 0.1.23 → 0.1.25
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.d.ts +11 -1
- package/dist/index.js +84 -10
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -334,8 +334,18 @@ interface MediaGalleryProps {
|
|
|
334
334
|
onChange: (items: MediaItem[]) => void;
|
|
335
335
|
uploadFn: MediaUploadFn;
|
|
336
336
|
max?: number;
|
|
337
|
+
/**
|
|
338
|
+
* Defer mode: do NOT upload on select. Each chosen screenshot is kept locally
|
|
339
|
+
* (a File + blob preview) and the current File list is emitted via
|
|
340
|
+
* onFilesChange, so the parent can upload them AFTER the owner entity exists
|
|
341
|
+
* (e.g. project create, before there is an ownerId — mirrors MediaUploader's
|
|
342
|
+
* deferUpload for logo/banner). In this mode `items`/`onChange` are unused for
|
|
343
|
+
* screenshots; the parent owns the upload + persistence.
|
|
344
|
+
*/
|
|
345
|
+
deferUpload?: boolean;
|
|
346
|
+
onFilesChange?: (files: File[]) => void;
|
|
337
347
|
}
|
|
338
|
-
declare function MediaGallery({ ownerType, ownerId, items, onChange, uploadFn, max }: MediaGalleryProps): react.JSX.Element;
|
|
348
|
+
declare function MediaGallery({ ownerType, ownerId, items, onChange, uploadFn, max, deferUpload, onFilesChange }: MediaGalleryProps): react.JSX.Element;
|
|
339
349
|
|
|
340
350
|
interface MarketplaceProjectCardProps {
|
|
341
351
|
name: string;
|
package/dist/index.js
CHANGED
|
@@ -1411,6 +1411,17 @@ import { Fragment as Fragment4, jsx as jsx23, jsxs as jsxs18 } from "react/jsx-r
|
|
|
1411
1411
|
function formatExtensions(mimes) {
|
|
1412
1412
|
return mimes.map((m) => m.split("/")[1].toUpperCase()).join(", ");
|
|
1413
1413
|
}
|
|
1414
|
+
async function readImageSize(file) {
|
|
1415
|
+
if (typeof createImageBitmap !== "function") return null;
|
|
1416
|
+
try {
|
|
1417
|
+
const bitmap = await createImageBitmap(file);
|
|
1418
|
+
const size = { width: bitmap.width, height: bitmap.height };
|
|
1419
|
+
bitmap.close?.();
|
|
1420
|
+
return size;
|
|
1421
|
+
} catch {
|
|
1422
|
+
return null;
|
|
1423
|
+
}
|
|
1424
|
+
}
|
|
1414
1425
|
function MediaUploader({
|
|
1415
1426
|
kind,
|
|
1416
1427
|
ownerType,
|
|
@@ -1453,6 +1464,30 @@ function MediaUploader({
|
|
|
1453
1464
|
});
|
|
1454
1465
|
return;
|
|
1455
1466
|
}
|
|
1467
|
+
if (file.type !== "image/svg+xml") {
|
|
1468
|
+
const size = await readImageSize(file);
|
|
1469
|
+
if (size) {
|
|
1470
|
+
const { width, height } = size;
|
|
1471
|
+
if (limit.maxWidth && width > limit.maxWidth || limit.maxHeight && height > limit.maxHeight) {
|
|
1472
|
+
setState({
|
|
1473
|
+
phase: "error",
|
|
1474
|
+
message: `Image too large (max ${limit.maxWidth}\xD7${limit.maxHeight}px \u2014 this is ${width}\xD7${height})`
|
|
1475
|
+
});
|
|
1476
|
+
return;
|
|
1477
|
+
}
|
|
1478
|
+
if (limit.aspectRatio) {
|
|
1479
|
+
const ratio = width / height;
|
|
1480
|
+
const tolerance = limit.aspectTolerance ?? 0;
|
|
1481
|
+
if (Math.abs(ratio - limit.aspectRatio) > limit.aspectRatio * tolerance) {
|
|
1482
|
+
setState({
|
|
1483
|
+
phase: "error",
|
|
1484
|
+
message: `Wrong aspect ratio (need ~${limit.aspectRatio}:1 \u2014 this is ${ratio.toFixed(2)}:1)`
|
|
1485
|
+
});
|
|
1486
|
+
return;
|
|
1487
|
+
}
|
|
1488
|
+
}
|
|
1489
|
+
}
|
|
1490
|
+
}
|
|
1456
1491
|
if (deferUpload) {
|
|
1457
1492
|
if (previewRef.current) URL.revokeObjectURL(previewRef.current);
|
|
1458
1493
|
previewRef.current = URL.createObjectURL(file);
|
|
@@ -1656,7 +1691,7 @@ function MediaUploader({
|
|
|
1656
1691
|
}
|
|
1657
1692
|
|
|
1658
1693
|
// src/components/media/MediaGallery.tsx
|
|
1659
|
-
import { useState as useState8 } from "react";
|
|
1694
|
+
import { useEffect as useEffect6, useState as useState8 } from "react";
|
|
1660
1695
|
import {
|
|
1661
1696
|
DndContext,
|
|
1662
1697
|
closestCenter
|
|
@@ -1690,14 +1725,34 @@ function SortableTile({ item, onRemove }) {
|
|
|
1690
1725
|
)
|
|
1691
1726
|
] });
|
|
1692
1727
|
}
|
|
1693
|
-
function MediaGallery({ ownerType, ownerId, items, onChange, uploadFn, max = 10 }) {
|
|
1728
|
+
function MediaGallery({ ownerType, ownerId, items, onChange, uploadFn, max = 10, deferUpload, onFilesChange }) {
|
|
1694
1729
|
const [adding, setAdding] = useState8(false);
|
|
1730
|
+
const [pending, setPending] = useState8([]);
|
|
1731
|
+
useEffect6(
|
|
1732
|
+
() => () => {
|
|
1733
|
+
pending.forEach((p) => URL.revokeObjectURL(p.preview));
|
|
1734
|
+
},
|
|
1735
|
+
[]
|
|
1736
|
+
// eslint-disable-line react-hooks/exhaustive-deps
|
|
1737
|
+
);
|
|
1738
|
+
const emitPending = (next) => {
|
|
1739
|
+
setPending(next);
|
|
1740
|
+
onFilesChange?.(next.map((p) => p.file));
|
|
1741
|
+
};
|
|
1695
1742
|
function handleDragEnd(e) {
|
|
1696
1743
|
if (!e.over || e.active.id === e.over.id) return;
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1744
|
+
if (deferUpload) {
|
|
1745
|
+
const oldIndex = pending.findIndex((p) => p.preview === e.active.id);
|
|
1746
|
+
const newIndex = pending.findIndex((p) => p.preview === e.over.id);
|
|
1747
|
+
emitPending(arrayMove(pending, oldIndex, newIndex));
|
|
1748
|
+
} else {
|
|
1749
|
+
const oldIndex = items.findIndex((i) => i.url === e.active.id);
|
|
1750
|
+
const newIndex = items.findIndex((i) => i.url === e.over.id);
|
|
1751
|
+
onChange(arrayMove(items, oldIndex, newIndex));
|
|
1752
|
+
}
|
|
1700
1753
|
}
|
|
1754
|
+
const count = deferUpload ? pending.length : items.length;
|
|
1755
|
+
const tileIds = deferUpload ? pending.map((p) => p.preview) : items.map((i) => i.url);
|
|
1701
1756
|
return /* @__PURE__ */ jsxs19(
|
|
1702
1757
|
"div",
|
|
1703
1758
|
{
|
|
@@ -1707,13 +1762,23 @@ function MediaGallery({ ownerType, ownerId, items, onChange, uploadFn, max = 10
|
|
|
1707
1762
|
children: [
|
|
1708
1763
|
/* @__PURE__ */ jsxs19("div", { className: "text-sm text-neutral-700 dark:text-white/70", children: [
|
|
1709
1764
|
"Screenshots (",
|
|
1710
|
-
|
|
1765
|
+
count,
|
|
1711
1766
|
"/",
|
|
1712
1767
|
max,
|
|
1713
1768
|
")"
|
|
1714
1769
|
] }),
|
|
1715
|
-
/* @__PURE__ */ jsx24(DndContext, { collisionDetection: closestCenter, onDragEnd: handleDragEnd, children: /* @__PURE__ */ jsx24(SortableContext, { items:
|
|
1716
|
-
|
|
1770
|
+
/* @__PURE__ */ jsx24(DndContext, { collisionDetection: closestCenter, onDragEnd: handleDragEnd, children: /* @__PURE__ */ jsx24(SortableContext, { items: tileIds, strategy: horizontalListSortingStrategy, children: /* @__PURE__ */ jsxs19("div", { className: "flex flex-wrap gap-2", children: [
|
|
1771
|
+
deferUpload ? pending.map((p, i) => /* @__PURE__ */ jsx24(
|
|
1772
|
+
SortableTile,
|
|
1773
|
+
{
|
|
1774
|
+
item: { type: "screenshot", url: p.preview },
|
|
1775
|
+
onRemove: () => {
|
|
1776
|
+
URL.revokeObjectURL(p.preview);
|
|
1777
|
+
emitPending(pending.filter((_, j) => j !== i));
|
|
1778
|
+
}
|
|
1779
|
+
},
|
|
1780
|
+
p.preview
|
|
1781
|
+
)) : items.map((item, i) => /* @__PURE__ */ jsx24(
|
|
1717
1782
|
SortableTile,
|
|
1718
1783
|
{
|
|
1719
1784
|
item,
|
|
@@ -1721,7 +1786,7 @@ function MediaGallery({ ownerType, ownerId, items, onChange, uploadFn, max = 10
|
|
|
1721
1786
|
},
|
|
1722
1787
|
item.url
|
|
1723
1788
|
)),
|
|
1724
|
-
|
|
1789
|
+
count < max && !adding && /* @__PURE__ */ jsx24(
|
|
1725
1790
|
"button",
|
|
1726
1791
|
{
|
|
1727
1792
|
type: "button",
|
|
@@ -1740,7 +1805,16 @@ function MediaGallery({ ownerType, ownerId, items, onChange, uploadFn, max = 10
|
|
|
1740
1805
|
ownerType,
|
|
1741
1806
|
ownerId,
|
|
1742
1807
|
uploadFn,
|
|
1743
|
-
|
|
1808
|
+
deferUpload,
|
|
1809
|
+
onFileSelected: deferUpload ? (file) => {
|
|
1810
|
+
if (file) {
|
|
1811
|
+
const preview = URL.createObjectURL(file);
|
|
1812
|
+
emitPending([...pending, { file, preview }]);
|
|
1813
|
+
}
|
|
1814
|
+
setAdding(false);
|
|
1815
|
+
} : void 0,
|
|
1816
|
+
onChange: deferUpload ? () => {
|
|
1817
|
+
} : (url) => {
|
|
1744
1818
|
if (url) {
|
|
1745
1819
|
const isDuplicate = items.some((i) => i.url === url);
|
|
1746
1820
|
if (!isDuplicate) {
|