@gallop.software/studio 0.1.72 → 0.1.74
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/{StudioUI-4FQNJCLU.js → StudioUI-4ODY2HZQ.js} +1 -1
- package/dist/StudioUI-4ODY2HZQ.js.map +1 -0
- package/dist/{StudioUI-YHQ2KQV6.mjs → StudioUI-N5Q5PMSR.mjs} +1 -1
- package/dist/StudioUI-N5Q5PMSR.mjs.map +1 -0
- package/dist/chunk-3RI33B7A.mjs +27 -0
- package/dist/chunk-3RI33B7A.mjs.map +1 -0
- package/dist/chunk-CN5NRNWB.js +27 -0
- package/dist/chunk-CN5NRNWB.js.map +1 -0
- package/dist/handlers.d.mts +1 -1
- package/dist/handlers.d.ts +1 -1
- package/dist/handlers.js +146 -305
- package/dist/handlers.js.map +1 -1
- package/dist/handlers.mjs +135 -294
- package/dist/handlers.mjs.map +1 -1
- package/dist/index.d.mts +2 -25
- package/dist/index.d.ts +2 -25
- package/dist/index.js +7 -37
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +7 -37
- package/dist/index.mjs.map +1 -1
- package/dist/types-C9CMIJLW.d.mts +57 -0
- package/dist/types-C9CMIJLW.d.ts +57 -0
- package/package.json +1 -1
- package/dist/StudioUI-4FQNJCLU.js.map +0 -1
- package/dist/StudioUI-YHQ2KQV6.mjs.map +0 -1
- package/dist/types-1m_7EjJU.d.mts +0 -79
- package/dist/types-1m_7EjJU.d.ts +0 -79
package/dist/handlers.mjs
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getAllThumbnailPaths
|
|
3
|
+
} from "./chunk-3RI33B7A.mjs";
|
|
4
|
+
|
|
1
5
|
// src/handlers.ts
|
|
2
6
|
import { NextResponse } from "next/server";
|
|
3
7
|
import { promises as fs } from "fs";
|
|
@@ -252,9 +256,9 @@ async function handleScan() {
|
|
|
252
256
|
const validFiles = [];
|
|
253
257
|
const imagesDir = path.join(process.cwd(), "public", "images");
|
|
254
258
|
const trackedPaths = /* @__PURE__ */ new Set();
|
|
255
|
-
for (const
|
|
256
|
-
for (const
|
|
257
|
-
trackedPaths.add(
|
|
259
|
+
for (const imageKey of Object.keys(meta)) {
|
|
260
|
+
for (const thumbPath of getAllThumbnailPaths(imageKey)) {
|
|
261
|
+
trackedPaths.add(thumbPath);
|
|
258
262
|
}
|
|
259
263
|
}
|
|
260
264
|
async function scanDir(dir, relativePath = "") {
|
|
@@ -279,20 +283,20 @@ async function handleScan() {
|
|
|
279
283
|
}
|
|
280
284
|
}
|
|
281
285
|
await scanDir(imagesDir);
|
|
282
|
-
for (const [
|
|
283
|
-
|
|
284
|
-
|
|
286
|
+
for (const [imageKey, entry] of Object.entries(meta)) {
|
|
287
|
+
if (entry.s) continue;
|
|
288
|
+
for (const thumbPath of getAllThumbnailPaths(imageKey)) {
|
|
289
|
+
const filePath = path.join(process.cwd(), "public", thumbPath);
|
|
285
290
|
try {
|
|
286
291
|
await fs.access(filePath);
|
|
287
292
|
} catch {
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
}
|
|
293
|
+
missingFiles.push(`${imageKey}: ${thumbPath}`);
|
|
294
|
+
break;
|
|
291
295
|
}
|
|
292
296
|
}
|
|
293
297
|
}
|
|
294
298
|
return NextResponse.json({
|
|
295
|
-
totalInMeta: Object.keys(meta
|
|
299
|
+
totalInMeta: Object.keys(meta).length,
|
|
296
300
|
validFiles: validFiles.length,
|
|
297
301
|
untrackedFiles,
|
|
298
302
|
missingFiles
|
|
@@ -319,9 +323,6 @@ async function handleUpload(request) {
|
|
|
319
323
|
const isSvg = ext === ".svg";
|
|
320
324
|
const isProcessableImage = isImage && !isSvg;
|
|
321
325
|
const meta = await loadMeta();
|
|
322
|
-
if (!meta.images) {
|
|
323
|
-
meta.images = {};
|
|
324
|
-
}
|
|
325
326
|
let relativeDir = "";
|
|
326
327
|
if (targetPath === "public") {
|
|
327
328
|
relativeDir = "";
|
|
@@ -344,10 +345,10 @@ async function handleUpload(request) {
|
|
|
344
345
|
path: `public/${relativeDir ? relativeDir + "/" : ""}${fileName}`
|
|
345
346
|
});
|
|
346
347
|
}
|
|
347
|
-
const
|
|
348
|
-
if (meta
|
|
348
|
+
const imageKey = "/" + (relativeDir ? `${relativeDir}/${fileName}` : fileName);
|
|
349
|
+
if (meta[imageKey]) {
|
|
349
350
|
return NextResponse.json(
|
|
350
|
-
{ error: `File '${
|
|
351
|
+
{ error: `File '${imageKey}' already exists in meta` },
|
|
351
352
|
{ status: 409 }
|
|
352
353
|
);
|
|
353
354
|
}
|
|
@@ -356,21 +357,10 @@ async function handleUpload(request) {
|
|
|
356
357
|
let originalWidth = 0;
|
|
357
358
|
let originalHeight = 0;
|
|
358
359
|
let blurhash = "";
|
|
359
|
-
let dominantColor = "#888888";
|
|
360
|
-
const sizes = {
|
|
361
|
-
full: { path: "", width: 0, height: 0 },
|
|
362
|
-
large: { path: "", width: 0, height: 0 },
|
|
363
|
-
medium: { path: "", width: 0, height: 0 },
|
|
364
|
-
small: { path: "", width: 0, height: 0 }
|
|
365
|
-
};
|
|
366
360
|
const originalPath = `/${relativeDir ? relativeDir + "/" : ""}${fileName}`;
|
|
367
361
|
if (isSvg) {
|
|
368
362
|
const fullPath = path.join(imagesPath, fileName);
|
|
369
363
|
await fs.writeFile(fullPath, buffer);
|
|
370
|
-
sizes.full = { path: `/images/${relativeDir ? relativeDir + "/" : ""}${fileName}`, width: 0, height: 0 };
|
|
371
|
-
sizes.large = { ...sizes.full };
|
|
372
|
-
sizes.medium = { ...sizes.full };
|
|
373
|
-
sizes.small = { ...sizes.full };
|
|
374
364
|
} else if (isProcessableImage) {
|
|
375
365
|
const sharpInstance = sharp(buffer);
|
|
376
366
|
const metadata = await sharpInstance.metadata();
|
|
@@ -384,11 +374,9 @@ async function handleUpload(request) {
|
|
|
384
374
|
} else {
|
|
385
375
|
await sharp(buffer).jpeg({ quality: 85 }).toFile(fullPath);
|
|
386
376
|
}
|
|
387
|
-
|
|
388
|
-
for (const [sizeName, sizeConfig] of Object.entries(DEFAULT_SIZES)) {
|
|
377
|
+
for (const [, sizeConfig] of Object.entries(DEFAULT_SIZES)) {
|
|
389
378
|
const { width: maxWidth, suffix } = sizeConfig;
|
|
390
379
|
if (originalWidth <= maxWidth) {
|
|
391
|
-
sizes[sizeName] = { ...sizes.full };
|
|
392
380
|
continue;
|
|
393
381
|
}
|
|
394
382
|
const ratio = originalHeight / originalWidth;
|
|
@@ -400,32 +388,18 @@ async function handleUpload(request) {
|
|
|
400
388
|
} else {
|
|
401
389
|
await sharp(buffer).resize(maxWidth, newHeight).jpeg({ quality: 80 }).toFile(sizePath);
|
|
402
390
|
}
|
|
403
|
-
sizes[sizeName] = {
|
|
404
|
-
path: `/images/${relativeDir ? relativeDir + "/" : ""}${sizeFileName}`,
|
|
405
|
-
width: maxWidth,
|
|
406
|
-
height: newHeight
|
|
407
|
-
};
|
|
408
391
|
}
|
|
409
392
|
const { data, info } = await sharp(buffer).resize(32, 32, { fit: "inside" }).ensureAlpha().raw().toBuffer({ resolveWithObject: true });
|
|
410
393
|
blurhash = encode(new Uint8ClampedArray(data), info.width, info.height, 4, 4);
|
|
411
|
-
const { dominant } = await sharp(buffer).stats();
|
|
412
|
-
dominantColor = `#${dominant.r.toString(16).padStart(2, "0")}${dominant.g.toString(16).padStart(2, "0")}${dominant.b.toString(16).padStart(2, "0")}`;
|
|
413
394
|
}
|
|
414
395
|
const entry = {
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
height: originalHeight,
|
|
419
|
-
fileSize: buffer.length
|
|
420
|
-
},
|
|
421
|
-
sizes,
|
|
422
|
-
blurhash,
|
|
423
|
-
dominantColor,
|
|
424
|
-
cdn: null
|
|
396
|
+
w: originalWidth,
|
|
397
|
+
h: originalHeight,
|
|
398
|
+
blur: blurhash
|
|
425
399
|
};
|
|
426
|
-
meta
|
|
400
|
+
meta[originalPath] = entry;
|
|
427
401
|
await saveMeta(meta);
|
|
428
|
-
return NextResponse.json({ success: true, imageKey:
|
|
402
|
+
return NextResponse.json({ success: true, imageKey: originalPath, entry });
|
|
429
403
|
} catch (error) {
|
|
430
404
|
console.error("Failed to upload:", error);
|
|
431
405
|
const message = error instanceof Error ? error.message : "Unknown error";
|
|
@@ -451,27 +425,26 @@ async function handleDelete(request) {
|
|
|
451
425
|
const stats = await fs.stat(absolutePath);
|
|
452
426
|
if (stats.isDirectory()) {
|
|
453
427
|
await fs.rm(absolutePath, { recursive: true });
|
|
454
|
-
const prefix = itemPath.replace(/^public\/images\/?/, "").replace(/^public\/?/, "");
|
|
455
|
-
for (const key of Object.keys(meta
|
|
428
|
+
const prefix = "/" + itemPath.replace(/^public\/images\/?/, "").replace(/^public\/?/, "");
|
|
429
|
+
for (const key of Object.keys(meta)) {
|
|
456
430
|
if (key.startsWith(prefix)) {
|
|
457
|
-
delete meta
|
|
431
|
+
delete meta[key];
|
|
458
432
|
}
|
|
459
433
|
}
|
|
460
434
|
} else {
|
|
461
435
|
await fs.unlink(absolutePath);
|
|
462
436
|
const isInImagesFolder = itemPath.startsWith("public/images/");
|
|
463
437
|
if (!isInImagesFolder) {
|
|
464
|
-
const imageKey = itemPath.replace(/^public\//, "");
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
const sizePath = path.join(process.cwd(), "public", sizeData.path);
|
|
438
|
+
const imageKey = "/" + itemPath.replace(/^public\//, "");
|
|
439
|
+
if (meta[imageKey]) {
|
|
440
|
+
for (const thumbPath of getAllThumbnailPaths(imageKey)) {
|
|
441
|
+
const absoluteThumbPath = path.join(process.cwd(), "public", thumbPath);
|
|
469
442
|
try {
|
|
470
|
-
await fs.unlink(
|
|
443
|
+
await fs.unlink(absoluteThumbPath);
|
|
471
444
|
} catch {
|
|
472
445
|
}
|
|
473
446
|
}
|
|
474
|
-
delete meta
|
|
447
|
+
delete meta[imageKey];
|
|
475
448
|
}
|
|
476
449
|
}
|
|
477
450
|
}
|
|
@@ -518,35 +491,34 @@ async function handleSync(request) {
|
|
|
518
491
|
const synced = [];
|
|
519
492
|
const errors = [];
|
|
520
493
|
for (const imageKey of imageKeys) {
|
|
521
|
-
const entry = meta
|
|
494
|
+
const entry = meta[imageKey];
|
|
522
495
|
if (!entry) {
|
|
523
496
|
errors.push(`Image not found in meta: ${imageKey}`);
|
|
524
497
|
continue;
|
|
525
498
|
}
|
|
526
|
-
if (entry.
|
|
499
|
+
if (entry.s) {
|
|
527
500
|
synced.push(imageKey);
|
|
528
501
|
continue;
|
|
529
502
|
}
|
|
530
503
|
try {
|
|
531
|
-
for (const
|
|
532
|
-
const localPath = path.join(process.cwd(), "public",
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
504
|
+
for (const thumbPath of getAllThumbnailPaths(imageKey)) {
|
|
505
|
+
const localPath = path.join(process.cwd(), "public", thumbPath);
|
|
506
|
+
try {
|
|
507
|
+
const fileBuffer = await fs.readFile(localPath);
|
|
508
|
+
await r2.send(
|
|
509
|
+
new PutObjectCommand({
|
|
510
|
+
Bucket: bucketName,
|
|
511
|
+
Key: thumbPath.replace(/^\//, ""),
|
|
512
|
+
Body: fileBuffer,
|
|
513
|
+
ContentType: getContentType(thumbPath)
|
|
514
|
+
})
|
|
515
|
+
);
|
|
516
|
+
} catch {
|
|
517
|
+
}
|
|
542
518
|
}
|
|
543
|
-
entry.
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
syncedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
547
|
-
};
|
|
548
|
-
for (const sizeData of Object.values(entry.sizes)) {
|
|
549
|
-
const localPath = path.join(process.cwd(), "public", sizeData.path);
|
|
519
|
+
entry.s = 1;
|
|
520
|
+
for (const thumbPath of getAllThumbnailPaths(imageKey)) {
|
|
521
|
+
const localPath = path.join(process.cwd(), "public", thumbPath);
|
|
550
522
|
try {
|
|
551
523
|
await fs.unlink(localPath);
|
|
552
524
|
} catch {
|
|
@@ -581,54 +553,24 @@ async function handleReprocess(request) {
|
|
|
581
553
|
for (const imageKey of imageKeys) {
|
|
582
554
|
try {
|
|
583
555
|
let buffer;
|
|
584
|
-
|
|
556
|
+
const entry = meta[imageKey];
|
|
585
557
|
const originalPath = path.join(process.cwd(), "public", imageKey);
|
|
586
558
|
try {
|
|
587
559
|
buffer = await fs.readFile(originalPath);
|
|
588
560
|
} catch {
|
|
589
|
-
if (entry) {
|
|
590
|
-
|
|
591
|
-
try {
|
|
592
|
-
buffer = await fs.readFile(entryOriginalPath);
|
|
593
|
-
} catch {
|
|
594
|
-
if (entry.cdn?.synced) {
|
|
595
|
-
buffer = await downloadFromCdn(entry.original.path);
|
|
596
|
-
} else {
|
|
597
|
-
throw new Error("Original not found locally and not on CDN");
|
|
598
|
-
}
|
|
599
|
-
}
|
|
561
|
+
if (entry?.s) {
|
|
562
|
+
buffer = await downloadFromCdn(imageKey);
|
|
600
563
|
} else {
|
|
601
564
|
throw new Error(`File not found: ${imageKey}`);
|
|
602
565
|
}
|
|
603
566
|
}
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
original: {
|
|
610
|
-
path: imageKey,
|
|
611
|
-
width: metadata.width || 0,
|
|
612
|
-
height: metadata.height || 0,
|
|
613
|
-
fileSize: stats.size
|
|
614
|
-
},
|
|
615
|
-
sizes: {
|
|
616
|
-
full: { path: "", width: 0, height: 0 },
|
|
617
|
-
large: { path: "", width: 0, height: 0 },
|
|
618
|
-
medium: { path: "", width: 0, height: 0 },
|
|
619
|
-
small: { path: "", width: 0, height: 0 }
|
|
620
|
-
},
|
|
621
|
-
blurhash: "",
|
|
622
|
-
dominantColor: "#000000",
|
|
623
|
-
cdn: null
|
|
624
|
-
};
|
|
625
|
-
}
|
|
626
|
-
const updatedEntry = await processImage(buffer, entry, imageKey);
|
|
627
|
-
meta.images[imageKey] = updatedEntry;
|
|
628
|
-
if (entry.cdn?.synced) {
|
|
629
|
-
await uploadToCdn(updatedEntry);
|
|
630
|
-
await deleteLocalFiles(updatedEntry);
|
|
567
|
+
const updatedEntry = await processImage(buffer, imageKey);
|
|
568
|
+
if (entry?.s) {
|
|
569
|
+
updatedEntry.s = 1;
|
|
570
|
+
await uploadToCdn(imageKey);
|
|
571
|
+
await deleteLocalThumbnails(imageKey);
|
|
631
572
|
}
|
|
573
|
+
meta[imageKey] = updatedEntry;
|
|
632
574
|
processed.push(imageKey);
|
|
633
575
|
} catch (error) {
|
|
634
576
|
console.error(`Failed to reprocess ${imageKey}:`, error);
|
|
@@ -755,6 +697,7 @@ async function handleProcessAllStream() {
|
|
|
755
697
|
sendEvent({ type: "start", total });
|
|
756
698
|
for (let i = 0; i < allImages.length; i++) {
|
|
757
699
|
const { key, fullPath } = allImages[i];
|
|
700
|
+
const imageKey = "/" + key;
|
|
758
701
|
sendEvent({
|
|
759
702
|
type: "progress",
|
|
760
703
|
current: i + 1,
|
|
@@ -773,45 +716,18 @@ async function handleProcessAllStream() {
|
|
|
773
716
|
const fileName = path.basename(key);
|
|
774
717
|
const destPath = path.join(imagesPath, fileName);
|
|
775
718
|
await fs.writeFile(destPath, buffer);
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
width: 0,
|
|
781
|
-
height: 0,
|
|
782
|
-
fileSize: buffer.length
|
|
783
|
-
},
|
|
784
|
-
sizes: {
|
|
785
|
-
full: { path: sizePath, width: 0, height: 0 },
|
|
786
|
-
large: { path: sizePath, width: 0, height: 0 },
|
|
787
|
-
medium: { path: sizePath, width: 0, height: 0 },
|
|
788
|
-
small: { path: sizePath, width: 0, height: 0 }
|
|
789
|
-
},
|
|
790
|
-
blurhash: "",
|
|
791
|
-
dominantColor: "#888888",
|
|
792
|
-
cdn: null
|
|
719
|
+
meta[imageKey] = {
|
|
720
|
+
w: 0,
|
|
721
|
+
h: 0,
|
|
722
|
+
blur: ""
|
|
793
723
|
};
|
|
794
724
|
} else {
|
|
795
|
-
const existingEntry = meta
|
|
796
|
-
const
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
fileSize: buffer.length
|
|
802
|
-
},
|
|
803
|
-
sizes: {
|
|
804
|
-
full: { path: "", width: 0, height: 0 },
|
|
805
|
-
large: { path: "", width: 0, height: 0 },
|
|
806
|
-
medium: { path: "", width: 0, height: 0 },
|
|
807
|
-
small: { path: "", width: 0, height: 0 }
|
|
808
|
-
},
|
|
809
|
-
blurhash: "",
|
|
810
|
-
dominantColor: "#888888",
|
|
811
|
-
cdn: null
|
|
812
|
-
};
|
|
813
|
-
const processedEntry = await processImage(buffer, baseEntry, key);
|
|
814
|
-
meta.images[key] = processedEntry;
|
|
725
|
+
const existingEntry = meta[imageKey];
|
|
726
|
+
const processedEntry = await processImage(buffer, imageKey);
|
|
727
|
+
if (existingEntry?.s) {
|
|
728
|
+
processedEntry.s = 1;
|
|
729
|
+
}
|
|
730
|
+
meta[imageKey] = processedEntry;
|
|
815
731
|
}
|
|
816
732
|
processed.push(key);
|
|
817
733
|
} catch (error) {
|
|
@@ -821,9 +737,9 @@ async function handleProcessAllStream() {
|
|
|
821
737
|
}
|
|
822
738
|
sendEvent({ type: "cleanup", message: "Removing orphaned thumbnails..." });
|
|
823
739
|
const trackedPaths = /* @__PURE__ */ new Set();
|
|
824
|
-
for (const
|
|
825
|
-
for (const
|
|
826
|
-
trackedPaths.add(
|
|
740
|
+
for (const imageKey of Object.keys(meta)) {
|
|
741
|
+
for (const thumbPath of getAllThumbnailPaths(imageKey)) {
|
|
742
|
+
trackedPaths.add(thumbPath);
|
|
827
743
|
}
|
|
828
744
|
}
|
|
829
745
|
async function findOrphans(dir, relativePath = "") {
|
|
@@ -898,57 +814,18 @@ async function handleProcessAllStream() {
|
|
|
898
814
|
}
|
|
899
815
|
async function loadMeta() {
|
|
900
816
|
const metaPath = path.join(process.cwd(), "_data", "_meta.json");
|
|
901
|
-
const emptyMeta = {
|
|
902
|
-
$schema: "https://gallop.software/schemas/studio-meta.json",
|
|
903
|
-
version: 1,
|
|
904
|
-
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
905
|
-
images: {}
|
|
906
|
-
};
|
|
907
817
|
try {
|
|
908
818
|
const content = await fs.readFile(metaPath, "utf-8");
|
|
909
|
-
|
|
910
|
-
if (parsed.images && typeof parsed.images === "object") {
|
|
911
|
-
return parsed;
|
|
912
|
-
}
|
|
913
|
-
const meta = { ...emptyMeta, images: {} };
|
|
914
|
-
for (const [imagePath, entry] of Object.entries(parsed)) {
|
|
915
|
-
const leanEntry = entry;
|
|
916
|
-
const key = imagePath.startsWith("/") ? imagePath.slice(1) : imagePath;
|
|
917
|
-
meta.images[key] = {
|
|
918
|
-
original: {
|
|
919
|
-
path: imagePath,
|
|
920
|
-
width: leanEntry.w,
|
|
921
|
-
height: leanEntry.h,
|
|
922
|
-
fileSize: 0
|
|
923
|
-
},
|
|
924
|
-
sizes: {},
|
|
925
|
-
blurhash: leanEntry.blur,
|
|
926
|
-
dominantColor: "",
|
|
927
|
-
cdn: leanEntry.s ? { synced: true, baseUrl: "", syncedAt: "" } : null
|
|
928
|
-
};
|
|
929
|
-
}
|
|
930
|
-
return meta;
|
|
819
|
+
return JSON.parse(content);
|
|
931
820
|
} catch {
|
|
932
|
-
return
|
|
821
|
+
return {};
|
|
933
822
|
}
|
|
934
823
|
}
|
|
935
824
|
async function saveMeta(meta) {
|
|
936
825
|
const dataDir = path.join(process.cwd(), "_data");
|
|
937
826
|
await fs.mkdir(dataDir, { recursive: true });
|
|
938
|
-
const lean = {};
|
|
939
|
-
for (const [key, entry] of Object.entries(meta.images)) {
|
|
940
|
-
const imagePath = entry.original?.path || `/${key}`;
|
|
941
|
-
lean[imagePath] = {
|
|
942
|
-
w: entry.original?.width || 0,
|
|
943
|
-
h: entry.original?.height || 0,
|
|
944
|
-
blur: entry.blurhash || ""
|
|
945
|
-
};
|
|
946
|
-
if (entry.cdn?.synced) {
|
|
947
|
-
lean[imagePath].s = 1;
|
|
948
|
-
}
|
|
949
|
-
}
|
|
950
827
|
const metaPath = path.join(dataDir, "_meta.json");
|
|
951
|
-
await fs.writeFile(metaPath, JSON.stringify(
|
|
828
|
+
await fs.writeFile(metaPath, JSON.stringify(meta, null, 2));
|
|
952
829
|
}
|
|
953
830
|
function isImageFile(filename) {
|
|
954
831
|
const ext = path.extname(filename).toLowerCase();
|
|
@@ -980,22 +857,17 @@ function getContentType(filePath) {
|
|
|
980
857
|
return "application/octet-stream";
|
|
981
858
|
}
|
|
982
859
|
}
|
|
983
|
-
async function processImage(buffer,
|
|
860
|
+
async function processImage(buffer, imageKey) {
|
|
984
861
|
const sharpInstance = sharp(buffer);
|
|
985
862
|
const metadata = await sharpInstance.metadata();
|
|
986
863
|
const originalWidth = metadata.width || 0;
|
|
987
864
|
const originalHeight = metadata.height || 0;
|
|
988
|
-
const
|
|
989
|
-
const
|
|
990
|
-
const
|
|
865
|
+
const keyWithoutSlash = imageKey.startsWith("/") ? imageKey.slice(1) : imageKey;
|
|
866
|
+
const baseName = path.basename(keyWithoutSlash, path.extname(keyWithoutSlash));
|
|
867
|
+
const ext = path.extname(keyWithoutSlash).toLowerCase();
|
|
868
|
+
const imageDir = path.dirname(keyWithoutSlash);
|
|
991
869
|
const imagesPath = path.join(process.cwd(), "public", "images", imageDir === "." ? "" : imageDir);
|
|
992
870
|
await fs.mkdir(imagesPath, { recursive: true });
|
|
993
|
-
const sizes = {
|
|
994
|
-
full: { path: "", width: originalWidth, height: originalHeight },
|
|
995
|
-
large: { path: "", width: 0, height: 0 },
|
|
996
|
-
medium: { path: "", width: 0, height: 0 },
|
|
997
|
-
small: { path: "", width: 0, height: 0 }
|
|
998
|
-
};
|
|
999
871
|
const isPng = ext === ".png";
|
|
1000
872
|
const outputExt = isPng ? ".png" : ".jpg";
|
|
1001
873
|
const fullFileName = imageDir === "." ? `${baseName}${outputExt}` : `${imageDir}/${baseName}${outputExt}`;
|
|
@@ -1005,11 +877,9 @@ async function processImage(buffer, entry, imageKey) {
|
|
|
1005
877
|
} else {
|
|
1006
878
|
await sharp(buffer).jpeg({ quality: 85 }).toFile(fullPath);
|
|
1007
879
|
}
|
|
1008
|
-
|
|
1009
|
-
for (const [sizeName, sizeConfig] of Object.entries(DEFAULT_SIZES)) {
|
|
880
|
+
for (const [, sizeConfig] of Object.entries(DEFAULT_SIZES)) {
|
|
1010
881
|
const { width: maxWidth, suffix } = sizeConfig;
|
|
1011
882
|
if (originalWidth <= maxWidth) {
|
|
1012
|
-
sizes[sizeName] = { ...sizes.full };
|
|
1013
883
|
continue;
|
|
1014
884
|
}
|
|
1015
885
|
const ratio = originalHeight / originalWidth;
|
|
@@ -1022,27 +892,13 @@ async function processImage(buffer, entry, imageKey) {
|
|
|
1022
892
|
} else {
|
|
1023
893
|
await sharp(buffer).resize(maxWidth, newHeight).jpeg({ quality: 80 }).toFile(sizePath);
|
|
1024
894
|
}
|
|
1025
|
-
sizes[sizeName] = {
|
|
1026
|
-
path: `/images/${sizeFilePath}`,
|
|
1027
|
-
width: maxWidth,
|
|
1028
|
-
height: newHeight
|
|
1029
|
-
};
|
|
1030
895
|
}
|
|
1031
896
|
const { data, info } = await sharp(buffer).resize(32, 32, { fit: "inside" }).ensureAlpha().raw().toBuffer({ resolveWithObject: true });
|
|
1032
897
|
const blurhash = encode(new Uint8ClampedArray(data), info.width, info.height, 4, 4);
|
|
1033
|
-
const { dominant } = await sharp(buffer).stats();
|
|
1034
|
-
const dominantColor = `#${dominant.r.toString(16).padStart(2, "0")}${dominant.g.toString(16).padStart(2, "0")}${dominant.b.toString(16).padStart(2, "0")}`;
|
|
1035
898
|
return {
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
width: originalWidth,
|
|
1040
|
-
height: originalHeight,
|
|
1041
|
-
fileSize: buffer.length
|
|
1042
|
-
},
|
|
1043
|
-
sizes,
|
|
1044
|
-
blurhash,
|
|
1045
|
-
dominantColor
|
|
899
|
+
w: originalWidth,
|
|
900
|
+
h: originalHeight,
|
|
901
|
+
blur: blurhash
|
|
1046
902
|
};
|
|
1047
903
|
}
|
|
1048
904
|
async function downloadFromCdn(originalPath) {
|
|
@@ -1071,7 +927,7 @@ async function downloadFromCdn(originalPath) {
|
|
|
1071
927
|
}
|
|
1072
928
|
return Buffer.concat(chunks);
|
|
1073
929
|
}
|
|
1074
|
-
async function uploadToCdn(
|
|
930
|
+
async function uploadToCdn(imageKey) {
|
|
1075
931
|
const accountId = process.env.CLOUDFLARE_R2_ACCOUNT_ID;
|
|
1076
932
|
const accessKeyId = process.env.CLOUDFLARE_R2_ACCESS_KEY_ID;
|
|
1077
933
|
const secretAccessKey = process.env.CLOUDFLARE_R2_SECRET_ACCESS_KEY;
|
|
@@ -1084,22 +940,25 @@ async function uploadToCdn(entry) {
|
|
|
1084
940
|
endpoint: `https://${accountId}.r2.cloudflarestorage.com`,
|
|
1085
941
|
credentials: { accessKeyId, secretAccessKey }
|
|
1086
942
|
});
|
|
1087
|
-
for (const
|
|
1088
|
-
const localPath = path.join(process.cwd(), "public",
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
943
|
+
for (const thumbPath of getAllThumbnailPaths(imageKey)) {
|
|
944
|
+
const localPath = path.join(process.cwd(), "public", thumbPath);
|
|
945
|
+
try {
|
|
946
|
+
const fileBuffer = await fs.readFile(localPath);
|
|
947
|
+
await r2.send(
|
|
948
|
+
new PutObjectCommand({
|
|
949
|
+
Bucket: bucketName,
|
|
950
|
+
Key: thumbPath.replace(/^\//, ""),
|
|
951
|
+
Body: fileBuffer,
|
|
952
|
+
ContentType: getContentType(thumbPath)
|
|
953
|
+
})
|
|
954
|
+
);
|
|
955
|
+
} catch {
|
|
956
|
+
}
|
|
1098
957
|
}
|
|
1099
958
|
}
|
|
1100
|
-
async function
|
|
1101
|
-
for (const
|
|
1102
|
-
const localPath = path.join(process.cwd(), "public",
|
|
959
|
+
async function deleteLocalThumbnails(imageKey) {
|
|
960
|
+
for (const thumbPath of getAllThumbnailPaths(imageKey)) {
|
|
961
|
+
const localPath = path.join(process.cwd(), "public", thumbPath);
|
|
1103
962
|
try {
|
|
1104
963
|
await fs.unlink(localPath);
|
|
1105
964
|
} catch {
|
|
@@ -1168,32 +1027,23 @@ async function handleRename(request) {
|
|
|
1168
1027
|
const meta = await loadMeta();
|
|
1169
1028
|
const oldRelativePath = safePath.replace(/^public\//, "");
|
|
1170
1029
|
const newRelativePath = path.join(path.dirname(oldRelativePath), sanitizedName);
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
const
|
|
1179
|
-
const
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
const oldThumbPath = path.join(thumbnailDir, oldThumbName);
|
|
1185
|
-
const newThumbPath = path.join(thumbnailDir, newThumbName);
|
|
1186
|
-
try {
|
|
1187
|
-
await fs.rename(oldThumbPath, newThumbPath);
|
|
1188
|
-
sizeData.path = `/images/${oldDirRelative}/${newThumbName}`.replace(/\/+/g, "/");
|
|
1189
|
-
} catch {
|
|
1190
|
-
}
|
|
1030
|
+
const oldKey = "/" + oldRelativePath;
|
|
1031
|
+
const newKey = "/" + newRelativePath;
|
|
1032
|
+
if (meta[oldKey]) {
|
|
1033
|
+
const entry = meta[oldKey];
|
|
1034
|
+
const oldThumbPaths = getAllThumbnailPaths(oldKey);
|
|
1035
|
+
const newThumbPaths = getAllThumbnailPaths(newKey);
|
|
1036
|
+
for (let i = 0; i < oldThumbPaths.length; i++) {
|
|
1037
|
+
const oldThumbPath = path.join(process.cwd(), "public", oldThumbPaths[i]);
|
|
1038
|
+
const newThumbPath = path.join(process.cwd(), "public", newThumbPaths[i]);
|
|
1039
|
+
await fs.mkdir(path.dirname(newThumbPath), { recursive: true });
|
|
1040
|
+
try {
|
|
1041
|
+
await fs.rename(oldThumbPath, newThumbPath);
|
|
1042
|
+
} catch {
|
|
1191
1043
|
}
|
|
1192
|
-
const newKey = `/${newRelativePath}`;
|
|
1193
|
-
delete meta.images[key];
|
|
1194
|
-
meta.images[newKey] = entry;
|
|
1195
|
-
break;
|
|
1196
1044
|
}
|
|
1045
|
+
delete meta[oldKey];
|
|
1046
|
+
meta[newKey] = entry;
|
|
1197
1047
|
}
|
|
1198
1048
|
await saveMeta(meta);
|
|
1199
1049
|
}
|
|
@@ -1260,33 +1110,24 @@ async function handleMove(request) {
|
|
|
1260
1110
|
if (stats.isFile() && isImageFile(itemName)) {
|
|
1261
1111
|
const oldRelativePath = safePath.replace(/^public\//, "");
|
|
1262
1112
|
const newRelativePath = path.join(safeDestination.replace(/^public\//, ""), itemName);
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
const
|
|
1271
|
-
const
|
|
1272
|
-
await fs.mkdir(
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
const oldThumbPath = path.join(oldThumbDir, thumbName);
|
|
1277
|
-
const newThumbPath = path.join(newThumbDir, thumbName);
|
|
1278
|
-
try {
|
|
1279
|
-
await fs.rename(oldThumbPath, newThumbPath);
|
|
1280
|
-
sizeData.path = `/images/${newDir}/${thumbName}`.replace(/\/+/g, "/");
|
|
1281
|
-
} catch {
|
|
1282
|
-
}
|
|
1113
|
+
const oldKey = "/" + oldRelativePath;
|
|
1114
|
+
const newKey = "/" + newRelativePath;
|
|
1115
|
+
if (meta[oldKey]) {
|
|
1116
|
+
const entry = meta[oldKey];
|
|
1117
|
+
const oldThumbPaths = getAllThumbnailPaths(oldKey);
|
|
1118
|
+
const newThumbPaths = getAllThumbnailPaths(newKey);
|
|
1119
|
+
for (let i = 0; i < oldThumbPaths.length; i++) {
|
|
1120
|
+
const oldThumbPath = path.join(process.cwd(), "public", oldThumbPaths[i]);
|
|
1121
|
+
const newThumbPath = path.join(process.cwd(), "public", newThumbPaths[i]);
|
|
1122
|
+
await fs.mkdir(path.dirname(newThumbPath), { recursive: true });
|
|
1123
|
+
try {
|
|
1124
|
+
await fs.rename(oldThumbPath, newThumbPath);
|
|
1125
|
+
} catch {
|
|
1283
1126
|
}
|
|
1284
|
-
const newKey = `/${newRelativePath}`;
|
|
1285
|
-
delete meta.images[key];
|
|
1286
|
-
meta.images[newKey] = entry;
|
|
1287
|
-
metaChanged = true;
|
|
1288
|
-
break;
|
|
1289
1127
|
}
|
|
1128
|
+
delete meta[oldKey];
|
|
1129
|
+
meta[newKey] = entry;
|
|
1130
|
+
metaChanged = true;
|
|
1290
1131
|
}
|
|
1291
1132
|
}
|
|
1292
1133
|
moved.push(itemPath);
|