@gallop.software/studio 2.3.88 → 2.3.90

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-BsTg7FtF.js"></script>
14
+ <script type="module" crossorigin src="/assets/index-D7CzOTkD.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,
@@ -4300,8 +4300,8 @@ async function handleGenerateFeaturedImage(request) {
4300
4300
  }
4301
4301
  } catch {
4302
4302
  }
4303
- const outputPath = getPublicPath(`featured.jpg`);
4304
- const relativePath = `public/featured.jpg`;
4303
+ const outputPath = getPublicPath(`screenshot.jpg`);
4304
+ const relativePath = `public/screenshot.jpg`;
4305
4305
  sendEvent({
4306
4306
  type: "start",
4307
4307
  total: 4,
@@ -4363,7 +4363,7 @@ async function handleGenerateFeaturedImage(request) {
4363
4363
  const width = metadata.width || 0;
4364
4364
  const height = metadata.height || 0;
4365
4365
  const meta = await loadMeta();
4366
- const metaKey = `/featured.jpg`;
4366
+ const metaKey = `/screenshot.jpg`;
4367
4367
  setMetaEntry(meta, metaKey, {
4368
4368
  o: { w: width, h: height }
4369
4369
  });
@@ -4373,7 +4373,7 @@ async function handleGenerateFeaturedImage(request) {
4373
4373
  processed: 1,
4374
4374
  errors: 0,
4375
4375
  outputPath: relativePath,
4376
- message: `Featured image saved to ${relativePath}`
4376
+ message: `Screenshot saved to ${relativePath}`
4377
4377
  });
4378
4378
  } finally {
4379
4379
  await browser.close();
@@ -4384,7 +4384,7 @@ async function handleGenerateFeaturedImage(request) {
4384
4384
  const errorMessage = error instanceof Error ? error.message : "Unknown error";
4385
4385
  sendEvent({
4386
4386
  type: "error",
4387
- message: `Failed to generate featured image: ${errorMessage}`
4387
+ message: `Failed to generate screenshot: ${errorMessage}`
4388
4388
  });
4389
4389
  controller.close();
4390
4390
  }
@@ -4439,8 +4439,8 @@ async function handleGetFeaturedImageOptions() {
4439
4439
  }
4440
4440
  async function handleCheckFeaturedImage() {
4441
4441
  try {
4442
- const expectedFilename = `featured.jpg`;
4443
- const metaKey = `/featured.jpg`;
4442
+ const expectedFilename = `screenshot.jpg`;
4443
+ const metaKey = `/screenshot.jpg`;
4444
4444
  const meta = await loadMeta();
4445
4445
  const exists = metaKey in meta && !Array.isArray(meta[metaKey]);
4446
4446
  return jsonResponse({
@@ -4456,6 +4456,124 @@ 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
+ let pipeline = sharp7(imageBuffer).rotate();
4484
+ if (rotation !== 0) {
4485
+ pipeline = pipeline.rotate(rotation);
4486
+ }
4487
+ const rotatedBuffer = await pipeline.toBuffer();
4488
+ const rotatedMeta = await sharp7(rotatedBuffer).metadata();
4489
+ const rotatedWidth = rotatedMeta.width || 0;
4490
+ const rotatedHeight = rotatedMeta.height || 0;
4491
+ const cropX = Math.max(0, Math.min(crop.x, rotatedWidth - 1));
4492
+ const cropY = Math.max(0, Math.min(crop.y, rotatedHeight - 1));
4493
+ const cropWidth = Math.min(crop.width, rotatedWidth - cropX);
4494
+ const cropHeight = Math.min(crop.height, rotatedHeight - cropY);
4495
+ let croppedPipeline = sharp7(rotatedBuffer);
4496
+ if (cropX > 0 || cropY > 0 || cropWidth < rotatedWidth || cropHeight < rotatedHeight) {
4497
+ croppedPipeline = croppedPipeline.extract({
4498
+ left: Math.round(cropX),
4499
+ top: Math.round(cropY),
4500
+ width: Math.round(cropWidth),
4501
+ height: Math.round(cropHeight)
4502
+ });
4503
+ }
4504
+ if (resize.width !== cropWidth || resize.height !== cropHeight) {
4505
+ croppedPipeline = croppedPipeline.resize(resize.width, resize.height);
4506
+ }
4507
+ const ext = path10.extname(imagePath).toLowerCase();
4508
+ let finalBuffer;
4509
+ if (ext === ".png") {
4510
+ finalBuffer = await croppedPipeline.png({ quality: 85 }).toBuffer();
4511
+ } else if (ext === ".webp") {
4512
+ finalBuffer = await croppedPipeline.webp({ quality: 85 }).toBuffer();
4513
+ } else if (ext === ".gif") {
4514
+ finalBuffer = await croppedPipeline.gif().toBuffer();
4515
+ } else {
4516
+ finalBuffer = await croppedPipeline.jpeg({ quality: 85 }).toBuffer();
4517
+ }
4518
+ const finalMeta = await sharp7(finalBuffer).metadata();
4519
+ const finalWidth = finalMeta.width || resize.width;
4520
+ const finalHeight = finalMeta.height || resize.height;
4521
+ await fs11.writeFile(absolutePath, finalBuffer);
4522
+ const meta = await loadMeta();
4523
+ const imageKey = "/" + imagePath.replace(/^public\//, "");
4524
+ const entry = meta[imageKey];
4525
+ const updatedEntry = {
4526
+ ...entry,
4527
+ o: { w: finalWidth, h: finalHeight }
4528
+ };
4529
+ delete updatedEntry.sm;
4530
+ delete updatedEntry.md;
4531
+ delete updatedEntry.lg;
4532
+ delete updatedEntry.f;
4533
+ meta[imageKey] = updatedEntry;
4534
+ await saveMeta(meta);
4535
+ const thumbnailPaths = getAllThumbnailPaths(imageKey);
4536
+ for (const thumbPath of thumbnailPaths) {
4537
+ const absoluteThumbPath = getPublicPath(thumbPath);
4538
+ try {
4539
+ await fs11.unlink(absoluteThumbPath);
4540
+ } catch {
4541
+ }
4542
+ }
4543
+ const stats = await fs11.stat(absolutePath);
4544
+ const updatedItem = {
4545
+ name: path10.basename(imagePath),
4546
+ path: imagePath,
4547
+ type: "file",
4548
+ size: stats.size,
4549
+ dimensions: { width: finalWidth, height: finalHeight },
4550
+ hasSm: false,
4551
+ hasMd: false,
4552
+ hasLg: false,
4553
+ hasFull: false,
4554
+ hasThumbnail: false,
4555
+ // Preserve CDN status from original entry
4556
+ cdnPushed: entry?.c !== void 0,
4557
+ isRemote: false,
4558
+ // If we're editing, it's local
4559
+ hasUpdate: entry?.c !== void 0
4560
+ // If was on CDN, now has local update
4561
+ };
4562
+ return jsonResponse({
4563
+ success: true,
4564
+ updatedItem,
4565
+ dimensions: { width: finalWidth, height: finalHeight }
4566
+ });
4567
+ } catch (error) {
4568
+ console.error("Edit image error:", error);
4569
+ const message = error instanceof Error ? error.message : "Unknown error";
4570
+ return jsonResponse(
4571
+ { error: `Failed to edit image: ${message}` },
4572
+ { status: 500 }
4573
+ );
4574
+ }
4575
+ }
4576
+
4459
4577
  // src/server/index.ts
4460
4578
  var __filename = fileURLToPath(import.meta.url);
4461
4579
  var __dirname = dirname(__filename);
@@ -4534,6 +4652,7 @@ async function startServer(options) {
4534
4652
  app.post("/api/studio/rename", wrapHandler(handleRename));
4535
4653
  app.post("/api/studio/rename-stream", wrapHandler(handleRenameStream, true));
4536
4654
  app.post("/api/studio/move", wrapHandler(handleMoveStream, true));
4655
+ app.post("/api/studio/edit-image", wrapHandler(handleEditImage));
4537
4656
  app.post("/api/studio/sync", wrapHandler(handleSync, true));
4538
4657
  app.post("/api/studio/sync-stream", wrapHandler(handleSyncStream, true));
4539
4658
  app.post(