@gallop.software/studio 2.3.26 → 2.3.28

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-5mrVXxKd.js"></script>
14
+ <script type="module" crossorigin src="/assets/index-OAEAClSN.js"></script>
15
15
  </head>
16
16
  <body>
17
17
  <div id="root"></div>
@@ -386,6 +386,20 @@ async function deleteThumbnailsFromCdn(imageKey) {
386
386
  }
387
387
  }
388
388
  }
389
+ async function deleteOriginalFromCdn(imageKey) {
390
+ const bucketName = process.env.CLOUDFLARE_R2_BUCKET_NAME;
391
+ if (!bucketName) throw new Error("R2 bucket not configured");
392
+ const r2 = getR2Client();
393
+ try {
394
+ await r2.send(
395
+ new DeleteObjectCommand({
396
+ Bucket: bucketName,
397
+ Key: imageKey.replace(/^\//, "")
398
+ })
399
+ );
400
+ } catch {
401
+ }
402
+ }
389
403
 
390
404
  // src/handlers/utils/response.ts
391
405
  function jsonResponse(data, init) {
@@ -1258,6 +1272,7 @@ async function handleCreateFolder(request) {
1258
1272
  }
1259
1273
  }
1260
1274
  async function handleRename(request) {
1275
+ const publicUrl = process.env.CLOUDFLARE_R2_PUBLIC_URL?.replace(/\/$/, "");
1261
1276
  try {
1262
1277
  const { oldPath, newName } = await request.json();
1263
1278
  if (!oldPath || !newName) {
@@ -1268,48 +1283,109 @@ async function handleRename(request) {
1268
1283
  if (!absoluteOldPath.startsWith(getPublicPath())) {
1269
1284
  return jsonResponse({ error: "Invalid path" }, { status: 400 });
1270
1285
  }
1286
+ const oldRelativePath = safePath.replace(/^public\//, "");
1287
+ const oldKey = "/" + oldRelativePath;
1288
+ const isImage = isImageFile(path6.basename(oldPath));
1289
+ const meta = await loadMeta();
1290
+ const cdnUrls = getCdnUrls(meta);
1291
+ const entry = meta[oldKey];
1292
+ const isInCloud = entry?.c !== void 0;
1293
+ const fileCdnUrl = isInCloud && entry.c !== void 0 ? cdnUrls[entry.c] : void 0;
1294
+ const isInOurR2 = isInCloud && fileCdnUrl === publicUrl;
1295
+ const hasThumbnails = entry ? isProcessed(entry) : false;
1296
+ let hasLocalFile = false;
1297
+ let isFile = true;
1271
1298
  try {
1272
- await fs6.access(absoluteOldPath);
1299
+ const stats = await fs6.stat(absoluteOldPath);
1300
+ hasLocalFile = true;
1301
+ isFile = stats.isFile();
1273
1302
  } catch {
1274
- return jsonResponse({ error: "File or folder not found" }, { status: 404 });
1303
+ if (!isInCloud) {
1304
+ return jsonResponse({ error: "File or folder not found" }, { status: 404 });
1305
+ }
1275
1306
  }
1276
- const stats = await fs6.stat(absoluteOldPath);
1277
- const isFile = stats.isFile();
1278
- const isImage = isFile && isImageFile(path6.basename(oldPath));
1279
1307
  const sanitizedName = isFile ? slugifyFilename(newName) : slugifyFolderName(newName);
1280
1308
  if (!sanitizedName) {
1281
1309
  return jsonResponse({ error: "Invalid name" }, { status: 400 });
1282
1310
  }
1283
1311
  const parentDir = path6.dirname(absoluteOldPath);
1284
1312
  const absoluteNewPath = path6.join(parentDir, sanitizedName);
1313
+ const newRelativePath = path6.join(path6.dirname(oldRelativePath), sanitizedName);
1314
+ const newKey = "/" + newRelativePath;
1315
+ if (meta[newKey]) {
1316
+ return jsonResponse({ error: "An item with this name already exists" }, { status: 400 });
1317
+ }
1285
1318
  try {
1286
1319
  await fs6.access(absoluteNewPath);
1287
1320
  return jsonResponse({ error: "An item with this name already exists" }, { status: 400 });
1288
1321
  } catch {
1289
1322
  }
1290
- await fs6.rename(absoluteOldPath, absoluteNewPath);
1291
- if (isImage) {
1292
- const meta = await loadMeta();
1293
- const oldRelativePath = safePath.replace(/^public\//, "");
1294
- const newRelativePath = path6.join(path6.dirname(oldRelativePath), sanitizedName);
1295
- const oldKey = "/" + oldRelativePath;
1296
- const newKey = "/" + newRelativePath;
1297
- if (meta[oldKey]) {
1298
- const entry = meta[oldKey];
1299
- const oldThumbPaths = getAllThumbnailPaths(oldKey);
1323
+ if (isInOurR2 && !hasLocalFile && isImage) {
1324
+ const buffer = await downloadFromCdn(oldKey);
1325
+ await fs6.mkdir(path6.dirname(absoluteNewPath), { recursive: true });
1326
+ await fs6.writeFile(absoluteNewPath, buffer);
1327
+ if (hasThumbnails) {
1300
1328
  const newThumbPaths = getAllThumbnailPaths(newKey);
1329
+ const oldThumbPaths = getAllThumbnailPaths(oldKey);
1301
1330
  for (let i = 0; i < oldThumbPaths.length; i++) {
1302
- const oldThumbPath = getPublicPath(oldThumbPaths[i]);
1303
- const newThumbPath = getPublicPath(newThumbPaths[i]);
1304
- await fs6.mkdir(path6.dirname(newThumbPath), { recursive: true });
1305
1331
  try {
1306
- await fs6.rename(oldThumbPath, newThumbPath);
1332
+ const thumbBuffer = await downloadFromCdn(oldThumbPaths[i]);
1333
+ const newThumbLocalPath = getPublicPath(newThumbPaths[i]);
1334
+ await fs6.mkdir(path6.dirname(newThumbLocalPath), { recursive: true });
1335
+ await fs6.writeFile(newThumbLocalPath, thumbBuffer);
1307
1336
  } catch {
1308
1337
  }
1309
1338
  }
1310
- delete meta[oldKey];
1311
- meta[newKey] = entry;
1312
1339
  }
1340
+ await deleteFromCdn(oldKey, hasThumbnails);
1341
+ await uploadOriginalToCdn(newKey);
1342
+ if (hasThumbnails) {
1343
+ await uploadToCdn(newKey);
1344
+ }
1345
+ try {
1346
+ await fs6.unlink(absoluteNewPath);
1347
+ } catch {
1348
+ }
1349
+ if (hasThumbnails) {
1350
+ await deleteLocalThumbnails(newKey);
1351
+ }
1352
+ delete meta[oldKey];
1353
+ meta[newKey] = entry;
1354
+ await saveMeta(meta);
1355
+ const newPath2 = path6.join(path6.dirname(safePath), sanitizedName);
1356
+ return jsonResponse({ success: true, newPath: newPath2 });
1357
+ }
1358
+ if (hasLocalFile) {
1359
+ await fs6.rename(absoluteOldPath, absoluteNewPath);
1360
+ }
1361
+ if (isImage && entry) {
1362
+ const oldThumbPaths = getAllThumbnailPaths(oldKey);
1363
+ const newThumbPaths = getAllThumbnailPaths(newKey);
1364
+ for (let i = 0; i < oldThumbPaths.length; i++) {
1365
+ const oldThumbPath = getPublicPath(oldThumbPaths[i]);
1366
+ const newThumbPath = getPublicPath(newThumbPaths[i]);
1367
+ await fs6.mkdir(path6.dirname(newThumbPath), { recursive: true });
1368
+ try {
1369
+ await fs6.rename(oldThumbPath, newThumbPath);
1370
+ } catch {
1371
+ }
1372
+ }
1373
+ if (isInOurR2) {
1374
+ const buffer = await fs6.readFile(absoluteNewPath);
1375
+ await fs6.mkdir(path6.dirname(absoluteNewPath), { recursive: true });
1376
+ await deleteFromCdn(oldKey, hasThumbnails);
1377
+ await uploadOriginalToCdn(newKey);
1378
+ if (hasThumbnails) {
1379
+ await uploadToCdn(newKey);
1380
+ }
1381
+ try {
1382
+ await fs6.unlink(absoluteNewPath);
1383
+ } catch {
1384
+ }
1385
+ await deleteLocalThumbnails(newKey);
1386
+ }
1387
+ delete meta[oldKey];
1388
+ meta[newKey] = entry;
1313
1389
  await saveMeta(meta);
1314
1390
  }
1315
1391
  const newPath = path6.join(path6.dirname(safePath), sanitizedName);
@@ -1914,7 +1990,9 @@ async function handleReprocessStream(request) {
1914
1990
  const updatedEntry = await processImage(buffer, imageKey);
1915
1991
  if (isInOurR2) {
1916
1992
  updatedEntry.c = existingCdnIndex;
1993
+ await deleteOriginalFromCdn(imageKey);
1917
1994
  await deleteThumbnailsFromCdn(imageKey);
1995
+ await uploadOriginalToCdn(imageKey);
1918
1996
  await uploadToCdn(imageKey);
1919
1997
  await deleteLocalThumbnails(imageKey);
1920
1998
  try {