@ai-matrx/capture 0.5.2 → 0.5.3

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/react.js CHANGED
@@ -1175,6 +1175,8 @@ function ImageEditSheet({
1175
1175
  const [workingSrc, setWorkingSrc] = useState4(null);
1176
1176
  const [aspect, setAspect] = useState4("free");
1177
1177
  const [crop, setCrop] = useState4(FULL_CROP);
1178
+ const [imageReady, setImageReady] = useState4(false);
1179
+ const [transforming, setTransforming] = useState4(false);
1178
1180
  const [saving, setSaving] = useState4(false);
1179
1181
  const [editError, setEditError] = useState4(null);
1180
1182
  const stageRef = useRef2(null);
@@ -1190,6 +1192,8 @@ function ImageEditSheet({
1190
1192
  setWorkingSrc(null);
1191
1193
  setAspect("free");
1192
1194
  setCrop(FULL_CROP);
1195
+ setImageReady(false);
1196
+ setTransforming(false);
1193
1197
  setSaving(false);
1194
1198
  setEditError(null);
1195
1199
  }
@@ -1198,21 +1202,53 @@ function ImageEditSheet({
1198
1202
  const bake = useCallback3(
1199
1203
  (op) => {
1200
1204
  const img = imgRef.current;
1201
- if (!img || img.naturalWidth === 0 || saving) return;
1205
+ if (!img || !imageReady || img.naturalWidth === 0 || saving || transforming) {
1206
+ setEditError("The image is still loading \u2014 wait a moment and try again.");
1207
+ return;
1208
+ }
1209
+ setTransforming(true);
1210
+ setEditError(null);
1202
1211
  try {
1212
+ const sourceX = Math.round(crop.x * img.naturalWidth);
1213
+ const sourceY = Math.round(crop.y * img.naturalHeight);
1214
+ const sourceWidth = Math.max(
1215
+ 1,
1216
+ Math.min(
1217
+ img.naturalWidth - sourceX,
1218
+ Math.round(crop.w * img.naturalWidth)
1219
+ )
1220
+ );
1221
+ const sourceHeight = Math.max(
1222
+ 1,
1223
+ Math.min(
1224
+ img.naturalHeight - sourceY,
1225
+ Math.round(crop.h * img.naturalHeight)
1226
+ )
1227
+ );
1203
1228
  const swap = op === "rotate-left";
1204
1229
  const canvas = document.createElement("canvas");
1205
- canvas.width = swap ? img.naturalHeight : img.naturalWidth;
1206
- canvas.height = swap ? img.naturalWidth : img.naturalHeight;
1230
+ canvas.width = swap ? sourceHeight : sourceWidth;
1231
+ canvas.height = swap ? sourceWidth : sourceHeight;
1207
1232
  const ctx = canvas.getContext("2d");
1208
1233
  if (!ctx) throw new Error("no 2d context");
1209
1234
  ctx.translate(canvas.width / 2, canvas.height / 2);
1210
1235
  if (swap) ctx.rotate(-Math.PI / 2);
1211
1236
  else ctx.scale(-1, 1);
1212
- ctx.drawImage(img, -img.naturalWidth / 2, -img.naturalHeight / 2);
1237
+ ctx.drawImage(
1238
+ img,
1239
+ sourceX,
1240
+ sourceY,
1241
+ sourceWidth,
1242
+ sourceHeight,
1243
+ -sourceWidth / 2,
1244
+ -sourceHeight / 2,
1245
+ sourceWidth,
1246
+ sourceHeight
1247
+ );
1213
1248
  canvas.toBlob(
1214
1249
  (blob) => {
1215
1250
  if (!blob) {
1251
+ setTransforming(false);
1216
1252
  setEditError(
1217
1253
  "This image is too large to process on this device."
1218
1254
  );
@@ -1221,26 +1257,32 @@ function ImageEditSheet({
1221
1257
  revokeOwned();
1222
1258
  const url = URL.createObjectURL(blob);
1223
1259
  ownedUrlsRef.current.push(url);
1260
+ setImageReady(false);
1224
1261
  setWorkingSrc(url);
1225
1262
  setCrop(FULL_CROP);
1226
1263
  setAspect("free");
1264
+ setTransforming(false);
1227
1265
  setEditError(null);
1228
1266
  },
1229
1267
  "image/jpeg",
1230
1268
  0.95
1231
1269
  );
1232
1270
  } catch {
1271
+ setTransforming(false);
1233
1272
  setEditError("This image could not be edited on this device.");
1234
1273
  }
1235
1274
  },
1236
- [saving, revokeOwned]
1275
+ [crop, imageReady, saving, transforming, revokeOwned]
1237
1276
  );
1238
1277
  const applyAspect = useCallback3((preset) => {
1278
+ const img = imgRef.current;
1279
+ if (!img || img.naturalWidth === 0) {
1280
+ setEditError("The image is still loading \u2014 wait a moment and try again.");
1281
+ return;
1282
+ }
1239
1283
  setAspect(preset);
1240
1284
  const ratio = ASPECTS.find((a) => a.id === preset)?.ratio ?? null;
1241
1285
  if (ratio === null) return;
1242
- const img = imgRef.current;
1243
- if (!img || img.naturalWidth === 0) return;
1244
1286
  const imageRatio = img.naturalWidth / img.naturalHeight;
1245
1287
  let w = 1;
1246
1288
  let h = 1;
@@ -1305,7 +1347,10 @@ function ImageEditSheet({
1305
1347
  }, []);
1306
1348
  const save = useCallback3(() => {
1307
1349
  const img = imgRef.current;
1308
- if (!img || img.naturalWidth === 0) return;
1350
+ if (!img || !imageReady || img.naturalWidth === 0) {
1351
+ setEditError("The image is still loading \u2014 wait a moment and try again.");
1352
+ return;
1353
+ }
1309
1354
  setSaving(true);
1310
1355
  setEditError(null);
1311
1356
  try {
@@ -1335,7 +1380,7 @@ function ImageEditSheet({
1335
1380
  setSaving(false);
1336
1381
  setEditError("Saving failed on this device \u2014 try again.");
1337
1382
  }
1338
- }, [crop, onSave, onClose]);
1383
+ }, [crop, imageReady, onSave, onClose]);
1339
1384
  if (!open || !src) return null;
1340
1385
  return /* @__PURE__ */ jsxs9("div", { className: "absolute inset-0 z-50 flex flex-col bg-black", children: [
1341
1386
  /* @__PURE__ */ jsxs9(
@@ -1363,12 +1408,13 @@ function ImageEditSheet({
1363
1408
  {
1364
1409
  type: "button",
1365
1410
  onClick: save,
1366
- disabled: saving,
1411
+ disabled: saving || transforming || !imageReady,
1367
1412
  "aria-label": "Save edited image",
1413
+ "aria-busy": saving || transforming || !imageReady,
1368
1414
  className: "flex h-11 items-center gap-1.5 rounded-full px-3 text-[15px] font-semibold text-[#FFCC00] disabled:opacity-50",
1369
1415
  children: [
1370
1416
  /* @__PURE__ */ jsx11(CheckIcon, { className: "h-5 w-5" }),
1371
- "Save"
1417
+ saving ? "Saving\u2026" : transforming || !imageReady ? "Loading\u2026" : "Save"
1372
1418
  ]
1373
1419
  }
1374
1420
  )
@@ -1391,6 +1437,12 @@ function ImageEditSheet({
1391
1437
  src: workingSrc ?? src,
1392
1438
  alt: "Image being edited",
1393
1439
  draggable: false,
1440
+ onLoad: () => setImageReady(true),
1441
+ onError: () => {
1442
+ setImageReady(false);
1443
+ setTransforming(false);
1444
+ setEditError("This image could not be loaded on this device.");
1445
+ },
1394
1446
  className: "max-h-[62dvh] max-w-full select-none object-contain"
1395
1447
  }
1396
1448
  ),
@@ -1434,6 +1486,7 @@ function ImageEditSheet({
1434
1486
  {
1435
1487
  type: "button",
1436
1488
  onClick: () => applyAspect(a.id),
1489
+ disabled: saving || transforming || !imageReady,
1437
1490
  "aria-pressed": aspect === a.id,
1438
1491
  className: cn(
1439
1492
  "touch-manipulation rounded-full px-3.5 py-1.5 text-[12px] font-semibold uppercase tracking-wide transition-colors",
@@ -1449,6 +1502,7 @@ function ImageEditSheet({
1449
1502
  {
1450
1503
  type: "button",
1451
1504
  onClick: () => bake("rotate-left"),
1505
+ disabled: saving || transforming || !imageReady,
1452
1506
  "aria-label": "Rotate left",
1453
1507
  className: "flex h-12 w-12 touch-manipulation items-center justify-center rounded-full bg-white/10 text-white",
1454
1508
  children: /* @__PURE__ */ jsx11(RotateCcwIcon, { className: "h-5 w-5" })
@@ -1459,6 +1513,7 @@ function ImageEditSheet({
1459
1513
  {
1460
1514
  type: "button",
1461
1515
  onClick: () => bake("flip"),
1516
+ disabled: saving || transforming || !imageReady,
1462
1517
  "aria-label": "Flip horizontally",
1463
1518
  className: "flex h-12 w-12 touch-manipulation items-center justify-center rounded-full bg-white/10 text-white",
1464
1519
  children: /* @__PURE__ */ jsx11(FlipHorizontal2Icon, { className: "h-5 w-5" })