@gallop.software/studio 2.3.89 → 2.3.91

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.
@@ -11,7 +11,7 @@
11
11
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
12
12
  }
13
13
  </style>
14
- <script type="module" crossorigin src="/assets/index-NLWtKEEw.js"></script>
14
+ <script type="module" crossorigin src="/assets/index-Bgp5iXtu.js"></script>
15
15
  </head>
16
16
  <body>
17
17
  <div id="root"></div>
@@ -3967,8 +3967,8 @@ import sharp4 from "sharp";
3967
3967
  function parseImageUrl(url) {
3968
3968
  const parsed = new URL(url);
3969
3969
  const base = `${parsed.protocol}//${parsed.host}`;
3970
- const path10 = parsed.pathname;
3971
- return { base, path: path10 };
3970
+ const path11 = parsed.pathname;
3971
+ return { base, path: path11 };
3972
3972
  }
3973
3973
  async function processRemoteImage(url) {
3974
3974
  const response = await fetch(url);
@@ -4029,10 +4029,10 @@ async function handleImportUrls(request) {
4029
4029
  const url = urls[i].trim();
4030
4030
  if (!url) continue;
4031
4031
  try {
4032
- const { base, path: path10 } = parseImageUrl(url);
4033
- const existingEntry = getMetaEntry(meta, path10);
4032
+ const { base, path: path11 } = parseImageUrl(url);
4033
+ const existingEntry = getMetaEntry(meta, path11);
4034
4034
  if (existingEntry) {
4035
- skipped.push(path10);
4035
+ skipped.push(path11);
4036
4036
  sendEvent({
4037
4037
  type: "progress",
4038
4038
  current: i + 1,
@@ -4045,13 +4045,13 @@ async function handleImportUrls(request) {
4045
4045
  }
4046
4046
  const cdnIndex = getOrAddCdnIndex(meta, base);
4047
4047
  const imageData = await processRemoteImage(url);
4048
- setMetaEntry(meta, path10, {
4048
+ setMetaEntry(meta, path11, {
4049
4049
  o: imageData.o,
4050
4050
  b: imageData.b,
4051
4051
  c: cdnIndex
4052
4052
  });
4053
4053
  await saveMeta(meta);
4054
- added.push(path10);
4054
+ added.push(path11);
4055
4055
  sendEvent({
4056
4056
  type: "progress",
4057
4057
  current: i + 1,
@@ -4456,6 +4456,137 @@ async function handleCheckFeaturedImage() {
4456
4456
  }
4457
4457
  }
4458
4458
 
4459
+ // src/handlers/edit-image.ts
4460
+ import { promises as fs11 } from "fs";
4461
+ import path10 from "path";
4462
+ import sharp7 from "sharp";
4463
+ async function handleEditImage(request) {
4464
+ try {
4465
+ const body = await request.json();
4466
+ const { imagePath, crop, rotation, resize } = body;
4467
+ if (!imagePath || !imagePath.startsWith("public/")) {
4468
+ return jsonResponse({ error: "Invalid image path" }, { status: 400 });
4469
+ }
4470
+ if (!isImageFile(path10.basename(imagePath))) {
4471
+ return jsonResponse({ error: "Not an image file" }, { status: 400 });
4472
+ }
4473
+ const absolutePath = getWorkspacePath(imagePath);
4474
+ try {
4475
+ await fs11.access(absolutePath);
4476
+ } catch {
4477
+ return jsonResponse(
4478
+ { error: "Image file not found locally. Download it first." },
4479
+ { status: 404 }
4480
+ );
4481
+ }
4482
+ const imageBuffer = await fs11.readFile(absolutePath);
4483
+ const exifCorrectedBuffer = await sharp7(imageBuffer).rotate().toBuffer();
4484
+ const exifMeta = await sharp7(exifCorrectedBuffer).metadata();
4485
+ const exifWidth = exifMeta.width || 0;
4486
+ const exifHeight = exifMeta.height || 0;
4487
+ let rotatedBuffer;
4488
+ let rotatedWidth;
4489
+ let rotatedHeight;
4490
+ if (rotation !== 0) {
4491
+ rotatedBuffer = await sharp7(exifCorrectedBuffer).rotate(rotation).toBuffer();
4492
+ if (rotation === 90 || rotation === 270) {
4493
+ rotatedWidth = exifHeight;
4494
+ rotatedHeight = exifWidth;
4495
+ } else {
4496
+ rotatedWidth = exifWidth;
4497
+ rotatedHeight = exifHeight;
4498
+ }
4499
+ } else {
4500
+ rotatedBuffer = exifCorrectedBuffer;
4501
+ rotatedWidth = exifWidth;
4502
+ rotatedHeight = exifHeight;
4503
+ }
4504
+ const cropX = Math.max(0, Math.min(crop.x, rotatedWidth - 1));
4505
+ const cropY = Math.max(0, Math.min(crop.y, rotatedHeight - 1));
4506
+ const cropWidth = Math.min(crop.width, rotatedWidth - cropX);
4507
+ const cropHeight = Math.min(crop.height, rotatedHeight - cropY);
4508
+ let croppedPipeline = sharp7(rotatedBuffer);
4509
+ if (cropX > 0 || cropY > 0 || cropWidth < rotatedWidth || cropHeight < rotatedHeight) {
4510
+ croppedPipeline = croppedPipeline.extract({
4511
+ left: Math.round(cropX),
4512
+ top: Math.round(cropY),
4513
+ width: Math.round(cropWidth),
4514
+ height: Math.round(cropHeight)
4515
+ });
4516
+ }
4517
+ if (resize.width !== cropWidth || resize.height !== cropHeight) {
4518
+ croppedPipeline = croppedPipeline.resize(resize.width, resize.height);
4519
+ }
4520
+ const ext = path10.extname(imagePath).toLowerCase();
4521
+ let finalBuffer;
4522
+ if (ext === ".png") {
4523
+ finalBuffer = await croppedPipeline.png({ quality: 85 }).toBuffer();
4524
+ } else if (ext === ".webp") {
4525
+ finalBuffer = await croppedPipeline.webp({ quality: 85 }).toBuffer();
4526
+ } else if (ext === ".gif") {
4527
+ finalBuffer = await croppedPipeline.gif().toBuffer();
4528
+ } else {
4529
+ finalBuffer = await croppedPipeline.jpeg({ quality: 85 }).toBuffer();
4530
+ }
4531
+ const finalMeta = await sharp7(finalBuffer).metadata();
4532
+ const finalWidth = finalMeta.width || resize.width;
4533
+ const finalHeight = finalMeta.height || resize.height;
4534
+ await fs11.writeFile(absolutePath, finalBuffer);
4535
+ const meta = await loadMeta();
4536
+ const imageKey = "/" + imagePath.replace(/^public\//, "");
4537
+ const entry = meta[imageKey];
4538
+ const updatedEntry = {
4539
+ ...entry,
4540
+ o: { w: finalWidth, h: finalHeight }
4541
+ };
4542
+ delete updatedEntry.sm;
4543
+ delete updatedEntry.md;
4544
+ delete updatedEntry.lg;
4545
+ delete updatedEntry.f;
4546
+ meta[imageKey] = updatedEntry;
4547
+ await saveMeta(meta);
4548
+ const thumbnailPaths = getAllThumbnailPaths(imageKey);
4549
+ for (const thumbPath of thumbnailPaths) {
4550
+ const absoluteThumbPath = getPublicPath(thumbPath);
4551
+ try {
4552
+ await fs11.unlink(absoluteThumbPath);
4553
+ } catch {
4554
+ }
4555
+ }
4556
+ const stats = await fs11.stat(absolutePath);
4557
+ const updatedItem = {
4558
+ name: path10.basename(imagePath),
4559
+ path: imagePath,
4560
+ type: "file",
4561
+ size: stats.size,
4562
+ dimensions: { width: finalWidth, height: finalHeight },
4563
+ hasSm: false,
4564
+ hasMd: false,
4565
+ hasLg: false,
4566
+ hasFull: false,
4567
+ hasThumbnail: false,
4568
+ // Preserve CDN status from original entry
4569
+ cdnPushed: entry?.c !== void 0,
4570
+ isRemote: false,
4571
+ // If we're editing, it's local
4572
+ hasUpdate: entry?.c !== void 0
4573
+ // If was on CDN, now has local update
4574
+ };
4575
+ return jsonResponse({
4576
+ success: true,
4577
+ updatedItem,
4578
+ dimensions: { width: finalWidth, height: finalHeight }
4579
+ });
4580
+ } catch (error) {
4581
+ console.error("Edit image error:", error);
4582
+ const message = error instanceof Error ? error.message : "Unknown error";
4583
+ return jsonResponse(
4584
+ { error: `Failed to edit image: ${message}` },
4585
+ { status: 500 }
4586
+ );
4587
+ }
4588
+ }
4589
+
4459
4590
  // src/server/index.ts
4460
4591
  var __filename = fileURLToPath(import.meta.url);
4461
4592
  var __dirname = dirname(__filename);
@@ -4534,6 +4665,7 @@ async function startServer(options) {
4534
4665
  app.post("/api/studio/rename", wrapHandler(handleRename));
4535
4666
  app.post("/api/studio/rename-stream", wrapHandler(handleRenameStream, true));
4536
4667
  app.post("/api/studio/move", wrapHandler(handleMoveStream, true));
4668
+ app.post("/api/studio/edit-image", wrapHandler(handleEditImage));
4537
4669
  app.post("/api/studio/sync", wrapHandler(handleSync, true));
4538
4670
  app.post("/api/studio/sync-stream", wrapHandler(handleSyncStream, true));
4539
4671
  app.post(